通过XSLT进行XSD转换:如何根据依赖项重新排序条目



我正在尝试使用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>&#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

让我纠结的是:

  1. 如何检查类型是否已经在列表中(不要重复Type3,因为它已经被添加为type2的依赖项)
  2. 如何递归地调用它(以便依赖项Type4将在Type3之前添加)
  3. 是否有可能在没有初始创建complexType列表(gen-type-list)的情况下做到这一点,即检查类型名称是否直接是complexType ?(我最初尝试过像<xsl:if test="//xs:complexType[name = @type]"这样的东西,但没有工作)
  4. 是否有更好/更少冗长的方法来做到这一点?(即根据依赖项按正确顺序处理项目)

事实上,最简单的方法是重新排序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="')&#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到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转换。

(它仍然没有解决循环依赖关系,但这对我来说很好,因为我正在处理的文件中没有循环依赖关系)

相关内容

  • 没有找到相关文章

最新更新