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

时间:2016-03-16 03:36:36

标签: 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 );

我发现我可以覆盖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&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,但我找不到任何代码可以让我在使用它之前添加东西,或尝试解析实体而不通过该类。

6 个答案:

答案 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&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

答案 1 :(得分:6)

  

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

XML只有five predefined entities&mdash;&nbsp;不在其中。它仅在纯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,然后将其用于构建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:

<?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. Is there a Java XML API that can parse a document without resolving character entities?
  5. <强>更新

      

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

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

    文档中有 5种方法 XMLStreamWriter

    1. xmlsw.writeStartDocument(); - 初始化一个空文档 可以添加元素
    2. xmlsw.writeStartElement(String s) - 创建一个名为s
    3. 的新元素
    4. xmlsw.writeAttribute(String name, String value) - 添加属性 name与a生成的最后一个元素的对应值 调用writeStartElement。可以添加属性 因为没有调用writeElementStart,writeCharacters或writeEndElement 已经完成了。
    5. xmlsw.writeEndElement - 关闭最后启动的元素
    6. xmlsw.writeCharacters(String s) - 创建一个新的文本节点 content s作为最后一个启动元素的内容。
    7. 随附一个示例:

      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

答案 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);