XSLT: перебор всех значений карты xsl:key

XML

<books>
  <book title="XML Today" author="David Perry" release="2016"/>
  <book title="XML and Microsoft" author="David Perry" release="2015"/>
  <book title="XML Productivity" author="Jim Kim" release="2015"/>
</books>

Следующий код XSL перебирает все книги Дэвида Перри.

XSL

<xsl:key name="title-search" match="book" use="@author"/>

<xsl:template match="/">
   <HTML>
      <BODY>
        <xsl:for-each select="key('title-search', 'David Perry')">
         <DIV>
         <xsl:value-of select="@title"/>
         </DIV>
      </xsl:for-each>
      </BODY>
   </HTML>
</xsl:template>

Вывод в формате HTML

<HTML>
  <BODY>
    <DIV>XML Today</DIV>
    <DIV>XML and Microsoft</DIV>
  </BODY>
</HTML>

Теперь я хотел бы перебрать не только все книги Дэвида Перри, но и все книги любого автора. Как будет выглядеть соответствующий внешний цикл? Или, другими словами: как перебрать все значения моего ключа title-search.

Вывод должен быть примерно таким:

<HTML>
  <BODY>
    <H1>David Perry</H1>
    <DIV>XML Today</DIV>
    <DIV>XML and Microsoft</DIV>
    <H1>Jim Kim</H1>
    <DIV>XML Productivity</DIV>
  </BODY>
</HTML>

person Lugaxx    schedule 31.08.2016    source источник
comment
Это группирующий вопрос. Выполните поиск - это, вероятно, наиболее часто задаваемый вопрос XSLT здесь. Обратите внимание, что ответы для XSLT 1.0 и 2.0 различны.   -  person michael.hor257k    schedule 31.08.2016


Ответы (2)


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

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

    <xsl:key name="title-search" match="book" use="@author"/>

    <xsl:template match="/books">
        <HTML>
            <BODY>
                <xsl:apply-templates select="book" />
            </BODY>
        </HTML>
    </xsl:template>

    <xsl:template match="book">
        <xsl:variable name="author" select="@author" />
        <xsl:if test="generate-id(.) = generate-id(key('title-search', $author)[1])">
            <H1><xsl:value-of select="@author" /></H1>
            <xsl:apply-templates select="//book[@author = $author]" mode="titles"/>
        </xsl:if>
    </xsl:template>

    <xsl:template match="book" mode="titles">
        <DIV>
            <xsl:value-of select="@title"/>
        </DIV>
    </xsl:template>

</xsl:stylesheet>

Он использует технику, называемую мюнхианской группировкой. Каждый элемент в XML-документе неявно имеет уникальный идентификатор, присвоенный ему XSLT-процессором (его также можно явно назначить с помощью атрибута id в самом документе). Эта часть:

generate-id(.) = generate-id(key('title-search', $author)[1])

в основном проверяет, совпадает ли идентификатор текущего элемента книги с идентификатором первого элемента книги с тем же автором. Переменная $author берется из текущей книги. Ключ используется для поиска элементов <book> с тем же автором, предикат [1] берет первый. В результате <H1> генерируется только для первого появления этого конкретного автора, и в том же элементе if мы затем применяем шаблон для перечисления книг этого автора. Режим используется, чтобы избежать конфликта между этими шаблонами. Без сомнения, есть решение, которое не использует режимы, но это тоже работает. Вы также можете сделать многое из этого с помощью <xsl:for-each>, но я сделал отдельные шаблоны, потому что XSLT является декларативным и лучше всего работает, когда он обрабатывается как таковой.

Группировка намного проще в XSLT 2, но когда вы застряли в XSLT 1, метод группировки по Мюнху часто дает решение, как только вы его разберетесь.

person G_H    schedule 31.08.2016
comment
Тем временем я нашел решение, похожее на подход Рудрамуни ТП, основанный на preceding. Так какой подход лучше? мюнхенские группировки или preceding? - person Lugaxx; 31.08.2016
comment
@Lugaxx Если оба выдают правильный результат для всех возможных сценариев входных документов, было бы лучше выбрать то, что имеет наилучшую производительность с точки зрения времени выполнения и использования памяти. Эти различия могут быть незначительными для небольших входных данных, но более заметными для больших документов. Трудно предсказать разницу в производительности между обоими подходами, и некоторые из них будут зависеть от внутренних компонентов процессора XSLT, поэтому, возможно, попробуйте оба с самым большим XML, который вы ожидаете увидеть, и выберите наиболее эффективный. - person G_H; 31.08.2016
comment
@Lugaxx, ответ G_H идеален, учти это. - person Rudramuni TP; 31.08.2016
comment
На самом деле мне нужно преобразовать XML-код в JSON. Поэтому мне нужно пропустить запятую (,) за последним элементом. Я знаю, как это сделать, используя for-each (подход Рудрамуни ТП). Но как бы вы сделали это, используя метод мюнхианской группировки? - person Lugaxx; 31.08.2016
comment
@Lugaxx Выведите запятую в условном выражении xsl:if. Вы можете снова использовать трюк с генерацией идентификатора, чтобы узнать, является ли книга последней для ее автора, например: <xsl:if test="generate-id(.) != generate-id(//book[@author = $author][last()])">. Вам нужно будет снова заполнить переменную автора в шаблоне книги или передать ее из другого в качестве параметра. - person G_H; 01.09.2016

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

XSLT2:

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

<xsl:template match="/">
 <HTML>
    <BODY>
        <xsl:for-each-group select="books/book" group-by="@author">
            <H1><xsl:value-of select="current-grouping-key()"/></H1>
             <xsl:for-each select="current-group()">
               <DIV><xsl:value-of select="@title"/></DIV>
             </xsl:for-each>
        </xsl:for-each-group>
    </BODY>
    </HTML>
</xsl:template>

</xsl:stylesheet>
person Rudramuni TP    schedule 31.08.2016
comment
Идея XSLT 1 могла бы сработать, но эта реализация ведет себя неправильно, если книги одного и того же автора не все находятся в одном прогоне во входном документе. Например, если книга написана автором а, затем автором б, а затем снова а, третья книга будет неправильно указана под автором б. - person G_H; 31.08.2016