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