/** * Copyright (c) 2014, the Railo Company Ltd. * Copyright (c) 2015, Lucee Assosication Switzerland * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see <http://www.gnu.org/licenses/>. * */ package lucee.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 lucee.commons.collection.MapFactory; import lucee.commons.io.IOUtil; import lucee.commons.io.SystemUtil; import lucee.commons.io.res.Resource; import lucee.commons.io.res.filter.ExtensionResourceFilter; import lucee.loader.engine.CFMLEngine; import lucee.runtime.config.Identification; import lucee.runtime.op.Caster; import lucee.runtime.text.xml.XMLUtil; import lucee.runtime.type.util.ArrayUtil; import lucee.runtime.type.util.ListUtil; import lucee.transformer.cfml.evaluator.ChildEvaluator; import lucee.transformer.cfml.evaluator.TagEvaluator; import lucee.transformer.library.function.FunctionLibFactory; 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; /** * 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[] systemTLDs=new TagLib[2]; private final TagLib lib; private TagLibTag tag; private boolean insideTag=false; private boolean insideScript=false; //private boolean insideBundle=false; private TagLibTagAttr att; private boolean insideAtt=false; private String inside; private StringBuffer content=new StringBuffer(); private TagLibTagScript script; private Attributes attributes; private final Identification id; // System default tld //private final static String TLD_1_0= "/resource/tld/web-cfmtaglibrary_1_0"; private final static String TLD_BASE = "/resource/tld/core-base.tld"; private final static String TLD_CFML = "/resource/tld/core-cfml.tld"; private final static String TLD_LUCEE= "/resource/tld/core-lucee.tld"; /** * 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, TagLib lib,Resource res, Identification id) throws TagLibException { this.id=id; this.lib=lib==null?new TagLib():lib; 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, TagLib lib,InputStream stream, Identification id) throws TagLibException { this.id=id; this.lib=lib==null?new TagLib():lib; 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, TagLib lib, String systemTLD, Identification id) throws TagLibException { this.id=id; this.lib=lib==null?new TagLib():lib; InputSource is=new InputSource(this.getClass().getResourceAsStream(systemTLD) ); init(saxParser,is); this.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) */ @Override public void startElement(String uri, String name, String qName, Attributes attributes) { inside=qName; this.attributes=attributes; 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) */ @Override 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) */ @Override 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); // alias else if(inside.equals("alias")) att.setAlias(value); // Values else if(inside.equals("values")) att.setValues(value); // Value Delimiter else if(inside.equals("value-delimiter")) att.setValueDelimiter(value); else if(inside.equals("introduced")) att.setIntroduced(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); // undefined-Value else if(inside.equals("undefined-value")) att.setUndefinedValue(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.setTagClassDefinition(value,id,attributes); else if(inside.equals("tagclass")) tag.setTagClassDefinition(value,id,attributes); // status else if(inside.equals("status")) tag.setStatus(toStatus(value)); // TAG - description else if(inside.equals("description")) tag.setDescription(value); else if(inside.equals("introduced")) tag.setIntroduced(value); // TTE - Class else if(inside.equals("tte")) tag.setTagEval(toTagEvaluator(value)); else if(inside.equals("tte-class")) tag.setTTEClassDefinition(value,id,attributes); // TTT - Class else if(inside.equals("ttt-class")) tag.setTTTClassDefinition(value,id,attributes); // TDBT - Class else if(inside.equals("tdbt-class")) tag.setTDBTClassDefinition(value,id,attributes); // TDBT - Class else if(inside.equals("att-class")) tag.setAttributeEvaluatorClassDefinition(value,id,attributes); // 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.setAttributeUndefinedValue(value); else if(inside.equals("att-undefined-value")) tag.setAttributeUndefinedValue(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,id,attributes); // 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); } } private static TagEvaluator toTagEvaluator(String value) { String[] arr = ListUtil.listToStringArray(value, ':'); if(arr.length==2 && arr[0].trim().equalsIgnoreCase("parent")){ String parent=arr[1].trim(); return new ChildEvaluator(parent); } throw new RuntimeException(value+" is not supported as tte defintion, you can do for example [parent:<parent-name>]!"); } /** * 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. * @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,Identification id) throws TagLibException { if(!dir.isDirectory())return new TagLib[0]; ArrayList<TagLib> arr=new ArrayList<TagLib>(); Resource[] files=dir.listResources(new ExtensionResourceFilter(new String[]{"tld","tldx"})); for(int i=0;i<files.length;i++) { if(files[i].isFile()) arr.add(TagLibFactory.loadFromFile(files[i],id)); } return arr.toArray(new TagLib[arr.size()]); } /** * 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,Identification id) throws TagLibException { // Read in XML TagLib lib=TagLibFactory.getHashLib(FunctionLibFactory.id(res)); if(lib==null) { lib=new TagLibFactory(DEFAULT_SAX_PARSER,null,res,id).getLib(); TagLibFactory.hashLib.put(FunctionLibFactory.id(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,Identification id) throws TagLibException { return new TagLibFactory(DEFAULT_SAX_PARSER,null,is,id).getLib(); } /** * Laedt die Systeminterne TLD. * @param saxParser Definition des Sax Parser mit dem die FunctionLib eingelsesen werden soll. * @return FunctionLib * @throws TagLibException */ private static TagLib[] loadFromSystem(Identification id) throws TagLibException { if(systemTLDs[CFMLEngine.DIALECT_CFML]==null) { TagLib cfml = new TagLibFactory(DEFAULT_SAX_PARSER,null,TLD_BASE,id).getLib(); TagLib lucee = cfml.duplicate(false); systemTLDs[CFMLEngine.DIALECT_CFML] = new TagLibFactory(DEFAULT_SAX_PARSER,cfml,TLD_CFML,id).getLib(); systemTLDs[CFMLEngine.DIALECT_LUCEE] = new TagLibFactory(DEFAULT_SAX_PARSER,lucee,TLD_LUCEE,id).getLib(); } return systemTLDs; } public static TagLib loadFromSystem(int dialect,Identification id) throws TagLibException { return loadFromSystem(id)[dialect]; } public static TagLib[] loadFrom(Resource res, Identification id) throws TagLibException { if(res.isDirectory())return loadFromDirectory(res,id); if(res.isFile()) return new TagLib[]{loadFromFile(res,id)}; 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.setELClassDefinition(extTL.getELClassDefinition()); 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"; } }