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

org.navimatrix.commons.data.XmlCoder2 Maven / Gradle / Ivy

There is a newer version: 1.0.5
Show newest version
package org.navimatrix.commons.data;

import java.io.*;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.navimatrix.commons.data.sdoimpl.BasicDataGraph;
import org.navimatrix.commons.data.sdoimpl.BasicDataObject;
import org.xmlpull.mxp1.MXParser;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;

public class XmlCoder2 implements Coder {

    private static final Log m_log = LogFactory.getLog(XmlCoder.class);

    public static final String PROP_DEBUG               = "debug";
    public static final String PROP_REPAIR_ERRORS       = "repair_errors";
    public static final String PROP_PRESERVE_PREFIX     = "preserve_prefix";
    public static final String PROP_WHITESPACE          = "whitespace";
    public static final String PROP_URL_ENCODE          = "url_encode";
    public static final String PROP_XML_PREAMBLE_OFF    = "xml_preamble_off";
    public static final String PROP_USE_APOSTROPHE      = "use_apostrophe";

    ////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////
    /////                                              /////
    /////    TODO: construct coders with properties    /////
    /////                                              /////
    ////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////

    private final boolean debug;
    private final boolean m_repair_errors;
    private final boolean m_url_encode;
    private final boolean m_preserve_prefix;
    private final boolean m_whitespace;
    private final boolean m_xml_preamble_off;
    private final boolean m_use_apostrophe;

    ////////////////////////////////////////////////////////
    // note, string interning is now managed by type and  //
    //  property impls                                    //
    ////////////////////////////////////////////////////////

    //ejs todo: rewrite w/o the static stuff. this is a mess

    private static boolean m_preserve_prefix_default = false;

    static {
        String i = System.getProperty("org.navimatrix.commons.data.preserve_prefix_default", "true");
        m_preserve_prefix_default = Boolean.valueOf(i).booleanValue();
    }

    private final Map m_prefix_map;

    //
    // begin constructors
    //

    public XmlCoder2(Map properties, Map prefix_map) {

        debug               = properties.containsKey(PROP_DEBUG);
        m_repair_errors     = properties.containsKey(PROP_REPAIR_ERRORS);
        m_whitespace        = properties.containsKey(PROP_WHITESPACE);
        m_url_encode        = properties.containsKey(PROP_URL_ENCODE);
        m_preserve_prefix   = properties.containsKey(PROP_PRESERVE_PREFIX);
        m_xml_preamble_off  = properties.containsKey(PROP_XML_PREAMBLE_OFF);
        m_use_apostrophe    = properties.containsKey(PROP_USE_APOSTROPHE);
        m_prefix_map = prefix_map;
    }

    public XmlCoder2(Map properties) {

        this(properties, null);
    }


    public XmlCoder2() {

        this(new Properties(), null);
    }

    //
    // end constructors
    //

    //
    // begin Coder ifc
    //

    public DataObject decodeSdo(InputStream input) throws IOException {

        try {

            InputStreamReader isr = new InputStreamReader(input, "UTF8");

            XmlDecoder d = new XmlDecoder(isr);

            d.run();

            return (d.data);

        } catch (XmlPullParserException xppe) {

            throw new IOException(xppe.toString());
        }
    }


    public void encodeSdo(DataObject data, OutputStream output) throws IOException {

        try {

            OutputStreamWriter writer = new OutputStreamWriter(output, "UTF8");

            XmlEncoder xe = new XmlEncoder(writer);
            xe.run(data);

        } catch (XmlPullParserException xppe) {

            throw new IOException(xppe.toString());
        }
    }


    //
    // end Coder ifc
    //


    /**
     */
    private final class XmlDecoder {

        private final MXParser xpp;

        DataObject data;

        public XmlDecoder(Reader input) throws XmlPullParserException, IOException {

            xpp = DataFactory.getParser();

            xpp.setInput(input);
        }

        public void run() throws XmlPullParserException, IOException {
            processDocument(xpp);
        }

        private void processDocument(XmlPullParser xpp)
        throws XmlPullParserException, IOException
        {
            int eventType = xpp.getEventType();
            do {

                if (eventType == xpp.START_DOCUMENT) {
                } else if (eventType == xpp.END_DOCUMENT) {
                } else if (eventType == xpp.START_TAG) {
                    processStartElement(xpp);
                } else if (eventType == xpp.END_TAG) {
                    processEndElement(xpp);
                } else if (eventType == xpp.TEXT) {
                    processText(xpp);
                }
                eventType = xpp.next();
            } while (eventType != xpp.END_DOCUMENT);
        }

        private void processStartElement (XmlPullParser xpp)
        throws XmlPullParserException
        {
            int depth = xpp.getDepth() - 1; //xpp starts at 1

            String name = xpp.getName();

            String uri = xpp.getNamespace();

            String prefix = xpp.getPrefix();

            switch (depth) {
            case 0:
                //start session
                DataGraph graph = new BasicDataGraph();
                data = graph.createRootObject(uri, prefix, name);
                if (data == null) {
                    throw new java.lang.NullPointerException("graph is broken");
                }
                setAttrs(xpp, data);
                break;

            default:
                data = data.createDataObject(name, uri, prefix);
                if (data == null) {
                    throw new java.lang.NullPointerException("dataobject factory is broken");
                }
                setAttrs(xpp, data);
            }
        }

        private void setAttrs(XmlPullParser xpp, DataObject data) {

            for (int i = 0; i < xpp.getAttributeCount(); i++) {

                String attrName = xpp.getAttributeName(i);

                String attrValue = xpp.getAttributeValue(i);

                data.setString("@" + attrName, attrValue);
            }
        }

        private void processEndElement (XmlPullParser xpp)
        throws XmlPullParserException
        {
            int depth = xpp.getDepth() - 1; //xpp starts at 1

            switch (depth) {
            case 0:
                //close session
                break;

            default:
                data = data.getContainer();
                if (data == null) {
                    throw new java.lang.NullPointerException("def lvl dataobject has no root");
                }
            }
        }

        //private int holderForStartAndLength[] = new int[2];
        private void processText (XmlPullParser xpp) throws XmlPullParserException
        {
            //warning, error!!!! seems to get called before all the text is read??? called multi
            //tricky to address current node...
            if (data != null) {
                String text = xpp.getText();
                if (text == null || text.trim().equals("")) {
                    return;
                }

                String decodedText = null;

                try {

                    if (m_url_encode) {
                        decodedText = URLDecoder.decode(text, "UTF8");
                    } else {
                        decodedText = text;
                    }
                } catch (java.io.UnsupportedEncodingException use) {

                    m_log.warn(use.toString());

                    decodedText = text;
                }

                ((BasicDataObject) data).setValue(decodedText);
            }
        }
    }


    private final class XmlEncoder {

        //String DEFAULT_NS = "";//this probabably can't easily be calculated.  set it
        //String DEFAULT_NS = "jabber:client";//this probabably can't easily be calculated.  set it
        // and the prefix will be ""

        final XmlSerializer serializer;

        String currentUri = null;

        Writer writer;

        public XmlEncoder(final Writer writer) throws XmlPullParserException, IOException {

            this.writer = writer;

            serializer = DataFactory.getSerializer();

            serializer.setOutput(writer);

            if (m_whitespace) {

                serializer.setProperty("http://xmlpull.org/v1/doc/properties.html#serializer-indentation", "  ");
                serializer.setProperty("http://xmlpull.org/v1/doc/properties.html#serializer-line-separator", "\n");
            }

            if (m_use_apostrophe) {
                serializer.setFeature("http://xmlpull.org/v1/doc/features.html#serializer-attvalue-use-apostrophe", true);
            }

            if (!m_xml_preamble_off) {

                serializer.startDocument("UTF8", true);
                if (m_whitespace) {
                    writer.write("\n");
                }
            }
        }

        public void run(final DataObject data) throws XmlPullParserException, IOException {

            final String uri = data.getType().getURI();

            String prefix;

            if (uri != null && m_prefix_map != null && m_prefix_map.containsKey(uri)) {

                prefix = (String) m_prefix_map.get(uri);

            } else {

                prefix = m_preserve_prefix ? data.getType().getURIPrefix() : "";
            }

            currentUri = uri;

            if (uri == null) {
                throw new NullPointerException("uri can not be null: " + data); //i18n
            }

            serializer.setPrefix(prefix, uri);

            serializer.startTag(uri, data.getType().getName());

            processAttrs(data);

            processData(data, null);

            serializer.endTag(uri, data.getType().getName());
        }

        private void processData(final DataObject data, final DataObject parent) throws XmlPullParserException, IOException {

            final List props = data.getType().getProperties();

            final Object grandparent = (parent != null ? parent.getContainer() : null);

            //process elements
            for (Iterator iter = props.iterator(); iter.hasNext();) {

                final Property p = (Property) iter.next();

                if (isAttr(p)) {

                    //don't process attributes here
                    continue;
                }

                if (data.isSet(p)) {

                    Object fld = data.get(p);

                    if (fld == data) continue; //circ
                    if (fld == parent) continue; //circ
                    if (fld == grandparent) continue; //circ

                    if (fld instanceof List) {

                        //process isMany
                        List lst = (List) fld;
                        for (int i = 0; i < lst.size(); i++) {

                            final Object lfld = lst.get(i);

                            if (lfld instanceof BasicDataObject) {

                                if (fld == data) continue; //circ
                                if (fld == parent) continue; //circ
                                if (fld == grandparent) continue; //circ
                                processElement((BasicDataObject) lfld, data);
                            }
                        }

                    } else if (fld instanceof BasicDataObject) {


                        if (fld == data) {
                            fld = "_this_";
                        }

                        //process !isMany elements
                        processElement((BasicDataObject) fld, data);
                    }
                }
            }
        }

        private void processElement(final BasicDataObject data, final DataObject parent) throws XmlPullParserException, IOException {

            final Object grandparent = (parent != null ? parent.getContainer() : null);

            String uri = data.getType().getURI();
            String prefix = data.getType().getURIPrefix();

            //if (!uri.equals(currentUri) && serializer.getPrefix(uri, false) == null) {
            if (!uri.equals(currentUri)) {

                boolean skip = false;

                //see if a prefix for this uri already exists
                if (uri != null         &&
                    !uri.equals("")     &&
                    serializer.getPrefix(uri, false) != null) {

                    skip = true;
                }
                if (!skip) {
                    //if this is a new uri, we don't want to use prefixes
                    serializer.setPrefix(prefix, uri);
                }
            }
            if (!uri.equals("")) {
                currentUri = uri;
            }

            /*
            serializer.setPrefix(prefix, uri);
            */

            final String name = data.getType().getName();

            serializer.startTag(uri, name);

            processAttrs(data);

            /////////////////////
            /// there is a bug here in that an element with attrs can't have a value, only
            /// children.  xml2data works fine, but data2xml loses values! todo: fixme
            ////////////////////

            if (!data.hasValue()) {
                processData(data, parent);
            } else {
                //serializer.text(data.toString());
                final Object v = data.getValue();
                if (v == parent || v == grandparent) {
                    return; //protect against circular refs
                }
                if (v != null) {
                    try {

                        //serializer.text(v.toString());
                        serializer.text(URLEncoder.encode(v.toString(),"UTF8"));

                    } catch (java.lang.IllegalStateException ile) {

                        if (m_repair_errors) {

                            serializer.text("_ERROR_BAD_DATA_");

                        } else {

                            throw ile;
                        }
                    }
                }
            }
            serializer.endTag(uri, name);
        }

        private void processAttrs(final Object object) throws IOException {
            if (!(object instanceof DataObject)) {
                return;
            }
            final DataObject data = (DataObject) object;
            final List props = data.getType().getProperties();

            for (int i = 0; i < props.size(); i++) {

                final Property p = (Property) props.get(i);

                if (isAttr(p) && data.isSet(p)) {

                    final String flddata = data.getString(p);
                    final String fldname = p.getName().substring(1, p.getName().length());

                    if (fldname != null && flddata != null) {
                        serializer.attribute(null, fldname, flddata);
                    }
                }
            }
        }

        private boolean isAttr(Property p) {
            return p.getName().charAt(0) == '@';
        }
    }
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy