当相同的元素可以同时作为父元素和子元素出现时,如何递归删除父元素?

问题描述 投票:0回答:1

我正在使用 XML 模式生成器为某个系统生成的一组 XML 文档创建模式。我使用这个生成的模式来生成 Java 代码来读取和写入 XML。我对生成的架构进行了某些手动更改,以便生成的 Java 代码对我更有用。例如,我添加枚举。

系统会不时更新,其生成的 XML 格式也会发生变化。这时,我需要重新生成架构。由于我对之前生成的模式进行了手动更改,因此我需要将现有的手动调整模式与新生成的模式进行比较,并手动移动相关更改,以避免破坏我的手动更改。我使用 Beyond Compare 来做到这一点。

我遇到的困难是,生成的模式的结构很大程度上取决于我用来生成模式的 XML 文档的某些特征——这些特征对我的用例来说并不重要。

每个生成的复杂类型都有一个序列作为其直接子代。没事儿。有时,除了元素子级之外,这些序列还具有选择或序列子级。通过像这样放置这些构造,生成器试图确保如果某些元素从未一起出现在源文档中,则它们将不会被允许一起出现在经过验证的文档中,并且如果某些元素总是一起出现在源文档,它们必须始终一起出现在生成的文档中。

我不关心这些事情。我宁愿有一个更简单、更宽松的模式,其中序列没有选择或序列子级,除非它们确实有必要。

我尝试编写 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>

这试图做的是:在我有选择/序列的地方,删除序列,使其子代成为选择的子代(将它们从孙子提升为子代)。为了使生成的模式验证所有与原始文档相同的文档,还必须确保所有提升的元素上的 @minOccurs=0。

它将相同的逻辑应用于序列/选择,其中选择没有属性。这是为了排除具有 @maxOccurs=unbounded 的选择,因为这种转换是非法的。

它也将其应用于序列/序列,其中子序列具有@minOccurs=0,同样的原因。

这是可行的,除了在我生成的模式中的一种情况下,我有一个像这样的构造:

<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>

它正确地消除了选择。但它并没有消除内部序列,而我绝对希望它这样做。

我不是 XSLT 专家,但我认为通过在我自己的模板中使用 apply-templates ,它会递归地应用模板,并且它应该处理这种情况。

xslt
1个回答
0
投票

尝试直接在子节点上使用

xsl:apply-templates
,而不是像

之类的东西
<xsl:template match="sequence/choice[not(@*)]">
  <xsl:for-each select="*">
    <xsl:copy>

使用

<xsl:template match="sequence/choice[not(@*)]">
  <xsl:apply-templates/>
</xsl:template>

您可能需要使用一种模式来确保可以填写 minOccurs 属性,或者您可以编写一个与添加属性的默认模式中的正确谓词匹配

sequence/choice
的模板。

目前,通过使用

for-each
,您正在破坏部分递归意图。

© www.soinside.com 2019 - 2024. All rights reserved.