如何用XSLT递归移除XML Schema中的冗余嵌套父元素?
背景
我通过XML Schema生成器生成系统输出XML对应的Schema,再基于该Schema生成XML读写Java代码,同时会手动修改Schema(比如添加枚举)来优化生成的代码。当系统更新导致XML格式变化时,需要重新生成Schema,并用Beyond Compare对比旧版手动优化的Schema,迁移自定义修改以避免被覆盖。
当前生成的Schema存在冗余嵌套结构:每个complexType的直接子元素为sequence,但部分sequence中包含多余的choice或sequence子元素,这不符合我对简洁、宽松Schema的需求。我编写了XSLT尝试移除这些冗余嵌套,但未完全生效,尤其是递归处理的问题。
当前XSLT代码
<xsl:template match="choice/sequence"> <xsl:for-each select="*"> <xsl:copy> <xsl:variable name="att" as="attribute() *"> <xsl:attribute name="minOccurs">0</xsl:attribute> </xsl:variable> <xsl:apply-templates select="(@*[not(local-name() eq 'minOccurs')], $att)"> <xsl:sort select="local-name()"/> </xsl:apply-templates> <xsl:apply-templates select="node()"/> </xsl:copy> </xsl:for-each> </xsl:template> <xsl:template match="sequence/choice[not(@*)]"> <xsl:for-each select="*"> <xsl:copy> <xsl:variable name="att" as="attribute() *"> <xsl:attribute name="minOccurs">0</xsl:attribute> </xsl:variable> <xsl:apply-templates select="(@*[not(local-name() eq 'minOccurs')], $att)"> <xsl:sort select="local-name()"/> </xsl:apply-templates> <xsl:apply-templates select="node()"/> </xsl:copy> </xsl:for-each> </xsl:template> <xsl:template match="sequence/sequence[@minOccurs eq '0']"> <xsl:for-each select="*"> <xsl:copy> <xsl:variable name="att" as="attribute() *"> <xsl:attribute name="minOccurs">0</xsl:attribute> </xsl:variable> <xsl:apply-templates select="(@*[not(local-name() eq 'minOccurs')], $att)"> <xsl:sort select="local-name()"/> </xsl:apply-templates> <xsl:apply-templates select="node()"/> </xsl:copy> </xsl:for-each> </xsl:template>
该XSLT旨在移除choice/sequence、无属性的sequence/choice、minOccurs="0"的sequence/sequence这类冗余父元素,将其子元素提升为直接子元素,并设置minOccurs="0"以保证Schema验证逻辑一致。
问题场景
处理以下嵌套结构时:
<xs:element name="blah1"> <xs:complexType> <xs:sequence> <xs:element ref="blah2"/> <xs:choice> <xs:element ref="blah3"/> <xs:sequence> <xs:element ref="blah4"/> <xs:element ref="blah5"/> </xs:sequence> </xs:choice> </xs:sequence> </xs:complexType> </xs:element>
得到的结果是:
<xs:element name="blah1"> <xs:complexType> <xs:sequence> <xs:element ref="blah2"/> <xs:element minOccurs="0" ref="blah3"/> <xs:sequence minOccurs="0"> <xs:element ref="blah4"/> <xs:element ref="blah5"/> </xs:sequence> </xs:sequence> </xs:complexType> </xs:element>
虽然成功移除了choice元素,但内部的sequence未被递归处理,这正是需要解决的问题。
解决方案
问题根源在于当前模板仅处理直接匹配的层级,未对提升后的子元素再次触发模板匹配。要实现递归处理,需调整XSLT逻辑:不再直接复制子元素,而是对这些子元素应用模板,让XSLT处理链能继续匹配后续的冗余结构。
修改后的XSLT代码如下:
<!-- 保留默认复制模板,确保未匹配的节点正常复制 --> <xsl:template match="@*|node()"> <xsl:copy> <xsl:apply-templates select="@*|node()"/> </xsl:copy> </xsl:template> <!-- 处理choice/sequence:移除父sequence,提升子元素并设置minOccurs="0" --> <xsl:template match="choice/sequence"> <xsl:apply-templates select="*" mode="promote-with-minOccurs-0"/> </xsl:template> <!-- 处理无属性的sequence/choice:移除父choice,提升子元素并设置minOccurs="0" --> <xsl:template match="sequence/choice[not(@*)]"> <xsl:apply-templates select="*" mode="promote-with-minOccurs-0"/> </xsl:template> <!-- 处理minOccurs="0"的sequence/sequence:移除父sequence,提升子元素并设置minOccurs="0" --> <xsl:template match="sequence/sequence[@minOccurs eq '0']"> <xsl:apply-templates select="*" mode="promote-with-minOccurs-0"/> </xsl:template> <!-- 自定义模式:提升元素并设置minOccurs="0"(保留原有其他属性) --> <xsl:template match="*" mode="promote-with-minOccurs-0"> <xsl:copy> <!-- 合并原有属性(排除minOccurs)和新的minOccurs="0" --> <xsl:apply-templates select="@*[not(local-name() eq 'minOccurs')]"> <xsl:sort select="local-name()"/> </xsl:apply-templates> <xsl:attribute name="minOccurs">0</xsl:attribute> <!-- 对当前元素的子节点应用模板,触发递归处理 --> <xsl:apply-templates select="node()"/> </xsl:copy> </xsl:template>
修改说明
- 添加默认复制模板:确保所有未被自定义模板匹配的节点都能正常复制,维持Schema的其他结构不变。
- 使用模式(mode)分离逻辑:创建
promote-with-minOccurs-0模式,专门处理元素提升和属性设置,避免与默认模板冲突。 - 递归触发模板匹配:在处理冗余父元素时,调用
<xsl:apply-templates>对子元素应用模板,这样提升后的子元素如果符合其他冗余结构的匹配条件(比如内部的sequence),会被对应的模板再次处理,实现递归移除。
用修改后的XSLT处理示例结构,会得到如下结果:
<xs:element name="blah1"> <xs:complexType> <xs:sequence> <xs:element ref="blah2"/> <xs:element minOccurs="0" ref="blah3"/> <xs:element minOccurs="0" ref="blah4"/> <xs:element minOccurs="0" ref="blah5"/> </xs:sequence> </xs:complexType> </xs:element>
这样就彻底移除了所有冗余的嵌套结构,同时保证了Schema的验证逻辑一致。
内容的提问来源于stack exchange,提问作者Danny MacMillan

