Изменение, с помощью xsl, иерархии элементов путем группировки элементов по значениям атрибутов без предположения о значениях атрибутов?
Вопрос
С xsl, как изменить иерархию элементов путем группировки элементов по значениям атрибутов, без предположения о значениях атрибутов?
Описание проблемы
Контекст документа следующий: xml отслеживает изменения (<releaseHistory/>
) структуры программного обеспечения по мере выпуска новых версий (<build/>
). Эта структура имеет несколько приложений / компонентов (<changes app='LibraryA|Driver|...'/>
). В заметках об изменениях записываются новые функции или исправления ошибок (<list kind='New|Enhancement'/>
).
Я хотел бы преобразовать этот документ так, чтобы все заметки об изменениях в разных сборках были объединены в списки, сгруппированные по значению атрибута 'app' и значениям атрибута 'kind', с элементами списка (<li/>
) отсортировано по атрибуту priority.
Кроме того, не следует делать никаких предположений относительно значений атрибутов "app" и "kind". Обратите внимание, что при необходимости я могу изменить схему XML, если она не идеальна.
Текущее состояние
- Что я смог сделать:
- получить список уникальных значений атрибутов app и kind.
- шаблон, который принимает в качестве параметров "приложение" и "вид" и пересекает документ XML, чтобы объединить все элементы, атрибут которых соответствует аргументам
- Чего не хватает:
- "зацикливание" на приведенном выше списке уникальных значений атрибутов и применение шаблона
Ввод и ожидаемый результат
XML-документ:
<?xml version="1.0" encoding="UTF-8"?>
<releaseHistory>
<build>
<description>A killer update</description>
<changes app='LibraryA'>
<list kind='New'>
<li priority='4'>Added feature about X</li>
<li priority='2'>Faster code for big matrices</li>
</list>
<list kind='Enhancement'>
<li priority='1'>Fixed integer addition</li>
</list>
</changes>
<changes app='Driver'>
<list kind='New'>
<li priority='3'>Supporting new CPU models</li>
<li priority='4'>Cross-platform-ness</li>
</list>
</changes>
</build>
<build>
<description>An update for Easter</description>
<changes app='LibraryA'>
<list kind='New'>
<li priority='1'>New feature about Y</li>
</list>
<list kind='Enhancement'>
<li priority='2'>Fixed bug 63451</li>
</list>
</changes>
<changes app='LibraryVector'>
<list kind='Enhancement'>
<li priority='5'>Fixed bug 59382</li>
</list>
</changes>
<changes app='Driver'>
<list kind='New'>
<li priority='0'>Compatibility with hardware Z</li>
</list>
</changes>
</build>
</releaseHistory>
Ожидаемый документ:
<?xml version="1.0" encoding="UTF-8"?>
<mergedHistory>
<changes app='LibraryA'>
<list kind='New'>
<li priority='1'>New feature about Y</li>
<li priority='2'>Faster code for big matrices</li>
<li priority='4'>Added feature about X</li>
</list>
<list kind='Enhancement'>
<li priority='1'>Fixed integer addition</li>
<li priority='2'>Fixed bug 63451</li>
</list>
</changes>
<changes app='Driver'>
<list kind='New'>
<li priority='0'>Compatibility with hardware Z</li>
<li priority='3'>Supporting new CPU models</li>
<li priority='4'>Cross-platform-ness</li>
</list>
</changes>
<changes app='LibraryVector'>
<list kind='Enhancement'>
<li priority='5'>Fixed bug 59382</li>
</list>
</changes>
</mergedHistory>
Часть решения
Я "уже" могу перечислить уникальные атрибуты "app" и "kind" с помощью xsl. Давайте подробно опишем текущее состояние xsl
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common" extension-element-prefixes="exsl"
>
Получить все отдельные значения атрибутов приложения (LibraryA,Driver,...) <changes app='...'/>
и сохраняя их в переменной (может быть параметром):
<xsl:key name="appDistinct" match="changes" use="@app"/>
<xsl:variable name="applicationListVarTmp">
<list>
<xsl:for-each select="//changes[generate-id() = generate-id(key('appDistinct', @app)[1])]">
<li>
<xsl:value-of select="normalize-space(@app)"/>
</li>
</xsl:for-each>
</list>
</xsl:variable>
Извлечь все различные значения атрибута kind (New, Enhancement) <list kind='...'/>
:
<xsl:key name="kindDistinct" match="changes/list" use="@kind"/>
<xsl:variable name="kindListVar">
<list>
<xsl:for-each select="//changes/list[generate-id() = generate-id(key('kindDistinct', @kind)[1])]">
<li>
<xsl:value-of select="normalize-space(@kind)"/>
</li>
</xsl:for-each>
</list>
</xsl:variable>
Шаблон для объединения всех <li/>
данного "приложения" и "вида" (упорядоченные по приоритету) с параметрами:
<xsl:template name="mergeSameKindChangesForAnApp">
<xsl:param name="application" />
<xsl:param name="kindness" />
<list><xsl:attribute name='kind'><xsl:value-of select="$kindness"/></xsl:attribute>
<xsl:for-each select="//changes[@app=$application]/list[@kind=$kindness]/li">
<xsl:sort select="@priority" data-type="number" order="ascending"/>
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:copy-of select="./*"/>
</xsl:copy>
</xsl:for-each>
</list>
</xsl:template>
Теперь, где я застрял, о "зацикливание" на appListVar
а также kindListVar
применить шаблон.
Если бы все "app" и "kind" были жестко закодированы, я мог бы сделать несколько звонков, например:
<xsl:call-template name="mergeSameKindChangesForAnApp">
<changes app='LibraryA'>
<xsl:with-param name="application">
LibraryA
</xsl:with-param>
<xsl:with-param name="kindness">
New
</xsl:with-param>
</changes>
</xsl:call-template>
но я хотел бы зациклить на "приложения и" вид, найденный в документе XML. С exsl:node-set()
например, я мог бы сделать
<xsl:param name="applicationListVar" select="exsl:node-set($applicationListVarTmp)" />
<xsl:call-template name="mergeSameKindChangesForAnApp">
<changes app='LibraryA'>
<xsl:with-param name="application">
<xsl:value-of select="$applicationListVar/list/li[2]"/>
</xsl:with-param>
<xsl:with-param name="kindness">
New
</xsl:with-param>
</changes>
</xsl:call-template>
но все же, как зацикливаться на $applicationListVar/list/li
элементы? "Зацикливание" не звучит xslt-ilish, может быть (наверняка?) Это не правильный подход.
Вопрос длинный, я попытался упростить его по сравнению с реальным делом.
1 ответ
Это должно сделать это:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:key name="kChange" match="changes" use="@app" />
<!-- A key for locating <list>s by the combination of their @app and @kind-->
<xsl:key name="kList" match="changes/list" use="concat(../@app, '+', @kind)" />
<!-- A node-set of the first instance of each <list> for each distinct
pair of @app + @kind -->
<xsl:variable name="distinctLists"
select="//changes/list[generate-id() =
generate-id(key('kList',
concat(../@app, '+', @kind) )[1]
)]"/>
<!-- Identity template -->
<xsl:template match="@* | node()">
<xsl:copy>
<xsl:apply-templates select="@* | node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="/*">
<mergedHistory>
<!-- Apply templates on distinct <changes> elements -->
<xsl:apply-templates select="build/changes[generate-id() =
generate-id(key('kChange', @app)[1])]" />
</mergedHistory>
</xsl:template>
<!-- Each distinct <changes> (based on @app) will be sent to this template -->
<xsl:template match="changes">
<changes>
<xsl:apply-templates select="@*" />
<!-- Apply templates on each distinct <list> with the same @app
as the current context-->
<xsl:apply-templates select="$distinctLists[../@app = current()/@app]" />
</changes>
</xsl:template>
<!-- Each distinct <list> (based on @app and @kind) will be
sent to this template -->
<xsl:template match="list">
<list>
<xsl:apply-templates select="@*" />
<!-- Apply templates on all <li>s below <list>s with the same @app and @kind
as the current one -->
<xsl:apply-templates select="key('kList', concat(../@app, '+', @kind))/li">
<xsl:sort select="@priority" order="ascending" data-type="number"/>
</xsl:apply-templates>
</list>
</xsl:template>
</xsl:stylesheet>
Метод, который следует здесь отметить, заключается в том, чтобы иметь ключ для элементов, основанный на паре значений, а не только на одном значении, и использовать его для нахождения разных экземпляров на основе пары значений, а затем находить все экземпляры с одинаковой парой значений,
Когда это выполняется на вашем вводном примере, он выдает запрошенный вывод:
<mergedHistory>
<changes app="LibraryA">
<list kind="New">
<li priority="1">New feature about Y</li>
<li priority="2">Faster code for big matrices</li>
<li priority="4">Added feature about X</li>
</list>
<list kind="Enhancement">
<li priority="1">Fixed integer addition</li>
<li priority="2">Fixed bug 63451</li>
</list>
</changes>
<changes app="Driver">
<list kind="New">
<li priority="0">Compatibility with hardware Z</li>
<li priority="3">Supporting new CPU models</li>
<li priority="4">Cross-platform-ness</li>
</list>
</changes>
<changes app="LibraryVector">
<list kind="Enhancement">
<li priority="5">Fixed bug 59382</li>
</list>
</changes>
</mergedHistory>