XSLT进行XSD转换:如何根据依赖关系对条目进行重新排序
我正在尝试使用XSLT将XSD方案(转换为C ++类)。 我现在遇到的问题是,我想根据依赖项重新排序complexType条目。 也就是说,如果一个complexType Type1包含一个complexType Type2属性,我希望Type2出现在输出之前的Type1中(因为明显的原因 - 如果Type2是在Type1之后定义的,则在C ++头中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
我与之奋斗的是:
<xsl:if test="//xs:complexType[name = @type]"
但不起作用的东西) 事实上,最简单的方法是重新订购XSD本身,不幸的是XSD不在我的控制之下,所以很难管理它。
注意:我知道有些工具可以将XSD转换为C ++,比如Code Synthesis xsdcxx,但我更喜欢使用XSLT,因为大多数工具都将依赖关系带入其他库,比如Xerces,我既不需要也不需要(除了工具本身的授权问题)。 而且在将来我还想使用类似的XSLT转换为不同的输出类型(例如Thrift消息文件)。
//编辑
我只是通过使用以下方法来管理点3):
<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-to-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转换。
(它仍然不能解决循环依赖问题,但是现在对我来说很好,因为我正在处理的文件中没有循环依赖关系)
链接地址: http://www.djcxy.com/p/5863.html上一篇: XSD transform by XSLT: How to reorder entries according to the dependencies