如何使用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(., '<') and contains(., '>')]"> <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(., '<') and contains(., '>'))]"/> </xsl:stylesheet>
关键修改说明
- 新增CDATA文本节点处理模板:使用
parse-xml()函数将CDATA内的XML字符串解析为可操作的节点集; - 元素模板新增
parent-xpath参数:传递CDATA父节点的XPath,用于拼接内部元素的最终路径; - 路径拼接逻辑:针对CDATA内部元素,通过
|@|CDATA|@|连接父节点与内部元素的XPath; - 保留原有属性处理逻辑并适配CDATA场景,确保属性的XPath也符合拼接规则。
内容的提问来源于stack exchange,提问作者neo
相关产品推荐
相关产品推荐

