You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用XSLT生成CDATA内部XML元素的XPath

需求

为CDATA段中的XML元素生成对应的XPath,要求将CDATA内部XML元素的XPath与父节点XPath通过|@|CDATA|@|拼接。

输入XML

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
    <s:Body>
        <CSBIV xmlns="http://tempuri.org/">
            <Response>true</Response>
            <CSBIV_CargoResult><![CDATA[
            <?xml version="1.0" encoding="UTF-8"?>
            <ShipmentRecords>
                <CustomerInfo>
                    <SoftwareVersion>6.0</SoftwareVersion>
                    <DefaultAccountNo>530901961</DefaultAccountNo>
                </CustomerInfo>
            </ShipmentRecords>  
            ]]></CSBIV_CargoResult>
        </CSBIV>
    </s:Body>
</s:Envelope>

当前使用的XSLT

<xsl:stylesheet version="3.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    <xsl:strip-space elements="*"/>
    
    <xsl:template match="*">
        <xsl:param name="path"/>
        <xsl:variable name="my-path">
            <xsl:for-each select="ancestor-or-self::*">
                <xsl:value-of select="concat('/',name())"/>
                <xsl:if test="(preceding-sibling::*|following-sibling::*)[name()=name(current())]">
                    <xsl:value-of select="concat('[',count(preceding-sibling::*[name()=name(current())])+1,']')"/>
                </xsl:if>
            </xsl:for-each>
        </xsl:variable>
        
        <xsl:copy>
            <xsl:variable name="elementXpath" select="replace($my-path, '/[a-zA-Z0-9-]*?:|/[a-zA-Z0-9-]*?','/*:')"/>
            <xsl:attribute name="xpath">
                <xsl:value-of select="concat($elementXpath,'/text()')"/>
            </xsl:attribute>
            <xsl:copy-of select="@*"/>
            
            <xsl:for-each select="@*">
                <xsl:attribute name="{name()}" select="concat($elementXpath,'/@',name())" separator=""/>
            </xsl:for-each>
            
            <xsl:apply-templates>
                <xsl:with-param name="path" select="$my-path"/>
            </xsl:apply-templates>
        </xsl:copy>
    </xsl:template>
    
    <xsl:template match="text()"/>
    
</xsl:stylesheet>

现有输出(忽略CDATA段内容)

<?xml version="1.0" encoding="UTF-8"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
            xpath="/*:Envelope/text()">
   <s:Body xpath="/*:Envelope/*:Body/text()">
      <CSBIV xmlns="http://tempuri.org/" xpath="/*:Envelope/*:Body/*:CSBIV/text()">
         <Response xpath="/*:Envelope/*:Body/*:CSBIV/*:Response/text()"/>
         <CSBIV_CargoResult xpath="/*:Envelope/*:Body/*:CSBIV/*:CSBIV_CargoResult/text()"/>
      </CSBIV>
   </s:Body>
</s:Envelope>

期望输出

<?xml version="1.0" encoding="UTF-8"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
    xpath="/*:Envelope/text()">
    <s:Body xpath="/*:Envelope/*:Body/text()">
        <CSBIV xmlns="http://tempuri.org/" xpath="/*:Envelope/*:Body/*:CSBIV/text()">
            <Response xpath="/*:Envelope/*:Body/*:CSBIV/*:Response/text()"/>
            <CSBIV_CargoResult xpath="/*:Envelope/*:Body/*:CSBIV/*:CSBIV_CargoResult/text()">
               <ShipmentRecords xpath="/*:Envelope/*:Body/*:CSBIV/*:CSBIV_CargoResult|@|CDATA|@|/*:ShipmentRecords/text()"> 
                    <CustomerInfo xpath="/*:Envelope/*:Body/*:CSBIV/*:CSBIV_CargoResult|@|CDATA|@|/*:ShipmentRecords/*:CustomerInfo/text()">
                        <SoftwareVersion xpath="/*:Envelope/*:Body/*:CSBIV/*:CSBIV_CargoResult|@|CDATA|@|/*:ShipmentRecords/*:CustomerInfo/*:SoftwareVersion/text()"/>
                        <DefaultAccountNo xpath="/*:Envelope/*:Body/*:CSBIV/*:CSBIV_CargoResult|@|CDATA|@|/*:ShipmentRecords/*:CustomerInfo/*:DefaultAccountNo/text()"/>
                    </CustomerInfo>
                </ShipmentRecords>
            </CSBIV_CargoResult>
        </CSBIV>
    </s:Body>
</s:Envelope>

修改后的XSLT解决方案

<xsl:stylesheet version="3.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    <xsl:strip-space elements="*"/>
    
    <!-- 处理普通元素的模板 -->
    <xsl:template match="*">
        <xsl:param name="parent-xpath" select="''"/>
        <!-- 生成当前元素的基础XPath -->
        <xsl:variable name="my-path">
            <xsl:for-each select="ancestor-or-self::*">
                <xsl:value-of select="concat('/',name())"/>
                <xsl:if test="(preceding-sibling::*|following-sibling::*)[name()=name(current())]">
                    <xsl:value-of select="concat('[',count(preceding-sibling::*[name()=name(current())])+1,']')"/>
                </xsl:if>
            </xsl:for-each>
        </xsl:variable>
        <xsl:variable name="elementXpath" select="replace($my-path, '/[a-zA-Z0-9-]*?:|/[a-zA-Z0-9-]*?','/*:')"/>
        <!-- 确定最终的xpath属性值 -->
        <xsl:variable name="final-xpath">
            <xsl:choose>
                <xsl:when test="$parent-xpath != ''">
                    <xsl:value-of select="concat($parent-xpath, '|@|CDATA|@|', $elementXpath, '/text()')"/>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:value-of select="concat($elementXpath, '/text()')"/>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:variable>
        
        <xsl:copy>
            <xsl:attribute name="xpath" select="$final-xpath"/>
            <xsl:copy-of select="@*"/>
            <!-- 处理属性的xpath -->
            <xsl:for-each select="@*">
                <xsl:attribute name="{name()}" select="concat(if ($parent-xpath != '') then concat($parent-xpath, '|@|CDATA|@|') else '', $elementXpath, '/@', name())" separator=""/>
            </xsl:for-each>
            <!-- 应用模板到子元素和文本节点 -->
            <xsl:apply-templates>
                <xsl:with-param name="parent-xpath" select="if ($parent-xpath != '') then $parent-xpath else $elementXpath"/>
            </xsl:apply-templates>
        </xsl:copy>
    </xsl:template>
    
    <!-- 处理包含XML内容的CDATA文本节点 -->
    <xsl:template match="text()[contains(., '&lt;') and contains(., '&gt;')]">
        <xsl:param name="parent-xpath"/>
        <!-- 解析CDATA中的XML字符串为节点集 -->
        <xsl:variable name="cdata-xml" select="parse-xml(.)"/>
        <!-- 递归处理解析后的XML元素 -->
        <xsl:apply-templates select="$cdata-xml/*">
            <xsl:with-param name="parent-xpath" select="$parent-xpath"/>
        </xsl:apply-templates>
    </xsl:template>
    
    <!-- 忽略空文本或不包含XML的文本 -->
    <xsl:template match="text()[not(contains(., '&lt;') and contains(., '&gt;'))]"/>
    
</xsl:stylesheet>

关键修改说明

  1. 新增CDATA文本节点处理模板:使用parse-xml()函数将CDATA内的XML字符串解析为可操作的节点集;
  2. 元素模板新增parent-xpath参数:传递CDATA父节点的XPath,用于拼接内部元素的最终路径;
  3. 路径拼接逻辑:针对CDATA内部元素,通过|@|CDATA|@|连接父节点与内部元素的XPath;
  4. 保留原有属性处理逻辑并适配CDATA场景,确保属性的XPath也符合拼接规则。

内容的提问来源于stack exchange,提问作者neo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 17:47:20