Чтение нескольких документов XML из сокета в Java - PullRequest
3 голосов
/ 28 мая 2009

Я пишу клиент, который должен читать несколько последовательных небольших XML-документов через сокет. Я могу предположить, что кодировка всегда UTF-8 и что между документами есть разделитель. Документы должны в конечном итоге войти в объекты DOM. Каков наилучший способ сделать это?

Суть проблемы в том, что синтаксические анализаторы ожидают один документ в потоке и учитывают оставшуюся часть содержимого. Я думал, что смогу искусственно завершить документ, отслеживая глубину элемента и создавая новый считыватель, используя существующий поток ввода. Например. что-то вроде:

// Broken 
public void parseInputStream(InputStream inputStream) throws Exception
{
    XMLInputFactory factory = XMLInputFactory.newInstance();
    XMLOutputFactory xof = XMLOutputFactory.newInstance();
    XMLEventFactory eventFactory = XMLEventFactory.newInstance();        
    DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
    DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
    Document doc = documentBuilder.newDocument();
    XMLEventWriter domWriter = xof.createXMLEventWriter(new DOMResult(doc));
    XMLStreamReader xmlStreamReader = factory.createXMLStreamReader(inputStream);
    XMLEventReader reader = factory.createXMLEventReader(xmlStreamReader);
    int depth = 0;

    while (reader.hasNext()) {
        XMLEvent evt = reader.nextEvent();
        domWriter.add(evt);

        switch (evt.getEventType()) {
        case XMLEvent.START_ELEMENT:
            depth++;
            break;

        case XMLEvent.END_ELEMENT:
            depth--;

            if (depth == 0) 
            {                       
                domWriter.add(eventFactory.createEndDocument());
                System.out.println(doc);
                reader.close();
                xmlStreamReader.close();

                xmlStreamReader = factory.createXMLStreamReader(inputStream);
                reader = factory.createXMLEventReader(xmlStreamReader);

                doc = documentBuilder.newDocument();
                domWriter = xof.createXMLEventWriter(new DOMResult(doc));    
                domWriter.add(eventFactory.createStartDocument());
            }
            break;                    
        }
    }
}

Однако выполнение этого при вводе, таком как , печатает первый документ и выдает исключение XMLStreamException. Какой правильный способ сделать это?

Разъяснение: К сожалению, протокол фиксируется сервером и не может быть изменен, поэтому предварительная длина или перенос содержимого не будут работать.

Ответы [ 9 ]

3 голосов
/ 28 мая 2009
  • Длина префикса каждого документа (в байтах).
  • Считать длину первого документа из сокета
  • Считайте столько данных из сокета, сбросив их в ByteArrayOutputStream
  • Создать ByteArrayInputStream из результатов
  • Разобрать это ByteArrayInputStream, чтобы получить первый документ
  • Повторите для второго документа и т. Д.
1 голос
/ 06 августа 2009

просто изменить на любой поток

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;

import javax.xml.namespace.QName;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamReader;

public class LogParser {

    private XMLInputFactory inputFactory = null;
    private XMLStreamReader xmlReader = null;
    InputStream is;
    private int depth;
    private QName rootElement;

    private static class XMLStream extends InputStream
    {
        InputStream delegate;
        StringReader startroot = new StringReader("<root>");
        StringReader endroot = new StringReader("</root>");

        XMLStream(InputStream delegate)
        {
            this.delegate = delegate;
        }

        public int read() throws IOException {
            int c = startroot.read();
            if(c==-1)
            {
                c = delegate.read();
            }
            if(c==-1)
            {
                c = endroot.read();
            }
            return c;
        }

    }

    public LogParser() {
        inputFactory = XMLInputFactory.newInstance();
    }

    public void read() throws Exception {
        is = new XMLStream(new FileInputStream(new File(
            "./myfile.log")));
        xmlReader = inputFactory.createXMLStreamReader(is);

        while (xmlReader.hasNext()) {
            printEvent(xmlReader);
            xmlReader.next();
        }
        xmlReader.close();

    }

    public void printEvent(XMLStreamReader xmlr) throws Exception {
        switch (xmlr.getEventType()) {
        case XMLStreamConstants.END_DOCUMENT:
            System.out.println("finished");
            break;
        case XMLStreamConstants.START_ELEMENT:
            System.out.print("<");
            printName(xmlr);
            printNamespaces(xmlr);
            printAttributes(xmlr);
            System.out.print(">");
            if(rootElement==null && depth==1)
            {
                rootElement = xmlr.getName();
            }
            depth++;
            break;
        case XMLStreamConstants.END_ELEMENT:
            System.out.print("</");
            printName(xmlr);
            System.out.print(">");
            depth--;
            if(depth==1 && rootElement.equals(xmlr.getName()))
            {
                rootElement=null;
                System.out.println("finished element");
            }
            break;
        case XMLStreamConstants.SPACE:
        case XMLStreamConstants.CHARACTERS:
            int start = xmlr.getTextStart();
            int length = xmlr.getTextLength();
            System.out
                    .print(new String(xmlr.getTextCharacters(), start, length));
            break;

        case XMLStreamConstants.PROCESSING_INSTRUCTION:
            System.out.print("<?");
            if (xmlr.hasText())
                System.out.print(xmlr.getText());
            System.out.print("?>");
            break;

        case XMLStreamConstants.CDATA:
            System.out.print("<![CDATA[");
            start = xmlr.getTextStart();
            length = xmlr.getTextLength();
            System.out
                    .print(new String(xmlr.getTextCharacters(), start, length));
            System.out.print("]]>");
            break;

        case XMLStreamConstants.COMMENT:
            System.out.print("<!--");
            if (xmlr.hasText())
                System.out.print(xmlr.getText());
            System.out.print("-->");
            break;

        case XMLStreamConstants.ENTITY_REFERENCE:
            System.out.print(xmlr.getLocalName() + "=");
            if (xmlr.hasText())
                System.out.print("[" + xmlr.getText() + "]");
            break;

        case XMLStreamConstants.START_DOCUMENT:
            System.out.print("<?xml");
            System.out.print(" version='" + xmlr.getVersion() + "'");
            System.out.print(" encoding='" + xmlr.getCharacterEncodingScheme()
                    + "'");
            if (xmlr.isStandalone())
                System.out.print(" standalone='yes'");
            else
                System.out.print(" standalone='no'");
            System.out.print("?>");
            break;

        }
    }

    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        try {
            new LogParser().read();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    private static void printName(XMLStreamReader xmlr) {
        if (xmlr.hasName()) {
            System.out.print(getName(xmlr));
        }
    }

    private static String getName(XMLStreamReader xmlr) {
        if (xmlr.hasName()) {
            String prefix = xmlr.getPrefix();
            String uri = xmlr.getNamespaceURI();
            String localName = xmlr.getLocalName();
            return getName(prefix, uri, localName);
        }
        return null;
    }

    private static String getName(String prefix, String uri, String localName) {
        String name = "";
        if (uri != null && !("".equals(uri)))
            name += "['" + uri + "']:";
        if (prefix != null)
            name += prefix + ":";
        if (localName != null)
            name += localName;
        return name;
    }   

    private static void printAttributes(XMLStreamReader xmlr) {
        for (int i = 0; i < xmlr.getAttributeCount(); i++) {
            printAttribute(xmlr, i);
        }
    }

    private static void printAttribute(XMLStreamReader xmlr, int index) {
        String prefix = xmlr.getAttributePrefix(index);
        String namespace = xmlr.getAttributeNamespace(index);
        String localName = xmlr.getAttributeLocalName(index);
        String value = xmlr.getAttributeValue(index);
        System.out.print(" ");
        System.out.print(getName(prefix, namespace, localName));
        System.out.print("='" + value + "'");
    }

    private static void printNamespaces(XMLStreamReader xmlr) {
        for (int i = 0; i < xmlr.getNamespaceCount(); i++) {
            printNamespace(xmlr, i);
        }
    }

    private static void printNamespace(XMLStreamReader xmlr, int index) {
        String prefix = xmlr.getNamespacePrefix(index);
        String uri = xmlr.getNamespaceURI(index);
        System.out.print(" ");
        if (prefix == null)
            System.out.print("xmlns='" + uri + "'");
        else
            System.out.print("xmlns:" + prefix + "='" + uri + "'");
    }

}
1 голос
/ 28 мая 2009

IIRC, XML-документы могут содержать комментарии и инструкции по обработке в конце, поэтому нет реального способа точно сказать, когда вы добрались до конца файла.

Несколько способов справиться с ситуацией уже были упомянуты. Другая альтернатива - ввести в поток недопустимый символ или байт, например, NUL или ноль. Преимущество этого заключается в том, что вам не нужно изменять документы и вам никогда не нужно буферизовать весь файл.

0 голосов
/ 31 августа 2012

Я использую подход JAXB для демаршаллинга сообщений из многопоточного потока:

MultiInputStream.java

public class MultiInputStream extends InputStream {
    private final Reader source;
    private final StringReader startRoot = new StringReader("<root>");
    private final StringReader endRoot = new StringReader("</root>");

    public MultiInputStream(Reader source) {
        this.source = source;
    }

    @Override
    public int read() throws IOException {
        int count = startRoot.read();
        if (count == -1) {
            count = source.read();
        }
        if (count == -1) {
            count = endRoot.read();
        }
        return count;
    }
}

MultiEventReader.java

public class MultiEventReader implements XMLEventReader {

    private final XMLEventReader reader;
    private boolean isXMLEvent = false;
    private int level = 0;

    public MultiEventReader(XMLEventReader reader) throws XMLStreamException {
        this.reader = reader;
        startXML();
    }

    private void startXML() throws XMLStreamException {
        while (reader.hasNext()) {
            XMLEvent event = reader.nextEvent();
            if (event.isStartElement()) {
                return;
            }
        }
    }

    public boolean hasNextXML() {
        return reader.hasNext();
    }

    public void nextXML() throws XMLStreamException {
        while (reader.hasNext()) {
            XMLEvent event = reader.peek();
            if (event.isStartElement()) {
                isXMLEvent = true;
                return;
            }
            reader.nextEvent();
        }
    }

    @Override
    public XMLEvent nextEvent() throws XMLStreamException {
        XMLEvent event = reader.nextEvent();
        if (event.isStartElement()) {
            level++;
        }
        if (event.isEndElement()) {
            level--;
            if (level == 0) {
                isXMLEvent = false;
            }
        }
        return event;
    }

    @Override
    public boolean hasNext() {
        return isXMLEvent;
    }

    @Override
    public XMLEvent peek() throws XMLStreamException {
        XMLEvent event = reader.peek();
        if (level == 0) {
            while (event != null && !event.isStartElement() && reader.hasNext()) {
                reader.nextEvent();
                event = reader.peek();
            }
        }
        return event;
    }

    @Override
    public String getElementText() throws XMLStreamException {
        throw new NotImplementedException();
    }

    @Override
    public XMLEvent nextTag() throws XMLStreamException {
        throw new NotImplementedException();
    }

    @Override
    public Object getProperty(String name) throws IllegalArgumentException {
        throw new NotImplementedException();
    }

    @Override
    public void close() throws XMLStreamException {
        throw new NotImplementedException();
    }

    @Override
    public Object next() {
        throw new NotImplementedException();
    }

    @Override
    public void remove() {
        throw new NotImplementedException();
    }
}

Message.java

@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "Message")
public class Message {

    public Message() {
    }

    @XmlAttribute(name = "ID", required = true)
    protected long id;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "Message{id=" + id + '}';
    }
}

Чтение кратных сообщений:

public static void main(String[] args) throws Exception{

    StringReader stringReader = new StringReader(
            "<Message ID=\"123\" />\n" +
            "<Message ID=\"321\" />"
    );

    JAXBContext context = JAXBContext.newInstance(Message.class);
    Unmarshaller unmarshaller = context.createUnmarshaller();

    XMLInputFactory inputFactory = XMLInputFactory.newFactory();
    MultiInputStream multiInputStream = new MultiInputStream(stringReader);
    XMLEventReader xmlEventReader = inputFactory.createXMLEventReader(multiInputStream);
    MultiEventReader multiEventReader = new MultiEventReader(xmlEventReader);

    while (multiEventReader.hasNextXML()) {
        Object message = unmarshaller.unmarshal(multiEventReader);
        System.out.println(message);
        multiEventReader.nextXML();
    }
}

Результаты:

Message{id=123}
Message{id=321}
0 голосов
/ 24 июля 2012

Я столкнулся с подобной проблемой. Веб-сервис, который я использую, в некоторых случаях возвращает несколько XML-документов в ответ на один запрос HTTP GET. Я мог бы прочитать весь ответ в строку и разделить его, но вместо этого я реализовал разделение входного потока на основе поста user467257 выше. Вот код:

public class AnotherSplittingInputStream extends InputStream {
    private final InputStream realStream;
    private final byte[] closeTag;

    private int matchCount;
    private boolean realStreamFinished;
    private boolean reachedCloseTag;

    public AnotherSplittingInputStream(InputStream realStream, String closeTag) {
        this.realStream = realStream;
        this.closeTag = closeTag.getBytes();
    }

    @Override
    public int read() throws IOException {
        if (reachedCloseTag) {
            return -1;
        }

        if (matchCount == closeTag.length) {
            matchCount = 0;
            reachedCloseTag = true;
            return -1;
        }

        int ch = realStream.read();
        if (ch == -1) {
            realStreamFinished = true;
        }
        else if (ch == closeTag[matchCount]) {
            matchCount++;
        } else {
            matchCount = 0;
        }
        return ch;
    }

    public boolean hasMoreData() {
        if (realStreamFinished == true) {
            return false;
        } else {
            reachedCloseTag = false;
            return true;
        }
    }
}

И использовать его:

String xml =
        "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
        "<root>first root</root>" +
        "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
        "<root>second root</root>";
ByteArrayInputStream is = new ByteArrayInputStream(xml.getBytes());
SplittingInputStream splitter = new SplittingInputStream(is, "</root>");
BufferedReader reader = new BufferedReader(new InputStreamReader(splitter));

while (splitter.hasMoreData()) {
    System.out.println("Starting next stream");
    String line = null;
    while ((line = reader.readLine()) != null) {
        System.out.println("line ["+line+"]");
    }
}
0 голосов
/ 27 июля 2011

Я должен был сделать что-то подобное, и во время моего исследования того, как к нему подойти, я обнаружил эту ветку, что, хотя она довольно старая, я просто ответил (себе) здесь , завернув все в свои собственный ридер для более простого использования

0 голосов
/ 06 октября 2010

Привет У меня также была эта проблема на работе (поэтому не буду публиковать полученный код). Самое элегантное решение, которое я мог придумать, и которое работает довольно хорошо imo, заключается в следующем:

Создайте класс, например DocumentSplittingInputStream, который расширяет InputStream и принимает базовый inputtream в своем конструкторе (или устанавливается после построения ...). Добавьте поле с байтовым массивом closeTag, содержащим байты искомого корневого узла, который вы ищете. Добавьте поле int с именем matchCount или что-то, инициализированное нулями. Добавьте поле логического значения с именем underInputStreamNotFinished, инициализированное в true

В реализации read ():

  1. Проверьте, соответствует ли matchCount == closeTag.length, если это так, установите matchCount равным -1, верните -1
  2. Если matchCount == -1, установите matchCount = 0, вызывайте read () для базового входного потока, пока не получите -1 или '<' (объявление xml следующего документа в потоке) и не вернете его. Обратите внимание, что, насколько мне известно, спецификация xml допускает комментарии после элемента документа, но я знала, что не собираюсь получать это из источника, поэтому не стала обрабатывать ее - если вы не уверены, что вам нужно будет изменить " сожрать немного. </li>
  3. В противном случае читать int из базового входного потока (если он равен closeTag [matchCount], затем увеличивать matchCount, если он не сбрасывает matchCount в ноль) и возвращать вновь прочитанный байт

Добавить метод, который возвращает логическое значение того, закрылся ли основной поток. Все чтения в базовом входном потоке должны проходить через отдельный метод, где он проверяет, является ли значение чтения -1, и, если это так, устанавливает поле "underInputStreamNotFinished" в false.

Возможно, я пропустил несколько незначительных моментов, но я уверен, что вы получите картину.

Затем в коде использования вы делаете что-то вроде, если вы используете xstream:

DocumentSplittingInputStream dsis = new DocumentSplittingInputStream(underlyingInputStream);
while (dsis.underlyingInputStreamNotFinished()) {
    MyObject mo = xstream.fromXML(dsis);
    mo.doSomething(); // or something.doSomething(mo);
}

David

0 голосов
/ 21 октября 2009

Нашел форумное сообщение (которое вы, вероятно, уже видели), решение которого заключается в переносе входного потока и проверке одного из двух символов ascii (см. Сообщение).

Вы можете попробовать адаптацию к этому, сначала преобразовав использование читателя (для правильной кодировки символов), а затем выполняя подсчет элементов, пока не достигнете закрывающего элемента, после чего вы запустите EOM.

0 голосов
/ 28 мая 2009

Простое решение - обернуть документы на отправляющей стороне в новый корневой элемент:

<?xml version="1.0"?>
<documents>
    ... document 1 ...
    ... document 2 ...
</documents>

Вы должны убедиться, что не включаете заголовок XML (<?xml ...?>). Если все документы используют одинаковую кодировку, это можно сделать с помощью простого фильтра, который просто игнорирует первую строку каждого документа, если он начинается с <?xml

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...