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 text — 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.
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 text — invalid!</bar></foo>";
Document doc = Jsoup.parse(html, "", Parser.xmlParser());
for (Element e : doc.select("bar")) {
System.out.println(e);
}
}
Résultat:
<bar>
Some text — invalid!
</bar>
Le chargement depuis un fichier peut être trouvé ici:
Issue - 1: Je dois analyser un tas de fichiers XML en Java qui parfois - et invalidly - contient des entités HTML telles que
—
XML n'a que cinq entités prédéfinies . Le —
,
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,
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 version="1.0" encoding="UTF-8"?>
<foo>
<bar>Some text — 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:
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.
xmlsw.writeStartDocument();
- initialise un document vide auquel des éléments peuvent être ajoutésxmlsw.writeStartElement(String s)
- crée un nouvel élément nommé sxmlsw.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é.xmlsw.writeEndElement
- ferme le dernier élément démarré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:
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);
}
}
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
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.
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.
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;
}
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);