Как включить ветку предка узла, выбранного с помощью параметра xpath, в вывод XSLT - PullRequest
8 голосов
/ 23 июня 2011

После более 8 часов попыток я надеюсь, что кто-то может помочь мне с этим:

Учитывая следующий (упрощенный) XML для книги:

<book>
    <section name="A">
        <chapter name="I">
            <paragraph name="1"/>
            <paragraph name="2"/>
        </chapter>
        <chapter name="II">
            <paragraph name="1"/>          
        </chapter>
    </section>
    <section name="B">
        <chapter name="III"/>
        <chapter name="IV"/>   
    </section>
</book>

Я могу извлечь любую часть (раздел, главу или параграф) XML книги на основе заданного параметра со следующим XSL:

<xsl:param name="subSelectionXPath" required="yes" as="node()"/>

<xsl:template match="/">
    <xsl:apply-templates select="$subSelectionXPath"/>
</xsl:template>

<xsl:template match="*">
    <!-- output node with all children -->
    <xsl:copy-of select="."/>
</xsl:template>

и параметр $ subSelectionXPath со значением типа

doc(<em>filename</em>)//chapter[@name='II']

в результате получается:

<chapter name="II">
    <paragraph name="1"/>          
</chapter>

Кроме того, я хочу добиться, чтобы выбранный фрагмент XML был заключен в ветвь XML предка, т. Е .:

<book>
    <section name="A">
        <chapter name="II">
            <paragraph name="1"/>          
        </chapter>
    </section>    
</book>

Я представляю (и пытался) обойти дерево XML и проверить, является ли текущий узел предком, что-то вроде (псевдокод):

<xsl:if test="node() in $subSelectionXPath/ancestor::node()">
    <xsl:copy>
       <xsl:apply-templates/>
    </xsl:copy>
</xsl:if>

Я также экспериментировал с xsl: key, но, боюсь, мои знания о XSLT на этом заканчиваются. Есть мысли?

Ответы [ 3 ]

6 голосов
/ 24 июня 2011

Из вашего кода видно, что вы используете XSLT 2.0.

Это преобразование XSLT 2.0 :

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

 <xsl:param name="subSelectionXPath"
  as="node()" select="//chapter[@name='II']"
  />

 <xsl:template match="*[descendant::node() intersect $subSelectionXPath]">
  <xsl:copy>
   <xsl:copy-of select="@*"/>
   <xsl:apply-templates select="*"/>
  </xsl:copy>
 </xsl:template>

 <xsl:template match="*[. intersect $subSelectionXPath]">
  <xsl:copy-of select="."/>
 </xsl:template>
</xsl:stylesheet>

при применении к предоставленномуXML-документ :

<book>
    <section name="A">
        <chapter name="I">
            <paragraph name="1"/>
            <paragraph name="2"/>
        </chapter>
        <chapter name="II">
            <paragraph name="1"/>
        </chapter>
    </section>
    <section name="B">
        <chapter name="III"/>
        <chapter name="IV"/>
    </section>
</book>

дает в точности нужный, правильный результат :

<book>
   <section name="A">
      <chapter name="II">
         <paragraph name="1"/>
      </chapter>
   </section>
</book>

Объяснение : у нас всего двашаблоны:

  1. Шаблон, который соответствует любому элементу, чьи потомки имеют непустое пересечение с набором узлов $subSelectionXPath.Здесь мы «мелко копируем» элемент и применяем шаблоны к его дочерним элементам.

  2. Шаблон, который соответствует элементам, которые принадлежат $subSelectionXPath множеству узлов.Здесь мы копируем целое поддерево с корнем в этом элементе.

  3. Обратите внимание на использование оператора XPath 2.0 intersect.

  4. Нет явная рекурсия.

II.Решение XSLT 1.0 :

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

 <xsl:param name="subSelectionXPath"
  select="//chapter[@name='II']"
  />

 <xsl:template match="*">
  <xsl:choose>
   <xsl:when test=
   "descendant::node()
        [count(.|$subSelectionXPath)
        =
         count($subSelectionXPath)
        ]
   ">
   <xsl:copy>
    <xsl:copy-of select="@*"/>
    <xsl:apply-templates select="*"/>
   </xsl:copy>
  </xsl:when>

   <xsl:when test=
   "count(.|$subSelectionXPath)
   =
    count($subSelectionXPath)
   ">
   <xsl:copy-of select="."/>
   </xsl:when>
  </xsl:choose>
 </xsl:template>
</xsl:stylesheet>

Когда это преобразование применяется к тому же XML-документу (показанному выше), получается тот же требуемый и правильный результат :

<book>
   <section name="A">
      <chapter name="II">
         <paragraph name="1"/>
      </chapter>
   </section>
</book>

Объяснение : По сути, это решение XSLT 2.0, в котором оператор XPath 2.0 intersect переведен в XPath 1.0 с использованием известной формулы Кейса (для @Michael Kay) для пересеченияиз двух наборов узлов $ns1 и $ns2:

$ns1[count(.|$ns2) = count($ns2)]
3 голосов
/ 24 июня 2011

Я думаю, что способ сделать то, что вы пытаетесь сделать, с помощью рекурсивного шаблона:

<xsl:stylesheet version="1.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

 <xsl:template match="/">
    <xsl:call-template name="copyElementsOnAncestorAxis">
      <xsl:with-param name="nodeList"
                      select="//chapter[@name='I']/ancestor-or-self::*"/>
    </xsl:call-template>
  </xsl:template>

  <xsl:template name="copyElementsOnAncestorAxis">
    <xsl:param name="nodeList"/>
    <xsl:choose>
      <!-- if the context node is the last node in the list, copy it entirely -->
      <xsl:when test=". = $nodeList[count($nodeList)]">
        <xsl:copy-of select="."/>
      </xsl:when>
      <!-- otherwise, just copy the element, its attributes, and any child element that 
           is also in the node list -->
      <xsl:otherwise>
        <xsl:copy>
          <xsl:copy-of select="@*"/>
          <xsl:for-each select="*[. = $nodeList]">
            <xsl:call-template name="copyElementsOnAncestorAxis">
              <xsl:with-param name="nodeList"
                              select="$nodeList"/>
            </xsl:call-template>
          </xsl:for-each>
        </xsl:copy>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

</xsl:stylesheet>

, который при применении к предоставленному вами XML дает следующее:

<book>
  <section name="A">
    <chapter name="I">
      <paragraph name="1" />
      <paragraph name="2" />
    </chapter>
  </section>
</book>
2 голосов
/ 24 июня 2011

Это еще одно решение на основе рекурсии. Объяснение:

  • первые применения шаблонов к последнему предку
  • тогда, если предок текущего узла параметра копирует его, а если родительский узел копирует узел и завершается, в противном случае повторяется

Вот преобразование:

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

    <xsl:param name="subSelectionXPath" 
        select="document('test_input2.xml')//chapter[@name='II']"/>

    <xsl:template match="/">
        <xsl:apply-templates 
            select="$subSelectionXPath/ancestor::*[position()=last()]"/>
    </xsl:template>

    <xsl:template match="*">
        <xsl:choose>

            <xsl:when test="$subSelectionXPath/ancestor::*
                [generate-id() = generate-id(current())]">
                <xsl:copy>
                    <xsl:copy-of select="@*"/>

                    <xsl:choose>
                        <xsl:when test="generate-id(.)=
                            generate-id($subSelectionXPath/ancestor::*[1])">
                            <xsl:copy-of select="$subSelectionXPath"/>
                        </xsl:when>
                        <xsl:otherwise>
                            <xsl:apply-templates select="*"/>
                        </xsl:otherwise>
                    </xsl:choose>

                </xsl:copy>
            </xsl:when>
            <xsl:otherwise/>
        </xsl:choose>
    </xsl:template>
  </xsl:stylesheet>

При применении к вводу, представленному в вопросе, при условии, что значение входного параметра в вопросе совпадает, выдает:

<book>
   <section name="A">
      <chapter name="II">
         <paragraph name="1"/>
      </chapter>
   </section>
</book>
...