it-swarm.dev

Analyzování XML souboru obsahujícího HTML entity v Javě bez změny XML

Musím analyzovat spoustu XML souborů v Javě, které někdy - a neplatně - obsahují HTML entity jako —, > a tak dále. Rozumím, že správný způsob, jak se s tím vypořádat, je přidat vhodné deklarace entity do XML souboru před syntaktickou analýzou. Nemohu to však udělat, protože nemám žádnou kontrolu nad těmito soubory XML.

Existuje nějaký druh zpětného volání, které mohu přepsat, když je spuštěn vždy, když se Java XML analyzátor setká s takovou entitou? V API jsem nebyl schopen najít.

Chci použít:

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

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

Zjistil jsem, že můžu přepsat resolveEntity v souboru org.xml.sax.helpers.DefaultHandler, ale jak je mohu použít s rozhraním API vyšší úrovně?

Zde je úplný příklad:

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

}

s test.xml:

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

Produkuje:

[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.

Update: Byl jsem se strkat do zdrojového kódu JDK s debugger, a chlapec, jaké množství špaget. Nemám tušení, co je to za design, nebo zda existuje. Kolik vrstev cibule může jedna vrstva na sobě?

Zdá se, že klíčová třída je com.Sun.org.Apache.xerces.internal.impl.XMLEntityManager, ale nemohu najít žádný kód, který by mi umožnil přidat věci do něj dříve, než se použije, nebo se pokusí vyřešit entity bez toho, aby prošli touto třídou.

18
Johannes Ernst

K tomuto účelu bych použil knihovnu jako Jsoup. Testoval jsem následující postup a funguje to. Nevím, jestli to pomůže. Může být umístěn zde: 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);
    }   


}

Výsledek: 

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

Načítání ze souboru naleznete zde:

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

8
applecrusher

Problém - 1: Musím analyzovat spoustu XML souborů v Javě, které někdy - a Neplatně - obsahují HTML entity jako &mdash;

XML má pouze pět předdefinovaných entit . &mdash;, &nbsp; mezi nimi nepatří. Funguje pouze při použití v prostém HTML nebo v JSP. SAX tak nepomůže. Lze to provést pomocí StaX, které má API na vysoké úrovni iterátoru. (Shromážděno z tohoto odkazu )

Problém - 2: Zjistil jsem, že mohu přepsat řešeníEntity v Org.xml.sax.helpers.DefaultHandler, ale jak to mám použít s rozhraním Vyšší úrovně API?

Streaming API pro XML, nazvaný StaX, je API pro reading and writing XML Documents.

StaX je model Pull-Parsing. Aplikace může převzít kontrolu nad syntaktickou analýzou dokumentů XML tažením (převzetím) událostí z analyzátoru.

Jádro StaX API spadá do two categories a jsou uvedeny níže. Oni jsou

  • Kurzorově založené API: Je to low-level API. Rozhraní API založené na kurzorech umožňuje aplikaci zpracovat XML jako tok tokeny aka události

  • API založené na Iteratoru: Rozhraní API higher-level založené na iterátoru umožňuje aplikaci zpracovávat XML jako sérii objektů událostí, z nichž každá komunikuje aplikaci se strukturou XML. 

STaX API has support for the notion of not replacing character entity references, prostřednictvím IS_REPLACING_ENTITY_REFERENCES property:

Vyžaduje, aby syntaktický analyzátor nahradil odkazy na interní entitu svým nahrazujícím textem A oznámil je jako znaky

To může být nastaveno na XmlInputFactory, které je pak použito k vytvoření XmlEventReader nebo XmlStreamReader

Rozhraní API však dbá na to, aby uvedla, že tato vlastnost je určena pouze k vynucení implementace k provedení náhrady, a nikoli k jejímu vynucení.

Můžete to zkusit. Doufám, že to vyřeší váš problém. Pro váš případ 

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>

Výstup:

Odkaz na entitu: nbsp

Odkaz na entitu: mdash

Zápočet jde do @skaffman.

Související odkaz:

  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. Existuje Java XML API, které dokáže analyzovat dokument bez rozlišení znakových entit?

AKTUALIZACE:

Problém - 3: Existuje způsob, jak použít StaX k "filtrování" entit (například nahrazení Něčím jiným) a ještě vytvoření dokumentu na konci proces?

Chcete-li vytvořit nový dokument pomocí rozhraní API StAX, je nutné vytvořit soubor XMLStreamWriter, který poskytuje metody pro vytváření značek otevírání a zavírání XML, atributů a obsahu znaků. 

Existují 5 metodXMLStreamWriter pro dokument.

  1. xmlsw.writeStartDocument(); - inicializuje prázdný dokument, ke kterému mohou být přidány prvky
  2. xmlsw.writeStartElement(String s) - vytvoří nový prvek s názvem s
  3. xmlsw.writeAttribute(String name, String value)- přidá název atributu s odpovídající hodnotou do posledního prvku vytvořeného voláním k příkazu writeStartElement. Je možné přidávat atributy tak dlouho, jako Jako žádné volání writeElementStart, writeCharacters nebo writeEndElement .
  4. xmlsw.writeEndElement - zavřete poslední zapnutý prvek
  5. xmlsw.writeCharacters(String s) - vytvoří nový textový uzel s obsahem s obsahem posledního započatého prvku.

Vzorový příklad je k němu připojen:

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

Další informace naleznete v tutoriálu

  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
6
SkyWalker

Další přístup, protože stejně nepoužíváte rigidní přístup OXM. Možná budete chtít zkusit použít méně rigidní syntaktický analyzátor, jako je JSoup? Tím se zastaví okamžité problémy s neplatnými schématy XML atd., Ale problém se převede do vašeho kódu.

3
Richard

Jednoduše vrhnout jiný přístup k řešení:

Můžete obálku svého vstupního proudu s proudovou implementací, která nahradí entity něčím legálním.

I když je to jistě hack, mělo by to být rychlé a snadné řešení (nebo lépe řečeno: řešení).
Není tak elegantní a čistý jako interní řešení xml rámce. 

1
rpy

Udělal jsem včera něco podobného musím přidat hodnotu z rozbalené XML v proudu do databáze. 

//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;
 }
1
Marek Derdzinski

Zkuste to pomocí balíčku 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);    
0
V_Dev