2017-01-23 41 views
2

我的目标是阅读对象(featureMember)到DOM,对其进行修改并写回新的XML。 XML太大而无法使用DOM本身。我想到了我需要的是StAX和TransformerFactory,但我无法使它工作。如何在Java中使用StAX读取修改XML片段?

这是我做了什么至今:

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解决它由撒克逊9 EE从saxonica.com支持?如果您编辑问题并向我们展示您希望在'featureMember'元素上实现哪些更改,以及您是要生成包含所有更改元素的单个结果还是每个更改元素的新文档,那么我可以告诉您如何执行该操作以声明的方式使用XSLT 3.0。 –

+0

我对XSLT 3.0一无所知,所以我没有考虑它。我无法真正说出我想在'featureMember'元素上改变什么 - 它取决于用户(这就是为什么我需要DOM)。我必须通过它的localID找到特定的'featureMember',并改变它的一些元素。我有一张桌子,上面有我想做的更改(它们并不总是相同的)。我想用所有更改的元素生成单个结果。 – sophiess

+0

是的,我很感兴趣。我已经做了它(它工作,但有点太慢 - 每个文件1小时)。如果真的更有效率,我可以使用一些帮助。 – sophiess

回答

0

的问题,你”再有就是当你创建StAXSource,你START_ELEMENT事件已经被消耗。所以XMLEventReader可能是在一些空白文本节点事件,还是其他什么东西不能是一个XML文档源。您可以使用peek()方法,无需消耗它来查看下一个事件。尽管如此,确保首先有hasNext()的事件发生。

我不是100%确定你想完成什么,所以这里有些事情可以根据情况做。

编辑:我刚看了一些这让事情变得更清晰一点上你的问题的意见。下面的内容仍然可以帮助你通过一些调整来达到预期的效果。还要注意的是Java的XSLT处理器允许扩展函数和扩展元素,它可以从一个XSLT样式表调入Java代码。这可以是一种强大的方法,可以使用外部资源(如数据库查询)扩展基本的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。 Java中的XSLT非常快速高效,所以这可能就足够了。


也许你真的想把每一个元素出现在它自己的输出文件中,并对它进行一些修改。以下是使用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

这很有帮助,但我不确定它是否能解决我的问题。我必须更改每个featureMember - 先检查localId,稍后再更改其他属性。这是可能的样式表?我用StAX得到的问题是它只能继续前进。 lokalId可以在featureMember的结尾处,所以当事件发生时,我无法更改以前的元素。 – sophiess

+0

@sophiess在这种情况下,XSLT是否易于使用将取决于您打算如何使用该ID。如果只根据XML中包含的ID和其他信息进行转换,那么XSLT将会很好。如果你不得不利用外部资源,这将变得更加困难,也许不是最好的选择。看看样式表转换可以做些什么。如果您遇到困难,您可以在这里提出与他们有关的问题,或者搜索类似的问题。 –