如何转换 xml 中的无序元素以匹配 xsd:sequence 顺序?
How to transform unordered elements in xml to match an xsd:sequence order?
对比the answered situation of equal named child elements
我正在尝试转换以下内容:
<Person>
<Address>5</Address>
<Firstname>1234567890</Firstname>
<Lastname>
<MaidenName>The BFG</MaidenName>
<StageName>GFB eht</StageName>
</Lastname>
</Person>
进入想要的结果:
<Person>
<Firstname>1234567890</Firstname>
<Lastname>
<StageName>GFB eht</StageName>
<MaidenName>The BFG</MaidenName>
</Lastname>
<Address>5</Address>
</Person>
但不断收到以下错误:
xml2xml.xsl 第 47 行 xsl:element 处的错误:
XTDE0820:提供的元素名称是零长度字符串
元素如何按正确的顺序传递和插入?
想要的结果符合提供的 XML 模式:
<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>
我正在使用我从 Can you transform unordered xml to match an xsd:sequence order? 获得的这个转换
xsdsequence-order 并试图适应:
<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>
我不确定这是否真的是您所期望的,但无论如何 - 创建所需的输出结构将通过这种方式实现:
<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>
排序是通过重构所需元素并复制所有子节点来实现的。
我认为这从根本上是错误的。您要么想要一个适用于任何模式的通用解决方案,要么想要一个面向特定模式的解决方案。目前,您正在尝试从模式中提取结构,但是您对模式的编写方式做出了太多假设,以至于您的解决方案非常脆弱(我什至不打算尝试找到特别的错误)。例如,您假设:
- 复杂类型的名称与元素的名称匹配
- 架构不使用 include 或 import
- 复杂类型包含一个xs:sequence,而xs:sequence仅包含元素粒子
- 序列中的所有元素恰好出现一次
- 元素粒子是局部元素声明而不是对全局元素声明的引用
这些假设的限制如此之大,以至于您实际上还不如在 XSLT 代码中对规则进行硬编码,而不是尝试从架构中提取它们。
如果您想正确执行此操作,请不要尝试从源模式文档开始工作,而是从模式编译器的输出开始工作——例如 Saxon 模式处理器生成的 SCM 文件,或模式可以使用 saxon:schema() 扩展函数在 XSLT 中访问的信息。
假设您只是想根据 XSD 调整顺序,试试下面的 XSLT。对于源 XML 中的每个元素,它检查在 XSD 中找到 xs:element
,并检查 xs:element
是否是复杂类型。如果是这样,它会相应地对子元素进行排序。
按照 Michael 的说法,这仍然非常脆弱。 Kay 的回答,虽然它不再依赖与元素名称匹配的复杂类型的名称。
<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>
对比the answered situation of equal named child elements 我正在尝试转换以下内容:
<Person>
<Address>5</Address>
<Firstname>1234567890</Firstname>
<Lastname>
<MaidenName>The BFG</MaidenName>
<StageName>GFB eht</StageName>
</Lastname>
</Person>
进入想要的结果:
<Person>
<Firstname>1234567890</Firstname>
<Lastname>
<StageName>GFB eht</StageName>
<MaidenName>The BFG</MaidenName>
</Lastname>
<Address>5</Address>
</Person>
但不断收到以下错误:
xml2xml.xsl 第 47 行 xsl:element 处的错误:
XTDE0820:提供的元素名称是零长度字符串
元素如何按正确的顺序传递和插入?
想要的结果符合提供的 XML 模式:
<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>
我正在使用我从 Can you transform unordered xml to match an xsd:sequence order? 获得的这个转换 xsdsequence-order 并试图适应:
<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>
我不确定这是否真的是您所期望的,但无论如何 - 创建所需的输出结构将通过这种方式实现:
<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>
排序是通过重构所需元素并复制所有子节点来实现的。
我认为这从根本上是错误的。您要么想要一个适用于任何模式的通用解决方案,要么想要一个面向特定模式的解决方案。目前,您正在尝试从模式中提取结构,但是您对模式的编写方式做出了太多假设,以至于您的解决方案非常脆弱(我什至不打算尝试找到特别的错误)。例如,您假设:
- 复杂类型的名称与元素的名称匹配
- 架构不使用 include 或 import
- 复杂类型包含一个xs:sequence,而xs:sequence仅包含元素粒子
- 序列中的所有元素恰好出现一次
- 元素粒子是局部元素声明而不是对全局元素声明的引用
这些假设的限制如此之大,以至于您实际上还不如在 XSLT 代码中对规则进行硬编码,而不是尝试从架构中提取它们。
如果您想正确执行此操作,请不要尝试从源模式文档开始工作,而是从模式编译器的输出开始工作——例如 Saxon 模式处理器生成的 SCM 文件,或模式可以使用 saxon:schema() 扩展函数在 XSLT 中访问的信息。
假设您只是想根据 XSD 调整顺序,试试下面的 XSLT。对于源 XML 中的每个元素,它检查在 XSD 中找到 xs:element
,并检查 xs:element
是否是复杂类型。如果是这样,它会相应地对子元素进行排序。
按照 Michael 的说法,这仍然非常脆弱。 Kay 的回答,虽然它不再依赖与元素名称匹配的复杂类型的名称。
<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>