2016-12-09 3 views
0

Пусть есть это XML-фрагмент:Как группировать несколько соседних элементов по столбцам в XSLT?

<a>t1</a> <a>t2</a> <b>t3</b> <b>t4</b> <b>t5</b> <c>t6</c> 

Мы могли бы представить его как:

<a>t1</a> <a>t2</a> 
<b>t3</b> <b>t4</b> <b>t5</b> 
<c>t6</c> 

Я хочу итерацию, так что элементы итерированными в первом проходе являются:

<a>t1</a> <b>t3</b> <c>t6</c> 

на втором плане:

<a>t2</a> <b>t4</b> 

и третий:

<b>t5</b> 

Приведенные выше данные только один пример. Возможно иметь более длинные последовательности соседних братьев и сестер, а не только этот фиксированный набор данных.

Требование состоит в том, чтобы каждая группа содержала элементы, которые имеют одинаковое количество предыдущих братьев и сестер, имеющих одно и то же имя элемента.

Например, в первом столбце «» < а/>, < б/> и < с/> не имеют предыдущих братьев с тем же названием, соответственно.

Второй «столбец» < а/> и < б/у > рассчитывать предшествующие родственный с тем же именем 1, соответственно.

Я хочу, чтобы иметь возможность перебирать элементы таким образом в инструкции для каждой группы, но я не уверен, как выразить предложение group-by.

+0

Это неясно. Между первым и вторым блоками нет семантической разницы. Что вы подразумеваете под «группой»? Вам нужно показать _output_, который вы хотите создать из ввода. Просто вставка новых строк после каждой строки тегов с тем же именем не является значимым преобразованием. –

+0

Я, с надеждой, уточнил описание вопроса. – dave

ответ

2

Шаблон

<xsl:template match="div"> 
    <xsl:for-each-group select="*" group-by="count(preceding-sibling::*[node-name(.) = node-name(current())])"> 
     <group key="{current-grouping-key()}"> 
      <xsl:copy-of select="current-group()"/> 
     </group> 
    </xsl:for-each-group> 
</xsl:template> 

превращает

<div> 
    <a>t1</a> <a>t2</a> <b>t3</b> <b>t4</b> <b>t5</b> <c>t6</c> 
</div> 

в

<group key="0"> 
    <a>t1</a> 
    <b>t3</b> 
    <c>t6</c> 
</group> 
<group key="1"> 
    <a>t2</a> 
    <b>t4</b> 
</group> 
<group key="2"> 
    <b>t5</b> 
</group> 
0

Принятая в настоящее время ответ имеет O (N^2) временную сложность, так как он использует preceding-sibling::* для каждого элемента ,

Вот решение, которое может быть более эффективным - нет preceding-sibling::* оси используется:

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

    <xsl:template match="/*"> 
    <xsl:variable name="vTop" select="."/> 
    <xsl:variable name="vNames" select="distinct-values(*/name())"/> 
    <xsl:variable name="vCountNames" select="count($vNames)"/> 

    <xsl:for-each select="1 to $vCountNames"> 
     <xsl:variable name="vCol" select="position()"/> 
     <xsl:for-each select="$vNames"> 
     <xsl:apply-templates select="$vTop/*[name() eq current()][$vCol]"/> 
     </xsl:for-each> 
    </xsl:for-each> 
    </xsl:template> 
</xsl:stylesheet> 

Когда это преобразование применяется на следующий документ XML (Предоставленный фрагмент, окруженный верхней (документ) элемент):

<t> 
<a>t1</a> <a>t2</a> 
<b>t3</b> <b>t4</b> <b>t5</b> 
<c>t6</c> 
</t> 

Требуемая результат получается (значения каждого е lement при проходимом столбцах):

t1t3t6t2t4t5 

Это решение O(N * M), где N есть число элементов, и М представляет собой число их различных названий.

Таким образом, если N = k times M, то это решение будет асимптотически k раз быстрее, чем решение O(N^2).


II. Один, чистый XPath 2.0 выражения посещения элементы в столбцам образом:

for $vTop in /*, 
     $vCol in 1 to count(distinct-values($vTop/*/name())), 
     $vName in distinct-values($vTop/*/name()) 
    return 
     $vTop/*[name() eq $vName][$vCol] 

XSLT на основе проверки:

<xsl:template match="/*"> 
    <xsl:sequence select= 
    "for $vTop in /*, 
      $vCol in 1 to count(distinct-values($vTop/*/name())), 
      $vName in distinct-values($vTop/*/name()) 
     return 
      $vTop/*[name() eq $vName][$vCol] 
    "/> 
    </xsl:template> 
</xsl:stylesheet> 

При нанесении на том же самом документе XML , это преобразование оценивает выражение XPath и выводит результат этой оценки:

t1t3t6t2t4t5 

III. XSLT 1.0 решение:

Это преобразование:

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

<xsl:key name="kByName" match="/*/*" use="name()"/> 

<xsl:variable name="vDistinctNamed" select= 
    "/*/*[generate-id() = generate-id(key('kByName', name())[1])]"/> 

    <xsl:variable name="vNumCols"> 
    <xsl:for-each select="/*/*[generate-id() = generate-id(key('kByName', name())[1])]"> 
     <xsl:sort select= 
     "count(key('kByName', name()))" data-type="number" order="descending"/> 
     <xsl:if test="position()=1"> 
     <xsl:value-of select="count(key('kByName', name()))"/> 
     </xsl:if> 
    </xsl:for-each> 
    </xsl:variable> 

    <xsl:template match="/*"> 
    <xsl:for-each select="*[not(position() > $vNumCols)]"> 
     <xsl:variable name="vCol" select="position()"/> 
     <xsl:for-each select="$vDistinctNamed"> 
     <xsl:variable name="vthisElement" select="/*/*[name() = name(current())][$vCol]"/> 
     <xsl:if test="$vthisElement"> 
      <xsl:value-of select="concat(/*/*[name() = name(current())][$vCol],', ')"/> 
     </xsl:if> 
     </xsl:for-each> 
    </xsl:for-each> 
    </xsl:template> 
</xsl:stylesheet> 

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

t1, t3, t6, t2, t4, t5, 
Смежные вопросы