Синтаксический анализ потока XML без корневого элемента


Мне нужно разобрать непрерывный поток хорошо сформированных XML-элементов, для которых мне дан только уже построенный объект java.io.Reader. Эти элементы не заключены в корневой элемент, и они не предваряются заголовком XML, как <?xml version="1.0"?>", но в остальном являются допустимыми XML.

Использование класса Java org.xml.sax.XMLReader не работает, поскольку средство чтения XML ожидает разбора хорошо сформированного XML, начиная с заключающего корневого элемента. Таким образом, он просто считывает первый элемент в потоке, который он воспринимает как корень, и терпит неудачу в следующем, с типичным

Орг.XML.саксофон.SAXParseException: разметка в документе после корневого элемента должна быть хорошо сформирована.

Для файлов, которые не содержат корневого элемента, но где такой элемент существует или может быть определен (и называется, скажем, MyRootElement), можно сделать что-то вроде следующего:

        Strint path = <the full path to the file>;

        XMLReader xmlReader = SAXParserFactory.newInstance().newSAXParser().getXMLReader();

        StringBuilder buffer = new StringBuilder();

        buffer.append("<?xml version="1.0"?>n");
        buffer.append("<!DOCTYPE MyRootElement ");
        buffer.append("[<!ENTITY data SYSTEM "file:///");
        buffer.append(path);
        buffer.append("">]>n");
        buffer.append("<MyRootElement xmlns:...>n");
        buffer.append("&data;n");
        buffer.append("</MyRootElement>n");

        InputSource source = new InputSource(new StringReader(buffer.toString()));

        xmlReader.parse(source);

Я протестировал вышесказанное, сохранив часть вывода java.io.Reader в файл, и это работает. Однако такой подход является не применимо в моем случае, и такая дополнительная информация (заголовок XML, корневой элемент) не может быть вставлена, так как объект java.io.Reader, переданный в мой код, уже построен.

По сути, я ищу "фрагментированный синтаксический анализ XML". Итак, мой вопрос заключается в том, можно ли это сделать, используя стандартные API Java (включая пакеты org.sax.xml.* и java.xml.*)?

6 16

6 ответов:

SequenceInputStream приходит на помощь:

    SAXParserFactory saxFactory = SAXParserFactory.newInstance();
    SAXParser parser = saxFactory.newSAXParser();

    parser.parse(
        new SequenceInputStream(
            Collections.enumeration(Arrays.asList(
            new InputStream[] {
                new ByteArrayInputStream("<dummy>".getBytes()),
                new FileInputStream(file),//bogus xml
                new ByteArrayInputStream("</dummy>".getBytes()),
            }))
        ), 
        new DefaultHandler()
    );

Вы можете обернуть данный Reader в подкласс FilterReader, который вы реализуете, чтобы сделать более или менее то, что вы делаете здесь.

Редактировать:

Хотя это похоже на предложение реализовать собственное делегирование Reader данному объекту Reader, данное парой других ответов, почти все методы в FilterReader должны быть переопределены, поэтому вы не можете получить много от использования суперкласса.

Интересным вариантом других предложений могло бы стать осуществление SequencedReader, который оборачивает несколько объектов Reader и переходит к следующему в последовательности, когда один из них используется. Затем вы можете передать объект StringReader с начальным текстом для корня, который вы хотите добавить, исходный Reader и другой StringReader с закрывающим тегом.

Вы можете написать свою собственную реализацию Reader, которая инкапсулирует данный Вам экземпляр Reader. Этот новый читатель должен делать то же самое, что и вы в своем примере кода, предоставить заголовок и корневой элемент, затем данные из базового читателя и, в конце концов, закрывающий корневой тег. Следуя этим путем, вы можете предоставить допустимый поток XML синтаксическому анализатору XML, а также использовать объект Reader, переданный в ваш код.

Просто вставьте фиктивный корневой элемент. Самое элегантное решение, которое я могу придумать, - это создать свой собственный InputStream или Reader, который обертывает обычный InputSteam / Reader и возвращает фиктивный <dummyroot>, когда вы вызываете его read() / readLine() в первый раз, а затем возвращает результат потока полезной нагрузки. Это должно удовлетворить Sax parser.

Вы можете создать свой собственный читатель, который делегирует предоставленному читателю, например:

final Reader reader = <whatever you are getting>;

Reader wrappedReader = new Reader()
{
    Reader readerCopy = reader;
    String start = "<?xml version=\"1.0\"?><MyRootElement>";
    String end = "</MyRootElement>";
    int index;

    @Override
    public void close() throws IOException
    {
        readerCopy.close();
    }

    @Override
    public int read(char[] cbuf, int off, int len) throws IOException
    {
        // You'll have to get the logic right here - this is only placeholder code

        if (index < start.length())
        {
            // Copy from start to cbuf
        }
        int result = readerCopy.read(cbuf, off, len);

        if (result == -1) {
            // Copy from end
        }

        index += len; 

        return result;
    }
};
Вам придется заполнить логику, чтобы сначала прочитать из start, затем делегировать читателю в середине, и, наконец, когда читатель пуст, прочитать из end.

Однако этот подход будет работать.

Ответ 3 работает, но для меня я должен был сделать дополнительный шаг создания inputsource из SequenceInputStream.

XMLReader xmlReader = saxParser.getXMLReader();
xmlReader.setContentHandler((ContentHandler) this);
// Trying to add root element
Enumeration<InputStream> streams = Collections.enumeration(
    Arrays.asList(new InputStream[] {
        new ByteArrayInputStream("<TopNode>".getBytes()),
        new FileInputStream(xmlFile),//bogus xml
        new ByteArrayInputStream("</TopNode>".getBytes()),
}));
InputSource is = new InputSource(seqStream);
xmlReader.parse(is);