如何处理未知实体引用?
Posted
技术标签:
【中文标题】如何处理未知实体引用?【英文标题】:How to deal with unknown entity references? 【发布时间】:2011-01-26 21:42:55 【问题描述】:我正在解析(很多)包含我事先不知道的实体引用的 XML 文件(无法改变这一事实)。
例如:
xml = "<tag>I'm content with &funny; &entity; &references;.</tag>"
当我尝试使用以下代码解析时:
final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
final DocumentBuilder db = dbf.newDocumentBuilder();
final InputSource is = new InputSource(new StringReader(xml));
final Document d = db.parse(is);
我得到以下异常:
org.xml.sax.SAXParseException: The entity "funny" was referenced, but not declared.
但是,我想要实现的是,解析器将每个未声明的实体(解析器未知)替换为空字符串“”。 或者更好的是,有没有办法将映射传递给解析器,例如:
Map<String,String> entityMapping = ...
entityMapping.put("funny","very");
entityMapping.put("entity","important");
entityMapping.put("references","stuff");
这样我就可以做到以下几点:
final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
final DocumentBuilder db = dbf.newDocumentBuilder();
final InputSource is = new InputSource(new StringReader(xml));
db.setEntityResolver(entityMapping);
final Document d = db.parse(is);
如果我会使用此示例代码从文档中获取文本,我应该会收到:
I'm content with very important stuff.
有什么建议吗?当然,我已经很乐意用空字符串替换未知实体。
谢谢,
【问题讨论】:
我对 SAX 工具包不够熟悉,无法了解它的 API,但可以想象它有一个与之关联的...Resolver
。此类将负责解析这些引用。这就是 .Net 模型的工作原理。我认为这些概念大致相同。
你的意思是像EntityResolver?这听起来确实应该有效,但是当我查看它的 API 时,它似乎并不完全针对这种实体。但是尝试它应该不会造成任何伤害。
EntityResolver
用于解析外部实体(例如DTD),但我们正在寻找能够处理内部实体的东西。跨度>
无法 EntityResolver2 通过 InputSource resolveEntity(String name, String publicId, String baseURI, String systemId) 做到这一点?
【参考方案1】:
StAX API 对此提供支持。看看XMLInputFactory,它有一个runtime property,它指示内部实体是扩展还是保留在原地。如果设置为 false
,则 StAX 事件流将包含 EntityReference
的实例以表示未扩展的实体。
如果你仍然想要一个 DOM 作为最终结果,你可以像这样将它链接在一起:
XMLInputFactory inputFactory = XMLInputFactory.newInstance();
inputFactory.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, false);
Transformer transformer = TransformerFactory.newInstance().newTransformer();
String xml = "my xml";
StringReader xmlReader = new StringReader(xml);
XMLEventReader eventReader = inputFactory.createXMLEventReader(xmlReader);
StAXSource source = new StAXSource(eventReader);
DOMResult result = new DOMResult();
transformer.transform(source, result);
Node document = result.getNode();
在这种情况下,生成的 DOM 将包含与文本节点混合的 org.w3c.dom.EntityReference
节点。然后,您可以根据需要处理这些内容。
【讨论】:
【参考方案2】:既然你的 XML 输入似乎可以作为字符串使用,你能不能用正则表达式替换做一个简单的预处理?
xml = "...";
/* replace entities before parsing */
for (Map.Entry<String,String> entry : entityMapping.entrySet())
xml = xml.replaceAll("&" + entry.getKey() + ";", entry.getValue());
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
...
这很 hacky,您可能需要花费一些额外的精力来确保正则表达式只匹配它们真正应该匹配的位置(想想<entity name="&don't-match-me;"/>
),但至少它是……
当然,有比多次调用replaceAll()
更有效的方法来达到同样的效果。
【讨论】:
我事先不知道所有实体(正如我在问题中已经提到的),我只知道一个子集。当我最终编写自己的解析器以便我可以使用 xml 数据时,使用已经成熟的解析器存在的 xml 的意义何在? 重点是:那些成熟的解析器旨在处理格式良好的 XML。您没有格式正确的 XML,并且您正在寻找解决方法以使解析器无论如何都能处理它。 好吧,我们可以争论很多,但我仍然有这个问题要解决。 @Chris:我在使用“entityMapping”HashMap 时指的是您的原始代码示例。如果在 for 循环之后添加xml = xml.replaceAll("&[^;]*;", "");
之类的内容,我的方法可以让您处理未知的实体引用。您是对的,使用这种变通方法很烦人,但是由于您正在尝试实现 XML 并非旨在处理的事情,因此像上面这样的 hack 可能确实是您解决此问题的一种方法。坦率地说,它不是你必须编写的“解析器”,而是一个(非常简单的)预处理器。【参考方案3】:
您可以在文件的开头添加实体。更多信息请关注here。
您还可以查看this thread,其中似乎有人实现了 EntityResolver 接口(您也可以实现 EntityResolver2 !),您可以在其中动态处理实体(例如使用您建议的 Map)。
WARNING: there is a bug! 在 jdk6 中,但你可以用 jdk5 试试
【讨论】:
没有定义如何翻译引用的 DTD 文件,而且我也不知道文档中可能出现的所有实体引用,因此我无法自己创建它。我只知道经常出现的引用的一小部分,但也不能说那个子集有多大。 好的,但是您可以将这些实体转换为 RAW CDATA 部分,或者您可以“跳过”或将这些实体即时转换为有效的 xml-pure-text 或其他实体。像这样。否则你将没有机会。以上是关于如何处理未知实体引用?的主要内容,如果未能解决你的问题,请参考以下文章
Apache,如何处理未知的 php 文件,如标准 URL?