Создание контекстных данных для первого и последнего вхождения каждого значения элемента

StackOverflow https://stackoverflow.com/questions/807578

  •  03-07-2019
  •  | 
  •  

Вопрос

Учитывая следующий xml-файл:

<container>
    <val>2</val>
    <id>1</id>
</container>
<container>
    <val>2</val>
    <id>2</id>
</container>
<container>
    <val>2</val>
    <id>3</id>
</container>
<container>
    <val>4</val>
    <id>1</id>
</container>
<container>
    <val>4</val>
    <id>2</id>
</container>
<container>
    <val>4</val>
    <id>3</id>
</container>

Я бы хотел вернуть что-то вроде

2 - 1
2 - 3
4 - 1
4 - 3

Используя набор узлов, я смог получить последнее вхождение через:

exsl:node-set($list)/container[not(val = following::val)]

но я не могу понять, как получить первый вариант.

Это было полезно?

Решение

Чтобы получить первое и последнее вхождение (порядок документов) в каждом "<val>" группа, вы можете использовать <xsl:key> вот так:

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

  <xsl:key name="ContainerGroupByVal" match="container" use="val" />

  <xsl:variable name="ContainerGroupFirstLast" select="//container[
    generate-id() = generate-id(key('ContainerGroupByVal', val)[1])
    or
    generate-id() = generate-id(key('ContainerGroupByVal', val)[last()])
  ]" />

  <xsl:template match="/">
    <xsl:for-each select="$ContainerGroupFirstLast">
      <xsl:value-of select="val" />
      <xsl:text> - </xsl:text>
      <xsl:value-of select="id" />
      <xsl:value-of select="'&#10;'" /><!-- LF -->
    </xsl:for-each>
  </xsl:template>

</xsl:stylesheet>

ПРАВКА №1:Небольшое объяснение, поскольку это может быть не сразу очевидно:

  • Тот Самый <xsl:key> возвращает все <container> узлы , имеющие заданный <val>.Вы используете key() функция для запроса к нему.
  • Тот Самый <xsl:variable> вот где все это происходит.Это читается как:
    • для каждого из <container> узлы в документе ("//container") проверить…
    • ... если у него тот же уникальный идентификатор (generate-id()) как первый узел, возвращенный key() или последний узел, возвращенный key()
    • где key('ContainerGroupByVal', val) возвращает набор <container> узлы, соответствующие текущему <val>
    • если уникальные идентификаторы совпадают, включите узел в выборку
  • тот самый <xsl:for-each> выполняет вывод.С таким же успехом это могло бы быть <xsl:apply-templates>.

ПРАВКА №2:Как справедливо отмечает Димитр Новатчев в комментариях, вам следует с осторожностью относиться к использованию "//- Сокращенный вариант XPath.Если вы можете избежать этого, сделайте это во что бы то ни стало — отчасти потому, что это потенциально выбирает ненужные вам узлы, и главным образом потому, что это медленнее, чем более конкретное выражение XPath.Например, если ваш документ выглядит следующим образом:

<containers>
  <container><!-- ... --></container>
  <container><!-- ... --></container>
  <container><!-- ... --></container>
</containers>

тогда вам следует использовать "/containers/container" или "/*/container" вместо "//container".


ПРАВКА №3:Альтернативный синтаксис из приведенных выше мог бы быть следующим:

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

  <xsl:key name="ContainerGroupByVal" match="container" use="val" />

  <xsl:variable name="ContainerGroupFirstLast" select="//container[
    count(
        .
      | key('ContainerGroupByVal', val)[1]
      | key('ContainerGroupByVal', val)[last()]
    ) = 2
  ]" />

  <xsl:template match="/">
    <xsl:for-each select="$ContainerGroupFirstLast">
      <xsl:value-of select="val" />
      <xsl:text> - </xsl:text>
      <xsl:value-of select="id" />
      <xsl:value-of select="'&#10;'" /><!-- LF -->
    </xsl:for-each>
  </xsl:template>

</xsl:stylesheet>

Объяснение:Оператор объединения XPath "|" объединяет его аргументы в набор узлов.По определению, набор узлов не может содержать повторяющихся узлов — например:". | . | ." создаст набор узлов, содержащий ровно один узел (текущий узел).

Это означает, что если мы создадим набор узлов объединения из текущего узла ("."), то "key(…)[1]" узел и "key(…)[last()]" узел, количество узлов будет равно 2, если (и только если) текущий узел равен одному из двух других узлов, во всех остальных случаях количество будет равно 3.

Другие советы

Базовые модели XPath:

//container[position() = 1]  <- this is the first one
//container[position() = last()]  <- this is the last one

Вот набор из Функции XPath поподробнее.

Я.XSLT 1.0

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

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

    <xsl:key name="kContByVal" match="container"
     use="val"/>

    <xsl:template match="/*">
      <xsl:for-each select=
       "container[generate-id()
                 =
                  generate-id(key('kContByVal',val)[1])
                 ]
       ">

       <xsl:variable name="vthisvalGroup"
        select="key('kContByVal', val)"/>

       <xsl:value-of select=
        "concat($vthisvalGroup[1]/val,
              '-',
              $vthisvalGroup[1]/id,
              '&#xA;'
              )
      "/>
       <xsl:value-of select=
        "concat($vthisvalGroup[last()]/val,
              '-',
              $vthisvalGroup[last()]/id,
              '&#xA;'
              )
        "/>
      </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>

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

<t>
    <container>
        <val>2</val>
        <id>1</id>
    </container>
    <container>
        <val>2</val>
        <id>2</id>
    </container>
    <container>
        <val>2</val>
        <id>3</id>
    </container>
    <container>
        <val>4</val>
        <id>1</id>
    </container>
    <container>
        <val>4</val>
        <id>2</id>
    </container>
    <container>
        <val>4</val>
        <id>3</id>
    </container>
</t>

желаемый результат получен:

2-1
2-3
4-1
4-3

Обратите внимание:

  1. Мы используем метод Мюнх для группировки , чтобы найти один container элемент для каждого набора таких элементов, которые имеют одинаковое значение для val.
  2. Из всего списка узлов container элементы с одинаковыми val значение, мы выводим необходимые данные для первого container элемент в группе и для последнего container элемент в группе.

II.XSLT 2.0

Эта трансформация:

<xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xsl:output method="text"/>

    <xsl:template match="/*">
      <xsl:for-each-group select="container"
           group-by="val">
        <xsl:for-each select="current-group()[1], current-group()[last()]">
          <xsl:value-of select=
           "concat(val, '-', id, '&#xA;')"/>
        </xsl:for-each>
    </xsl:for-each-group>
    </xsl:template>
</xsl:stylesheet>

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

2-1
2-3
4-1
4-3

Обратите внимание:

  1. Использование <xsl:for-each-group> Инструкция XSLT.

  2. Использование current-group() функция.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top