Разбор потока XML без корневого элемента - PullRequest
16 голосов
/ 10 июля 2011

Мне нужно проанализировать непрерывный поток правильно сформированных элементов 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.*)?

Ответы [ 6 ]

13 голосов
/ 24 марта 2012

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()
    );
9 голосов
/ 10 июля 2011

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

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

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

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

5 голосов
/ 10 июля 2011

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

3 голосов
/ 10 июля 2011

Вы можете создать свой собственный 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.

Хотя этот подход будет работать.

3 голосов
/ 10 июля 2011

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

2 голосов
/ 10 апреля 2013

Ответ 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);
...