Проблема использования оси предка в предикате XPath - PullRequest
4 голосов
/ 30 ноября 2009

Использование этого исходного документа:

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

Это должен быть результат:

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

Это моя таблица. Он добавляет элементы атрибута, соответствующие условию 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, это работает. Я внес эти изменения в таблицу:

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

3 голосов
/ 01 декабря 2009

Я бы использовал только один шаблон (сопоставление с шаблоном стоит дорого):

<?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 голос
/ 01 декабря 2009

Вам не нужно использовать 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>
1 голос
/ 01 декабря 2009

Я думаю, что это проблема процессора XSL, а не вашего XSLT. (За исключением count(ancestor::*[@AttributeToCheck]) > 0 должно быть, например, ancestor::*[@AttributeToCheck='true'], как вы могли заметить.)

Я попробовал следующее, и, похоже, он правильно работает на XmlStarlet:

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

В моей интерпретации §2.1 и §3.4 спецификации XPath 1.0 ваше выражение и одно из приведенных выше имеют одинаковый эффект (как минимум) в этом случае.

Другое решение - использовать шаблоны с режимом. Это может быть полезно, если @AttributeToCheck может быть вложенным.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...