001/**
002 *
003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
004 *
005 * This library is free software; you can redistribute it and/or
006 * modify it under the terms of the GNU Lesser General Public
007 * License as published by the Free Software Foundation; either 
008 * version 2.1 of the License, or (at your option) any later version.
009 * 
010 * This library is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013 * Lesser General Public License for more details.
014 * 
015 * You should have received a copy of the GNU Lesser General Public 
016 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
017 * 
018 **/
019package lucee.transformer.library.tag;
020
021import java.io.IOException;
022import java.io.InputStream;
023import java.io.Reader;
024import java.net.URISyntaxException;
025import java.nio.charset.Charset;
026import java.util.ArrayList;
027import java.util.Iterator;
028import java.util.Map;
029import java.util.Set;
030
031import lucee.commons.collection.MapFactory;
032import lucee.commons.io.IOUtil;
033import lucee.commons.io.SystemUtil;
034import lucee.commons.io.res.Resource;
035import lucee.commons.io.res.filter.ExtensionResourceFilter;
036import lucee.commons.io.res.util.ResourceUtil;
037import lucee.runtime.op.Caster;
038import lucee.runtime.text.xml.XMLUtil;
039import lucee.runtime.type.util.ArrayUtil;
040
041import org.xml.sax.Attributes;
042import org.xml.sax.InputSource;
043import org.xml.sax.SAXException;
044import org.xml.sax.XMLReader;
045import org.xml.sax.helpers.DefaultHandler;
046
047/**
048 * Die Klasse TagLibFactory liest die XML Repraesentation einer TLD ein 
049 * und laedt diese in eine Objektstruktur. 
050 * Sie tut dieses mithilfe eines Sax Parser.
051 * Die Klasse kann sowohl einzelne Files oder gar ganze Verzeichnisse von TLD laden.
052 */
053public final class TagLibFactory extends DefaultHandler {
054        
055        /**
056         * Standart Sax Parser
057         */
058        public final static String DEFAULT_SAX_PARSER="org.apache.xerces.parsers.SAXParser";
059        
060        /**
061         * Field <code>TYPE_CFML</code>
062         */
063        public final static short TYPE_CFML=0;
064        /**
065         * Field <code>TYPE_JSP</code>
066         */
067        public final static short TYPE_JSP=1;
068        
069
070        private final static String TLD_1_0=    "/resource/tld/web-cfmtaglibrary_1_0";
071        
072        //private short type=TYPE_CFML;
073        
074        private XMLReader xmlReader;
075        
076        private static Map<String,TagLib> hashLib=MapFactory.<String,TagLib>getConcurrentMap();
077        private static TagLib systemTLD;
078        private TagLib lib=new TagLib();
079        
080        private TagLibTag tag;
081        private boolean insideTag=false;
082        private boolean insideScript=false;
083        
084        private TagLibTagAttr att;
085        private boolean insideAtt=false;
086
087        private String inside;
088        private StringBuffer content=new StringBuffer();
089        private TagLibTagScript script;
090        
091        /**
092         * Privater Konstruktor, der als Eingabe die TLD als File Objekt erhaelt.
093         * @param saxParser String Klassenpfad zum Sax Parser.
094         * @param file File Objekt auf die TLD.
095         * @throws TagLibException
096         * @throws IOException 
097         */
098        private TagLibFactory(String saxParser,Resource res) throws TagLibException {
099                Reader r=null;
100                try {
101                        InputSource is=new InputSource(r=IOUtil.getReader(res.getInputStream(), (Charset)null));
102                        is.setSystemId(res.getPath());
103                        init(saxParser,is);
104                } catch (IOException e) {e.printStackTrace();
105                        throw new TagLibException(e);
106                }
107                finally {
108                        IOUtil.closeEL(r);
109                }
110        }
111
112        /**
113         * Privater Konstruktor, der als Eingabe die TLD als File Objekt erhaelt.
114         * @param saxParser String Klassenpfad zum Sax Parser.
115         * @param file File Objekt auf die TLD.
116         * @throws TagLibException
117         */
118        private TagLibFactory(String saxParser,InputStream stream) throws TagLibException {
119                try {
120                        InputSource is=new InputSource(IOUtil.getReader(stream, SystemUtil.getCharset()));
121                        //is.setSystemId(file.toString());
122                        init(saxParser,is);
123                } catch (IOException e) {
124                        throw new TagLibException(e);
125                }
126        }
127        
128        /**
129         * Privater Konstruktor nur mit Sax Parser Definition, liest Default TLD vom System ein.
130         * @param saxParser String Klassenpfad zum Sax Parser.
131         * @throws TagLibException
132         */
133        private TagLibFactory(String saxParser) throws TagLibException {
134                InputSource is=new InputSource(this.getClass().getResourceAsStream(TLD_1_0) );
135                init(saxParser,is);             
136                lib.setIsCore(true);
137        }
138        
139        /**
140         * Generelle Initialisierungsmetode der Konstruktoren.
141         * @param saxParser String Klassenpfad zum Sax Parser.
142         * @param  is InputStream auf die TLD.
143         * @throws TagLibException
144         */
145        private void init(String saxParser,InputSource is) throws TagLibException       {
146                //print.dumpStack();
147                try {
148                        xmlReader=XMLUtil.createXMLReader(saxParser);
149                        xmlReader.setContentHandler(this);
150                        xmlReader.setErrorHandler(this);
151                        xmlReader.setEntityResolver(new TagLibEntityResolver());
152                        xmlReader.parse(is);
153                } 
154                catch (IOException e) {
155                        throw new TagLibException(e);
156                } 
157                catch (SAXException e) {
158                        e.printStackTrace();
159                        //String fileName=is.getSystemId();
160                        //String message="SAXException: ";
161                        //if(fileName!=null) message+="In File ["+fileName+"], ";
162                        throw new TagLibException(e);
163                } 
164                
165    }
166
167        /**
168         * Geerbte Methode von org.xml.sax.ContentHandler, 
169         * wird bei durchparsen des XML, beim Auftreten eines Start-Tag aufgerufen.
170         *  
171         * @see org.xml.sax.ContentHandler#startElement(String, String, String, Attributes)
172         */
173        public void startElement(String uri, String name, String qName, Attributes atts) {
174
175                inside=qName;
176                
177                
178                if(qName.equals("tag")) startTag();
179                else if(qName.equals("attribute")) startAtt();
180                else if(qName.equals("script")) startScript();
181                
182        }
183    
184        /**
185         * Geerbte Methode von org.xml.sax.ContentHandler, 
186         * wird bei durchparsen des XML, beim auftreten eines End-Tag aufgerufen.
187         *  
188         * @see org.xml.sax.ContentHandler#endElement(String, String, String)
189         */
190        public void endElement(String uri, String name, String qName) {
191                setContent(content.toString().trim());
192                content=new StringBuffer();
193                inside="";
194                /*
195                if(tag!=null && tag.getName().equalsIgnoreCase("input")) {
196                        print.ln(tag.getName()+"-"+att.getName()+":"+inside+"-"+insideTag+"-"+insideAtt);
197                        
198                }
199                */
200                if(qName.equals("tag")) endTag();
201                else if(qName.equals("attribute")) endAtt();
202                else if(qName.equals("script")) endScript();
203        }
204        
205        
206    /** 
207     * Geerbte Methode von org.xml.sax.ContentHandler, 
208         * wird bei durchparsen des XML, zum einlesen des Content eines Body Element aufgerufen.
209         * 
210         * @see org.xml.sax.ContentHandler#characters(char[], int, int)
211         */
212        public void characters (char ch[], int start, int length)       {
213                content.append(new String(ch,start,length));
214        }
215        
216        private void setContent(String value)   {
217        if(insideTag)   {
218                // Att Args
219                if(insideAtt)   {
220                        // description?
221                        // Name
222                        if(inside.equals("name")) att.setName(value);
223                        // alias
224                        if(inside.equals("alias")) att.setAlias(value);
225
226                        // Values
227                        if(inside.equals("values")) att.setValues(value);
228
229                        // Value Delimiter
230                        if(inside.equals("value-delimiter")) att.setValueDelimiter(value);
231
232                                // Required
233                                else if(inside.equals("required")) 
234                                        att.setRequired(Caster.toBooleanValue(value,false));
235                                // Rtexprvalue
236                                else if(inside.equals("rtexprvalue")) 
237                                        att.setRtexpr(Caster.toBooleanValue(value,false));
238                                // Type
239                                else if(inside.equals("type")) att.setType(value);
240                                // Default-Value
241                                else if(inside.equals("default-value")) att.setDefaultValue(value);
242                        // status
243                                else if(inside.equals("status")) att.setStatus(toStatus(value));
244                        // Description
245                                else if(inside.equals("description")) att.setDescription(value);
246                        // No-Name
247                                else if(inside.equals("noname")) att.setNoname(Caster.toBooleanValue(value,false));
248                                // default
249                                else if(inside.equals("default")) att.isDefault(Caster.toBooleanValue(value,false));
250                                else if(inside.equals("script-support")) att.setScriptSupport(value);
251                }
252                else if(insideScript)   {
253                        // type
254                        if(inside.equals("type")) script.setType(value);
255                        if(inside.equals("rtexprvalue")) script.setRtexpr(Caster.toBooleanValue(value,false));
256                        if(inside.equals("context")) script.setContext(value);
257                                
258                }
259                // Tag Args
260                else    {
261                    // TODO TEI-class
262                        // Name
263                        if(inside.equals("name"))                               {tag.setName(value);}
264                                // TAG - Class
265                        else if(inside.equals("tag-class"))     tag.setTagClass(value);
266                        else if(inside.equals("tagclass"))      tag.setTagClass(value);
267                        // status
268                                else if(inside.equals("status"))        tag.setStatus(toStatus(value));
269                        // TAG - description
270                                else if(inside.equals("description"))   tag.setDescription(value);
271                        // TTE - Class
272                                else if(inside.equals("tte-class"))     tag.setTteClass(value);
273                        // TTT - Class
274                                else if(inside.equals("ttt-class"))     tag.setTttClass(value);
275                                // TDBT - Class
276                                else if(inside.equals("tdbt-class"))    tag.setTdbtClass(value);
277                                // TDBT - Class
278                                else if(inside.equals("att-class"))     tag.setAttributeEvaluatorClassName(value);
279                                // Body Content
280                                else if(inside.equals("body-content") || inside.equals("bodycontent"))  {
281                    tag.setBodyContent(value);
282                }
283                        //allow-removing-literal
284                                else if(inside.equals("allow-removing-literal"))  {
285                    tag.setAllowRemovingLiteral(Caster.toBooleanValue(value,false));
286                }
287                                else if(inside.equals("att-default-value"))     tag.setAttributeDefaultValue(value);
288                                
289                        
290                        
291                // Handle Exceptions
292                                else if(inside.equals("handle-exception"))      {
293                                        tag.setHandleExceptions(Caster.toBooleanValue(value,false));
294                                }
295                                // Appendix
296                                else if(inside.equals("appendix"))      {
297                                        tag.setAppendix(Caster.toBooleanValue(value,false));
298                                }
299                        // Body rtexprvalue
300                        else if(inside.equals("body-rtexprvalue")) {
301                                tag.setParseBody(Caster.toBooleanValue(value,false));
302                        }
303                        // Att - min
304                        else if(inside.equals("attribute-min")) 
305                                tag.setMin(Integer.parseInt(value));
306                        // Att - max
307                        else if(inside.equals("attribute-max")) 
308                                tag.setMax(Integer.parseInt(value));
309                        // Att Type
310                                else if(inside.equals("attribute-type"))        {
311                                        int type=TagLibTag.ATTRIBUTE_TYPE_FIXED;
312                                        if(value.toLowerCase().equals("fix"))type=TagLibTag.ATTRIBUTE_TYPE_FIXED;
313                                        else if(value.toLowerCase().equals("fixed"))type=TagLibTag.ATTRIBUTE_TYPE_FIXED;
314                                        else if(value.toLowerCase().equals("dynamic"))type=TagLibTag.ATTRIBUTE_TYPE_DYNAMIC;
315                                        else if(value.toLowerCase().equals("noname"))type=TagLibTag.ATTRIBUTE_TYPE_NONAME;
316                                        else if(value.toLowerCase().equals("mixed"))type=TagLibTag.ATTRIBUTE_TYPE_MIXED;
317                                        else if(value.toLowerCase().equals("fulldynamic"))type=TagLibTag.ATTRIBUTE_TYPE_DYNAMIC;// deprecated
318                                        tag.setAttributeType(type);
319                                }
320                }
321        }
322        // Tag Lib
323        else    {
324            // TagLib Typ
325            if(inside.equals("jspversion")) {
326                //type=TYPE_JSP;
327                lib.setType("jsp");
328            }
329            else if(inside.equals("cfml-version")) {
330                //type=TYPE_CFML;
331                lib.setType("cfml");
332            }
333            
334            
335                // EL Class
336                else if(inside.equals("el-class")) lib.setELClass(value);
337                // Name-Space
338                else if(inside.equals("name-space")) lib.setNameSpace(value);
339                // Name Space Sep
340                else if(inside.equals("name-space-separator")) lib.setNameSpaceSeperator(value);
341                // short-name
342            else if(inside.equals("short-name")) lib.setShortName(value);
343            else if(inside.equals("shortname")) lib.setShortName(value);
344            // display-name
345            else if(inside.equals("display-name")) lib.setDisplayName(value);
346            else if(inside.equals("displayname")) lib.setDisplayName(value);
347            // ignore-unknow-tags
348            else if(inside.equals("ignore-unknow-tags")) lib.setIgnoreUnknowTags(Caster.toBooleanValue(value,false));
349            
350            
351            else if(inside.equals("uri")) { 
352                                try {
353                                        lib.setUri(value);
354                                } catch (URISyntaxException e) {} 
355                        } 
356                        else if(inside.equals("description")) lib.setDescription(value);                
357                
358        }
359    }   
360        
361
362        /**
363         * Wird jedesmal wenn das Tag tag beginnt aufgerufen, um intern in einen anderen Zustand zu gelangen.
364         */
365        private void startTag() {
366        tag=new TagLibTag(lib);
367        insideTag=true;
368    }
369        
370        /**
371         * Wird jedesmal wenn das Tag tag endet aufgerufen, um intern in einen anderen Zustand zu gelangen.
372         */
373        private void endTag()   {
374                lib.setTag(tag);
375        insideTag=false;
376    }
377        
378        private void startScript()      {
379        script=new TagLibTagScript(tag);
380        insideScript=true;
381    }
382        
383        /**
384         * Wird jedesmal wenn das Tag tag endet aufgerufen, um intern in einen anderen Zustand zu gelangen.
385         */
386        private void endScript()        {
387                tag.setScript(script);
388        insideScript=false;
389    }
390        
391        
392        
393        /**
394         * Wird jedesmal wenn das Tag attribute beginnt aufgerufen, um intern in einen anderen Zustand zu gelangen.
395         */
396        private void startAtt() {
397        att=new TagLibTagAttr(tag);
398        insideAtt=true;
399    }
400        
401        
402        /**
403         * Wird jedesmal wenn das Tag tag endet aufgerufen, um intern in einen anderen Zustand zu gelangen.
404         */
405        private void endAtt()   {
406        tag.setAttribute(att);
407        insideAtt=false;
408    }
409
410        /**
411         * Gibt die interne TagLib zurueck.
412         * @return Interne Repraesentation der zu erstellenden TagLib.
413         */
414        private TagLib getLib() {
415                return lib;
416        }
417        
418        /**
419         * TagLib werden innerhalb der Factory in einer HashMap gecacht,
420         * so das diese einmalig von der Factory geladen werden. 
421         * Diese Methode gibt eine gecachte TagLib anhand dessen key zurueck, 
422         * falls diese noch nicht im Cache existiert, gibt die Methode null zurueck.
423         * 
424         * @param key Absoluter Filepfad zur TLD.
425         * @return TagLib
426         */
427        private static TagLib getHashLib(String key) {
428                return hashLib.get(key);
429        }
430        
431        /**
432         * Laedt mehrere TagLib's die innerhalb eines Verzeichnisses liegen.
433         * @param dir Verzeichnis im dem die TagLib's liegen.
434         * @return TagLib's als Array
435         * @throws TagLibException
436         */
437        public static TagLib[] loadFromDirectory(Resource dir) throws TagLibException   {
438                return loadFromDirectory(dir,DEFAULT_SAX_PARSER);
439        }
440        
441        /**
442         * Laedt mehrere TagLib's die innerhalb eines Verzeichnisses liegen.
443         * @param dir Verzeichnis im dem die TagLib's liegen.
444         * @param saxParser Definition des Sax Parser mit dem die TagLib's eingelesen werden sollen.
445         * @return TagLib's als Array
446         * @throws TagLibException
447         */
448        public static TagLib[] loadFromDirectory(Resource dir,String saxParser) throws TagLibException  {
449                if(!dir.isDirectory())return new TagLib[0];
450                ArrayList<TagLib> arr=new ArrayList<TagLib>();
451                
452                Resource[] files=dir.listResources(new ExtensionResourceFilter(new String[]{"tld","tldx"}));
453                for(int i=0;i<files.length;i++)      {
454                        if(files[i].isFile())
455                                arr.add(TagLibFactory.loadFromFile(files[i],saxParser));
456                                
457                }
458                return arr.toArray(new TagLib[arr.size()]);
459        }
460
461        /**
462         * Laedt eine einzelne TagLib.
463         * @param file TLD die geladen werden soll.
464         * @return TagLib
465         * @throws TagLibException
466         */
467        public static TagLib loadFromFile(Resource res) throws TagLibException  {
468                return loadFromFile(res,DEFAULT_SAX_PARSER);
469        }
470
471        /**
472         * Laedt eine einzelne TagLib.
473         * @param file TLD die geladen werden soll.
474         * @return TagLib
475         * @throws TagLibException
476         */
477        public static TagLib loadFromStream(InputStream is) throws TagLibException      {
478                return loadFromStream(is,DEFAULT_SAX_PARSER);
479        }
480
481        /**
482         * Laedt eine einzelne TagLib.
483         * @param file TLD die geladen werden soll.
484         * @param saxParser Definition des Sax Parser mit dem die TagLib eingelsesen werden soll.
485         * @return TagLib
486         * @throws TagLibException
487         */
488        public static TagLib loadFromFile(Resource res,String saxParser) throws TagLibException {
489
490                // Read in XML
491            TagLib lib=TagLibFactory.getHashLib(ResourceUtil.getCanonicalPathEL(res));
492                if(lib==null)   {
493                    lib=new TagLibFactory(saxParser,res).getLib();
494                        TagLibFactory.hashLib.put(ResourceUtil.getCanonicalPathEL(res),lib);
495                }
496                lib.setSource(res.toString());
497                return lib;
498        }
499        /**
500         * Laedt eine einzelne TagLib.
501         * @param file TLD die geladen werden soll.
502         * @param saxParser Definition des Sax Parser mit dem die TagLib eingelsesen werden soll.
503         * @return TagLib
504         * @throws TagLibException
505         */
506        public static TagLib loadFromStream(InputStream is,String saxParser) throws TagLibException     {
507        return new TagLibFactory(saxParser,is).getLib();
508        }
509        
510        /**
511         * Laedt die Systeminterne TLD.
512         * @return FunctionLib
513         * @throws TagLibException
514         */
515        public static TagLib loadFromSystem() throws TagLibException    {
516                return loadFromSystem(DEFAULT_SAX_PARSER);
517        }
518        
519        /**
520         * Laedt die Systeminterne TLD.
521         * @param saxParser Definition des Sax Parser mit dem die FunctionLib eingelsesen werden soll.
522         * @return FunctionLib
523         * @throws TagLibException
524         */
525        public static TagLib loadFromSystem(String saxParser) throws TagLibException    {
526                if(systemTLD==null) systemTLD=new TagLibFactory(saxParser).getLib();
527                return systemTLD;
528        }
529
530        public static TagLib[] loadFrom(Resource res) throws TagLibException {
531                if(res.isDirectory())return loadFromDirectory(res);
532                if(res.isFile()) return new TagLib[]{loadFromFile(res)};
533                throw new TagLibException("can not load tag library descriptor from ["+res+"]");
534        }
535
536        
537        /**
538         * return one FunctionLib contain content of all given Function Libs
539         * @param tlds
540         * @return combined function lib
541         */
542        public static TagLib combineTLDs(TagLib[] tlds){
543                TagLib tl = new TagLib();
544                if(ArrayUtil.isEmpty(tlds)) return tl;
545
546                setAttributes(tlds[0],tl);
547                
548                // add functions
549                for(int i=0;i<tlds.length;i++){
550                        copyTags(tlds[i],tl);
551                }
552                return tl;
553        }
554        
555        public static TagLib combineTLDs(Set tlds){
556                TagLib newTL = new TagLib(),tmp;
557                if(tlds.size()==0) return newTL ;
558
559                Iterator it = tlds.iterator();
560                int count=0;
561                while(it.hasNext()){
562                        tmp=(TagLib) it.next();
563                        if(count++==0) setAttributes(tmp,newTL);
564                        copyTags(tmp,newTL);
565                }
566                return newTL;
567        }
568        
569        private static void setAttributes(TagLib extTL, TagLib newTL) {
570                newTL.setDescription(extTL.getDescription());
571                newTL.setDisplayName(extTL.getDisplayName());
572                newTL.setELClass(extTL.getELClass());
573                newTL.setIsCore(extTL.isCore());
574                newTL.setNameSpace(extTL.getNameSpace());
575                newTL.setNameSpaceSeperator(extTL.getNameSpaceSeparator());
576                newTL.setShortName(extTL.getShortName());
577                newTL.setSource(extTL.getSource());
578                newTL.setType(extTL.getType());
579                newTL.setUri(extTL.getUri());
580                
581        }
582        
583        private static void copyTags(TagLib extTL, TagLib newTL) {
584                Iterator it = extTL.getTags().entrySet().iterator();
585                TagLibTag tlt;
586                while(it.hasNext()){
587                        tlt= (TagLibTag) ((Map.Entry)it.next()).getValue(); // TODO function must be duplicated because it gets a new FunctionLib assigned
588                        newTL.setTag(tlt);
589                }
590        }
591        
592
593        public static short toStatus(String value) {
594                value=value.trim().toLowerCase();
595                if("deprecated".equals(value)) return TagLib.STATUS_DEPRECATED;
596                if("dep".equals(value)) return TagLib.STATUS_DEPRECATED;
597                
598                if("unimplemented".equals(value)) return TagLib.STATUS_UNIMPLEMENTED;
599                if("unimplemeted".equals(value)) return TagLib.STATUS_UNIMPLEMENTED;
600                if("notimplemented".equals(value)) return TagLib.STATUS_UNIMPLEMENTED;
601                if("not-implemented".equals(value)) return TagLib.STATUS_UNIMPLEMENTED;
602                if("hidden".equals(value)) return TagLib.STATUS_HIDDEN;
603                
604                return TagLib.STATUS_IMPLEMENTED;
605        }
606        public static String toStatus(short value) {
607                switch(value){
608                case TagLib.STATUS_DEPRECATED: return "deprecated";
609                case TagLib.STATUS_UNIMPLEMENTED: return "unimplemeted";
610                case TagLib.STATUS_HIDDEN: return "hidden";
611                }
612                return "implemeted";
613        }
614        
615}
616
617
618
619
620
621
622
623