XSLT:如何使用 XSLT 1.0 和 XALAN 处理器转换部分转义的 XML?

Posted

技术标签:

【中文标题】XSLT:如何使用 XSLT 1.0 和 XALAN 处理器转换部分转义的 XML?【英文标题】:XSLT: How to transform partially escaped XML with XSLT 1.0 and XALAN processor? 【发布时间】:2013-12-06 07:22:31 【问题描述】:

我有这个:

<root>
<row>
    <field>&amp;lt;![CDATA[&amp;lt;comprobante xmlns:xsi="http://www.w3.org/2001/XMLSchema"&amp;gt;
        &amp;lt;inicioCFD&amp;gt;
        &amp;lt;idArchivo&amp;gt;182NAI053402&amp;lt;/idArchivo&amp;gt;
        &amp;lt;etiquetaCFD&amp;gt;NCR&amp;lt;/etiquetaCFD&amp;gt;
        &amp;lt;/inicioCFD&amp;gt;
        &amp;lt;/comprobante&amp;gt;]]&amp;gt;</field>
</row>
</root>

我需要这个:

<comprobante>
  <idArchivo etiquetaCFD="NCR">182NAI053402</idArchivo>
</comprobante>

我正在使用这个 xslt:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
xmlns:a="http://www.tralix.com/cfd/2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema"
xmlns:xalan="http://xml.apache.org/xalan"
extension-element-prefixes="exsl xalan">
<xsl:strip-space elements="*"/>
<xsl:output indent="yes"/>
<xsl:template match="/root/row/field">
    <xsl:variable name="comprobante_">
        <xsl:variable name="p6">
            <xsl:variable name="p5">
                <xsl:variable name="p4">
                    <xsl:variable name="p3">
                        <xsl:variable name="p2">
                            <xsl:variable name="p1">
                                <xsl:value-of select="substring-before(substring-after(.,'CDATA['),']]')"/>
                            </xsl:variable>
                            <xsl:call-template name="replace-string">
                                <xsl:with-param name="text" select="$p1"/>
                                <xsl:with-param name="replace" select="'gt;'" />
                                <xsl:with-param name="with" select="'¬'"/>
                            </xsl:call-template>
                        </xsl:variable>
                        <xsl:call-template name="replace-string">
                            <xsl:with-param name="text" select="$p2"/>
                            <xsl:with-param name="replace" select="'lt;'"/>
                            <xsl:with-param name="with" select="'~'"/>
                        </xsl:call-template>
                    </xsl:variable>
                    <xsl:call-template name="replace-string">
                        <xsl:with-param name="text" select="$p3"/>
                        <xsl:with-param name="replace" select="'&amp;~'"/>
                        <xsl:with-param name="with" select="'&lt;'"/>
                    </xsl:call-template>
                </xsl:variable>
                <xsl:call-template name="replace-string">
                    <xsl:with-param name="text" select="$p4"/>
                    <xsl:with-param name="replace" select="'&amp;¬'"/>
                    <xsl:with-param name="with" select="'&gt;'"/>
                </xsl:call-template>
            </xsl:variable>
            <xsl:value-of select="$p5" disable-output-escaping="yes"/>
        </xsl:variable>
        <xsl:copy-of select="xalan:nodeset($p6)"/>
    </xsl:variable>
    <xsl:variable name="comprobante" select="xalan:nodeset($comprobante_)"/>
    <comprobante>
      <idArchivo>
          <xsl:attribute name="etiquetaCFD">
              <xsl:value-of select="$comprobante/comprobante/inicioCFD/etiquetaCFD"/>
          </xsl:attribute>
              <xsl:value-of select="$comprobante/comprobante/inicioCFD/idArchivo"/>
      </idArchivo>  
    </comprobante>
       </xsl:template>
<xsl:template name="replace-string">
    <xsl:param name="text"/>
    <xsl:param name="replace"/>
    <xsl:param name="with"/>
    <xsl:choose>
        <xsl:when test="contains($text,$replace)">
            <xsl:value-of select="substring-before($text,$replace)"/>
            <xsl:value-of select="$with"/>
            <xsl:call-template name="replace-string">
                <xsl:with-param name="text"
                    select="substring-after($text,$replace)"/>
                <xsl:with-param name="replace" select="$replace"/>
                <xsl:with-param name="with" select="$with"/>
            </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
            <xsl:value-of select="$text"/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>
</xsl:stylesheet>

它产生这个:

<comprobante>
  <idArchivo etiquetaCFD=""></idArchivo>
</comprobante>

空值是因为转义的 XML 不是 XSLT: How to transform partially escaped XML? 所说的那样的 XML,所以我无法从我的 $comprobante 变量中读取任何内容。

但在那篇文章中,Dimitri 说它可以用于 saxon:parse()。好吧,我正在使用 Xalan 处理器,但我找不到类似的东西。我仅限于使用 xalan 和 xslt 1.0。

有什么帮助吗?

提前致谢

【问题讨论】:

【参考方案1】:

这样的事情会从 field 中提取转义的内容并将其输出为纯文本(恰好是格式正确的 XML):

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:output method="text" />

  <xsl:template match="/">
    <xsl:variable name="withoutCDataStart"
       select="substring(root/row/field, 13)" />
    <xsl:variable name="withoutCDataEnd"
       select="substring($withoutCDataStart, 1,
                         string-length($withoutCDataStart) - 6)" />

    <xsl:call-template name="unescape">
      <xsl:with-param name="text" select="$withoutCDataEnd" />
    </xsl:call-template>
  </xsl:template>

  <xsl:template name="unescape">
    <xsl:param name="text" />
    <xsl:choose>
      <xsl:when test="contains($text, '&amp;')">
        <xsl:value-of select="substring-before($text, '&amp;')" />
        <xsl:variable name="afterAmp" select="substring-after($text, '&amp;')" />
        <xsl:choose>
          <xsl:when test="starts-with($afterAmp, 'amp;')">&amp;</xsl:when>
          <xsl:when test="starts-with($afterAmp, 'lt;')">&lt;</xsl:when>
          <xsl:when test="starts-with($afterAmp, 'gt;')">&gt;</xsl:when>
          <xsl:when test="starts-with($afterAmp, 'quot;')">"</xsl:when>
          <xsl:when test="starts-with($afterAmp, 'apos;')">'</xsl:when>
        </xsl:choose>
        <xsl:call-template name="unescape">
          <xsl:with-param name="text" select="substring-after($afterAmp, ';')" />
        </xsl:call-template>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="$text" />
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
</xsl:stylesheet>

然后,您必须将其输出反馈到另一个样式表中才能进行您想要的实际转换。

【讨论】:

非常感谢,但我需要使用一个样式表来获取输出。有可能吗? 仅通过调用将词法 XML 解析为树的 Java 扩展函数(沿着 saxon:parse() 的行)。你为什么要对解决方案施加如此多的限制?为什么你还在使用 XSLT 1.0? 我正在集成一个系统(不是我编程的),它只使用 java 默认引擎 xslt,它是 Xalan。它接受任何 xslt,但需要用 xslt 1.0 编写。我想换系统,但我不能,所以我需要用xslt 1.0实现两个系统。 我的问题是关于是否存在像 Xalan 中的 saxon:parse() ;我浏览了 Xalan API (xml.apache.org/xalan-j/apidocs/index.html) 和 Xalan Library (xml.apache.org/xalan-j/extensionslib.html) 但我很迷茫。 看到这个帖子***.com/questions/14508513/…,我看用XSLT 1.0或者XSLT 2.0都做不到,所以唯一的希望就是使用xalan的扩展【参考方案2】:

@IanRoberts,@MichaelKay,在你的帮助下,我想出了如何创建一个转义的 xml 解析器,结果就是这个工作的 xslt。谢谢你的帮助!

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
xmlns:exsl="http://exslt.org/common" 
extension-element-prefixes="exsl"
xmlns:xsi="http://www.w3.org/2001/XMLSchema">
<xsl:output indent="yes"/>
<xsl:template match="/">
    <xsl:variable name="withoutCDataStart"
        select="substring-after(root/row/field, '&amp;lt;![CDATA[')"/>
    <xsl:variable name="withoutCDataEnd"
        select="substring-before($withoutCDataStart, ']]&amp;gt;')"/>
    <xsl:variable name="unEscapedXml">
        <xsl:call-template name="unescape">
            <xsl:with-param name="text" select="$withoutCDataEnd"/>
        </xsl:call-template>
    </xsl:variable>
    <xsl:variable name="parsedXml_">
        <xsl:call-template name="parseXml">
            <xsl:with-param name="text" select="$unEscapedXml"/>
        </xsl:call-template>
    </xsl:variable>
    <xsl:variable name="parsedXml" select="exsl:node-set($parsedXml_)"/>
    <comprobante>
        <idArchivo>
            <xsl:attribute name="etiquetaCFD">
                <xsl:value-of select="$parsedXml/comprobante/inicioCFD/etiquetaCFD"/>
            </xsl:attribute>
            <xsl:value-of select="$parsedXml/comprobante/inicioCFD/idArchivo"/>
        </idArchivo>  
    </comprobante>
</xsl:template>
<xsl:template name="unescape">
    <xsl:param name="text"/>
    <xsl:choose>
        <xsl:when test="contains($text, '&amp;')">
            <xsl:value-of select="substring-before($text, '&amp;')"/>
            <xsl:variable name="afterAmp" select="substring-after($text, '&amp;')"/>
            <xsl:choose>
                <xsl:when test="starts-with($afterAmp, 'amp;')">&amp;</xsl:when>
                <xsl:when test="starts-with($afterAmp, 'lt;')">&lt;</xsl:when>
                <xsl:when test="starts-with($afterAmp, 'gt;')">&gt;</xsl:when>
                <xsl:when test="starts-with($afterAmp, 'quot;')">"</xsl:when>
                <xsl:when test="starts-with($afterAmp, 'apos;')">'</xsl:when>
            </xsl:choose>
            <xsl:call-template name="unescape">
                <xsl:with-param name="text" select="substring-after($afterAmp, ';')"/>
            </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
            <xsl:value-of select="$text"/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>
<xsl:template name="parseXml">
    <xsl:param name="text"/>
    <xsl:choose>
        <xsl:when test="contains($text, '&gt;')">
            <xsl:variable name="topLevelTag">
                <xsl:call-template name="getTopLevelTag">
                    <xsl:with-param name="text" select="$text"/>
                </xsl:call-template>
            </xsl:variable>
            <xsl:variable name="openingTag">
                <xsl:value-of select="$topLevelTag"/>
            </xsl:variable>
            <xsl:variable name="tagName">
                <xsl:call-template name="getTopLevelTagName">
                    <xsl:with-param name="text" select="$text"/>
                </xsl:call-template>
            </xsl:variable>
            <xsl:variable name="closingTag">
                <xsl:value-of select="concat('&lt;/',$tagName,'&gt;')"/>
            </xsl:variable>
            <xsl:variable name="firstNode">
                <xsl:if test="not(contains($topLevelTag,'/&gt;'))">
                    <xsl:value-of select="substring-before(substring-after($text,$openingTag),$closingTag)"/>        
                </xsl:if>
            </xsl:variable>
            <xsl:variable name="afterFirstNode">
                <xsl:choose>
                    <xsl:when test="not(contains($topLevelTag,'/&gt;'))">
                        <xsl:value-of select="substring-after($text,concat($firstNode,$closingTag))"/>        
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:value-of select="substring-after($text,$topLevelTag)"/>
                    </xsl:otherwise>
                </xsl:choose>
            </xsl:variable>
            <xsl:element name="$tagName">
                <xsl:call-template name="createAttributes">
                    <xsl:with-param name="text" select="$topLevelTag"/>
                </xsl:call-template>
                <xsl:call-template name="parseXml">
                    <xsl:with-param name="text" select="$firstNode"/>
                </xsl:call-template>
            </xsl:element>
            <xsl:call-template name="parseXml">
                <xsl:with-param name="text" select="$afterFirstNode"/>
            </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
            <xsl:value-of select="$text"/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>
<xsl:template name="getTopLevelTagName">
    <xsl:param name="text"/>
    <xsl:choose>
        <xsl:when test="contains($text, '&gt;')">
            <xsl:variable name="tagWithAttributesWithoutEnd">
                <xsl:value-of select="substring-before($text, '&gt;')"/>
            </xsl:variable>
            <xsl:variable name="tagWithAttributesWithoutBegining">
                <xsl:value-of select="substring-after($tagWithAttributesWithoutEnd, '&lt;')"/>
            </xsl:variable>
            <xsl:variable name="tagName">
                <xsl:choose>
                    <xsl:when test="contains($tagWithAttributesWithoutBegining,' ')">
                        <xsl:value-of
                            select="substring-before($tagWithAttributesWithoutBegining, ' ')"/>
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:value-of select="$tagWithAttributesWithoutBegining"/>
                    </xsl:otherwise>
                </xsl:choose>
            </xsl:variable>
            <xsl:value-of select="$tagName"/>
        </xsl:when>
    </xsl:choose>
</xsl:template>
<xsl:template name="getTopLevelTag">
    <xsl:param name="text"/>
    <xsl:choose>
        <xsl:when test="contains($text, '&gt;')">
            <xsl:variable name="tagWithAttributesWithoutEnd">
                <xsl:value-of select="substring-before($text, '&gt;')"/>
            </xsl:variable>
            <xsl:value-of select="concat($tagWithAttributesWithoutEnd,'&gt;')"/>
        </xsl:when>
    </xsl:choose>
</xsl:template>
<xsl:template name="createAttributes">
    <xsl:param name="text"/>
    <xsl:choose>
        <xsl:when test="contains($text, '=&quot;')">
            <xsl:variable name="attributeName">
                <xsl:value-of select="substring-before(substring-after($text,' '),'=&quot;')"/>
            </xsl:variable>
            <xsl:message>
                <xsl:value-of select="$text"/>
            </xsl:message>
            <xsl:variable name="attributeValue">
                <xsl:value-of select="substring-before(substring-after($text,concat($attributeName,'=&quot;')),'&quot;')"/>
            </xsl:variable>
            <xsl:attribute name="$attributeName">
                <xsl:value-of select="$attributeValue"/>
            </xsl:attribute>
            <xsl:call-template name="createAttributes">
                <xsl:with-param name="text" select="substring-after($text,concat($attributeName,'=&quot;',$attributeValue,'&quot;'))"/>
            </xsl:call-template>
        </xsl:when>
    </xsl:choose>        
</xsl:template>
</xsl:stylesheet>

它产生我需要的输出:

<comprobante xmlns:xsi="http://www.w3.org/2001/XMLSchema">
    <idArchivo etiquetaCFD="NCR">182NAI053402</idArchivo>
</comprobante>

我发表我的作品希望对其他人有所帮助。

【讨论】:

以上是关于XSLT:如何使用 XSLT 1.0 和 XALAN 处理器转换部分转义的 XML?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Muenchian 分组 XSLT 1.0 在每个目录中按标题分组

XSLT 1.0 - 如何将 2 个元素/兄弟元素组合成 1 个组

如何在 XSLT 1.0 中获取具有唯一数字的数字?

XSLT 1.0:CSV 到 XML - 如何分而治之

XSLT 1.0 / 使用条件对每个结果进行排序

xslt 1.0 字符串替换功能