XML Document Transform StackOverflowError

lukengda

Mitglied
Hallo zusammen, ich baue mir ein XML-Dokument mit einer Klasse, die für das einfügen und traversieren im Dokument zuständig ist. Die Klasse sieht folgendermaßen aus:

Java:
import java.io.StringWriter;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xembly.ImpossibleModificationException;

public class XMLParser {
   
    private Document doc;
    private Node currentNode;
   
    public XMLParser(String path) {

        try {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();
           
            doc = builder.newDocument();
            currentNode = doc.createElement("Root");
            ((Element) currentNode).setAttribute("Path", path);
            doc.appendChild(currentNode);


        } catch (ParserConfigurationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }
   
    public XMLParser add(String name){
        currentNode = currentNode.appendChild(doc.createElement(name));
       
        return this;
    }
   
    public XMLParser attr(String name, String value){
        ((Element) currentNode).setAttribute(name, value);
       
        return this;
    }
   
    public XMLParser set(String value){
        currentNode = currentNode.appendChild(doc.createTextNode(value));
       
        return this;
    }
   
    public XMLParser up(){
        currentNode = currentNode.getParentNode();
       
        return this;
    }
   
    public String toXML() throws ImpossibleModificationException{
        final Transformer transformer;
        try {
            transformer = TransformerFactory.newInstance().newTransformer();
        } catch (final TransformerConfigurationException ex) {
            throw new IllegalStateException(ex);
        }
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "3");
        final StringWriter writer = new StringWriter();
        try {
            transformer.transform(
                new DOMSource(doc),
                new StreamResult(writer)
            );
        } catch (final TransformerException ex) {
            throw new IllegalArgumentException(ex);
        }
        return writer.toString();
    }
   
   
}

Ich kann dann mit Aufrufen wie "new XMLParser("").add("A").up().add("B").toXML();" Ein XML String erstellen.

Mit kleinen Dokumenten geht das auch, aber wenn das XML-Dokument zu groß wird, dann bekomme ich im letzten Schritt bei transformer.transform(...) einen Stackoverflow:

Code:
Exception in thread "main" java.lang.StackOverflowError
    at com.sun.org.apache.xml.internal.serializer.ToStream.characters(Unknown Source)
    at com.sun.org.apache.xml.internal.serializer.ToUnknownStream.characters(Unknown Source)
    at com.sun.org.apache.xml.internal.serializer.ToUnknownStream.characters(Unknown Source)
    at com.sun.org.apache.xalan.internal.xsltc.trax.DOM2TO.parse(Unknown Source)
    at com.sun.org.apache.xalan.internal.xsltc.trax.DOM2TO.parse(Unknown Source)
    at com.sun.org.apache.xalan.internal.xsltc.trax.DOM2TO.parse(Unknown Source)
    at com.sun.org.apache.xalan.internal.xsltc.trax.DOM2TO.parse(Unknown Source)
    at com.sun.org.apache.xalan.internal.xsltc.trax.DOM2TO.parse(Unknown Source)
    at com.sun.org.apache.xalan.internal.xsltc.trax.DOM2TO.parse(Unknown Source)
    at com.sun.org.apache.xalan.internal.xsltc.trax.DOM2TO.parse(Unknown Source)
    at com.sun.org.apache.xalan.internal.xsltc.trax.DOM2TO.parse(Unknown Source)
    at com.sun.org.apache.xalan.internal.xsltc.trax.DOM2TO.parse(Unknown Source)
    at com.sun.org.apache.xalan.internal.xsltc.trax.DOM2TO.parse(Unknown Source)
   ...[1000 Zeilen lang]

Hat jemand eine Idee für mich, wie ich - ohne die JVM umzuschrauben - das Prozedere Stack-freundlicher gestalten kann?
 
In deinem Beispiel willst du dich mit "up" und "add" dynamisch durch den Baum bewegen. Das geht eben nur wenn der gesamte XML-Baum im Speicher gehalten wird.

Ein erster Test wäre, ob es mit größeren Stackspace geht (JVM Setting -Xss:2m oder mehr).
Unabhängig ob man das so nutzen kann hätte man dann andere Fehlerquellen (Classloader etc) ausgeschlossen.

Eine Idee wäre: die End-Datei zu stückeln. Wer z.B. 200 Seiten Report baut, kann ja 10 Seiten bauen, generieren, wegspeichern, dann die nächsten 10 Seiten und am Ende alle zusammenfügen. Je nach Dokumentenstruktur kann das einfach oder schwierig sein.

Eine weitere Möglichkeit wäre das manuelle herausschreiben mit einem XMLStreamWriter (Stichwort: Java StaX). Dieser Weg ist aber sehr technisch, da man vom erzeugten finalen DOM-Baum erst alle Tags durchgehen muss.

Code:
Document document = ...
document.appendChild(...)
document.appendChild(...)
...

// Wenn fertig, root Node holen

Element rootNode = document.getDocumentElement();

// Hier wird StaX genutzt
XMLOutputFactory outputFactory = XMLOutputFactory.newFactory();
XMLStreamWriter writer = outputFactory.createXMLStreamWriter(dateiName, encoding);

// Pseudocode!
// Hier einfach das ganze Dokument durchgehen und jedes Element
// systematisch rausschreiben, mit oder ohne Attribute etc.

for (Element childNode : rootNode.allNodes())
 { 
    writer.writeStartElement(childNode.getElementName());
    // When attribute dann schreiben 
    // writer.writeAttribute(name);
   ...
    writer.writeEndElement();
    writer.flush(); <- hier wird der Outputbuffer geleert und Speicher zurückgegeben!
}

writer.writeEndDocument();
writer.close();

Dies ist etwas technisch, sollte aber in kurzer Zeit zu einem Ergebnis führen, wenn man unbedingt die Freiheit braucht sich durch den XML-Baum bewegen zu müssen.
 

Zurück
Oben