XSL группировка xml по элементам - PullRequest
0 голосов
/ 12 октября 2010

Я работаю с XSLT1.0 (процессор не может обработать 2.0) и у меня возникла проблема при попытке сгруппировать выходные данные структуры xml:



текст заказа 1





текст оплаты 1





некоторый текст заказа 2





некоторые контактные данные 1





некоторые контактные данные 2



Сегодня мы выбираем все строки и вызываем шаблон применения для каждого (каждый тип имеет свой собственный шаблон, который записывает свое тело), ​​который создает вывод, например:

Заказ: некоторый текст заказа1
Заказ: некоторый заказ text2
Оплата: некоторая оплата text1
Контакт: некоторые контактные данные1
Контакт: некоторые контактные данные2

Но я хотел бы (в XSLT 1.0) сгруппировать вывод так, чтобы:

Заказать

  1. текст заказа1
  2. некоторый текст заказа2

Оплата

  1. текст оплаты1

Контакт

  1. некоторые контактные данные1
  2. некоторые контактные данные2

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

EDIT

Ты, некоторые отличные ответы, как изменилось бы решение группирования Мюнхена, если бы у меня была структура, скажем,

<customers>
  <person>
    <row>....</row> (row is same as above)
    <row>....</row>
  </person>
  <person>
    <row>....</row>
    <row>....</row>
    <row>....</row>
  </person>

Тогда ключ:

  <xsl:key name="type" match="row/*" use="local-name()"/>

Выбрал бы все строки для всех людей, а это не то, что я хотел. Спасибо за отличные ответы.

Ответы [ 4 ]

4 голосов
/ 12 октября 2010

Для этого в XSLT 1.0 вам нужно использовать мюнхенскую группировку , но ее проще (на мой взгляд) решить с помощью xsl:for-each-group в XSLT 2.0.

Следующая таблица стилей XSLT 1.0будет делать то, что вы просите, ключ должен использовать ключ (doh!), который позволит вам сгруппировать по узлам локальное имя.

Ввод:

<?xml version="1.0" encoding="UTF-8"?>
<root>
  <row>
    <order>
      <text>some order text 1</text>
    </order>
  </row>

  <row>
    <payment>
      <text>some payment text 1</text>
    </payment>
  </row>

  <row>
    <order>
      <text>some order text 2</text>
    </order>
  </row>

  <row>
    <contact>
      <text>some contact details 1</text>
    </contact>
  </row>

  <row>
    <contact>
      <text>some contact details 2</text>
    </contact>
  </row>
</root>

XSLT:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:output method="text"/>
  <xsl:key name="type" match="row/*" use="local-name()"/>

  <xsl:template match="root">
    <xsl:for-each select="row/*[
      generate-id() = generate-id(key('type', local-name())[1])]">
      <xsl:value-of select="local-name()"/>
      <xsl:text>&#x0a;</xsl:text>
      <xsl:for-each select="key('type', local-name())">
        <xsl:value-of select="concat('  ', position(), '. ')"/>
        <xsl:apply-templates select="text"/>
        <xsl:text>&#x0a;</xsl:text>
      </xsl:for-each>
    </xsl:for-each>
  </xsl:template>

</xsl:stylesheet>

Выход:

order
  1. some order text 1
  2. some order text 2
payment
  1. some payment text 1
contact
  1. some contact details 1
  2. some contact details 2
1 голос
/ 12 октября 2010

Помимо хороших ответов с методом группировки, эта таблица стилей:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text"/>
    <xsl:variable name="vSort" select="'|order|payment|contact|'"/>
    <xsl:template match="/">
        <xsl:apply-templates select="*/row">
            <xsl:sort select="string-length(
                                 substring-before($vSort,
                                                  concat('|',
                                                         name(),
                                                         '|')))"/>
        </xsl:apply-templates>
    </xsl:template>
    <xsl:template match="row/*">
        <xsl:variable name="vName" select="name()"/>
        <xsl:variable name="vNumber">
            <xsl:number level="any" count="*[name()=$vName]" from="/"/>
        </xsl:variable>
        <xsl:if test="$vNumber = 1">
            <xsl:value-of select="concat(translate(substring(name(),1,1),
                                                       'opc',
                                                       'OPC'),
                                             substring(name(),2),
                                             '&#xA;')"/>
        </xsl:if>
        <xsl:value-of select="concat($vNumber,'. ',text,'&#xA;')"/>
    </xsl:template>
</xsl:stylesheet>

Выход (с правильно сформированным входом):

Order
1.  some order text 1
Payment
1.  some payment text 1
2.  some order text 2
Contact
1.  some contact details 1
2.  some contact details 2
1 голос
/ 12 октября 2010

Опираясь на ответ Флинна ...

Если у вас есть этот шаблон для родителя (не показан в вашем примере):

<xsl:template match="row-parent">
  <xsl:apply-templates select="row">
    <xsl:sort select="name(*[1])" />
  </xsl:apply-templates>
</xsl:template>

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

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

<xsl:template match="row">
   <xsl:variable name="childName" select="name(*[1])"/>
   <!-- if this is the first row with an element child of this name -->
   <xsl:if test="not(preceding-sibling::row[name(*[1]) = $childName])">
      <xsl:value-of select="concat('&#10;',
         translate(substring($childName, 1, 1), $lower, $upper),
         substring($childName, 2), '&#10;&#10;')"/>
   </xsl:if>

Затем выведите данные для каждой строки этой группы в нужном вам формате:

   <xsl:number level="any" count="row[name(*[1]) = $childName]" format=" 1. "
      from="row-parent"/>
   <xsl:value-of select="normalize-space(*[1])"/>
   <xsl:text>&#10;</xsl:text>
</xsl:template>

Как обычно, $ lower и $ upper определяются в верхней части шаблона (или таблицы стилей) как

<xsl:variable name="lower" select="'abcdefghijklmnopqrstuvwxyz'"/>
<xsl:variable name="upper" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"/>

И заставить таблицу стилей использовать метод вывода text:

<xsl:output method="text"/>

Вывод вышеуказанной таблицы стилей на ваш ввод (в оболочке <row-parent>):

Contact

 1. some contact details 1
 2. some contact details 2

Order

 1. some order text 1
 2. some order text 2

Payment

 1. some payment text 1

В качестве альтернативы и более надежно, вы можете использовать Muenchian grouping : сначала сгруппировать строки по имени дочернего элемента, затем (вывести заголовок для каждой группы и) обработать все строки в группе.

1 голос
/ 12 октября 2010

Попробуйте:

<xsl:template match="(parent element-whatever contains the 'row' elements)">
  <xsl:apply-templates>
    <xsl:sort select="name(*)" />
  </xsl:apply-templates>
</xsl:template>

Сортирует элементы строки по имени первого потомка.

Этот шаблон добавляет в заголовок:

<xsl:template match="row">
    <xsl:copy>
    <xsl:if test="not(preceding-sibling::*[name(*) = name(current()/*)])">
      <!-- Output header here -->
      <xsl:value-of select="name(*)" />
    </xsl:if>
    <xsl:apply-templates select="@* | node()"/>
  </xsl:copy>
</xsl:template>

Тест в основном говорит: «Выведите это, если нет предыдущих братьев и сестер с тем же именем».

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