我必须解析Java中的一堆XML文件,有时 - 并且无效地 - 包含HTML实体,例如—
,>
等等。我理解处理这个问题的正确方法是在解析之前向XML文件添加合适的实体声明。但是,我无法做到这一点,因为我无法控制这些XML文件。
当Java XML解析器遇到这样的实体时,是否存在我可以覆盖的某种回调?我还没能在API中找到一个。
我想使用:
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder parser = dbf.newDocumentBuilder();
Document doc = parser.parse( stream );
我发现我可以覆盖resolveEntity
中的org.xml.sax.helpers.DefaultHandler
,但如何将其与更高级别的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:
<?xml version="1.0" encoding="UTF-8"?>
<foo>
<bar>Some text — invalid!</bar>
</foo>
产地:
[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
,但我找不到任何代码可以让我在使用它之前添加东西,或尝试解析实体而不通过该类。
答案 0 :(得分:8)
为此,我会使用像Jsoup这样的库。我测试了下面的以下内容并且它有效。我不知道这是否有帮助。它可以位于:http://jsoup.org/download
public static void main(String args[]){
String html = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><foo>" +
"<bar>Some text — invalid!</bar></foo>";
Document doc = Jsoup.parse(html, "", Parser.xmlParser());
for (Element e : doc.select("bar")) {
System.out.println(e);
}
}
结果:
<bar>
Some text — invalid!
</bar>
可以在此处找到从文件加载:
答案 1 :(得分:6)
问题 - 1:我必须解析Java中的一堆XML文件 - 有时 - 和 无效 - 包含HTML实体,例如
—
XML只有five predefined entities。 —
,
不在其中。它仅在纯HTML或旧JSP中使用时才有效。所以,SAX无济于事。可以使用StaX
具有基于高级迭代器的API 来完成。 (收集自link)
问题 - 2:我发现我可以覆盖resolveEntity org.xml.sax.helpers.DefaultHandler,但我如何使用它 更高级别的API ?
XML的流式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属性:
要求解析器用它们替换内部实体引用 替换文本并将其作为字符报告
可以将其设置为XmlInputFactory
,然后将其用于构建XmlEventReader
或XmlStreamReader
。
但是,API谨慎地说,此属性仅用于强制实现执行替换,而不是强制它替换它们。
你可以尝试一下。希望它能解决你的问题。对于你的情况,
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();
}
}
}
<?xml version="1.0" encoding="UTF-8"?>
<foo>
<bar>Some text — invalid!</bar>
</foo>
<强>输出:
实体参考:nbsp
实体参考:mdash
信用转到@skaffman
。
相关链接:
<强>更新
问题 - 3:有没有办法使用StaX来“过滤”实体(替换它们) 与其他东西,例如)并仍然生成一个文件 这个过程的结束?
要使用StAX API创建新文档,需要创建一个XMLStreamWriter
,它提供了生成XML开始和结束标记,属性和字符内容的方法。
文档中有 5种方法 XMLStreamWriter
。
xmlsw.writeStartDocument();
- 初始化一个空文档
可以添加元素xmlsw.writeStartElement(String s)
- 创建一个名为s xmlsw.writeAttribute(String name, String value)
- 添加属性
name与a生成的最后一个元素的对应值
调用writeStartElement。可以添加属性
因为没有调用writeElementStart,writeCharacters或writeEndElement
已经完成了。xmlsw.writeEndElement
- 关闭最后启动的元素xmlsw.writeCharacters(String s)
- 创建一个新的文本节点
content s作为最后一个启动元素的内容。随附一个示例:
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);
}
}
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 ");
}
}
更多信息,您可以按照教程
进行操作答案 2 :(得分:3)
另一种方法,因为您无论如何都不使用严格的OXM方法。 您可能想尝试使用不太严格的解析器,例如JSoup? 这将阻止无效XML模式等的直接问题,但它只会将问题转移到您的代码中。
答案 3 :(得分:1)
只是提出一种不同的解决方案:
您可以使用流式补充来封装输入流,以便通过合法的内容替换实体。
虽然这是一个肯定的黑客,它应该是一个快速简单的解决方案(或更好的说:解决方法)。
不过,它不像xml框架内部解决方案那样优雅和干净。
答案 4 :(得分:1)
我昨天做了类似的事情,我需要在流中向数据库中添加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;
}
答案 5 :(得分:0)
使用org.apache.commons包尝试一下:
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder parser = dbf.newDocumentBuilder();
InputStream in = new FileInputStream(xmlfile);
String unescapeHtml4 = IOUtils.toString(in);
CharSequenceTranslator obj = new AggregateTranslator(new LookupTranslator(EntityArrays.ISO8859_1_UNESCAPE()),
new LookupTranslator(EntityArrays.HTML40_EXTENDED_UNESCAPE())
);
unescapeHtml4 = obj.translate(unescapeHtml4);
StringReader readerInput= new StringReader(unescapeHtml4);
InputSource is = new InputSource(readerInput);
Document doc = parser.parse(is);