com.vinexs.tool.XML.java Source code

Java tutorial

Introduction

Here is the source code for com.vinexs.tool.XML.java

Source

/*
 * Copyright (c) 2015. Vin @ vinexs.com (MIT License)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package com.vinexs.tool;

import android.text.TextUtils;
import android.util.Log;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

/**
 * XML parser, parse standard XML to JSONObject or JSON Stringify.
 * To fix that org.json components conflict with android internal library.
 * This parser use org.w3c.dom component to re-format XML and put data to JSONObject.
 * <p>
 * Dependence on com.vinexs.tool.Determinator
 */
@SuppressWarnings("unused")
public class XML {

    /**
     * Parse standard XML to JSONObject.
     *
     * @param xml XML document stored by string.
     * @return JSONObject
     */
    public static JSONObject toJSONObject(String xml) {
        InputStream inputStream = new ByteArrayInputStream(xml.getBytes(Charset.forName("UTF-8")));
        return toJSONObject(inputStream);
    }

    /**
     * Parse standard XML to JSONObject.
     *
     * @param file XML document stored in a file.
     * @return JSONObject
     */
    public static JSONObject toJSONObject(File file) {
        try {
            InputStream fileInputStream = new FileInputStream(file);
            return toJSONObject(fileInputStream);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * Parse standard XML to JSONObject.
     *
     * @param inputStream InputStream point to a XML document.
     * @return JSONObject
     */
    public static JSONObject toJSONObject(InputStream inputStream) {
        JSONObject json = new JSONObject();
        try {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();
            Document document = builder.parse(inputStream);
            org.w3c.dom.Element tag = document.getDocumentElement();
            json.put(tag.getTagName(), getChildJSONObject(tag));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return json;
    }

    /**
     * Parse standard XML to JSON Stringify.
     *
     * @param xml XML document stored by string.
     * @return JSONObject
     */
    public static String toJSONStringify(String xml) {
        InputStream inputStream = new ByteArrayInputStream(xml.getBytes(Charset.forName("UTF-8")));
        return toJSONStringify(inputStream);
    }

    /**
     * Parse standard XML to JSON Stringify.
     *
     * @param file XML document stored in a file.
     * @return JSONObject
     */
    public static String toJSONStringify(File file) {
        try {
            InputStream fileInputStream = new FileInputStream(file);
            return toJSONStringify(fileInputStream);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * Parse standard XML to JSON Stringify.
     *
     * @param inputStream InputStream point to a XML document.
     * @return JSONObject
     */
    public static String toJSONStringify(InputStream inputStream) {
        JSONObject json = toJSONObject(inputStream);
        if (json == null) {
            return "{}";
        }
        return json.toString();
    }

    private static Object getChildJSONObject(org.w3c.dom.Element tag) {
        int i, k;

        //get attributes && child nodes
        NamedNodeMap attributes = tag.getAttributes();
        NodeList childNodes = tag.getChildNodes();
        int numAttr = attributes.getLength();
        int numChild = childNodes.getLength();

        //get element nodes
        Boolean hasTagChild = false;
        Map<String, ArrayList<Object>> childMap = new HashMap<>();
        for (i = 0; i < numChild; i++) {
            Node node = childNodes.item(i);
            //not process non-element node
            if (node.getNodeType() != org.w3c.dom.Node.ELEMENT_NODE) {
                continue;
            }
            hasTagChild = true;
            org.w3c.dom.Element childTag = (org.w3c.dom.Element) node;
            String tagName = childTag.getTagName();
            if (!childMap.containsKey(tagName)) {
                childMap.put(tagName, new ArrayList<>());
            }
            childMap.get(tagName).add(getChildJSONObject(childTag));
        }
        if (numAttr == 0 && !hasTagChild) {
            // Return String
            return stringToValue(tag.getTextContent());
        } else {
            // Return JSONObject
            JSONObject data = new JSONObject();
            if (numAttr > 0) {
                for (i = 0; i < numAttr; i++) {
                    Node attr = attributes.item(i);
                    try {
                        data.put(attr.getNodeName(), stringToValue(attr.getNodeValue()));
                    } catch (JSONException e) {
                        e.printStackTrace();
                    }
                }
            }
            if (hasTagChild) {
                for (Map.Entry<String, ArrayList<Object>> tagMap : childMap.entrySet()) {
                    ArrayList<Object> tagList = tagMap.getValue();
                    if (tagList.size() == 1) {
                        try {
                            data.put(tagMap.getKey(), tagList.get(0));
                        } catch (JSONException e) {
                            e.printStackTrace();
                        }
                    } else {
                        JSONArray array = new JSONArray();
                        for (k = 0; k < tagList.size(); k++) {
                            array.put(tagList.get(k));
                        }
                        try {
                            data.put(tagMap.getKey(), array);
                        } catch (JSONException e) {
                            e.printStackTrace();
                        }
                    }
                }
            } else {
                try {
                    data.put("content", stringToValue(tag.getTextContent()));
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }
            return data;
        }
    }

    /**
     * Convert a string to its possible data type.
     *
     * @param string String to be converted.
     * @return Value in Boolean, Null, Integer, Double or String.
     */
    public static Object stringToValue(String string) {
        if ("true".equalsIgnoreCase(string)) {
            return Boolean.TRUE;
        }
        if ("false".equalsIgnoreCase(string)) {
            return Boolean.FALSE;
        }
        if ("null".equalsIgnoreCase(string)) {
            return JSONObject.NULL;
        }
        if (Determinator.isNumeric(string)) {
            if (Determinator.isInteger(string)) {
                Integer strInt = Integer.valueOf(string);
                if (strInt.toString().equals(string)) {
                    return strInt;
                }
            }
            return Double.valueOf(string);
        }
        return string;
    }

    // =============================================================================================

    /**
     * Parse standard XML to element structure, allow access with css selector.
     *
     * @param xml XML document stored by string.
     * @return Element
     */
    public static Element toElement(String xml) throws IOException, SAXException, ParserConfigurationException {
        InputStream inputStream = new ByteArrayInputStream(xml.getBytes(Charset.forName("UTF-8")));
        return toElement(inputStream);
    }

    /**
     * Parse standard XML to element structure, allow access with css selector.
     *
     * @param file XML document stored in a file.
     * @return JSONObject
     */
    public static Element toElement(File file) throws IOException, SAXException, ParserConfigurationException {
        try {
            InputStream fileInputStream = new FileInputStream(file);
            return toElement(fileInputStream);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * Parse standard XML to element structure, allow access with css selector.
     *
     * @param inputStream InputStream point to a XML document.
     * @return JSONObject
     */
    public static Element toElement(InputStream inputStream)
            throws ParserConfigurationException, SAXException, IOException {
        SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
        SAXParser parser = saxParserFactory.newSAXParser();
        parseHandler handler = new parseHandler();
        parser.parse(inputStream, handler);
        return handler.datas;
    }

    public static class parseHandler extends DefaultHandler {

        private StringBuilder stringBuilder = new StringBuilder(1024);
        private Element currentChild = this.datas;
        protected Element datas = new Element(null, "root");

        @Override
        public void startElement(String uri, String localName, String qName, Attributes attributes)
                throws SAXException {
            this.currentChild = this.currentChild.addChild(!TextUtils.isEmpty(localName) ? localName : qName);
            this.currentChild.addAttributes(attributes);
        }

        @Override
        public void endElement(String uri, String localName, String name) throws SAXException {
            this.currentChild.value = this.stringBuilder.toString().trim();
            this.stringBuilder.setLength(0);
            this.currentChild = this.currentChild.parent;
        }

        @Override
        public void characters(char[] chars, int start, int length) throws SAXException {
            this.stringBuilder.append(chars, start, length);
        }
    }

    public static class Elements extends ArrayList<Element> {

        private static final long serialVersionUID = (new Random()).nextLong();

        public String attr(String attrName) {
            return !this.isEmpty() ? this.get(0).attr(attrName) : "";
        }

        public String val() {
            return !this.isEmpty() ? this.get(0).val() : "";
        }

        public Elements find(String selector) {
            Elements elements = new Elements();

            for (Element element : this) {
                elements.addAll(element.find(selector));
            }
            return elements;
        }

        public Elements findChildren(String tagName) {
            Elements elements = new Elements();

            for (Element element : this) {
                elements.addAll(element.findChildren(tagName));
            }

            return elements;
        }

        public Elements findByAttribute(String key, String value) {
            Elements elements = new Elements();

            for (Element element : this) {
                elements.addAll(element.findByAttribute(key, value));
            }

            return elements;
        }
    }

    public static class Element {

        private static final String TAG = "Element";

        private static final String HOOK_START = "[";
        private static final String REGEX_HOOK_START = "\\[";
        private static final String REGEX_HOOK_END = "\\]";
        private static final String REGEX_DOT_STAR = ".*";
        private static final String REGEX_DOLLAR = "$";

        private static final String TO_STRING_SPACE = " ";
        private static final String TO_STRING_BRACKET_START = "<";
        private static final String TO_STRING_BRACKET_START_SLASH = "</";
        private static final String TO_STRING_BRACKET_END = ">";
        private static final String TO_STRING_BRACKET_END_SLASH = "/>";
        private static final String TO_STRING_NEW_LINE = "\n";
        private static final String TO_STRING_TABULATION = "\t";
        private static final String TO_STRING_DOUBLE_QUOTE = "\"";
        private static final String TO_STRING_EQUALS_QUOTE = "=\"";

        private static final String ATTRIBUTE_EQUALS = "=";
        private static final String ATTRIBUTE_NOT_EQUALS = "!=";
        private static final String ATTRIBUTE_CONTAINS = "~=";
        private static final String ATTRIBUTE_STARTS_WITH = "^=";
        private static final String ATTRIBUTE_ENDS_WITH = "$=";

        public String name;
        protected Element parent;
        private HashMap<String, String> attributes;
        private ArrayList<Element> children;
        protected String value;

        public Element(Element parent, String name) {
            this.parent = parent;
            this.name = name;
        }

        protected void addAttributes(Attributes attributes) {
            int attributesLength = attributes.getLength();
            if (attributesLength > 0) {
                if (this.attributes == null) {
                    this.attributes = new HashMap<>();
                }
                for (int attributeIndex = 0; attributeIndex < attributesLength; attributeIndex++) {
                    this.attributes.put(attributes.getLocalName(attributeIndex),
                            attributes.getValue(attributeIndex));
                }
            }
        }

        protected Element addChild(String childName) {
            if (this.children == null) {
                this.children = new ArrayList<>();
            }
            Element child = new Element(this, childName);
            this.children.add(child);
            return child;
        }

        public Element firstChild() {
            Element child = null;
            if (this.children != null) // if not null, there is at least one child
            {
                child = this.children.get(0);
            }
            return child;
        }

        public String attr(String attrName) {
            String value = null;
            if (this.attributes != null) {
                value = this.attributes.get(attrName);
            }
            if (value == null) {
                value = "";
            }
            return value;
        }

        public String attr(String key, String value) {
            return this.attributes.put(key, value);
        }

        public String val() {
            return this.value;
        }

        public Elements find(String selector) {
            long start = System.currentTimeMillis();
            Elements elements;
            if (TextUtils.isEmpty(selector)) {
                elements = new Elements();
                elements.add(this);
            } else if (selector.contains(Element.TO_STRING_BRACKET_END)) {
                elements = this.selectByArrow(selector);
            } else if (selector.contains(Element.HOOK_START)) {
                elements = this.selectByAttribute(selector, false);
            } else {
                elements = new Elements();
                if (this.children != null) {
                    for (Element element : this.children) {
                        if (element.name.equals(selector)) {
                            elements.add(element);
                        }
                        elements.addAll(element.find(selector));
                    }
                }
            }

            long end = System.currentTimeMillis();
            long duration = end - start;
            if (duration > 1) {
                StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
                StackTraceElement caller = stackTraceElements[3];
                String fullClassName = caller.getClassName();
                Log.w(Element.TAG,
                        "find(" + selector + ") " + elements.size() + " elements in " + duration + "ms called by "
                                + fullClassName.substring(fullClassName.lastIndexOf(".") + 1) + "."
                                + caller.getMethodName());
            }
            return elements;
        }

        protected Elements findChildren(String tagName) {
            Elements elements;
            if (TextUtils.isEmpty(tagName)) {
                elements = new Elements();
                if (this.children != null) {
                    elements.addAll(this.children);
                }
            } else if (tagName.contains(Element.HOOK_START)) {
                elements = this.selectByAttribute(tagName, true);
            } else {
                elements = new Elements();
                if (this.children != null) {
                    for (Element element : this.children) {
                        if (element.name.equals(tagName)) {
                            elements.add(element);
                        }
                    }
                }
            }
            return elements;
        }

        protected boolean hasAttribute(String key, String value) {
            boolean hasAttribute;
            if (value == null) {
                hasAttribute = this.hasAttribute(key);
            } else {
                hasAttribute = (this.attributes != null) && value.equals(this.attributes.get(key));
            }
            return hasAttribute;
        }

        protected boolean matchesAttribute(String key, String valueExp) {
            boolean isMatching = false;
            if (valueExp == null) {
                isMatching = this.hasAttribute(key);
            } else if (this.attributes != null) {
                String value = this.attributes.get(key);
                isMatching = (value != null) && value.matches(valueExp);
            }

            return isMatching;
        }

        protected boolean hasAttribute(String key) {
            return (this.attributes != null) && this.attributes.containsKey(key);
        }

        protected Elements findByAttribute(String key) {
            Elements elements = new Elements();
            if (this.children != null) {
                for (Element element : this.children) {
                    if (element.attr(key) != null) {
                        elements.add(element);
                    }
                }
            }
            return elements;
        }

        protected Elements findByAttribute(String key, String value) {
            Elements elements = new Elements();
            if (value == null) {
                elements = this.findByAttribute(key);
            } else if (this.children != null) {
                for (Element element : this.children) {
                    if (value.equals(element.attr(key))) {
                        elements.add(element);
                    }
                }
            }
            return elements;
        }

        protected Elements selectByArrow(String tagName) {
            String[] tags = tagName.split(Element.TO_STRING_BRACKET_END);
            String currentTag = tags[0].trim();
            Elements elements = this.find(currentTag);

            for (int tagIndex = 1; tagIndex < tags.length; tagIndex++) {
                currentTag = tags[tagIndex].trim();
                elements = elements.findChildren(currentTag);
            }

            return elements;
        }

        protected Elements selectByAttribute(String selector, boolean isChild) {
            Elements elements;
            String[] tmpTagNames = selector.split(Element.REGEX_HOOK_START);
            ArrayList<String> attributeSelectors = new ArrayList<>();

            String currentTag = tmpTagNames[0].trim();
            if (isChild) {
                elements = this.findChildren(currentTag);
            } else {
                elements = this.find(currentTag);
            }

            for (String tmpTag : tmpTagNames) {
                currentTag = tmpTag.split(Element.REGEX_HOOK_END)[0].trim();
                attributeSelectors.add(currentTag);
            }

            Elements finalElements = new Elements();
            for (String attributeSelector : attributeSelectors) {
                String key;
                String valueExp = null;
                boolean isPositive = true;
                if (attributeSelector.contains(Element.ATTRIBUTE_NOT_EQUALS)) {
                    String[] tab = attributeSelector.split(Element.ATTRIBUTE_NOT_EQUALS);
                    key = tab[0];
                    valueExp = tab[1];
                    isPositive = false;
                } else if (attributeSelector.contains(Element.ATTRIBUTE_STARTS_WITH)) {
                    String[] tab = attributeSelector.split(Element.ATTRIBUTE_STARTS_WITH);
                    key = tab[0];
                    valueExp = tab[1] + Element.REGEX_DOT_STAR;
                } else if (attributeSelector.contains(Element.ATTRIBUTE_ENDS_WITH)) {
                    String[] tab = attributeSelector.split(Element.ATTRIBUTE_ENDS_WITH);
                    key = tab[0];
                    valueExp = Element.REGEX_DOT_STAR + tab[1] + Element.REGEX_DOLLAR;
                } else if (attributeSelector.contains(Element.ATTRIBUTE_CONTAINS)) {
                    String[] tab = attributeSelector.split(Element.ATTRIBUTE_CONTAINS);
                    key = tab[0];
                    valueExp = Element.REGEX_DOT_STAR + tab[1] + Element.REGEX_DOT_STAR;
                } else if (attributeSelector.contains(Element.ATTRIBUTE_EQUALS)) {
                    String[] tab = attributeSelector.split(Element.ATTRIBUTE_EQUALS);
                    key = tab[0];
                    valueExp = tab[1];
                } else {
                    key = attributeSelector;
                }

                for (Element element : elements) {
                    if (isPositive) {
                        if (element.matchesAttribute(key, valueExp)) {
                            finalElements.add(element);
                        }
                    } else if (!element.hasAttribute(key, valueExp)) {
                        finalElements.add(element);
                    }
                }
            }
            return finalElements;
        }

        @Override
        public String toString() {
            return this.toString(0);
        }

        protected String toString(int index) {
            int nextRound = index + 1;
            StringBuilder text = new StringBuilder(Element.TO_STRING_BRACKET_START).append(this.name);

            if (this.attributes != null) {
                for (String key : this.attributes.keySet()) {
                    text.append(Element.TO_STRING_SPACE).append(key).append(Element.TO_STRING_EQUALS_QUOTE)
                            .append(this.attributes.get(key)).append(Element.TO_STRING_DOUBLE_QUOTE);
                }
            }
            if ((this.children == null) && TextUtils.isEmpty(this.value)) {
                text.append(Element.TO_STRING_BRACKET_END_SLASH);
            } else {
                text.append(Element.TO_STRING_BRACKET_END);
                if (!TextUtils.isEmpty(this.value)) {
                    text.append(this.value);
                }
                if (this.children != null) {
                    for (Element element : this.children) {
                        text.append(Element.TO_STRING_NEW_LINE);
                        for (int i = 0; i < nextRound; i++) {
                            text.append(Element.TO_STRING_TABULATION);
                        }
                        text.append(element.toString(nextRound));
                    }
                }
                for (int i = 0; i < index; i++) {
                    text.append(Element.TO_STRING_TABULATION);
                }
                text.append(Element.TO_STRING_BRACKET_START_SLASH).append(this.name)
                        .append(Element.TO_STRING_BRACKET_END);
            }
            return text.append(Element.TO_STRING_NEW_LINE).toString();
        }
    }
}