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

Разбор XML-потока без корневого элемента

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

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

org.xml.sax.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.*)?

4b9b3361

Ответ 1

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()
    );

Ответ 2

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

Edit:

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

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

Ответ 3

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

Ответ 4

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

Ответ 5

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

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.

Этот подход будет работать.

Ответ 6

Ответ 3 работает, но для меня мне пришлось сделать дополнительный шаг создания источника данных из 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);