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; } } }