package railo.transformer.library.tag; import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.net.URISyntaxException; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Iterator; import java.util.Map; import java.util.Set; import org.xml.sax.Attributes; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; import org.xml.sax.helpers.DefaultHandler; import railo.commons.collection.MapFactory; import railo.commons.io.IOUtil; import railo.commons.io.SystemUtil; import railo.commons.io.res.Resource; import railo.commons.io.res.filter.ExtensionResourceFilter; import railo.commons.io.res.util.ResourceUtil; import railo.runtime.op.Caster; import railo.runtime.text.xml.XMLUtil; import railo.runtime.type.util.ArrayUtil; /** * Die Klasse TagLibFactory liest die XML Repraesentation einer TLD ein * und laedt diese in eine Objektstruktur. * Sie tut dieses mithilfe eines Sax Parser. * Die Klasse kann sowohl einzelne Files oder gar ganze Verzeichnisse von TLD laden. */ public final class TagLibFactory extends DefaultHandler { /** * Standart Sax Parser */ public final static String DEFAULT_SAX_PARSER="org.apache.xerces.parsers.SAXParser"; /** * Field <code>TYPE_CFML</code> */ public final static short TYPE_CFML=0; /** * Field <code>TYPE_JSP</code> */ public final static short TYPE_JSP=1; //private short type=TYPE_CFML; private XMLReader xmlReader; private static Map<String,TagLib> hashLib=MapFactory.<String,TagLib>getConcurrentMap(); private static TagLib systemTLD; private TagLib lib=new TagLib(); private TagLibTag tag; private boolean insideTag=false; private boolean insideScript=false; private TagLibTagAttr att; private boolean insideAtt=false; private String inside; private StringBuffer content=new StringBuffer(); private TagLibTagScript script; // System default tld private final static String TLD_1_0= "/resource/tld/web-cfmtaglibrary_1_0"; /** * Privater Konstruktor, der als Eingabe die TLD als File Objekt erhaelt. * @param saxParser String Klassenpfad zum Sax Parser. * @param file File Objekt auf die TLD. * @throws TagLibException * @throws IOException */ private TagLibFactory(String saxParser,Resource res) throws TagLibException { Reader r=null; try { InputSource is=new InputSource(r=IOUtil.getReader(res.getInputStream(), (Charset)null)); is.setSystemId(res.getPath()); init(saxParser,is); } catch (IOException e) { throw new TagLibException(e); } finally { IOUtil.closeEL(r); } } /** * Privater Konstruktor, der als Eingabe die TLD als File Objekt erhaelt. * @param saxParser String Klassenpfad zum Sax Parser. * @param file File Objekt auf die TLD. * @throws TagLibException */ private TagLibFactory(String saxParser,InputStream stream) throws TagLibException { try { InputSource is=new InputSource(IOUtil.getReader(stream, SystemUtil.getCharset())); //is.setSystemId(file.toString()); init(saxParser,is); } catch (IOException e) { throw new TagLibException(e); } } /** * Privater Konstruktor nur mit Sax Parser Definition, liest Default TLD vom System ein. * @param saxParser String Klassenpfad zum Sax Parser. * @throws TagLibException */ private TagLibFactory(String saxParser) throws TagLibException { InputSource is=new InputSource(this.getClass().getResourceAsStream(TLD_1_0) ); init(saxParser,is); lib.setIsCore(true); } /** * Generelle Initialisierungsmetode der Konstruktoren. * @param saxParser String Klassenpfad zum Sax Parser. * @param is InputStream auf die TLD. * @throws TagLibException */ private void init(String saxParser,InputSource is) throws TagLibException { //print.dumpStack(); try { xmlReader=XMLUtil.createXMLReader(saxParser); xmlReader.setContentHandler(this); xmlReader.setErrorHandler(this); xmlReader.setEntityResolver(new TagLibEntityResolver()); xmlReader.parse(is); } catch (IOException e) { //String fileName=is.getSystemId(); //String message="IOException: "; //if(fileName!=null) message+="In File ["+fileName+"], "; throw new TagLibException(e); } catch (SAXException e) { e.printStackTrace(); //String fileName=is.getSystemId(); //String message="SAXException: "; //if(fileName!=null) message+="In File ["+fileName+"], "; throw new TagLibException(e); } } /** * Geerbte Methode von org.xml.sax.ContentHandler, * wird bei durchparsen des XML, beim Auftreten eines Start-Tag aufgerufen. * * @see org.xml.sax.ContentHandler#startElement(String, String, String, Attributes) */ public void startElement(String uri, String name, String qName, Attributes atts) { inside=qName; if(qName.equals("tag")) startTag(); else if(qName.equals("attribute")) startAtt(); else if(qName.equals("script")) startScript(); } /** * Geerbte Methode von org.xml.sax.ContentHandler, * wird bei durchparsen des XML, beim auftreten eines End-Tag aufgerufen. * * @see org.xml.sax.ContentHandler#endElement(String, String, String) */ public void endElement(String uri, String name, String qName) { setContent(content.toString().trim()); content=new StringBuffer(); inside=""; /* if(tag!=null && tag.getName().equalsIgnoreCase("input")) { print.ln(tag.getName()+"-"+att.getName()+":"+inside+"-"+insideTag+"-"+insideAtt); } */ if(qName.equals("tag")) endTag(); else if(qName.equals("attribute")) endAtt(); else if(qName.equals("script")) endScript(); } /** * Geerbte Methode von org.xml.sax.ContentHandler, * wird bei durchparsen des XML, zum einlesen des Content eines Body Element aufgerufen. * * @see org.xml.sax.ContentHandler#characters(char[], int, int) */ public void characters (char ch[], int start, int length) { content.append(new String(ch,start,length)); } private void setContent(String value) { if(insideTag) { // Att Args if(insideAtt) { // description? // Name if(inside.equals("name")) att.setName(value); // Values if(inside.equals("values")) att.setValues(value); // Value Delimiter if(inside.equals("value-delimiter")) att.setValueDelimiter(value); // Required else if(inside.equals("required")) att.setRequired(Caster.toBooleanValue(value,false)); // Rtexprvalue else if(inside.equals("rtexprvalue")) att.setRtexpr(Caster.toBooleanValue(value,false)); // Type else if(inside.equals("type")) att.setType(value); // Default-Value else if(inside.equals("default-value")) att.setDefaultValue(value); // status else if(inside.equals("status")) att.setStatus(toStatus(value)); // Description else if(inside.equals("description")) att.setDescription(value); // No-Name else if(inside.equals("noname")) att.setNoname(Caster.toBooleanValue(value,false)); // default else if(inside.equals("default")) att.isDefault(Caster.toBooleanValue(value,false)); else if(inside.equals("script-support")) att.setScriptSupport(value); } else if(insideScript) { // type if(inside.equals("type")) script.setType(value); if(inside.equals("rtexprvalue")) script.setRtexpr(Caster.toBooleanValue(value,false)); if(inside.equals("context")) script.setContext(value); } // Tag Args else { // TODO TEI-class // Name if(inside.equals("name")) {tag.setName(value);} // TAG - Class else if(inside.equals("tag-class")) tag.setTagClass(value); else if(inside.equals("tagclass")) tag.setTagClass(value); // status else if(inside.equals("status")) tag.setStatus(toStatus(value)); // TAG - description else if(inside.equals("description")) tag.setDescription(value); // TTE - Class else if(inside.equals("tte-class")) tag.setTteClass(value); // TTT - Class else if(inside.equals("ttt-class")) tag.setTttClass(value); // TDBT - Class else if(inside.equals("tdbt-class")) tag.setTdbtClass(value); // TDBT - Class else if(inside.equals("att-class")) tag.setAttributeEvaluatorClassName(value); // Body Content else if(inside.equals("body-content") || inside.equals("bodycontent")) { tag.setBodyContent(value); } //allow-removing-literal else if(inside.equals("allow-removing-literal")) { tag.setAllowRemovingLiteral(Caster.toBooleanValue(value,false)); } else if(inside.equals("att-default-value")) tag.setAttributeDefaultValue(value); // Handle Exceptions else if(inside.equals("handle-exception")) { tag.setHandleExceptions(Caster.toBooleanValue(value,false)); } // Appendix else if(inside.equals("appendix")) { tag.setAppendix(Caster.toBooleanValue(value,false)); } // Body rtexprvalue else if(inside.equals("body-rtexprvalue")) { tag.setParseBody(Caster.toBooleanValue(value,false)); } // Att - min else if(inside.equals("attribute-min")) tag.setMin(Integer.parseInt(value)); // Att - max else if(inside.equals("attribute-max")) tag.setMax(Integer.parseInt(value)); // Att Type else if(inside.equals("attribute-type")) { int type=TagLibTag.ATTRIBUTE_TYPE_FIXED; if(value.toLowerCase().equals("fix"))type=TagLibTag.ATTRIBUTE_TYPE_FIXED; else if(value.toLowerCase().equals("fixed"))type=TagLibTag.ATTRIBUTE_TYPE_FIXED; else if(value.toLowerCase().equals("dynamic"))type=TagLibTag.ATTRIBUTE_TYPE_DYNAMIC; else if(value.toLowerCase().equals("noname"))type=TagLibTag.ATTRIBUTE_TYPE_NONAME; else if(value.toLowerCase().equals("mixed"))type=TagLibTag.ATTRIBUTE_TYPE_MIXED; else if(value.toLowerCase().equals("fulldynamic"))type=TagLibTag.ATTRIBUTE_TYPE_DYNAMIC;// deprecated tag.setAttributeType(type); } } } // Tag Lib else { // TagLib Typ if(inside.equals("jspversion")) { //type=TYPE_JSP; lib.setType("jsp"); } else if(inside.equals("cfml-version")) { //type=TYPE_CFML; lib.setType("cfml"); } // EL Class else if(inside.equals("el-class")) lib.setELClass(value); // Name-Space else if(inside.equals("name-space")) lib.setNameSpace(value); // Name Space Sep else if(inside.equals("name-space-separator")) lib.setNameSpaceSeperator(value); // short-name else if(inside.equals("short-name")) lib.setShortName(value); else if(inside.equals("shortname")) lib.setShortName(value); // display-name else if(inside.equals("display-name")) lib.setDisplayName(value); else if(inside.equals("displayname")) lib.setDisplayName(value); // ignore-unknow-tags else if(inside.equals("ignore-unknow-tags")) lib.setIgnoreUnknowTags(Caster.toBooleanValue(value,false)); else if(inside.equals("uri")) { try { lib.setUri(value); } catch (URISyntaxException e) {} } else if(inside.equals("description")) lib.setDescription(value); } } /** * Wird jedesmal wenn das Tag tag beginnt aufgerufen, um intern in einen anderen Zustand zu gelangen. */ private void startTag() { tag=new TagLibTag(lib); insideTag=true; } /** * Wird jedesmal wenn das Tag tag endet aufgerufen, um intern in einen anderen Zustand zu gelangen. */ private void endTag() { lib.setTag(tag); insideTag=false; } private void startScript() { script=new TagLibTagScript(tag); insideScript=true; } /** * Wird jedesmal wenn das Tag tag endet aufgerufen, um intern in einen anderen Zustand zu gelangen. */ private void endScript() { tag.setScript(script); insideScript=false; } /** * Wird jedesmal wenn das Tag attribute beginnt aufgerufen, um intern in einen anderen Zustand zu gelangen. */ private void startAtt() { att=new TagLibTagAttr(tag); insideAtt=true; } /** * Wird jedesmal wenn das Tag tag endet aufgerufen, um intern in einen anderen Zustand zu gelangen. */ private void endAtt() { tag.setAttribute(att); insideAtt=false; } /** * Gibt die interne TagLib zurueck. * @return Interne Repraesentation der zu erstellenden TagLib. */ private TagLib getLib() { return lib; } /** * TagLib werden innerhalb der Factory in einer HashMap gecacht, * so das diese einmalig von der Factory geladen werden. * Diese Methode gibt eine gecachte TagLib anhand dessen key zurueck, * falls diese noch nicht im Cache existiert, gibt die Methode null zurueck. * * @param key Absoluter Filepfad zur TLD. * @return TagLib */ private static TagLib getHashLib(String key) { return hashLib.get(key); } /** * Laedt mehrere TagLib's die innerhalb eines Verzeichnisses liegen. * @param dir Verzeichnis im dem die TagLib's liegen. * @return TagLib's als Array * @throws TagLibException */ public static TagLib[] loadFromDirectory(Resource dir) throws TagLibException { return loadFromDirectory(dir,DEFAULT_SAX_PARSER); } /** * Laedt mehrere TagLib's die innerhalb eines Verzeichnisses liegen. * @param dir Verzeichnis im dem die TagLib's liegen. * @param saxParser Definition des Sax Parser mit dem die TagLib's eingelesen werden sollen. * @return TagLib's als Array * @throws TagLibException */ public static TagLib[] loadFromDirectory(Resource dir,String saxParser) throws TagLibException { if(!dir.isDirectory())return new TagLib[0]; ArrayList<TagLib> arr=new ArrayList<TagLib>(); Resource[] files=dir.listResources(new ExtensionResourceFilter("tld")); for(int i=0;i<files.length;i++) { if(files[i].isFile()) arr.add(TagLibFactory.loadFromFile(files[i],saxParser)); } return arr.toArray(new TagLib[arr.size()]); } /** * Laedt eine einzelne TagLib. * @param file TLD die geladen werden soll. * @return TagLib * @throws TagLibException */ public static TagLib loadFromFile(Resource res) throws TagLibException { return loadFromFile(res,DEFAULT_SAX_PARSER); } /** * Laedt eine einzelne TagLib. * @param file TLD die geladen werden soll. * @return TagLib * @throws TagLibException */ public static TagLib loadFromStream(InputStream is) throws TagLibException { return loadFromStream(is,DEFAULT_SAX_PARSER); } /** * Laedt eine einzelne TagLib. * @param file TLD die geladen werden soll. * @param saxParser Definition des Sax Parser mit dem die TagLib eingelsesen werden soll. * @return TagLib * @throws TagLibException */ public static TagLib loadFromFile(Resource res,String saxParser) throws TagLibException { // Read in XML TagLib lib=TagLibFactory.getHashLib(ResourceUtil.getCanonicalPathEL(res)); if(lib==null) { lib=new TagLibFactory(saxParser,res).getLib(); TagLibFactory.hashLib.put(ResourceUtil.getCanonicalPathEL(res),lib); } lib.setSource(res.toString()); return lib; } /** * Laedt eine einzelne TagLib. * @param file TLD die geladen werden soll. * @param saxParser Definition des Sax Parser mit dem die TagLib eingelsesen werden soll. * @return TagLib * @throws TagLibException */ public static TagLib loadFromStream(InputStream is,String saxParser) throws TagLibException { return new TagLibFactory(saxParser,is).getLib(); } /** * Laedt die Systeminterne TLD. * @return FunctionLib * @throws TagLibException */ public static TagLib loadFromSystem() throws TagLibException { return loadFromSystem(DEFAULT_SAX_PARSER); } /** * Laedt die Systeminterne TLD. * @param saxParser Definition des Sax Parser mit dem die FunctionLib eingelsesen werden soll. * @return FunctionLib * @throws TagLibException */ public static TagLib loadFromSystem(String saxParser) throws TagLibException { if(systemTLD==null) systemTLD=new TagLibFactory(saxParser).getLib(); return systemTLD; } public static TagLib[] loadFrom(Resource res) throws TagLibException { if(res.isDirectory())return loadFromDirectory(res); if(res.isFile()) return new TagLib[]{loadFromFile(res)}; throw new TagLibException("can not load tag library descriptor from ["+res+"]"); } /** * return one FunctionLib contain content of all given Function Libs * @param tlds * @return combined function lib */ public static TagLib combineTLDs(TagLib[] tlds){ TagLib tl = new TagLib(); if(ArrayUtil.isEmpty(tlds)) return tl; setAttributes(tlds[0],tl); // add functions for(int i=0;i<tlds.length;i++){ copyTags(tlds[i],tl); } return tl; } public static TagLib combineTLDs(Set tlds){ TagLib newTL = new TagLib(),tmp; if(tlds.size()==0) return newTL ; Iterator it = tlds.iterator(); int count=0; while(it.hasNext()){ tmp=(TagLib) it.next(); if(count++==0) setAttributes(tmp,newTL); copyTags(tmp,newTL); } return newTL; } private static void setAttributes(TagLib extTL, TagLib newTL) { newTL.setDescription(extTL.getDescription()); newTL.setDisplayName(extTL.getDisplayName()); newTL.setELClass(extTL.getELClass()); newTL.setIsCore(extTL.isCore()); newTL.setNameSpace(extTL.getNameSpace()); newTL.setNameSpaceSeperator(extTL.getNameSpaceSeparator()); newTL.setShortName(extTL.getShortName()); newTL.setSource(extTL.getSource()); newTL.setType(extTL.getType()); newTL.setUri(extTL.getUri()); } private static void copyTags(TagLib extTL, TagLib newTL) { Iterator it = extTL.getTags().entrySet().iterator(); TagLibTag tlt; while(it.hasNext()){ tlt= (TagLibTag) ((Map.Entry)it.next()).getValue(); // TODO function must be duplicated because it gets a new FunctionLib assigned newTL.setTag(tlt); } } public static short toStatus(String value) { value=value.trim().toLowerCase(); if("deprecated".equals(value)) return TagLib.STATUS_DEPRECATED; if("dep".equals(value)) return TagLib.STATUS_DEPRECATED; if("unimplemented".equals(value)) return TagLib.STATUS_UNIMPLEMENTED; if("unimplemeted".equals(value)) return TagLib.STATUS_UNIMPLEMENTED; if("notimplemented".equals(value)) return TagLib.STATUS_UNIMPLEMENTED; if("not-implemented".equals(value)) return TagLib.STATUS_UNIMPLEMENTED; if("hidden".equals(value)) return TagLib.STATUS_HIDDEN; return TagLib.STATUS_IMPLEMENTED; } public static String toStatus(short value) { switch(value){ case TagLib.STATUS_DEPRECATED: return "deprecated"; case TagLib.STATUS_UNIMPLEMENTED: return "unimplemeted"; case TagLib.STATUS_HIDDEN: return "hidden"; } return "implemeted"; } }