Подтвердить что ты не робот

Анализ XML файла, содержащего объекты HTML в Java, без изменения XML

Мне нужно разобрать кучу XML файлов на Java, которые иногда и недействительно содержат 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, но я не могу найти какой-либо код, который либо позволяет мне добавлять в него материал до его использования, либо пытается разрешить объекты, не пройдя через этот класс.

4b9b3361

Ответ 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: Я должен разобрать кучу XML файлов на Java, которые иногда - и неверно - содержать объекты HTML, такие как &mdash;

XML имеет только пять предопределенных объектов. &mdash;, &nbsp; не входит в их число. Он работает только при использовании в простом HTML или в устаревшем JSP. Таким образом, SAX не поможет. Это можно сделать, используя StaX, который имеет API-интерфейс с высоким уровнем итератора. (Собрано из этой ссылки)

Проблема - 2: Я обнаружил, что могу переопределить resolEntity в org.xml.sax.helpers.DefaultHandler, но как это использовать с API более высокого уровня?

Streaming API для XML, называемый StaX, является API для reading and writing XML Documents.

StaX - модель Pull-Parsing. Приложение может взять на себя управление анализом XML-документов, потянув (принимая) события от парсера.

Ядро StaX API попадает в two categories, и они перечислены ниже. Они

  • API на основе курсора: Это low-level API. API на основе курсора позволяет приложению обрабатывать XML как поток токенов или событий

  • API на основе Iterator: API-интерфейс, основанный на итераторе higher-level, позволяет приложению обрабатывать XML как ряд объектов событий, каждый из которых передает часть структуры XML в выражение.

STaX API has support for the notion of not replacing character entity references, используя свойство IS_REPLACING_ENTITY_REFERENCES:

Требует, чтобы синтаксический анализатор заменил ссылки на внутренние сущности своими заменить текст и сообщить о них как символы

Это можно установить в XmlInputFactory, который затем, в свою очередь, используется для построения XmlEventReader или XmlStreamReader.

Однако 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.

Связанная ссылка:

UPDATE:

Проблема - 3: Есть ли способ использовать StaX для "фильтрации" объектов (их замены с чем-то еще, например) и все еще производить документ на конец процесса?

Чтобы создать новый документ с использованием API StAX, необходимо создать XMLStreamWriter, который предоставляет методы для создания открывающих и закрывающих тегов XML, атрибутов и содержимого символов.

Для документа 5 методов XMLStreamWriter.

  • xmlsw.writeStartDocument(); - инициализирует пустой документ, к которому элементы могут быть добавлены
  • xmlsw.writeStartElement(String s) - создает новый элемент с именем s
  • xmlsw.writeAttribute(String name, String value) - добавляет атрибут имя с соответствующим значением последнему элементу, создаваемому вызов writeStartElement. Можно добавлять атрибуты так долго как нет вызова writeElementStart, writeCharacters или writeEndElement было сделано.
  • xmlsw.writeEndElement - закрыть последний начальный элемент
  • xmlsw.writeCharacters(String s) - создает новый текст node с content 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);
    }

}

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

Для получения дополнительной информации вы можете следовать руководству

Ответ 3

Другой подход, поскольку в любом случае вы не используете жесткий OXM-подход. Возможно, вы захотите попробовать использовать менее жесткий парсер, такой как JSoup? Это остановит немедленные проблемы с недопустимыми схемами XML и т.д., Но это просто передаст проблему в ваш код.

Ответ 4

Просто чтобы применить другой подход к решению:

Вы можете окупить свой входной поток потоком, который заменяет объекты каким-то юридическим.

Хотя это взломать наверняка, это должно быть быстрым и простым решением (или, лучше сказать, обходным путем).
Тем не менее, это не так элегантно и чисто, как внутреннее решение xml framework.

Ответ 5

Я сделал вчера что-то подобное, мне нужно добавить значение из 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;
 }