在Java中解析包含HTML实体的XML文件而不更改XML

Joh*_*nst 18 java xml xml-parsing

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

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

我想用:

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

DocumentBuilder parser = dbf.newDocumentBuilder();
Document        doc    = parser.parse( stream );
Run Code Online (Sandbox Code Playgroud)

我发现我可以重写resolveEntityorg.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" ));
    }
Run Code Online (Sandbox Code Playgroud)

}

使用test.xml:

<?xml version="1.0" encoding="UTF-8"?>
<foo>
    <bar>Some&nbsp;text &mdash; invalid!</bar>
</foo>
Run Code Online (Sandbox Code Playgroud)

生产:

[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.
Run Code Online (Sandbox Code Playgroud)

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

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

app*_*her 8

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

public static void main(String args[]){


    String html = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><foo>" + 
                  "<bar>Some&nbsp;text &mdash; invalid!</bar></foo>";
    Document doc = Jsoup.parse(html, "", Parser.xmlParser());

    for (Element e : doc.select("bar")) {
        System.out.println(e);
    }   


}
Run Code Online (Sandbox Code Playgroud)

结果:

<bar>
 Some&nbsp;text — invalid!
</bar>
Run Code Online (Sandbox Code Playgroud)

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

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


Sky*_*ker 6

问题 - 1:我必须解析 Java中的一堆XML文件,有时 - 并且无效地 - 包含HTML实体,例如&mdash;

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

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

用于XML的Streaming API,称为StaX,是一个API reading and writing XML Documents.

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属性:

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

这可以设置为a 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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

的test.xml:

<?xml version="1.0" encoding="UTF-8"?>
<foo>
    <bar>Some&nbsp;text &mdash; invalid!</bar>
</foo>
Run Code Online (Sandbox Code Playgroud)

输出:

实体参考: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开始和结束标记,属性和字符内容的方法.

5种方法XMLStreamWriter文档.

  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);
    }

}
Run Code Online (Sandbox Code Playgroud)

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 ");
    }
}
Run Code Online (Sandbox Code Playgroud)

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

  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