001    package railo.transformer.cfml.tag;
002    
003    import java.io.IOException;
004    import java.util.ArrayList;
005    import java.util.Iterator;
006    import java.util.Map;
007    import java.util.Map.Entry;
008    
009    import railo.commons.io.res.util.ResourceUtil;
010    import railo.commons.lang.StringUtil;
011    import railo.commons.lang.types.RefBoolean;
012    import railo.commons.lang.types.RefBooleanImpl;
013    import railo.runtime.Info;
014    import railo.runtime.SourceFile;
015    import railo.runtime.config.Config;
016    import railo.runtime.config.ConfigImpl;
017    import railo.runtime.exp.ApplicationException;
018    import railo.runtime.exp.PageExceptionImpl;
019    import railo.runtime.exp.TemplateException;
020    import railo.runtime.op.Caster;
021    import railo.transformer.bytecode.Body;
022    import railo.transformer.bytecode.BodyBase;
023    import railo.transformer.bytecode.Page;
024    import railo.transformer.bytecode.cast.Cast;
025    import railo.transformer.bytecode.expression.Expression;
026    import railo.transformer.bytecode.expression.var.NullExpression;
027    import railo.transformer.bytecode.literal.LitBoolean;
028    import railo.transformer.bytecode.literal.LitString;
029    import railo.transformer.bytecode.statement.PrintOut;
030    import railo.transformer.bytecode.statement.StatementBase;
031    import railo.transformer.bytecode.statement.tag.Attribute;
032    import railo.transformer.bytecode.statement.tag.Tag;
033    import railo.transformer.bytecode.util.ASMUtil;
034    import railo.transformer.cfml.ExprTransformer;
035    import railo.transformer.cfml.attributes.AttributeEvaluatorException;
036    import railo.transformer.cfml.evaluator.EvaluatorException;
037    import railo.transformer.cfml.evaluator.EvaluatorPool;
038    import railo.transformer.cfml.evaluator.impl.ProcessingDirectiveException;
039    import railo.transformer.cfml.expression.SimpleExprTransformer;
040    import railo.transformer.cfml.script.CFMLScriptTransformer;
041    import railo.transformer.cfml.script.CFMLScriptTransformer.ComponentTemplateException;
042    import railo.transformer.library.function.FunctionLib;
043    import railo.transformer.library.tag.CustomTagLib;
044    import railo.transformer.library.tag.TagLib;
045    import railo.transformer.library.tag.TagLibException;
046    import railo.transformer.library.tag.TagLibFactory;
047    import railo.transformer.library.tag.TagLibTag;
048    import railo.transformer.library.tag.TagLibTagAttr;
049    import railo.transformer.util.CFMLString;
050    
051    
052    /**
053     * Die Klasse CFMLTransformer ist das Herzstck des ᅵbersetzungsprozess, 
054     * es liest die bergebene CFML Datei ein und bersetzt diese in ein valid (CFXD) XML Dokument 
055     * in der Form eines org.w3c.dom.Document Object, 
056     * die dann als weitere Vorlage zum bersetzten in PHP dient.
057     * Der CFMLTransformer bersetzt nur die Tags die innerhalb einer CFML Seite vorkommen, 
058     * nicht die Ausdrcke die innerhalb von Attributen und dem Body eines Tag vorkommen k￶nnen, 
059     * fr dies ist der ExprTransformer zust¦ndig, 
060     * der in der jeweiligen Tag Library definiert ist.
061     * Der CFMLTransformer kann zwar durch seine Grammatik, 
062     * Tags erkennen aber nicht validieren. 
063     * Erst mithilfe der im zugeteilten Tag Libraries kann er vergleichen ob ein Tag nur 
064     * ein normaler HTML Tag ist, das er einfach als literale Zeichenkette aufnimmt, 
065     * oder ob es sich um einen Tag handelt der eine konkrete Anweisung implementiert. 
066     * Die Tag Library definiert alle in CFML vorhanden Tags, 
067     * deren individuelle Grammatik und deren Aufbau und Verhalten. 
068    
069     * <pre>
070                    Parser Grammatik nach EBNF (Extended Backus-Naur Form) 
071                    
072                    transform       = {body}
073                    body            = [comment] ("</" | "<" tag body | literal body);
074                    comment         = "<!---" {?-"--->"} "--->";
075                    literal         = ("<" | {?-"#"-"<"} "<" | {"#" expression "#"} "<" ) | ({?-"<"} "<")
076                                      (* Welcher Teil der "oder" Bedingung ausgefhrt wird, ist abh¦ngig was die Tag-Lib vorgibt, 
077                                         dass Expression geparst werden sollen oder nicht. *)
078                    tag             = name-space identifier spaces attributes ("/>" | ">" [body "</" identifier spaces ">"]);
079                                      (* Ob dem Tag ein Body und ein End-Tag folgt ist abh¦ngig von Definition des body-content in Tag-Lib, gleices gilt fr appendix *)
080                    name-space      = < tagLib[].getNameSpaceAndSeperator() >;
081                                      (* Vergleicht Zeichen mit den Namespacedefinitionen der Tag Libraries. *)
082                    attributes      = ({spaces attribute} "/>" | {spaces attribute} ">") | attribute-value;
083                                      (* Welcher Teil der "oder" Bedingung ausgefhrt wird, ist abh¦ngig von der Tag Attribute Definition in der Tag Lib. *)
084                    attribute       = attribute-name  spaces "=" spaces attribute-value;
085                    attribute-name  = ("expression"|'expression'|expression) | identifier;
086                                      (* Ruft identifier oder den Expression Transformer auf je nach Attribute Definition in der Tag Lib. *)
087                    attribute-value = expression; 
088                    identifier      = (letter | "_") {letter | "_"|digit};
089                    letter                  = "a".."z"|"A".."Z";
090                    digit                   = "0".."9";
091                    expression      = <ExprTransfomer.expression()>; (* Ruft den Expression Transformer auf. *)
092                    spaces         = {space};
093                    space          = "\s"|"\t"|"\f"|"\t"|"\n";
094                    
095                    {"x"}= 0 bis n mal "x"
096                    ["x"]= 0 bis 1 mal "x"
097                    ("x" | "y")"z" = "xz" oder "yz"
098    </pre>
099     * 
100     *
101     */
102    public final class CFMLTransformer {
103            
104        public static short TAG_LIB_GLOBAL=0;
105        public static short TAG_LIB_PAGE=1;
106        
107        public class Data {
108                    
109                    private TagLib[][] tlibs;//=new TagLib[][]{null,new TagLib[0]};
110                    private FunctionLib[] flibs;
111                    private CFMLString cfml;
112                    private EvaluatorPool ep=new EvaluatorPool();
113                    private SimpleExprTransformer set;
114                private Config config;
115                    private Page page;
116             
117                public Data(TagLib[][] tlibs, FunctionLib[] flibs, CFMLString cfml,Config config,Page page) {
118                            super();
119                            this.tlibs = tlibs;
120                            this.flibs = flibs;
121                            this.cfml = cfml;
122                            this.config = config;
123                            this.page = page;
124                    }
125        }
126            
127            /**
128             * Startmethode zum transfomieren einer CFML Datei.
129             * <br />
130             * EBNF:<br />
131             * <code>{body}</code>
132             * @param config
133             * @param sf CFML File
134             * @param tlibs Tag Library Deskriptoren, nach denen innerhalb der CFML Datei geprft werden soll.
135             * @param flibs Function Library Deskriptoren, nach denen innerhalb der Expressions der CFML Datei geprft werden soll.
136             * @return ᅵbersetztes CFXD Dokument Element.
137             * @throws TemplateException
138             * @throws IOException
139             */
140            public Page transform(ConfigImpl config,SourceFile sf, TagLib[] tlibs, FunctionLib[] flibs) throws TemplateException, IOException       {
141                    Page p;
142                    CFMLString cfml;
143                    String charset;
144                    boolean writeLog;
145                    
146                    writeLog=config.getExecutionLogEnabled();
147                    charset=config.getTemplateCharset();
148                    
149                    
150                    
151                    while(true){
152                            try {
153                                    cfml=new CFMLString(sf,charset,writeLog);
154                                    p = transform(config,cfml,tlibs,flibs,sf.getFile().lastModified());
155                                    break;
156                            }
157                            catch(ProcessingDirectiveException pde) {
158                                    writeLog=pde.getWriteLog();
159                                    charset=pde.getCharset();
160                            }
161                    }
162                    
163                    // if cfc has no component tag or is script without cfscript
164                    if(p.isPage() && ResourceUtil.getExtension(sf.getFile(),"").equalsIgnoreCase(config.getCFCExtension())){
165                            cfml.setPos(0);
166                            TagLibTag tlt;
167                            CFMLString original = cfml; 
168                            
169                            // try inside a cfscript
170                            tlt = CFMLTransformer.getTLT(original,"script");
171                            String text="<"+tlt.getFullName()+">"+original.getText()+"</"+tlt.getFullName()+">";
172                            cfml=new CFMLString(text,charset,writeLog,sf);
173                            
174                            try {
175                                    while(true){
176                                            if(cfml==null){
177                                                    cfml=new CFMLString(sf,charset,writeLog);
178                                                    text="<"+tlt.getFullName()+">"+cfml.getText()+"</"+tlt.getFullName()+">";
179                                                    cfml=new CFMLString(text,charset,writeLog,sf);
180                                            }
181                                            try {
182                                                    p= transform(config,cfml,tlibs,flibs,sf.getFile().lastModified());
183                                                    break;
184                                            }
185                                            catch(ProcessingDirectiveException pde) {
186                                                    writeLog=pde.getWriteLog();
187                                                    charset=pde.getCharset();
188                                                    cfml=null;
189                                            }
190                                    }
191                            }
192                            catch (ComponentTemplateException e) {
193                                    throw e.getTemplateException();
194                            }
195                            catch (TemplateException e) {
196                                    //print.printST(e);
197                            }
198                            
199                            
200                            
201                            
202                            // try inside a component
203                            if(p.isPage()){
204                                    tlt = CFMLTransformer.getTLT(original,"component");
205                                    text="<"+tlt.getFullName()+">"+original.getText()+"</"+tlt.getFullName()+">";
206                                    cfml=new CFMLString(text,charset,writeLog,sf);
207                                                    
208                                    while(true){
209                                            if(cfml==null){
210                                                    cfml=new CFMLString(sf,charset,writeLog);
211                                                    text="<"+tlt.getFullName()+">"+cfml.getText()+"</"+tlt.getFullName()+">";
212                                                    cfml=new CFMLString(text,charset,writeLog,sf);
213                                            }
214                                            try {
215                                                    p= transform(config,cfml,tlibs,flibs,sf.getFile().lastModified());
216                                                    break;
217                                            }
218                                            catch(ProcessingDirectiveException pde) {
219                                                    writeLog=pde.getWriteLog();
220                                                    charset=pde.getCharset();
221                                                    cfml=null;
222                                            }
223                                    }
224                            }
225                            
226                    }
227                    
228                    
229                    return p;
230            }
231            
232    
233            public static TagLibTag getTLT(CFMLString cfml,String name) throws TemplateException {
234                    TagLib tl;
235                    try {
236                            // this is already loaded, oherwise we where not here
237                            tl = TagLibFactory.loadFromSystem();
238                            return tl.getTag(name);
239                    } 
240                    catch (TagLibException e) {
241                            throw new TemplateException(cfml,e);
242                    }
243            }
244            
245    
246            /**
247             * Startmethode zum transfomieren einer CFMLString.
248             * <br />
249             * EBNF:<br />
250             * <code>{body}</code>
251             * @param config
252             * @param cfml CFMLString
253             * @param tlibs Tag Library Deskriptoren, nach denen innerhalb der CFML Datei geprft werden soll.
254             * @param flibs Function Library Deskriptoren, nach denen innerhalb der Expressions der CFML Datei geprft werden soll.
255             * @param sourceLastModified 
256             * @return ᅵbersetztes CFXD Dokument Element.
257             * @throws TemplateException
258             */
259            public Page transform(ConfigImpl config,CFMLString cfml,TagLib[] tlibs,FunctionLib[] flibs, long sourceLastModified) throws TemplateException {
260                    
261                    TagLib[][] _tlibs=new TagLib[][]{null,new TagLib[0]};
262                    _tlibs[TAG_LIB_GLOBAL]=tlibs;
263                    // reset page tlds
264                    if(_tlibs[TAG_LIB_PAGE].length>0) {
265                            _tlibs[TAG_LIB_PAGE]=new TagLib[0];
266                    }
267                    
268                    
269                    
270    
271                    SourceFile source=cfml.getSourceFile(); 
272                    
273                    Page page=new Page(source.getPhyscalFile(),source.getFullClassName(),Info.getFullVersionInfo(),sourceLastModified,cfml.getWriteLog());
274                    Data data = new Data(_tlibs,flibs,cfml,config,page);
275                    
276                    //Body body=page;
277                    try {
278                            do {
279                                    
280                                    body(data,page,false,null);
281                                    
282                                    if(data.cfml.isAfterLast()) break;
283                                    if(data.cfml.forwardIfCurrent("</")){
284                                            TagLib tagLib=nameSpace(data);
285                                            if(tagLib==null){
286                                                    page.addPrintOut("</", data.cfml.getLine());
287                                            }
288                                            else {
289                                                    throw new TemplateException(cfml,"no matching start tag for end tag ["+tagLib.getNameSpaceAndSeparator()+identifier(data.cfml,true)+"]");
290                            
291                                            }
292                                    }
293                                    else 
294                                            throw new TemplateException(cfml,"Error while transforming CFML File");
295                            }while(true);
296                            
297                            // call-back of evaluators
298                            data.ep.run();
299                            
300                            return page;
301                    }
302                    catch(TemplateException e) {
303                        data.ep.clear();
304                        /*if(e instanceof ProcessingDirectiveException) throw e;
305                        throw new TemplateException(
306                                    "\n-----------------------------------------------------\n"+
307                                    "line:"+e.getLine()+"\n"+
308                                    "message:"+e.getMessage()+"\n"+
309                                    data.cfml.toString()+"\n-----------------------------------------------------\n");
310                        */throw e;
311                    }
312            }
313    
314            /**
315             * Liest den Body eines Tag ein. Kommentare, Tags und Literale inkl. Expressions.
316             * <br />
317             * EBNF:<br />
318             * <code>[comment] ("</" | "<" tag body | literal body);</code>
319             * @param body CFXD Body Element dem der Inhalt zugeteilt werden soll.
320             * @param parseExpression Definiert ob Expressions innerhalb von Literalen bersetzt werden sollen oder nicht.
321             * @param transformer Expression Transfomer zum bersetzten von Expression.
322             * @throws TemplateException
323             */
324            private void body(Data data,Body body, boolean parseExpression, ExprTransformer transformer) throws TemplateException {
325                    boolean parseLiteral=true;
326                    
327            // Comment 
328            comment(data.cfml,false);
329            // Tag
330                    // is Tag Beginning
331                    if(data.cfml.isCurrent('<')) {
332                            // return if end tag and inside tag
333                            if(data.cfml.isNext('/'))       {
334                                //railo.print.ln("early return");
335                                    return;
336                            }
337                            parseLiteral=!tag(data,body,parseExpression);
338                    }
339                    // no Tag
340                    if(parseLiteral) {
341                            literal(data,body, parseExpression, transformer);
342                    }
343                    // not at the end 
344                    if(data.cfml.isValidIndex()) 
345                            body(data,body,parseExpression, transformer);
346            }
347            
348            /**
349             * Liest einen Kommentar ein, Kommentare werden nicht in die CFXD bertragen sondern verworfen.
350             * Komentare k￶nnen auch Kommentare enthalten.
351             * <br />
352             * EBNF:<br />
353             * <code>"<!---" {?-"--->"} "--->";</code>
354             * @throws TemplateException
355             */
356    
357            public static void comment(CFMLString cfml,boolean removeSpace) throws TemplateException {
358                    if(!removeSpace) {
359                            comment(cfml);
360                    }
361                    else {
362                            cfml.removeSpace();
363                            if(comment(cfml))cfml.removeSpace();
364                    }
365                    
366            }
367            
368            public static boolean comment(CFMLString cfml) throws TemplateException {
369                    if(!cfml.forwardIfCurrent("<!---"))
370                            return false;
371                    
372                    int start=cfml.getPos();
373                    short counter=1;
374                    while(true) {
375                            if(cfml.isAfterLast()) {
376                                    cfml.setPos(start);
377                                    throw new TemplateException(cfml,"no end comment found");
378                            }
379                            else if(cfml.forwardIfCurrent("<!---")) {
380                                    counter++;
381                            }
382                            else if(cfml.forwardIfCurrent("--->")) {
383                                    if(--counter==0) {
384                                            comment(cfml);
385                                            return true;
386                                    }
387                            }
388                            else {
389                                    cfml.next();
390                            }
391                    }
392            }
393    
394    
395    
396            /**
397             * Liest Literale Zeichenketten ein die sich innerhalb und auserhalb von tgas befinden, 
398             * beim Einlesen wird unterschieden ob Expression geparsst werden mssen oder nicht, 
399             * dies ist abh¦ngig, von der Definition des Tag in dem man sich allenfalls befindet, innerhalb der TLD.
400             * @param parent bergeordnetes Element.
401             * @param parseExpression Definiert on Expressions geparset werden sollen oder nicht.
402             * @param transformer Expression Transfomer zum bersetzen der Expressions innerhalb des Literals.
403             * @throws TemplateException
404             * 
405             * <br />
406             * EBNF:<br />
407             * <code>("<" | {?-"#"-"<"} "<" | {"#" expression "#"} "<" ) | ({?-"<"} "<")
408                            (* Welcher Teil der "oder" Bedingung ausgefhrt wird, ist abh¦ngig ob die Tag-Lib vorgibt, 
409                             dass Expression geparst werden sollen oder nicht. *)</code>
410             */
411            private void literal(Data data,Body parent,boolean parseExpression, ExprTransformer transformer) throws TemplateException {
412                    // with expression
413                    if(parseExpression) {
414                            if(data.cfml.isAfterLast())return;
415                // data.cfml.getCurrent()
416                            StringBuffer text=new StringBuffer();
417                            int count=0;
418                            while(data.cfml.isValidIndex()) {
419                                    count++;
420                                    // #
421                                    if(data.cfml.isCurrent('#')) {
422                                            data.cfml.next();
423                                            if(data.cfml.isCurrent('#')) {
424                                                    text.append('#');
425                                            }
426                                            else {
427                                                    if(text.length()>0)  {
428                                                            parent.addPrintOut(text.toString(),-1);
429                                                            text=new StringBuffer();
430                                                    }
431                            int line=data.cfml.getLine();
432                                                    parent.addStatement(new PrintOut(transformer.transform(data.ep,data.flibs,data.cfml),line));
433                                                            
434                                                    if(!data.cfml.isCurrent('#'))
435                                                            throw new TemplateException(data.cfml,"missing terminating [#] for expression");
436                                            }
437                                    }
438                                    else if(data.cfml.isCurrent('<') && count>1) {
439                                            break;
440                                    }
441                                    else
442                                            text.append(data.cfml.getCurrent());
443                            data.cfml.next();
444                            }
445                            if(text.length()>0)parent.addPrintOut(text.toString(), -1);
446                    }
447                    // no expression
448                    else {
449                            int start=data.cfml.getPos();
450                            data.cfml.next();
451                            int end=data.cfml.indexOfNext('<');
452                            String text;
453                            if(end==-1) {
454                                    text=data.cfml.substring(start);
455                                    data.cfml.setPos(data.cfml.length());
456                            }
457                            else {
458                                    text=data.cfml.substring(start,end-start);
459                                    data.cfml.setPos(end);
460                            }
461                            parent.addPrintOut(text, -1);
462                            
463                    }
464            }
465    
466            /**
467             * Liest einen Tag ein, prft hierbei ob das Tag innerhalb einer der geladenen Tag-Lib existiert, 
468             * ansonsten wird ein Tag einfach als literal-string aufgenommen.
469             * <br />
470             * EBNF:<br />
471             * <code>name-space identifier spaces attributes ("/>" | ">" [body "</" identifier spaces ">"]);(* Ob dem Tag ein Body und ein End-Tag folgt ist abh¦ngig von Definition des body-content in Tag-Lib, gleices gilt fr appendix *)</code>
472             * @param parent bergeornetes Tag
473             * @param parseExpression sollen Expresson innerhalb des Body geparste werden oder nicht.
474             * @return Gibt zurck ob es sich um ein Tag as einer Tag-Lib handelte oder nicht.
475             * @throws TemplateException
476             */
477            private boolean tag(Data data,Body parent,boolean parseExpression) throws TemplateException {
478                //railo.print.ln("--->"+data.cfml.getCurrent());
479                boolean hasBody=false;
480                    
481                    int line=data.cfml.getLine();
482                    //int column=data.cfml.getColumn();
483                    int start=data.cfml.getPos();
484                    data.cfml.next();
485                    
486                    // read in namesapce of tag
487                    TagLib tagLib=nameSpace(data);
488                    
489                    // return if no matching tag lib
490                    if(tagLib==null)        {
491                            data.cfml.previous();
492                            return false;
493                    }
494                                    
495                    // Get matching tag from tag lib
496                    String strNameNormal=identifier(data.cfml,false);
497                    if(strNameNormal==null) {
498                            data.cfml.setPos((data.cfml.getPos()-tagLib.getNameSpaceAndSeparator().length())-1);
499                            return false;
500                    }
501                    
502                    String strName=strNameNormal.toLowerCase();
503                    String appendix=null;
504                    TagLibTag tagLibTag=tagLib.getTag(strName);
505                    
506                    // get taglib
507                    if(tagLibTag==null)     {
508                            tagLibTag=tagLib.getAppendixTag(strName);
509                             if(tagLibTag==null)
510                                     throw new TemplateException(data.cfml,"undefined tag ["+tagLib.getNameSpaceAndSeparator()+strName+"]");
511                            appendix=StringUtil.removeStartingIgnoreCase(strNameNormal,tagLibTag.getName());
512                     }
513                    
514                    // CFXD Element 
515                    Tag tag;
516                    try {
517                            tag = tagLibTag.getTag(line,data.cfml.getLine());
518                    } 
519                    catch (Exception e) {
520                            throw new TemplateException(data.cfml,e);
521                    }
522                    parent.addStatement(tag);
523                    
524                    // get tag from tag library
525                     if(appendix!=null)     {
526                            tag.setAppendix(appendix);
527                            tag.setFullname(tagLibTag.getFullName().concat(appendix));
528                     }
529                     else {
530                             tag.setFullname(tagLibTag.getFullName());               
531                     }
532                     if(tag.getFullname().equalsIgnoreCase("cfcomponent"))data.page.setIsComponent(true);   // MUST to hardcoded, to better
533                     else if(tag.getFullname().equalsIgnoreCase("cfinterface"))data.page.setIsInterface(true);      // MUST to hardcoded, to better
534                             
535                    tag.setTagLibTag(tagLibTag);
536                    comment(data.cfml,true);
537                    
538                    // Tag Translator Evaluator
539                    if(tagLibTag.hasTteClass())     {
540                            data.ep.add(tagLibTag,tag,data.flibs,data.cfml);
541                    }
542                    
543                    //get Attributes
544                    attributes(data,tagLibTag,tag);
545                    
546                    if(tagLibTag.hasAttributeEvaluator()) {
547                            try {
548                                    tagLibTag=tagLibTag.getAttributeEvaluator().evaluate(tagLibTag,tag);
549                            } catch (AttributeEvaluatorException e) {
550                                    
551                                    throw new TemplateException(data.cfml, e);
552                            }
553                    }
554                    
555                    
556                    
557                    // End of begin Tag
558    //              TODO muss erlaubt sein
559                    if(data.cfml.forwardIfCurrent('>'))  {
560                            hasBody=tagLibTag.getHasBody();
561                    }
562                    else if(data.cfml.forwardIfCurrent('/','>')) {
563                            if(tagLibTag.getHasBody())tag.setBody(new BodyBase());
564                    }
565                    else {
566                            throw createTemplateException(data.cfml, "tag ["+tagLibTag.getFullName()+"] is not closed",tagLibTag);
567                    }
568                    
569    
570                    // Body
571                    if(hasBody)     {
572    
573                        
574                            // get Body
575                            if(tagLibTag.isTagDependent()) {
576                                    // get TagDependentBodyTransformer
577                                    TagDependentBodyTransformer tdbt=null;
578                                    try {
579                                            tdbt=tagLibTag.getBodyTransformer();
580                                    } catch (TagLibException e) {
581                                            throw new TemplateException(data.cfml,e);
582                                    }
583                                    if(tdbt==null) throw createTemplateException(data.cfml,"Tag dependent body Transformer is invalid for Tag ["+tagLibTag.getFullName()+"]",tagLibTag);
584                                    tdbt.transform(data.config,this,data.ep,data.flibs,tag,tagLibTag,data.cfml);
585                                    
586                                    //      get TagLib of end Tag
587                                    if(!data.cfml.forwardIfCurrent("</")) {
588                                            // MUST this is a patch, do a more proper implementation
589                                            TemplateException te = new TemplateException(data.cfml,"invalid construct");
590                                            if(tdbt!=null && tdbt instanceof CFMLScriptTransformer && ASMUtil.containsComponent(tag.getBody())) {
591                                                    throw new CFMLScriptTransformer.ComponentTemplateException(te);
592                                            }
593                                            throw te;
594                                    }
595                                    
596                                    TagLib tagLibEnd=nameSpace(data);
597                                    // same NameSpace
598                                    if(!(tagLibEnd!=null && tagLibEnd.getNameSpaceAndSeparator().equals(tagLib.getNameSpaceAndSeparator())))
599                                            throw new TemplateException(data.cfml,"invalid construct");
600                                    // get end Tag
601                                    String strNameEnd=identifier(data.cfml,true).toLowerCase();
602    
603                                    // not the same name Tag
604                                    if(!strName.equals(strNameEnd)) {
605                                            data.cfml.setPos(start);
606                                            throw new TemplateException(data.cfml,"Start and End Tag has not the same Name ["+tagLib.getNameSpaceAndSeparator()+strName+"-"+tagLibEnd.getNameSpaceAndSeparator()+strNameEnd+"]");                           
607                                     }
608                                     data.cfml.removeSpace();
609                                     if(!data.cfml.forwardIfCurrent('>'))
610                                             throw new TemplateException(data.cfml,"End Tag ["+tagLibEnd.getNameSpaceAndSeparator()+strNameEnd+"] not closed");
611                            }
612                            else {
613                                    // get body of Tag
614                                    BodyBase body=new BodyBase();
615                                    body.setParent(tag);
616                                    //tag.setBody(body);
617                                            //parseExpression=(tagLibTag.getParseBody())?true:parseExpression;
618                                    if(tagLibTag.getParseBody())parseExpression=true;
619                                    
620                                    while(true)     {
621                                            
622                                            // Load Expession Transformer from TagLib
623                                            ExprTransformer transfomer=null;
624                                            if(parseExpression) {
625                                                    try {
626                                                            transfomer = tagLibTag.getTagLib().getExprTransfomer();
627                                                    } catch (TagLibException e) {
628                                                            throw new TemplateException(data.cfml,e);
629                                                    }
630                                            }
631                                            
632    
633                                            // call body
634                                            
635                                        body(data,body,parseExpression,transfomer);
636                                       
637                                        
638                                        // no End Tag
639                                            if(data.cfml.isAfterLast()) {
640                                                
641                                                    if(tagLibTag.isBodyReq()) {
642                                                        data.cfml.setPos(start);
643                                                            throw createTemplateException(data.cfml,"No matching end tag found for tag ["+tagLibTag.getFullName()+"]",tagLibTag);
644                                                    }
645                                                    body.moveStatmentsTo(parent);
646                                                    return executeEvaluator(data,tagLibTag, tag);
647                                            }
648                                            
649                                            // Invalid Construct
650                                            int posBeforeEndTag=data.cfml.getPos();
651                                            if(!data.cfml.forwardIfCurrent('<','/'))
652                                                    throw createTemplateException(data.cfml,"Missing end tag for ["+tagLibTag.getFullName()+"]",tagLibTag);
653                                            
654                                            // get TagLib of end Tag
655                                            TagLib tagLibEnd=nameSpace(data);
656                                            
657                                            // same NameSpace
658                                            if(tagLibEnd!=null)     {
659                                                String strNameEnd="";
660                                                //railo.print.ln(data.cfml.getLine()+" - "+data.cfml.getColumn()+" - "+tagLibEnd.getNameSpaceAndSeperator()+".equals("+tagLib.getNameSpaceAndSeperator()+")");
661                                                if(tagLibEnd.getNameSpaceAndSeparator().equals(tagLib.getNameSpaceAndSeparator())) {
662                                                    
663                                                        // get end Tag
664                                                            strNameEnd=identifier(data.cfml,true).toLowerCase();
665                                                            // not the same name Tag
666                                                            
667                                                            // new part
668                                                            data.cfml.removeSpace();
669                                                            if(strName.equals(strNameEnd)) {
670                                                                if(!data.cfml.forwardIfCurrent('>'))
671                                                                            throw new TemplateException(data.cfml,"End Tag ["+tagLibEnd.getNameSpaceAndSeparator()+strNameEnd+"] not closed");
672                                                                    break;
673                                                            }
674                                                            
675                                                }
676                                                // new part
677                                                if(tagLibTag.isBodyReq()) {
678                                                            TagLibTag endTag = tagLibEnd.getTag(strNameEnd);
679                                                            if(endTag!=null && !endTag.getHasBody())
680                                                                    throw new TemplateException(data.cfml,
681                                                                                    "End Tag ["+
682                                                                                    tagLibEnd.getNameSpaceAndSeparator()+strNameEnd+"] is not allowed, for this tag only a Start Tag is allowed");
683                                                            data.cfml.setPos(start);
684                                                            
685                                                            throw new TemplateException(data.cfml,
686                                                                            "Start and End Tag has not the same Name ["+
687                                                                            tagLib.getNameSpaceAndSeparator()+strName+"-"+tagLibEnd.getNameSpaceAndSeparator()+strNameEnd+"]");
688                                                    }
689                                                    body.moveStatmentsTo(parent);
690                                                    data.cfml.setPos(posBeforeEndTag);
691                                                    return executeEvaluator(data,tagLibTag, tag);
692                                                /// new part        
693                                            }
694                                            body.addPrintOut("</",data.cfml.getLine());
695                                            
696                                    }
697                                    tag.setBody(body);
698                                    
699                            }
700                    }
701                    if(tag instanceof StatementBase)
702                            ((StatementBase)tag).setEndLine(data.cfml.getLine());
703                    // Tag Translator Evaluator
704                    
705                    return executeEvaluator(data,tagLibTag, tag);
706            
707            }
708            
709            private boolean executeEvaluator(Data data,TagLibTag tagLibTag, Tag tag) throws TemplateException {
710                    if(tagLibTag.hasTteClass())     {
711                            try {
712                                    TagLib lib=tagLibTag.getEvaluator().execute(data.config,tag,tagLibTag,data.flibs,data.cfml);
713                                    if(lib!=null) {
714                                            // set
715                                            for(int i=0;i<data.tlibs[TAG_LIB_PAGE].length;i++) {
716                                    if(data.tlibs[TAG_LIB_PAGE][i].getNameSpaceAndSeparator().equalsIgnoreCase(lib.getNameSpaceAndSeparator())){
717                                            boolean extIsCustom=data.tlibs[TAG_LIB_PAGE][i] instanceof CustomTagLib;
718                                            boolean newIsCustom=lib instanceof CustomTagLib;
719                                            // TagLib + CustomTagLib (visa/versa)
720                                            if(extIsCustom){
721                                                    ((CustomTagLib)data.tlibs[TAG_LIB_PAGE][i]).append(lib);
722                                                    return true;
723                                            }
724                                            else if(newIsCustom){
725                                                    ((CustomTagLib)lib).append(data.tlibs[TAG_LIB_PAGE][i]);
726                                                    data.tlibs[TAG_LIB_PAGE][i]=lib;
727                                                    return true;
728                                            }
729                                    }
730                                }
731                                            // TODO make sure longer namespace ar checked firts to support subsets, same for core libs
732                                            // insert
733                                TagLib[] newTlibs=new TagLib[data.tlibs[TAG_LIB_PAGE].length+1]; 
734                        for(int i=0;i<data.tlibs[TAG_LIB_PAGE].length;i++) {
735                            newTlibs[i]=data.tlibs[TAG_LIB_PAGE][i];
736                        }
737                        newTlibs[data.tlibs[TAG_LIB_PAGE].length]=lib;
738                        data.tlibs[TAG_LIB_PAGE]=newTlibs;    
739                                    }
740                            } 
741                catch (EvaluatorException e) {
742                    throw new TemplateException(data.cfml,e);
743                }
744                    }
745                    return true;
746            }
747            
748            /**
749             * Vergleicht folgende Zeichen mit den Namespacedefinitionen der Tag Libraries,
750             * gibt eine Tag-Lib zurck falls eine passt, ansonsten null. 
751             * <br />
752             * EBNF:<br />
753             * <code>< tagLib[].getNameSpaceAndSeperator() >(* Vergleicht Zeichen mit den Namespacedefinitionen der Tag Libraries. *) </code>
754             * @return TagLib Passende Tag Lirary oder null.
755             */
756            private TagLib nameSpace(Data data) {
757                    boolean hasTag=false;
758                    int start = data.cfml.getPos();
759                    TagLib tagLib=null;
760                    
761                    // loop over NameSpaces
762                    for(int i=1;i>=0;i--)        {
763                            for(int ii=0;ii<data.tlibs[i].length;ii++)   {
764                                    tagLib= data.tlibs[i][ii];
765                                    char[] c=tagLib.getNameSpaceAndSeperatorAsCharArray();
766                                    // Loop over char of NameSpace and Sepearator
767                                    hasTag=true;
768                                    for(int y=0;y<c.length;y++)  {
769                                            if(!(data.cfml.isValidIndex() && c[y]==data.cfml.getCurrentLower())) {
770                                                    //hasTag=true;
771                                            //} else {
772                                                    hasTag=false;
773                                                    data.cfml.setPos(start);
774                                                    break;
775                                            }
776                                            data.cfml.next();
777                                    }
778                                    if(hasTag)return tagLib;//break;
779                            }
780                            //if(hasTag) return tagLib;
781                    }
782                    return null;
783            }
784    
785            /**
786             * Liest die Attribute eines Tags ein, dies Abh¦ngig von der Definition innerhalb der Tag-Lib.
787             * Hierbei unterscheiden wir vier verschiedene Arten von Attributen:<br>
788             * <ul>
789             * <li>FIX: Definierte Attribute Fix, fr jedes Attribut ist definiert ob es required ist oder nicht (gleich wie JSP). </li>
790             * <li>DYNAMIC: Die Attribute des Tag sind frei, keine Namen sind vorgegeben. 
791             * Es kann aber definiert sein wieviele Attribute maximal und minimal verwendetet werden drfen.</li>
792             * <li>FULLDYNAMIC: Gleich wie DYNAMIC, jedoch kann der Name des Attribut auch ein dynamischer Wert sein (wie bei cfset).</li>
793             * <li>NONAME: Ein Tag welches nur ein Attribut besitzt ohne Name, sondern einfach nur mit einem Attribut Wert</li>
794             * </ul>
795             * <br />
796             * EBNF:<br />
797             * <code>({spaces attribute} "/>" | {spaces attribute} ">") | attribute-value;(* Welcher Teil der "oder" Bedingung ausgefhrt wird, ist abh¦ngig von der Tag Attribute Definition in der Tag Lib. *)</code>
798             * @param tag
799             * @param parent
800             * @throws TemplateException
801             */
802            public static void attributes(Data data,TagLibTag tag, Tag parent) throws TemplateException {
803                    int type=tag.getAttributeType();
804                    
805            // Tag with attribute names
806                    if(     type!=TagLibTag.ATTRIBUTE_TYPE_NONAME)  {
807                            int min=tag.getMin();
808                            int max=tag.getMax();
809                            int count=0;
810                            ArrayList<String> args=new ArrayList<String>();
811                            RefBoolean allowDefaultValue=new RefBooleanImpl(tag.getDefaultAttribute()!=null);
812                            while(data.cfml.isValidIndex()) {
813                                    data.cfml.removeSpace();
814                                    // if no more attributes break
815                                    if(data.cfml.isCurrent('/') || data.cfml.isCurrent('>')) break;
816                                    
817                                    parent.addAttribute(attribute(data,tag,args,allowDefaultValue));
818                                    count++;                
819                            } 
820                
821                            // set default values
822                            if(tag.hasDefaultValue()) {
823                                Map<String, TagLibTagAttr> hash = tag.getAttributes();
824                                    Iterator<String> it = hash.keySet().iterator();
825                            
826                                    while(it.hasNext())     {
827                                            TagLibTagAttr att=(TagLibTagAttr) hash.get(it.next());
828                                            if(!parent.containsAttribute(att.getName()) && att.hasDefaultValue())   {
829                                            
830                                                    Attribute attr=new Attribute(tag.getAttributeType()==TagLibTag.ATTRIBUTE_TYPE_DYNAMIC,
831                                                            att.getName(),
832                                                            Cast.toExpression(LitString.toExprString(Caster.toString(att.getDefaultValue(),null), -1),att.getType()),att.getType()
833                                            );
834                                            parent.addAttribute(attr);
835                                            }
836                                    }
837                            }
838                            
839                            boolean hasAttributeCollection=args.contains("attributecollection");
840                            
841                            // to less attributes
842                            if(!hasAttributeCollection && min>count)
843                                    throw createTemplateException(data.cfml,"the tag "+tag.getFullName()+" must have "+min+" attributes at least",tag);
844                            
845                            // too much attributes
846                            if(!hasAttributeCollection && max>0 && max<count)
847                                    throw createTemplateException(data.cfml,"the tag "+tag.getFullName()+" can have "+max+" attributes maximal",tag);
848                            
849                            // not defined attributes
850                            if(type==TagLibTag.ATTRIBUTE_TYPE_FIXED || type==TagLibTag.ATTRIBUTE_TYPE_MIXED)        {
851                                    Map<String, TagLibTagAttr> hash = tag.getAttributes();
852                                    Iterator<String> it = hash.keySet().iterator();
853                                    
854                                    while(it.hasNext())     {
855                                            TagLibTagAttr att=(TagLibTagAttr) hash.get(it.next());
856                                            if(att.isRequired() && !args.contains(att.getName()) && att.getDefaultValue()==null)    {
857                                                    if(!hasAttributeCollection)throw createTemplateException(data.cfml,"attribute "+att.getName()+" is required for tag "+tag.getFullName(),tag);
858                                                    parent.addMissingAttribute(att.getName(),att.getType());
859                                            }
860                                    }
861                            }
862                    }
863            // tag without attributes name
864                    else    {
865                            TagLibTagAttr attr=tag.getFirstAttribute();
866                            String strName="noname";
867                            String strType="any";
868                            boolean pe=true;
869                            if(attr!=null) {
870                                    strName=attr.getName();
871                                    strType=attr.getType(); 
872                                    pe=attr.getRtexpr();
873                            }
874                            //LitString.toExprString("",-1);
875                            Attribute att=new Attribute(false,strName,attributeValue(data,tag,strType,pe,true,NullExpression.NULL_EXPRESSION),strType);
876                            parent.addAttribute(att);
877                    }
878            }
879    
880        /**
881         * Liest ein einzelnes Atribut eines tag ein (nicht NONAME).
882         * <br />
883         * EBNF:<br />
884         * <code>attribute-name  spaces "=" spaces attribute-value;</code>
885         * @param tag Definition des Tag das dieses Attribut enth¦lt.
886         * @param args Container zum Speichern einzelner Attribute Namen zum nachtr¦glichen Prufen gegen die Tag-Lib.
887         * @return Element Attribute Element.
888         * @throws TemplateException
889         */
890        private static Attribute attribute(Data data,TagLibTag tag, ArrayList<String> args,RefBoolean allowDefaultValue) throws TemplateException {
891            Expression value=null;
892            
893            // Name
894            StringBuffer sbType=new StringBuffer();
895            RefBoolean dynamic=new RefBooleanImpl(false);
896            boolean isDefaultValue=false;
897            boolean[] parseExpression=new boolean[2];
898            parseExpression[0]=true;
899            parseExpression[1]=false;
900            String name=attributeName(data.cfml,dynamic,args,tag,sbType,parseExpression,allowDefaultValue.toBooleanValue());
901            
902            // mixed in a noname attribute
903            if(StringUtil.isEmpty(name)){
904                    allowDefaultValue.setValue(false);
905                    TagLibTagAttr attr = tag.getDefaultAttribute();
906                    if(attr==null)
907                            throw new TemplateException(data.cfml,"Invalid Identifer.");
908                    name=attr.getName();
909                    sbType.append(attr.getType());
910                    isDefaultValue=true;
911            }
912            
913            
914            comment(data.cfml,true);
915            
916            if(isDefaultValue || data.cfml.forwardIfCurrent('='))   {
917                    comment(data.cfml,true);
918                    // Value
919                    value=attributeValue(data,tag,sbType.toString(),parseExpression[0],false,LitString.toExprString("",-1));        
920            }
921            // default value boolean true
922            else {
923                    value=LitBoolean.toExprBoolean(true, -1);
924                    if(sbType.toString().length()>0) {
925                            value=Cast.toExpression(value, sbType.toString());
926                    }
927            }               
928            comment(data.cfml,true);
929            
930            return new Attribute(dynamic.toBooleanValue(),name,value,sbType.toString());
931        }
932    
933            /**
934             * Liest den Namen eines Attribut ein, je nach Attribut-Definition innerhalb der Tag-Lib, 
935             * wird der Name ber den identifier oder den Expression Transformer eingelesen.
936             * <ul>
937             * <li>FIX und DYNAMIC --> identifier </li>
938             * <li>FULLDYNAMIC --> Expression Transformer </li>
939             * </ul>
940             * <br />
941             * EBNF:<br />
942             * <code>("expression"|'expression'|expression) | identifier;(* Ruft identifier oder den Expression Transformer auf je nach Attribute Definition in der Tag Lib. *)</code>
943             * @param dynamic 
944             * @param args Container zum Speichern einzelner Attribute Namen zum nachtr¦glichen Prufen gegen die Tag-Lib.
945             * @param tag Aktuelles tag aus der Tag-Lib
946             * @param sbType Die Methode speichert innerhalb von sbType den Typ des Tags, zur Interpretation in der attribute Methode.
947             * @param parseExpression Soll der Wert des Attributes geparst werden
948             * @return Attribute Name
949             * @throws TemplateException
950             */
951            private static String attributeName(CFMLString cfml,RefBoolean dynamic, ArrayList<String> args, TagLibTag tag, 
952                            StringBuffer sbType, boolean[] parseExpression,boolean allowDefaultValue) throws TemplateException {
953                    
954                    String _id = identifier(cfml,!allowDefaultValue);
955                    if(StringUtil.isEmpty(_id)){
956                            return null;
957                    }
958                    
959                    int typeDef=tag.getAttributeType();
960                    String id=StringUtil.toLowerCase(_id);
961            if(args.contains(id)) throw createTemplateException(cfml,"you can't use the same tag attribute ["+id+"] twice",tag);
962                    args.add(id);
963                    
964                    if("attributecollection".equals(id)){
965                            dynamic.setValue(tag.getAttribute(id)==null);
966                            sbType.append("struct");
967                            parseExpression[0]=true;
968                            parseExpression[1]=true;
969                    }
970                    else if(typeDef==TagLibTag.ATTRIBUTE_TYPE_FIXED || typeDef==TagLibTag.ATTRIBUTE_TYPE_MIXED) {
971                            TagLibTagAttr attr=tag.getAttribute(id);
972                            if(attr==null) {
973                                    if(typeDef==TagLibTag.ATTRIBUTE_TYPE_FIXED) {
974                                            String names=tag.getAttributeNames();
975                                            if(StringUtil.isEmpty(names))
976                                                    throw createTemplateException(cfml,
977                                                                    "Attribute "+id+" is not allowed for tag "+tag.getFullName(),tag);
978                                            
979                                                    throw createTemplateException(cfml,
980                                                            "Attribute "+id+" is not allowed for tag "+tag.getFullName(),
981                                                            "valid attribute names are ["+names+"]",tag);
982                                    }
983                                    dynamic.setValue(true);
984                            }
985                            else {
986                                    sbType.append(attr.getType());
987                                    parseExpression[0]=attr.getRtexpr();
988                            }
989                    }
990                    else if(typeDef==TagLibTag.ATTRIBUTE_TYPE_DYNAMIC){
991                            dynamic.setValue(true);
992                    }
993                    return id;
994            }
995            
996            
997            
998            
999            
1000    
1001    
1002            /**
1003             * Liest den Wert eines Attribut, mithilfe des innerhalb der Tag-Lib definierten Expression Transformer, ein.
1004              * <br />
1005             * EBNF:<br />
1006             * <code>expression;</code>
1007             * @param tag
1008             * @param type
1009             * @param parseExpression
1010             * @param isNonName
1011             * @return Element Eingelesener bersetzer Wert des Attributes.
1012             * @throws TemplateException
1013             */
1014            public static Expression attributeValue(Data data,TagLibTag tag, String type,boolean parseExpression,boolean isNonName, Expression noExpression) throws TemplateException {
1015                    Expression expr;
1016                    try {
1017                            ExprTransformer transfomer=null;
1018                            if(parseExpression){
1019                                transfomer = tag.getTagLib().getExprTransfomer();
1020                            }
1021                            else  {
1022                                    if(data.set==null) {
1023                                            data.set=new SimpleExprTransformer('#');
1024                                            //set.setSpecialChar();
1025                                    }
1026                                    transfomer=data.set;                            
1027                            }
1028                            if(isNonName) {
1029                                int pos=data.cfml.getPos();
1030                                try {
1031                                expr=transfomer.transform(data.ep,data.flibs,data.cfml);
1032                                }
1033                                catch(TemplateException ete) {
1034                                   if(data.cfml.getPos()==pos)expr=noExpression;
1035                                   else throw ete;
1036                                }
1037                            }
1038                            else expr=transfomer.transformAsString(data.ep,data.flibs,data.cfml,true);
1039                            if(type.length()>0) {
1040                                    expr=Cast.toExpression(expr, type);
1041                            }
1042                    } catch (TagLibException e) {
1043                            throw new TemplateException(data.cfml,e);
1044                    } 
1045                    return expr;
1046            }
1047            
1048            /**
1049             * Liest einen Identifier ein und gibt diesen als String zurck.
1050              * <br />
1051             * EBNF:<br />
1052             * <code>(letter | "_") {letter | "_"|digit};</code>
1053             * @param throwError throw error or return null if name is invalid
1054             * @return Identifier String.
1055             * @throws TemplateException
1056             */
1057            public static String identifier(CFMLString cfml,boolean throwError) throws TemplateException  {
1058                    int start = cfml.getPos();
1059                    if(!cfml.isCurrentBetween('a','z') && !cfml.isCurrent('_')) {
1060                            if(throwError)throw new TemplateException(cfml,"Invalid Identifer.");
1061                            return null;
1062                    }
1063                    do {
1064                            cfml.next();
1065                            if(!(cfml.isCurrentBetween('a','z')
1066                                    || cfml.isCurrentBetween('0','9')
1067                                    || cfml.isCurrent('_')
1068                                    || cfml.isCurrent(':')
1069                                    || cfml.isCurrent('-'))) {
1070                                            break;
1071                                    }
1072                    }
1073                    while (cfml.isValidIndex());
1074                    return cfml.substring(start,cfml.getPos()-start);
1075            }
1076            
1077            
1078            public static TemplateException createTemplateException(CFMLString cfml,String msg, String detail,TagLibTag tag) {
1079                    TemplateException te = new TemplateException(cfml,msg,detail);
1080                    setAddional(te,tag);
1081                    return te;
1082            }
1083            public static TemplateException createTemplateException(CFMLString cfml,String msg, TagLibTag tag) {
1084                    TemplateException te = new TemplateException(cfml,msg);
1085                    setAddional(te,tag);
1086                    return te;
1087            }
1088            
1089            public static TemplateException setAddional(TemplateException te, TagLibTag tlt) {
1090                    setAddional((PageExceptionImpl)te, tlt);
1091                    return te;
1092            }
1093            
1094            public static ApplicationException setAddional(ApplicationException ae, TagLibTag tlt) {
1095                    setAddional((PageExceptionImpl)ae, tlt);
1096                    return ae;
1097            }
1098    
1099    
1100            private static void setAddional(PageExceptionImpl pe, TagLibTag tlt) {
1101                    Map<String, TagLibTagAttr> attrs = tlt.getAttributes();
1102                    Iterator<Entry<String, TagLibTagAttr>> it = attrs.entrySet().iterator();
1103                    Entry<String, TagLibTagAttr> entry;
1104                    TagLibTagAttr attr;
1105                    
1106                    // Pattern
1107                    StringBuilder pattern=new StringBuilder("<");
1108                    pattern.append((tlt.getFullName()));
1109                    StringBuilder req=new StringBuilder();
1110                    StringBuilder opt=new StringBuilder();
1111                    StringBuilder tmp;
1112                    
1113                    
1114                    pattern.append(" ");
1115                    int c=0;
1116                    while(it.hasNext()){
1117                            entry = it.next();
1118                            attr=entry.getValue();
1119                            tmp=attr.isRequired()?req:opt;
1120                            
1121                            tmp.append(" ");
1122                            if(!attr.isRequired()) tmp.append("[");
1123                            if(c++>0)pattern.append(" ");
1124                            tmp.append(attr.getName());
1125                            tmp.append("\"");
1126                            tmp.append(attr.getType());
1127                            tmp.append("\"");
1128                            if(!attr.isRequired()) tmp.append("]");
1129                    }
1130    
1131                    if(req.length()>0)pattern.append(req);
1132                    if(opt.length()>0)pattern.append(opt);
1133                    
1134                    if(tlt.getAttributeType()==TagLibTag.ATTRIBUTE_TYPE_MIXED || tlt.getAttributeType()==TagLibTag.ATTRIBUTE_TYPE_DYNAMIC)
1135                            pattern.append(" ...");
1136                    pattern.append(">");
1137                    if(tlt.getHasBody()) {
1138                            if(tlt.isBodyReq()){
1139                                    pattern.append("</");
1140                                    pattern.append(tlt.getFullName());
1141                                    pattern.append(">");
1142                            }
1143                            else if(tlt.isBodyFree()){
1144                                    pattern.append("[</");
1145                                    pattern.append(tlt.getFullName());
1146                                    pattern.append(">]");
1147                            }
1148                    }
1149                    
1150                    pe.setAdditional("Pattern", pattern);
1151                    
1152                    // Documentation
1153                    StringBuilder doc=new StringBuilder(tlt.getDescription());
1154                    req=new StringBuilder();
1155                    opt=new StringBuilder();
1156                    
1157                    doc.append("\n");
1158                    
1159                    it = attrs.entrySet().iterator();
1160                    while(it.hasNext()){
1161                            entry = it.next();
1162                            attr=entry.getValue();
1163                            tmp=attr.isRequired()?req:opt;
1164                            
1165                            tmp.append("* ");
1166                            tmp.append(attr.getName());
1167                            tmp.append(" (");
1168                            tmp.append(attr.getType());
1169                            tmp.append("): ");
1170                            tmp.append(attr.getDescription());
1171                            tmp.append("\n");
1172                    }
1173    
1174                    if(req.length()>0)doc.append("\nRequired:\n").append(req);
1175                    if(opt.length()>0)doc.append("\nOptional:\n").append(opt);
1176                    
1177                    pe.setAdditional("Documentation", doc);
1178            }
1179            
1180    }
1181    
1182    
1183    
1184    
1185    
1186    
1187    
1188