XML,XSLT 按标题对具有 Child 个节点的相同元素进行分组
XML, XSLT Group Same Elements by Title With Child Nodes
我正在尝试根据 wkhtmltopdf
的大纲 XML 输出构建一个 appendix/index 页面。
有没有办法在不使用 key() 函数或 XSLT 2.0 for-each-group 的情况下循环遍历元素并按特定属性值对它们进行分组?这是因为在 wkhtmltopdf 中使用的 XSL 处理器有一些限制。
我正在考虑使用 preceding-sibling 检查标题是否仍然相同。
<xsl:for-each select="//o:item">
<xsl:sort select="@title"></xsl:sort>
<xsl:variable name="key" select="@title" />
<xsl:if test="not(preceding-sibling::o:item[@title=$key])">
<xsl:value-of select="$key"></xsl:value-of>
<xsl:for-each select="current()/o:item">
<xsl:element name="{@title}" />
</xsl:for-each>
</xsl:if>
</xsl:for-each>
我对 XSLT 还很陌生,因此非常感谢任何帮助。
这是来自 wkhtmltopdf 的大纲 xml:
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="toc.xsl"?>
<outline xmlns="http://wkhtmltopdf.org/outline">
<item title="PDF" page="0" link="__WKANCHOR_0" backLink="__WKANCHOR_1">
<item title="Type1" page="1" link="__WKANCHOR_2" backLink="__WKANCHOR_3">
<item title="SubType1" page="1" link="__WKANCHOR_4" backLink="__WKANCHOR_5">
<item title="Collection1" page="1" link="__WKANCHOR_6" backLink="__WKANCHOR_7">
<item title="Item1" page="1" link="__WKANCHOR_8" backLink="__WKANCHOR_9"/>
</item>
<item title="Collection2" page="1" link="__WKANCHOR_a" backLink="__WKANCHOR_b">
<item title="Item2" page="1" link="__WKANCHOR_c" backLink="__WKANCHOR_d"/>
<item title="Item3" page="2" link="__WKANCHOR_e" backLink="__WKANCHOR_f"/>
</item>
</item>
<item title="SubType2" page="3" link="__WKANCHOR_g" backLink="__WKANCHOR_h">
<item title="Collection1" page="3" link="__WKANCHOR_i" backLink="__WKANCHOR_j">
<item title="Item4" page="3" link="__WKANCHOR_k" backLink="__WKANCHOR_l"/>
</item>
</item>
</item>
<item title="Type2" page="4" link="__WKANCHOR_m" backLink="__WKANCHOR_n">
<item title="SubType1" page="4" link="__WKANCHOR_o" backLink="__WKANCHOR_p">
<item title="Collection1" page="5" link="__WKANCHOR_u" backLink="__WKANCHOR_v">
<item title="Item5" page="4" link="__WKANCHOR_q" backLink="__WKANCHOR_r"/>
</item>
</item>
<item title="SubType3" page="5" link="__WKANCHOR_s" backLink="__WKANCHOR_t">
<item title="Collection3" page="5" link="__WKANCHOR_u" backLink="__WKANCHOR_v">
<item title="Item6" page="5" link="__WKANCHOR_w" backLink="__WKANCHOR_x"/>
<item title="Item7" page="5" link="__WKANCHOR_y" backLink="__WKANCHOR_z"/>
<item title="Item8" page="5" link="__WKANCHOR_10" backLink="__WKANCHOR_11"/>
</item>
</item>
</item>
</item>
</outline>
预期输出是(将所有不同的第 4 项 child 项分组):
<Collection1>
<Item1></Item1>
<Item4></Item4>
<Item5></Item5>
</Collection1>
<Collection2>
<Item2></Item2>
<Item3></Item3>
</Collection2>
<Collection3>
<Item6></Item6>
<Item7></Item7>
<Item8></Item8>
</Collection3>
您可以在 @title[contains(., 'Collection')]
上使用 <xsl:for-each-group>
和 group-by
来准备组,然后在 current-group()
上循环获取元素。
请尝试以下 XSLT 2.0 解决方案
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:o="http://wkhtmltopdf.org/outline">
<xsl:output method="xml" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="/">
<xsl:for-each-group select="//o:item" group-by="@title[contains(., 'Collection')]">
<xsl:element name="{current-grouping-key()}">
<xsl:for-each select="current-group()/o:item">
<xsl:element name="{@title}" />
</xsl:for-each>
</xsl:element>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
如果使用 XSLT 1.0,则必须定义 <xsl:key>
,然后循环应该 运行 分组元素。下面是 XSLT 1.0 解决方案。
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:o="http://wkhtmltopdf.org/outline">
<xsl:output method="xml" />
<xsl:strip-space elements="*" />
<xsl:key name="kTitle" match="//o:item" use="@title[contains(.,'Collection')]" />
<xsl:template match="/">
<xsl:for-each select="//o:item[generate-id() = generate-id(key('kTitle', @title[contains(.,'Collection')])[1])]">
<xsl:element name="{@title}">
<xsl:for-each select="key('kTitle', @title[contains(.,'Collection')])/o:item">
<xsl:element name="{@title}" />
</xsl:for-each>
</xsl:element>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
输出
<Collection1>
<Item1/>
<Item4/>
<Item5/>
</Collection1>
<Collection2>
<Item2/>
<Item3/>
</Collection2>
<Collection3>
<Item6/>
<Item7/>
<Item8/>
</Collection3>
我正在尝试根据 wkhtmltopdf
的大纲 XML 输出构建一个 appendix/index 页面。
有没有办法在不使用 key() 函数或 XSLT 2.0 for-each-group 的情况下循环遍历元素并按特定属性值对它们进行分组?这是因为在 wkhtmltopdf 中使用的 XSL 处理器有一些限制。
我正在考虑使用 preceding-sibling 检查标题是否仍然相同。
<xsl:for-each select="//o:item">
<xsl:sort select="@title"></xsl:sort>
<xsl:variable name="key" select="@title" />
<xsl:if test="not(preceding-sibling::o:item[@title=$key])">
<xsl:value-of select="$key"></xsl:value-of>
<xsl:for-each select="current()/o:item">
<xsl:element name="{@title}" />
</xsl:for-each>
</xsl:if>
</xsl:for-each>
我对 XSLT 还很陌生,因此非常感谢任何帮助。
这是来自 wkhtmltopdf 的大纲 xml:
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="toc.xsl"?>
<outline xmlns="http://wkhtmltopdf.org/outline">
<item title="PDF" page="0" link="__WKANCHOR_0" backLink="__WKANCHOR_1">
<item title="Type1" page="1" link="__WKANCHOR_2" backLink="__WKANCHOR_3">
<item title="SubType1" page="1" link="__WKANCHOR_4" backLink="__WKANCHOR_5">
<item title="Collection1" page="1" link="__WKANCHOR_6" backLink="__WKANCHOR_7">
<item title="Item1" page="1" link="__WKANCHOR_8" backLink="__WKANCHOR_9"/>
</item>
<item title="Collection2" page="1" link="__WKANCHOR_a" backLink="__WKANCHOR_b">
<item title="Item2" page="1" link="__WKANCHOR_c" backLink="__WKANCHOR_d"/>
<item title="Item3" page="2" link="__WKANCHOR_e" backLink="__WKANCHOR_f"/>
</item>
</item>
<item title="SubType2" page="3" link="__WKANCHOR_g" backLink="__WKANCHOR_h">
<item title="Collection1" page="3" link="__WKANCHOR_i" backLink="__WKANCHOR_j">
<item title="Item4" page="3" link="__WKANCHOR_k" backLink="__WKANCHOR_l"/>
</item>
</item>
</item>
<item title="Type2" page="4" link="__WKANCHOR_m" backLink="__WKANCHOR_n">
<item title="SubType1" page="4" link="__WKANCHOR_o" backLink="__WKANCHOR_p">
<item title="Collection1" page="5" link="__WKANCHOR_u" backLink="__WKANCHOR_v">
<item title="Item5" page="4" link="__WKANCHOR_q" backLink="__WKANCHOR_r"/>
</item>
</item>
<item title="SubType3" page="5" link="__WKANCHOR_s" backLink="__WKANCHOR_t">
<item title="Collection3" page="5" link="__WKANCHOR_u" backLink="__WKANCHOR_v">
<item title="Item6" page="5" link="__WKANCHOR_w" backLink="__WKANCHOR_x"/>
<item title="Item7" page="5" link="__WKANCHOR_y" backLink="__WKANCHOR_z"/>
<item title="Item8" page="5" link="__WKANCHOR_10" backLink="__WKANCHOR_11"/>
</item>
</item>
</item>
</item>
</outline>
预期输出是(将所有不同的第 4 项 child 项分组):
<Collection1>
<Item1></Item1>
<Item4></Item4>
<Item5></Item5>
</Collection1>
<Collection2>
<Item2></Item2>
<Item3></Item3>
</Collection2>
<Collection3>
<Item6></Item6>
<Item7></Item7>
<Item8></Item8>
</Collection3>
您可以在 @title[contains(., 'Collection')]
上使用 <xsl:for-each-group>
和 group-by
来准备组,然后在 current-group()
上循环获取元素。
请尝试以下 XSLT 2.0 解决方案
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:o="http://wkhtmltopdf.org/outline">
<xsl:output method="xml" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="/">
<xsl:for-each-group select="//o:item" group-by="@title[contains(., 'Collection')]">
<xsl:element name="{current-grouping-key()}">
<xsl:for-each select="current-group()/o:item">
<xsl:element name="{@title}" />
</xsl:for-each>
</xsl:element>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
如果使用 XSLT 1.0,则必须定义 <xsl:key>
,然后循环应该 运行 分组元素。下面是 XSLT 1.0 解决方案。
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:o="http://wkhtmltopdf.org/outline">
<xsl:output method="xml" />
<xsl:strip-space elements="*" />
<xsl:key name="kTitle" match="//o:item" use="@title[contains(.,'Collection')]" />
<xsl:template match="/">
<xsl:for-each select="//o:item[generate-id() = generate-id(key('kTitle', @title[contains(.,'Collection')])[1])]">
<xsl:element name="{@title}">
<xsl:for-each select="key('kTitle', @title[contains(.,'Collection')])/o:item">
<xsl:element name="{@title}" />
</xsl:for-each>
</xsl:element>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
输出
<Collection1>
<Item1/>
<Item4/>
<Item5/>
</Collection1>
<Collection2>
<Item2/>
<Item3/>
</Collection2>
<Collection3>
<Item6/>
<Item7/>
<Item8/>
</Collection3>