Создание контекстных данных для первого и последнего вхождения каждого значения элемента
Вопрос
Учитывая следующий 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="' '" /><!-- 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="' '" /><!-- 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,
'
'
)
"/>
<xsl:value-of select=
"concat($vthisvalGroup[last()]/val,
'-',
$vthisvalGroup[last()]/id,
'
'
)
"/>
</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
Обратите внимание:
- Мы используем метод Мюнх для группировки , чтобы найти один
container
элемент для каждого набора таких элементов, которые имеют одинаковое значение дляval
. - Из всего списка узлов
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, '
')"/>
</xsl:for-each>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
при применении к тому же XML-документу, что и выше, достигается желаемый результат:
2-1
2-3
4-1
4-3
Обратите внимание:
Использование
<xsl:for-each-group>
Инструкция XSLT.Использование
current-group()
функция.