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

2022-09-02 04:01:33

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

是否有某种我可以重写的回调,每当 Java XML 解析器遇到这样的实体时,就会调用该回调?我无法在API中找到一个。

我想使用:

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

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

我发现我可以在 中覆盖,但是如何将其与更高级别的API一起使用?resolveEntityorg.xml.sax.helpers.DefaultHandler

下面是一个完整的示例:

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

}

与测试.xml:

<?xml version="1.0" encoding="UTF-8"?>
<foo>
    <bar>Some&nbsp;text &mdash; 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


答案 1

为此,我会使用像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);
    }   


}

结果:

<bar>
 Some&nbsp;text — invalid!
</bar>

可在此处找到从文件加载的内容:

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


答案 2

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

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

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

用于 XML 的流式 API,称为 StaX,是 用于 的 API。reading and writing XML Documents

StaX是拉取解析模型。应用程序可以通过从解析器中提取(获取)事件来控制 XML 文档的分析。

核心 StaX API 属于,它们在下面列出。他们是two categories

  • 基于游标的 API:是的。基于游标的 API 允许应用程序将 XML 作为令牌流(即事件)进行处理low-level API

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

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

要求分析器将内部实体引用替换为其替换文本,并将其报告为字符

这可以设置为 一个 ,然后又用于构造 一个 或 。XmlInputFactoryXmlEventReaderXmlStreamReader

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

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

主要.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();
        }
    }
}

测试.xml:

<?xml version="1.0" encoding="UTF-8"?>
<foo>
    <bar>Some&nbsp;text &mdash; invalid!</bar>
</foo>

输出:

实体参考: 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 创建新文档,需要创建一个提供生成 XML 开始和结束标记、属性和字符内容的方法的 API。XMLStreamWriter

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

}

紧凑型刻度.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

推荐