如何使用XSL Transformation解析同名列?附SOAP XML示例
用XSLT解析带同名列的SOAP XML数据
嘿,我来帮你搞定这个问题!先看你给的SOAP响应,核心有俩难点:一是processOCIMessageReturn节点里的内容是CDATA包裹的嵌套XML,XSLT默认不会自动解析它;二是要处理嵌套XML里的同名列。我分步骤给你拆解:
第一步:把CDATA里的嵌套XML提取出来
首先得把CDATA里的XML转成XSLT能处理的节点树。如果用XSLT 2.0+(现在大部分工具都支持,比如Saxon),直接用parse-xml函数就行,非常方便:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:bs="urn:com:broadsoft:webservice"> <xsl:output method="xml" indent="yes"/> <!-- 匹配SOAP的Body节点,从这里开始处理 --> <xsl:template match="/soapenv:Envelope/soapenv:Body"> <!-- 提取CDATA文本,解析成XML节点树 --> <xsl:variable name="inner-xml" select="parse-xml(bs:processOCIMessageResponse/bs:processOCIMessageReturn/text())"/> <!-- 把解析后的XML传给下一个模板处理 --> <xsl:apply-templates select="$inner-xml"/> </xsl:template> </xsl:stylesheet>
要是你只能用XSLT 1.0,那就得借助扩展函数(比如EXSLT的node-set)配合第三方解析工具了,不过还是优先用XSLT 2.0+,省好多事。
第二步:处理嵌套XML里的同名列
同名列的情况分两种,我分别给你说怎么处理:
情况1:同名列属于不同命名空间
看你的示例里有<sessionId xmlns="",这就是空命名空间的节点,而外层MyDocument属于C命名空间。这种情况咱们用命名空间前缀来区分就行:
<!-- 接着上面的样式表,添加处理MyDocument的模板 --> <xsl:template match="*:MyDocument"> <ExtractedData> <!-- 空命名空间的sessionId,直接写节点名就行 --> <SessionID> <xsl:value-of select="sessionId"/> </SessionID> <!-- 如果C命名空间下也有同名的sessionId,用前缀调用(先在样式表里声明前缀xmlns:c="C") --> <NamespaceSessionID> <xsl:value-of select="c:sessionId"/> </NamespaceSessionID> </ExtractedData> </xsl:template>
这里*:是通配所有命名空间,要是你明确知道命名空间URI,直接声明前缀更清晰。
情况2:同一命名空间下的多个同名列
如果是同一个命名空间里有多个同名节点(比如多个<item>),咱们可以用位置索引或者节点属性来区分:
<!-- 假设MyDocument下有多个c:item节点 --> <xsl:template match="c:MyDocument"> <AllItems> <!-- 遍历所有item,用position()标记位置 --> <xsl:for-each select="c:item"> <Item index="{position()}"> <xsl:value-of select="."/> </Item> </xsl:for-each> <!-- 如果节点有属性区分,比如type属性,直接过滤 --> <UserItems> <xsl:value-of select="c:item[@type='user']"/> </UserItems> </AllItems> </xsl:template>
完整的示例样式表
把上面的步骤整合起来,完整的XSLT如下,你可以直接拿去测试:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:bs="urn:com:broadsoft:webservice" xmlns:c="C"> <xsl:output method="xml" indent="yes"/> <xsl:template match="/"> <xsl:apply-templates select="soapenv:Envelope/soapenv:Body"/> </xsl:template> <xsl:template match="soapenv:Body"> <xsl:variable name="inner-xml" select="parse-xml(bs:processOCIMessageResponse/bs:processOCIMessageReturn/text())"/> <xsl:apply-templates select="$inner-xml"/> </xsl:template> <xsl:template match="c:MyDocument"> <ParsedResult> <!-- 提取空命名空间的sessionId --> <SessionID> <xsl:value-of select="sessionId"/> </SessionID> <!-- 处理同一命名空间下的多个同名列 --> <Items> <xsl:for-each select="c:item"> <Item position="{position()}"> <xsl:value-of select="."/> </Item> </xsl:for-each> </Items> </ParsedResult> </xsl:template> <!-- 匹配无关的文本和属性,避免默认输出冗余内容 --> <xsl:template match="text()|@*"/> </xsl:stylesheet>
几个关键提醒
- 命名空间别忘声明:样式表里一定要把所有用到的命名空间都声明好,不然XSLT会找不到节点。
- CDATA解析的版本差异:XSLT 1.0处理CDATA麻烦很多,尽量用2.0+版本的处理器。
- 同名列区分优先级:优先用命名空间、节点属性来区分,实在不行再用位置索引,这样代码更健壮。
内容的提问来源于stack exchange,提问作者Alex
相关产品推荐
相关产品推荐

