我正在尝试使用XSLT转换XSD模式(到c++类)。我目前遇到的问题是,我想根据依赖项重新排序complexType条目。例如,如果一个complexType Type1包含一个complexType Type2的属性,我希望Type2在输出中出现在Type1之前(原因很明显——在c++头文件中,如果Type2在Type1之后定义,则Type2不能用于声明Type1的属性)。
示例输入XSD文件:
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:complexType name="Type1">
<xs:sequence>
<xs:element name="id" type="xs:short" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="Type2">
<xs:sequence>
<xs:element name="id" type="xs:short" />
<xs:element name="value" type="Type3" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="Type3">
<xs:sequence>
<xs:element name="id" type="xs:short" />
<xs:element name="value" type="Type4" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="Type4">
<xs:sequence>
<xs:element name="id" type="xs:short" />
</xs:sequence>
</xs:complexType>
</xs:schema>
到目前为止,我管理的是这个XSLT(这是一个简化的示例,只是生成类型名称排序,而不是实际的类):
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output method="text" encoding="utf-8" indent="no"/>
<xsl:strip-space elements="*"/>
<xsl:template name="gen-type-list">
<xsl:param name="name"/>
<xsl:value-of select="concat('[', $name, ']')"/>
</xsl:template>
<!--
Generate the type order according to dependencies
Dependency should come before the dependent type.
-->
<xsl:template name="gen-type-order">
<xsl:param name="name"/>
<xsl:param name="typeList"/>
<xsl:for-each select="xs:attribute | xs:complexContent/xs:extension//xs:attribute | xs:sequence/xs:element | xs:complexContent/xs:extension/xs:sequence/xs:element">
<xsl:variable name="typeEntry">
<xsl:value-of select="concat('[', @type, ']')"/>
</xsl:variable>
<xsl:if test="contains($typeList, $typeEntry)">
<xsl:value-of select="$typeEntry"/>
</xsl:if>
</xsl:for-each>
<xsl:value-of select="concat('[', $name, ']')"/>
</xsl:template>
<!--
Print the ordered listing (line by line)
-->
<xsl:template name="print-type-order">
<xsl:param name="typeList"/>
<xsl:choose>
<xsl:when test="not(contains($typeList, ']['))">
<xsl:value-of select="substring-after(substring-before($typeList, ']'), '[')"/>
<xsl:text>
</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="substring-after(substring-before($typeList, ']'), '[')"/>
<xsl:text>
</xsl:text>
<xsl:call-template name="print-type-order">
<xsl:with-param name="typeList" select="substring-after($typeList, ']')"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!--
The main processing template handling the entire document
-->
<xsl:template match="/xs:schema">
<xsl:variable name="typeList">
<xsl:for-each select="xs:complexType">
<xsl:call-template name="gen-type-list">
<xsl:with-param name="name" select="@name"/>
</xsl:call-template>
</xsl:for-each>
</xsl:variable>
<xsl:value-of select="$typeList"/>
<xsl:text>
</xsl:text>
<xsl:variable name="typeOrder">
<xsl:for-each select="xs:complexType">
<xsl:call-template name="gen-type-order">
<xsl:with-param name="name" select="@name"/>
<xsl:with-param name="typeList" select="$typeList"/>
</xsl:call-template>
</xsl:for-each>
</xsl:variable>
<xsl:call-template name="print-type-order">
<xsl:with-param name="typeList" select="$typeOrder"/>
</xsl:call-template>
</xsl:template>
</xsl:stylesheet>
在示例XSD上运行的输出如下:
[Type1][Type2][Type3][Type4]
Type1
Type3
Type2
Type4
Type3
Type4
第一行只是所有类型的简单列表(出于调试目的,它用于实际检查gen-type-order
模板中的类型是否为复杂类型)。这已经非常接近我想要的了,应该是:
Type1
Type4
Type3
Type2
让我纠结的是:
- 如何检查类型是否已经在列表中(不要重复Type3,因为它已经被添加为type2的依赖项)
- 如何递归地调用它(以便依赖项Type4将在Type3之前添加)
- 是否有可能在没有初始创建complexType列表(gen-type-list)的情况下做到这一点,即检查类型名称是否直接是complexType ?(我最初尝试过像
<xsl:if test="//xs:complexType[name = @type]"
这样的东西,但没有工作) - 是否有更好/更少冗长的方法来做到这一点?(即根据依赖项按正确顺序处理项目)
事实上,最简单的方法是重新排序XSD本身,不幸的是XSD不在我的控制之下,所以很难管理。
注意:我知道有一些工具可以将XSD转换为c++,比如Code Synthesis xsdcxx,但我更喜欢使用XSLT,因为大多数工具会对其他库(如Xerces)产生依赖,这是我既不需要也不想要的(除了工具本身的许可问题)。而且在将来,我还想使用类似的XSLT来转换为不同的输出类型(例如Thrift消息文件)。
//编辑
我用了下面的句子来强调这一点:
<xsl:template name="gen-type-order">
<xsl:param name="name"/>
<xsl:for-each select="xs:attribute | xs:complexContent/xs:extension//xs:attribute | xs:sequence/xs:element | xs:complexContent/xs:extension/xs:sequence/xs:element">
<xsl:variable name="typeEntry">
<xsl:value-of select="concat('[', @type, ']')"/>
</xsl:variable>
<xsl:variable name="typeName">
<xsl:value-of select="@type"/>
</xsl:variable>
<xsl:if test="//xs:complexType[@name = $typeName]">
<xsl:value-of select="$typeEntry"/>
</xsl:if>
</xsl:for-each>
<xsl:value-of select="concat('[', $name, ']')"/>
</xsl:template>
在XSLT 2.0中,将事物按正确的顺序排列并不难。
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output method="text" encoding="utf-8" indent="no"/>
<xsl:strip-space elements="*"/>
<xsl:key name="type" match="xs:complexType" use="@name"/>
<!-- Returns value 1 if there are no dependencies, 2 if there's 1 etc. -->
<xsl:template match="*" mode="get-dependency-depth">
<xsl:param name="depthSummands" as="xs:integer*">
<xsl:apply-templates mode="get-dependency-depth"
select="key('type', (
xs:attribute | xs:complexContent/xs:extension//xs:attribute |
xs:sequence/xs:element | xs:complexContent/xs:extension/xs:sequence/xs:element
)/@type)"/>
</xsl:param>
<xsl:copy-of select="sum($depthSummands) + 1"/>
</xsl:template>
<xsl:template match="*" mode="process-types">
<xsl:value-of select="concat(@name, ' (dependency depth: ')"/>
<xsl:apply-templates mode="get-dependency-depth" select="."/>
<xsl:value-of select="') '"/>
</xsl:template>
<xsl:template match="/xs:schema">
<xsl:apply-templates select="xs:complexType" mode="process-types">
<xsl:sort data-type="number">
<xsl:apply-templates mode="get-dependency-depth" select="."/>
</xsl:sort>
</xsl:apply-templates>
</xsl:template>
</xsl:stylesheet>
应用于输入XML时的输出:
Type1 (dependency depth: 1)
Type4 (dependency depth: 1)
Type3 (dependency depth: 2)
Type2 (dependency depth: 3)
这既不测试循环依赖,也不是对大型依赖树特别有效——对于这些依赖树,有一个类似于依赖深度缓存的东西会很有用。
接受@Thomas的答案,虽然我没有按原来的样子使用它,但它给了我非常有用的提示,特别是如何处理依赖关系的想法。
最后,我使用了两步处理,首先根据依赖项重新排序XSD complexType元素,然后将XML结果传递给一个额外的XSLT调用,在那里完成XSD到c++的最终转换。
我设法用XSLT 1.0 (Xalan)处理文件,并使用EXSLT扩展(将存储在变量中的XML转换为节点集)。基本上,模板按原样复制<xs:complexType>
条目以外的所有条目,然后处理<xs:complexType>
重新排序:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:ext="http://exslt.org/common">
<xsl:output method="xml" encoding="utf-8" indent="yes"/>
<xsl:param name="debug" select="no"/>
<xsl:strip-space elements="*"/>
<xsl:key name="type" match="xs:complexType" use="@name"/>
<!--
Retrieve the dependency levels of a <xs:complexType> as sequence of
synthesized <level> elements.
-->
<xsl:template match="*" mode="get-dependency-levels">
<!-- the <xs:complexType> @name the dependencies are retrieved for -->
<xsl:param name="type"/>
<xsl:param name="level" select="1"/>
<!-- apply recursively on each dependent type -->
<xsl:apply-templates mode="get-dependency-levels"
select="key('type', (
xs:attribute | xs:complexContent/xs:extension//xs:attribute |
xs:sequence/xs:element | xs:complexContent/xs:extension/xs:sequence/xs:element
)/@type)">
<!-- using the original type (dependency levels for that type) -->
<xsl:with-param name="type" select="$type"/>
<xsl:with-param name="level" select="$level+1"/>
</xsl:apply-templates>
<!-- create the <level> element in the output /variable/ -->
<xsl:element name="level">
<xsl:attribute name="type"><xsl:value-of select="$type"/></xsl:attribute>
<xsl:value-of select="$level"/>
</xsl:element>
</xsl:template>
<!--
Min/Max helper template: Retrieve just the first item of a sequence.
-->
<xsl:template match="*" mode="get-first">
<xsl:if test="position()=1">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:template>
<!--
Retrieve the maximal dependency level of the input <xs:complexType>
as the <level> element.
-->
<xsl:template match="*" mode="get-max-dependency-level">
<!-- retrieve all the dependency levels for a single <xs:complexType> into variable -->
<xsl:variable name="levels">
<xsl:apply-templates mode="get-dependency-levels" select=".">
<!-- pass the type name -->
<xsl:with-param name="type" select="@name"/>
</xsl:apply-templates>
</xsl:variable>
<!-- sort descending according to the dependency level and retrieve the first item
- the maximal dependency level of the <xs:complexType> -->
<xsl:apply-templates mode="get-first" select="ext:node-set($levels)/level">
<xsl:sort data-type="number" order="descending"/>
</xsl:apply-templates>
</xsl:template>
<!--
Print the <xs:complexType> according to the input <level> element.
-->
<xsl:template match="*" mode="print-type">
<xsl:param name="main-doc"/>
<xsl:param name="debug"/>
<xsl:if test="$debug='yes'">
<xsl:copy-of select="."/>
</xsl:if>
<!-- the @type of of the <level> element -->
<xsl:variable name="type" select="@type"/>
<!-- copy the <xs:complexType name="$type"> of the main document -->
<xsl:copy-of select="$main-doc//xs:complexType[@name=$type]"/>
</xsl:template>
<!--
Copy the input node unchanged.
-->
<xsl:template match="*" mode="copy-node">
<xsl:copy-of select="."/>
</xsl:template>
<!--
The main processing template handling the entire document.
-->
<xsl:template match="/xs:schema">
<!-- copy the root element attributes -->
<xsl:element name="{name()}">
<!-- copy all nodes except 'xs:complexType' -->
<xsl:apply-templates mode="copy-node" select="*[not(name()='xs:complexType')]"/>
<!-- retrieve the max. dependency levels for each <xs:complexType> -->
<xsl:variable name="levels">
<xsl:apply-templates select="xs:complexType" mode="get-max-dependency-level"/>
</xsl:variable>
<!-- print the complex types sorted by the dependency levels -->
<xsl:apply-templates select="ext:node-set($levels)/level" mode="print-type">
<xsl:sort data-type="number"/>
<!-- pass the main document to access it in the scope of the $levels synthesized node set -->
<xsl:with-param name="main-doc" select="/"/>
<xsl:with-param name="debug" select="$debug"/>
</xsl:apply-templates>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
当在问题中的示例上运行时,它产生结果:
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:complexType name="Type1">
<xs:sequence>
<xs:element name="id" type="xs:short"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="Type4">
<xs:sequence>
<xs:element name="id" type="xs:short"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="Type3">
<xs:sequence>
<xs:element name="id" type="xs:short"/>
<xs:element name="value" type="Type4"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="Type2">
<xs:sequence>
<xs:element name="id" type="xs:short"/>
<xs:element name="value" type="Type3"/>
</xs:sequence>
</xs:complexType>
</xs:schema>
这是正确的顺序,然后可以通过管道进行最终的XSLT转换。
(它仍然没有解决循环依赖关系,但这对我来说很好,因为我正在处理的文件中没有循环依赖关系)