All Downloads are FREE. Search and download functionalities are using the official Maven repository.

lux.xml.Serializer Maven / Gradle / Ivy

There is a newer version: 1.1.0
Show newest version
package lux.xml;

import java.util.HashMap;
import java.util.LinkedList;

import javax.xml.XMLConstants;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;

import lux.exception.LuxException;

import org.codehaus.stax2.DTDInfo;
import org.codehaus.stax2.XMLStreamReader2;

/**
 * Accumulates a serialized XML document from StAX events according to a prescribed set of conventions,
 * as follows:
 * Line endings are normalized; CDATA blocks are preserved; entities and numeric references are preserved; redundant namespace 
 * declarations are dropped.  Comments and processing instructions are preserved.
 * No XML declaration is emitted. DOCTYPE declarations are preserved, but not the DTD internal subset.
 * Empty elements are represented with both open and close tags.
 */
public class Serializer implements StAXHandler {

    private StringBuilder doc;

    private HashMap nsContextFrame;
    private HashMap inScopeNamespaces;
    private LinkedList> namespaceContexts;
    
    public String getDocument () {
        return doc.toString();
    }

    /**
     * Consume the XML stream, saving text to the document buffer.
     *
     * @param r source of xml StAX events
     * @param evtType the type of StAX event
     */
    @Override
    public void handleEvent (XMLStreamReader r, int evtType)
    {
        switch (evtType) {
        case XMLStreamConstants.START_DOCUMENT:
            inScopeNamespaces = new HashMap();
            inScopeNamespaces.put("", "");
            inScopeNamespaces.put("xml", XMLConstants.W3C_XML_SCHEMA_NS_URI);
            namespaceContexts = new  LinkedList>();
            doc = new StringBuilder ();
            break;

        case XMLStreamConstants.CDATA:
            doc.append("");
            break;

        case XMLStreamConstants.SPACE:
            // fall through

        case XMLStreamConstants.CHARACTERS:
            appendText(r);
            break;

        case XMLStreamConstants.COMMENT:
            doc.append("");
            break;

        case XMLStreamConstants.END_DOCUMENT:
            doc.append('\n');
            break;

        case XMLStreamConstants.END_ELEMENT:
            doc.append("");
            unwindNamespaceContext();
            break;

        case XMLStreamConstants.ENTITY_DECLARATION:
        case XMLStreamConstants.NOTATION_DECLARATION:
            break;

        case XMLStreamConstants.ENTITY_REFERENCE:
            doc.append('&').append(r.getLocalName()).append(';');
            break;

        case XMLStreamConstants.PROCESSING_INSTRUCTION:
            doc.append("");
            break;

        case XMLStreamConstants.START_ELEMENT:
            {
                nsContextFrame = null;
                doc.append('<');
                appendQName(r);
                handleNamespace(r.getPrefix(), r.getNamespaceURI());
                // Any declared namespaces?
                for (int i = 0, len = r.getNamespaceCount(); i < len; ++i) {
                    handleNamespace (r.getNamespacePrefix(i), r.getNamespaceURI(i));
                }
                // And then the attributes:
                for (int i = 0, len = r.getAttributeCount(); i < len; ++i) {
                    String prefix = r.getAttributePrefix(i);
                    doc.append (' ');
                    if (prefix != null && prefix.length() > 0) {
                        doc.append (prefix).append(':');
                    }
                    String value = r.getAttributeValue(i).replace("&", "&").replace("\"", """).replace("<", "<");
                    doc.append(r.getAttributeLocalName(i)).append ("=\"").append(value).append('"');
                }
                doc.append(">");
                namespaceContexts.push(nsContextFrame);                
            }
            break;

        case XMLStreamConstants.DTD:
            DTDInfo dtd;
            try {
                dtd = ((XMLStreamReader2)r).getDTDInfo();
            } catch (XMLStreamException e) {
                break;
            }
            doc.append("\n");
            // Lose the DTD internal subset, sorry
            break;

        default:
            throw new LuxException("Unexpected StAX event: " + r.getEventType());
        }
    }

    private void appendText(XMLStreamReader r) {
        int end = r.getTextStart() + r.getTextLength();
        char [] c = r.getTextCharacters();
        for (int i = r.getTextStart(); i < end; i++) {
            // TODO: look ahead and copy characters in blocks?
            switch (c[i]) {
            case '<': doc.append("<"); break;
            case '&': doc.append("&"); break;
            default: doc.append(c[i]);
            }
        }
    }

    private void unwindNamespaceContext () {
        HashMap nsContext = namespaceContexts.pop();
        if (nsContext != null) {
            inScopeNamespaces.putAll(nsContext);
        }
    }
    
    private String handleNamespace(String prefix, String namespace) {
        if (namespace == null) {
            namespace = "";
        }
        if (prefix == null) {
            prefix = "";
        }
        if (! namespace.equals(inScopeNamespaces.get(prefix))) {
            appendNamespaceDeclaration (prefix, namespace);
            if (nsContextFrame == null) {
                nsContextFrame = new HashMap();
            }
            // retain the outer context so we can restore it later
            nsContextFrame.put(prefix, inScopeNamespaces.get(prefix));
            inScopeNamespaces.put (prefix, namespace);
        }
        return namespace;
    }

    private void appendNamespaceDeclaration(String prefix, String namespace) {
        doc.append (" xmlns");
        if (prefix.length() > 0) {
            doc.append (':').append(prefix);
        }
        doc.append("=\"");
        namespace = namespace.replace("\"", """);
        doc.append(namespace);
        doc.append("\"");
    }
    
    @Override
    public void reset () {
        doc = null;
        namespaceContexts = null;
        inScopeNamespaces = null;
        namespaceContexts = null;
    }

    private void appendQName(XMLStreamReader r) {
        String elemPrefix = r.getPrefix();
        String ln = r.getLocalName();
        if (elemPrefix != null && elemPrefix.length() > 0) {
            doc.append (elemPrefix).append(':');
            
        } 
        doc.append(ln);
    }
    
}

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */




© 2015 - 2024 Weber Informatics LLC | Privacy Policy