web-dev-qa-db-fra.com

Analyser un fichier XML contenant des entités HTML en Java sans changer le XML

Je dois analyser plusieurs fichiers XML en Java qui contiennent parfois - et de manière non valide - des entités HTML telles que —, > et ainsi de suite. Je comprends que la façon correcte de traiter cela consiste à ajouter des déclarations d'entité appropriées au fichier XML avant l'analyse. Cependant, je ne peux pas faire cela car je n'ai aucun contrôle sur ces fichiers XML.

Existe-t-il une sorte de rappel que je peux remplacer et qui est appelé chaque fois que l'analyseur XML Java rencontre une telle entité? Je n'ai pas pu en trouver un dans l'API.

J'aimerais utiliser:

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

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

J'ai découvert que je pouvais remplacer resolveEntity dans org.xml.sax.helpers.DefaultHandler, mais comment l'utiliser avec l'API de niveau supérieur?

Voici un exemple complet:

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

}

avec test.xml:

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

Produit:

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

Mise à jour: J'ai fouillé dans le code source de JDK avec un débogueur, et bon sang, quelle quantité de spaghettis. Je ne sais pas du tout quelle est la conception ou s'il y en a une. Combien de couches d'un oignon peuvent-elles être superposées?

Leur classe de clé semble être com.Sun.org.Apache.xerces.internal.impl.XMLEntityManager, mais je ne trouve aucun code qui me permet d’y ajouter des éléments avant qu’il ne soit utilisé ou qui tente de résoudre des entités sans passer par cette classe.

18
Johannes Ernst

Je voudrais utiliser une bibliothèque comme Jsoup à cette fin. J'ai testé ce qui suit ci-dessous et cela fonctionne. Je ne sais pas si cela aide. Vous pouvez le trouver ici: 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);
    }   


}

Résultat: 

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

Le chargement depuis un fichier peut être trouvé ici:

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

8
applecrusher

Issue - 1: Je dois analyser un tas de fichiers XML en Java qui parfois - et invalidly - contient des entités HTML telles que &mdash;

XML n'a que cinq entités prédéfinies . Le &mdash;, &nbsp; ne figure pas parmi eux. Il ne fonctionne que lorsqu'il est utilisé en HTML pur ou dans un JSP hérité. Donc, SAX ne va pas aider. Cela peut être fait en utilisant StaX qui a une API de haut niveau basée sur un itérateur. (Recueilli à partir de ce link )

Issue - 2: J'ai trouvé que je pouvais écraser resolEntity dans org.xml.sax.helpers.DefaultHandler, mais comment l'utiliser avec le API de niveau supérieur?

Streaming API for XML, appelée StaX, est une API pour reading and writing XML Documents.

StaX est un modèle d'analyse syntaxique. L'application peut prendre le contrôle de l'analyse des documents XML en extrayant les événements de l'analyseur.

L'API StaX de base tombe dans two categories et est répertoriée ci-dessous. Elles sont

  • API basée sur le curseur: Il s'agit de low-level API. L'API basée sur le curseur permet à l'application de traiter XML en tant que flux de jetons ou événements

  • API basée sur Iterator: L’API basée sur higher-level itérateur permet à l’application de traiter XML comme une série d’objets d’événement, qui communiquent chacun un élément de la structure XML à l’application. 

STaX API has support for the notion of not replacing character entity references, par le biais de IS_REPLACING_ENTITY_REFERENCES property:

Requiert que l'analyseur syntaxique remplace les références d'entité internes par leur texte de remplacement Et les signale sous forme de caractères

Cela peut être défini dans un XmlInputFactory, qui est ensuite utilisé pour construire un XmlEventReader ou XmlStreamReader

Cependant, l'API prend soin de préciser que cette propriété est uniquement destinée à forcer l'implémentation à effectuer le remplacement, plutôt que de la forcer à ne pas la remplacer.

Vous pouvez l'essayer. J'espère que cela résoudra votre problème. Pour votre cas, 

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>

Sortie:

Référence de l'entité: nbsp

Référence de l'entité: mdash

Le crédit va à @skaffman.

Lien connexe:

  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. Existe-t-il une API Java XML qui peut analyser un document sans résoudre les entités de caractères?

METTRE À JOUR:

Issue - 3: Existe-t-il un moyen d’utiliser StaX pour "filtrer" les entités (en les remplaçant Par quelque chose d’autre, par exemple) tout en produisant un document au format fin du processus?

Pour créer un nouveau document à l'aide de l'API StAX, il est nécessaire de créer une variable XMLStreamWriter fournissant des méthodes permettant de générer des balises d'ouverture et de fermeture XML, des attributs et du contenu en caractères. 

Il existe 5 méthodes de XMLStreamWriter pour document.

  1. xmlsw.writeStartDocument(); - initialise un document vide auquel des éléments peuvent être ajoutés
  2. xmlsw.writeStartElement(String s) - crée un nouvel élément nommé s
  3. xmlsw.writeAttribute(String name, String value)- ajoute l'attributname avec la valeur correspondante au dernier élément produit par acall à writeStartElement. Il est possible d’ajouter des attributs aussi longtempsaucun appel à writeElementStart, writeCharacters ou writeEndElement N’a été effectué.
  4. xmlsw.writeEndElement - ferme le dernier élément démarré
  5. xmlsw.writeCharacters(String s) - crée un nouveau nœud de texte avec content s comme contenu du dernier élément démarré.

Vous trouverez ci-joint un exemple d'exemple:

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

Pour plus, vous pouvez suivre le tutoriel

  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

Une autre approche, puisque vous n’utilisez de toute façon pas une approche OXM rigide. Vous voudrez peut-être essayer d’utiliser un analyseur moins rigide, tel que JSoup? Cela résoudra les problèmes immédiats liés à des schémas XML non valides, etc.

3
Richard

Juste pour jeter une approche différente à une solution:

Vous pouvez envelopper votre flux d'entrée avec une implémentation de flux qui remplace les entités par quelque chose de légal.

Bien que ce soit un bidouillage à coup sûr, ce devrait être une solution rapide et facile (ou mieux dire: solution de contournement).
Pas aussi élégant et propre qu'une solution interne du framework xml, cependant. 

1
rpy

Hier, j'ai créé quelque chose de similaire. Je dois ajouter de la valeur à partir du XML décompressé dans un flux de données. 

//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

Essayez ceci en utilisant le package 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