package railo.runtime.text.xml;
import java.io.IOException;
import java.io.OutputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Transformer;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Attr;
import org.w3c.dom.CDATASection;
import org.w3c.dom.CharacterData;
import org.w3c.dom.Comment;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import org.xml.sax.InputSource;
import railo.commons.io.IOUtil;
import railo.commons.io.res.Resource;
import railo.commons.lang.HTMLEntities;
import railo.commons.lang.StringUtil;
import railo.runtime.PageContext;
import railo.runtime.dump.DumpData;
import railo.runtime.dump.DumpProperties;
import railo.runtime.dump.DumpTable;
import railo.runtime.dump.DumpUtil;
import railo.runtime.dump.SimpleDumpData;
import railo.runtime.exp.CasterException;
import railo.runtime.exp.ExpressionException;
import railo.runtime.exp.PageException;
import railo.runtime.exp.XMLException;
import railo.runtime.functions.decision.IsNumeric;
import railo.runtime.functions.list.ListLast;
import railo.runtime.op.Caster;
import railo.runtime.text.xml.struct.XMLStruct;
import railo.runtime.text.xml.struct.XMLStructFactory;
import railo.runtime.type.Collection;
import railo.runtime.type.Collection.Key;
import railo.runtime.type.Struct;
/**
* Cast Objects to XML Objects of different types
*/
public final class XMLCaster {
/**
* casts a value to a XML Text
* @param doc XML Document
* @param o Object to cast
* @return XML Text Object
* @throws PageException
*/
public static Text toText(Document doc, Object o) throws PageException {
if(o instanceof Text) return (Text)o;
else if(o instanceof CharacterData) return doc.createTextNode(((CharacterData)o).getData());
return doc.createTextNode(Caster.toString(o));
}
public static Text toCDATASection(Document doc, Object o) throws PageException {
if(o instanceof CDATASection) return (CDATASection)o;
else if(o instanceof CharacterData) return doc.createCDATASection(((CharacterData)o).getData());
return doc.createCDATASection(Caster.toString(o));
}
/**
* casts a value to a XML Text Array
* @param doc XML Document
* @param o Object to cast
* @return XML Text Array
* @throws PageException
*/
public static Text[] toTextArray(Document doc,Object o) throws PageException {
// Node[]
if(o instanceof Node[]) {
Node[] nodes=(Node[])o;
if(_isAllOfSameType(nodes,Node.TEXT_NODE))return (Text[])nodes;
Text[] textes=new Text[nodes.length];
for(int i=0;i<nodes.length;i++) {
textes[i]=toText(doc,nodes[i]);
}
return textes;
}
// Collection
else if(o instanceof Collection) {
Collection coll=(Collection)o;
Iterator<Object> it = coll.valueIterator();
List<Text> textes=new ArrayList<Text>();
while(it.hasNext()) {
textes.add(toText(doc,it.next()));
}
return textes.toArray(new Text[textes.size()]);
}
// Node Map and List
Node[] nodes=_toNodeArray(doc,o);
if(nodes!=null) return toTextArray(doc,nodes);
// Single Text Node
try {
return new Text[]{toText(doc,o)};
} catch (ExpressionException e) {
throw new XMLException("can't cast Object of type "+Caster.toClassName(o)+" to a XML Text Array");
}
}
/**
* casts a value to a XML Attribute Object
* @param doc XML Document
* @param o Object to cast
* @return XML Comment Object
* @throws PageException
*/
public static Attr toAttr(Document doc, Object o) throws PageException {
if(o instanceof Attr) return (Attr)o;
if(o instanceof Struct && ((Struct)o).size()==1) {
Struct sct=(Struct)o;
Entry<Key, Object> e = sct.entryIterator().next();
Attr attr= doc.createAttribute(e.getKey().getString());
attr.setValue(Caster.toString(e.getValue()));
return attr;
}
throw new XMLException("can't cast Object of type "+Caster.toClassName(o)+" to a XML Attribute");
}
/**
* casts a value to a XML Attr Array
* @param doc XML Document
* @param o Object to cast
* @return XML Attr Array
* @throws PageException
*/
public static Attr[] toAttrArray(Document doc,Object o) throws PageException {
// Node[]
if(o instanceof Node[]) {
Node[] nodes=(Node[])o;
if(_isAllOfSameType(nodes,Node.ATTRIBUTE_NODE))return (Attr[])nodes;
Attr[] attres=new Attr[nodes.length];
for(int i=0;i<nodes.length;i++) {
attres[i]=toAttr(doc,nodes[i]);
}
return attres;
}
// Collection
else if(o instanceof Collection) {
Collection coll=(Collection)o;
Iterator<Entry<Key, Object>> it = coll.entryIterator();
Entry<Key, Object> e;
List<Attr> attres=new ArrayList<Attr>();
Attr attr;
Collection.Key k;
while(it.hasNext()) {
e = it.next();
k=e.getKey();
attr=doc.createAttribute(IsNumeric.call(null,k.getString())?"attribute-"+k.getString():k.getString());
attr.setValue(Caster.toString(e.getValue()));
attres.add(attr);
}
return attres.toArray(new Attr[attres.size()]);
}
// Node Map and List
Node[] nodes=_toNodeArray(doc,o);
if(nodes!=null) return toAttrArray(doc,nodes);
// Single Text Node
try {
return new Attr[]{toAttr(doc,o)};
} catch (ExpressionException e) {
throw new XMLException("can't cast Object of type "+Caster.toClassName(o)+" to a XML Attributes Array");
}
}
/**
* casts a value to a XML Comment Object
* @param doc XML Document
* @param o Object to cast
* @return XML Comment Object
* @throws PageException
*/
public static Comment toComment(Document doc,Object o) throws PageException {
if(o instanceof Comment) return (Comment)o;
else if(o instanceof CharacterData) return doc.createComment(((CharacterData)o).getData());
return doc.createComment(Caster.toString(o));
}
/**
* casts a value to a XML Comment Array
* @param doc XML Document
* @param o Object to cast
* @return XML Comment Array
* @throws PageException
*/
public static Comment[] toCommentArray(Document doc,Object o) throws PageException {
// Node[]
if(o instanceof Node[]) {
Node[] nodes=(Node[])o;
if(_isAllOfSameType(nodes,Node.COMMENT_NODE))return (Comment[])nodes;
Comment[] comments=new Comment[nodes.length];
for(int i=0;i<nodes.length;i++) {
comments[i]=toComment(doc,nodes[i]);
}
return comments;
}
// Collection
else if(o instanceof Collection) {
Collection coll=(Collection)o;
Iterator<Object> it = coll.valueIterator();
List<Comment> comments=new ArrayList<Comment>();
while(it.hasNext()) {
comments.add(toComment(doc,it.next()));
}
return comments.toArray(new Comment[comments.size()]);
}
// Node Map and List
Node[] nodes=_toNodeArray(doc,o);
if(nodes!=null) return toCommentArray(doc,nodes);
// Single Text Node
try {
return new Comment[]{toComment(doc,o)};
} catch (ExpressionException e) {
throw new XMLException("can't cast Object of type "+Caster.toClassName(o)+" to a XML Comment Array");
}
}
/**
* casts a value to a XML Element
* @param doc XML Document
* @param o Object to cast
* @return XML Element Object
* @throws PageException
*/
public static Element toElement(Document doc,Object o) throws PageException {
if(o instanceof Element)return (Element)o;
else if(o instanceof Node)throw new ExpressionException("Object "+Caster.toClassName(o)+" must be a XML Element");
return doc.createElement(Caster.toString(o));
}
/**
* casts a value to a XML Element Array
* @param doc XML Document
* @param o Object to cast
* @return XML Comment Array
* @throws PageException
*/
public static Element[] toElementArray(Document doc,Object o) throws PageException {
// Node[]
if(o instanceof Node[]) {
Node[] nodes=(Node[])o;
if(_isAllOfSameType(nodes,Node.ELEMENT_NODE))return (Element[])nodes;
Element[] elements=new Element[nodes.length];
for(int i=0;i<nodes.length;i++) {
elements[i]=toElement(doc,nodes[i]);
}
return elements;
}
// Collection
else if(o instanceof Collection) {
Collection coll=(Collection)o;
Iterator<Object> it = coll.valueIterator();
List<Element> elements=new ArrayList<Element>();
while(it.hasNext()) {
elements.add(toElement(doc,it.next()));
}
return elements.toArray(new Element[elements.size()]);
}
// Node Map and List
Node[] nodes=_toNodeArray(doc,o);
if(nodes!=null) return toElementArray(doc,nodes);
// Single Text Node
try {
return new Element[]{toElement(doc,o)};
} catch (ExpressionException e) {
throw new XMLException("can't cast Object of type "+Caster.toClassName(o)+" to a XML Element Array");
}
}
/**
* casts a value to a XML Node
* @param doc XML Document
* @param o Object to cast
* @return XML Element Object
* @throws PageException
* @deprecated replaced with toRawNode
*/
public static Node toNode(Object o) throws PageException {
if(o instanceof XMLStruct)return ((XMLStruct)o).toNode();
if(o instanceof Node)return (Node)o;
throw new CasterException(o,"node");
}
/**
* remove railo node wraps (XMLStruct) from node
* @param node
* @return raw node (without wrap)
*/
public static Node toRawNode(Node node) {
if(node instanceof XMLStruct)return ((XMLStruct)node).toNode();
return node;
}
public static Node toNode(Document doc,Object o, boolean clone) throws PageException {
Node n=null;
if(o instanceof XMLStruct)
n = ((XMLStruct)o).toNode();
else if(o instanceof Node)
n = ((Node)o);
if(n!=null)return clone?n.cloneNode(true):n;
String nodeName=Caster.toString(o);
if(nodeName.length()==0)nodeName="Empty";
return doc.createElement(nodeName);
}
/**
* casts a value to a XML Element Array
* @param doc XML Document
* @param o Object to cast
* @return XML Comment Array
* @throws PageException
*/
public static Node[] toNodeArray(Document doc,Object o) throws PageException {
if(o instanceof Node) return new Node[]{(Node)o};
// Node[]
if(o instanceof Node[]) {
return (Node[])o;
}
// Collection
else if(o instanceof Collection) {
Collection coll=(Collection)o;
Iterator<Object> it = coll.valueIterator();
List<Node> nodes=new ArrayList<Node>();
while(it.hasNext()) {
nodes.add(toNode(doc,it.next(),false));
}
return nodes.toArray(new Node[nodes.size()]);
}
// Node Map and List
Node[] nodes=_toNodeArray(doc,o);
if(nodes!=null) return nodes;
// Single Text Node
try {
return new Node[]{toNode(doc,o,false)};
} catch (ExpressionException e) {
throw new XMLException("can't cast Object of type "+Caster.toClassName(o)+" to a XML Node Array");
}
}
/**
* casts a value to a XML Object defined by type parameter
* @param doc XML Document
* @param o Object to cast
* @param type type to cast to
* @return XML Text Object
* @throws PageException
*/
public static Node toNode(Document doc, Object o, short type) throws PageException {
if(Node.TEXT_NODE == type) toText(doc, o);
else if(Node.ATTRIBUTE_NODE == type) toAttr(doc, o);
else if(Node.COMMENT_NODE == type) toComment(doc, o);
else if(Node.ELEMENT_NODE == type) toElement(doc, o);
throw new ExpressionException("invalid node type definition");
}
/**
* casts a value to a XML Object Array defined by type parameter
* @param doc XML Document
* @param o Object to cast
* @param type type to cast to
* @return XML Node Array Object
* @throws PageException
*/
public static Node[] toNodeArray(Document doc, Object o, short type) throws PageException {
if(Node.TEXT_NODE == type) toTextArray(doc, o);
else if(Node.ATTRIBUTE_NODE == type) toAttrArray(doc, o);
else if(Node.COMMENT_NODE == type) toCommentArray(doc, o);
else if(Node.ELEMENT_NODE == type) toElementArray(doc, o);
throw new ExpressionException("invalid node type definition");
}
/* *
* cast a xml node to a String
* @param node
* @return xml node as String
* @throws ExpressionException
* /
public static String toString(Node node) throws ExpressionException {
//Transformer tf;
try {
OutputFormat format = new OutputFormat();
StringWriter writer = new StringWriter();
XMLSerializer serializer = new XMLSerializer(writer, format);
if(node instanceof Element)serializer.serialize((Element)node);
else serializer.serialize(XMLUtil.getDocument(node));
return writer.toString();
} catch (Exception e) {
throw ExpressionException.newInstance(e);
}
}
public static String toString(Node node,String defaultValue) {
//Transformer tf;
try {
OutputFormat format = new OutputFormat();
StringWriter writer = new StringWriter();
XMLSerializer serializer = new XMLSerializer(writer, format);
if(node instanceof Element)serializer.serialize((Element)node);
else serializer.serialize(XMLUtil.getDocument(node));
return writer.toString();
} catch (Exception e) {
return defaultValue;
}
}*/
public static String toHTML(Node node) throws ExpressionException {
if(Node.DOCUMENT_NODE==node.getNodeType())
return toHTML(XMLUtil.getRootElement(node,true));
StringBuffer sb=new StringBuffer();
toHTML(node, sb);
return sb.toString();
}
private static void toHTML(Node node,StringBuffer sb) throws ExpressionException {
short type=node.getNodeType();
if(Node.ELEMENT_NODE==type) {
Element el = (Element) node;
String tagName=el.getTagName();
sb.append('<');
sb.append(tagName);
NamedNodeMap attrs = el.getAttributes();
Attr attr;
int len = attrs.getLength();
for(int i=0;i<len;i++) {
attr=(Attr) attrs.item(i);
sb.append(' ');
sb.append(attr.getName());
sb.append("=\"");
sb.append(attr.getValue());
sb.append('"');
}
NodeList children = el.getChildNodes();
len = children.getLength();
boolean doEndTag=len!=0 || (tagName.length()==4 && (tagName.equalsIgnoreCase("head") || tagName.equalsIgnoreCase("body")));
if(!doEndTag)sb.append(" />");
else sb.append('>');
for(int i=0;i<len;i++) {
toHTML(children.item(i),sb);
}
if(doEndTag) {
sb.append("</");
sb.append(el.getTagName());
sb.append('>');
}
}
else if(node instanceof CharacterData) {
sb.append(HTMLEntities.escapeHTML(node.getNodeValue()));
}
}
/**
* write a xml Dom to a file
* @param node
* @param file
* @throws PageException
*/
public static void writeTo(Node node, Resource file) throws PageException {
OutputStream os=null;
try {
os=IOUtil.toBufferedOutputStream(file.getOutputStream());
writeTo(node, new StreamResult(os),false,false,null,null,null);
}
catch(IOException ioe){
throw Caster.toPageException(ioe);
}
finally {
IOUtil.closeEL(os);
}
}
public static String toString(Node node) throws PageException {
StringWriter sw=new StringWriter();
try {
writeTo(node, new StreamResult(sw),false,false,null,null,null);
}
finally {
IOUtil.closeEL(sw);
}
return sw.getBuffer().toString();
}
public static String toString(Node node,boolean omitXMLDecl, boolean indent) throws PageException {
StringWriter sw=new StringWriter();
try {
writeTo(node, new StreamResult(sw),omitXMLDecl,indent,null,null,null);
}
finally {
IOUtil.closeEL(sw);
}
return sw.getBuffer().toString();
}
public static String toString(Node node,boolean omitXMLDecl,boolean indent,String publicId,String systemId,String encoding) throws PageException {
StringWriter sw=new StringWriter();
try {
writeTo(node, new StreamResult(sw),omitXMLDecl,indent,publicId,systemId,encoding);
}
finally {
IOUtil.closeEL(sw);
}
return sw.getBuffer().toString();
}
public static String toString(NodeList nodes,boolean omitXMLDecl, boolean indent) throws PageException {
StringWriter sw=new StringWriter();
try {
int len = nodes.getLength();
for(int i=0;i<len;i++){
writeTo(nodes.item(i), new StreamResult(sw),omitXMLDecl,indent,null,null,null);
}
}
finally {
IOUtil.closeEL(sw);
}
return sw.getBuffer().toString();
}
public static String toString(Node node,String defaultValue) {
StringWriter sw=new StringWriter();
try {
writeTo(node, new StreamResult(sw),false,false,null,null,null);
}
catch(Throwable t){
return defaultValue;
}
finally {
IOUtil.closeEL(sw);
}
return sw.getBuffer().toString();
}
public static void writeTo(Node node,Result res,boolean omitXMLDecl,boolean indent, String publicId,String systemId,String encoding) throws PageException {
try {
Transformer t = XMLUtil.getTransformerFactory().newTransformer();
t.setOutputProperty(OutputKeys.INDENT,indent?"yes":"no");
t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION,omitXMLDecl?"yes":"no");
//t.setOutputProperty("{http://xml.apache.org/xalan}indent-amount", "2");
// optional properties
if(!StringUtil.isEmpty(publicId,true))t.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC,publicId);
if(!StringUtil.isEmpty(systemId,true))t.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM,systemId);
if(!StringUtil.isEmpty(encoding,true))t.setOutputProperty(OutputKeys.ENCODING,encoding);
t.transform(new DOMSource(node), res);
} catch (Exception e) {
throw Caster.toPageException(e);
}
}
/**
* Casts a XML Node to a HTML Presentation
* @param node
* @param pageContext
* @return html output
*/
public static DumpData toDumpData(Node node, PageContext pageContext, int maxlevel, DumpProperties props) {
if(maxlevel<=0) {
return DumpUtil.MAX_LEVEL_REACHED;
}
maxlevel--;
// Document
if(node instanceof Document) {
DumpTable table = new DumpTable("xml","#cc9999","#ffffff","#000000");
table.setTitle("XML Document");
table.appendRow(1,new SimpleDumpData("XmlComment"),new SimpleDumpData(XMLUtil.getProperty(node,XMLUtil.XMLCOMMENT,null).toString()));
table.appendRow(1,new SimpleDumpData("XmlRoot"), DumpUtil.toDumpData(XMLUtil.getProperty(node,XMLUtil.XMLROOT,null), pageContext,maxlevel,props));
return table;
}
// Element
if(node instanceof Element) {
DumpTable table = new DumpTable("xml","#cc9999","#ffffff","#000000");
table.setTitle("XML Element");
table.appendRow(1,new SimpleDumpData("xmlName"), new SimpleDumpData(XMLUtil.getProperty(node,XMLUtil.XMLNAME,null).toString()));
table.appendRow(1,new SimpleDumpData("XmlNsPrefix"), new SimpleDumpData(XMLUtil.getProperty(node,XMLUtil.XMLNSPREFIX,null).toString()));
table.appendRow(1,new SimpleDumpData("XmlNsURI"), new SimpleDumpData(XMLUtil.getProperty(node,XMLUtil.XMLNSURI,null).toString()));
table.appendRow(1,new SimpleDumpData("XmlText"), DumpUtil.toDumpData(XMLUtil.getProperty(node,XMLUtil.XMLTEXT,null), pageContext,maxlevel,props));
table.appendRow(1,new SimpleDumpData("XmlComment"), new SimpleDumpData(XMLUtil.getProperty(node,XMLUtil.XMLCOMMENT,null).toString()));
table.appendRow(1,new SimpleDumpData("XmlAttributes"),DumpUtil.toDumpData(XMLUtil.getProperty(node,XMLUtil.XMLATTRIBUTES,null), pageContext,maxlevel,props));
table.appendRow(1,new SimpleDumpData("XmlChildren"), DumpUtil.toDumpData(XMLUtil.getProperty(node,XMLUtil.XMLCHILDREN,null), pageContext,maxlevel,props));
return table;
}
// Attr
if(node instanceof Attr) {
DumpTable table = new DumpTable("xml","#cc9999","#ffffff","#000000");
table.setTitle("XML Attr");
table.appendRow(1,new SimpleDumpData("xmlName"), new SimpleDumpData(XMLUtil.getProperty(node,XMLUtil.XMLNAME,null).toString()));
table.appendRow(1,new SimpleDumpData("XmlValue"), DumpUtil.toDumpData(((Attr)node).getValue(), pageContext,maxlevel,props));
table.appendRow(1,new SimpleDumpData("XmlType"), new SimpleDumpData(XMLUtil.getTypeAsString(node,true)));
return table;
}
// Node
DumpTable table = new DumpTable("xml","#cc9999","#ffffff","#000000");
table.setTitle("XML Node ("+ListLast.call(null,node.getClass().getName(),".")+")");
table.appendRow(1,new SimpleDumpData("xmlName"), new SimpleDumpData(XMLUtil.getProperty(node,XMLUtil.XMLNAME,null).toString()));
table.appendRow(1,new SimpleDumpData("XmlNsPrefix"), new SimpleDumpData(XMLUtil.getProperty(node,XMLUtil.XMLNSPREFIX,null).toString()));
table.appendRow(1,new SimpleDumpData("XmlNsURI"), new SimpleDumpData(XMLUtil.getProperty(node,XMLUtil.XMLNSURI,null).toString()));
table.appendRow(1,new SimpleDumpData("XmlText"), DumpUtil.toDumpData(XMLUtil.getProperty(node,XMLUtil.XMLTEXT,null), pageContext,maxlevel,props));
table.appendRow(1,new SimpleDumpData("XmlComment"), new SimpleDumpData(XMLUtil.getProperty(node,XMLUtil.XMLCOMMENT,null).toString()));
table.appendRow(1,new SimpleDumpData("XmlAttributes"),DumpUtil.toDumpData(XMLUtil.getProperty(node,XMLUtil.XMLATTRIBUTES,null), pageContext,maxlevel,props));
table.appendRow(1,new SimpleDumpData("XmlChildren"), DumpUtil.toDumpData(XMLUtil.getProperty(node,XMLUtil.XMLCHILDREN,null), pageContext,maxlevel,props));
table.appendRow(1,new SimpleDumpData("XmlType"), new SimpleDumpData(XMLUtil.getTypeAsString(node,true)));
return table;
}
/**
* casts a value to a XML named Node Map
* @param doc XML Document
* @param o Object to cast
* @return XML named Node Map Object
*/
private static Node[] _toNodeArray(Document doc,Object o) {
if(o instanceof Node) return new Node[]{(Node)o};
// Node[]
if(o instanceof Node[]) return ((Node[])o);
// NamedNodeMap
else if(o instanceof NamedNodeMap) {
NamedNodeMap map=(NamedNodeMap)o;
int len=map.getLength();
Node[] nodes=new Node[len];
for(int i=0;i<len;i++) {
nodes[i]=map.item(i);
}
return nodes;
}
// XMLAttributes
else if(o instanceof XMLAttributes) {
return _toNodeArray(doc, ((XMLAttributes)o).toNamedNodeMap());
}
// NodeList
else if(o instanceof NodeList) {
NodeList list=(NodeList)o;
int len=list.getLength();
Node[] nodes=new Node[len];
for(int i=0;i<nodes.length;i++) {
nodes[i]=list.item(i);
}
return nodes;
}
return null;
}
/**
* Check if all Node are of the type defnined by para,meter
* @param nodes nodes to check
* @param type to compare
* @return are all of the same type
*/
private static boolean _isAllOfSameType(Node[] nodes, short type) {
for(int i=0;i<nodes.length;i++) {
if(nodes[i].getNodeType()!=type)return false;
}
return true;
}
/**
* creates a XMLCollection Object from a Node
* @param node
* @param caseSensitive
* @return xmlstruct from node
*/
public static XMLStruct toXMLStruct(Node node, boolean caseSensitive) {
return XMLStructFactory.newInstance(node,caseSensitive);
}
public static Element toRawElement(Object value, Element defaultValue) {
if(value instanceof Node) {
Node node=XMLCaster.toRawNode((Node) value);
if(node instanceof Document) return ((Document) node).getDocumentElement();
if(node instanceof Element) return (Element) node;
return defaultValue;
}
try {
return XMLUtil.parse(new InputSource(new StringReader(Caster.toString(value))),null,false).getDocumentElement();
} catch (Throwable t) {
return defaultValue;
}
}
}