通过XSLT插入和删除元素的最佳实践

时间:2012-06-25 16:36:44

标签: xml xslt

我是XSLT的新手,现在我有一个相当复杂的问题...我的输入文件看起来像

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<doc>
  <outerElement>
    <first>
      <textElement>Some Text</textElement>
    </first>
    <second>
      <textElement>Some Text</textElement>
    </second>
    <third>
      <textElement>Some Text</textElement>
    </third>
  </outerElement>
</doc>

“第二个”元素出现问题。在输入文件中,它可以有以下三种形式之一:

MISSING

<second>
  <textElement>Some Text</textElement>
</second>

<second missingCause="" />

在输出文件中,它应该像第二种形式一样插入。如果它在textElement指示它被模板插入之前丢失了,那么重要的是它必须位于第二个位置,因为我想根据xsd模式验证它...

这是我目前的XSL:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output indent="yes"/>
  <xsl:strip-space elements="*"/>

  <!-- COPY ALL ELEMENTS -->
  <xsl:template match="node()|@*">
    <xsl:copy>
      <xsl:apply-templates select="node()|@*"/>
    </xsl:copy>
  </xsl:template>

  <!-- remove Elements with attribute deleteme -->
  <xsl:template match="outerElement/second[@missingCause='*']" />

  <!-- look if second is there. If not insert -->
  <xsl:template match="outerElement">
    <xsl:copy>
      <xsl:apply-templates select="node()|@*"/>
      <xsl:if test="not(second)">
        <second>
        </second>
      </xsl:if>
    </xsl:copy>    
  </xsl:template>

  <!-- Insert Element second -->
  <xsl:template match="outerElement/second">
    <xsl:apply-templates select="node()|@*"/>
    <xsl:copy>
      <xsl:if test="not(textElement)">
        <textElement>Inserted by Template</textElement>
      </xsl:if>
    </xsl:copy>    
  </xsl:template>

</xsl:stylesheet> 

如果缺少“秒”,我的输出文件只会得到一个元素“”,但它是空的,最后一个转换不会被应用。其他一切看起来很好,而当我在文件中我收到警告......

是否有人可以帮我将元素移动到原来的位置,以便对Schema进行验证并使其在所有三种情况下都能正常工作?

我认为我的方式似乎不太好并且最终会变得混乱,因为我有几个类似的元素插入/删除像这样。

感谢阅读;)

2 个答案:

答案 0 :(得分:4)

这是一个通用的解决方案,适用于outerElement的任意数量的不同命名的子项以及它们之间的任何首选顺序

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:ext="http://exslt.org/common" exclude-result-prefixes="ext">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:param name="pUncertainElName" select="'second'"/>

 <xsl:param name="pOrderedNames" select="'|first|second|third|'"/>

 <xsl:template match="node()|@*">
  <xsl:copy>
   <xsl:apply-templates select="node()|@*"/>
  </xsl:copy>
 </xsl:template>

 <xsl:template match="outerElement">
  <xsl:variable name="vrtfFirstPass">
      <xsl:copy>
       <xsl:apply-templates select="node()|@*"/>
       <xsl:apply-templates select=
         "self::*[not(*[name() = $pUncertainElName])
                or
                 *[name()=$pUncertainElName and @missing-cause]]"
         mode="missing"/>
      </xsl:copy>
  </xsl:variable>

  <xsl:apply-templates select="ext:node-set($vrtfFirstPass)/*" mode="pass2"/>
 </xsl:template>

 <xsl:template match="*[@missing-cause]"/>

 <xsl:template match="*" mode="missing">
    <xsl:element name="{$pUncertainElName}">
        <textElement>Some Text</textElement>
    </xsl:element>
 </xsl:template>

 <xsl:template match="outerElement" mode="pass2">
  <xsl:copy>
   <xsl:apply-templates>
     <xsl:sort data-type="number" select=
     "string-length(substring-before($pOrderedNames,
                                     concat('|', name(), '|')
                                     )
                   )"/>
   </xsl:apply-templates>
  </xsl:copy>
 </xsl:template>
</xsl:stylesheet>

对以下XML文档应用此转换时:

<doc>
    <outerElement>
        <first>
            <textElement>Some Text</textElement>
        </first>
        <second missing-cause="">
            <textElement>Some Text</textElement>
        </second>
        <third>
            <textElement>Some Text</textElement>
        </third>
    </outerElement>
</doc>

产生了想要的正确结果:

<doc>
   <outerElement>
      <first>
         <textElement>Some Text</textElement>
      </first>
      <second>
         <textElement>Some Text</textElement>
      </second>
      <third>
         <textElement>Some Text</textElement>
      </third>
   </outerElement>
</doc>

在此XML文档上应用时:

<doc>
    <outerElement>
        <first>
            <textElement>Some Text</textElement>
        </first>
        <third>
            <textElement>Some Text</textElement>
        </third>
    </outerElement>
</doc>

再次生成想要的正确结果

<doc>
   <outerElement>
      <first>
         <textElement>Some Text</textElement>
      </first>
      <second>
         <textElement>Some Text</textElement>
      </second>
      <third>
         <textElement>Some Text</textElement>
      </third>
   </outerElement>
</doc>

最后,当对此XML文档应用相同的转换时:

<doc>
    <outerElement>
        <first>
            <textElement>Some Text</textElement>
        </first>
        <second>
            <textElement>Some Text</textElement>
        </second>
        <third>
            <textElement>Some Text</textElement>
        </third>
    </outerElement>
</doc>

同样想要的,产生了正确的结果:

<doc>
   <outerElement>
      <first>
         <textElement>Some Text</textElement>
      </first>
      <second>
         <textElement>Some Text</textElement>
      </second>
      <third>
         <textElement>Some Text</textElement>
      </third>
   </outerElement>
</doc>

请注意

outerElement(不仅仅是三个)可以有任意数量的不同名称的孩子,而且他们的订单可能不会提前知道。

例如

鉴于此XML文档

<doc>
    <outerElement>
        <first>
            <textElement>Some Text</textElement>
        </first>
        <second missing-cause="">
            <textElement>Some Text</textElement>
        </second>
        <third>
            <textElement>Some Text</textElement>
        </third>
        <fourth>
            <textElement>Some Text</textElement>
        </fourth>
    </outerElement>
</doc>

和此订单:第四,第二,第三,第一

我们只需要替换

 <xsl:param name="pOrderedNames" select="'|first|second|third|'"/>

<强>与

 <xsl:param name="pOrderedNames" select="'|fourth|second|third|first|'"/>

现在生成新的通缉结果

<doc>
   <outerElement>
      <fourth>
         <textElement>Some Text</textElement>
      </fourth>
      <second>
         <textElement>Some Text</textElement>
      </second>
      <third>
         <textElement>Some Text</textElement>
      </third>
      <first>
         <textElement>Some Text</textElement>
      </first>
   </outerElement>
</doc>

<强>解释

  1. 这是两次转换。

  2. 在外部/全局参数$pUncertainElName中指定可能存在或可能不存在的元素的名称。为方便起见,我们将此元素称为second

  3. 在第一次传递中,outerElement的{​​{1}}除second属性之外的所有子项都按“原样”复制。如果missing-cause元素不存在或具有second属性,我们会输出一个新的missing-cause子元素 - 完全想要的outerElement元素。

  4. 在第二遍中,我们根据其优先级对第一遍中生成的second的子项进行排序,如另一个名为outerElement的外部/全局参数(名称)中所指定的在此字符串中留下另一个名称,具有更高的优先级)

答案 1 :(得分:3)

我认为你误解了模板的工作原理。对于您需要完成的任务,似乎只需要一个模板(加上身份模板)。尝试并反馈:

<xsl:template match="outerElement[not(second) or second[@missingCause='']]">
    <xsl:apply-templates select="@*|first"/>
    <second>
        <textElement>Inserted by Template</textElement>
    </second>
    <xsl:apply-templates select="third"/>
</xsl:template>