在不改变XML的情况下,在Java中解析包含HTML实体的XML文件

我必须用Java解析一堆XML文件,有时 – 并且无效地 – 包含HTML实体,例如> 等等。 我理解处理这个问题的正确方法是在解析之前向XML文件添加合适的实体声明。 但是,我无法做到这一点,因为我无法控制这些XML文件。

是否有某种我可以覆盖的回调,只要Java XML解析器遇到这样的实体就会调用它? 我无法在API中找到一个。

我想用:

 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder parser = dbf.newDocumentBuilder(); Document doc = parser.parse( stream ); 

我发现我可以覆盖org.xml.sax.helpers.DefaultHandler resolveEntity ,但是如何在更高级别的API中使用它?

这是一个完整的例子:

 public class Main { public static void main( String [] args ) throws Exception { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder parser = dbf.newDocumentBuilder(); Document doc = parser.parse( new FileInputStream( "test.xml" )); } 

}

使用test.xml:

   Some text — invalid!  

生产:

 [Fatal Error] :3:20: The entity "nbsp" was referenced, but not declared. Exception in thread "main" org.xml.sax.SAXParseException; lineNumber: 3; columnNumber: 20; The entity "nbsp" was referenced, but not declared. 

更新:我一直在使用调试器和男孩,在JDK源代码中探讨,意味着什么样的意大利面。 我不知道那里的设计是什么,或者是否有设计。 洋葱的层数可以叠加多少层?

它们的关键类似乎是com.sun.org.apache.xerces.internal.impl.XMLEntityManager ,但是我找不到任何代码可以让我在使用它之前添加东西,或者尝试解析实体而不通过那个class。

为此,我会使用像Jsoup这样的库。 我在下面测试了以下内容并且它有效。 我不知道这是否有帮助。 它可以位于: http : //jsoup.org/download

 public static void main(String args[]){ String html = "" + "Some text — invalid!"; Document doc = Jsoup.parse(html, "", Parser.xmlParser()); for (Element e : doc.select("bar")) { System.out.println(e); } } 

结果:

  Some text — invalid!  

可以在此处找到从文件加载:

http://jsoup.org/cookbook/input/load-document-from-file

问题 – 1:我必须用Java 解析一堆XML文件 ,有时 – 并且无效地 – 包含HTML实体,例如

XML只有五个预定义实体 。   不属于他们。 它仅在纯HTML或旧JSP中使用时才有效。 所以,SAX无济于事。 它可以使用具有基于高级迭代器的API的 StaX来完成。 (从此链接收集)

问题-2:我发现我可以覆盖org.xml.sax.helpers.DefaultHandler中的resolveEntity,但是如何在更高级别的API中使用它

用于XML的Streaming API(称为StaX )是用于reading and writing XML Documents的API。

StaX是一种Pull-Parsing模型。 应用程序可以通过从解析器中提取(获取)事件来控制解析XML文档。

核心StaX API分为two categories ,下面列出了它们。 他们是

  • 基于游标的API:它是low-level API 。 基于游标的API允许应用程序将XML作为标记流事件处理

  • 基于迭代器的API:基于higher-level迭代器的API允许应用程序将XML作为一系列事件对象进行处理,每个事件对象都将一段XML结构传递给应用程序。

STaX API has support for the notion of not replacing character entity references通过IS_REPLACING_ENTITY_REFERENCES属性STaX API has support for the notion of not replacing character entity references

需要解析器将内部实体引用替换为其替换文本并将其作为字符报告

这可以设置为XmlInputFactory ,然后用于构造XmlEventReaderXmlStreamReader

但是,API谨慎地说,此属性仅用于强制实现执行替换,而不是强制它替换它们。

你可以尝试一下。 希望它能解决你的问题。 对于你的情况,

Main.java

 import java.io.FileInputStream; import java.io.FileNotFoundException; import javax.xml.stream.XMLEventReader; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.events.EntityReference; import javax.xml.stream.events.XMLEvent; public class Main { public static void main(String[] args) { XMLInputFactory inputFactory = XMLInputFactory.newInstance(); inputFactory.setProperty( XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, false); XMLEventReader reader; try { reader = inputFactory .createXMLEventReader(new FileInputStream("F://test.xml")); while (reader.hasNext()) { XMLEvent event = reader.nextEvent(); if (event.isEntityReference()) { EntityReference ref = (EntityReference) event; System.out.println("Entity Reference: " + ref.getName()); } } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (XMLStreamException e) { e.printStackTrace(); } } } 

的test.xml:

   Some text — invalid!  

输出:

实体参考:nbsp

实体参考:mdash

@skaffman

相关链接:

  1. http://www.journaldev.com/1191/how-to-read-xml-file-in-java-using-java-stax-api
  2. http://www.journaldev.com/1226/java-stax-cursor-based-api-read-xml-example
  3. http://www.vogella.com/tutorials/JavaXML/article.html
  4. 是否有Java XML API可以在不解析字符实体的情况下解析文档?

更新:

问题-3:有没有办法使用StaX“过滤”实体(例如用其他东西替换它们)并在流程结束时仍然生成一个文档?

要使用StAX API创建新文档,需要创建一个XMLStreamWriter ,它提供了生成XML打开和关闭标记,属性和字符内容的方法。

XMLStreamWriter5种方法用于文档。

  1. xmlsw.writeStartDocument(); – 初始化可以添加元素的空文档
  2. xmlsw.writeStartElement(String s)创建一个名为s的新元素
  3. xmlsw.writeAttribute(String name, String value) – 将属性名称和相应的值添加到对writeStartElement的调用产生的最后一个元素。 只要没有调用writeElementStart,writeCharacters或writeEndElement,就可以添加属性。
  4. xmlsw.writeEndElement – 关闭最后一个启动的元素
  5. xmlsw.writeCharacters(String s) – 创建一个新文本节点,其内容为s作为最后一个启动元素的内容。

随附一个示例:

StAXExpand.java

 import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; import java.util.Arrays; public class StAXExpand { static XMLStreamWriter xmlsw = null; public static void main(String[] argv) { try { xmlsw = XMLOutputFactory.newInstance() .createXMLStreamWriter(System.out); CompactTokenizer tok = new CompactTokenizer( new FileReader(argv[0])); String rootName = "dummyRoot"; // ignore everything preceding the word before the first "[" while(!tok.nextToken().equals("[")){ rootName=tok.getToken(); } // start creating new document xmlsw.writeStartDocument(); ignorableSpacing(0); xmlsw.writeStartElement(rootName); expand(tok,3); ignorableSpacing(0); xmlsw.writeEndDocument(); xmlsw.flush(); xmlsw.close(); } catch (XMLStreamException e){ System.out.println(e.getMessage()); } catch (IOException ex) { System.out.println("IOException"+ex); ex.printStackTrace(); } } public static void expand(CompactTokenizer tok, int indent) throws IOException,XMLStreamException { tok.skip("["); while(tok.getToken().equals("@")) {// add attributes String attName = tok.nextToken(); tok.nextToken(); xmlsw.writeAttribute(attName,tok.skip("[")); tok.nextToken(); tok.skip("]"); } boolean lastWasElement=true; // for controlling the output of newlines while(!tok.getToken().equals("]")){ // process content String s = tok.getToken().trim(); tok.nextToken(); if(tok.getToken().equals("[")){ if(lastWasElement)ignorableSpacing(indent); xmlsw.writeStartElement(s); expand(tok,indent+3); lastWasElement=true; } else { xmlsw.writeCharacters(s); lastWasElement=false; } } tok.skip("]"); if(lastWasElement)ignorableSpacing(indent-3); xmlsw.writeEndElement(); } private static char[] blanks = "\n".toCharArray(); private static void ignorableSpacing(int nb) throws XMLStreamException { if(nb>blanks.length){// extend the length of space array blanks = new char[nb+1]; blanks[0]='\n'; Arrays.fill(blanks,1,blanks.length,' '); } xmlsw.writeCharacters(blanks, 0, nb+1); } } 

CompactTokenizer.java

 import java.io.Reader; import java.io.IOException; import java.io.StreamTokenizer; public class CompactTokenizer { private StreamTokenizer st; CompactTokenizer(Reader r){ st = new StreamTokenizer(r); st.resetSyntax(); // remove parsing of numbers... st.wordChars('\u0000','\u00FF'); // everything is part of a word // except the following... st.ordinaryChar('\n'); st.ordinaryChar('['); st.ordinaryChar(']'); st.ordinaryChar('@'); } public String nextToken() throws IOException{ st.nextToken(); while(st.ttype=='\n'|| (st.ttype==StreamTokenizer.TT_WORD && st.sval.trim().length()==0)) st.nextToken(); return getToken(); } public String getToken(){ return (st.ttype == StreamTokenizer.TT_WORD) ? st.sval : (""+(char)st.ttype); } public String skip(String sym) throws IOException { if(getToken().equals(sym)) return nextToken(); else throw new IllegalArgumentException("skip: "+sym+" expected but"+ sym +" found "); } } 

有关更多信息,请按照教程进行操作

  1. https://docs.oracle.com/javase/tutorial/jaxp/stax/example.html
  2. http://www.ibm.com/developerworks/library/x-tipstx2/index.html
  3. http://www.iro.umontreal.ca/~lapalme/ForestInsteadOfTheTrees/HTML/ch09s03.html
  4. http://staf.sourceforge.net/current/STAXDoc.pdf

另一种方法,因为你还没有使用严格的OXM方法。 您可能想尝试使用不那么严格的解析器,例如JSoup? 这将阻止无效XML模式等的即时问题,但它只会将问题转移到您的代码中。

只是提出一种不同的解决方案:

您可以使用流式实现来对输入流进行封装,以便通过合法的方式替换实体。

虽然这是一个肯定的黑客,它应该是一个快速简单的解决方案(或更好的说:解决方法)。
不过,它不像xml框架内部解决方案那样优雅和干净。

我昨天做了类似的事情,我需要从流中的unziped XML添加到数据库的值。

 //import I'm not sure if all are necessary :) import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.xpath.*; import org.w3c.dom.Document; import org.xml.sax.InputSource; import org.xml.sax.SAXException; //I didnt checked this code now because i'm in work for sure its work maybe you will need to do little changes InputSource is = new InputSource(new FileInputStream("test.xml")); DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db = dbf.newDocumentBuilder(); Document doc = db.parse(is); XPathFactory xpf = XPathFactory.newInstance(); XPath xpath = xpf.newXPath(); String words= xpath.evaluate("/foo/bar", doc.getDocumentElement()); ParsingHexToChar.parseToChar(words); // lib which i use common-lang3.jar //metod to parse public static String parseToChar( String words){ String decode= org.apache.commons.lang3.StringEscapeUtils.unescapeHtml4(words); return decode; }