001// Copyright (c) 2001 Hursh Jain (http://www.mollypages.org) 002// The Molly framework is freely distributable under the terms of an 003// MIT-style license. For details, see the molly pages web site at: 004// http://www.mollypages.org/. Use, modify, have fun ! 005 006package fc.util.pagetemplate; 007 008import java.io.*; 009import java.util.*; 010import java.util.regex.*; 011import fc.io.*; 012import fc.util.*; 013 014/* 015NOTES 016 017Code blocks of the form 018 [...] 019cause problems with java arrays 020 021String[] or foo[4] etc.., craps out. So we need to use 022 [[...]] 023for the molly code blocks 024 025The order of switch'es in a case statement in various methods is not always arbitrary 026(the order matters in this sort of recursive descent parsing) 027 028Read www.mollypages.org/page/grammar/index.mp for a intro to parsing 029*/ 030 031/** 032Parses a template page and writes out the corresponding java file to the specified 033output. The parser and scanner is combined into one class here for simplicity (a 034seperate scanner is overkill for a simple LL(1) grammar such as page templates). 035 036@author hursh jain 037*/ 038public final class TemplateParser 039{ 040private static final boolean dbg = false; 041private static final int EOF = -1; 042private int dbgtab = 0; 043 044String classname; 045String packagename = TemplatePage.PACKAGE_NAME; 046TemplateReader in; 047PrintWriter out; 048Log log; 049File inputFile; 050File outputFile; 051boolean includeMode = false; 052 053//Read data 054//we use these since stringbuffer/builders do not have a clear/reset function 055CharArrayWriter buf = new CharArrayWriter(4096); 056CharArrayWriter wsbuf = new CharArrayWriter(32); // ^(whitespace)* 057int c = EOF; 058 059//PageData 060List decl = new ArrayList(); //declarations 061List inc_decl = new ArrayList(); //external included declarations 062List imps = new ArrayList(); //imports 063List tree = new ArrayList(); //code, exp, text etc. 064Map directives = new HashMap(); //page options 065Set circularityTrack = new HashSet(); //track pages already included to stop circular refs 066 067/** The name of the ("remove-initial-whitespace") directive */ 068public static String d_remove_initial_emptylines = "remove-initial-whitespace"; 069/** The name of the ("remove-all-emptylines") directive */ 070public static String d_remove_all_emptylines = "remove-all-emptylines"; 071 072/* 073This constructor for internal use. 074 075The parser can be invoked recursively to parse included files as 076well..that's what the includeMode() does (and this construtor is invoked 077when including). When including, we already have a output writer 078created, we use that writer (instead of creating a new one based on 079src_encoding as we do for in normal page parsing mode). 080 081@param contextRoot absolute path to the webapp context root directory 082@param input absolute path to the input page file 083@param input absolute path to the output file (to be written to). 084@param classname classname to give to the generated java class. 085*/ 086private TemplateParser(File input, PrintWriter outputWriter, String classname, Log log) 087throws IOException 088 { 089 this.inputFile = input; 090 this.in = new TemplateReader(input); 091 this.out = outputWriter; 092 this.classname = classname; 093 this.log = log; 094 095 circularityTrack.add(input.getAbsolutePath()); 096 } 097 098 099/** 100Creates a new page parser. 101 102@param contextRoot absolute path to the webapp context root directory 103@param input absolute path to the input page file 104@param output absolute path to the output file (to be written to). 105@param classname classname to give to the generated java class. 106@log log destination for internal logging output. 107*/ 108public TemplateParser(File input, File output, String classname, Log log) 109throws IOException 110 { 111 this.inputFile = input; 112 this.in = new TemplateReader(input); 113 this.outputFile = output; 114 this.classname = classname; 115 this.log = log; 116 117 circularityTrack.add(input.getAbsolutePath()); 118 } 119 120void append(final int c) 121 { 122 Argcheck.istrue(c >= 0, "Internal error: recieved c=" + c); 123 buf.append((char)c); 124 } 125 126void append(final char c) 127 { 128 buf.append(c); 129 } 130 131void append(final String str) 132 { 133 buf.append(str); 134 } 135 136/* not used anymore */ 137TemplateParser includeMode() 138 { 139 includeMode = true; 140 return this; 141 } 142 143/** 144Parses the page. If the parse is successful, the java source will be 145generated. 146 147@throws IOException a parse failure occurred. The java source file 148 may or may not be properly generated or written 149 in this case. 150*/ 151public void parse() throws IOException 152 { 153 parseText(); 154 if (! includeMode) { 155 writePage(); 156 out.close(); 157 } 158 else{ 159 out.flush(); 160 } 161 in.close(); 162 } 163 164//util method for use in the case '[' branch of parseText below. 165private Text newTextNode() 166 { 167 Text text = new Text(buf); 168 tree.add(text); 169 buf.reset(); 170 return text; 171 } 172 173void parseText() throws IOException 174 { 175 if (dbg) dbgenter(); 176 177 while (true) 178 { 179 c = in.read(); 180 181 if (c == EOF) { 182 tree.add(new Text(buf)); 183 buf.reset(); 184 break; 185 } 186 187 switch (c) 188 { 189 //Escape start tags 190 case '\\': 191 /* we don't need to do this: previously, expressions 192 were [...] but now they are [=...], previously we needed 193 to escape \[[ entirely (since if we escaped \[ the second 194 [ would start an expression 195 */ 196 /* 197 if (in.match("[[")) 198 append("[["); 199 */ 200 //escape only \[... otherwise leave \ alone 201 if (in.match("[")) 202 append("["); 203 else 204 append(c); 205 break; 206 207 case '[': 208 /* suppose we have 209 \[[ 210 escape handling above will capture \[ 211 then the second '[' drops down here. Good so far. 212 But we must not create a new text object here by 213 default...only if we see another [[ or [= or [include or 214 whatever. 215 */ 216 /* 217 But creating a text object at the top is easier 218 then repeating this code at every if..else branch below 219 but this creates superfluous line breaks. 220 221 hello[haha]world 222 -->prints as--> 223 hello (text node 1) 224 [haha] (text node 2) 225 world (text node 3) 226 --> we want 227 hello[haha]world (text node 1) 228 */ 229 230 if (in.match('[')) { 231 newTextNode(); 232 parseCode(); 233 } 234 else if (in.match('=')) { 235 Text text = newTextNode(); 236 parseExpression(text); 237 } 238 else if (in.match('!')) { 239 newTextNode(); 240 parseDeclaration(); 241 } 242 else if (in.match("/*")) { 243 newTextNode(); 244 parseComment(); 245 } 246 else if (in.matchIgnoreCase("page")) { 247 newTextNode(); 248 parseDirective(); 249 } 250 else if (in.matchIgnoreCase("include-file")) { 251 newTextNode(); 252 parseIncludeFile(); 253 } 254 else if (in.matchIgnoreCase("include-decl")) { 255 newTextNode(); 256 parseIncludeDecl(); 257 } 258 else if (in.matchIgnoreCase("import")) { 259 newTextNode(); 260 parseImport(); 261 } 262 else { 263 //System.out.println("c1=" + (char)c); 264 append(c); 265 } 266 break; 267 268 default: 269 //System.out.println("c2=" + (char)c); 270 append(c); 271 272 } //switch 273 } //while 274 275 if (dbg) dbgexit(); 276 } 277 278void parseCode() throws IOException 279 { 280 if (dbg) dbgenter(); 281 282 int startline = in.getLine(); 283 int startcol = in.getCol(); 284 285 while (true) 286 { 287 c = in.read(); 288 289 switch (c) /* the order of case tags is important. */ 290 { 291 case EOF: 292 unclosed("code", startline, startcol); 293 if (dbg) dbgexit(); 294 return; 295 296 case '/': //Top level: // and /* comments 297 append(c); 298 c = in.read(); 299 append(c); 300 if (c == '/') 301 appendCodeSlashComment(); 302 else if (c == '*') 303 appendCodeStarComment(); 304 break; 305 306 case '"': //strings outside of any comment 307 append(c); 308 appendCodeString(); 309 break; 310 311 case '\'': 312 append(c); 313 appendCodeCharLiteral(); 314 break; 315 316 case ']': 317 if (in.match(']')) { 318 tree.add(new Code(buf)); 319 buf.reset(); 320 if (dbg) dbgexit(); 321 return; 322 } 323 else { 324 append(c); 325 } 326 break; 327 328 /* 329 a hash by itself on a line starts a hash section. 330 whitespace before the # on that line is used as an 331 printing 'out' statements for that hash. 332 333 for (int n = 0; n < ; n++) { 334 ....# foo # 335 | } 336 |=> 4 spaces 337 so nice if generated code looked like: 338 339 for (int n = 0; n < ; n++) { 340 out.print(" foo "); 341 } 342 */ 343 case '\n': 344 case '\r': 345 append(c); //the \n or \r just read 346 readToFirstNonWS(); //won't read past more newlines 347 //is '#' is first non-ws on this line ? 348 c = in.read(); 349 if (c == '#') { 350 tree.add(new Code(buf)); 351 buf.reset(); 352 //whitespace provides indentation offset 353 parseHash(wsbuf.toString()); 354 } 355 else{ 356 append(wsbuf.toString()); //wsbuf contains codetext 357 //let other cases also handle first non-ws or EOF 358 in.unread(); 359 } 360 break; 361 362 /* in this case, hash does not start on a new line, like: 363 for (...) { # 364 */ 365 case '#': 366 tree.add(new Code(buf)); 367 buf.reset(); 368 parseHash(null); 369 break; 370 371 default: 372 append(c); 373 } //switch 374 } //while 375 } 376 377void parseHash(String offset) throws IOException 378 { 379 if (dbg) dbgenter(); 380 381 int startline = in.getLine(); 382 int startcol = in.getCol(); 383 384 while (true) 385 { 386 c = in.read(); 387 388 switch (c) 389 { 390 case EOF: 391 unclosed("hash", startline, startcol); 392 if (dbg) dbgexit(); 393 return; 394 395 //special case: very common and would be a drag to escape 396 //this every time: 397 // # <table bgcolor="#ffffff">.... # 398 //Now, all of: 399 // bgcolor="#xxx" 400 // bgcolor='#xxx' 401 // bgcolor="\#xxx" 402 //will work the same and give: bgcolor="#xxx" 403 //1) 404 //However to get a: 405 // bgcolor=#xxx (no quoted around #xxx) 406 //we still have to say: 407 // bgcolor=\#xxx 408 //2) 409 //Of course, since we special case this, then: 410 // #"bar"# 411 // that ending # is lost and we end up with 412 // #"bar" with no closing hash 413 //So we need to make sure that we write: 414 // #"bar" # 415 // instead 416 417 case '\'': 418 case '"': 419 append(c); 420 if (in.match('#')) 421 append('#'); 422 break; 423 424 case '\\': 425 if (in.match('[')) 426 append('['); 427 else if (in.match('#')) 428 append('#'); 429 else 430 append(c); 431 break; 432 433 case '[': 434 if (in.match('=')) { 435 Hash hash = new Hash(offset, buf); 436 tree.add(hash); 437 buf.reset(); 438 parseExpression(hash); 439 } 440 else{ 441 append(c); 442 } 443 break; 444 445 /* 446 this case is not needed but is a bit of a optimization 447 for (int n = 0; n < 1; n++) { 448 # 449 foo 450 ....#...NL 451 } 452 avoids printing the dots (spaces) and NL in this case 453 (the newline after foo is still printed) 454 */ 455 case '\n': 456 case '\r': 457 append(c); 458 readToFirstNonWS(); 459 c = in.read(); 460 //'#' is first non-ws on the line 461 if (c == '#') { 462 tree.add(new Hash(offset, buf)); 463 buf.reset(); 464 //skipIfWhitespaceToEnd(); 465 if (dbg) dbgexit(); 466 return; 467 } 468 else { 469 append(wsbuf.toString()); 470 in.unread(); //let other cases also handle first non-ws 471 } 472 break; 473 474 case '#': 475 tree.add(new Hash(offset, buf)); 476 //skipIfWhitespaceToEnd(); 477 buf.reset(); 478 if (dbg) dbgexit(); 479 return; 480 481 default: 482 append(c); 483 } //switch 484 } //while 485 } 486 487/** 488[page <<<FOO] 489...as-is..no parse, no interpolation.. 490FOO 491*/ 492void parseHeredoc(StringBuilder directives_buf) throws IOException 493 { 494 if (dbg) dbgenter(); 495 496 int startline = in.getLine(); 497 int startcol = in.getCol(); 498 499 int i = directives_buf.indexOf("<<<"); /* "<<<".length = 3 */ 500 CharSequence subseq = directives_buf.substring( 501 i+3, 502 /*directives_buf does not have a ending ']' */ 503 directives_buf.length() 504 ); 505 506 final String heredoc = subseq.toString().trim(); 507 final int heredoc_len = heredoc.length(); 508 final CharArrayWriter heredoc_buf = new CharArrayWriter(2048); 509 510 /* 511 the ending heredoc after newline speeds things up a bit 512 which is why is traditionally used i guess, otherwise 513 we have to try a full match every first match. this 514 implementation doesn't care where the ending heredoc 515 appears (can be anywhere)...simplifies the implementation. 516 */ 517 518 while (true) 519 { 520 c = in.read(); 521 522 if (c == EOF) { 523 unclosed("heredoc: <<<"+heredoc, startline, startcol); 524 break; 525 } 526 527 if (c == heredoc.charAt(0)) 528 { 529 boolean matched = true; 530 if (heredoc_len > 1) { 531 matched = in.match(heredoc.substring(1)); 532 } 533 if (matched) { 534 tree.add(new Heredoc(heredoc_buf)); 535 break; 536 } 537 } 538 539 //default action 540 heredoc_buf.append((char)c); 541 } //while 542 543 if (dbg) dbgexit(); 544 } 545 546/* 547Text is the parent node for the expression. A new expression is parsed, 548created and added to the text object by this method 549*/ 550void parseExpression(Element parent) throws IOException 551 { 552 if (dbg) dbgenter(); 553 554 int startline = in.getLine(); 555 int startcol = in.getCol(); 556 557 while (true) 558 { 559 c = in.read(); 560 561 switch (c) 562 { 563 case EOF: 564 unclosed("expression", startline, startcol); 565 if (dbg) dbgexit(); 566 return; 567 568 case '\\': 569 if (in.match(']')) 570 append(']'); 571 else 572 append(c); 573 break; 574 575 case ']': 576 if (buf.toString().trim().length() == 0) 577 error("Empty expression not allowed", startline, startcol); 578 parent.addExp(new Exp(buf)); 579 buf.reset(); 580 if (dbg) dbgexit(); 581 return; 582 583 default: 584 append(c); 585 } 586 } 587 } 588 589void parseComment() throws IOException 590 { 591 if (dbg) dbgenter(); 592 593 int startline = in.getLine(); 594 int startcol = in.getCol(); 595 596 while (true) 597 { 598 c = in.read(); 599 600 switch (c) 601 { 602 case EOF: 603 unclosed("comment", startline, startcol); 604 if (dbg) dbgexit(); 605 return; 606 607 case '*': 608 if (in.match("/]")) 609 { 610 tree.add(new Comment(buf)); 611 buf.reset(); 612 if (dbg) dbgexit(); 613 return; 614 } 615 else 616 append(c); 617 break; 618 619 default: 620 append(c); 621 } 622 } 623 } 624 625void parseDeclaration() throws IOException 626 { 627 if (dbg) dbgenter(); 628 int startline = in.getLine(); 629 int startcol = in.getCol(); 630 631 while (true) 632 { 633 c = in.read(); 634 635 switch (c) 636 { 637 case EOF: 638 unclosed("declaration", startline, startcol); 639 if (dbg) dbgexit(); 640 return; 641 642 case '!': 643 if (in.match(']')) { 644 decl.add(new Decl(buf)); 645 buf.reset(); 646 if (dbg) dbgexit(); 647 return; 648 } 649 else{ 650 append(c); 651 } 652 break; 653 654 //top level // and /* comments, ']' (close decl tag) 655 //is ignored within them 656 case '/': 657 append(c); 658 c = in.read(); 659 append(c); 660 if (c == '/') 661 appendCodeSlashComment(); 662 else if (c == '*') 663 appendCodeStarComment(); 664 break; 665 666 //close tags are ignored within them 667 case '"': //strings outside of any comment 668 append(c); 669 appendCodeString(); 670 break; 671 672 case '\'': 673 append(c); 674 appendCodeCharLiteral(); 675 break; 676 677 default: 678 append(c); 679 } 680 } 681 682 } 683 684void parseDirective() throws IOException 685 { 686 if (dbg) dbgenter(); 687 688 int startline = in.getLine(); 689 int startcol = in.getCol(); 690 691 StringBuilder directives_buf = new StringBuilder(1024); 692 693 while (true) 694 { 695 c = in.read(); 696 697 switch (c) 698 { 699 case EOF: 700 unclosed("directive", startline, startcol); 701 if (dbg) dbgexit(); 702 return; 703 704 case ']': 705 if (directives_buf.indexOf("<<<") >= 0) { 706 parseHeredoc(directives_buf); 707 } 708 else{/* other directives used at page-generation time */ 709 addDirectives(directives_buf); 710 } 711 712 if (dbg) dbgexit(); 713 return; 714 715 default: 716 directives_buf.append((char)c); 717 } 718 } 719 720 } 721 722//[a-zA-Z_\-0-9] == ( \w | - ) 723static final Pattern directive_pat = Pattern.compile( 724 //foo = "bar baz" (embd. spaces) 725 "\\s*([a-zA-Z_\\-0-9]+)\\s*=\\s*\"((?:.|\r|\n)+?)\"" 726 + "|" 727 //foo = "bar$@#$" (no spaces) OR foo = bar (quotes optional) 728 + "\\s*([a-zA-Z_\\-0-9]+)\\s*=\\s*(\\S+)" 729 ); 730 731 732void addDirectives(StringBuilder directives_buf) throws TemplateParseException 733 { 734 if (dbg) { 735 dbgenter(); 736 System.out.println("-------directives section--------"); 737 System.out.println(directives_buf.toString()); 738 System.out.println("-------end directives-------"); 739 } 740 741 String name, value; 742 try { 743 Matcher m = directive_pat.matcher(directives_buf); 744 while (m.find()) 745 { 746 if (dbg) System.out.println(">>>>[0]->" + m.group() 747 + "; [1]->" + m.group(1) 748 + " [2]->" + m.group(2) 749 + " [3]->" + m.group(3) 750 + " [4]->" + m.group(4)); 751 752 name = m.group(1) != null ? m.group(1).toLowerCase() : 753 m.group(3).toLowerCase(); 754 value = m.group(2) != null ? m.group(2).toLowerCase() : 755 m.group(4).toLowerCase(); 756 757 if (name.equals(d_remove_initial_emptylines)) { 758 directives.put(name, value.replace("\"|'","")); 759 } 760 else if (name.equals(d_remove_all_emptylines)) { 761 directives.put(name, value.replace("\"|'","")); 762 } 763 //else if .... other directives here as needed.... 764 else 765 throw new Exception("Do not understand directive: " + m.group()); 766 } 767 if (dbg) System.out.println("Added directives: " + directives); 768 } 769 catch (Exception e) { 770 throw new TemplateParseException("File: " + inputFile.getAbsolutePath() 771 + ";\n" + e.toString()); 772 } 773 774 if (dbg) dbgexit(); 775 } 776 777void parseIncludeFile() throws IOException 778 { 779 if (dbg) dbgenter(); 780 781 int startline = in.getLine(); 782 int startcol = in.getCol(); 783 String option = null; 784 785 while (true) 786 { 787 c = in.read(); 788 789 switch (c) 790 { 791 case EOF: 792 unclosed("include-file", startline, startcol); 793 if (dbg) dbgexit(); 794 return; 795 796 case '[': 797 if (in.match('=')) { 798 //log.warn("Expressions cannot exist in file includes. Ignoring \"[=\" 799 //in [include-file... section starting at:", startline, startcol); 800 //instead of warn, we will error out. failing early is better. 801 //this does preclude having '[=' in the file name, but it's a good 802 //tradeoff 803 error("Expressions cannot exist in file includes. The offending static-include section starts at:", startline, startcol); 804 } 805 append(c); 806 break; 807 808 case ']': 809 includeFile(buf, option); /* not added in the tree, just included in the stream */ 810 buf.reset(); 811 if (dbg) dbgexit(); 812 return; 813 814 case 'o': 815 if (! in.match("ption")) 816 append(c); 817 else{ 818 skipWS(); 819 if (! in.match("=")) { 820 error("bad option parameter in file include: ", startline, startcol); 821 } 822 skipWS(); 823 824 int c2; 825 StringBuilder optionbuf = new StringBuilder(); 826 while (true) { 827 c2 = in.read(); 828 if (c2 == ']' || c2 == EOF || Character.isWhitespace(c2)) { 829 in.unread(); 830 break; 831 } 832 optionbuf.append((char)c2); 833 } 834 835 option = optionbuf.toString(); 836 //System.out.println(option); 837 } //else 838 break; 839 840 default: 841 append(c); 842 } 843 } 844 } 845 846void parseIncludeDecl() throws IOException 847 { 848 if (dbg) dbgenter(); 849 850 int startline = in.getLine(); 851 int startcol = in.getCol(); 852 String option = null; 853 854 while (true) 855 { 856 c = in.read(); 857 858 switch (c) 859 { 860 case EOF: 861 unclosed("include-decl", startline, startcol); 862 if (dbg) dbgexit(); 863 return; 864 865 case '[': 866 if (in.match('=')) { 867 //log.warn("Expressions cannot exist in file includes. Ignoring \"[=\" in [include-static... section starting at:", startline, startcol); 868 //we will throw an exception. failing early is better. this 869 //does preclude having '[=' in the file name, but it's a good tradeoff 870 error("Expressions cannot exist in include-decl. The offending static-include section starts at:", startline, startcol); 871 } 872 append(c); 873 break; 874 875 case ']': 876 IncludeDecl i = new IncludeDecl(buf); 877 if (option != null) 878 i.setOption(option); 879 inc_decl.add(i); 880 buf.reset(); 881 if (dbg) dbgexit(); 882 return; 883 884 case 'o': 885 if (! in.match("ption")) 886 append(c); 887 else{ 888 skipWS(); 889 if (! in.match("=")) { 890 error("bad option parameter in include-code: ", startline, startcol); 891 } 892 skipWS(); 893 894 int c2; 895 StringBuilder optionbuf = new StringBuilder(); 896 while (true) { 897 c2 = in.read(); 898 if (c2 == ']' || c2 == EOF || Character.isWhitespace(c2)) { 899 in.unread(); 900 break; 901 } 902 optionbuf.append((char)c2); 903 } 904 905 option = optionbuf.toString(); 906 //System.out.println(option); 907 } //else 908 break; 909 910 default: 911 append(c); 912 } 913 } 914 } 915 916 917//we need to parse imports seperately because they go outside 918//a class declaration (and [!...!] goes inside a class) 919//import XXX.*; 920//class YYY { 921//[!....stuff from here ....!] 922//... 923void parseImport() throws IOException 924 { 925 if (dbg) dbgenter(); 926 927 int startline = in.getLine(); 928 int startcol = in.getCol(); 929 930 while (true) 931 { 932 c = in.read(); 933 934 switch (c) 935 { 936 case EOF: 937 unclosed("import", startline, startcol); 938 if (dbg) dbgexit(); 939 return; 940 941 case '\n': 942 imps.add(new Import(buf)); 943 buf.reset(); 944 break; 945 946 case ']': 947 imps.add(new Import(buf)); 948 buf.reset(); 949 if (dbg) dbgexit(); 950 return; 951 952 default: 953 append(c); 954 } 955 } 956 } 957 958/* 959Called when // was read at the top level inside a code block. Appends 960the contents of a // comment to the buffer (not including the trailing 961newline) 962*/ 963void appendCodeSlashComment() throws IOException 964 { 965 if (dbg) dbgenter(); 966 967 while (true) 968 { 969 c = in.read(); 970 971 if (c == EOF) 972 break; 973 974 //do not append \r, \r\n, or \n, that finishes the // comment 975 //we need that newline to figure out if the next line is a hash 976 //line 977 if (c == '\r') { 978 in.unread(); 979 break; 980 } 981 982 if (c == '\n') { 983 in.unread(); 984 break; 985 } 986 987 append(c); 988 } 989 990 if (dbg) dbgread("CodeSLASHComment Finished: Buffer=" + StringUtil.viewableAscii(buf.toString())); 991 if (dbg) dbgexit(); 992 } 993 994/* 995Called when /* was read at the top level inside a code block. Appends 996the contents of a /*comment to the buffer. (not including any trailing 997newline or spaces) 998*/ 999void appendCodeStarComment() throws IOException 1000 { 1001 if (dbg) dbgenter(); 1002 1003 while (true) 1004 { 1005 c = in.read(); 1006 1007 if (c == EOF) 1008 break; 1009 1010 append(c); 1011 1012 if (c == '*') 1013 { 1014 if (in.match('/')) { 1015 append('/'); 1016 break; 1017 } 1018 } 1019 } 1020 1021 if (dbg) dbgread("CodeSTARComment Finished: Buffer=" + StringUtil.viewableAscii(buf.toString())); 1022 if (dbg) dbgexit(); 1023 } 1024 1025/* 1026Called (outside of any comments in the code block) when: 1027--> parseCode() 1028 ... " 1029 ^ (we are here) 1030*/ 1031void appendCodeString() throws IOException 1032 { 1033 if (dbg) dbgenter(); 1034 1035 int startline = in.getLine(); 1036 int startcol = in.getCol(); 1037 1038 while (true) 1039 { 1040 c = in.read(); 1041 1042 if (c == EOF || c == '\r' || c == '\n') 1043 unclosed("string literal", startline, startcol); 1044 1045 append(c); 1046 1047 if (c == '\\') { 1048 c = in.read(); 1049 if (c == EOF) 1050 unclosed("string literal", startline, startcol); 1051 else { 1052 append(c); 1053 continue; //so \" does not hit the if below and break 1054 } 1055 } 1056 1057 if (c == '"') 1058 break; 1059 } 1060 1061 if (dbg) dbgread("appendCodeString Finished: Buffer=" + StringUtil.viewableAscii(buf.toString())); 1062 if (dbg) dbgexit(); 1063 } 1064 1065 1066/* 1067Called (outside of any comments in the code block) when: 1068--> parseCode() 1069 ... ' 1070 ^ (we are here) 1071*/ 1072void appendCodeCharLiteral() throws IOException 1073 { 1074 if (dbg) dbgenter(); 1075 1076 int startline = in.getLine(); 1077 int startcol = in.getCol(); 1078 1079 while (true) 1080 { 1081 c = in.read(); 1082 1083 if (c == EOF || c == '\r' || c == '\n') 1084 unclosed("char literal", startline, startcol); 1085 1086 append(c); 1087 1088 if (c == '\\') { 1089 c = in.read(); 1090 if (c == EOF) 1091 unclosed("char literal", startline, startcol); 1092 else { 1093 append(c); 1094 continue; //so \' does not hit the if below and break 1095 } 1096 } 1097 1098 if (c == '\'') 1099 break; 1100 } 1101 1102 if (dbg) dbgread("appendCodeCharLiteral Finished: Buffer=" + StringUtil.viewableAscii(buf.toString())); 1103 if (dbg) dbgexit(); 1104 } 1105 1106 1107/* 1108Reads from the current position till the first nonwhitespace char, EOF or 1109newline is encountered. Reads are into the whitespace buffer. does not 1110consume the character past the non-whitespace character and does 1111NOT read multiple lines of whitespace. 1112*/ 1113void readToFirstNonWS() throws IOException 1114 { 1115 wsbuf.reset(); 1116 1117 while (true) 1118 { 1119 c = in.read(); 1120 1121 if (c == '\r' || c == '\n') 1122 break; 1123 1124 if (c == EOF || ! Character.isWhitespace(c)) 1125 break; 1126 1127 wsbuf.append((char)c); 1128 } 1129 1130 in.unread(); 1131 } 1132 1133//skip till end of whitespace or EOF. does not consume any chars past 1134//the whitespace. 1135void skipWS() throws IOException 1136 { 1137 int c2 = EOF; 1138 while (true) { 1139 c2 = in.read(); 1140 if (c2 == EOF || ! Character.isWhitespace(c2)) { 1141 in.unread(); 1142 break; 1143 } 1144 } 1145 } 1146 1147//skips to the end of line if the rest of the line is (from the current 1148//position), all whitespace till the end. otherwise, does not change 1149//current position. consumes trailing newlines (if present) when reading 1150//whitespace. 1151void skipIfWhitespaceToEnd() throws IOException 1152 { 1153 int count = 0; 1154 1155 while (true) 1156 { 1157 c = in.read(); 1158 count++; 1159 1160 if (c == '\r') { 1161 in.match('\n'); 1162 return; 1163 } 1164 1165 if (c == '\n' || c == EOF) 1166 return; 1167 1168 if (! Character.isWhitespace(c)) 1169 break; 1170 } 1171 1172 in.unread(count); 1173 } 1174 1175//not used anymore but left here for potential future use. does not 1176//consume the newline (if present) 1177void skipToLineEnd() throws IOException 1178 { 1179 while (true) 1180 { 1181 int c = in.read(); 1182 if (c == EOF) { 1183 in.unread(); 1184 break; 1185 } 1186 if (c == '\n' || c == '\r') { 1187 in.unread(); 1188 break; 1189 } 1190 } 1191 } 1192 1193String quote(final char c) 1194 { 1195 switch (c) 1196 { 1197 case '\r': 1198 return "\\r"; 1199 1200 case '\n': 1201 return "\\n"; 1202 1203 case '\"': 1204 //can also say: new String(new char[] {'\', '"'}) 1205 return "\\\""; //--> \" 1206 1207 case '\\': 1208 return "\\\\"; 1209 1210 default: 1211 return String.valueOf(c); 1212 } 1213 } 1214 1215//======= util and debug methods ========================== 1216String methodName(int framenum) 1217 { 1218 StackTraceElement ste[] = new Exception().getStackTrace(); 1219 //get method that called us, we are ste[0] 1220 StackTraceElement st = ste[framenum]; 1221 String file = st.getFileName(); 1222 int line = st.getLineNumber(); 1223 String method = st.getMethodName(); 1224 String threadname = Thread.currentThread().getName(); 1225 return method + "()"; 1226 } 1227 1228void dbgenter() { 1229 System.out.format("%s-->%s\n", StringUtil.repeat('\t', dbgtab++), methodName(2)); 1230 } 1231 1232void dbgexit() { 1233 System.out.format("%s<--%s\n", StringUtil.repeat('\t', --dbgtab), methodName(2)); 1234 } 1235 1236void dbgread(String str) { 1237 System.out.format("%s %s\n", StringUtil.repeat('\t', dbgtab), StringUtil.viewableAscii(str)); 1238 } 1239 1240void dbgread(String str, List list) { 1241 System.out.format("%s %s: ", StringUtil.repeat('\t', dbgtab), StringUtil.viewableAscii(str)); 1242 for (int n = 0; n < list.size(); n++) { 1243 System.out.print( StringUtil.viewableAscii( (String)list.get(n) ) ); 1244 } 1245 System.out.println(""); 1246 } 1247 1248void dbgread(char c) { 1249 System.out.format("%s %s\n", StringUtil.repeat('\t', dbgtab), StringUtil.viewableAscii(c)); 1250 } 1251 1252void dbgread(CharArrayWriter buf) { 1253 System.out.format("%s %s\n", StringUtil.repeat('\t', dbgtab), StringUtil.viewableAscii(buf.toString())); 1254 } 1255 1256void unclosed(String blockname, int startline, int startcol) throws IOException 1257 { 1258 throw new IOException(blockname + " tag not closed.\nThis tag was possibly opened in: \nFile:" 1259 + inputFile + ", line:" 1260 + startline + " column:" + startcol + 1261 ".\nCurrent line:" + in.getLine() + " column:" + in.getCol()); 1262 } 1263 1264void error(String msg, int line, int col) throws IOException 1265 { 1266 throw new IOException("Error in File:" + inputFile + " Line:" + line + " Col:" + col + " " + msg); 1267 } 1268 1269void error(String msg) throws IOException 1270 { 1271 throw new IOException("Error in File:" + inputFile + " " + msg); 1272 } 1273 1274//============== Non Parsing methods ================================ 1275void o(Object str) { 1276 out.print(str); 1277 } 1278 1279void ol(Object str) { 1280 out.println(str); 1281 } 1282 1283void ol() { 1284 out.println(); 1285 } 1286 1287 1288/* 1289include an external file whose contents will be rendered as part of the page. 1290*/ 1291void includeFile(CharArrayWriter buf, String option) throws IOException 1292 { 1293 String str; 1294 1295 if (dbg) dbgread("<new INCLUDE-FILE> "); 1296 str = removeLeadingTrailingQuote(buf.toString().trim()); 1297 1298 File includeFile = null; 1299 File parentDir = inputFile.getParentFile(); 1300 if (parentDir == null) { 1301 parentDir = new File("."); 1302 } 1303 1304 if (str.startsWith("/")) 1305 includeFile = new File(str); 1306 else 1307 includeFile = new File(parentDir, str); 1308 1309 //System.out.println(">>>>>>>>>> f="+f +";root="+contextRoot); 1310 1311 if (! includeFile.exists()) { 1312 throw new IOException("Include file does not exist: " + includeFile.getCanonicalPath()); 1313 } 1314 1315 if (circularityTrack.contains(includeFile.getAbsolutePath())) { 1316 throw new IOException("Circularity detected when including: " + includeFile.getCanonicalPath() + "\nAlready included the following files: " + circularityTrack); 1317 } 1318 1319 tree.add(new MollyComment( 1320 "//>>>START INCLUDE from: " + includeFile.getAbsolutePath())); 1321 1322 /* 1323 TemplateParser pp = new TemplateParser(contextRoot, includeFile, out, classname, log); 1324 pp.includeMode().parse(); //writes to out 1325 */ 1326 1327 in.insertIntoStream(includeFile); 1328 1329 /* this is printed immediately before the inserted contents can be processed, so don't add this */ 1330 /* 1331 tree.add(new MollyComment( 1332 "//>>>END INCLUDE from: " + includeFile.getAbsolutePath())); 1333 */ 1334 1335 circularityTrack.add(includeFile.getAbsolutePath()); 1336 } 1337 1338 1339void writePage() throws IOException 1340 { 1341 if (! includeMode) 1342 { 1343 //create a appropriate PrintWriter based on either the default 1344 //jave encoding or the page specified java encoding 1345 //the java source file will be written out in this encoding 1346 1347 FileOutputStream fout = new FileOutputStream(outputFile); 1348 OutputStreamWriter fw = new OutputStreamWriter(fout, TemplatePage.DEFAULT_ENCODING); 1349 1350 out = new PrintWriter(new BufferedWriter(fw)); 1351 } 1352 1353 if (! includeMode) 1354 { 1355 writePackage(); 1356 writeImports(); 1357 1358 o ("public class "); 1359 o (classname); 1360 ol(" extends fc.util.pagetemplate.TemplatePage"); 1361 ol("{"); 1362 } 1363 1364 writeFields(); 1365 1366 if (! includeMode) { 1367 writeConstructor(); 1368 } 1369 1370 writeMethods(); 1371 1372 if (! includeMode) { 1373 ol("}"); 1374 } 1375 } 1376 1377void writePackage() 1378 { 1379 o ("package "); 1380 o (packagename); 1381 ol(";"); 1382 ol(); 1383 } 1384 1385void writeImports() throws IOException 1386 { 1387 ol("import java.io.*;"); 1388 ol("import java.util.*;"); 1389 ol("import java.sql.*;"); 1390 for (int n = 0; n < imps.size(); n++) { 1391 ((Element)imps.get(n)).render(); 1392 ol(); 1393 } 1394 ol(); 1395 } 1396 1397void writeFields() 1398 { 1399 } 1400 1401void writeConstructor() 1402 { 1403 } 1404 1405void writeMethods() throws IOException 1406 { 1407 writeDeclaredMethods(); 1408 writeIncludedMethods(); 1409 writeRenderMethod(); 1410 } 1411 1412void writeDeclaredMethods() throws IOException 1413 { 1414 for (int n = 0; n < decl.size(); n++) { 1415 ((Element)decl.get(n)).render(); 1416 } 1417 1418 if (decl.size() > 0) 1419 ol(); 1420 } 1421 1422void writeIncludedMethods() throws IOException 1423 { 1424 for (int n = 0; n < inc_decl.size(); n++) { 1425 ((Element)inc_decl.get(n)).render(); 1426 } 1427 1428 if (inc_decl.size() > 0) 1429 ol(); 1430 } 1431 1432void writeRenderMethod() throws IOException 1433 { 1434 if (! includeMode) { 1435 writeRenderTop(); 1436 } 1437 1438 /* remove leading emptylines if directed */ 1439 if (directives.containsKey(d_remove_initial_emptylines)) 1440 { 1441 if (dbg) System.out.println("[d_remove_initial_emptylines] directive found, removing leading whitepace"); 1442 boolean white_space = true; 1443 1444 //have to use iterator when removing while transversing 1445 Iterator it = tree.iterator(); 1446 while (it.hasNext()) 1447 { 1448 Element e = (Element) it.next(); 1449 if (e instanceof Text) 1450 { 1451 Text t = (Text) e; 1452 if (t.isOnlyWhitespace()) { 1453 it.remove(); 1454 } 1455 } 1456 else{ 1457 if (! (e instanceof Comment || e instanceof Decl || e instanceof MollyComment)) { 1458 //the initial whitespace mode is not applicable since some other declaration seen 1459 break; 1460 } 1461 } 1462 } 1463 } 1464 1465 /* remove all empty lines if directed */ 1466 if (directives.containsKey(d_remove_all_emptylines)) 1467 { 1468 if (dbg) System.out.println("[d_remove_all_emptylines] directive found, removing leading whitepace"); 1469 boolean white_space = true; 1470 1471 //have to use iterator when removing while transversing 1472 Iterator it = tree.iterator(); 1473 while (it.hasNext()) 1474 { 1475 Element e = (Element) it.next(); 1476 if (e instanceof Text) 1477 { 1478 Text t = (Text) e; 1479 if (t.isOnlyWhitespace()) { 1480 it.remove(); 1481 } 1482 } 1483 } 1484 } 1485 1486 1487 for (int n = 0; n < tree.size(); n++) { 1488 ((Element)tree.get(n)).render(); 1489 } 1490 1491 if (! includeMode) { 1492 writeRenderBottom(); 1493 } 1494 1495 } 1496 1497void writeRenderTop() throws IOException 1498 { 1499 ol("public void render(PrintWriter out) throws Exception"); 1500 ol("\t{"); 1501 ol(); 1502 } 1503 1504void writeRenderBottom() throws IOException 1505 { 1506 ol(); 1507 o("\t"); 1508 ol("out.flush();"); 1509 ol("out.close();"); 1510 ol("\t} //~render end"); 1511 } 1512 1513 1514/* 1515int tabcount = 1; 1516String tab = "\t"; 1517void tabInc() { 1518 tab = StringUtil.repeat('\t', ++tabcount); 1519 } 1520void tabDec() { 1521 tab = StringUtil.repeat('\t', --tabcount); 1522 } 1523*/ 1524 1525abstract class Element { 1526 abstract void render() throws IOException; 1527 //text, include etc., implement this as needed. 1528 void addExp(Exp e) { 1529 throw new RuntimeException("Internal error: not implemented by this object"); 1530 } 1531 } 1532 1533//this should NOT be added to the tree directly but added to Text or Hash 1534//via the addExp() method. This is because exps must be printed inline 1535class Exp extends Element 1536 { 1537 String str; 1538 1539 Exp(CharArrayWriter buf) { 1540 this.str = buf.toString(); 1541 if (dbg) dbgread("<new EXP> "+ str); 1542 } 1543 1544 void render() { 1545 o("out.print ("); 1546 o(str); 1547 ol(");"); 1548 } 1549 1550 public String toString() { 1551 return "Exp: [" + str + "]"; 1552 } 1553 } 1554 1555class Text extends Element 1556 { 1557 String offset_space; 1558 final List list = new ArrayList(); 1559 1560 //each text section is parsed by a text node. Within EACH text 1561 //node, we split it's contained text into separate lines and 1562 //generate code to print each line with a "out.println(...)" 1563 //statement. This maintains the same source order as the molly 1564 //page. If we munge together everything and print all of it's 1565 //contents with just one out.println(...)" statement, we would 1566 //get one large line with embedded \n and that would make 1567 //things more difficult to co-relate with the source file. 1568 1569 Text(final String offset, final CharArrayWriter b) 1570 { 1571 if (offset == null) 1572 offset_space = "\t"; 1573 else 1574 offset_space = "\t" + offset; 1575 1576 final char[] buf = b.toCharArray(); 1577 1578 boolean prevWasCR = false; 1579 //jdk default is 32. we say 256. not too large, maybe 1580 //less cache pressure. not too important, gets resized 1581 //as needed anyway. 1582 final CharArrayWriter tmp = new CharArrayWriter(256); 1583 1584 for (int i=0, j=1; i < buf.length; i++, j++) 1585 { 1586 char c = buf[i]; 1587 if (j == buf.length) { 1588 tmp.append(quote(c)); 1589 list.add(tmp.toString()); 1590 tmp.reset(); 1591 } 1592 else if (c == '\n') { 1593 tmp.append(quote(c)); 1594 if (! prevWasCR) { 1595 list.add(tmp.toString()); 1596 tmp.reset(); 1597 } 1598 } 1599 else if (c == '\r') { 1600 tmp.append(quote(c)); 1601 list.add(tmp.toString()); 1602 tmp.reset(); 1603 prevWasCR = true; 1604 } 1605 else{ 1606 tmp.append(quote(c)); 1607 prevWasCR = false; 1608 } 1609 } 1610 1611 if (dbg) { 1612 String classname = getClass().getName(); 1613 dbgread("<new " + classname.substring(classname.indexOf("$")+1,classname.length()) + ">",list); 1614 } 1615 } 1616 1617 Text(CharArrayWriter b) 1618 { 1619 this(null, b); 1620 } 1621 1622 void addExp(Exp e) 1623 { 1624 list.add(e); 1625 } 1626 1627 void render() 1628 { 1629 for (int i=0; i<list.size(); i++) 1630 { 1631 Object obj = list.get(i); //can be String or Exp 1632 if (obj instanceof Exp) { 1633 o(offset_space); 1634 ((Exp)obj).render(); 1635 } 1636 else{ 1637 o(offset_space); 1638 o("out.print (\""); 1639 o(obj); 1640 ol("\");"); 1641 } 1642 } 1643 } //render 1644 1645 //a newline is actuall '\' and '\n' since it's fed to out (..) 1646 //to check for whitepace we need to check for '\', 'n', etc 1647 boolean isOnlyWhitespace() 1648 { 1649 for (int i = 0; i < list.size(); i++) 1650 { 1651 Object obj = list.get(i); //can be String or Exp 1652 if (obj instanceof Exp) { 1653 return false; 1654 } 1655 else{ 1656 String s = (String) obj; 1657 //dont even ask my why \\\\, fucking ridiculous 1658 if (! s.matches("^(\\\\n|\\\\r|\\\\t| )*$")) { 1659 return false; 1660 } 1661 } 1662 } 1663 1664 return true; 1665 } 1666 1667 public String toString() { 1668 StringBuilder buf = new StringBuilder(); 1669 buf.append("Text: ["); 1670 for (int n = 0; n < list.size(); n++) { 1671 buf.append(StringUtil.viewableAscii(String.valueOf(list.get(n)))); 1672 } 1673 buf.append("]"); 1674 return buf.toString(); 1675 } 1676 1677 } 1678 1679class Hash extends Text 1680 { 1681 Hash(final String offset, final CharArrayWriter b) 1682 { 1683 super(offset, b); 1684 } 1685 1686 //same as super.render() except for j == list.size() o/ol() below 1687 void render() 1688 { 1689 for (int i=0, j=1; i<list.size(); i++, j++) 1690 { 1691 Object obj = list.get(i); //can be String or Exp 1692 if (obj instanceof Exp) { 1693 o(offset_space); 1694 ((Exp)obj).render(); 1695 } 1696 else{ 1697 o(offset_space); 1698 o("out.print (\""); 1699 o(obj); 1700 1701 if (j == list.size()) 1702 o ("\");"); 1703 else 1704 ol("\");"); 1705 } 1706 } 1707 } //render 1708 1709 public String toString() { 1710 return "Hash: " + list; 1711 } 1712 } 1713 1714class Heredoc extends Text 1715 { 1716 Heredoc(final CharArrayWriter buf) 1717 { 1718 super(null, buf); 1719 } 1720 1721 //override, exp cannot be added to heredoc sections 1722 void addExp(Exp e) 1723 { 1724 throw new IllegalStateException("Internal implementation error: this method should not be called for a Heredoc object"); 1725 } 1726 1727 void render() 1728 { 1729 for (int i=0, j=1; i<list.size(); i++, j++) 1730 { 1731 Object obj = list.get(i); 1732 o(offset_space); 1733 o("out.print (\""); 1734 o(obj); 1735 ol("\");"); 1736 } 1737 } //render 1738 1739 public String toString() { 1740 return "Heredoc: " + list; 1741 } 1742 1743 } 1744 1745 1746class Code extends Element 1747 { 1748 List list = new ArrayList(); 1749 1750 Code(CharArrayWriter b) 1751 { 1752 //we split the code section into separate lines and 1753 //print each line with a out.print(...). This maintains 1754 //the same source order as the molly page. If we munge together 1755 //everything, we would get one large line with embedded \n 1756 //and that would make things more difficult to co-relate. 1757 final char[] buf = b.toCharArray(); 1758 CharArrayWriter tmp = new CharArrayWriter(); 1759 for (int i=0, j=1; i < buf.length; i++, j++) { 1760 char c = buf[i]; 1761 if (j == buf.length) { //end of buffer 1762 tmp.append(c); 1763 list.add(tmp.toString()); 1764 tmp.reset(); 1765 } 1766 else if (c == '\n') { 1767 tmp.append(c); 1768 list.add(tmp.toString()); 1769 tmp.reset(); 1770 } 1771 else 1772 tmp.append(c); 1773 } 1774 if (dbg) { 1775 String classname = getClass().getName(); 1776 dbgread("<new " + classname.substring(classname.indexOf("$")+1,classname.length()) + ">",list); 1777 } 1778 } 1779 1780 void render() { 1781 for (int i = 0; i < list.size(); i++) { 1782 o('\t'); 1783 o(list.get(i)); 1784 } 1785 } 1786 1787 public String toString() { 1788 return "Code: " + list; 1789 } 1790 } 1791 1792class Comment extends Element 1793 { 1794 String str; 1795 1796 Comment(CharArrayWriter buf) { 1797 this.str = buf.toString(); 1798 if (dbg) dbgread("<new COMMENT> "+ str); 1799 } 1800 1801 void render() { 1802 //we don't print commented sections 1803 } 1804 1805 public String toString() { 1806 return "Comment: [" + str + "]"; 1807 } 1808 } 1809 1810class Decl extends Code 1811 { 1812 Decl(CharArrayWriter buf) { 1813 super(buf); 1814 } 1815 1816 void render() { 1817 for (int i = 0; i < list.size(); i++) { 1818 o (list.get(i)); 1819 } 1820 } 1821 } 1822 1823 1824 1825/* a molly mechanism to include an external file containing code and method 1826 declarations. These are typically commom utility methods and global 1827 vars. The included file is not parsed by the template parser... the contents 1828 are treated as if they were written directly inside a [!....!] block. 1829*/ 1830class IncludeDecl extends Element 1831 { 1832 String str; 1833 String opt; 1834 1835 IncludeDecl(CharArrayWriter buf) { 1836 if (dbg) dbgread("<new INCLUDE-DECL> "); 1837 str = removeLeadingTrailingQuote(buf.toString().trim()); 1838 } 1839 1840 void setOption(String opt) { 1841 this.opt = opt; 1842 } 1843 1844 void render() throws IOException 1845 { 1846 File f = null; 1847 File parentDir = inputFile.getParentFile(); 1848 if (parentDir == null) { 1849 parentDir = new File("."); 1850 } 1851 1852 final int strlen = str.length(); 1853 1854 if (str.startsWith("\"") || str.startsWith("'")) 1855 { 1856 if (strlen == 1) //just " or ' 1857 throw new IOException("Bad include file name: " + str); 1858 1859 str = str.substring(1, strlen); 1860 } 1861 1862 if (str.endsWith("\"") || str.endsWith("'")) 1863 { 1864 if (strlen == 1) //just " or ' 1865 throw new IOException("Bad include file name: " + str); 1866 1867 str = str.substring(0, strlen-1); 1868 } 1869 1870 if (str.startsWith("/")) 1871 f = new File(str); 1872 else 1873 f = new File(parentDir, str); 1874 1875 if (! f.exists()) { 1876 throw new IOException("Include file does not exist: " + f.getCanonicalPath()); 1877 } 1878 1879 o("//>>>START INCLUDE DECLARTIONS from: "); 1880 o(f.getAbsolutePath()); 1881 ol(); 1882 1883 o(IOUtil.inputStreamToString(new FileInputStream(f))); 1884 1885 o("//>>>END INCLUDE DECLARATIONS from: "); 1886 o(f.getAbsolutePath()); 1887 ol(); 1888 1889 //circularities are tricky, later 1890 //includeMap.put(pageloc, f.getCanonicalPath()); 1891 } 1892 1893 public String toString() { 1894 return "IncludeDecl: [" + str + "; options: " + opt + "]"; 1895 } 1896 } 1897 1898class Import extends Code 1899 { 1900 Import(CharArrayWriter buf) { 1901 super(buf); 1902 } 1903 1904 void render() { 1905 for (int i = 0; i < list.size(); i++) { 1906 o (list.get(i)); 1907 } 1908 } 1909 } 1910 1911class MollyComment extends Element 1912 { 1913 String str; 1914 1915 MollyComment(String str) { 1916 this.str = str; 1917 if (dbg) dbgread("<new MollyComment> "+ str); 1918 } 1919 1920 void render() { 1921 ol(str); 1922 } 1923 1924 public String toString() { 1925 return "MollyComment: [" + str + "]"; 1926 } 1927 } 1928 1929/** 1930removes starting and trailing single/double quotes. used by the 1931include/forward render methods only, NOT used while parsing. 1932*/ 1933private static String removeLeadingTrailingQuote(String str) 1934 { 1935 if (str == null) 1936 return str; 1937 1938 if ( str.startsWith("\"") || str.startsWith("'") ) { 1939 str = str.substring(1, str.length()); 1940 } 1941 1942 if ( str.endsWith("\"") || str.endsWith("'") ) { 1943 str = str.substring(0, str.length()-1); 1944 } 1945 1946 return str; 1947 } 1948 1949//=============================================== 1950 1951public static void main (String args[]) throws IOException 1952 { 1953 Args myargs = new Args(args); 1954 myargs.setUsage("java " + myargs.getMainClassName() 1955 + "\n" 1956 + "Required params:\n" 1957 + " -classname output_class_name\n" 1958 + " -in input_page_file\n" 1959 + "\nOptional params:\n" 1960 + " -encoding <page_encoding>\n" 1961 + " -out <output_file_name>\n" 1962 + " the output file is optional and defaults to the standard out if not specified." 1963 ); 1964 //String encoding = myargs.get("encoding", Page.DEFAULT_ENCODING); 1965 1966 File input = new File(myargs.getRequired("in")); 1967 1968 PrintWriter output; 1969 1970 if (myargs.get("out") != null) 1971 output = new PrintWriter(new FileWriter(myargs.get("out"))); 1972 else 1973 output = new PrintWriter(new OutputStreamWriter(System.out)); 1974 1975 TemplateParser parser = new TemplateParser(input, output, myargs.getRequired("classname"), Log.getDefault()); 1976 parser.parse(); 1977 } 1978 1979}