Reputation: 3
in contrast to the answered situation of equal named child elements I'm trying to convert the following:
<Person>
<Address>5</Address>
<Firstname>1234567890</Firstname>
<Lastname>
<MaidenName>The BFG</MaidenName>
<StageName>GFB eht</StageName>
</Lastname>
</Person>
into the wanted result:
<Person>
<Firstname>1234567890</Firstname>
<Lastname>
<StageName>GFB eht</StageName>
<MaidenName>The BFG</MaidenName>
</Lastname>
<Address>5</Address>
</Person>
But keep getting the following error:
Error at xsl:element on line 47 of xml2xml.xsl:
XTDE0820: Supplied element name is a zero-length string
How can the elements passed and inserted in the correct order?
the wanted result is complying to the provided XML schema:
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
<xs:element name="Person" type="Person"/>
<xs:complexType name="Person">
<xs:sequence>
<xs:element name="Firstname" type="xs:string"/>
<xs:element name="Lastname" type="Lastname"/>
<xs:element name="Address" type="xs:string"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="Lastname">
<xs:sequence>
<xs:element name="StageName" type="xs:string"/>
<xs:element name="MaidenName" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:schema>
I'm using this transformation, which I got from Can you transform unordered xml to match an xsd:sequence order? xsdsequence-order and tried to adapt:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kxsElemByName" match="xs:complexType" use="@name"/>
<xsl:variable name="vSchema" select=
"document('file:///D:/xslt/test/schema.xsd')"/>
<xsl:variable name="vDoc" select="/"/>
<xsl:template match="/*">
<xsl:variable name="vElem" select="."/>
<xsl:for-each select="$vSchema">
<xsl:apply-templates select=
"key('kxsElemByName', name($vElem))">
<xsl:with-param name="pElement" select="$vElem"/>
</xsl:apply-templates>
</xsl:for-each>
</xsl:template>
<xsl:template match="xs:complexType">
<xsl:param name="pElement"/>
<xsl:element name="{name($pElement)}">
<xsl:apply-templates mode="generate"
select="xs:sequence/*">
<xsl:with-param name="pParent" select="$pElement"/>
</xsl:apply-templates>
</xsl:element>
</xsl:template>
<xsl:template match="xs:element" mode="generate">
<xsl:param name="pParent"/>
<xsl:variable name="vProp" select=
"$pParent/*[local-name(.) = local-name(current())]/*"/>
<xsl:element name="{local-name($vProp)}">
<xsl:value-of select="$vProp"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Upvotes: 0
Views: 1322
Reputation: 70598
Assuming you just wanted to adjust the ordering based on the XSD, try the XSLT below. For each element in the source XML, it checks to find an xs:element
in the XSD, and for that xs:element
whether is a complex type. If so, it then orders the child elements accordingly.
This is still very fragile, as per Michael. Kay's answer, although it no longer relies on the name of the complex type matching the name of the element.
<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="xml" indent="yes" />
<xsl:key name="kxsElemByName" match="xs:element" use="@name"/>
<xsl:key name="kxsTypeByName" match="xs:complexType" use="@name"/>
<xsl:variable name="vSchema" select="document('file:///D:/xslt/test/schema.xsd')"/>
<xsl:template match="@*|node()" name="identity">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*" priority="2">
<xsl:variable name="current" select="." />
<xsl:copy>
<xsl:apply-templates select="@*"/>
<xsl:for-each select="$vSchema">
<xsl:variable name="element" select="key('kxsElemByName', name($current))" />
<xsl:variable name="complex" select="key('kxsTypeByName', $element/@type)" />
<xsl:choose>
<xsl:when test="$complex">
<xsl:for-each select="$complex/xs:sequence/xs:element">
<xsl:apply-templates select="$current/*[name() = current()/@name]" />
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="$current/node()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Upvotes: 0
Reputation: 163262
I think this is fundamentally misguided. Either you want a general solution that works with any schema, or you want a solution that's oriented to one particular schema. At the moment, you're trying to extract the structure from the schema, but you've made so many assumptions about the way the schema is written that your solution is hopelessly fragile (and I'm not even going to try to find the particular bug). You've assumed, for example, that:
These assumptions are so constraining that you really might as well hard-code the rules in the XSLT code rather than trying to extract them from the schema.
If you want to do this properly, don't try to work from the source schema document, work instead from the output of a schema compiler - for example the SCM files produced by the Saxon schema processor, or the schema information that can be accessed in XSLT using the saxon:schema() extension function.
Upvotes: 2
Reputation: 29022
I'm not sure that this is really what you are expecting, but anyway - creating the desired output structure would be achieved this way:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:template match="Person">
<xsl:element name="Person">
<xsl:copy-of select="Firstname" />
<xsl:copy-of select="Lastname" />
<xsl:copy-of select="Address" />
</xsl:element>
</xsl:template>
</xsl:stylesheet>
The ordering is achieved by reconstructing the required elements and copying all subnodes.
Upvotes: 1