org.apache.jasper.compiler.Parser Maven / Gradle / Ivy
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2004 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.jasper.compiler;
import java.io.CharArrayWriter;
import java.io.FileNotFoundException;
import java.net.URL;
// START GlassFish 750
import java.util.concurrent.ConcurrentHashMap;
// START GlassFish 750
import java.util.Iterator;
import java.util.List;
import javax.servlet.jsp.tagext.TagAttributeInfo;
import javax.servlet.jsp.tagext.TagFileInfo;
import javax.servlet.jsp.tagext.TagInfo;
import javax.servlet.jsp.tagext.TagLibraryInfo;
import org.apache.jasper.Constants;
import org.apache.jasper.JasperException;
import org.apache.jasper.JspCompilationContext;
import org.xml.sax.Attributes;
import org.xml.sax.helpers.AttributesImpl;
/**
* This class implements a parser for a JSP page (non-xml view).
* JSP page grammar is included here for reference. The token '#'
* that appears in the production indicates the current input token
* location in the production.
*
* @author Kin-man Chung
* @author Shawn Bayern
* @author Mark Roth
*/
class Parser implements TagConstants {
private ParserController parserController;
private JspCompilationContext ctxt;
private JspReader reader;
private Mark start;
private ErrorDispatcher err;
private int scriptlessCount;
private boolean isTagFile;
private boolean directivesOnly;
private URL jarFileUrl;
private PageInfo pageInfo;
private boolean errorOnUndeclaredNamespace;
// Virtual body content types, to make parsing a little easier.
// These are not accessible from outside the parser.
private static final String JAVAX_BODY_CONTENT_PARAM =
"JAVAX_BODY_CONTENT_PARAM";
private static final String JAVAX_BODY_CONTENT_PLUGIN =
"JAVAX_BODY_CONTENT_PLUGIN";
private static final String JAVAX_BODY_CONTENT_TEMPLATE_TEXT =
"JAVAX_BODY_CONTENT_TEMPLATE_TEXT";
/**
* The constructor
*/
private Parser(ParserController pc, JspReader reader, boolean isTagFile,
boolean directivesOnly, URL jarFileUrl, boolean hasBom) {
this.parserController = pc;
this.ctxt = pc.getJspCompilationContext();
this.pageInfo = pc.getCompiler().getPageInfo();
this.err = pc.getCompiler().getErrorDispatcher();
this.reader = reader;
this.scriptlessCount = 0;
this.isTagFile = isTagFile;
this.directivesOnly = directivesOnly;
this.jarFileUrl = jarFileUrl;
start = reader.mark();
}
/**
* The main entry for Parser
*
* @param pc The ParseController, use for getting other objects in compiler
* and for parsing included pages
* @param reader To read the page
* @param parent The parent node to this page, null for top level page
* @return list of nodes representing the parsed page
*/
public static Node.Nodes parse(ParserController pc,
String path,
JspReader reader,
Node parent,
boolean isTagFile,
boolean directivesOnly,
URL jarFileUrl,
String pageEnc,
String jspConfigPageEnc,
boolean isDefaultPageEncoding,
boolean hasBom)
throws JasperException {
Parser parser = new Parser(pc, reader, isTagFile, directivesOnly,
jarFileUrl, hasBom);
Node.Root root = new Node.Root(reader.mark(), parent, false);
root.setPageEncoding(pageEnc);
root.setJspConfigPageEncoding(jspConfigPageEnc);
root.setIsDefaultPageEncoding(isDefaultPageEncoding);
root.setHasBom(hasBom);
if (hasBom) {
// Consume (remove) BOM, so it won't appear in page output
char bomChar = (char) reader.nextChar();
if (bomChar != 0xFEFF) {
parser.err.jspError(
reader.mark(),
"jsp.error.invalidBom",
Integer.toHexString(bomChar).toUpperCase());
}
}
if (directivesOnly) {
parser.parseTagFileDirectives(root);
return new Node.Nodes(root);
}
// For the Top level page, add inlcude-prelude and include-coda
PageInfo pageInfo = pc.getCompiler().getPageInfo();
if (parent == null) {
parser.addInclude(root, pageInfo.getIncludePrelude());
}
while (reader.hasMoreInput()) {
parser.parseElements(root);
}
if (parent == null) {
parser.addInclude(root, pageInfo.getIncludeCoda());
parser.pageInfo.setRootPath(path);
}
Node.Nodes page = new Node.Nodes(root);
return page;
}
/**
* Attributes ::= (S Attribute)* S?
*/
Attributes parseAttributes() throws JasperException {
AttributesImpl attrs = new AttributesImpl();
reader.skipSpaces();
while (parseAttribute(attrs))
reader.skipSpaces();
return attrs;
}
/**
* Parse Attributes for a reader, provided for external use
*/
public static Attributes parseAttributes(ParserController pc,
JspReader reader)
throws JasperException {
Parser tmpParser = new Parser(pc, reader, false, false, null, false);
return tmpParser.parseAttributes();
}
/**
* Attribute ::= Name S? Eq S?
* ( '"<%=' RTAttributeValueDouble
* | '"' AttributeValueDouble
* | "'<%=" RTAttributeValueSingle
* | "'" AttributeValueSingle
* }
* Note: JSP and XML spec does not allow while spaces around Eq. It is
* added to be backward compatible with Tomcat, and with other xml parsers.
*/
private boolean parseAttribute(AttributesImpl attrs)
throws JasperException {
// Get the qualified name
String qName = parseName();
if (qName == null)
return false;
// Determine prefix and local name components
String localName = qName;
String uri = "";
int index = qName.indexOf(':');
if (index != -1) {
String prefix = qName.substring(0, index);
uri = pageInfo.getURI(prefix);
if (uri == null) {
err.jspError(reader.mark(),
"jsp.error.attribute.invalidPrefix", prefix);
}
localName = qName.substring(index+1);
}
reader.skipSpaces();
if (!reader.matches("="))
err.jspError(reader.mark(), "jsp.error.attribute.noequal");
reader.skipSpaces();
char quote = (char) reader.nextChar();
if (quote != '\'' && quote != '"')
err.jspError(reader.mark(), "jsp.error.attribute.noquote");
String watchString = "";
if (reader.matches("<%="))
watchString = "%>";
watchString = watchString + quote;
String attrValue = parseAttributeValue(watchString);
attrs.addAttribute(uri, localName, qName, "CDATA", attrValue);
return true;
}
/**
* Name ::= (Letter | '_' | ':') (Letter | Digit | '.' | '_' | '-' | ':')*
*/
private String parseName() throws JasperException {
char ch = (char)reader.peekChar();
if (Character.isLetter(ch) || ch == '_' || ch == ':') {
StringBuilder buf = new StringBuilder();
buf.append(ch);
reader.nextChar();
ch = (char)reader.peekChar();
while (Character.isLetter(ch) || Character.isDigit(ch) ||
ch == '.' || ch == '_' || ch == '-' || ch == ':') {
buf.append(ch);
reader.nextChar();
ch = (char) reader.peekChar();
}
return buf.toString();
}
return null;
}
/**
* AttributeValueDouble ::= (QuotedChar - '"')*
* ('"' | )
* RTAttributeValueDouble ::= ((QuotedChar - '"')* - ((QuotedChar-'"')'%>"')
* ('%>"' | TRANSLATION_ERROR)
*/
private String parseAttributeValue(String watch) throws JasperException {
Mark start = reader.mark();
Mark stop = reader.skipUntilIgnoreEsc(watch);
if (stop == null) {
err.jspError(start, "jsp.error.attribute.unterminated", watch);
}
String ret = parseQuoted(reader.getText(start, stop));
if (watch.length() == 1) // quote
return ret;
// putback delimiter '<%=' and '%>', since they are needed if the
// attribute does not allow RTexpression.
return "<%=" + ret + "%>";
}
/**
* QuotedChar ::= '''
* | '"'
* | '\\'
* | '\"'
* | "\'"
* | '\>'
* | '\$'
* | '\#'
* | Char
*/
// Need to preserve "\$", and "\#" here since they
// may be send to EL processor.
private String parseQuoted(String tx) {
StringBuilder buf = new StringBuilder();
int size = tx.length();
int i = 0;
while (i < size) {
char ch = tx.charAt(i);
if (ch == '&') {
if (i+5 < size && tx.charAt(i+1) == 'a'
&& tx.charAt(i+2) == 'p' && tx.charAt(i+3) == 'o'
&& tx.charAt(i+4) == 's' && tx.charAt(i+5) == ';') {
buf.append('\'');
i += 6;
} else if (i+5 < size && tx.charAt(i+1) == 'q'
&& tx.charAt(i+2) == 'u' && tx.charAt(i+3) == 'o'
&& tx.charAt(i+4) == 't' && tx.charAt(i+5) == ';') {
buf.append('"');
i += 6;
} else {
buf.append(ch);
++i;
}
} else if (ch == '\\' && i+1 < size) {
ch = tx.charAt(i+1);
if (ch == '\\' || ch == '\"' || ch == '\'' || ch == '>') {
buf.append(ch);
i += 2;
} else {
buf.append('\\');
++i;
}
} else {
buf.append(ch);
++i;
}
}
return buf.toString();
}
private String parseScriptText(String tx) {
CharArrayWriter cw = new CharArrayWriter();
int size = tx.length();
int i = 0;
while (i < size) {
char ch = tx.charAt(i);
if (i+2 < size && ch == '%' && tx.charAt(i+1) == '\\'
&& tx.charAt(i+2) == '>') {
cw.write('%');
cw.write('>');
i += 3;
} else {
cw.write(ch);
++i;
}
}
cw.close();
return cw.toString();
}
/*
* Invokes parserController to parse the included page
*/
private void processIncludeDirective(String file, Node parent)
throws JasperException {
if (file == null) {
return;
}
try {
parserController.parse(file, parent, jarFileUrl);
} catch (FileNotFoundException ex) {
err.jspError(start, "jsp.error.file.not.found", file);
} catch (Exception ex) {
err.jspError(start, ex);
}
}
/*
* Parses a page directive with the following syntax:
* PageDirective ::= ( S Attribute)*
*/
private void parsePageDirective(Node parent) throws JasperException {
Attributes attrs = parseAttributes();
Node.PageDirective n = new Node.PageDirective(attrs, start, parent);
/*
* A page directive may contain multiple 'import' attributes, each of
* which consists of a comma-separated list of package names.
* Store each list with the node, where it is parsed.
*/
for (int i = 0; i < attrs.getLength(); i++) {
if ("import".equals(attrs.getQName(i))) {
n.addImport(attrs.getValue(i));
}
}
}
/*
* Parses an include directive with the following syntax:
* IncludeDirective ::= ( S Attribute)*
*/
private void parseIncludeDirective(Node parent) throws JasperException {
Attributes attrs = parseAttributes();
// Included file expanded here
Node includeNode = new Node.IncludeDirective(attrs, start, parent);
processIncludeDirective(attrs.getValue("file"), includeNode);
}
/**
* Add a list of files. This is used for implementing include-prelude
* and include-coda of jsp-config element in web.xml
*/
private void addInclude(Node parent, List files) throws JasperException {
if( files != null ) {
Iterator iter = files.iterator();
while (iter.hasNext()) {
String file = (String) iter.next();
AttributesImpl attrs = new AttributesImpl();
attrs.addAttribute("", "file", "file", "CDATA", file);
// Create a dummy Include directive node
Node includeNode = new Node.IncludeDirective(attrs,
reader.mark(), parent);
processIncludeDirective(file, includeNode);
}
}
}
/*
* Parses a taglib directive with the following syntax:
* Directive ::= ( S Attribute)*
*/
private void parseTaglibDirective(Node parent) throws JasperException {
Attributes attrs = parseAttributes();
String uri = attrs.getValue("uri");
String prefix = attrs.getValue("prefix");
if (prefix != null) {
Mark prevMark = pageInfo.getNonCustomTagPrefix(prefix);
if (prevMark != null) {
err.jspError(reader.mark(), "jsp.error.prefix.use_before_dcl",
prefix, prevMark.getFile(), "" + prevMark.getLineNumber());
}
if (uri != null) {
String uriPrev = pageInfo.getURI(prefix);
if (uriPrev != null && !uriPrev.equals(uri)) {
err.jspError(reader.mark(), "jsp.error.prefix.refined",
prefix, uri, uriPrev);
}
/* GlassFish 750
if (pageInfo.getTaglib(uri) == null) {
String[] location = ctxt.getTldLocation(uri);
TagLibraryInfoImpl taglib = null;
try {
taglib = new TagLibraryInfoImpl(ctxt,
parserController,
prefix,
uri,
location,
err);
} catch (JasperException je) {
err.throwException(reader.mark(), je);
}
pageInfo.addTaglib(uri, taglib);
}
*/
// START GlassFish 750
ConcurrentHashMap taglibs =
ctxt.getTaglibs();
TagLibraryInfoImpl taglib = taglibs.get(uri);
if (taglib == null) {
synchronized (taglibs) {
taglib = taglibs.get(uri);
if (taglib == null) {
String[] location = ctxt.getTldLocation(uri);
try {
taglib = new TagLibraryInfoImpl(
ctxt,
parserController,
prefix,
uri,
location,
err);
} catch (JasperException je) {
err.throwException(reader.mark(), je);
}
ctxt.addTaglib(uri, taglib);
pageInfo.addTaglib(uri, taglib);
}
}
}
if (pageInfo.getTaglib(uri) == null) {
pageInfo.addTaglib(uri,
new TagLibraryInfoImpl(prefix,
uri,
taglib,
pageInfo));
}
// END GlassFish 750
pageInfo.addPrefixMapping(prefix, uri);
} else {
String tagdir = attrs.getValue("tagdir");
if (tagdir != null) {
String urnTagdir = URN_JSPTAGDIR + tagdir;
if (pageInfo.getTaglib(urnTagdir) == null) {
pageInfo.addTaglib(urnTagdir,
new ImplicitTagLibraryInfo(
ctxt,
parserController,
prefix,
tagdir,
err));
}
pageInfo.addPrefixMapping(prefix, urnTagdir);
}
}
}
new Node.TaglibDirective(attrs, start, parent);
}
/*
* Parses a directive with the following syntax:
* Directive ::= S? ( 'page' PageDirective
* | 'include' IncludeDirective
* | 'taglib' TagLibDirective)
* S? '%>'
*
* TagDirective ::= S? ('tag' PageDirective
* | 'include' IncludeDirective
* | 'taglib' TagLibDirective)
* | 'attribute AttributeDirective
* | 'variable VariableDirective
* S? '%>'
*/
private void parseDirective(Node parent) throws JasperException {
reader.skipSpaces();
String directive = null;
if (reader.matches("page")) {
directive = "<%@ page";
if (isTagFile) {
err.jspError(reader.mark(), "jsp.error.directive.istagfile",
directive);
}
parsePageDirective(parent);
} else if (reader.matches("include")) {
directive = "<%@ include";
parseIncludeDirective(parent);
} else if (reader.matches("taglib")) {
if (directivesOnly) {
// No need to get the tagLibInfo objects. This alos suppresses
// parsing of any tag files used in this tag file.
return;
}
directive = "<%@ taglib";
parseTaglibDirective(parent);
} else if (reader.matches("tag")) {
directive = "<%@ tag";
if (!isTagFile) {
err.jspError(reader.mark(), "jsp.error.directive.isnottagfile",
directive);
}
parseTagDirective(parent);
} else if (reader.matches("attribute")) {
directive = "<%@ attribute";
if (!isTagFile) {
err.jspError(reader.mark(), "jsp.error.directive.isnottagfile",
directive);
}
parseAttributeDirective(parent);
} else if (reader.matches("variable")) {
directive = "<%@ variable";
if (!isTagFile) {
err.jspError(reader.mark(), "jsp.error.directive.isnottagfile",
directive);
}
parseVariableDirective(parent);
} else {
err.jspError(reader.mark(), "jsp.error.invalid.directive",
reader.parseToken(false));
}
reader.skipSpaces();
if (!reader.matches("%>")) {
err.jspError(start, "jsp.error.unterminated", directive);
}
}
/*
* Parses a directive with the following syntax:
*
* XMLJSPDirectiveBody ::= S? ( ( 'page' PageDirectiveAttrList
* S? ( '/>' | ( '>' S? ETag ) )
* | ( 'include' IncludeDirectiveAttrList
* S? ( '/>' | ( '>' S? ETag ) )
* |
*
* XMLTagDefDirectiveBody ::= ( ( 'tag' TagDirectiveAttrList
* S? ( '/>' | ( '>' S? ETag ) )
* | ( 'include' IncludeDirectiveAttrList
* S? ( '/>' | ( '>' S? ETag ) )
* | ( 'attribute' AttributeDirectiveAttrList
* S? ( '/>' | ( '>' S? ETag ) )
* | ( 'variable' VariableDirectiveAttrList
* S? ( '/>' | ( '>' S? ETag ) )
* )
* |
*/
private void parseXMLDirective(Node parent) throws JasperException {
reader.skipSpaces();
String eTag = null;
if (reader.matches("page")) {
eTag = "jsp:directive.page";
if (isTagFile) {
err.jspError(reader.mark(), "jsp.error.directive.istagfile",
"<" + eTag);
}
parsePageDirective(parent);
} else if (reader.matches("include")) {
eTag = "jsp:directive.include";
parseIncludeDirective(parent);
} else if (reader.matches("tag")) {
eTag = "jsp:directive.tag";
if (!isTagFile) {
err.jspError(reader.mark(), "jsp.error.directive.isnottagfile",
"<" + eTag);
}
parseTagDirective(parent);
} else if (reader.matches("attribute")) {
eTag = "jsp:directive.attribute";
if (!isTagFile) {
err.jspError(reader.mark(), "jsp.error.directive.isnottagfile",
"<" + eTag);
}
parseAttributeDirective(parent);
} else if (reader.matches("variable")) {
eTag = "jsp:directive.variable";
if (!isTagFile) {
err.jspError(reader.mark(), "jsp.error.directive.isnottagfile",
"<" + eTag);
}
parseVariableDirective(parent);
} else {
err.jspError(reader.mark(), "jsp.error.invalid.directive",
reader.parseToken(false));
}
reader.skipSpaces();
if( reader.matches( ">" ) ) {
reader.skipSpaces();
if( !reader.matchesETag( eTag ) ) {
err.jspError(start, "jsp.error.unterminated", "<" + eTag );
}
}
else if( !reader.matches( "/>" ) ) {
err.jspError(start, "jsp.error.unterminated", "<" + eTag );
}
}
/*
* Parses a tag directive with the following syntax:
* PageDirective ::= ( S Attribute)*
*/
private void parseTagDirective(Node parent) throws JasperException {
Attributes attrs = parseAttributes();
Node.TagDirective n = new Node.TagDirective(attrs, start, parent);
/*
* A page directive may contain multiple 'import' attributes, each of
* which consists of a comma-separated list of package names.
* Store each list with the node, where it is parsed.
*/
for (int i = 0; i < attrs.getLength(); i++) {
if ("import".equals(attrs.getQName(i))) {
n.addImport(attrs.getValue(i));
}
}
}
/*
* Parses a attribute directive with the following syntax:
* AttributeDirective ::= ( S Attribute)*
*/
private void parseAttributeDirective(Node parent) throws JasperException {
Attributes attrs = parseAttributes();
new Node.AttributeDirective(attrs, start, parent);
}
/*
* Parses a variable directive with the following syntax:
* PageDirective ::= ( S Attribute)*
*/
private void parseVariableDirective(Node parent) throws JasperException {
Attributes attrs = parseAttributes();
new Node.VariableDirective(attrs, start, parent);
}
/*
* JSPCommentBody ::= (Char* - (Char* '--%>')) '--%>'
*/
private void parseComment(Node parent) throws JasperException {
start = reader.mark();
Mark stop = reader.skipUntil("--%>");
if (stop == null) {
err.jspError(start, "jsp.error.unterminated", "<%--");
}
new Node.Comment(reader.getText(start, stop), start, parent);
}
/*
* DeclarationBody ::= (Char* - (char* '%>')) '%>'
*/
private void parseDeclaration(Node parent) throws JasperException {
start = reader.mark();
Mark stop = reader.skipUntil("%>");
if (stop == null) {
err.jspError(start, "jsp.error.unterminated", "<%!");
}
new Node.Declaration(parseScriptText(reader.getText(start, stop)),
start, parent);
}
/*
* XMLDeclarationBody ::= ( S? '/>' )
* | ( S? '>' (Char* - (char* '<')) CDSect?)* ETag
* |
* CDSect ::= CDStart CData CDEnd
* CDStart ::= '' Char*))
* CDEnd ::= ']]>'
*/
private void parseXMLDeclaration(Node parent) throws JasperException {
reader.skipSpaces();
if( !reader.matches( "/>" ) ) {
if( !reader.matches( ">" ) ) {
err.jspError(start, "jsp.error.unterminated",
"<jsp:declaration>");
}
Mark stop;
String text;
while (true) {
start = reader.mark();
stop = reader.skipUntil("<");
if (stop == null) {
err.jspError(start, "jsp.error.unterminated",
"<jsp:declaration>");
}
text = parseScriptText(reader.getText(start, stop));
new Node.Declaration(text, start, parent);
if (reader.matches("![CDATA[")) {
start = reader.mark();
stop = reader.skipUntil("]]>");
if (stop == null) {
err.jspError(start, "jsp.error.unterminated", "CDATA");
}
text = parseScriptText(reader.getText(start, stop));
new Node.Declaration(text, start, parent);
}
else {
break;
}
}
if (!reader.matchesETagWithoutLessThan( "jsp:declaration" ) ) {
err.jspError(start, "jsp.error.unterminated",
"<jsp:declaration>");
}
}
}
/*
* ExpressionBody ::= (Char* - (char* '%>')) '%>'
*/
private void parseExpression(Node parent) throws JasperException {
start = reader.mark();
Mark stop = reader.skipUntil("%>");
if (stop == null) {
err.jspError(start, "jsp.error.unterminated", "<%=");
}
new Node.Expression(parseScriptText(reader.getText(start, stop)),
start, parent);
}
/*
* XMLExpressionBody ::= ( S? '/>' )
* | ( S? '>' (Char* - (char* '<')) CDSect?)* ETag )
* |
*/
private void parseXMLExpression(Node parent) throws JasperException {
reader.skipSpaces();
if( !reader.matches( "/>" ) ) {
if( !reader.matches( ">" ) ) {
err.jspError(start, "jsp.error.unterminated",
"<jsp:expression>");
}
Mark stop;
String text;
while (true) {
start = reader.mark();
stop = reader.skipUntil("<");
if (stop == null) {
err.jspError(start, "jsp.error.unterminated",
"<jsp:expression>");
}
text = parseScriptText(reader.getText(start, stop));
new Node.Expression(text, start, parent);
if (reader.matches("![CDATA[")) {
start = reader.mark();
stop = reader.skipUntil("]]>");
if (stop == null) {
err.jspError(start, "jsp.error.unterminated", "CDATA");
}
text = parseScriptText(reader.getText(start, stop));
new Node.Expression(text, start, parent);
}
else {
break;
}
}
if (!reader.matchesETagWithoutLessThan( "jsp:expression" )) {
err.jspError(start, "jsp.error.unterminated",
"<jsp:expression>");
}
}
}
/*
* ELExpressionBody
* (following "${" or "#{"to first unquoted "}")
* // XXX add formal production and confirm implementation against it,
* // once it's decided
*/
private void parseELExpression(Node parent, String typeEL)
throws JasperException {
start = reader.mark();
boolean singleQuoted = false, doubleQuoted = false;
int curl = 0;
int currentChar;
do {
// XXX could move this logic to JspReader
currentChar = reader.nextChar();
if (currentChar == '\\' && (singleQuoted || doubleQuoted)) {
// skip character following '\' within quotes
reader.nextChar();
currentChar = reader.nextChar();
}
if (currentChar == -1)
err.jspError(start, "jsp.error.unterminated", typeEL);
if (currentChar == '"')
doubleQuoted = !doubleQuoted;
else if (currentChar == '\'')
singleQuoted = !singleQuoted;
else if (currentChar == '{')
curl++;
else if (currentChar == '}')
curl--;
} while (currentChar != '}' || curl >= 0 || singleQuoted || doubleQuoted);
String text = typeEL + reader.getText(start, reader.mark());
new Node.ELExpression(text, start, parent);
}
/*
* ScriptletBody ::= (Char* - (char* '%>')) '%>'
*/
private void parseScriptlet(Node parent) throws JasperException {
start = reader.mark();
Mark stop = reader.skipUntil("%>");
if (stop == null) {
err.jspError(start, "jsp.error.unterminated", "<%");
}
new Node.Scriptlet(parseScriptText(reader.getText(start, stop)),
start, parent);
}
/*
* XMLScriptletBody ::= ( S? '/>' )
* | ( S? '>' (Char* - (char* '<')) CDSect?)* ETag )
* |
*/
private void parseXMLScriptlet(Node parent) throws JasperException {
reader.skipSpaces();
if( !reader.matches( "/>" ) ) {
if( !reader.matches( ">" ) ) {
err.jspError(start, "jsp.error.unterminated",
"<jsp:scriptlet>");
}
Mark stop;
String text;
while (true) {
start = reader.mark();
stop = reader.skipUntil("<");
if (stop == null) {
err.jspError(start, "jsp.error.unterminated",
"<jsp:scriptlet>");
}
text = parseScriptText(reader.getText(start, stop));
new Node.Scriptlet(text, start, parent);
if (reader.matches("![CDATA[")) {
start = reader.mark();
stop = reader.skipUntil("]]>");
if (stop == null) {
err.jspError(start, "jsp.error.unterminated", "CDATA");
}
text = parseScriptText(reader.getText(start, stop));
new Node.Scriptlet(text, start, parent);
}
else {
break;
}
}
if (!reader.matchesETagWithoutLessThan( "jsp:scriptlet" )) {
err.jspError(start, "jsp.error.unterminated",
"<jsp:scriptlet>");
}
}
}
/**
* Param ::= '' S? ( ' )
* S? ETag
* )
* | ( '>' S? Param* ETag )
*
* EmptyBody ::= '/>'
* | ( '>' ETag )
* | ( '>' S? '' Param* ''
*/
private void parseInclude(Node parent) throws JasperException {
Attributes attrs = parseAttributes();
reader.skipSpaces();
Node includeNode = new Node.IncludeAction( attrs, start, parent );
parseOptionalBody(includeNode, "jsp:include",
JAVAX_BODY_CONTENT_PARAM);
}
/*
* For Forward:
* StdActionContent ::= Attributes ParamBody
*/
private void parseForward(Node parent) throws JasperException {
Attributes attrs = parseAttributes();
reader.skipSpaces();
Node forwardNode = new Node.ForwardAction( attrs, start, parent );
parseOptionalBody(forwardNode, "jsp:forward",
JAVAX_BODY_CONTENT_PARAM);
}
private void parseInvoke(Node parent) throws JasperException {
Attributes attrs = parseAttributes();
reader.skipSpaces();
Node invokeNode = new Node.InvokeAction(attrs, start, parent);
parseEmptyBody(invokeNode, "jsp:invoke");
}
private void parseDoBody(Node parent) throws JasperException {
Attributes attrs = parseAttributes();
reader.skipSpaces();
Node doBodyNode = new Node.DoBodyAction(attrs, start, parent);
parseEmptyBody(doBodyNode, "jsp:doBody");
}
private void parseElement(Node parent) throws JasperException {
Attributes attrs = parseAttributes();
reader.skipSpaces();
Node elementNode = new Node.JspElement(attrs, start, parent);
parseOptionalBody( elementNode, "jsp:element",
TagInfo.BODY_CONTENT_JSP );
}
/*
* For GetProperty:
* StdActionContent ::= Attributes EmptyBody
*/
private void parseGetProperty(Node parent) throws JasperException {
Attributes attrs = parseAttributes();
reader.skipSpaces();
Node getPropertyNode = new Node.GetProperty( attrs, start, parent );
parseOptionalBody(getPropertyNode, "jsp:getProperty",
TagInfo.BODY_CONTENT_EMPTY);
}
/*
* For SetProperty:
* StdActionContent ::= Attributes EmptyBody
*/
private void parseSetProperty(Node parent) throws JasperException {
Attributes attrs = parseAttributes();
reader.skipSpaces();
Node setPropertyNode = new Node.SetProperty( attrs, start, parent );
parseOptionalBody(setPropertyNode, "jsp:setProperty",
TagInfo.BODY_CONTENT_EMPTY);
}
/*
* EmptyBody ::= '/>'
* | ( '>' ETag )
* | ( '>' S? ' ") ) {
// Done
}
else if( reader.matches( ">" ) ) {
if( reader.matchesETag( tag ) ) {
// Done
}
else if( reader.matchesOptionalSpacesFollowedBy(
"' ETag )
* | ( '>' S? '' Body ETag )
*
* ScriptlessActionBody ::= JspAttributeAndBody
* | ( '>' ScriptlessBody ETag )
*
* TagDependentActionBody ::= JspAttributeAndBody
* | ( '>' TagDependentBody ETag )
*
*/
private void parseOptionalBody( Node parent, String tag, String bodyType )
throws JasperException
{
if (reader.matches("/>")) {
// EmptyBody
return;
}
if (!reader.matches(">")) {
err.jspError(reader.mark(), "jsp.error.unterminated",
"<" + tag );
}
if( reader.matchesETag( tag ) ) {
// EmptyBody
return;
}
if( !parseJspAttributeAndBody( parent, tag, bodyType ) ) {
// Must be ( '>' # Body ETag )
parseBody(parent, tag, bodyType );
}
}
/**
* Attempts to parse 'JspAttributeAndBody' production. Returns true if
* it matched, or false if not. Assumes EmptyBody is okay as well.
*
* JspAttributeAndBody ::=
* ( '>' # S? ( ' )
* S? ETag
* )
*/
private boolean parseJspAttributeAndBody( Node parent, String tag,
String bodyType )
throws JasperException
{
boolean result = false;
if( reader.matchesOptionalSpacesFollowedBy( " elements:
parseNamedAttributes( parent );
result = true;
}
if( reader.matchesOptionalSpacesFollowedBy( " but something other than
// or the end tag, translation error.
err.jspError(reader.mark(), "jsp.error.jspbody.required",
"<" + tag );
}
return result;
}
/*
* Params ::= `>' S?
* ( ( `'
* ( ( S? Param+ S? ` ' )
* |
* )
* )
* | Param+
* )
* ''
*/
private void parseJspParams(Node parent) throws JasperException {
Node jspParamsNode = new Node.ParamsAction(start, parent);
parseOptionalBody(jspParamsNode, "jsp:params",
JAVAX_BODY_CONTENT_PARAM );
}
/*
* Fallback ::= '/>'
* | ( `>' S? `'
* ( ( S?
* ( Char* - ( Char* ` ' ) )
* ` ' S?
* )
* |
* )
* `'
* )
* | ( '>'
* ( Char* - ( Char* '' ) )
* ''
* )
*/
private void parseFallBack(Node parent) throws JasperException {
Node fallBackNode = new Node.FallBackAction(start, parent);
parseOptionalBody(fallBackNode, "jsp:fallback",
JAVAX_BODY_CONTENT_TEMPLATE_TEXT);
}
/*
* For Plugin:
* StdActionContent ::= Attributes PluginBody
*
* PluginBody ::= EmptyBody
* | ( '>' S? ( ' )
* S? ETag
* )
* | ( '>' S? PluginTags ETag )
*
* EmptyBody ::= '/>'
* | ( '>' ETag )
* | ( '>' S? '
*
* Attributes ::= ( S Attribute )* S?
*
* CustomActionEnd ::= CustomActionTagDependent
* | CustomActionJSPContent
* | CustomActionScriptlessContent
*
* CustomActionTagDependent ::= TagDependentOptionalBody
*
* CustomActionJSPContent ::= OptionalBody
*
* CustomActionScriptlessContent ::= ScriptlessOptionalBody
*/
private boolean parseCustomTag(Node parent) throws JasperException {
if (reader.peekChar() != '<') {
return false;
}
// Parse 'CustomAction' production (tag prefix and custom action name)
reader.nextChar(); // skip '<'
String tagName = reader.parseToken(false);
int i = tagName.indexOf(':');
if (i == -1) {
reader.reset(start);
return false;
}
String prefix = tagName.substring(0, i);
String shortTagName = tagName.substring(i+1);
// Check if this is a user-defined tag.
String uri = pageInfo.getURI(prefix);
if (uri == null) {
// If error-on-undeclared-namespace is set to true in
// jsp-property-group, then it is an error
if (pageInfo.errorOnUndeclaredNamespace()) {
err.jspError(start, "jsp.error.undeclared.namespace", prefix);
}
reader.reset(start);
// Remember the prefix for later error checking
pageInfo.putNonCustomTagPrefix(prefix, reader.mark());
return false;
}
TagLibraryInfo tagLibInfo = pageInfo.getTaglib(uri);
TagInfo tagInfo = tagLibInfo.getTag(shortTagName);
TagFileInfo tagFileInfo = tagLibInfo.getTagFile(shortTagName);
if (tagInfo == null && tagFileInfo == null) {
err.jspError(start, "jsp.error.bad_tag", shortTagName, prefix);
}
Class tagHandlerClass = null;
if (tagInfo != null) {
// Must be a classic tag, load it here.
// tag files will be loaded later, in TagFileProcessor
String handlerClassName = tagInfo.getTagClassName();
try {
tagHandlerClass = ctxt.getClassLoader().loadClass(handlerClassName);
} catch (Exception e) {
err.jspError(start, "jsp.error.loadclass.taghandler",
handlerClassName, tagName);
}
}
// Parse 'CustomActionBody' production:
// At this point we are committed - if anything fails, we produce
// a translation error.
// Parse 'Attributes' production:
Attributes attrs = parseAttributes();
reader.skipSpaces();
// Parse 'CustomActionEnd' production:
if (reader.matches("/>")) {
if (tagInfo != null) {
new Node.CustomTag(tagLibInfo.getRequiredVersion(),
tagName, prefix, shortTagName, uri, attrs,
start, parent, tagInfo, tagHandlerClass);
} else {
new Node.CustomTag(tagLibInfo.getRequiredVersion(),
tagName, prefix, shortTagName, uri, attrs,
start, parent, tagFileInfo);
}
return true;
}
// Now we parse one of 'CustomActionTagDependent',
// 'CustomActionJSPContent', or 'CustomActionScriptlessContent'.
// depending on body-content in TLD.
// Looking for a body, it still can be empty; but if there is a
// a tag body, its syntax would be dependent on the type of
// body content declared in the TLD.
String bc;
if (tagInfo != null) {
bc = tagInfo.getBodyContent();
} else {
bc = tagFileInfo.getTagInfo().getBodyContent();
}
Node tagNode = null;
if (tagInfo != null) {
tagNode = new Node.CustomTag(tagLibInfo.getRequiredVersion(),
tagName, prefix, shortTagName, uri,
attrs, start, parent, tagInfo,
tagHandlerClass);
} else {
tagNode = new Node.CustomTag(tagLibInfo.getRequiredVersion(),
tagName, prefix, shortTagName, uri,
attrs, start, parent, tagFileInfo);
}
parseOptionalBody( tagNode, tagName, bc );
return true;
}
/*
* Parse for a template text string until '<' or "${" is encountered,
* recognizing escape sequences "\%" ,"\$", and \#.
*/
private void parseTemplateText(Node parent) throws JasperException {
if (!reader.hasMoreInput())
return;
CharArrayWriter ttext = new CharArrayWriter();
// Output the first character
int ch = reader.nextChar();
if (ch == '\\') {
reader.pushChar();
} else {
ttext.write(ch);
}
while (reader.hasMoreInput()) {
ch = reader.nextChar();
if (ch == '<') {
reader.pushChar();
break;
}
else if( ch == '$' || ch == '#') {
if (!reader.hasMoreInput()) {
ttext.write(ch);
break;
}
if (reader.nextChar() == '{') {
reader.pushChar();
reader.pushChar();
break;
}
ttext.write(ch);
reader.pushChar();
continue;
}
else if (ch == '\\') {
if (!reader.hasMoreInput()) {
ttext.write('\\');
break;
}
char next = (char)reader.peekChar();
// Looking for \% or \$
// Note that this behavior can be altered by the attributes
// el-ignored and deferred-syntax-allowed-as-literal and
// similar attributes in a page directive. However, since
// the page direcitve may appear later in the same page, the
// '\' will be regenerated in Generator.java.
if (next == '%' || next == '$' || next == '#') {
ch = reader.nextChar();
}
}
ttext.write(ch);
}
new Node.TemplateText(ttext.toString(), start, parent);
}
/*
* XMLTemplateText ::= ( S? '/>' )
* | ( S? '>'
* ( ( Char* - ( Char* ( '<' | '${' ) ) )
* ( '${' ELExpressionBody )?
* CDSect?
* )* ETag
* )
* |
*/
private void parseXMLTemplateText(Node parent) throws JasperException {
reader.skipSpaces();
if( !reader.matches( "/>" ) ) {
if( !reader.matches( ">" ) ) {
err.jspError(start, "jsp.error.unterminated",
"<jsp:text>" );
}
CharArrayWriter ttext = new CharArrayWriter();
while (reader.hasMoreInput()) {
int ch = reader.nextChar();
if( ch == '<' ) {
// Check for ");
if (stop == null) {
err.jspError(start, "jsp.error.unterminated", "CDATA");
}
String text = reader.getText(start, stop);
ttext.write(text, 0, text.length());
}
else if( ch == '\\') {
if (!reader.hasMoreInput()) {
ttext.write('\\');
break;
}
ch = reader.nextChar();
if (ch != '$' && ch != '#') {
ttext.write('\\');
}
ttext.write(ch);
}
else if( ch == '$' || ch == '#') {
if (!reader.hasMoreInput()) {
ttext.write(ch);
break;
}
if (reader.nextChar() != '{') {
ttext.write(ch);
reader.pushChar();
continue;
}
// Create a template text node
new Node.TemplateText( ttext.toString(), start, parent);
// Mark and parse the EL expression and create its node:
start = reader.mark();
parseELExpression(parent, (ch == '$')? "${": "#{");
start = reader.mark();
ttext = new CharArrayWriter();
}
else {
ttext.write( ch );
}
}
new Node.TemplateText( ttext.toString(), start, parent );
if (! reader.hasMoreInput()) {
err.jspError( start, "jsp.error.unterminated",
"<jsp:text>" );
} else if( !reader.matchesETagWithoutLessThan( "jsp:text" ) ) {
err.jspError( start, "jsp.error.jsptext.badcontent");
}
}
}
/*
* AllBody ::= ( '<%--' JSPCommentBody )
* | ( '<%@' DirectiveBody )
* | ( ' 0 ) {
// vc: ScriptlessBody
// We must follow the ScriptlessBody production if one of
// our parents is ScriptlessBody.
parseElementsScriptless( parent );
return;
}
start = reader.mark();
if (reader.matches("<%--")) {
parseComment(parent);
} else if (reader.matches("<%@")) {
parseDirective(parent);
} else if (reader.matches(" )
* | ( ' )
* | ( '<%=' )
* | ( ' )
* | ( '<%' )
* | ( ' )
* | ( ' )
* | ( ' )
* | ( '<%=' )
* | ( ' )
* | ( '<%' )
* | ( ' )
* | ( ' )
* | ( '${' )
* | ( ' )
* | TemplateText
*/
private void parseElementsTemplateText(Node parent)
throws JasperException
{
start = reader.mark();
if (reader.matches("<%--")) {
parseComment(parent);
} else if (reader.matches("<%@")) {
parseDirective(parent);
} else if (reader.matches(" ")) {
if (!reader.matches(">")) {
err.jspError(start, "jsp.error.unterminated",
"<jsp:body");
}
parseBody( bodyNode, "jsp:body", bodyType );
}
}
/*
* Parse the body as JSP content.
* @param tag The name of the tag whose end tag would terminate the body
* @param bodyType One of the TagInfo body types
*/
private void parseBody(Node parent, String tag, String bodyType)
throws JasperException
{
if( bodyType.equalsIgnoreCase( TagInfo.BODY_CONTENT_TAG_DEPENDENT ) ) {
parseTagDependentBody( parent, tag );
}
else if( bodyType.equalsIgnoreCase( TagInfo.BODY_CONTENT_EMPTY ) ) {
if( !reader.matchesETag( tag ) ) {
err.jspError(start, "jasper.error.emptybodycontent.nonempty",
tag);
}
}
else if( bodyType == JAVAX_BODY_CONTENT_PLUGIN ) {
// (note the == since we won't recognize JAVAX_*
// from outside this module).
parsePluginTags(parent);
if( !reader.matchesETag( tag ) ) {
err.jspError( reader.mark(), "jsp.error.unterminated",
"<" + tag );
}
}
else if( bodyType.equalsIgnoreCase( TagInfo.BODY_CONTENT_JSP ) ||
bodyType.equalsIgnoreCase( TagInfo.BODY_CONTENT_SCRIPTLESS ) ||
(bodyType == JAVAX_BODY_CONTENT_PARAM) ||
(bodyType == JAVAX_BODY_CONTENT_TEMPLATE_TEXT) )
{
while (reader.hasMoreInput()) {
if (reader.matchesETag(tag)) {
return;
}
// Check for nested jsp:body or jsp:attribute
if (tag.equals("jsp:body") || tag.equals("jsp:attribute")) {
if (reader.matches(" ")) {
if (!reader.matches(">")) {
err.jspError(start, "jsp.error.unterminated",
"<jsp:attribute");
}
if (namedAttributeNode.isTrim()) {
reader.skipSpaces();
}
parseBody(namedAttributeNode, "jsp:attribute",
getAttributeBodyType(parent,
attrs.getValue("name")));
if (namedAttributeNode.isTrim()) {
Node.Nodes subElems = namedAttributeNode.getBody();
if (subElems != null) {
Node lastNode = subElems.getNode(subElems.size() - 1);
if (lastNode instanceof Node.TemplateText) {
((Node.TemplateText)lastNode).rtrim();
}
}
}
}
reader.skipSpaces();
} while( reader.matches( " from the enclosing node
*/
private String getAttributeBodyType(Node n, String name) {
if (n instanceof Node.CustomTag) {
TagInfo tagInfo = ((Node.CustomTag)n).getTagInfo();
TagAttributeInfo[] tldAttrs = tagInfo.getAttributes();
for (int i=0; i
© 2015 - 2024 Weber Informatics LLC | Privacy Policy