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