XSLT 1.0遍历多Detail标签时For-Each分组输出异常问题
XSLT 1.0 多Detail节点分组输出问题修复
问题说明
使用XSLT 1.0处理包含多个Detail标签的XML时,内层for-each循环仅输出第一个Detail对应的Oracle分组,第二个Detail的分组无法输出,预期每个Detail标签各输出1个Oracle分组。
原始XML
<ns0:Root xmlns:ns0="Test"> <Detail> <Supplier>Supplier_1</Supplier> <Member> <CompanyName>Oracle</CompanyName> <PersonName>Ashwin</PersonName> <State>KS</State> <Country>USA</Country> <Amount>1000</Amount> <CombinedState>Yes</CombinedState> </Member> <Member> <CompanyName>Oracle</CompanyName> <PersonName>Lingam</PersonName> <State>AZ</State> <Country>USA</Country> <Amount>1001</Amount> <CombinedState>Yes</CombinedState> </Member> <Member> <CompanyName>Oracle</CompanyName> <PersonName>John</PersonName> <State>KS</State> <Country>USA</Country> <Amount>1000</Amount> <CombinedState>Yes</CombinedState> </Member> <Member> <CompanyName>Flipkart</CompanyName> <PersonName>Larry</PersonName> <State>IL</State> <Country>USA</Country> <Amount>1000</Amount> <CombinedState>No</CombinedState> </Member> <Member> <CompanyName>Flipkart</CompanyName> <PersonName>Lingam</PersonName> <State>NJ</State> <Country>USA</Country> <Amount>1001</Amount> <CombinedState>Yes</CombinedState> </Member> <Member> <CompanyName>Addidas</CompanyName> <PersonName>John</PersonName> <State>FL</State> <Country>USA</Country> <Amount>1000</Amount> <CombinedState>Yes</CombinedState> </Member> </Detail> <Detail> <Supplier>Supplier_2</Supplier> <Member> <CompanyName>Oracle</CompanyName> <PersonName>Akhil</PersonName> <State>KS</State> <Country>USA</Country> <Amount>1000</Amount> <CombinedState>Yes</CombinedState> </Member> <Member> <CompanyName>Oracle</CompanyName> <PersonName>Sebastian</PersonName> <State>AZ</State> <Country>USA</Country> <Amount>1001</Amount> <CombinedState>Yes</CombinedState> </Member> <Member> <CompanyName>Flipkart</CompanyName> <PersonName>Larry</PersonName> <State>IL</State> <Country>USA</Country> <Amount>1000</Amount> <CombinedState>No</CombinedState> </Member> <Member> <CompanyName>Flipkart</CompanyName> <PersonName>Lingam</PersonName> <State>NJ</State> <Country>USA</Country> <Amount>1001</Amount> <CombinedState>Yes</CombinedState> </Member> </Detail> </ns0:Root>
原有XSLT代码
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:var="http://schemas.microsoft.com/BizTalk/2003/var" xmlns:s0="Test" xmlns:ns0="http://BizTalk_Server_Project1.Output" xmlns:userCSharp="http://schemas.microsoft.com/BizTalk/2003/userCSharp" exclude-result-prefixes="msxsl var s0 userCSharp" version="1.0"> <xsl:output omit-xml-declaration="yes" method="xml" version="1.0"/> <xsl:key name="group1" match="Member" use="CompanyName"/> <xsl:template match="/"> <xsl:apply-templates select="/s0:Root"/> </xsl:template> <xsl:template match="/s0:Root"> <xsl:variable name="var:v1" select="'"1"'"/> <xsl:variable name="var:v2" select="'"1234567"'"/> <ns0:Root> <T> <SeqNo> <xsl:value-of select="$var:v1"/> </SeqNo> <Name> <xsl:value-of select="$var:v2"/> </Name> </T> <xsl:for-each select="Detail"> <xsl:for-each select="Member[generate-id(.)=generate-id(key('group1',CompanyName))]"> <xsl:if test="(CompanyName='Oracle')"> <Group> <A> <CompanyName> <xsl:value-of select="CompanyName/text()"/> </CompanyName> <Segment> <xsl:value-of select="'A'"/> </Segment> </A> </Group> </xsl:if> </xsl:for-each> </xsl:for-each> </ns0:Root> </xsl:template> </xsl:stylesheet>
预期输出
<ns0:Root xmlns:ns0="http://BizTalk_Server_Project1.Output"> <T> <SeqNo>"1"</SeqNo> <Name>"1234567"</Name> </T> <Group> <A> <CompanyName>Oracle</CompanyName> <Segment>A</Segment> </A> </Group> <Group> <A> <CompanyName>Oracle</CompanyName> <Segment>A</Segment> </A> </Group> </ns0:Root>
问题原因
XSLT 1.0 中的key是全局作用域,原有代码定义的group1会匹配整个XML文档内所有Member节点,分组依据是全局的CompanyName,所以generate-id匹配时仅会命中整个文档中第一个CompanyName为Oracle的Member节点,第二个Detail内的Oracle Member无法匹配条件,因此不会输出。
修复方案
修改key的分组规则,将父节点Detail的唯一标识加入分组条件,实现每个Detail内部独立分组:
- 修改key定义,拼接父Detail的唯一ID和CompanyName作为分组键
- 调整内层for-each的匹配条件,匹配当前Detail下的分组
修复后完整XSLT代码
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:var="http://schemas.microsoft.com/BizTalk/2003/var" xmlns:s0="Test" xmlns:ns0="http://BizTalk_Server_Project1.Output" xmlns:userCSharp="http://schemas.microsoft.com/BizTalk/2003/userCSharp" exclude-result-prefixes="msxsl var s0 userCSharp" version="1.0"> <xsl:output omit-xml-declaration="yes" method="xml" version="1.0"/> <!-- 修改key定义,加入父Detail的唯一标识 --> <xsl:key name="group1" match="Member" use="concat(generate-id(parent::Detail), '|', CompanyName)"/> <xsl:template match="/"> <xsl:apply-templates select="/s0:Root"/> </xsl:template> <xsl:template match="/s0:Root"> <xsl:variable name="var:v1" select="'"1"'"/> <xsl:variable name="var:v2" select="'"1234567"'"/> <ns0:Root> <T> <SeqNo> <xsl:value-of select="$var:v1"/> </SeqNo> <Name> <xsl:value-of select="$var:v2"/> </Name> </T> <xsl:for-each select="Detail"> <!-- 外层循环的current()为当前Detail节点,拼接分组键匹配当前Detail内的分组 --> <xsl:for-each select="Member[generate-id(.) = generate-id(key('group1', concat(generate-id(current()), '|', CompanyName))[1])]"> <xsl:if test="CompanyName='Oracle'"> <Group> <A> <CompanyName> <xsl:value-of select="CompanyName/text()"/> </CompanyName> <Segment> <xsl:value-of select="'A'"/> </Segment> </A> </Group> </xsl:if> </xsl:for-each> </xsl:for-each> </ns0:Root> </xsl:template> </xsl:stylesheet>
如果仅需要判断当前Detail是否存在Oracle的Member即可输出分组,不需要通用分组逻辑,也可以简化内层循环为直接判断:
<xsl:for-each select="Detail"> <xsl:if test="Member[CompanyName='Oracle']"> <Group> <A> <CompanyName>Oracle</CompanyName> <Segment>A</Segment> </A> </Group> </xsl:if> </xsl:for-each>
内容的提问来源于stack exchange,提问作者Akhil Sebastian
相关产品推荐
相关产品推荐

