/*
*Copyright [2016] [Nabarun Mondal]
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.noga.njexl.lang.extension.dataaccess;
import com.noga.njexl.lang.JexlArithmetic;
import com.noga.njexl.lang.ObjectContext;
import com.noga.njexl.lang.extension.TypeUtility;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.lang.reflect.Array;
import java.nio.file.Files;
import java.util.*;
/**
* Created by noga on 02/04/15.
*/
public class XmlMap {
public static String toJSON(Object obj){
if ( obj == null ) return "" ;
if ( obj instanceof Map ){
StringBuffer sBuf = new StringBuffer( "{" );
Set set = ((Map) obj).entrySet() ;
if ( !set.isEmpty() ){
Iterator iterator = set.iterator() ;
Object entry = iterator.next() ;
String key = "\"" + String.valueOf ( ((Map.Entry)entry).getKey() ) + "\"" ;
String value = toJSON( ((Map.Entry)entry).getValue() );
sBuf.append(key).append(" : ").append(value);
while ( iterator.hasNext() ){
entry = iterator.next() ;
key = "\"" + String.valueOf ( ((Map.Entry)entry).getKey() ) + "\"" ;
value = toJSON( ((Map.Entry)entry).getValue() );
sBuf.append(",").append(key).append(" : ").append(value);
}
}
sBuf.append("}");
return sBuf.toString();
}
if ( obj instanceof List ){
StringBuffer sBuf = new StringBuffer( "[" );
List l = ((List) obj) ;
if ( !l.isEmpty() ){
Iterator iterator = l.iterator() ;
Object entry = iterator.next() ;
String value = toJSON( entry );
sBuf.append(value);
while ( iterator.hasNext() ){
entry = iterator.next() ;
value = toJSON( entry) ;
sBuf.append(",").append(value);
}
}
sBuf.append("]");
return sBuf.toString();
}
if ( obj.getClass().isArray() ){
StringBuffer sBuf = new StringBuffer( "[" );
int size = Array.getLength( obj ) ;
int i = 0 ;
if ( size != 0 ){
Object entry = Array.get(obj,i++) ;
String value = toJSON( entry );
sBuf.append(value);
while ( i < size ){
entry = Array.get(obj,i++) ;
value = toJSON( entry) ;
sBuf.append(",").append(value);
}
}
sBuf.append("]");
return sBuf.toString();
}
if ( obj instanceof CharSequence ){
// TODO : must escape double quotes ...
return "\"" + String.valueOf(obj) + "\"" ;
}
return String.valueOf(obj);
}
public static String getFirstLevelTextContent(Node node) {
NodeList list = node.getChildNodes();
StringBuilder textContent = new StringBuilder();
for (int i = 0; i < list.getLength(); ++i) {
Node child = list.item(i);
if (child.getNodeType() == Node.TEXT_NODE)
textContent.append(child.getTextContent());
}
return textContent.toString();
}
public static String jsonDict(Map map) {
String tmp = "";
for (Object k : map.keySet()) {
String prop = k.toString();
String value = map.get(k).toString();
value = value.replaceAll("\n", "\\\\n");
value = value.replaceAll("\r", "\\\\r");
String pair = String.format("\"%s\" : \"%s\"", prop, value);
tmp = tmp + pair + ",";
}
if (!tmp.isEmpty()) {
int l = tmp.length();
tmp = tmp.substring(0, l - 1);
}
tmp = "{" + tmp + "}";
return tmp;
}
public static String jsonProp(String prop, String value) {
value = value.replaceAll("\n", "\\\\n");
value = value.replaceAll("\r", "\\\\r");
String pair = String.format("\"%s\" : \"%s\"", prop, value);
return pair;
}
public static class XmlElement {
public static final XPath X_PATH = XPathFactory.newInstance().newXPath();
// the dom root
public XmlMap root;
// the attributes
public HashMap<String, String> attr;
// the name
public String name;
// the namespace
public String ns;
// the prefix
public String prefix;
// the elements
public ArrayList<XmlElement> children;
// the text
public String text;
// parent node of me
public XmlElement parent;
// this is actually me
public Node node;
public XmlElement(Node n, XmlElement p) {
node = n;
parent = p;
name = n.getNodeName();
ns = "";
if (n.getNamespaceURI() != null) {
ns = n.getNamespaceURI();
}
prefix = "";
if (n.getPrefix() != null) {
prefix = n.getPrefix();
}
attr = new HashMap<>();
if (n.hasAttributes()) {
NamedNodeMap map = n.getAttributes();
int len = map.getLength();
for (int i = 0; i < len; i++) {
Node a = map.item(i);
String name = a.getNodeName();
String value = a.getTextContent();
attr.put(name, value);
}
}
text = getFirstLevelTextContent(node);
children = new ArrayList<>();
}
protected void populate(XmlMap root) {
if (parent != null) {
parent.children.add(this);
}
this.root = root;
this.root.nodes.put(node, this);
NodeList nodeList = node.getChildNodes();
int count = nodeList.getLength();
for (int i = 0; i < count; i++) {
Node c = nodeList.item(i);
if (c.getNodeType() == Node.ELEMENT_NODE) {
XmlElement child = new XmlElement(c, this);
child.populate(root);
}
}
}
@Override
public String toString() {
return json();
}
public NodeList nodes(String expression) throws Exception {
NodeList nodeList = (NodeList) X_PATH.compile(expression).evaluate(
this.node, XPathConstants.NODESET);
return nodeList;
}
public List<XmlElement> elements(String expression) throws Exception {
NodeList nodeList = nodes(expression);
ArrayList<XmlElement> elements = new ArrayList<>();
int size = nodeList.getLength();
for (int i = 0; i < size; i++) {
Node node = nodeList.item(i);
XmlElement e = root.nodes.get(node);
elements.add(e);
}
return elements;
}
public Node node(String expression) throws Exception {
Node node = (Node) X_PATH.compile(expression).evaluate(this.node, XPathConstants.NODE);
return node;
}
public XmlElement element(String expression) throws Exception {
Node node = node(expression);
return root.nodes.get(node);
}
public boolean exists(String expression) throws Exception {
boolean exists = (boolean)X_PATH.compile("boolean(" + expression + ")").evaluate(this.node,
XPathConstants.BOOLEAN);
return exists ;
}
public String xpath(String expression) throws Exception {
return xpath(expression,null);
}
public String xpath(String expression, String defaultValue) throws Exception {
boolean exists = exists(expression);
if ( ! exists ) return defaultValue ;
String val = (String) X_PATH.compile(expression).evaluate(this.node, XPathConstants.STRING);
return val;
}
/**
* Converts this to JSON String
*
* @return json string for this element
*/
public String json() {
StringBuilder builder = new StringBuilder();
builder.append("{ ");
String prop = "";
prop = jsonProp("name", name);
builder.append(prop).append(" , ");
prop = jsonProp("ns", ns);
builder.append(prop).append(", ");
prop = jsonProp("prefix", prefix);
builder.append(prop).append(", ");
prop = jsonProp("text", text);
builder.append(prop).append(", ");
// now the attributes
String attrStrings = jsonDict(attr);
builder.append("\"attr\" : ").append(attrStrings).append(", ");
// now the children
builder.append("\"children\" : [ ");
if (!children.isEmpty()) {
int i = 0;
String ej = children.get(i).json();
builder.append(ej);
i++;
for (; i < children.size(); i++) {
builder.append(",\n");
ej = children.get(i).json();
builder.append(ej);
}
}
builder.append(" ]");
builder.append(" }");
return builder.toString();
}
}
public final XmlElement root;
public final Document doc;
public final HashMap<Node, XmlElement> nodes;
public static XmlMap file2xml(String... args) throws Exception {
if ( args.length == 0 ) return null;
byte[] arr = Files.readAllBytes(new File(args[0]).toPath());
String text = new String(arr);
String encoding = "UTF-8" ;
if ( args.length > 1 ){
encoding = args[1];
}
return string2xml(text, encoding);
}
public static String dict2xml(Map m){
StringBuffer buf = new StringBuffer();
for ( Object key : m.keySet() ){
String elemName = String.valueOf(key) ;
buf.append("<").append(elemName).append(">");
String val = obj2xml( m.get(key) );
buf.append(val);
buf.append("</").append(elemName).append(">");
}
return buf.toString();
}
public static String array2xml(Object array){
int len = Array.getLength(array);
StringBuffer buf = new StringBuffer();
for ( int i = 0 ; i < len; i++ ){
Object o = Array.get(array,i);
buf.append("<i>") ; // item
String s = obj2xml(o);
buf.append(s);
buf.append("</i>") ; // item
}
return buf.toString();
}
public static String obj2xml(Object o) {
if ( o == null ) return "" ;
if ( o instanceof Map ){
return dict2xml((Map)o);
}else if ( o.getClass().isArray() ){
return array2xml(o) ;
}else if ( o instanceof String ){
return (String)o;
}
else if ( o instanceof Number ){
return String.valueOf(o);
}
else if (JexlArithmetic.isTimeLike(o)){
return String.valueOf(o);
}
try {
o = TypeUtility.makeDict(o);
return dict2xml((Map)o);
}catch (Exception e){
return String.valueOf(o);
}
}
public static String xml(Object o){
String val = obj2xml(o);
return "<root>" + val + "</root>" ;
}
public static XmlMap string2xml(String... args) throws Exception {
if ( args.length == 0 ) return null;
// this is funny ...
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
String encoding = "UTF-8" ;
if ( args.length > 1 ){
encoding = args[1];
}
Document doc = db.parse(new ByteArrayInputStream(args[0].getBytes(encoding)));
doc.getDocumentElement().normalize();
return new XmlMap(doc);
}
public XmlMap(Document doc) {
this.doc = doc;
root = new XmlElement(doc.getDocumentElement(), null);
nodes = new HashMap<>();
root.populate(this);
}
public XmlElement element(String expression) throws Exception {
return root.element(expression);
}
public List<XmlElement> elements(String expression) throws Exception {
return root.elements(expression);
}
public String xpath(String expression) throws Exception {
return xpath(expression,null);
}
public String xpath(String expression, String defaultValue) throws Exception {
return root.xpath(expression,defaultValue);
}
public boolean exists(String expression) throws Exception {
return root.exists(expression);
}
/**
* Converts this to JSON String
*
* @return json string for this xml
*/
public String json() {
return root.json();
}
@Override
public String toString() {
return json();
}
}