XSL技术疑问:如何选择节点到根的所有祖先,排除后续兄弟节点
问题描述
我有如下XML payload:
<Root> <Header> <Name>B</Name> </Header> <Item> <Id>10</Id> <Description>Item10</Description> </Item> <Item> <Id>20</Id> <Description>Item20</Description> </Item> <Package> <Id>A</Id> </Package> <Package> <Id>B</Id> </Package> <Package> <Id>C</Id> </Package> </Root>
我希望针对每个Package节点,输出其从自身到根节点的所有祖先组成的完整消息,但不包含当前Package的后续兄弟节点(其他Package),期望输出为三个分别对应单个Package的Root结构:
<Root> <Header> <Name>B</Name> </Header> <Item> <Id>10</Id> <Description>Item10</Description> </Item> <Item> <Id>20</Id> <Description>Item20</Description> </Item> <Package> <Id>A</Id> </Package> </Root> <Root> <Header> <Name>B</Name> </Header> <Item> <Id>10</Id> <Description>Item10</Description> </Item> <Item> <Id>20</Id> <Description>Item20</Description> </Item> <Package> <Id>B</Id> </Package> </Root> <Root> <Header> <Name>B</Name> </Header> <Item> <Id>10</Id> <Description>Item10</Description> </Item> <Item> <Id>20</Id> <Description>Item20</Description> </Item> <Package> <Id>C</Id> </Package> </Root>
但目前我的代码输出的3条消息均包含所有Package,我的代码如下:
<?xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format"?> <xsl:template match="/"> <Messages> <xsl:for-each select="//Package"> <xsl:copy-of select="/*[not(following-sibling::*)]" /> </xsl:for-each> </Messages> </xsl:template>
请问我的代码哪里出错了?
错误分析
你的代码存在两个核心问题:
- 逻辑完全偏离预期:
<xsl:copy-of select="/*[not(following-sibling::*)]" />里的/*直接选中了整个根节点<Root>,而[not(following-sibling::*)]的判断永远为真(XML只能有一个根节点,自然没有后续兄弟),所以每次循环都原样复制了完整的原始XML,导致每个输出都包含所有Package节点。 - 未区分当前目标Package:你没有针对循环中的当前
Package节点,做任何过滤其后续兄弟节点的操作,完全没实现“只保留当前Package”的要求。
修正后的XSLT代码
方案一(通用场景适配)
适合后续XML结构可能变化的情况,自动保留当前Package之前的所有非Package节点,以及当前Package:
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <!-- 默认模板:复制所有节点和属性 --> <xsl:template match="node()|@*"> <xsl:copy> <xsl:apply-templates select="node()|@*"/> </xsl:copy> </xsl:template> <xsl:template match="/Root"> <xsl:for-each select="Package"> <Root> <!-- 复制Root下所有非Package节点,以及当前Package之前的兄弟节点(如果有) --> <xsl:apply-templates select="../*[not(self::Package) or generate-id() = generate-id(current())]"/> </Root> </xsl:for-each> </xsl:template> </xsl:stylesheet>
方案二(针对当前场景简化)
如果XML结构固定,非Package节点只有Header和Item,可以直接指定复制目标,写法更直观:
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:template match="/"> <xsl:for-each select="//Package"> <Root> <!-- 复制所有Header和Item节点 --> <xsl:copy-of select="/Root/Header | /Root/Item"/> <!-- 复制当前循环的Package节点 --> <xsl:copy-of select="."/> </Root> </xsl:for-each> </xsl:template> </xsl:stylesheet>
内容的提问来源于stack exchange,提问作者badbee
相关产品推荐
相关产品推荐

