【问题标题】:Best practice for inserting and deleting elements via XSLT通过 XSLT 插入和删除元素的最佳实践
【发布时间】:2012-06-25 16:36:44
【问题描述】:

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

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

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> 

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

有人可以帮我将元素移动到它必须在的位置,以便验证到架构并使其在所有三种情况下都能正常工作吗?

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

感谢阅读;)

【问题讨论】:

    标签: xml xslt


    【解决方案1】:

    这是一个通用解决方案,适用于 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 的所有子级(具有 missing-cause 属性的 second 除外)都“按原样”复制。如果second 元素不存在或具有missing-cause 属性,我们将输出outerElement 的新子元素——正是想要的second 元素。

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

    【讨论】:

    • 嘿,感谢您的回答,这似乎是我需要做的,但我不明白它在各个方面的工作原理。第一个模板是身份模板,没关系。第二个似乎做了一些疯狂的事情,它将模式设置为丢失,所以第四个模板插入元素,而第三个模板正在删除缺少原因的对象? - 第一个是如何工作的,为什么里面有一个变量?
    • @DennisIch:这称为多遍处理。主要思想是每次传递的输出都被捕获在 xsl:variable 中,然后在下一次传递中将模板应用到包含在该变量中的树上。我猜您对使用ext:node-set() 扩展函数感到困惑——这在XSLT 1.0 中是必需的(在XSLT 2.0 中不是必需的),因为XSLT 1.0 的限制(臭名昭著的RTF 数据类型)。例如看这个答案:stackoverflow.com/a/6529617/36305
    • 好的,澄清一下... 变量在变量中的这个模板本身不产生输出,但会被处理,然后它会进行身份复制?这第二个选择是否会触发“丢失”模板?我很困惑它是如何从模板跳转到模板的;( - 如果我将 OuterElement 存储到一个变量中并在其上使用 xsl:call-template 会更容易/更清晰吗?然后我还可以创建多个缺失的变量。
    • @DennisIch:我明白了,您对xsl:apply-templates 指令感到困惑。其目的是在中间结果中添加缺失的元素。这也可以通过xsl:call-templates 完成。使用xsl:apply-templates 并避免使用xsl:call-templates 更符合XSLT 的精神。前者是多态的(就像调用 C++ 虚方法一样)并且比后者强大得多。
    • 感谢您的帮助,我想我基本上明白了...我需要添加的大部分元素彼此相似,所以我想我可以将您的模板用作基础并使其通用有更多参数,但这并不像我预期的那样工作;)。如果我想添加的元素的路径不再像 outerElement 之类的东西,我可以使用
    【解决方案2】:

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

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

    【讨论】:

    • 它按预期工作谢谢:) - 为什么第一个选择像“@*|first”而最后一个选择=“第三个”?有什么方法可以自动化这个元素放在 xml 中的位置,以便它适合 xsd 模式,还是我必须自己做所有这些?
    • 通过第一次选择,我们确保模板应用于 outerElement 属性。第二个问题我没看懂。
    • ah ok :) - 对于第二个问题.. 我有一个模式,在这个模式中,一些元素是 (0-1),其中一些元素对我的应用程序是强制性的。所以我得到了缺少这些元素的数据,我想把它们全部放在 XSL 中。有没有办法告诉 XSL,它必须订购我插入的元素以适应模式,以便它验证。有一些元素有大量的子元素,并且按顺序应用模板将是一项艰巨的工作,因为我必须考虑到每个可能存在的元素。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-19
    • 1970-01-01
    • 1970-01-01
    • 2010-10-17
    • 2011-04-12
    • 1970-01-01
    相关资源
    最近更新 更多