在使用XSLT转换XML时,如何保留实体引用?

9
如何在使用XSLT(2.0)转换XML时保留实体引用?我尝试过的所有处理器都会默认解析实体。我可以使用xsl:character-map来处理字符实体,但是文本实体怎么办?
例如,这个XML:
<!DOCTYPE doc [
<!ENTITY so "stackoverflow">
<!ENTITY question "How can I preserve the entity reference when transforming with XSLT??">
]>
<doc>
  <text>Hello &so;!</text>
  <text>&question;</text>
</doc>

使用以下XSLT进行转换:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output indent="yes"/>
  <xsl:strip-space elements="*"/>

  <xsl:template match="node()|@*">
    <xsl:copy>
      <xsl:apply-templates select="node()|@*"/>
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

生成以下输出:
<doc>
   <text>Hello stackoverflow!</text>
   <text>How can I preserve the entity reference when transforming with XSLT??</text>
</doc>

输出应该与输入相同(暂时不包括文档类型声明):
<doc>
  <text>Hello &so;!</text>
  <text>&question;</text>
</doc>

希望我不必预处理输入,将所有的&符号替换为&(如&question;),然后在后处理输出时将所有的&替换为&。
也许这是处理器特定的问题?我正在使用Saxon 9。
谢谢!

好问题,+1。使用XSLT几乎不可能完成所需的处理,我不建议经常使用我的答案。 - Dimitre Novatchev
5个回答

5

如果您知道将使用哪些实体以及它们如何定义,您可以执行以下操作(相当原始和容易出错,但仍然比没有好):

<xsl:stylesheet version="2.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:xs="http://www.w3.org/2001/XMLSchema"
 xmlns:my="my:my">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:character-map name="mapEntities">
  <xsl:output-character character="&amp;" string="&amp;"/>
 </xsl:character-map>

 <xsl:variable name="vEntities" select=
 "'stackoverflow',
 'How can I preserve the entity reference when transforming with XSLT\?\?'
 "/>

 <xsl:variable name="vReplacements" select=
 "'&amp;so;', '&amp;question;'"/>

 <xsl:template match="node()|@*">
  <xsl:copy>
   <xsl:apply-templates select="node()|@*"/>
  </xsl:copy>
 </xsl:template>

 <xsl:template match="/">
  <xsl:text disable-output-escaping="yes"><![CDATA[<!DOCTYPE doc [ <!ENTITY so "stackoverflow">
<!ENTITY question
"How can I preserve the entity reference when transforming with XSLT??"> ]>
]]>
  </xsl:text>

  <xsl:apply-templates/>
 </xsl:template>

 <xsl:template match="text()">
  <xsl:value-of select=
  "my:multiReplace(.,
                   $vEntities,
                   $vReplacements,
                   count($vEntities)
                   )
  " disable-output-escaping="yes"/>
 </xsl:template>

 <xsl:function name="my:multiReplace">
  <xsl:param name="pText" as="xs:string"/>
  <xsl:param name="pEnts" as="xs:string*"/>
  <xsl:param name="pReps" as="xs:string*"/>
  <xsl:param name="pCount" as="xs:integer"/>

  <xsl:sequence select=
  "if($pCount > 0)
     then
      my:multiReplace(replace($pText,
                              $pEnts[1],
                              $pReps[1]
                              ),
                      subsequence($pEnts,2),
                      subsequence($pReps,2),
                      $pCount -1
                      )
      else
       $pText
  "/>
 </xsl:function>
</xsl:stylesheet>

当应用于提供的XML文档时:

<!DOCTYPE doc [ <!ENTITY so "stackoverflow">
<!ENTITY question
"How can I preserve the entity reference when transforming with XSLT??"> ]>
<doc>
    <text>Hello &so;!</text>
    <text>&question;</text>
</doc>

期望的结果已经成功生成:

<!DOCTYPE doc [ <!ENTITY so "stackoverflow">
<!ENTITY question
"How can I preserve the entity reference when transforming with XSLT??"> ]>

  <doc>
      <text>Hello &so;!</text>
      <text>&question;</text>
</doc>

请注意:
  1. 替换中的特殊字符(正则表达式)必须进行转义。

  2. 我们需要使用 DOE 进行解决,但这并不被建议,因为它违反了 XSLT 架构和处理模型的原则,换句话说,这种解决方案是一种糟糕的 hack。


非常感谢你,Dimitre。我很担心这个问题。不幸的是,我不知道正在使用哪些实体。我想我会在这个项目中继续使用OmniMark。尽管如此,你的回答非常有帮助,我很感激你花费的时间。+1并接受答案。 - Daniel Haley

3

如果你正在使用像 S1000D 这样的东西,这可能是一个特别棘手的问题。它使用实体和@boardno属性来链接到图表。这是其 SGML 根源的遗留问题。

由于这种自动实体扩展行为虽然正确但不可取,我在使用 S1000D 作为输入时常常不得不退回到像 sed、awk 和批处理脚本这样的工具来管理某些数据分析任务。

在我看来,这将是一个伟大的变更提案,关于即将推出的 XSLT 规范之一:一个符合规范的处理器可以接受一个运行时参数,以打开或关闭实体扩展。


我主要处理ATA iSpec 2200,并有一些关于S1000D的工作经验,所以我知道你的意思。 - Daniel Haley

1
如果您使用的是Java实现的XSLT 2.0处理器(如Saxon 9 Java),您可能想要检查http://andrewjwelch.com/lexev/是否有所帮助,您可以通过这种方式预处理XML实体和字符引用,以便将它们标记为XML元素,然后根据需要进行转换。

1
我使用这个解决方案,效果很好:

<xsl:variable name="prolog" select="substring-before(unparsed-text(document-uri(.)),'&lt;root')"/>

<xsl:template match="/">
    <xsl:value-of select="$prolog" disable-output-escaping="yes"/>
  <xsl:apply-templates/>
</xsl:template>

我没有尝试过这个,但看起来它只会保留prolog;实体引用仍然会被展开。我可以使用xsl:analyze-string分析prolog并建立一个结构(或3.0中的映射)的键/值对,然后在处理过程中替换它们。我可能会有一天尝试这个(+1为这个想法)。为了真正解决这个问题/问题,我最终编写了一个类似于另一个答案中提到的“lexev”Java程序的Omnimark程序。 - Daniel Haley

0

您可以使用带有“entities”参数设置为true的DOM LS解析器来保留文档中的EntityReference节点。 http://docs.oracle.com/javase/6/docs/api/org/w3c/dom/DOMConfiguration.html

规范说明默认值为true,但根据解析器的不同,它可能为false,请注意。

要加载Xerces:

DOMImplementationLS domImpl = new org.apache.xerces.dom.CoreDOMImplementationImpl();

你也可以像下面这样使用注册表,但个人而言,我宁愿硬编码我想要的实现方式,就像上面那样:

DOMImplementationRegistry registry = DOMImplementationRegistry.newInstance();
DOMImplementationLS domImpl = (DOMImplementationLS) registry.getDOMImplementation("XML 3.0 LS 3.0"); 

然后,要加载您的文档:

// XML parser with XSD schema 
LSParser parser = domImpl.createLSParser(DOMImplementationLS.MODE_SYNCHRONOUS, "http://www.w3.org/2001/XMLSchema");
DOMConfiguration config = parser.getDomConfig();
config.setParameter("entities", true);
LSInput input = impl.createLSInput();
Document lDoc = parser.parse(your XML stream);

那么,您的XML实体在DOM中没有展开。

因此,由于SAXON不处理未展开的实体(“DOM中不支持的节点类型!5”错误),您不能使用net.sf.saxon.xpath.XPathFactoryImpl,您必须使用XPathFactory.newInstance()设置Xerces的默认XPathFactory。


我尝试使用这种方法,但是当将文档作为DOM源使用时,您会得到[致命错误]:xxx:yyy:字符引用“&# - Archimedes Trajano
你能提供更多的细节和源代码、XML输入吗? - jguiraud
实际上我已经没有它了,我找到了一种替代方法来处理我的需求,这种方法将实体数据保留在一个不会被翻译的属性中。 - Archimedes Trajano

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接