1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.jasper.compiler;
18
19 import java.io.CharArrayWriter;
20 import java.io.FileNotFoundException;
21 import java.net.URL;
22 import java.util.Iterator;
23 import java.util.List;
24
25 import javax.servlet.jsp.tagext.TagAttributeInfo;
26 import javax.servlet.jsp.tagext.TagFileInfo;
27 import javax.servlet.jsp.tagext.TagInfo;
28 import javax.servlet.jsp.tagext.TagLibraryInfo;
29
30 import org.apache.jasper.JasperException;
31 import org.apache.jasper.JspCompilationContext;
32 import org.xml.sax.Attributes;
33 import org.xml.sax.helpers.AttributesImpl;
34
35 /**
36 * This class implements a parser for a JSP page (non-xml view). JSP page
37 * grammar is included here for reference. The token '#' that appears in the
38 * production indicates the current input token location in the production.
39 *
40 * @author Kin-man Chung
41 * @author Shawn Bayern
42 * @author Mark Roth
43 */
44
45 class Parser implements TagConstants {
46
47 private ParserController parserController;
48
49 private JspCompilationContext ctxt;
50
51 private JspReader reader;
52
53 private String currentFile;
54
55 private Mark start;
56
57 private ErrorDispatcher err;
58
59 private int scriptlessCount;
60
61 private boolean isTagFile;
62
63 private boolean directivesOnly;
64
65 private URL jarFileUrl;
66
67 private PageInfo pageInfo;
68
69 // Virtual body content types, to make parsing a little easier.
70 // These are not accessible from outside the parser.
71 private static final String JAVAX_BODY_CONTENT_PARAM = "JAVAX_BODY_CONTENT_PARAM";
72
73 private static final String JAVAX_BODY_CONTENT_PLUGIN = "JAVAX_BODY_CONTENT_PLUGIN";
74
75 private static final String JAVAX_BODY_CONTENT_TEMPLATE_TEXT = "JAVAX_BODY_CONTENT_TEMPLATE_TEXT";
76
77 /**
78 * The constructor
79 */
80 private Parser(ParserController pc, JspReader reader, boolean isTagFile,
81 boolean directivesOnly, URL jarFileUrl) {
82 this.parserController = pc;
83 this.ctxt = pc.getJspCompilationContext();
84 this.pageInfo = pc.getCompiler().getPageInfo();
85 this.err = pc.getCompiler().getErrorDispatcher();
86 this.reader = reader;
87 this.currentFile = reader.mark().getFile();
88 this.scriptlessCount = 0;
89 this.isTagFile = isTagFile;
90 this.directivesOnly = directivesOnly;
91 this.jarFileUrl = jarFileUrl;
92 start = reader.mark();
93 }
94
95 /**
96 * The main entry for Parser
97 *
98 * @param pc
99 * The ParseController, use for getting other objects in compiler
100 * and for parsing included pages
101 * @param reader
102 * To read the page
103 * @param parent
104 * The parent node to this page, null for top level page
105 * @return list of nodes representing the parsed page
106 */
107 public static Node.Nodes parse(ParserController pc, JspReader reader,
108 Node parent, boolean isTagFile, boolean directivesOnly,
109 URL jarFileUrl, String pageEnc, String jspConfigPageEnc,
110 boolean isDefaultPageEncoding, boolean isBomPresent) throws JasperException {
111
112 Parser parser = new Parser(pc, reader, isTagFile, directivesOnly,
113 jarFileUrl);
114
115 Node.Root root = new Node.Root(reader.mark(), parent, false);
116 root.setPageEncoding(pageEnc);
117 root.setJspConfigPageEncoding(jspConfigPageEnc);
118 root.setIsDefaultPageEncoding(isDefaultPageEncoding);
119 root.setIsBomPresent(isBomPresent);
120
121 // For the Top level page, add include-prelude and include-coda
122 PageInfo pageInfo = pc.getCompiler().getPageInfo();
123 if (parent == null && !isTagFile) {
124 parser.addInclude(root, pageInfo.getIncludePrelude());
125 }
126 if (directivesOnly) {
127 parser.parseFileDirectives(root);
128 } else {
129 while (reader.hasMoreInput()) {
130 parser.parseElements(root);
131 }
132 }
133 if (parent == null && !isTagFile) {
134 parser.addInclude(root, pageInfo.getIncludeCoda());
135 }
136
137 Node.Nodes page = new Node.Nodes(root);
138 return page;
139 }
140
141 /**
142 * Attributes ::= (S Attribute)* S?
143 */
144 Attributes parseAttributes() throws JasperException {
145 AttributesImpl attrs = new AttributesImpl();
146
147 reader.skipSpaces();
148 while (parseAttribute(attrs))
149 reader.skipSpaces();
150
151 return attrs;
152 }
153
154 /**
155 * Parse Attributes for a reader, provided for external use
156 */
157 public static Attributes parseAttributes(ParserController pc,
158 JspReader reader) throws JasperException {
159 Parser tmpParser = new Parser(pc, reader, false, false, null);
160 return tmpParser.parseAttributes();
161 }
162
163 /**
164 * Attribute ::= Name S? Eq S? ( '"<%=' RTAttributeValueDouble | '"'
165 * AttributeValueDouble | "'<%=" RTAttributeValueSingle | "'"
166 * AttributeValueSingle } Note: JSP and XML spec does not allow while spaces
167 * around Eq. It is added to be backward compatible with Tomcat, and with
168 * other xml parsers.
169 */
170 private boolean parseAttribute(AttributesImpl attrs) throws JasperException {
171
172 // Get the qualified name
173 String qName = parseName();
174 if (qName == null)
175 return false;
176
177 // Determine prefix and local name components
178 String localName = qName;
179 String uri = "";
180 int index = qName.indexOf(':');
181 if (index != -1) {
182 String prefix = qName.substring(0, index);
183 uri = pageInfo.getURI(prefix);
184 if (uri == null) {
185 err.jspError(reader.mark(),
186 "jsp.error.attribute.invalidPrefix", prefix);
187 }
188 localName = qName.substring(index + 1);
189 }
190
191 reader.skipSpaces();
192 if (!reader.matches("="))
193 err.jspError(reader.mark(), "jsp.error.attribute.noequal");
194
195 reader.skipSpaces();
196 char quote = (char) reader.nextChar();
197 if (quote != '\'' && quote != '"')
198 err.jspError(reader.mark(), "jsp.error.attribute.noquote");
199
200 String watchString = "";
201 if (reader.matches("<%="))
202 watchString = "%>";
203 watchString = watchString + quote;
204
205 String attrValue = parseAttributeValue(watchString);
206 attrs.addAttribute(uri, localName, qName, "CDATA", attrValue);
207 return true;
208 }
209
210 /**
211 * Name ::= (Letter | '_' | ':') (Letter | Digit | '.' | '_' | '-' | ':')*
212 */
213 private String parseName() throws JasperException {
214 char ch = (char) reader.peekChar();
215 if (Character.isLetter(ch) || ch == '_' || ch == ':') {
216 StringBuffer buf = new StringBuffer();
217 buf.append(ch);
218 reader.nextChar();
219 ch = (char) reader.peekChar();
220 while (Character.isLetter(ch) || Character.isDigit(ch) || ch == '.'
221 || ch == '_' || ch == '-' || ch == ':') {
222 buf.append(ch);
223 reader.nextChar();
224 ch = (char) reader.peekChar();
225 }
226 return buf.toString();
227 }
228 return null;
229 }
230
231 /**
232 * AttributeValueDouble ::= (QuotedChar - '"')* ('"' | <TRANSLATION_ERROR>)
233 * RTAttributeValueDouble ::= ((QuotedChar - '"')* - ((QuotedChar-'"')'%>"')
234 * ('%>"' | TRANSLATION_ERROR)
235 */
236 private String parseAttributeValue(String watch) throws JasperException {
237 Mark start = reader.mark();
238 Mark stop = reader.skipUntilIgnoreEsc(watch);
239 if (stop == null) {
240 err.jspError(start, "jsp.error.attribute.unterminated", watch);
241 }
242
243 String ret = null;
244 try {
245 char quote = 0;
246 if (watch.length() == 1) {
247 quote = watch.charAt(0);
248 }
249 ret = AttributeParser.getUnquoted(reader.getText(start, stop),
250 quote, pageInfo.isELIgnored(),
251 pageInfo.isDeferredSyntaxAllowedAsLiteral());
252 } catch (IllegalArgumentException iae) {
253 err.jspError(start, iae.getMessage());
254 }
255 if (watch.length() == 1) // quote
256 return ret;
257
258 // Put back delimiter '<%=' and '%>', since they are needed if the
259 // attribute does not allow RTexpression.
260 return "<%=" + ret + "%>";
261 }
262
263 private String parseScriptText(String tx) {
264 CharArrayWriter cw = new CharArrayWriter();
265 int size = tx.length();
266 int i = 0;
267 while (i < size) {
268 char ch = tx.charAt(i);
269 if (i + 2 < size && ch == '%' && tx.charAt(i + 1) == '\\'
270 && tx.charAt(i + 2) == '>') {
271 cw.write('%');
272 cw.write('>');
273 i += 3;
274 } else {
275 cw.write(ch);
276 ++i;
277 }
278 }
279 cw.close();
280 return cw.toString();
281 }
282
283 /*
284 * Invokes parserController to parse the included page
285 */
286 private void processIncludeDirective(String file, Node parent)
287 throws JasperException {
288 if (file == null) {
289 return;
290 }
291
292 try {
293 parserController.parse(file, parent, jarFileUrl);
294 } catch (FileNotFoundException ex) {
295 err.jspError(start, "jsp.error.file.not.found", file);
296 } catch (Exception ex) {
297 err.jspError(start, ex.getMessage());
298 }
299 }
300
301 /*
302 * Parses a page directive with the following syntax: PageDirective ::= ( S
303 * Attribute)*
304 */
305 private void parsePageDirective(Node parent) throws JasperException {
306 Attributes attrs = parseAttributes();
307 Node.PageDirective n = new Node.PageDirective(attrs, start, parent);
308
309 /*
310 * A page directive may contain multiple 'import' attributes, each of
311 * which consists of a comma-separated list of package names. Store each
312 * list with the node, where it is parsed.
313 */
314 for (int i = 0; i < attrs.getLength(); i++) {
315 if ("import".equals(attrs.getQName(i))) {
316 n.addImport(attrs.getValue(i));
317 }
318 }
319 }
320
321 /*
322 * Parses an include directive with the following syntax: IncludeDirective
323 * ::= ( S Attribute)*
324 */
325 private void parseIncludeDirective(Node parent) throws JasperException {
326 Attributes attrs = parseAttributes();
327
328 // Included file expanded here
329 Node includeNode = new Node.IncludeDirective(attrs, start, parent);
330 processIncludeDirective(attrs.getValue("file"), includeNode);
331 }
332
333 /**
334 * Add a list of files. This is used for implementing include-prelude and
335 * include-coda of jsp-config element in web.xml
336 */
337 private void addInclude(Node parent, List files) throws JasperException {
338 if (files != null) {
339 Iterator iter = files.iterator();
340 while (iter.hasNext()) {
341 String file = (String) iter.next();
342 AttributesImpl attrs = new AttributesImpl();
343 attrs.addAttribute("", "file", "file", "CDATA", file);
344
345 // Create a dummy Include directive node
346 Node includeNode = new Node.IncludeDirective(attrs, reader
347 .mark(), parent);
348 processIncludeDirective(file, includeNode);
349 }
350 }
351 }
352
353 /*
354 * Parses a taglib directive with the following syntax: Directive ::= ( S
355 * Attribute)*
356 */
357 private void parseTaglibDirective(Node parent) throws JasperException {
358
359 Attributes attrs = parseAttributes();
360 String uri = attrs.getValue("uri");
361 String prefix = attrs.getValue("prefix");
362 if (prefix != null) {
363 Mark prevMark = pageInfo.getNonCustomTagPrefix(prefix);
364 if (prevMark != null) {
365 err.jspError(reader.mark(), "jsp.error.prefix.use_before_dcl",
366 prefix, prevMark.getFile(), ""
367 + prevMark.getLineNumber());
368 }
369 if (uri != null) {
370 String uriPrev = pageInfo.getURI(prefix);
371 if (uriPrev != null && !uriPrev.equals(uri)) {
372 err.jspError(reader.mark(), "jsp.error.prefix.refined",
373 prefix, uri, uriPrev);
374 }
375 if (pageInfo.getTaglib(uri) == null) {
376 TagLibraryInfoImpl impl = null;
377 if (ctxt.getOptions().isCaching()) {
378 impl = (TagLibraryInfoImpl) ctxt.getOptions()
379 .getCache().get(uri);
380 }
381 if (impl == null) {
382 String[] location = ctxt.getTldLocation(uri);
383 impl = new TagLibraryInfoImpl(ctxt, parserController, pageInfo,
384 prefix, uri, location, err);
385 if (ctxt.getOptions().isCaching()) {
386 ctxt.getOptions().getCache().put(uri, impl);
387 }
388 } else {
389 // Current compilation context needs location of cached
390 // tag files
391 for (TagFileInfo info : impl.getTagFiles()) {
392 ctxt.setTagFileJarUrl(info.getPath(),
393 ctxt.getTagFileJarUrl());
394 }
395 }
396 pageInfo.addTaglib(uri, impl);
397 }
398 pageInfo.addPrefixMapping(prefix, uri);
399 } else {
400 String tagdir = attrs.getValue("tagdir");
401 if (tagdir != null) {
402 String urnTagdir = URN_JSPTAGDIR + tagdir;
403 if (pageInfo.getTaglib(urnTagdir) == null) {
404 pageInfo.addTaglib(urnTagdir,
405 new ImplicitTagLibraryInfo(ctxt,
406 parserController, pageInfo, prefix, tagdir, err));
407 }
408 pageInfo.addPrefixMapping(prefix, urnTagdir);
409 }
410 }
411 }
412
413 new Node.TaglibDirective(attrs, start, parent);
414 }
415
416 /*
417 * Parses a directive with the following syntax: Directive ::= S? ( 'page'
418 * PageDirective | 'include' IncludeDirective | 'taglib' TagLibDirective) S?
419 * '%>'
420 *
421 * TagDirective ::= S? ('tag' PageDirective | 'include' IncludeDirective |
422 * 'taglib' TagLibDirective) | 'attribute AttributeDirective | 'variable
423 * VariableDirective S? '%>'
424 */
425 private void parseDirective(Node parent) throws JasperException {
426 reader.skipSpaces();
427
428 String directive = null;
429 if (reader.matches("page")) {
430 directive = "<%@ page";
431 if (isTagFile) {
432 err.jspError(reader.mark(), "jsp.error.directive.istagfile",
433 directive);
434 }
435 parsePageDirective(parent);
436 } else if (reader.matches("include")) {
437 directive = "<%@ include";
438 parseIncludeDirective(parent);
439 } else if (reader.matches("taglib")) {
440 if (directivesOnly) {
441 // No need to get the tagLibInfo objects. This alos suppresses
442 // parsing of any tag files used in this tag file.
443 return;
444 }
445 directive = "<%@ taglib";
446 parseTaglibDirective(parent);
447 } else if (reader.matches("tag")) {
448 directive = "<%@ tag";
449 if (!isTagFile) {
450 err.jspError(reader.mark(), "jsp.error.directive.isnottagfile",
451 directive);
452 }
453 parseTagDirective(parent);
454 } else if (reader.matches("attribute")) {
455 directive = "<%@ attribute";
456 if (!isTagFile) {
457 err.jspError(reader.mark(), "jsp.error.directive.isnottagfile",
458 directive);
459 }
460 parseAttributeDirective(parent);
461 } else if (reader.matches("variable")) {
462 directive = "<%@ variable";
463 if (!isTagFile) {
464 err.jspError(reader.mark(), "jsp.error.directive.isnottagfile",
465 directive);
466 }
467 parseVariableDirective(parent);
468 } else {
469 err.jspError(reader.mark(), "jsp.error.invalid.directive");
470 }
471
472 reader.skipSpaces();
473 if (!reader.matches("%>")) {
474 err.jspError(start, "jsp.error.unterminated", directive);
475 }
476 }
477
478 /*
479 * Parses a directive with the following syntax:
480 *
481 * XMLJSPDirectiveBody ::= S? ( ( 'page' PageDirectiveAttrList S? ( '/>' | (
482 * '>' S? ETag ) ) | ( 'include' IncludeDirectiveAttrList S? ( '/>' | ( '>'
483 * S? ETag ) ) | <TRANSLATION_ERROR>
484 *
485 * XMLTagDefDirectiveBody ::= ( ( 'tag' TagDirectiveAttrList S? ( '/>' | (
486 * '>' S? ETag ) ) | ( 'include' IncludeDirectiveAttrList S? ( '/>' | ( '>'
487 * S? ETag ) ) | ( 'attribute' AttributeDirectiveAttrList S? ( '/>' | ( '>'
488 * S? ETag ) ) | ( 'variable' VariableDirectiveAttrList S? ( '/>' | ( '>' S?
489 * ETag ) ) ) | <TRANSLATION_ERROR>
490 */
491 private void parseXMLDirective(Node parent) throws JasperException {
492 reader.skipSpaces();
493
494 String eTag = null;
495 if (reader.matches("page")) {
496 eTag = "jsp:directive.page";
497 if (isTagFile) {
498 err.jspError(reader.mark(), "jsp.error.directive.istagfile",
499 "<" + eTag);
500 }
501 parsePageDirective(parent);
502 } else if (reader.matches("include")) {
503 eTag = "jsp:directive.include";
504 parseIncludeDirective(parent);
505 } else if (reader.matches("tag")) {
506 eTag = "jsp:directive.tag";
507 if (!isTagFile) {
508 err.jspError(reader.mark(), "jsp.error.directive.isnottagfile",
509 "<" + eTag);
510 }
511 parseTagDirective(parent);
512 } else if (reader.matches("attribute")) {
513 eTag = "jsp:directive.attribute";
514 if (!isTagFile) {
515 err.jspError(reader.mark(), "jsp.error.directive.isnottagfile",
516 "<" + eTag);
517 }
518 parseAttributeDirective(parent);
519 } else if (reader.matches("variable")) {
520 eTag = "jsp:directive.variable";
521 if (!isTagFile) {
522 err.jspError(reader.mark(), "jsp.error.directive.isnottagfile",
523 "<" + eTag);
524 }
525 parseVariableDirective(parent);
526 } else {
527 err.jspError(reader.mark(), "jsp.error.invalid.directive");
528 }
529
530 reader.skipSpaces();
531 if (reader.matches(">")) {
532 reader.skipSpaces();
533 if (!reader.matchesETag(eTag)) {
534 err.jspError(start, "jsp.error.unterminated", "<" + eTag);
535 }
536 } else if (!reader.matches("/>")) {
537 err.jspError(start, "jsp.error.unterminated", "<" + eTag);
538 }
539 }
540
541 /*
542 * Parses a tag directive with the following syntax: PageDirective ::= ( S
543 * Attribute)*
544 */
545 private void parseTagDirective(Node parent) throws JasperException {
546 Attributes attrs = parseAttributes();
547 Node.TagDirective n = new Node.TagDirective(attrs, start, parent);
548
549 /*
550 * A page directive may contain multiple 'import' attributes, each of
551 * which consists of a comma-separated list of package names. Store each
552 * list with the node, where it is parsed.
553 */
554 for (int i = 0; i < attrs.getLength(); i++) {
555 if ("import".equals(attrs.getQName(i))) {
556 n.addImport(attrs.getValue(i));
557 }
558 }
559 }
560
561 /*
562 * Parses a attribute directive with the following syntax:
563 * AttributeDirective ::= ( S Attribute)*
564 */
565 private void parseAttributeDirective(Node parent) throws JasperException {
566 Attributes attrs = parseAttributes();
567 Node.AttributeDirective n = new Node.AttributeDirective(attrs, start,
568 parent);
569 }
570
571 /*
572 * Parses a variable directive with the following syntax: PageDirective ::= (
573 * S Attribute)*
574 */
575 private void parseVariableDirective(Node parent) throws JasperException {
576 Attributes attrs = parseAttributes();
577 Node.VariableDirective n = new Node.VariableDirective(attrs, start,
578 parent);
579 }
580
581 /*
582 * JSPCommentBody ::= (Char* - (Char* '--%>')) '--%>'
583 */
584 private void parseComment(Node parent) throws JasperException {
585 start = reader.mark();
586 Mark stop = reader.skipUntil("--%>");
587 if (stop == null) {
588 err.jspError(start, "jsp.error.unterminated", "<%--");
589 }
590
591 new Node.Comment(reader.getText(start, stop), start, parent);
592 }
593
594 /*
595 * DeclarationBody ::= (Char* - (char* '%>')) '%>'
596 */
597 private void parseDeclaration(Node parent) throws JasperException {
598 start = reader.mark();
599 Mark stop = reader.skipUntil("%>");
600 if (stop == null) {
601 err.jspError(start, "jsp.error.unterminated", "<%!");
602 }
603
604 new Node.Declaration(parseScriptText(reader.getText(start, stop)),
605 start, parent);
606 }
607
608 /*
609 * XMLDeclarationBody ::= ( S? '/>' ) | ( S? '>' (Char* - (char* '<'))
610 * CDSect?)* ETag | <TRANSLATION_ERROR> CDSect ::= CDStart CData CDEnd
611 * CDStart ::= '<![CDATA[' CData ::= (Char* - (Char* ']]>' Char*)) CDEnd
612 * ::= ']]>'
613 */
614 private void parseXMLDeclaration(Node parent) throws JasperException {
615 reader.skipSpaces();
616 if (!reader.matches("/>")) {
617 if (!reader.matches(">")) {
618 err.jspError(start, "jsp.error.unterminated",
619 "<jsp:declaration>");
620 }
621 Mark stop;
622 String text;
623 while (true) {
624 start = reader.mark();
625 stop = reader.skipUntil("<");
626 if (stop == null) {
627 err.jspError(start, "jsp.error.unterminated",
628 "<jsp:declaration>");
629 }
630 text = parseScriptText(reader.getText(start, stop));
631 new Node.Declaration(text, start, parent);
632 if (reader.matches("![CDATA[")) {
633 start = reader.mark();
634 stop = reader.skipUntil("]]>");
635 if (stop == null) {
636 err.jspError(start, "jsp.error.unterminated", "CDATA");
637 }
638 text = parseScriptText(reader.getText(start, stop));
639 new Node.Declaration(text, start, parent);
640 } else {
641 break;
642 }
643 }
644
645 if (!reader.matchesETagWithoutLessThan("jsp:declaration")) {
646 err.jspError(start, "jsp.error.unterminated",
647 "<jsp:declaration>");
648 }
649 }
650 }
651
652 /*
653 * ExpressionBody ::= (Char* - (char* '%>')) '%>'
654 */
655 private void parseExpression(Node parent) throws JasperException {
656 start = reader.mark();
657 Mark stop = reader.skipUntil("%>");
658 if (stop == null) {
659 err.jspError(start, "jsp.error.unterminated", "<%=");
660 }
661
662 new Node.Expression(parseScriptText(reader.getText(start, stop)),
663 start, parent);
664 }
665
666 /*
667 * XMLExpressionBody ::= ( S? '/>' ) | ( S? '>' (Char* - (char* '<'))
668 * CDSect?)* ETag ) | <TRANSLATION_ERROR>
669 */
670 private void parseXMLExpression(Node parent) throws JasperException {
671 reader.skipSpaces();
672 if (!reader.matches("/>")) {
673 if (!reader.matches(">")) {
674 err.jspError(start, "jsp.error.unterminated",
675 "<jsp:expression>");
676 }
677 Mark stop;
678 String text;
679 while (true) {
680 start = reader.mark();
681 stop = reader.skipUntil("<");
682 if (stop == null) {
683 err.jspError(start, "jsp.error.unterminated",
684 "<jsp:expression>");
685 }
686 text = parseScriptText(reader.getText(start, stop));
687 new Node.Expression(text, start, parent);
688 if (reader.matches("![CDATA[")) {
689 start = reader.mark();
690 stop = reader.skipUntil("]]>");
691 if (stop == null) {
692 err.jspError(start, "jsp.error.unterminated", "CDATA");
693 }
694 text = parseScriptText(reader.getText(start, stop));
695 new Node.Expression(text, start, parent);
696 } else {
697 break;
698 }
699 }
700 if (!reader.matchesETagWithoutLessThan("jsp:expression")) {
701 err.jspError(start, "jsp.error.unterminated",
702 "<jsp:expression>");
703 }
704 }
705 }
706
707 /*
708 * ELExpressionBody (following "${" to first unquoted "}") // XXX add formal
709 * production and confirm implementation against it, // once it's decided
710 */
711 private void parseELExpression(Node parent, char type) throws JasperException {
712 start = reader.mark();
713 Mark last = null;
714 boolean singleQuoted = false, doubleQuoted = false;
715 int currentChar;
716 do {
717 // XXX could move this logic to JspReader
718 last = reader.mark(); // XXX somewhat wasteful
719 currentChar = reader.nextChar();
720 if (currentChar == '\\' && (singleQuoted || doubleQuoted)) {
721 // skip character following '\' within quotes
722 reader.nextChar();
723 currentChar = reader.nextChar();
724 }
725 if (currentChar == -1)
726 err.jspError(start, "jsp.error.unterminated", type + "{");
727 if (currentChar == '"' && !singleQuoted)
728 doubleQuoted = !doubleQuoted;
729 if (currentChar == '\'' && !doubleQuoted)
730 singleQuoted = !singleQuoted;
731 } while (currentChar != '}' || (singleQuoted || doubleQuoted));
732
733 new Node.ELExpression(type, reader.getText(start, last), start, parent);
734 }
735
736 /*
737 * ScriptletBody ::= (Char* - (char* '%>')) '%>'
738 */
739 private void parseScriptlet(Node parent) throws JasperException {
740 start = reader.mark();
741 Mark stop = reader.skipUntil("%>");
742 if (stop == null) {
743 err.jspError(start, "jsp.error.unterminated", "<%");
744 }
745
746 new Node.Scriptlet(parseScriptText(reader.getText(start, stop)), start,
747 parent);
748 }
749
750 /*
751 * XMLScriptletBody ::= ( S? '/>' ) | ( S? '>' (Char* - (char* '<'))
752 * CDSect?)* ETag ) | <TRANSLATION_ERROR>
753 */
754 private void parseXMLScriptlet(Node parent) throws JasperException {
755 reader.skipSpaces();
756 if (!reader.matches("/>")) {
757 if (!reader.matches(">")) {
758 err.jspError(start, "jsp.error.unterminated",
759 "<jsp:scriptlet>");
760 }
761 Mark stop;
762 String text;
763 while (true) {
764 start = reader.mark();
765 stop = reader.skipUntil("<");
766 if (stop == null) {
767 err.jspError(start, "jsp.error.unterminated",
768 "<jsp:scriptlet>");
769 }
770 text = parseScriptText(reader.getText(start, stop));
771 new Node.Scriptlet(text, start, parent);
772 if (reader.matches("![CDATA[")) {
773 start = reader.mark();
774 stop = reader.skipUntil("]]>");
775 if (stop == null) {
776 err.jspError(start, "jsp.error.unterminated", "CDATA");
777 }
778 text = parseScriptText(reader.getText(start, stop));
779 new Node.Scriptlet(text, start, parent);
780 } else {
781 break;
782 }
783 }
784
785 if (!reader.matchesETagWithoutLessThan("jsp:scriptlet")) {
786 err.jspError(start, "jsp.error.unterminated",
787 "<jsp:scriptlet>");
788 }
789 }
790 }
791
792 /**
793 * Param ::= '<jsp:param' S Attributes S? EmptyBody S?
794 */
795 private void parseParam(Node parent) throws JasperException {
796 if (!reader.matches("<jsp:param")) {
797 err.jspError(reader.mark(), "jsp.error.paramexpected");
798 }
799 Attributes attrs = parseAttributes();
800 reader.skipSpaces();
801
802 Node paramActionNode = new Node.ParamAction(attrs, start, parent);
803
804 parseEmptyBody(paramActionNode, "jsp:param");
805
806 reader.skipSpaces();
807 }
808
809 /*
810 * For Include: StdActionContent ::= Attributes ParamBody
811 *
812 * ParamBody ::= EmptyBody | ( '>' S? ( '<jsp:attribute' NamedAttributes )? '<jsp:body'
813 * (JspBodyParam | <TRANSLATION_ERROR> ) S? ETag ) | ( '>' S? Param* ETag )
814 *
815 * EmptyBody ::= '/>' | ( '>' ETag ) | ( '>' S? '<jsp:attribute'
816 * NamedAttributes ETag )
817 *
818 * JspBodyParam ::= S? '>' Param* '</jsp:body>'
819 */
820 private void parseInclude(Node parent) throws JasperException {
821 Attributes attrs = parseAttributes();
822 reader.skipSpaces();
823
824 Node includeNode = new Node.IncludeAction(attrs, start, parent);
825
826 parseOptionalBody(includeNode, "jsp:include", JAVAX_BODY_CONTENT_PARAM);
827 }
828
829 /*
830 * For Forward: StdActionContent ::= Attributes ParamBody
831 */
832 private void parseForward(Node parent) throws JasperException {
833 Attributes attrs = parseAttributes();
834 reader.skipSpaces();
835
836 Node forwardNode = new Node.ForwardAction(attrs, start, parent);
837
838 parseOptionalBody(forwardNode, "jsp:forward", JAVAX_BODY_CONTENT_PARAM);
839 }
840
841 private void parseInvoke(Node parent) throws JasperException {
842 Attributes attrs = parseAttributes();
843 reader.skipSpaces();
844
845 Node invokeNode = new Node.InvokeAction(attrs, start, parent);
846
847 parseEmptyBody(invokeNode, "jsp:invoke");
848 }
849
850 private void parseDoBody(Node parent) throws JasperException {
851 Attributes attrs = parseAttributes();
852 reader.skipSpaces();
853
854 Node doBodyNode = new Node.DoBodyAction(attrs, start, parent);
855
856 parseEmptyBody(doBodyNode, "jsp:doBody");
857 }
858
859 private void parseElement(Node parent) throws JasperException {
860 Attributes attrs = parseAttributes();
861 reader.skipSpaces();
862
863 Node elementNode = new Node.JspElement(attrs, start, parent);
864
865 parseOptionalBody(elementNode, "jsp:element", TagInfo.BODY_CONTENT_JSP);
866 }
867
868 /*
869 * For GetProperty: StdActionContent ::= Attributes EmptyBody
870 */
871 private void parseGetProperty(Node parent) throws JasperException {
872 Attributes attrs = parseAttributes();
873 reader.skipSpaces();
874
875 Node getPropertyNode = new Node.GetProperty(attrs, start, parent);
876
877 parseOptionalBody(getPropertyNode, "jsp:getProperty",
878 TagInfo.BODY_CONTENT_EMPTY);
879 }
880
881 /*
882 * For SetProperty: StdActionContent ::= Attributes EmptyBody
883 */
884 private void parseSetProperty(Node parent) throws JasperException {
885 Attributes attrs = parseAttributes();
886 reader.skipSpaces();
887
888 Node setPropertyNode = new Node.SetProperty(attrs, start, parent);
889
890 parseOptionalBody(setPropertyNode, "jsp:setProperty",
891 TagInfo.BODY_CONTENT_EMPTY);
892 }
893
894 /*
895 * EmptyBody ::= '/>' | ( '>' ETag ) | ( '>' S? '<jsp:attribute'
896 * NamedAttributes ETag )
897 */
898 private void parseEmptyBody(Node parent, String tag) throws JasperException {
899 if (reader.matches("/>")) {
900 // Done
901 } else if (reader.matches(">")) {
902 if (reader.matchesETag(tag)) {
903 // Done
904 } else if (reader.matchesOptionalSpacesFollowedBy("<jsp:attribute")) {
905 // Parse the one or more named attribute nodes
906 parseNamedAttributes(parent);
907 if (!reader.matchesETag(tag)) {
908 // Body not allowed
909 err.jspError(reader.mark(),
910 "jsp.error.jspbody.emptybody.only", "<" + tag);
911 }
912 } else {
913 err.jspError(reader.mark(), "jsp.error.jspbody.emptybody.only",
914 "<" + tag);
915 }
916 } else {
917 err.jspError(reader.mark(), "jsp.error.unterminated", "<" + tag);
918 }
919 }
920
921 /*
922 * For UseBean: StdActionContent ::= Attributes OptionalBody
923 */
924 private void parseUseBean(Node parent) throws JasperException {
925 Attributes attrs = parseAttributes();
926 reader.skipSpaces();
927
928 Node useBeanNode = new Node.UseBean(attrs, start, parent);
929
930 parseOptionalBody(useBeanNode, "jsp:useBean", TagInfo.BODY_CONTENT_JSP);
931 }
932
933 /*
934 * Parses OptionalBody, but also reused to parse bodies for plugin and param
935 * since the syntax is identical (the only thing that differs substantially
936 * is how to process the body, and thus we accept the body type as a
937 * parameter).
938 *
939 * OptionalBody ::= EmptyBody | ActionBody
940 *
941 * ScriptlessOptionalBody ::= EmptyBody | ScriptlessActionBody
942 *
943 * TagDependentOptionalBody ::= EmptyBody | TagDependentActionBody
944 *
945 * EmptyBody ::= '/>' | ( '>' ETag ) | ( '>' S? '<jsp:attribute'
946 * NamedAttributes ETag )
947 *
948 * ActionBody ::= JspAttributeAndBody | ( '>' Body ETag )
949 *
950 * ScriptlessActionBody ::= JspAttributeAndBody | ( '>' ScriptlessBody ETag )
951 *
952 * TagDependentActionBody ::= JspAttributeAndBody | ( '>' TagDependentBody
953 * ETag )
954 *
955 */
956 private void parseOptionalBody(Node parent, String tag, String bodyType)
957 throws JasperException {
958 if (reader.matches("/>")) {
959 // EmptyBody
960 return;
961 }
962
963 if (!reader.matches(">")) {
964 err.jspError(reader.mark(), "jsp.error.unterminated", "<" + tag);
965 }
966
967 if (reader.matchesETag(tag)) {
968 // EmptyBody
969 return;
970 }
971
972 if (!parseJspAttributeAndBody(parent, tag, bodyType)) {
973 // Must be ( '>' # Body ETag )
974 parseBody(parent, tag, bodyType);
975 }
976 }
977
978 /**
979 * Attempts to parse 'JspAttributeAndBody' production. Returns true if it
980 * matched, or false if not. Assumes EmptyBody is okay as well.
981 *
982 * JspAttributeAndBody ::= ( '>' # S? ( '<jsp:attribute' NamedAttributes )? '<jsp:body' (
983 * JspBodyBody | <TRANSLATION_ERROR> ) S? ETag )
984 */
985 private boolean parseJspAttributeAndBody(Node parent, String tag,
986 String bodyType) throws JasperException {
987 boolean result = false;
988
989 if (reader.matchesOptionalSpacesFollowedBy("<jsp:attribute")) {
990 // May be an EmptyBody, depending on whether
991 // There's a "<jsp:body" before the ETag
992
993 // First, parse <jsp:attribute> elements:
994 parseNamedAttributes(parent);
995
996 result = true;
997 }
998
999 if (reader.matchesOptionalSpacesFollowedBy("<jsp:body")) {
1000 // ActionBody
1001 parseJspBody(parent, bodyType);
1002 reader.skipSpaces();
1003 if (!reader.matchesETag(tag)) {
1004 err.jspError(reader.mark(), "jsp.error.unterminated", "<"
1005 + tag);
1006 }
1007
1008 result = true;
1009 } else if (result && !reader.matchesETag(tag)) {
1010 // If we have <jsp:attribute> but something other than
1011 // <jsp:body> or the end tag, translation error.
1012 err.jspError(reader.mark(), "jsp.error.jspbody.required", "<"
1013 + tag);
1014 }
1015
1016 return result;
1017 }
1018
1019 /*
1020 * Params ::= `>' S? ( ( `<jsp:body>' ( ( S? Param+ S? `</jsp:body>' ) |
1021 * <TRANSLATION_ERROR> ) ) | Param+ ) '</jsp:params>'
1022 */
1023 private void parseJspParams(Node parent) throws JasperException {
1024 Node jspParamsNode = new Node.ParamsAction(start, parent);
1025 parseOptionalBody(jspParamsNode, "jsp:params", JAVAX_BODY_CONTENT_PARAM);
1026 }
1027
1028 /*
1029 * Fallback ::= '/>' | ( `>' S? `<jsp:body>' ( ( S? ( Char* - ( Char* `</jsp:body>' ) ) `</jsp:body>'
1030 * S? ) | <TRANSLATION_ERROR> ) `</jsp:fallback>' ) | ( '>' ( Char* - (
1031 * Char* '</jsp:fallback>' ) ) '</jsp:fallback>' )
1032 */
1033 private void parseFallBack(Node parent) throws JasperException {
1034 Node fallBackNode = new Node.FallBackAction(start, parent);
1035 parseOptionalBody(fallBackNode, "jsp:fallback",
1036 JAVAX_BODY_CONTENT_TEMPLATE_TEXT);
1037 }
1038
1039 /*
1040 * For Plugin: StdActionContent ::= Attributes PluginBody
1041 *
1042 * PluginBody ::= EmptyBody | ( '>' S? ( '<jsp:attribute' NamedAttributes )? '<jsp:body' (
1043 * JspBodyPluginTags | <TRANSLATION_ERROR> ) S? ETag ) | ( '>' S? PluginTags
1044 * ETag )
1045 *
1046 * EmptyBody ::= '/>' | ( '>' ETag ) | ( '>' S? '<jsp:attribute'
1047 * NamedAttributes ETag )
1048 *
1049 */
1050 private void parsePlugin(Node parent) throws JasperException {
1051 Attributes attrs = parseAttributes();
1052 reader.skipSpaces();
1053
1054 Node pluginNode = new Node.PlugIn(attrs, start, parent);
1055
1056 parseOptionalBody(pluginNode, "jsp:plugin", JAVAX_BODY_CONTENT_PLUGIN);
1057 }
1058
1059 /*
1060 * PluginTags ::= ( '<jsp:params' Params S? )? ( '<jsp:fallback' Fallback?
1061 * S? )?
1062 */
1063 private void parsePluginTags(Node parent) throws JasperException {
1064 reader.skipSpaces();
1065
1066 if (reader.matches("<jsp:params")) {
1067 parseJspParams(parent);
1068 reader.skipSpaces();
1069 }
1070
1071 if (reader.matches("<jsp:fallback")) {
1072 parseFallBack(parent);
1073 reader.skipSpaces();
1074 }
1075 }
1076
1077 /*
1078 * StandardAction ::= 'include' StdActionContent | 'forward'
1079 * StdActionContent | 'invoke' StdActionContent | 'doBody' StdActionContent |
1080 * 'getProperty' StdActionContent | 'setProperty' StdActionContent |
1081 * 'useBean' StdActionContent | 'plugin' StdActionContent | 'element'
1082 * StdActionContent
1083 */
1084 private void parseStandardAction(Node parent) throws JasperException {
1085 Mark start = reader.mark();
1086
1087 if (reader.matches(INCLUDE_ACTION)) {
1088 parseInclude(parent);
1089 } else if (reader.matches(FORWARD_ACTION)) {
1090 parseForward(parent);
1091 } else if (reader.matches(INVOKE_ACTION)) {
1092 if (!isTagFile) {
1093 err.jspError(reader.mark(), "jsp.error.action.isnottagfile",
1094 "<jsp:invoke");
1095 }
1096 parseInvoke(parent);
1097 } else if (reader.matches(DOBODY_ACTION)) {
1098 if (!isTagFile) {
1099 err.jspError(reader.mark(), "jsp.error.action.isnottagfile",
1100 "<jsp:doBody");
1101 }
1102 parseDoBody(parent);
1103 } else if (reader.matches(GET_PROPERTY_ACTION)) {
1104 parseGetProperty(parent);
1105 } else if (reader.matches(SET_PROPERTY_ACTION)) {
1106 parseSetProperty(parent);
1107 } else if (reader.matches(USE_BEAN_ACTION)) {
1108 parseUseBean(parent);
1109 } else if (reader.matches(PLUGIN_ACTION)) {
1110 parsePlugin(parent);
1111 } else if (reader.matches(ELEMENT_ACTION)) {
1112 parseElement(parent);
1113 } else if (reader.matches(ATTRIBUTE_ACTION)) {
1114 err.jspError(start, "jsp.error.namedAttribute.invalidUse");
1115 } else if (reader.matches(BODY_ACTION)) {
1116 err.jspError(start, "jsp.error.jspbody.invalidUse");
1117 } else if (reader.matches(FALLBACK_ACTION)) {
1118 err.jspError(start, "jsp.error.fallback.invalidUse");
1119 } else if (reader.matches(PARAMS_ACTION)) {
1120 err.jspError(start, "jsp.error.params.invalidUse");
1121 } else if (reader.matches(PARAM_ACTION)) {
1122 err.jspError(start, "jsp.error.param.invalidUse");
1123 } else if (reader.matches(OUTPUT_ACTION)) {
1124 err.jspError(start, "jsp.error.jspoutput.invalidUse");
1125 } else {
1126 err.jspError(start, "jsp.error.badStandardAction");
1127 }
1128 }
1129
1130 /*
1131 * # '<' CustomAction CustomActionBody
1132 *
1133 * CustomAction ::= TagPrefix ':' CustomActionName
1134 *
1135 * TagPrefix ::= Name
1136 *
1137 * CustomActionName ::= Name
1138 *
1139 * CustomActionBody ::= ( Attributes CustomActionEnd ) | <TRANSLATION_ERROR>
1140 *
1141 * Attributes ::= ( S Attribute )* S?
1142 *
1143 * CustomActionEnd ::= CustomActionTagDependent | CustomActionJSPContent |
1144 * CustomActionScriptlessContent
1145 *
1146 * CustomActionTagDependent ::= TagDependentOptionalBody
1147 *
1148 * CustomActionJSPContent ::= OptionalBody
1149 *
1150 * CustomActionScriptlessContent ::= ScriptlessOptionalBody
1151 */
1152 private boolean parseCustomTag(Node parent) throws JasperException {
1153
1154 if (reader.peekChar() != '<') {
1155 return false;
1156 }
1157
1158 // Parse 'CustomAction' production (tag prefix and custom action name)
1159 reader.nextChar(); // skip '<'
1160 String tagName = reader.parseToken(false);
1161 int i = tagName.indexOf(':');
1162 if (i == -1) {
1163 reader.reset(start);
1164 return false;
1165 }
1166
1167 String prefix = tagName.substring(0, i);
1168 String shortTagName = tagName.substring(i + 1);
1169
1170 // Check if this is a user-defined tag.
1171 String uri = pageInfo.getURI(prefix);
1172 if (uri == null) {
1173 reader.reset(start);
1174 // Remember the prefix for later error checking
1175 pageInfo.putNonCustomTagPrefix(prefix, reader.mark());
1176 return false;
1177 }
1178
1179 TagLibraryInfo tagLibInfo = pageInfo.getTaglib(uri);
1180 TagInfo tagInfo = tagLibInfo.getTag(shortTagName);
1181 TagFileInfo tagFileInfo = tagLibInfo.getTagFile(shortTagName);
1182 if (tagInfo == null && tagFileInfo == null) {
1183 err.jspError(start, "jsp.error.bad_tag", shortTagName, prefix);
1184 }
1185 Class tagHandlerClass = null;
1186 if (tagInfo != null) {
1187 // Must be a classic tag, load it here.
1188 // tag files will be loaded later, in TagFileProcessor
1189 String handlerClassName = tagInfo.getTagClassName();
1190 try {
1191 tagHandlerClass = ctxt.getClassLoader().loadClass(
1192 handlerClassName);
1193 } catch (Exception e) {
1194 err.jspError(start, "jsp.error.loadclass.taghandler",
1195 handlerClassName, tagName);
1196 }
1197 }
1198
1199 // Parse 'CustomActionBody' production:
1200 // At this point we are committed - if anything fails, we produce
1201 // a translation error.
1202
1203 // Parse 'Attributes' production:
1204 Attributes attrs = parseAttributes();
1205 reader.skipSpaces();
1206
1207 // Parse 'CustomActionEnd' production:
1208 if (reader.matches("/>")) {
1209 if (tagInfo != null) {
1210 new Node.CustomTag(tagName, prefix, shortTagName, uri, attrs,
1211 start, parent, tagInfo, tagHandlerClass);
1212 } else {
1213 new Node.CustomTag(tagName, prefix, shortTagName, uri, attrs,
1214 start, parent, tagFileInfo);
1215 }
1216 return true;
1217 }
1218
1219 // Now we parse one of 'CustomActionTagDependent',
1220 // 'CustomActionJSPContent', or 'CustomActionScriptlessContent'.
1221 // depending on body-content in TLD.
1222
1223 // Looking for a body, it still can be empty; but if there is a
1224 // a tag body, its syntax would be dependent on the type of
1225 // body content declared in the TLD.
1226 String bc;
1227 if (tagInfo != null) {
1228 bc = tagInfo.getBodyContent();
1229 } else {
1230 bc = tagFileInfo.getTagInfo().getBodyContent();
1231 }
1232
1233 Node tagNode = null;
1234 if (tagInfo != null) {
1235 tagNode = new Node.CustomTag(tagName, prefix, shortTagName, uri,
1236 attrs, start, parent, tagInfo, tagHandlerClass);
1237 } else {
1238 tagNode = new Node.CustomTag(tagName, prefix, shortTagName, uri,
1239 attrs, start, parent, tagFileInfo);
1240 }
1241
1242 parseOptionalBody(tagNode, tagName, bc);
1243
1244 return true;
1245 }
1246
1247 /*
1248 * Parse for a template text string until '<' or "${" or "#{" is encountered,
1249 * recognizing escape sequences "\%", "\$", and "\#".
1250 */
1251 private void parseTemplateText(Node parent) throws JasperException {
1252
1253 if (!reader.hasMoreInput())
1254 return;
1255
1256 CharArrayWriter ttext = new CharArrayWriter();
1257 // Output the first character
1258 int ch = reader.nextChar();
1259 if (ch == '\\') {
1260 reader.pushChar();
1261 } else {
1262 ttext.write(ch);
1263 }
1264
1265 while (reader.hasMoreInput()) {
1266 ch = reader.nextChar();
1267 if (ch == '<') {
1268 reader.pushChar();
1269 break;
1270 } else if ((ch == '$' || ch == '#') && !pageInfo.isELIgnored()) {
1271 if (!reader.hasMoreInput()) {
1272 ttext.write(ch);
1273 break;
1274 }
1275 if (reader.nextChar() == '{') {
1276 reader.pushChar();
1277 reader.pushChar();
1278 break;
1279 }
1280 ttext.write(ch);
1281 reader.pushChar();
1282 continue;
1283 } else if (ch == '\\') {
1284 if (!reader.hasMoreInput()) {
1285 ttext.write('\\');
1286 break;
1287 }
1288 char next = (char) reader.peekChar();
1289 // Looking for \% or \$ or \#
1290 if (next == '%' || ((next == '$' || next == '#') &&
1291 !pageInfo.isELIgnored())) {
1292 ch = reader.nextChar();
1293 }
1294 }
1295 ttext.write(ch);
1296 }
1297 new Node.TemplateText(ttext.toString(), start, parent);
1298 }
1299
1300 /*
1301 * XMLTemplateText ::= ( S? '/>' ) | ( S? '>' ( ( Char* - ( Char* ( '<' |
1302 * '${' ) ) ) ( '${' ELExpressionBody )? CDSect? )* ETag ) |
1303 * <TRANSLATION_ERROR>
1304 */
1305 private void parseXMLTemplateText(Node parent) throws JasperException {
1306 reader.skipSpaces();
1307 if (!reader.matches("/>")) {
1308 if (!reader.matches(">")) {
1309 err.jspError(start, "jsp.error.unterminated",
1310 "<jsp:text>");
1311 }
1312 CharArrayWriter ttext = new CharArrayWriter();
1313 while (reader.hasMoreInput()) {
1314 int ch = reader.nextChar();
1315 if (ch == '<') {
1316 // Check for <![CDATA[
1317 if (!reader.matches("![CDATA[")) {
1318 break;
1319 }
1320 start = reader.mark();
1321 Mark stop = reader.skipUntil("]]>");
1322 if (stop == null) {
1323 err.jspError(start, "jsp.error.unterminated", "CDATA");
1324 }
1325 String text = reader.getText(start, stop);
1326 ttext.write(text, 0, text.length());
1327 } else if (ch == '\\') {
1328 if (!reader.hasMoreInput()) {
1329 ttext.write('\\');
1330 break;
1331 }
1332 ch = reader.nextChar();
1333 if (ch != '$' && ch != '#') {
1334 ttext.write('\\');
1335 }
1336 ttext.write(ch);
1337 } else if (ch == '$' || ch == '#') {
1338 if (!reader.hasMoreInput()) {
1339 ttext.write(ch);
1340 break;
1341 }
1342 if (reader.nextChar() != '{') {
1343 ttext.write(ch);
1344 reader.pushChar();
1345 continue;
1346 }
1347 // Create a template text node
1348 new Node.TemplateText(ttext.toString(), start, parent);
1349
1350 // Mark and parse the EL expression and create its node:
1351 start = reader.mark();
1352 parseELExpression(parent, (char) ch);
1353
1354 start = reader.mark();
1355 ttext = new CharArrayWriter();
1356 } else {
1357 ttext.write(ch);
1358 }
1359 }
1360
1361 new Node.TemplateText(ttext.toString(), start, parent);
1362
1363 if (!reader.hasMoreInput()) {
1364 err.jspError(start, "jsp.error.unterminated",
1365 "<jsp:text>");
1366 } else if (!reader.matchesETagWithoutLessThan("jsp:text")) {
1367 err.jspError(start, "jsp.error.jsptext.badcontent");
1368 }
1369 }
1370 }
1371
1372 /*
1373 * AllBody ::= ( '<%--' JSPCommentBody ) | ( '<%@' DirectiveBody ) | ( '<jsp:directive.'
1374 * XMLDirectiveBody ) | ( '<%!' DeclarationBody ) | ( '<jsp:declaration'
1375 * XMLDeclarationBody ) | ( '<%=' ExpressionBody ) | ( '<jsp:expression'
1376 * XMLExpressionBody ) | ( '${' ELExpressionBody ) | ( '<%' ScriptletBody ) | ( '<jsp:scriptlet'
1377 * XMLScriptletBody ) | ( '<jsp:text' XMLTemplateText ) | ( '<jsp:'
1378 * StandardAction ) | ( '<' CustomAction CustomActionBody ) | TemplateText
1379 */
1380 private void parseElements(Node parent) throws JasperException {
1381 if (scriptlessCount > 0) {
1382 // vc: ScriptlessBody
1383 // We must follow the ScriptlessBody production if one of
1384 // our parents is ScriptlessBody.
1385 parseElementsScriptless(parent);
1386 return;
1387 }
1388
1389 start = reader.mark();
1390 if (reader.matches("<%--")) {
1391 parseComment(parent);
1392 } else if (reader.matches("<%@")) {
1393 parseDirective(parent);
1394 } else if (reader.matches("<jsp:directive.")) {
1395 parseXMLDirective(parent);
1396 } else if (reader.matches("<%!")) {
1397 parseDeclaration(parent);
1398 } else if (reader.matches("<jsp:declaration")) {
1399 parseXMLDeclaration(parent);
1400 } else if (reader.matches("<%=")) {
1401 parseExpression(parent);
1402 } else if (reader.matches("<jsp:expression")) {
1403 parseXMLExpression(parent);
1404 } else if (reader.matches("<%")) {
1405 parseScriptlet(parent);
1406 } else if (reader.matches("<jsp:scriptlet")) {
1407 parseXMLScriptlet(parent);
1408 } else if (reader.matches("<jsp:text")) {
1409 parseXMLTemplateText(parent);
1410 } else if (!pageInfo.isELIgnored() && reader.matches("${")) {
1411 parseELExpression(parent, '$');
1412 } else if (!pageInfo.isELIgnored()
1413 && !pageInfo.isDeferredSyntaxAllowedAsLiteral()
1414 && reader.matches("#{")) {
1415 parseELExpression(parent, '#');
1416 } else if (reader.matches("<jsp:")) {
1417 parseStandardAction(parent);
1418 } else if (!parseCustomTag(parent)) {
1419 checkUnbalancedEndTag();
1420 parseTemplateText(parent);
1421 }
1422 }
1423
1424 /*
1425 * ScriptlessBody ::= ( '<%--' JSPCommentBody ) | ( '<%@' DirectiveBody ) | ( '<jsp:directive.'
1426 * XMLDirectiveBody ) | ( '<%!' <TRANSLATION_ERROR> ) | ( '<jsp:declaration'
1427 * <TRANSLATION_ERROR> ) | ( '<%=' <TRANSLATION_ERROR> ) | ( '<jsp:expression'
1428 * <TRANSLATION_ERROR> ) | ( '<%' <TRANSLATION_ERROR> ) | ( '<jsp:scriptlet'
1429 * <TRANSLATION_ERROR> ) | ( '<jsp:text' XMLTemplateText ) | ( '${'
1430 * ELExpressionBody ) | ( '<jsp:' StandardAction ) | ( '<' CustomAction
1431 * CustomActionBody ) | TemplateText
1432 */
1433 private void parseElementsScriptless(Node parent) throws JasperException {
1434 // Keep track of how many scriptless nodes we've encountered
1435 // so we know whether our child nodes are forced scriptless
1436 scriptlessCount++;
1437
1438 start = reader.mark();
1439 if (reader.matches("<%--")) {
1440 parseComment(parent);
1441 } else if (reader.matches("<%@")) {
1442 parseDirective(parent);
1443 } else if (reader.matches("<jsp:directive.")) {
1444 parseXMLDirective(parent);
1445 } else if (reader.matches("<%!")) {
1446 err.jspError(reader.mark(), "jsp.error.no.scriptlets");
1447 } else if (reader.matches("<jsp:declaration")) {
1448 err.jspError(reader.mark(), "jsp.error.no.scriptlets");
1449 } else if (reader.matches("<%=")) {
1450 err.jspError(reader.mark(), "jsp.error.no.scriptlets");
1451 } else if (reader.matches("<jsp:expression")) {
1452 err.jspError(reader.mark(), "jsp.error.no.scriptlets");
1453 } else if (reader.matches("<%")) {
1454 err.jspError(reader.mark(), "jsp.error.no.scriptlets");
1455 } else if (reader.matches("<jsp:scriptlet")) {
1456 err.jspError(reader.mark(), "jsp.error.no.scriptlets");
1457 } else if (reader.matches("<jsp:text")) {
1458 parseXMLTemplateText(parent);
1459 } else if (!pageInfo.isELIgnored() && reader.matches("${")) {
1460 parseELExpression(parent, '$');
1461 } else if (!pageInfo.isELIgnored()
1462 && !pageInfo.isDeferredSyntaxAllowedAsLiteral()
1463 && reader.matches("#{")) {
1464 parseELExpression(parent, '#');
1465 } else if (reader.matches("<jsp:")) {
1466 parseStandardAction(parent);
1467 } else if (!parseCustomTag(parent)) {
1468 checkUnbalancedEndTag();
1469 parseTemplateText(parent);
1470 }
1471
1472 scriptlessCount--;
1473 }
1474
1475 /*
1476 * TemplateTextBody ::= ( '<%--' JSPCommentBody ) | ( '<%@' DirectiveBody ) | ( '<jsp:directive.'
1477 * XMLDirectiveBody ) | ( '<%!' <TRANSLATION_ERROR> ) | ( '<jsp:declaration'
1478 * <TRANSLATION_ERROR> ) | ( '<%=' <TRANSLATION_ERROR> ) | ( '<jsp:expression'
1479 * <TRANSLATION_ERROR> ) | ( '<%' <TRANSLATION_ERROR> ) | ( '<jsp:scriptlet'
1480 * <TRANSLATION_ERROR> ) | ( '<jsp:text' <TRANSLATION_ERROR> ) | ( '${'
1481 * <TRANSLATION_ERROR> ) | ( '<jsp:' <TRANSLATION_ERROR> ) | TemplateText
1482 */
1483 private void parseElementsTemplateText(Node parent) throws JasperException {
1484 start = reader.mark();
1485 if (reader.matches("<%--")) {
1486 parseComment(parent);
1487 } else if (reader.matches("<%@")) {
1488 parseDirective(parent);
1489 } else if (reader.matches("<jsp:directive.")) {
1490 parseXMLDirective(parent);
1491 } else if (reader.matches("<%!")) {
1492 err.jspError(reader.mark(), "jsp.error.not.in.template",
1493 "Declarations");
1494 } else if (reader.matches("<jsp:declaration")) {
1495 err.jspError(reader.mark(), "jsp.error.not.in.template",
1496 "Declarations");
1497 } else if (reader.matches("<%=")) {
1498 err.jspError(reader.mark(), "jsp.error.not.in.template",
1499 "Expressions");
1500 } else if (reader.matches("<jsp:expression")) {
1501 err.jspError(reader.mark(), "jsp.error.not.in.template",
1502 "Expressions");
1503 } else if (reader.matches("<%")) {
1504 err.jspError(reader.mark(), "jsp.error.not.in.template",
1505 "Scriptlets");
1506 } else if (reader.matches("<jsp:scriptlet")) {
1507 err.jspError(reader.mark(), "jsp.error.not.in.template",
1508 "Scriptlets");
1509 } else if (reader.matches("<jsp:text")) {
1510 err.jspError(reader.mark(), "jsp.error.not.in.template",
1511 "<jsp:text");
1512 } else if (!pageInfo.isELIgnored() && reader.matches("${")) {
1513 err.jspError(reader.mark(), "jsp.error.not.in.template",
1514 "Expression language");
1515 } else if (!pageInfo.isELIgnored()
1516 && !pageInfo.isDeferredSyntaxAllowedAsLiteral()
1517 && reader.matches("#{")) {
1518 err.jspError(reader.mark(), "jsp.error.not.in.template",
1519 "Expression language");
1520 } else if (reader.matches("<jsp:")) {
1521 err.jspError(reader.mark(), "jsp.error.not.in.template",
1522 "Standard actions");
1523 } else if (parseCustomTag(parent)) {
1524 err.jspError(reader.mark(), "jsp.error.not.in.template",
1525 "Custom actions");
1526 } else {
1527 checkUnbalancedEndTag();
1528 parseTemplateText(parent);
1529 }
1530 }
1531
1532 /*
1533 * Flag as error if an unbalanced end tag appears by itself.
1534 */
1535 private void checkUnbalancedEndTag() throws JasperException {
1536
1537 if (!reader.matches("</")) {
1538 return;
1539 }
1540
1541 // Check for unbalanced standard actions
1542 if (reader.matches("jsp:")) {
1543 err.jspError(start, "jsp.error.unbalanced.endtag", "jsp:");
1544 }
1545
1546 // Check for unbalanced custom actions
1547 String tagName = reader.parseToken(false);
1548 int i = tagName.indexOf(':');
1549 if (i == -1 || pageInfo.getURI(tagName.substring(0, i)) == null) {
1550 reader.reset(start);
1551 return;
1552 }
1553
1554 err.jspError(start, "jsp.error.unbalanced.endtag", tagName);
1555 }
1556
1557 /**
1558 * TagDependentBody :=
1559 */
1560 private void parseTagDependentBody(Node parent, String tag)
1561 throws JasperException {
1562 Mark bodyStart = reader.mark();
1563 Mark bodyEnd = reader.skipUntilETag(tag);
1564 if (bodyEnd == null) {
1565 err.jspError(start, "jsp.error.unterminated", "<" + tag);
1566 }
1567 new Node.TemplateText(reader.getText(bodyStart, bodyEnd), bodyStart,
1568 parent);
1569 }
1570
1571 /*
1572 * Parses jsp:body action.
1573 */
1574 private void parseJspBody(Node parent, String bodyType)
1575 throws JasperException {
1576 Mark start = reader.mark();
1577 Node bodyNode = new Node.JspBody(start, parent);
1578
1579 reader.skipSpaces();
1580 if (!reader.matches("/>")) {
1581 if (!reader.matches(">")) {
1582 err.jspError(start, "jsp.error.unterminated", "<jsp:body");
1583 }
1584 parseBody(bodyNode, "jsp:body", bodyType);
1585 }
1586 }
1587
1588 /*
1589 * Parse the body as JSP content. @param tag The name of the tag whose end
1590 * tag would terminate the body @param bodyType One of the TagInfo body
1591 * types
1592 */
1593 private void parseBody(Node parent, String tag, String bodyType)
1594 throws JasperException {
1595 if (bodyType.equalsIgnoreCase(TagInfo.BODY_CONTENT_TAG_DEPENDENT)) {
1596 parseTagDependentBody(parent, tag);
1597 } else if (bodyType.equalsIgnoreCase(TagInfo.BODY_CONTENT_EMPTY)) {
1598 if (!reader.matchesETag(tag)) {
1599 err.jspError(start, "jasper.error.emptybodycontent.nonempty",
1600 tag);
1601 }
1602 } else if (bodyType == JAVAX_BODY_CONTENT_PLUGIN) {
1603 // (note the == since we won't recognize JAVAX_*
1604 // from outside this module).
1605 parsePluginTags(parent);
1606 if (!reader.matchesETag(tag)) {
1607 err.jspError(reader.mark(), "jsp.error.unterminated", "<"
1608 + tag);
1609 }
1610 } else if (bodyType.equalsIgnoreCase(TagInfo.BODY_CONTENT_JSP)
1611 || bodyType.equalsIgnoreCase(TagInfo.BODY_CONTENT_SCRIPTLESS)
1612 || (bodyType == JAVAX_BODY_CONTENT_PARAM)
1613 || (bodyType == JAVAX_BODY_CONTENT_TEMPLATE_TEXT)) {
1614 while (reader.hasMoreInput()) {
1615 if (reader.matchesETag(tag)) {
1616 return;
1617 }
1618
1619 // Check for nested jsp:body or jsp:attribute
1620 if (tag.equals("jsp:body") || tag.equals("jsp:attribute")) {
1621 if (reader.matches("<jsp:attribute")) {
1622 err.jspError(reader.mark(),
1623 "jsp.error.nested.jspattribute");
1624 } else if (reader.matches("<jsp:body")) {
1625 err.jspError(reader.mark(), "jsp.error.nested.jspbody");
1626 }
1627 }
1628
1629 if (bodyType.equalsIgnoreCase(TagInfo.BODY_CONTENT_JSP)) {
1630 parseElements(parent);
1631 } else if (bodyType
1632 .equalsIgnoreCase(TagInfo.BODY_CONTENT_SCRIPTLESS)) {
1633 parseElementsScriptless(parent);
1634 } else if (bodyType == JAVAX_BODY_CONTENT_PARAM) {
1635 // (note the == since we won't recognize JAVAX_*
1636 // from outside this module).
1637 reader.skipSpaces();
1638 parseParam(parent);
1639 } else if (bodyType == JAVAX_BODY_CONTENT_TEMPLATE_TEXT) {
1640 parseElementsTemplateText(parent);
1641 }
1642 }
1643 err.jspError(start, "jsp.error.unterminated", "<" + tag);
1644 } else {
1645 err.jspError(start, "jasper.error.bad.bodycontent.type");
1646 }
1647 }
1648
1649 /*
1650 * Parses named attributes.
1651 */
1652 private void parseNamedAttributes(Node parent) throws JasperException {
1653 do {
1654 Mark start = reader.mark();
1655 Attributes attrs = parseAttributes();
1656 Node.NamedAttribute namedAttributeNode = new Node.NamedAttribute(
1657 attrs, start, parent);
1658
1659 reader.skipSpaces();
1660 if (!reader.matches("/>")) {
1661 if (!reader.matches(">")) {
1662 err.jspError(start, "jsp.error.unterminated",
1663 "<jsp:attribute");
1664 }
1665 if (namedAttributeNode.isTrim()) {
1666 reader.skipSpaces();
1667 }
1668 parseBody(namedAttributeNode, "jsp:attribute",
1669 getAttributeBodyType(parent, attrs.getValue("name")));
1670 if (namedAttributeNode.isTrim()) {
1671 Node.Nodes subElems = namedAttributeNode.getBody();
1672 if (subElems != null) {
1673 Node lastNode = subElems.getNode(subElems.size() - 1);
1674 if (lastNode instanceof Node.TemplateText) {
1675 ((Node.TemplateText) lastNode).rtrim();
1676 }
1677 }
1678 }
1679 }
1680 reader.skipSpaces();
1681 } while (reader.matches("<jsp:attribute"));
1682 }
1683
1684 /**
1685 * Determine the body type of <jsp:attribute> from the enclosing node
1686 */
1687 private String getAttributeBodyType(Node n, String name) {
1688
1689 if (n instanceof Node.CustomTag) {
1690 TagInfo tagInfo = ((Node.CustomTag) n).getTagInfo();
1691 TagAttributeInfo[] tldAttrs = tagInfo.getAttributes();
1692 for (int i = 0; i < tldAttrs.length; i++) {
1693 if (name.equals(tldAttrs[i].getName())) {
1694 if (tldAttrs[i].isFragment()) {
1695 return TagInfo.BODY_CONTENT_SCRIPTLESS;
1696 }
1697 if (tldAttrs[i].canBeRequestTime()) {
1698 return TagInfo.BODY_CONTENT_JSP;
1699 }
1700 }
1701 }
1702 if (tagInfo.hasDynamicAttributes()) {
1703 return TagInfo.BODY_CONTENT_JSP;
1704 }
1705 } else if (n instanceof Node.IncludeAction) {
1706 if ("page".equals(name)) {
1707 return TagInfo.BODY_CONTENT_JSP;
1708 }
1709 } else if (n instanceof Node.ForwardAction) {
1710 if ("page".equals(name)) {
1711 return TagInfo.BODY_CONTENT_JSP;
1712 }
1713 } else if (n instanceof Node.SetProperty) {
1714 if ("value".equals(name)) {
1715 return TagInfo.BODY_CONTENT_JSP;
1716 }
1717 } else if (n instanceof Node.UseBean) {
1718 if ("beanName".equals(name)) {
1719 return TagInfo.BODY_CONTENT_JSP;
1720 }
1721 } else if (n instanceof Node.PlugIn) {
1722 if ("width".equals(name) || "height".equals(name)) {
1723 return TagInfo.BODY_CONTENT_JSP;
1724 }
1725 } else if (n instanceof Node.ParamAction) {
1726 if ("value".equals(name)) {
1727 return TagInfo.BODY_CONTENT_JSP;
1728 }
1729 } else if (n instanceof Node.JspElement) {
1730 return TagInfo.BODY_CONTENT_JSP;
1731 }
1732
1733 return JAVAX_BODY_CONTENT_TEMPLATE_TEXT;
1734 }
1735
1736 private void parseFileDirectives(Node parent) throws JasperException {
1737 reader.setSingleFile(true);
1738 reader.skipUntil("<");
1739 while (reader.hasMoreInput()) {
1740 start = reader.mark();
1741 if (reader.matches("%--")) {
1742 // Comment
1743 reader.skipUntil("--%>");
1744 } else if (reader.matches("%@")) {
1745 parseDirective(parent);
1746 } else if (reader.matches("jsp:directive.")) {
1747 parseXMLDirective(parent);
1748 } else if (reader.matches("%!")) {
1749 // Declaration
1750 reader.skipUntil("%>");
1751 } else if (reader.matches("%=")) {
1752 // Expression
1753 reader.skipUntil("%>");
1754 } else if (reader.matches("%")) {
1755 // Scriptlet
1756 reader.skipUntil("%>");
1757 }
1758 reader.skipUntil("<");
1759 }
1760 }
1761 }