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
006 package fc.util.pagetemplate;
007
008 import java.io.*;
009 import java.util.*;
010 import java.util.regex.*;
011 import fc.io.*;
012 import fc.util.*;
013
014 /*
015 NOTES
016
017 Code blocks of the form
018 [...]
019 cause problems with java arrays
020
021 String[] or foo[4] etc.., craps out. So we need to use
022 [[...]]
023 for the molly code blocks
024
025 The 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
028 Read www.mollypages.org/page/grammar/index.mp for a intro to parsing
029 */
030
031 /**
032 Parses a template page and writes out the corresponding java file to the specified
033 output. The parser and scanner is combined into one class here for simplicity (a
034 seperate scanner is overkill for a simple LL(1) grammar such as page templates).
035
036 @author hursh jain
037 */
038 public final class TemplateParser
039 {
040 private static final boolean dbg = false;
041 private static final int EOF = -1;
042 private int dbgtab = 0;
043
044 String classname;
045 String packagename = TemplatePage.PACKAGE_NAME;
046 TemplateReader in;
047 PrintWriter out;
048 Log log;
049 File inputFile;
050 File outputFile;
051 boolean includeMode = false;
052
053 //Read data
054 //we use these since stringbuffer/builders do not have a clear/reset function
055 CharArrayWriter buf = new CharArrayWriter(4096);
056 CharArrayWriter wsbuf = new CharArrayWriter(32); // ^(whitespace)*
057 int c = EOF;
058
059 //PageData
060 List decl = new ArrayList(); //declarations
061 List inc_decl = new ArrayList(); //external included declarations
062 List imps = new ArrayList(); //imports
063 List tree = new ArrayList(); //code, exp, text etc.
064 Map directives = new HashMap(); //page options
065 Set circularityTrack = new HashSet(); //track pages already included to stop circular refs
066
067 /** The name of the ("remove-initial-whitespace") directive */
068 public static String d_remove_initial_emptylines = "remove-initial-emptylines";
069 /** The name of the ("remove-all-emptylines") directive */
070 public static String d_remove_all_emptylines = "remove-all-emptylines";
071
072 /*
073 This constructor for internal use.
074
075 The parser can be invoked recursively to parse included files as
076 well..that's what the includeMode() does (and this construtor is invoked
077 when including). When including, we already have a output writer
078 created, we use that writer (instead of creating a new one based on
079 src_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 */
086 private TemplateParser(File input, PrintWriter outputWriter, String classname, Log log)
087 throws 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 /**
100 Creates 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 */
108 public TemplateParser(File input, File output, String classname, Log log)
109 throws 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
120 void append(final int c)
121 {
122 Argcheck.istrue(c >= 0, "Internal error: recieved c=" + c);
123 buf.append((char)c);
124 }
125
126 void append(final char c)
127 {
128 buf.append(c);
129 }
130
131 void append(final String str)
132 {
133 buf.append(str);
134 }
135
136 /* not used anymore */
137 TemplateParser includeMode()
138 {
139 includeMode = true;
140 return this;
141 }
142
143 /**
144 Parses the page. If the parse is successful, the java source will be
145 generated.
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 */
151 public 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.
165 private Text newTextNode()
166 {
167 Text text = new Text(buf);
168 tree.add(text);
169 buf.reset();
170 return text;
171 }
172
173 void 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
278 void 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
377 void 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..
490 FOO
491 */
492 void 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 /*
547 Text is the parent node for the expression. A new expression is parsed,
548 created and added to the text object by this method
549 */
550 void 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
589 void 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
625 void 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
684 void 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 | - )
723 static 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
732 void 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
777 void 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
846 void 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 //...
923 void 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 /*
959 Called when // was read at the top level inside a code block. Appends
960 the contents of a // comment to the buffer (not including the trailing
961 newline)
962 */
963 void 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 /*
995 Called when /* was read at the top level inside a code block. Appends
996 the contents of a /*comment to the buffer. (not including any trailing
997 newline or spaces)
998 */
999 void 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 /*
1026 Called (outside of any comments in the code block) when:
1027 --> parseCode()
1028 ... "
1029 ^ (we are here)
1030 */
1031 void 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 /*
1067 Called (outside of any comments in the code block) when:
1068 --> parseCode()
1069 ... '
1070 ^ (we are here)
1071 */
1072 void 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 /*
1108 Reads from the current position till the first nonwhitespace char, EOF or
1109 newline is encountered. Reads are into the whitespace buffer. does not
1110 consume the character past the non-whitespace character and does
1111 NOT read multiple lines of whitespace.
1112 */
1113 void 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.
1135 void 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.
1151 void 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)
1177 void 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
1193 String 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 ==========================
1216 String 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
1228 void dbgenter() {
1229 System.out.format("%s-->%s\n", StringUtil.repeat('\t', dbgtab++), methodName(2));
1230 }
1231
1232 void dbgexit() {
1233 System.out.format("%s<--%s\n", StringUtil.repeat('\t', --dbgtab), methodName(2));
1234 }
1235
1236 void dbgread(String str) {
1237 System.out.format("%s %s\n", StringUtil.repeat('\t', dbgtab), StringUtil.viewableAscii(str));
1238 }
1239
1240 void 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
1248 void dbgread(char c) {
1249 System.out.format("%s %s\n", StringUtil.repeat('\t', dbgtab), StringUtil.viewableAscii(c));
1250 }
1251
1252 void dbgread(CharArrayWriter buf) {
1253 System.out.format("%s %s\n", StringUtil.repeat('\t', dbgtab), StringUtil.viewableAscii(buf.toString()));
1254 }
1255
1256 void 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
1264 void 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
1269 void error(String msg) throws IOException
1270 {
1271 throw new IOException("Error in File:" + inputFile + " " + msg);
1272 }
1273
1274 //============== Non Parsing methods ================================
1275 void o(Object str) {
1276 out.print(str);
1277 }
1278
1279 void ol(Object str) {
1280 out.println(str);
1281 }
1282
1283 void ol() {
1284 out.println();
1285 }
1286
1287
1288 /*
1289 include an external file whose contents will be rendered as part of the page.
1290 */
1291 void 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
1339 void 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
1377 void writePackage()
1378 {
1379 o ("package ");
1380 o (packagename);
1381 ol(";");
1382 ol();
1383 }
1384
1385 void 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
1397 void writeFields()
1398 {
1399 }
1400
1401 void writeConstructor()
1402 {
1403 }
1404
1405 void writeMethods() throws IOException
1406 {
1407 writeDeclaredMethods();
1408 writeIncludedMethods();
1409 writeRenderMethod();
1410 }
1411
1412 void 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
1422 void 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
1432 void 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
1497 void writeRenderTop() throws IOException
1498 {
1499 ol("public void render(PrintWriter out) throws Exception");
1500 ol("\t{");
1501 ol();
1502 }
1503
1504 void 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 /*
1515 int tabcount = 1;
1516 String tab = "\t";
1517 void tabInc() {
1518 tab = StringUtil.repeat('\t', ++tabcount);
1519 }
1520 void tabDec() {
1521 tab = StringUtil.repeat('\t', --tabcount);
1522 }
1523 */
1524
1525 abstract 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
1535 class 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
1555 class 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
1679 class 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
1714 class 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
1746 class 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
1792 class 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
1810 class 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 */
1830 class 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
1898 class 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
1911 class 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 /**
1930 removes starting and trailing single/double quotes. used by the
1931 include/forward render methods only, NOT used while parsing.
1932 */
1933 private 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
1951 public 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 }