У меня есть этот XML, сгруппированный по странам. При сохранении последовательности узел со значением @country является родительским, а дочерние элементы — это элементы с пустой страной под ним.
<catalog>
<cd>
<country>USA</country>
<artist>Bob Dylan</artist>
</cd>
<cd>
<country></country>
<artist>Percy Sledge</artist>
</cd>
<cd>
<country></country>
<artist>Joe Cocker</artist>
</cd>
<cd>
<country>UK</country>
<artist>Kenny Rogers</artist>
</cd>
<cd>
<country></country>
<artist>Bonnie Tyler</artist>
</cd>
</catalog>
используя xsl 1.0. я хочу сгруппировать их так, чтобы узел с пустой страной был сгруппирован под узлом со страной над ним, как показано ниже.
Это то, что я имел в виду. Первый цикл foreach фильтрует все узлы по стране, а второй для каждого цикла предназначен для поиска всех узлов с одной и той же страной и всех пустых стран под этим узлом. У меня проблемы со вторым для каждого цикла. не мог понять, как сделать цикл.
Я ценю всю помощь. Спасибо.
<table>
<tr > <th>country</th> <th>Artist</th> </tr>
<xsl:for-each select = "catalog/cd[not(country='')]">
<xsl:variable name = "cntry" select = "country"/>
<tr>
<td><xsl:value-of select = "$cntry" /></td>
<xsl:for-each select = "catalog/cd[country='$cntry' or catalog/cd[country='']]">
<td><xsl:value-of select = "artist" /></td>
</xsl:for-each>
</tr>
</xsl:for-each>
</table>
Адаптация ответа (для XSLT 1.0) из https://stackoverflow.com/a/74612904/3016153:
<xsl:stylesheet version = "1.0" xmlns:xsl = "http://www.w3.org/1999/XSL/Transform">
<xsl:output method = "xml" indent = "yes"/>
<xsl:key name = "k1" match = "cd[not(country/text())]" use = "preceding-sibling::cd[country/text()][1]/country" />
<xsl:template match = "/catalog">
<table border = "1">
<tr>
<th>Country</th>
<th>Artist</th>
</tr>
<xsl:for-each select = "cd[country/text()]">
<tr>
<td>
<xsl:value-of select = "country"/>
</td>
<td>
<xsl:for-each select = ". | key('k1', country)">
<xsl:value-of select = "artist"/>
<xsl:if test = "position() != last()">, </xsl:if>
</xsl:for-each>
</td>
</tr>
</xsl:for-each>
</table>
</xsl:template>
</xsl:stylesheet>
Лучше всего это решить с помощью метода, называемого одноуровневой рекурсией, как описано, например, в Группируйте определенные элементы в таблицах с помощью xslt 1.0
Основной подход:
Для родительского элемента примените шаблоны к первому дочернему элементу.
<xsl:template match = "catalog">
<table><xsl:apply-templates select = "cd[1]"/></table>
</xsl:template>
Для дочернего элемента, который должен начать новую группу, создайте группу, обработайте ее первый элемент и после закрытия группы перейдите к следующему элементу начала группы:
<xsl:template match = "cd[normalize-space(country)]">
<tr>
<td><xsl:value-of select = "country"/></td>
<td>
<xsl:value-of select = "artist"/>
<xsl:value-of select = "following-sibling::cd
[1][not(normalize-space(country))]"/>
</td>
</tr>
<xsl:apply-templates select = "following-sibling::cd
[normalize-space(country)][1]"/>
</xsl:template>
Для дочернего элемента, который не должен начинать новую группу, создайте выходные данные и перейдите к следующему родственному элементу, если он не является частью группы:
<xsl:template match = "cd[not(normalize-space(country))]">
<xsl:text>, </xsl:text>
<xsl:value-of select = "artist"/>
<xsl:apply-templates select = "following-sibling::cd
[1][not(normalize-space(country))]"/>
</xsl:template>
Обратите внимание на различие между following-sibling::*[condition][1]
, который выбирает первого следующего брата, удовлетворяющего условию, и following-sibling::*[1][condition]
, который выбирает первого следующего брата, при условии, что он удовлетворяет условию.
Спасибо. Я узнал что-то новое сегодня. Я ценю ваш ответ.
Почему ваш вопрос также помечен как
xslt-2.0
, хотяxsl 1.0
есть и в заголовке, и в тексте? Это тривиальная проблема в XSLT 2.0, где вы можете использоватьxsl:for-each-group
сgroup-starting-with
.