Xpath в XSLT: выберите элементы, которые находятся между 2 другими элементами, часть II - PullRequest
1 голос
/ 10 октября 2009

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

У меня есть вопрос, касающийся выбора набора элементов, которые встречаются между «другими / разграничивающими» элементами. Такая ситуация возникает при попытке преобразования плоской таблицы HTML в иерархическая структура XML с использованием XSLT. Я пытался использовать рекурсию в шаблонах, но Саксон отказался принять это, поскольку это привело к взаимоблокировке, скорее всего, по моей вине, но давайте начнем с самого начала.

Сначала исходными данными является таблица HTML:

<table >
    <thead>
        <tr>
            <th>Column 1</th>
            <th>Column 2</th>
            <th>Column 3</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <th colspan="3" >Group 1</th>
        </tr>
        <tr>
            <td>attribute 1.1.1</td>
            <td>attribute 1.1.3</td>
            <td>attribute 1.1.2</td>
        </tr>
        <tr>
            <td>attribute 1.2.1</td>
            <td>attribute 1.2.2</td>
            <td>attribute 1.2.3</td>
        </tr>
        <tr>
            <td>attribute 1.3.1</td>
            <td>attribute 1.3.2</td>
            <td>attribute 1.3.3</td>
        </tr>
        <tr>
            <th colspan="3" >Group 2</th>
        </tr>
        <tr>
            <td>attribute 2.1.1</td>
            <td>attribute 2.1.3</td>
            <td>attribute 2.1.2</td>
        </tr>
        <tr>
            <td>attribute 2.2.1</td>
            <td>attribute 2.2.2</td>
            <td>attribute 2.2.3</td>
        </tr>
        <tr>
            <td>attribute 2.3.1</td>
            <td>attribute 2.3.2</td>
            <td>attribute 2.3.3</td>
        </tr>
    </tbody>
</table>

Целевой вывод в XML будет:

 <groups>
    <group name="Group 1">
        <item attribute1="attribute 1.1.1" attribute2="attribute 1.1.3" attribute3="attribute 1.1.2"/>
        <item attribute1="attribute 1.2.1" attribute2="attribute 1.2.2" attribute3="attribute 1.2.3"/>
        <item attribute1="attribute 1.3.1" attribute2="attribute 1.3.2" attribute3="attribute 1.3.3"/>
    </group>
    <group name="Group 2">
        <item attribute1="attribute 2.1.1" attribute2="attribute 2.1.3" attribute3="attribute 2.1.2"/>
        <item attribute1="attribute 2.2.1" attribute2="attribute 2.2.2" attribute3="attribute 2.2.3"/>
        <item attribute1="attribute 2.3.1" attribute2="attribute 2.3.2" attribute3="attribute 2.3.3"/>
    </group>
</groups>

Итак, я хочу получить все записи об элементах (элементы TR) и добавить их в группу. Это в основном сводится к выбору всех следующих TR элементов, пока мы не встретим элемент, у которого элемент TH является дочерним. Если бы я мог только определить позицию этого первого TR с дочерним элементом TH, указав новый заголовок для группы, это можно было бы сделать с помощью:

<xsl:for-each select="tbody/tr">
    <xsl:if test="th">
        <xsl:element name="group">
            <xsl:attribute name="name"><xsl:value-of select="th"/></xsl:attribute>
            <xsl:for-each select="following-sibling::tr[position() < $positionOfNextThElement]">            
                <xsl:call-template name="item"/>
            </xsl:for-each>
        </xsl:element>
    </xsl:if>
</xsl:for-each>

Однако я не могу определить положение первого обнаруженного тега TR / TH.

Как уже говорилось, я пытался работать с рекурсией в шаблонах: всегда вызывать шаблон "item" и в этом шаблоне определить, хотим ли мы также вызывать его для следующего элемента. Я думаю, что проблема заключается в вызове шаблона изнутри шаблона. Элемент в контексте не увеличивается? Должен ли я передать параметр, чтобы определить, над каким элементом мы работаем?

Во всяком случае, это было то, что я придумал:

<xsl:for-each select="tbody/tr">
    <xsl:if test="th">
        <xsl:element name="group">
            <xsl:attribute name="name"><xsl:value-of select="th"/></xsl:attribute>
            <xsl:call-template name="item"/>
        </xsl:element>
    </xsl:if>
</xsl:for-each>

<xsl:template name="item">
    <xsl:element name="item">
        <xsl:attribute name="attribute1"><xsl:value-of select="following-sibling::tr[1]/td[1]"/></xsl:attribute>
        <xsl:attribute name="attribute2"><xsl:value-of select="following-sibling::tr[1]/td[2]"/></xsl:attribute>
        <xsl:attribute name="attribute2"><xsl:value-of select="following-sibling::tr[1]/td[3]"/></xsl:attribute>
    </xsl:element>
    <!-- When the next element has not got a TH tag, continue with invoking this template -->
    <xsl:if test="count(following-sibling::tr[1]/th) != 1">
        <xsl:call-template name="item"/>
    </xsl:if>
</xsl:template>

Любые предложения о том, как это реализовать, приветствуются!

Ответы [ 2 ]

1 голос
/ 10 октября 2009

Причина, по которой контекст не увеличивается при рекурсивном вызове шаблона "item", заключается в том, что xs: call-template всегда передает текущий элемент контекста как контекст. Итак, как вы, вероятно, видели, преобразование просто входит в бесконечную рекурсию.

Предполагая, что вам всегда нужно создать ровно три атрибута, вам даже не нужна рекурсия.

Попробуйте это:

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

    <xsl:template match="table">
        <groups>
            <xsl:apply-templates select="tbody/tr[th]"/>
        </groups>
    </xsl:template>

    <xsl:template match="tr[th]">
        <xsl:variable name="id" select="generate-id(.)"/>
        <group name="{string(th)}">
            <xsl:apply-templates
                select="following-sibling::tr[not(th)][generate-id(preceding-sibling::tr[th][1]) = $id]"/>
        </group>
    </xsl:template>

    <xsl:template match="tr">
        <item attribute1="{td[1]}" attribute2="{td[2]}" attribute3="{td[3]}" />                    
    </xsl:template>

</xsl:stylesheet>

Это работает путем применения шаблонов к каждой строке заголовка. Каждый из этих шаблонов использует этот сложный xpath для вызова «своих» следующих строк, которые являются любыми последующими одноуровневыми строками, которые имеют эту конкретную строку в качестве первой предыдущей строки с заголовком.

Конечно, если количество атрибутов варьируется, то вам нужно будет рекурсировать туда и увеличить пропуск параметра, указывающего позицию.

Существует несколько установленных методов группировки XSLT, один из которых является рекурсивным, как вы это делали. Другой метод называется группировкой мюнхена. Хорошая статья написана здесь .

0 голосов
/ 11 октября 2009

Альтернативное решение, приспособленное для переменного количества атрибутов без рекурсии.

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

  <xsl:template match="table">
    <groups>
      <xsl:apply-templates select="tbody/tr[th]"/>
    </groups>
  </xsl:template>

  <xsl:template match="tr[th]">
    <group name="{th}">
      <xsl:apply-templates select="
        following-sibling::tr[not(th)][
          generate-id(preceding-sibling::tr[th][1]) = generate-id(current())
        ]
      "/>
    </group>
  </xsl:template>

  <xsl:template match="tr">
    <item>
     <xsl:apply-templates select="td" />
    </item>
  </xsl:template>

  <xsl:template match="td">
    <xsl:attribute name="attribute{position()}">
      <xsl:value-of select="." />
    </xsl:attribute>
  </xsl:template>

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