我想将具有相同 id 的项目合并到单个项目中。结果项目需要具有项目组中的 第一个 非空元素。如果项目组中没有非空元素,则元素本身将从项目组中忽略,并且不应出现在结果项目中。
示例XML输入:
<?xml version="1.0" encoding="UTF-8" ?>
<shiporder orderid="orderid106">
<orderperson id="123">orderperson107</orderperson>
<shipto>
<name>name108</name>
<address>address109</address>
<city>city110</city>
<country>country111</country>
</shipto>
<!--Item Group 100-->
<item>
<id>100</id>
<title>
<first>
<a></a>
</first>
<last>item100_lastTitle1</last>
</title>
<note></note>
<quantity></quantity>
</item>
<item>
<id>100</id>
<title>
<first>
<a>a_100_2</a>
</first>
<last>item100_lastTitle2</last>
</title>
<note>note100_2</note>
<quantity></quantity>
</item>
<item>
<id>100</id>
<title>
<first>
<a att1="abc" att2='cde'>a_100_3</a>
</first>
<last id="1" attr="2">item100_lastTitle3</last>
</title>
<note>note100_3</note>
<quantity>1</quantity>
</item>
<!--Item Group 101-->
<item>
<id>101</id>
<title>
<first>
<a>a_101_1</a>
</first>
<last></last>
</title>
<note>note101_1</note>
<quantity>10</quantity>
</item>
<item>
<id>101</id>
<title>
<first>
<a>a_101_2</a>
</first>
<last>item101_lastTitle2</last>
</title>
<note>note101_2</note>
<quantity>5</quantity>
</item>
<!--Item Group 103-->
<item>
<id>103</id>
<title>
<first>
<a>a_103_2</a>
</first>
<last>item103_lastTitle2</last>
</title>
<note>note103_1</note>
<quantity></quantity>
</item>
</shiporder>
示例XML输出:
<?xml version = '1.0' encoding = 'UTF-8'?>
<shiporder orderid="orderid106">
<item>
<id>100</id>
<title>
<first>
<a>a_100_2</a>
</first>
<last>item100_lastTitle1</last>
</title>
<note>note100_2</note>
<quantity>6</quantity>
</item>
<item>
<id>101</id>
<title>
<first>
<a>a_101_1</a>
</first>
<last>item101_lastTitle2</last>
</title>
<note>note101_1</note>
<quantity>10</quantity>
</item>
<item>
<id>103</id>
<title>
<first>
<a>a_103_2</a>
</first>
<last>item103_lastTitle2</last>
</title>
<note>note103_1</note>
</item>
</shiporder>
我尝试使用以下XSL代码来获取上述输出:
<?xml version="1.0" encoding="windows-1252" ?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/shiporder">
<shiporder>
<xsl:if test="@orderid">
<xsl:attribute name="orderid">
<xsl:value-of select="@orderid"/>
</xsl:attribute>
</xsl:if>
<!-- Grouping each item based on its ID element value-->
<xsl:for-each-group select="item" group-by="id">
<item-group>
<!-- For Each group search for first non empty child element-->
<xsl:for-each select="current-group()[id/text()][1]">
<xsl:copy-of select="id"/>
</xsl:for-each>
<title>
<first>
<!-- for 'a' element -->
<xsl:variable name="temp1">
<xsl:for-each select="current-group()/title/first[a/text()][1]">
<elm>
<xsl:copy-of select="a"/>
</elm>
</xsl:for-each>
</xsl:variable>
<xsl:copy-of select="$temp1/elm[1]/a"/>
</first>
<!-- for 'last' element -->
<xsl:variable name="temp2">
<xsl:for-each select="current-group()/title[last/text()][1]">
<elm>
<xsl:copy-of select="last"/>
</elm>
</xsl:for-each>
</xsl:variable>
<xsl:copy-of select="$temp2/elm[1]/last"/>
</title>
<!-- for 'note' element -->
<xsl:for-each select="current-group()[note/text()][1]">
<xsl:copy-of select="note"/>
</xsl:for-each>
<!-- for 'quantity' element -->
<xsl:for-each select="current-group()[quantity/text()][1]">
<xsl:copy-of select="quantity"/>
</xsl:for-each>
</item-group>
</xsl:for-each-group>
</shiporder>
</xsl:template>
</xsl:stylesheet>
代码工作正常但最糟糕的是,我无法概括。对于项目下的每个元素,我需要编写特定于特定元素的xpath的xsl代码。我甚至无法根据用户定义的模板编写代码。
我在“Recursively combine identical sibling elements in XSLT”标题中找到了已回答的问题。但是我无法根据我的要求对其进行定制。
我在每个项目下都有数百个这样的子元素或者decedent元素,每个元素的深度是任意的。编写xsl代码对应于每个子或后代是非常荒谬的。任何专家都可以指导我编写通用xsl代码(无论元素数量及其深度)或优化现有代码吗?
提前致谢,
卡锡尔
答案 0 :(得分:0)
这实际上是一个XSLT 1.0解决方案(我手头没有XSLT2处理器),但它使用了一个通用的“组合”命名模板,可以满足你的需要。
<?xml version="1.0" encoding="windows-1252" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="item" use="id" match="item" />
<xsl:template match="item[generate-id() = generate-id(key('item',id)[1])]">
<xsl:call-template name="combine">
<xsl:with-param name="list" select="../*[id = current()/id]" />
</xsl:call-template>
</xsl:template>
<xsl:template match="item" />
<xsl:template name="combine">
<xsl:param name="list" />
<xsl:element name="{name($list[1])}">
<xsl:for-each select="*[not(preceding-sibling::*[name() = name(current())])]" >
<xsl:call-template name="combine">
<xsl:with-param name="list" select="$list/*[name() = name(current())]" />
</xsl:call-template>
</xsl:for-each>
<xsl:value-of select="$list[text()][1]/text()" />
</xsl:element>
</xsl:template>
<xsl:template match="@* | node()">
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
combine
模板获取节点列表,并将它们递归地组合成一个节点,获取每个节点的第一个可用值。此模板也不会合并属性,但由于您提供的项目XML中没有任何属性,因此这应该没问题。模板中间的<xsl:for-each
使用XSLT1技术挑选出唯一的元素名称;它只选择那些没有同名兄弟的兄弟。在XSLT2中使用for-each-group可能有一种方法可以做到这一点,但我不能在这里查看。