如何处理未知实体引用?

8

我正在解析(大量的)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模型的工作方式。我认为这些概念在很大程度上是相同的。 - Marvin Smit
你是指像EntityResolver这样的吗?那肯定听起来应该可以工作,但是当我查看它的API时,它似乎并不完全针对这种类型的实体。不过尝试一下应该不会有任何损害。 - Paul Clapham
1
EntityResolver 用于解析 外部 实体(例如 DTD),但我们正在寻找处理 内部 实体的东西。 - skaffman
EntityResolver2无法通过InputSource resolveEntity(String name, String publicId, String baseURI, String systemId)来实现这个功能吗? - Karussell
3个回答

4
StAX API提供了支持。请查看XMLInputFactory,它有一个运行时属性来决定是否展开内部实体,或者保留在原处。如果设置为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有何意义呢? - Chris
2
重点在于:那些成熟的解析器是设计用来处理格式良好的XML的。你没有格式良好的XML,而且你正在寻找解决方法让解析器无论如何都能处理它。 - Paul Clapham
好的,我们可以争论很多,但我仍然需要解决这个问题。 - Chris
@Chris:在我的 'entityMapping' HashMap 中,我是指你的原始代码示例。如果你在 for 循环之后添加类似 xml = xml.replaceAll("&[^;]*;", ""); 的内容,我的方法可以让你处理未知实体引用。你说得对,使用这样的解决方法很烦人,但由于你试图实现 XML 无法处理的功能,像上面那样的 hack 可能确实是解决这个问题的一种方式。而且,坦白地说,你需要编写的不是一个“解析器”,而是一个(非常简单的)预处理器。 - Thomas

0

你可以在文件开头添加实体。点击这里了解更多信息。

你也可以看看这个帖子,其中有人似乎已经实现了EntityResolver接口(你也可以实现EntityResolver2!),在那里你可以动态处理实体(例如使用你提出的Map)。

警告:jdk6中存在一个错误!但你可以尝试使用jdk5。


1
没有定义如何翻译引用的DTD文件,我也不知道文档中可能出现的所有实体引用,因此无法自己创建。我只知道一小部分经常出现的引用,但也不能确定这个子集有多大。 - Chris
好的,但是你要么将这些实体转换为原始 CDATA 部分,要么可以在飞行中“跳过”或将这些实体转换为有效的 xml-纯文本或类似于此的东西。否则你就没有机会了。 - Karussell

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