2017-01-23 3 views
2

Моя цель - прочитать объекты (featureMember) в DOM, изменить их и записать в новый XML. XML слишком велик, чтобы использовать DOM. Я понял, что мне нужно, это StAX и TransformerFactory, но я не могу заставить его работать.Как читать фрагменты XML с использованием StAX в Java?

Это то, что я сделал до сих пор:

private void change(File pathIn, File pathOut) { 
    try { 

     XMLInputFactory factory = XMLInputFactory.newInstance(); 
     XMLOutputFactory factoryOut = XMLOutputFactory.newInstance(); 

     TransformerFactory tf = TransformerFactory.newInstance(); 
     Transformer t = tf.newTransformer(); 

     XMLEventReader in = factory.createXMLEventReader(new FileReader(pathIn)); 
     XMLEventWriter out = factoryOut.createXMLEventWriter(new FileWriter(pathOut)); 

     while (in.hasNext()) { 
      XMLEvent e = in.nextTag(); 
      if (e.getEventType() == XMLStreamConstants.START_ELEMENT) { 
       if (((StartElement) e).getName().getLocalPart().equals("featureMember")) { 
        DOMResult result = new DOMResult(); 
        t.transform(new StAXSource(in), result); 
        Node domNode = result.getNode(); 
        System.out.println(domnode); 
       } 
      } 
      out.add(e); 
     } 
     in.close(); 
     out.close(); 

    } catch (FileNotFoundException e1) { 
     e1.printStackTrace(); 
    } catch (IOException e1) { 
     e1.printStackTrace(); 
    } catch (TransformerConfigurationException e1) { 
     e1.printStackTrace(); 
    } catch (XMLStreamException e1) { 
     e1.printStackTrace(); 
    } catch (TransformerException e1) { 
     e1.printStackTrace(); 
    } 
} 

я получаю исключение (на t.transform()):

Exception in thread "AWT-EventQueue-0" java.lang.IllegalStateException: StAXSource(XMLEventReader) with XMLEventReader not in XMLStreamConstants.START_DOCUMENT or XMLStreamConstants.START_ELEMENT state 

Упрощенная версия моего XML выглядит как (он имеет Пространства имен):

<?xml version="1.0" encoding="UTF-8"?> 
<gml:FeatureCollection xmlns:gml="http://www.opengis.net/gml/3.2" gml:id="featureCollection"> 
    <gml:featureMember> 
    </eg:RST> 
    <eg:pole>Krakow</eg:pole> 
    <eg:localId>id1234</eg:localId> 
    </gml:featureMember> 
    <gml:featureMember> 
    <eg:RST>1002</eg:RST> 
    <eg:pole>Rzeszow</eg:pole> 
    <eg:localId>id1235</eg:localId> 
    </gml:featureMember> 
</gml:FeatureCollection> 

у меня есть список localId-х объектов (featureMember), которые я хочу изменить и correspoding изменился RST или полюс (Это зависит от пользователя, который изменяется один):

localId (id1234) RST (1001)

localId (id1236) RST (1003)

...

+0

Вы рассматривали ее решение с потоковым XSLT 3.0, который поддерживается Saxon 9 EE от saxonica.com? Если вы отредактируете свой вопрос и покажете нам, какое изменение вы хотите реализовать на элементах 'featureMember', и хотите ли вы генерировать единый результат со всеми измененными элементами или новым документом для каждого измененного элемента, тогда я мог бы показать вам, как это сделать с XSLT 3.0 декларативным способом. –

+0

Я ничего не знаю о XSLT 3.0, поэтому я не рассматривал его. Я не могу сказать, что я хочу изменить на элементах 'featureMember' - это зависит от пользователя (вот почему мне нужен DOM здесь). Я должен найти конкретный 'featureMember' своим локальным идентификатором и изменить некоторые его элементы. У меня есть таблица с изменениями, которые я хочу сделать (они не всегда одинаковы). Я хочу создать единый результат со всеми измененными элементами. – sophiess

+0

Да, мне интересно. Я уже сделал это как-то (это работает, но слишком медленно - 1 час в файле). Если это действительно эффективнее, я могу воспользоваться некоторой помощью. – sophiess

ответ

0

Проблема вы» что при создании StAXSource ваше START_ELEMENT событие уже было использовано. Таким образом, XMLEventReader, вероятно, находится на каком-то событии текстового узла в виде пробела или что-то еще, что не может быть источником XML-документа. Вы можете использовать метод peek() для просмотра следующего события без его использования. Однако убедитесь, что есть событие с hasNext().

Я не уверен на 100% того, чего вы хотите достичь, поэтому вот некоторые вещи, которые вы могли бы сделать в зависимости от сценария.

EDIT: Я просто прочитал некоторые комментарии по вашему вопросу, которые делают вещи более понятными. Нижеследующее может помочь вам достичь желаемого результата с некоторой корректировкой. Также обратите внимание, что Java XSLT-процессоры позволяют использовать функции расширения и элементы расширения, которые могут вызывать Java-код из таблицы стилей XSLT. Это может быть мощным способом расширения базовых функций XSLT с помощью внешних ресурсов, таких как запросы к базе данных.


В случае, если вы хотите, входной XML должны быть преобразованы в один выходной XML, вы могли бы быть лучше просто с помощью преобразования XML стилей. В вашем коде вы создаете трансформатор без каких-либо шаблонов, поэтому он становится стандартным «преобразователем идентичности», который просто копирует входные данные для вывода. Предположим, что ваш входной XML выглядит следующим образом:

<?xml version="1.0" encoding="UTF-8"?> 
<gml:FeatureCollection xmlns:gml="http://www.opengis.net/gml/3.2" gml:id="featureCollection" xmlns:eg="acme.com"> 
    <gml:featureMember> 
    <eg:RST/> 
    <eg:pole>Krakow</eg:pole> 
    <eg:localId>id1234</eg:localId> 
    </gml:featureMember> 
    <gml:featureMember> 
    <eg:RST>1002</eg:RST> 
    <eg:pole>Rzeszow</eg:pole> 
    <eg:localId>id1235</eg:localId> 
    </gml:featureMember> 
</gml:FeatureCollection> 

Я связан префикс eg к некоторому фиктивного пространства имен, так как он отсутствует в образце и установил некорректный элемент RST.

Следующая программа будет запускать преобразование XSLT на вашем входе и записывать его в выходной файл.

package xsltplayground; 

import java.io.File; 
import java.net.URL; 
import java.util.logging.Level; 
import java.util.logging.Logger; 
import javax.xml.transform.Result; 
import javax.xml.transform.Source; 
import javax.xml.transform.Transformer; 
import javax.xml.transform.TransformerConfigurationException; 
import javax.xml.transform.TransformerException; 
import javax.xml.transform.TransformerFactory; 
import javax.xml.transform.stream.StreamResult; 
import javax.xml.transform.stream.StreamSource; 

public class XSLTplayground { 

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

     URL url = XSLTplayground.class.getResource("sample.xml"); 
     File input = new File(url.toURI()); 
     URL url2 = XSLTplayground.class.getResource("stylesheet.xsl"); 
     File xslt = new File(url2.toURI()); 
     URL url3 = XSLTplayground.class.getResource("."); 
     File output = new File(new File(url3.toURI()), "output.xml"); 
     change(input, output, xslt); 

    } 

    private static void change(File pathIn, File pathOut, File xsltFile) { 
     try { 

      // Creating transformer with XSLT file 
      TransformerFactory tf = TransformerFactory.newInstance(); 
      Source xsltSource = new StreamSource(xsltFile); 
      Transformer t = tf.newTransformer(xsltSource); 

      // Input source 
      Source input = new StreamSource(pathIn); 

      // Output target 
      Result output = new StreamResult(pathOut); 

      // Transforming 
      t.transform(input, output); 

     } catch (TransformerConfigurationException ex) { 
      Logger.getLogger(XSLTplayground.class.getName()).log(Level.SEVERE, null, ex); 
     } catch (TransformerException ex) { 
      Logger.getLogger(XSLTplayground.class.getName()).log(Level.SEVERE, null, ex); 
     } 
    } 

} 

Вот stylesheet.xsl пример файла, который для удобства я просто сбрасывали в том же пакете, что и входной XML и класса.

<?xml version="1.0" encoding="UTF-8"?> 
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" xmlns:gml="http://www.opengis.net/gml/3.2" xmlns:eg="acme.com"> 

    <xsl:output method="xml" indent="yes"/> 

    <xsl:template match="node()|@*"> 
     <xsl:copy> 
      <xsl:apply-templates select="node()|@*" /> 
     </xsl:copy> 
    </xsl:template> 

    <xsl:template match="gml:featureMember"> 
     <gml:member> 
      <xsl:apply-templates select="node()|@*" /> 
     </gml:member> 
    </xsl:template> 

</xsl:stylesheet> 

выше таблица стилей будет копировать все по умолчанию, но когда дело доходит до <gml:featureMember> элемента будет обернуть содержимое в новый <gml:member> элемент. Просто очень простой пример того, что вы можете сделать с XSLT.

Выход будет:

<?xml version="1.0" encoding="UTF-8"?> 
<gml:FeatureCollection xmlns:gml="http://www.opengis.net/gml/3.2" xmlns:eg="acme.com" gml:id="featureCollection"> 
    <gml:member> 
    <eg:RST/> 
    <eg:pole>Krakow</eg:pole> 
    <eg:localId>id1234</eg:localId> 
    </gml:member> 
    <gml:member> 
    <eg:RST>1002</eg:RST> 
    <eg:pole>Rzeszow</eg:pole> 
    <eg:localId>id1235</eg:localId> 
    </gml:member> 
</gml:FeatureCollection> 

Так как вход и выход потока файлов, вам не нужен весь DOM в памяти. XSLT в Java довольно быстро и эффективно, поэтому этого может быть достаточно.


Возможно, вы действительно хотите разбить каждое вхождение какого-либо элемента в свой собственный выходной файл с некоторыми изменениями в нем. Вот пример кода, который использует StAX для разделения элементов <gml:featureMember> как отдельных документов. Затем вы можете перебирать созданные файлы и преобразовывать их, но вы хотите (XSLT снова будет хорошим выбором). Очевидно, что обработка ошибок должна быть немного более надежной. Это просто для демонстрации.

package xsltplayground; 

import java.io.File; 
import java.io.FileOutputStream; 
import java.io.IOException; 
import java.io.OutputStream; 
import java.net.URL; 
import java.util.logging.Level; 
import java.util.logging.Logger; 
import javax.xml.stream.XMLEventFactory; 
import javax.xml.stream.XMLEventReader; 
import javax.xml.stream.XMLEventWriter; 
import javax.xml.stream.XMLInputFactory; 
import javax.xml.stream.XMLOutputFactory; 
import javax.xml.stream.XMLStreamException; 
import javax.xml.stream.events.XMLEvent; 
import javax.xml.transform.stream.StreamSource; 

public class XSLTplayground { 

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

     URL url = XSLTplayground.class.getResource("sample.xml"); 
     File input = new File(url.toURI()); 
     URL url2 = XSLTplayground.class.getResource("stylesheet.xsl"); 
     File xslt = new File(url2.toURI()); 
     URL url3 = XSLTplayground.class.getResource("."); 
     File output = new File(url3.toURI()); 
     change(input, output, xslt); 

    } 

    private static void change(File pathIn, File directoryOut, File xsltFile) throws InterruptedException { 
     try { 

      // Creating a StAX event reader from the input 
      XMLInputFactory xmlIf = XMLInputFactory.newFactory(); 
      XMLEventReader reader = xmlIf.createXMLEventReader(new StreamSource(pathIn)); 

      // Create a StAX output factory 
      XMLOutputFactory xmlOf = XMLOutputFactory.newInstance(); 

      int counter = 1; 
      // Keep going until no more events 
      while (reader.hasNext()) { 
       // Peek into the next event to find out what it is 
       XMLEvent next = reader.peek(); 
       // If it's the start of a featureMember element, commence output 
       if (next.isStartElement() 
         && next.asStartElement().getName().getLocalPart().equals("featureMember")) { 
        File output = new File(directoryOut, "output_" + counter + ".xml"); 
        try (OutputStream ops = new FileOutputStream(output)) { 
         XMLEventWriter writer = xmlOf.createXMLEventWriter(ops); 
         copy(reader, writer); 
         writer.flush(); 
         writer.close(); 
        } 
        counter++; 
       } else { 
        // Not in a featureMember element: ignore 
        reader.next(); 
       } 
      } 

     } catch (XMLStreamException ex) { 
      Logger.getLogger(XSLTplayground.class.getName()).log(Level.SEVERE, null, ex); 
     } catch (IOException ex) { 
      Logger.getLogger(XSLTplayground.class.getName()).log(Level.SEVERE, null, ex); 
     } 
    } 

    private static void copy(XMLEventReader reader, XMLEventWriter writer) throws XMLStreamException { 

     // Creating an XMLEventFactory 
     XMLEventFactory ef = XMLEventFactory.newFactory(); 
     // Writing an XML document start 
     writer.add(ef.createStartDocument()); 

     int depth = 0; 
     boolean stop = false; 
     while (!stop) { 
      XMLEvent next = reader.nextEvent(); 
      writer.add(next); 
      if (next.isStartElement()) { 
       depth++; 
      } else if (next.isEndElement()) { 
       depth--; 
       if (depth == 0) { 
        writer.add(ef.createEndDocument()); 
        stop = true; 
       } 
      } 
     } 

    } 

} 
+0

Это было полезно, но я не уверен, что он решает мои проблемы. Мне нужно изменить каждую функцию. Сначала проверьте localId и измените остальные атрибуты позже. Возможно ли это с таблицей стилей? Проблема с StAX заключается в том, что она может идти только вперед. LokalId может быть в конце featureMember, поэтому, когда событие доходит до него, я не могу изменить предыдущие элементы. – sophiess

+0

@sophiess Будет ли XSLT прост в использовании в этом сценарии будет зависеть от того, что вы планируете делать с этим ID. Если есть преобразования, зависящие только от идентификатора и другой информации, содержащейся в XML, то XSLT будет в порядке. Если вам нужно использовать внешние ресурсы, это станет намного сложнее и, возможно, не лучшим выбором. Взгляните на то, что можно сделать с помощью преобразований стилей. Если вы застряли, вы можете задать вопрос, связанный с ними, или найти похожие вопросы. –