XSD transform by XSLT: How to reorder entries according to the dependencies
I'm trying to use XSLT to convert XSD scheme (to C++ classes). The problem I currently have is that I'd would like to reorder the complexType entries according to the dependencies. Ie if a complexType Type1 contains an attribute of complexType Type2, I'd like the Type2 appear before Type1 in the output (for obvious reasons - in C++ header the Type2 cannot be used to declare attribute in Type1 if the Type2 is defined after Type1).
Example input XSD file:
<?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>
What I managed so far is this XSLT (this is a simplified example just generating the type name ordering, not the actual classes):
<?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>
The output of that run on the example XSD is the following:
[Type1][Type2][Type3][Type4]
Type1
Type3
Type2
Type4
Type3
Type4
The first line is just the simple list of all types (for debugging purposes, and it is used to actually check if the type is a complex type in the gen-type-order
template). This is already quite close to what I want, which should be:
Type1
Type4
Type3
Type2
What I struggle with is:
<xsl:if test="//xs:complexType[name = @type]"
but that did not work) Indeed the easiest way would be to re-order the XSD itself, unfortunately the XSD is not under my control so it would be difficult to manage that.
Note: I'm aware that there are tools for converting XSD to C++, like Code Synthesis xsdcxx, but I'd prefer to use XSLT, because most of the tools bring dependencies to other libraries like Xerces, which I neither need nor want (besides licensing issues of the tools themselves). And also in the future I'd like to use similar XSLT to convert to different output types (eg Thrift message file).
// EDIT
I just managed the point 3) by using the following:
<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>
But the other points still remain.
In XSLT 2.0, it's not as hard to get things in the right order.
<?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>
Output when applied to your input XML:
Type1 (dependency depth: 1)
Type4 (dependency depth: 1)
Type3 (dependency depth: 2)
Type2 (dependency depth: 3)
This does neither test for circular dependencies nor is it particularly efficient for large dependency trees - for those it would be useful to have something like a cache for dependency depths.
Accepted the @Thomas answer, although I didn't use it as-is, but it gave me very useful hints, and especially the idea how to process the dependencies.
At the end I'm using two-step processing, first re-ordering the XSD complexType elements according to the dependencies, and piping that XML result to an additional XSLT call where the final XSD-to-c++ conversion is done.
I managed to process the file with XSLT 1.0 (Xalan), and using the EXSLT extension (to turn XML stored in a variable to a node set). Basically, the template copies everything except <xs:complexType>
entries as is, and then processes the <xs:complexType>
reordering:
<?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>
When run on the example from the question, it produces the result:
<?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>
Which is the correct ordering and can then be piped to the final XSLT conversion.
(It still doesn't solve circular dependencies, however that is fine for me at the moment, as there are no circular dependencies in the files I'm handling)
链接地址: http://www.djcxy.com/p/5864.html