stax - получить узел XML в виде строки

XML выглядит так:

<statements>
   <statement account="123">
      ...stuff...
   </statement>
   <statement account="456">
      ...stuff...
   </statement>
</statements>

Я использую Stax для обработки одного "<statement>"за один раз, и у меня это получилось. Мне нужно получить весь узел оператора в виде строки, чтобы я мог создавать"123.xml"и"456.xml"или даже загружать их в таблицу базы данных, проиндексированную по учетной записи.

используя этот подход: http://www.devx.com/Java/Article/30298/1954

Я хочу сделать что-то вроде этого:

String statementXml = staxXmlReader.getNodeByName("statement");

//load statementXml into database

6 ответов

Решение

Почему бы просто не использовать xpath для этого?

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

Вот так:

//statement

РЕДАКТИРОВАТЬ #1: Если возможно, взгляните на dom4j. Вы можете прочитать String и получить все узлы операторов достаточно просто.

РЕДАКТИРОВАТЬ #2: Используя dom4j, вот как вы это сделаете: (из их кулинарной книги)

String text = "your xml here";
Document document = DocumentHelper.parseText(text);

public void bar(Document document) {
   List list = document.selectNodes( "//statement" );
   // loop through node data
}

У меня была похожая задача, и хотя первоначальный вопрос был старше года, я не смог найти удовлетворительного ответа. Самым интересным ответом до сих пор был ответ Блейза Дафана, но я не смог запустить его на ожидаемом XML (возможно, некоторые параметры для базового синтаксического анализатора могли бы изменить это?). Вот XML, очень просто:

<many-many-tags>
    <description>
        ...
        <p>Lorem ipsum...</p>
        Devils inside...
        ...
    </description>
</many-many-tags>

Мое решение:

public static String readElementBody(XMLEventReader eventReader)
    throws XMLStreamException {
    StringWriter buf = new StringWriter(1024);

    int depth = 0;
    while (eventReader.hasNext()) {
        // peek event
        XMLEvent xmlEvent = eventReader.peek();

        if (xmlEvent.isStartElement()) {
            ++depth;
        }
        else if (xmlEvent.isEndElement()) {
            --depth;

            // reached END_ELEMENT tag?
            // break loop, leave event in stream
            if (depth < 0)
                break;
        }

        // consume event
        xmlEvent = eventReader.nextEvent();

        // print out event
        xmlEvent.writeAsEncodedUnicode(buf);
    }

    return buf.getBuffer().toString();
}

Пример использования:

XMLEventReader eventReader = ...;
while (eventReader.hasNext()) {
    XMLEvent xmlEvent = eventReader.nextEvent();
    if (xmlEvent.isStartElement()) {
        StartElement elem = xmlEvent.asStartElement();
        String name = elem.getName().getLocalPart();

        if ("DESCRIPTION".equals(name)) {
            String xmlFragment = readElementBody(eventReader);
            // do something with it...
            System.out.println("'" + fragment + "'");
        }
    }
    else if (xmlEvent.isEndElement()) {
        // ...
    }
}

Обратите внимание, что извлеченный фрагмент XML будет содержать полное извлеченное содержимое тела, включая пробелы и комментарии. Фильтрация по требованию или настройка параметризации размера буфера были исключены для краткости кода:

'
    <description>
        ...
        <p>Lorem ipsum...</p>
        Devils inside...
        ...
    </description>
    '

Вы можете использовать StAX для этого. Вам просто нужно продвинуть XMLStreamReader к элементу start для оператора. Проверьте атрибут учетной записи, чтобы получить имя файла. Затем используйте API-интерфейсы javax.xml.transform для преобразования StAXSource в StreamResult, обертывающий файл. Это улучшит XMLStreamReader, а затем просто повторите этот процесс.

import java.io.File;
import java.io.FileReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamReader;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stax.StAXSource;
import javax.xml.transform.stream.StreamResult;

public class Demo {

    public static void main(String[] args) throws Exception  {
        XMLInputFactory xif = XMLInputFactory.newInstance();
        XMLStreamReader xsr = xif.createXMLStreamReader(new FileReader("input.xml"));
        xsr.nextTag(); // Advance to statements element

        while(xsr.nextTag() == XMLStreamConstants.START_ELEMENT) {
            TransformerFactory tf = TransformerFactory.newInstance();
            Transformer t = tf.newTransformer();
            File file = new File("out" + xsr.getAttributeValue(null, "account") + ".xml");
            t.transform(new StAXSource(xsr), new StreamResult(file));
        }
    }

}

Stax - это низкоуровневый API доступа, и у него нет ни поиска, ни методов, которые рекурсивно обращаются к контенту. Но что вы на самом деле пытаетесь сделать? И почему вы рассматриваете Stax?

Помимо использования древовидной модели (DOM, XOM, JDOM, Dom4j), которая будет хорошо работать с XPath, лучшим выбором при работе с данными обычно является библиотека привязки данных, такая как JAXB. С его помощью вы можете передать Stax или SAX Reader и попросить его связать XML-данные с Java-бинами, а не связываться с Java-объектами процесса XML. Это часто более удобно, и это обычно довольно производительность. Единственная хитрость с большими файлами заключается в том, что вы не хотите связывать все целиком сразу, а связываете каждое поддерево (в вашем случае, одно "утверждение" за раз). Это проще всего сделать, выполнив итерацию Stax XmlStreamReader с последующим использованием JAXB для привязки.

Я гуглил, и это кажется болезненно трудным.

учитывая мой xml, я думаю, что это может быть проще:

StringBuilder buffer = new StringBuilder();
for each line in file {
   buffer.append(line)
   if(line.equals(STMT_END_TAG)){
      parse(buffer.toString())
      buffer.delete(0,buffer.length)
   }
 }

 private void parse(String statement){
    //saxParser.parse( new InputSource( new StringReader( xmlText ) );
    // do stuff
    // save string
 }

У меня была аналогичная проблема, и я нашел решение. Я использовал решение, предложенное @t0r0X, но оно не работает в текущей реализации на Java 11, метод создает недопустимое строковое представление начального элемента (в class) в результирующем XML-фрагменте, поэтому мне пришлось его изменить, но тогда он, похоже, работал хорошо, в чем я мог сразу убедиться, разобрав фрагмент с помощью DOM и JaxBMarshaller на конкретные контейнеры данных.

В моем случае у меня была огромная структура

      <Orders>
   <ns2:SyncOrder xmlns:ns2="..." xmlns:ns3="....." ....>
      .....
   </ns2:SyncOrder>
   <ns2:SyncOrder xmlns:ns2="..." xmlns:ns3="....." ....>
      .....
   </ns2:SyncOrder>
   ...
</Orders>

в файле размером в несколько сотен мегабайт (много повторяющихся структур «SyncOrder»), поэтому использование DOM приведет к большому потреблению памяти и медленной оценке. Поэтому я использовал StAX для разделения огромного XML на более мелкие фрагменты XML, которые я проанализировал с помощью DOM и использовал JaxbElements, сгенерированные из определения элемента xsd. (Эта инфраструктура у меня была от вебсервиса, который использует ту же структуру, но это не важно).

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

      private static <T> List<T> unmarshallMultipleSyncOrderXmlData(
        InputStream aOrdersXmlContainingSyncOrderItems,
        Function<SyncOrderType, T> aConversionFunction) throws XMLStreamException, ParserConfigurationException, IOException, SAXException {

    DocumentBuilderFactory locDocumentBuilderFactory = DocumentBuilderFactory.newInstance();
    locDocumentBuilderFactory.setNamespaceAware(true);
    DocumentBuilder locDocBuilder = locDocumentBuilderFactory.newDocumentBuilder();

    List<T> locResult = new ArrayList<>();
    XMLInputFactory locFactory = XMLInputFactory.newFactory();
    XMLEventReader locReader = locFactory.createXMLEventReader(aOrdersXmlContainingSyncOrderItems);

    boolean locIsInSyncOrder = false;
    QName locSyncOrderElementQName = null;
    StringWriter locXmlTextBuffer = new StringWriter();
    int locDepth = 0;
    while (locReader.hasNext()) {

        XMLEvent locEvent = locReader.nextEvent();

        if (locEvent.isStartElement()) {
            if (locDepth == 0 && Objects.equals(locEvent.asStartElement().getName().getLocalPart(), "Orders")) {
                locDepth++;
            } else {
                if (locDepth <= 0)
                    throw new IllegalStateException("There has been passed invalid XML stream intot he function. "
                                                                                    + "Expecting the element 'Orders' as the root alament of the document, but found was '"
                                                                                    + locEvent.asStartElement().getName().getLocalPart() + "'.");
                locDepth++;
                if (locSyncOrderElementQName == null) {
                    /* First element after the "Orders" has passed, so we retrieve
                     * the name of the element with the namespace prefix: */
                    locSyncOrderElementQName = locEvent.asStartElement().getName();
                }
                if(Objects.equals(locEvent.asStartElement().getName(), locSyncOrderElementQName)) {
                    locIsInSyncOrder = true;
                }
            }
        } else if (locEvent.isEndElement()) {
            locDepth--;
            if(locDepth == 1 && Objects.equals(locEvent.asEndElement().getName(), locSyncOrderElementQName)) {
                locEvent.writeAsEncodedUnicode(locXmlTextBuffer);
                /* at this moment the call of locXmlTextBuffer.toString() gets the complete fragment 
                 * of XML containing the valid SyncOrder element, but I have continued to other processing,
                 * which immediatelly validates the produced XML fragment is valid and passes the values 
                 * to communication object: */
                Document locDocument = locDocBuilder.parse(new ByteArrayInputStream(locXmlTextBuffer.toString().getBytes()));
                SyncOrderType locItem = unmarshallSyncOrderDomNodeToCo(locDocument);
                locResult.add(aConversionFunction.apply(locItem));
                locXmlTextBuffer = new StringWriter();
                locIsInSyncOrder = false;
            }
        }
        if (locIsInSyncOrder) {
            if (locEvent.isStartElement()) {
                /* here replaced the standard implementation of startElement's method writeAsEncodedUnicode: */ 
                locXmlTextBuffer.write(startElementToStrng(locEvent.asStartElement()));
            } else {
                locEvent.writeAsEncodedUnicode(locXmlTextBuffer);
            }
        }
    }
    return locResult;
}

private static String startElementToString(StartElement aStartElement) {

    StringBuilder locStartElementBuffer = new StringBuilder();

    // open element
    locStartElementBuffer.append("<");
    String locNameAsString = null;
    if ("".equals(aStartElement.getName().getNamespaceURI())) {
        locNameAsString = aStartElement.getName().getLocalPart();
    } else if (aStartElement.getName().getPrefix() != null
            && !"".equals(aStartElement.getName().getPrefix())) {
        locNameAsString = aStartElement.getName().getPrefix()
                + ":" + aStartElement.getName().getLocalPart();
    } else {
        locNameAsString = aStartElement.getName().getLocalPart();
    }

    locStartElementBuffer.append(locNameAsString);

    // add any attributes
    Iterator<Attribute> locAttributeIterator = aStartElement.getAttributes();
    Attribute attr;
    while (locAttributeIterator.hasNext()) {
        attr = locAttributeIterator.next();
        locStartElementBuffer.append(" ");
        locStartElementBuffer.append(attributeToString(attr));
    }

    // add any namespaces
    Iterator<Namespace> locNamespaceIterator = aStartElement.getNamespaces();
    Namespace locNamespace;
    while (locNamespaceIterator.hasNext()) {
        locNamespace = locNamespaceIterator.next();
        locStartElementBuffer.append(" ");
        locStartElementBuffer.append(attributeToString(locNamespace));
    }

    // close start tag
    locStartElementBuffer.append(">");

    // return StartElement as a String
    return locStartElementBuffer.toString();
}

private static String attributeToString(Attribute aAttr) {
    if( aAttr.getName().getPrefix() != null && aAttr.getName().getPrefix().length() > 0 )
        return aAttr.getName().getPrefix() + ":" + aAttr.getName().getLocalPart() + "='" + aAttr.getValue() + "'";
    else
        return aAttr.getName().getLocalPart() + "='" + aAttr.getValue() + "'";
}

public static SyncOrderType unmarshallSyncOrderDomNodeToCo(
        Node aSyncOrderItemNode) {
    Source locSource = new DOMSource(aSyncOrderItemNode);
    Object locUnmarshalledObject = getMarshallerAndUnmarshaller().unmarshal(locSource);
    SyncOrderType locCo = ((JAXBElement<SyncOrderType>) locUnmarshalledObject).getValue();
    return locCo;
}
Другие вопросы по тегам