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>&#x0a;</xsl:text>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="substring-after(substring-before($typeList, ']'), '[')"/>
                <xsl:text>&#x0a;</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>&#x0a;</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,因为它已经被添加为类型2的依赖项)
  • 如何递归地调用它(以便在Type3之前添加依赖项Type4)
  • 如果没有初始创建complexType列表(gen-type-list),是否可以这样做,即直接检查类型名称是否为complexType? (我最初尝试使用类似于<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="')&#10;'"/>
      </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

    下一篇: XSD Class generators: keeping track of element order