如何在单个XSLT文件中实现CDATA内容提取与二次转换?
单个XSLT实现CDATA内容提取与目标XML生成
核心需求:编写单个XSLT文件,先提取源XML中CDATA区段内的XML内容,再基于提取后的内容转换为指定格式的目标XML。
源XML文件
<Docs> <Doc> <Content> <![CDATA[ <Doc xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <Header DocNumber="1" Description="Desc1"></Header> <Poss> <Pos Id="1" Name="Pos1"></Pos> <Pos Id="2" Name="Pos2"></Pos> </Poss> </Doc> ]]> </Content> </Doc> <Doc> <Content> <![CDATA[ <Doc xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <Header DocNumber="2" Description="Desc2"></Header> <Poss> <Pos Id="3" Name="Pos3"></Pos> <Pos Id="4" Name="Pos4"></Pos> </Poss> </Doc> ]]> </Content> </Doc>
现有XSLT(仅提取CDATA)
<?xml version="1.0" encoding="Windows-1250"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:template match="/"> <xsl:element name="Docs"> <xsl:for-each select="Docs/Doc"> <xsl:value-of select="Content/text()" disable-output-escaping="yes"/> </xsl:for-each> </xsl:element> </xsl:template> </xsl:stylesheet>
中间输出
<Docs> <Doc xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <Header DocNumber="1" Description="Desc1"></Header> <Poss> <Pos Id="1" Name="Pos1"></Pos> <Pos Id="2" Name="Pos2"></Pos> </Poss> </Doc> <Doc xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <Header DocNumber="2" Description="Desc2"></Header> <Poss> <Pos Id="3" Name="Pos3"></Pos> <Pos Id="4" Name="Pos4"></Pos> </Poss> </Doc> </Docs>
目标XML格式
<DOCS> <DOC NUMBER="1" DESC="Desc1"> <POSS> <POS>Pos1</POS> <POS>Pos2</POS> </POSS> </DOC> <DOC NUMBER="2" DESC="Desc2"> <POSS> <POS>Pos3</POS> <POS>Pos4</POS> </POSS> </DOC> </DOCS>
解决方案:单个XSLT实现完整转换
由于XSLT 1.0本身无法直接解析CDATA中的XML字符串为节点集,需要借助EXSLT扩展函数(大部分主流XSLT处理器如libxslt、Saxon均支持)来实现。以下是完整的XSLT代码:
<?xml version="1.0" encoding="Windows-1250"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exsl="http://exslt.org/common" extension-element-prefixes="exsl"> <!-- 主模板:遍历外层Docs/Doc --> <xsl:template match="/"> <DOCS> <xsl:for-each select="Docs/Doc"> <!-- 将CDATA内容转为临时XML节点 --> <xsl:variable name="cdata-xml"> <xsl:value-of select="Content/text()" disable-output-escaping="yes"/> </xsl:variable> <!-- 将临时XML转为节点集,以便后续处理 --> <xsl:variable name="doc-node" select="exsl:node-set($cdata-xml)/Doc"/> <!-- 生成目标DOC节点 --> <DOC NUMBER="{$doc-node/Header/@DocNumber}" DESC="{$doc-node/Header/@Description}"> <POSS> <!-- 遍历Pos节点,提取Name属性值 --> <xsl:for-each select="$doc-node/Poss/Pos"> <POS><xsl:value-of select="@Name"/></POS> </xsl:for-each> </POSS> </DOC> </xsl:for-each> </DOCS> </xsl:template> </xsl:stylesheet>
代码解释
- 引入EXSLT扩展:通过
xmlns:exsl="http://exslt.org/common"声明扩展命名空间,并启用extension-element-prefixes="exsl"让处理器识别扩展函数。 - 临时XML节点生成:使用
<xsl:variable>将CDATA内容以未转义的形式存入变量,此时变量内容是XML字符串。 - 节点集转换:通过
exsl:node-set()将XML字符串转换为可遍历的节点集,这样就能像处理普通XML节点一样访问CDATA内的Doc、Header、Pos等元素。 - 目标XML生成:基于转换后的节点集,直接提取属性值并生成符合要求的
<DOCS>、<DOC>、<POSS>、<POS>结构。
注意事项
- 确保使用的XSLT处理器支持EXSLT扩展(如libxslt、Saxon 6.x等);若使用XSLT 2.0及以上版本,可直接使用
xsl:parse函数解析XML字符串,无需依赖EXSLT。 - 若CDATA内的XML存在命名空间(如示例中的
xsi),无需额外处理,exsl:node-set()会自动保留命名空间,但由于目标XML不需要该命名空间,直接提取属性即可。
内容的提问来源于stack exchange,提问作者jeffers
相关产品推荐
相关产品推荐

