2016-05-17 7 views
3

大きな(5MB)XMLファイルで単一の値を変更しようとしています。私は値が最初の10行にあることを常に知っているので、ファイルの99%を読む必要はありません。それでも、Javaで部分的にXMLを読み込むのは非常に難しいようです。JavaでXMLファイルをマイナーに編集する方法

この写真では、私がアクセスする必要がある単一の値を見ることができます。

私はJavaのXMLとそれを扱うベストプラクティスについてたくさん読んでいます。しかし、この場合、最良のアプローチが何であるかはわかりません.DOM、STAX、SAXパーサはすべて、異なるユースケースのシナリオが異なるようです。この問題に最も適しているかどうかはわかりません。私がする必要があるのは、一つの値を編集することだけです。

は多分、私もXMLパーサーを使用しないでくださいとだけ正規表現で行くが、それは正しい方向に私を指すことができpretty bad idea to use regex on XML

を望んで誰かがあるようには思え、 ありがとう!

+2

あなたはファイル全体を持っていないかのようにまあ「部分読み取り」として本物のようなもの何もない、それはおそらく不適切にフォーマットさポータルであるため、解析できないだろう、それはパースない場合その属性にアクセスすることはできません。そのような小さな編集のためには、ファイル全体を文字列として読み込み(デシリアライズしようとするよりもはるかに高速)、文字列置換/パターン検索を行うだけです。 – Wobbles

+2

@Wobblesうまくいきません。SAXとStAXパーサは、ドキュメント全体をメモリにロードしたくない正確なシナリオ用に構築されています。 –

+0

もちろん、部分的に読むこともできます。しかし、部分的な書き込み(ファイルの末尾に追加する場合を除く)はありません。 – Kayaman

答えて

2

DOMのSAXまたはStAXをAPIの(単純な)単純化のために選択します。はい、DOMを実装するための定型コードがいくつかありますが、いったんそれを超えるとかなり簡単になります。

XMLソースが100または1000メガバイトの場合、ストリーミングAPIの1つがより適していると言われています。

import java.io.File; 
import javax.xml.parsers.*; 
import javax.xml.transform.*; 
import javax.xml.transform.dom.*; 
import javax.xml.transform.stream.*; 
import javax.xml.xpath.*; 
import org.w3c.dom.*; 

public class ChangeVersion 
{ 
    public static void main(String[] args) 
      throws Exception 
    { 
     if (args.length < 3) { 
      System.err.println("Usage: ChangeVersion <input> <output> <new version>"); 
      System.exit(1); 
     } 

     File inputFile = new File(args[0]); 
     File outputFile = new File(args[1]); 
     int updatedVersion = Integer.parseInt(args[2], 10); 

     DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance(); 
     DocumentBuilder docBuilder = domFactory.newDocumentBuilder(); 
     Document doc = docBuilder.parse(inputFile); 

     XPathFactory xpathFactory = XPathFactory.newInstance(); 
     XPath xpath = xpathFactory.newXPath(); 
     XPathExpression expr = xpath.compile("/PremiereData/Project/@Version"); 

     NodeList versionAttrNodes = (NodeList) expr.evaluate(doc, XPathConstants.NODESET); 

     for (int i = 0; i < versionAttrNodes.getLength(); i++) { 
      Attr versionAttr = (Attr) versionAttrNodes.item(i); 
      versionAttr.setNodeValue(String.valueOf(updatedVersion)); 
     } 

     TransformerFactory transformerFactory = TransformerFactory.newInstance(); 
     Transformer transformer = transformerFactory.newTransformer(); 

     transformer.setOutputProperty(OutputKeys.INDENT, "yes"); 
     transformer.transform(new DOMSource(doc), new StreamResult(outputFile)); 
    } 
} 
+1

十分に公正であり、完全で実用的な例では+1です。私はDOMに5MBも読み込むことには気をつけますが、あなたが正しいと確信しています。ストリーミングによるパフォーマンスの向上はおそらく学習曲線によってわずらわされているでしょう... –

+0

完全に正しいです。私が心配していたことを知りません - これは魅力のように動作します、ありがとう! – nmu

2

あなたはそれを読んでXMLを書くためのStAXパーサーを使用することができます:それはあるので、5メガバイトは、私は、大規模なデータセットを検討するので、先に行くとDOMを使用して、一日それを呼ぶものではありません。これを実行している間は、解析するときにコンテンツを置き換えることができます。 StAXパーサーを使用すると、任意の時点でメモリ内のXMLの一部のみが含まれます。

public static void main(String [] args) throws Exception { 
    final String newProjectId = "888"; 

    File inputFile = new File("in.xml"); 
    File outputFile = new File("out.xml"); 
    System.out.println("Reading " + inputFile); 
    System.out.println("Writing " + outputFile); 

    XMLInputFactory inFactory = XMLInputFactory.newInstance(); 
    XMLEventReader eventReader = inFactory.createXMLEventReader(new FileInputStream(inputFile)); 
    XMLOutputFactory factory = XMLOutputFactory.newInstance(); 
    XMLEventWriter writer = factory.createXMLEventWriter(new FileWriter(outputFile)); 
    XMLEventFactory eventFactory = XMLEventFactory.newInstance(); 


    boolean useExistingEvent; // specifies if we should use the event right from the reader 
    while (eventReader.hasNext()) { 
     XMLEvent event = eventReader.nextEvent(); 
     useExistingEvent = true; 

     // look for our Project element 
     if(event.getEventType() == XMLEvent.START_ELEMENT) { 
      // read characters 
      StartElement elemEvent = event.asStartElement(); 
      Attribute attr = elemEvent.getAttributeByName(QName.valueOf("ObjectID")); 
      // check to see if this is the project we want 
      // TODO: put what logic you want here 
      if("Project".equals(elemEvent.getName().getLocalPart()) && attr != null && attr.getValue().equals("1")) { 
       Attribute versionAttr = elemEvent.getAttributeByName(QName.valueOf("Version")); 

       // we need to make a list of new attributes for this element which doesnt include the Version a 
       List<Attribute> newAttrs = new ArrayList<>(); // new list of attrs 
       Iterator<Attribute> existingAttrs = elemEvent.getAttributes(); 
       while(existingAttrs.hasNext()) { 
        Attribute existing = existingAttrs.next(); 
        // copy over everything but version attribute 
        if(!existing.getName().getLocalPart().equals("Version")) { 
         newAttrs.add(existing); 
        } 
       } 
       // add our new attribute for projectId 
       newAttrs.add(eventFactory.createAttribute(versionAttr.getName(), newProjectId)); 

       // were using our own event instead of the existing one 
       useExistingEvent = false; 
       writer.add(eventFactory.createStartElement(elemEvent.getName(), newAttrs.iterator(), elemEvent.getNamespaces())); 
      } 
     } 

     // persist the existing event. 
     if(useExistingEvent) { 
      writer.add(event); 
     } 

    } 
    writer.close(); 
} 
関連する問題