Чтение одного XML-документа из потока с помощью dom4j

StackOverflow https://stackoverflow.com/questions/226105

  •  03-07-2019
  •  | 
  •  

Вопрос

Я пытаюсь прочитать один XML-документ из потока за раз, используя dom4j, обработать его, затем перейти к следующему документу в потоке.К сожалению, SAXReader от dom4j (использующий JAXP under the covers) продолжает чтение и блокирует следующий элемент документа.

Есть ли способ заставить SAXReader прекратить чтение потока, как только он найдет конец элемента document?Есть ли лучший способ добиться этого?

Это было полезно?

Решение

Я смог заставить это работать с некоторыми упражнениями по гимнастике, используя некоторые внутренние классы JAXP:

  • Создайте пользовательский сканер, подкласс XMLNSDocumentScannerImpl
    • Создайте пользовательский драйвер, реализацию XMLNSDocumentScannerImpl.Driver, внутри пользовательского сканера, который возвращает END_DOCUMENT, когда он видит объявление или элемент.Получите ScannedEntity из fElementScanner.getCurrentEntity().Если у объекта есть PushbackReader, верните оставшиеся непрочитанные символы из буфера объекта в устройство чтения.
    • В конструкторе заменяет fTrailingMiscDriver экземпляром этого пользовательского драйвера.
  • Создайте класс пользовательской конфигурации, подкласс XIncludeAwareParserConfiguration, который заменяет стандартный DOCUMENT_SCANNER экземпляром этого пользовательского сканера в его конструкторе.
  • Установите экземпляр этого пользовательского класса конфигурации как свойство "com.sun.org.apache.xerces.internal.xni.parser.XMLParserConfiguration", чтобы он был создан, когда класс SAXReader dom4j попытается создать XmlReader JAXP.
  • При передаче средства чтения методу SAXReader.read() dom4j предоставьте PushbackReader размер буфера, значительно превышающий односимвольный размер по умолчанию.По крайней мере, 8192 должно быть достаточно для поддержки размера буфера по умолчанию XMLEntityManager внутри JAXP-копии Apache2.

Это не самое чистое решение, поскольку оно включает в себя создание подклассов внутренних классов JAXP, но оно действительно работает.

Другие советы

Скорее всего, вы не хотите иметь более одного документа в одном потоке одновременно. Я не думаю, что SAXReader достаточно умен, чтобы остановиться, когда он доберется до конца первого документа. Почему необходимо иметь несколько документов в одном потоке, как это?

Я думаю, вам нужно было бы добавить адаптер, что-то, чтобы обернуть поток, и чтобы эта вещь возвращала конец файла, когда он видит начало следующего документа. Насколько я знаю, синтаксические анализаторы, как написано, будут работать до конца файла или до ошибки ... и обнаружение другого <?xml version="1.0"?> наверняка будет ошибкой.

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

// Any value that is invalid for an XML character will do.
static final char DOC_TERMINATOR=4;

BOOL addDocumentToStream(BufferedWriter streamOut, char xmlData[])
{
  streamOut.write(xmlData);
  streamOut.write(DOC_TERMINATOR);
}

Затем при чтении из потока считывайте в массив, пока не встретите DOC_TERMINATOR.

char *getNextDocuument(BufferedReader streamIn)
{
  StringBuffer buffer = new StringBuffer();
  int character;

  while (true)
  {
    character = streamIn.read();
    if (character == DOC_TERMINATOR)
      break;

    buffer.append(character);
  }
  return buffer.toString().toCharArray();
}

Поскольку 4 - недопустимое символьное значение, вы не встретите его, кроме случаев, когда вы явно добавили его. Таким образом, вы можете разделить документы. Теперь просто оберните соответствующий массив символов для ввода в SAX и все готово.

...
  XMLReader xmlReader = XMLReaderFactory.createXMLReader();
...
  while (true)
  {
    char xmlDoc = getNextDocument(streamIn);

    if (xmlDoc.length == 0)
      break;

    InputSource saxInputSource = new InputSource(new CharArrayReader(xmlDoc));
    xmlReader.parse(saxInputSource);
  }
...

Обратите внимание, что цикл завершается, когда он получает документ длиной 0. Это означает, что вы должны либо добавить второй DOC_TERMINATOR после того, как последнему документу вам необходимо добавить что-то, чтобы обнаружить конец потока в getNextDocument ().

Я делал это раньше, обертывая базовый ридер другим ридером моего собственного создания, который обладал очень простой возможностью синтаксического анализа.Предполагая, что вы знаете закрывающий тег для документа, оболочка просто анализирует его на соответствие, напримердля "</MyDocument>".Когда он обнаруживает, что возвращает EOF.Оболочку можно сделать адаптивной, проанализировав первый открывающий тег и вернув EOF для соответствующего закрывающего тега.Я обнаружил, что на самом деле не было необходимости определять уровень для закрывающего тега, поскольку ни в одном документе я не использовал тег document внутри себя, поэтому было гарантировано, что первое появление закрывающего тега завершит документ.

Насколько я помню, один из трюков состоял в том, чтобы закрыть блок-оболочку(), поскольку считыватель DOM закрывает источник ввода.

Итак, учитывая входные данные читателя, ваш код мог бы выглядеть как:

SubdocReader sdr=new SubdocReader(input);
while(!sdr.eof()) {
    sdr.next();
    // read doc here using DOM
    // then process document
    }
input.close();

Метод eof() возвращает значение true, если встречается значение EOF.Метод next() помечает считыватель, чтобы он перестал возвращать значение -1 для read().

Надеюсь, это укажет вам полезное направление.

-- Киви.

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

Единственная реальная разница между обработкой потока с одним xml и потоком с несколькими xmls - это логика буфера и разделения.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top