在XPath谓词中使用祖先轴的问题

时间:2009-11-30 15:05:49

标签: xml xslt xpath

使用此源文档:

<?xml version="1.0" encoding="UTF-8"?>
<Root>
  <Element1 id="UniqueId1">
    <SubElement1/>
    <SubElement2>
      <LeafElement1/>
      <LeafElement1/>
    </SubElement2>
  </Element1>
  <Element2 id="UniqueId2" AttributeToCheck="true">
    <SubElement1>
      <LeafElement1/>
      <LeafElement1/>
    </SubElement1>
  </Element2> 
</Root>

我想将属性foo =“bar”添加到两个元素:

  1. 拥有同名的兄弟元素
  2. 拥有属性AttributeToCheck
  3. 的祖先

    这应该是结果:

    <?xml version="1.0"?>
    <Root>
      <Element1 id="UniqueId1">
        <SubElement1/>
        <SubElement2>
          <LeafElement1/>
          <LeafElement1/>
        </SubElement2>
      </Element1>
      <Element2 id="UniqueId2" AttributeToCheck="true">
        <SubElement1>
          <LeafElement1 foo="bar"/>
          <LeafElement1 foo="bar"/>
        </SubElement1>
      </Element2>
    </Root>
    

    到目前为止,这是我的stlesheet。它添加了匹配条件1的属性元素,但未考虑条件2.

    <?xml version="1.0" encoding="UTF-8"?>
    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
      <xsl:output method="xml" version="1.0" indent="yes"/>
    
      <xsl:template match="* | @*">
        <xsl:copy>
          <xsl:apply-templates select="* | @*"/>
        </xsl:copy>
      </xsl:template>
    
      <xsl:template match="*[count(../*[name(.) = name(current())]) > 1]">
        <xsl:copy>
          <xsl:attribute name="foo">bar</xsl:attribute>
          <xsl:apply-templates select="* | @*"/>
        </xsl:copy>
      </xsl:template>
    </xsl:stylesheet>
    

    输出错误:

    <?xml version="1.0"?>
    <Root>
      <Element1 id="UniqueId1">
        <SubElement1/>
        <SubElement2>
          <LeafElement1 foo="bar"/> (incorrect)
          <LeafElement1 foo="bar"/> (incorrect)
        </SubElement2>
      </Element1>
      <Element2 id="UniqueId2" AttributeToCheck="true">
        <SubElement1>
          <LeafElement1 foo="bar"/> (correct)
          <LeafElement1 foo="bar"/> (correct)
        </SubElement1>
      </Element2>
    </Root>
    

    由于第二个模板已正确匹配具有相同名称的兄弟元素的元素,因此应该很容易使用祖先XPath轴来排除没有AttributeToCheck祖先的元素。我在第二个模板中添加了另一个谓词。

    <xsl:template match="*[ancestor::*[@AttributeToCheck]][count(../*[name(.) = name(current())]) > 1]">
    

    当我应用此样式表时,输出文档与输入文档相同,表明第二个模板与任何元素都不匹配。我还尝试更改新谓词以使用节点计数。

    <xsl:template match="*[count(ancestor::*[@AttributeToCheck]) > 0][count(../*[name(.) = name(current())]) > 1]">
    

    这也不起作用,输出文档与输入文档相同。这是令人惊讶的,因为当我使用这个祖先表达式输出具有AttributeToCheck的节点名称时,它可以工作。我对sylesheet进行了以下更改:

    <?xml version="1.0" encoding="UTF-8"?>
    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
      <xsl:output method="xml" version="1.0" indent="yes"/>
    
      <xsl:template match="* | @*">
        <xsl:copy>
          <xsl:apply-templates select="* | @*"/>
        </xsl:copy>
      </xsl:template>
    
      <xsl:template match="*[count(../*[name(.) = name(current())]) > 1]">
        <xsl:copy>
          <xsl:attribute name="foo">bar</xsl:attribute>
          <xsl:attribute name="AncestorCount">
            <xsl:value-of select="count(ancestor::*[@AttributeToCheck])"/>
          </xsl:attribute>
          <xsl:attribute name="AncestorName">
            <xsl:value-of select="name(ancestor::*[@AttributeToCheck])"/>
          </xsl:attribute>
          <xsl:apply-templates select="* | @*"/>
        </xsl:copy>
      </xsl:template>
    
    </xsl:stylesheet>
    

    生成此输出:

    <?xml version="1.0"?>
    <Root>
      <Element1 id="UniqueId1">
        <SubElement1/>
        <SubElement2>
          <LeafElement1 foo="bar" AncestorCount="0" AncestorName=""/>
          <LeafElement1 foo="bar" AncestorCount="0" AncestorName=""/>
        </SubElement2>
      </Element1>
      <Element2 id="UniqueId2" AttributeToCheck="true">
        <SubElement1>
          <LeafElement1 foo="bar" AncestorCount="1" AncestorName="Element2"/>
          <LeafElement1 foo="bar" AncestorCount="1" AncestorName="Element2"/>
        </SubElement1>
      </Element2>
    </Root>
    

    我的问题是,为什么XPath谓词*[ancestor::*[@AttributeToCheck]][count(../*[name(.) = name(current())]) > 1]不能正确匹配条件1和2的元素?我应该使用什么XPath表达式呢?

3 个答案:

答案 0 :(得分:3)

我只使用一个模板(模式匹配成本很高):

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" version="1.0" indent="yes"/>

  <xsl:template match="* | @*">
    <xsl:copy>
      <xsl:if test="count(../*[name(current()) = name()]) > 1 and ancestor::*[@AttributeToCheck='true']">
        <xsl:attribute name="foo">bar</xsl:attribute>      
      </xsl:if>
      <xsl:apply-templates select="* | @*"/>
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

答案 1 :(得分:1)

我认为这是XSL处理器而不是XSLT的问题。 (正如您可能会注意到的,count(ancestor::*[@AttributeToCheck]) > 0除外ancestor::*[@AttributeToCheck='true']除外。)

我尝试了以下内容,它似乎在XmlStarlet上正常工作:

<xsl:template match="*[ancestor::*[@AttributeToCheck='true'] and count(../*[name(.)=name(current())]) > 1]">

根据我对XPath 1.0规范的§2.1和§3.4的解释,在这种情况下,你的表达式和上面的表达式(至少)具有相同的效果。

另一种解决方案是使用模式模板。 如果@AttributeToCheck可以嵌套,这可能很有用。

答案 2 :(得分:1)

您不需要使用count(),因为空节点集的计算结果为false。只需选择您感兴趣的节点就足够了:

<xsl:stylesheet 
  version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
  <!-- modified identity transform -->
  <xsl:template match="node() | @*">
    <xsl:copy>
      <!-- check both your conditions -->
      <xsl:if test="
        ancestor::*[@AttributeToCheck = 'true']
        and
        (preceding-sibling::* | following-sibling::*)[name() = name(current())]
      ">
        <xsl:attribute name="foo">bar</xsl:attribute>
      </xsl:if>
      <xsl:apply-templates select="node() | @*" />
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>