如何让XML Parser了解所有字符实体引用?

时间:2016-08-04 15:06:03

标签: java xml parsing xml-parsing

我从服务器获取任意XML并使用此Java代码解析它:

String xmlStr; // arbitrary XML input
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 
try {
    DocumentBuilder builder = factory.newDocumentBuilder();
    InputSource is = new InputSource(new StringReader(xmlStr));
    return builder.parse(is);
}
catch (SAXException | IOException | ParserConfigurationException e) {
    LOGGER.error("Failed to  parse XML.", e);
}

每隔一段时间,XML输入就包含一些未知的实体引用,如 ,并且失败并显示错误,例如org.xml.sax.SAXParseException: The entity "nbsp" was referenced, but not declared.

我可以通过预处理原始xmlStr并在解析之前翻译所有有问题的实体引用来解决此问题。这是一个有效的虚拟实现:

protected static String translateEntityReferences(String xml) {
    String newXml = xml;
    Map<String, String> entityRefs = new HashMap<>();
    entityRefs.put("&nbsp;", "&#160;");
    entityRefs.put("&laquo;", "&#171;");
    entityRefs.put("&raquo;", "&#187;");
    // ... and 250 more...
    for(Entry<String, String> er : entityRefs.entrySet()) {
        newXml = newXml.replace(er.getKey(), er.getValue());
    }
    return newXml;
}

然而,这真的不能令人满意,因为are a huge number of entity references我不希望所有硬编码到我的Java类中。

有没有简单的方法来教授DocumentBuilder的整个字符实体引用列表?

1 个答案:

答案 0 :(得分:1)

如果您可以更改代码以使用StAX而不是DOM,那么简单的解决方案是使用设置为XMLInputFactory的{​​{1}}属性IS_REPLACING_ENTITY_REFERENCES

false

输出:

public static void main(String[] args) throws Exception
{
    String doc = "<doc>&nbsp;</doc>";
    ByteArrayInputStream is = new ByteArrayInputStream(doc.getBytes());

    XMLInputFactory xif = XMLInputFactory.newFactory();
    xif.setProperty(javax.xml.stream.XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, false);
    XMLStreamReader xr = xif.createXMLStreamReader(is);

    while(xr.hasNext())
    {
        int t = xr.getEventType();
        switch(t) {
            case XMLEvent.ENTITY_REFERENCE:
                System.out.println("Entity: "+ xr.getLocalName());
                break;
            case XMLEvent.START_DOCUMENT:
                System.out.println("Start Document");
                break;
            case XMLEvent.START_ELEMENT:
                System.out.println("Start Element: " + xr.getLocalName());
                break;
            case XMLEvent.END_DOCUMENT:
                System.out.println("End Document");
                break;
            case XMLEvent.END_ELEMENT:
                System.out.println("End Element: " + xr.getLocalName());
                break;
            default:
                System.out.println("Other:  ");
                break;
        }
        xr.next();
    }
}

但是如果你真的需要内存中的完整DOM树,那么你的代码中可能需要重写太多。

我花了一个小时来跟踪DOM实现,但找不到任何方法来从Start Document Start Element: doc Entity: nbsp null End Element: doc 读取DOM解析器。

在代码中还有证据表明内部DOM解析器实现有一个类似于XMLStreamReader的选项,但我找不到任何方法从外部设置它。