【问题标题】:Reading multiple xml documents from a socket in java从java中的套接字读取多个xml文档
【发布时间】:2009-05-28 13:42:53
【问题描述】:

我正在编写一个客户端,它需要通过一个套接字读取多个连续的小型 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。这样做的正确方法是什么?

澄清:不幸的是,协议由服务器固定,无法更改,因此添加长度或包装内容将不起作用。

【问题讨论】:

  • 你不能只捕获 XMLStreamException 并将其用作触发器来再次解析下一个文档的输入流吗?

标签: java xml


【解决方案1】:
  • 每个文档的长度前缀(以字节为单位)。
  • 从套接字读取第一个文档的长度
  • 从套接字读取这么多数据,将其转储到ByteArrayOutputStream
  • 根据结果创建ByteArrayInputStream
  • 解析 ByteArrayInputStream 以获取第一个文档
  • 重复第二个文档等

【讨论】:

    【解决方案2】:

    IIRC,XML 文档末尾可以有 cmets 和处理指令,因此没有真正的方法可以准确地告诉您何时到达文件末尾。

    已经提到了几种处理这种情况的方法。另一种选择是将非法字符或字节放入流中,例如 NUL 或零。这样做的好处是您无需更改文档,也无需缓冲整个文件。

    【讨论】:

      【解决方案3】:

      只要换成任何流

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

      【讨论】:

        【解决方案4】:

        一个简单的解决方案是将发送端的文档包装在一个新的根元素中:

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

        不过,您必须确保不包含 XML 标头 (&lt;?xml ...?&gt;)。如果所有文档都使用相同的编码,这可以通过一个简单的过滤器来完成,如果每个文档的第一行以 &lt;?xml 开头,它就会忽略它。

        【讨论】:

        • 这个问题可能是他需要能够分别解析每个文档。一种解决方法是使用用于生成 DOM 的 SAX 解析器并让它包装一个生成 DOM 的 SAX 解析器,然后它实际上可以从更大的文档中生成更小的文档,这实际上永远不会结束。
        【解决方案5】:

        找到了这个forum message(您可能已经看到了),它通过包装输入流并测试两个 ascii 字符之一来提供解决方案(参见帖子)。

        您可以尝试对此进行调整,首先转换为使用阅读器(用于正确的字符编码),然后进行元素计数,直到到达结束元素,此时触发 EOM。

        【讨论】:

          【解决方案6】:

          嗨 我在工作中也遇到了这个问题(所以不会发布代码)。我能想到的最优雅的解决方案,并且在 imo 中效果很好,如下

          创建一个类,例如 DocumentSplittingInputStream,它扩展 InputStream 并在其构造函数中获取底层输入流(或在构造后设置......)。 添加一个带有字节数组 closeTag 的字段,其中包含您要查找的关闭根节点的字节。 添加一个名为 matchCount 的字段 int 或其他东西,初始化为零。 添加一个名为underlyingInputStreamNotFinished的布尔字段,初始化为true

          关于 read() 实现:

          1. 检查 matchCount == closeTag.length,如果是,将 matchCount 设置为 -1,返回 -1
          2. 如果 matchCount == -1,则设置 matchCount = 0,在底层输入流上调用 read(),直到获得 -1 或 '
          3. 否则从底层输入流中读取一个 int(如果它等于 closeTag[matchCount] 则增加 matchCount,如果不等于则将 matchCount 重置为零)并返回新读取的字节

          添加一个返回底层流是否关闭的布尔值的方法。 对底层输入流的所有读取都应通过一个单独的方法检查读取的值是否为 -1,如果是,则将字段“underlyingInputStreamNotFinished”设置为 false。

          我可能遗漏了一些小问题,但我相信你明白了。

          然后在使用代码中,如果您使用的是 xstream,则执行以下操作:

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

          大卫

          【讨论】:

            【解决方案7】:

            我不得不做这样的事情,在我研究如何处理它的过程中,我发现这个线程即使它已经很老了,我只是回答(对自己)here 将所有内容包装在它自己的阅读器中以便更简单使用

            【讨论】:

              【解决方案8】:

              我遇到了类似的问题。我正在使用的 Web 服务将(在某些情况下)返回多个 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+"]");
                  }
              }
              

              【讨论】:

              • 这仅适用于根标签唯一且中间没有空格的 XML。
              • 没错,这就是我试图解决的问题。在 xml 文档之间使用“空白/垃圾”数据会很简单,尤其是如果每个文档都以 标头开头。如果根元素不是唯一的,那么它会更复杂但仍然可行。
              【解决方案9】:

              我使用 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}
              

              【讨论】:

              • 顺便说一句,最好使用 java.io.SequenceInputStream 而不是 MultiInputStream
              猜你喜欢
              • 2012-05-16
              • 2013-10-12
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2016-02-11
              • 2019-03-09
              • 2016-02-20
              • 1970-01-01
              相关资源
              最近更新 更多