/*
* Licensed to the Apache Software Foundation ( ASF ) under one or more
* contributor license agreements . See the NOTICE file distributed with
* this work for additional information regarding copyright ownership .
* The ASF licenses this file to You 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.util.Collection;
import jakarta.servlet.jsp.tagext.TagAttributeInfo;
import jakarta.servlet.jsp.tagext.TagFileInfo;
import jakarta.servlet.jsp.tagext.TagInfo;
import jakarta.servlet.jsp.tagext.TagLibraryInfo;
import org.apache.jasper.JasperException;
import org.apache.jasper.JspCompilationContext;
import org.apache.jasper.util.UniqueAttributesImpl;
import org.apache.tomcat.Jar;
import org.apache.tomcat.util.descriptor.tld.TldResourcePath;
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 final ParserController parserController;
private final JspCompilationContext ctxt;
private final JspReader reader;
private Mark start;
private final ErrorDispatcher err;
private int scriptlessCount;
private final boolean isTagFile;
private final boolean directivesOnly;
private final Jar jar;
private final PageInfo pageInfo;
// 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, Jar jar) {
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 .jar = jar;
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
* @ param isTagFile Is the page being parsed a tag file ?
* @ param directivesOnly Should only directives be parsed ?
* @ param jar JAR , if any , that this page was loaded from
* @ param pageEnc The encoding of the source
* @ param jspConfigPageEnc The encoding for the page
* @ param isDefaultPageEncoding Is the page encoding the default ?
* @ param isBomPresent Is a BOM present in the source
* @ return list of nodes representing the parsed page
*
* @ throws JasperException If an error occurs during parsing
*/
public static Node.Nodes parse(ParserController pc, JspReader reader,
Node parent, boolean isTagFile, boolean directivesOnly,
Jar jar, String pageEnc, String jspConfigPageEnc,
boolean isDefaultPageEncoding, boolean isBomPresent)
throws JasperException {
Parser parser = new Parser(pc, reader, isTagFile, directivesOnly, jar);
Node.Root root = new Node.Root(reader.mark(), parent, false ,
pc.getJspCompilationContext().getOptions().getTempVariableNamePrefix());
root.setPageEncoding(pageEnc);
root.setJspConfigPageEncoding(jspConfigPageEnc);
root.setIsDefaultPageEncoding(isDefaultPageEncoding);
root.setIsBomPresent(isBomPresent);
// For the Top level page, add include-prelude and include-coda
PageInfo pageInfo = pc.getCompiler().getPageInfo();
if (parent == null && !isTagFile) {
parser.addInclude(root, pageInfo.getIncludePrelude());
}
if (directivesOnly) {
parser.parseFileDirectives(root);
} else {
while (reader.hasMoreInput()) {
parser.parseElements(root);
}
}
if (parent == null && !isTagFile) {
parser.addInclude(root, pageInfo.getIncludeCoda());
}
Node.Nodes page = new Node.Nodes(root);
return page;
}
/**
* Attributes : : = ( S Attribute ) * S ?
*/
Attributes parseAttributes() throws JasperException {
return parseAttributes(false );
}
Attributes parseAttributes(boolean pageDirective) throws JasperException {
UniqueAttributesImpl attrs = new UniqueAttributesImpl(pageDirective);
reader.skipSpaces();
int ws = 1 ;
try {
while (parseAttribute(attrs)) {
if (ws == 0 && ctxt.getOptions().getStrictWhitespace()) {
err.jspError(reader.mark(),
"jsp.error.attribute.nowhitespace" );
}
ws = reader.skipSpaces();
}
} catch (IllegalArgumentException iae) {
// Duplicate attribute
err.jspError(reader.mark(), "jsp.error.attribute.duplicate" );
}
return attrs;
}
/**
* Parse Attributes for a reader , provided for external use
*
* @ param pc The parser
* @ param reader The source
*
* @ return The parsed attributes
*
* @ throws JasperException If an error occurs during parsing
*/
public static Attributes parseAttributes(ParserController pc,
JspReader reader) throws JasperException {
Parser tmpParser = new Parser(pc, reader, false , false , null );
return tmpParser.parseAttributes(true );
}
/**
* 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 ;
}
boolean ignoreEL = pageInfo.isELIgnored();
// 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 = "%>" ;
// Can't embed EL in a script expression
ignoreEL = true ;
}
watchString = watchString + quote;
String attrValue = parseAttributeValue(qName, watchString, ignoreEL);
attrs.addAttribute(uri, localName, qName, "CDATA" , attrValue);
return true ;
}
/**
* Name : : = ( Letter | ' _ ' | ' : ' ) ( Letter | Digit | ' . ' | ' _ ' | ' - ' | ' : ' ) *
*/
private String parseName() {
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 - ' " ' ) * ( ' " ' | & lt ; TRANSLATION_ERROR > )
* RTAttributeValueDouble : : = ( ( QuotedChar - ' " ' ) * - ( ( QuotedChar - ' " ' ) ' % > " ' )
* ( ' % > " ' | TRANSLATION_ERROR )
*/
private String parseAttributeValue(String qName, String watch, boolean ignoreEL) throws JasperException {
boolean quoteAttributeEL = ctxt.getOptions().getQuoteAttributeEL();
Mark start = reader.mark();
// In terms of finding the end of the value, quoting EL is equivalent to
// ignoring it.
Mark stop = reader.skipUntilIgnoreEsc(watch, ignoreEL || quoteAttributeEL);
if (stop == null ) {
err.jspError(start, "jsp.error.attribute.unterminated" , qName);
}
String ret = null ;
try {
char quote = watch.charAt(watch.length() - 1 );
// If watch is longer than 1 character this is a scripting
// expression and EL is always ignored
boolean isElIgnored =
pageInfo.isELIgnored() || watch.length() > 1 ;
ret = AttributeParser.getUnquoted(reader.getText(start, stop),
quote, isElIgnored,
pageInfo.isDeferredSyntaxAllowedAsLiteral(),
ctxt.getOptions().getStrictQuoteEscaping(),
quoteAttributeEL);
} catch (IllegalArgumentException iae) {
err.jspError(start, iae.getMessage());
}
if (watch.length() == 1 ) {
return ret;
}
// Put back delimiter '<%=' and '%>', since they are needed if the
// attribute does not allow RTexpression.
return "<%=" + ret + "%>" ;
}
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, jar);
} catch (FileNotFoundException ex) {
err.jspError(start, "jsp.error.file.not.found" , file);
} catch (Exception ex) {
err.jspError(start, ex.getMessage());
}
}
/*
* Parses a page directive with the following syntax : PageDirective : : = ( S
* Attribute ) *
*/
private void parsePageDirective(Node parent) throws JasperException {
Attributes attrs = parseAttributes(true );
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, Collection<String> files) throws JasperException {
if (files != null ) {
for (String file : files) {
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);
}
if (pageInfo.getTaglib(uri) == null ) {
TagLibraryInfoImpl impl = null ;
if (ctxt.getOptions().isCaching()) {
impl = (TagLibraryInfoImpl) ctxt.getOptions()
.getCache().get(uri);
}
if (impl == null ) {
TldResourcePath tldResourcePath = ctxt.getTldResourcePath(uri);
impl = new TagLibraryInfoImpl(ctxt, parserController,
pageInfo, prefix, uri, tldResourcePath, err);
if (ctxt.getOptions().isCaching()) {
ctxt.getOptions().getCache().put(uri, impl);
}
}
pageInfo.addTaglib(uri, impl);
}
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, pageInfo, prefix,
tagdir, err));
}
pageInfo.addPrefixMapping(prefix, urnTagdir);
}
}
}
@SuppressWarnings("unused" )
Node unused = 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 also 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.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 ) ) | < TRANSLATION_ERROR >
*
* XMLTagDefDirectiveBody : : = ( ( ' tag ' TagDirectiveAttrList S ? ( ' / > ' | (
* ' > ' S ? ETag ) ) | ( ' include ' IncludeDirectiveAttrList S ? ( ' / > ' | ( ' > '
* S ? ETag ) ) | ( ' attribute ' AttributeDirectiveAttrList S ? ( ' / > ' | ( ' > '
* S ? ETag ) ) | ( ' variable ' VariableDirectiveAttrList S ? ( ' / > ' | ( ' > ' S ?
* ETag ) ) ) | < TRANSLATION_ERROR >
*/
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.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(true );
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();
@SuppressWarnings("unused" )
Node unused = 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();
@SuppressWarnings("unused" )
Node unused = 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" , "<%--" );
}
@SuppressWarnings("unused" )
Node unused =
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" , "<%!" );
}
@SuppressWarnings("unused" )
Node unused = new Node.Declaration(
parseScriptText(reader.getText(start, stop)), start, parent);
}
/*
* XMLDeclarationBody : : = ( S ? ' / > ' ) | ( S ? ' > ' ( Char * - ( char * ' < ' ) )
* CDSect ? ) * ETag | < TRANSLATION_ERROR > CDSect : : = CDStart CData CDEnd
* CDStart : : = ' < ! [ CDATA [ ' CData : : = ( Char * - ( Char * ' ] ] > ' 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));
@SuppressWarnings("unused" )
Node unused = 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));
@SuppressWarnings("unused" )
Node unused2 = 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" , "<%=" );
}
@SuppressWarnings("unused" )
Node unused = new Node.Expression(
parseScriptText(reader.getText(start, stop)), start, parent);
}
/*
* XMLExpressionBody : : = ( S ? ' / > ' ) | ( S ? ' > ' ( Char * - ( char * ' < ' ) )
* CDSect ? ) * ETag ) | < TRANSLATION_ERROR >
*/
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));
@SuppressWarnings("unused" )
Node unused = 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));
@SuppressWarnings("unused" )
Node unused2 = new Node.Expression(text, start, parent);
} else {
break ;
}
}
if (!reader.matchesETagWithoutLessThan("jsp:expression" )) {
err.jspError(start, "jsp.error.unterminated" ,
"<jsp:expression>" );
}
}
}
/*
* ELExpressionBody . Starts with " # { " or " $ { " . Ends with " } " .
* See JspReader . skipELExpression ( ) .
*/
private void parseELExpression(Node parent, char type)
throws JasperException {
start = reader.mark();
Mark last = reader.skipELExpression();
if (last == null ) {
err.jspError(start, "jsp.error.unterminated" , type + "{" );
}
@SuppressWarnings("unused" )
Node unused = new Node.ELExpression(type, reader.getText(start, last),
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" , "<%" );
}
@SuppressWarnings("unused" )
Node unused = new Node.Scriptlet(
parseScriptText(reader.getText(start, stop)), start, parent);
}
/*
* XMLScriptletBody : : = ( S ? ' / > ' ) | ( S ? ' > ' ( Char * - ( char * ' < ' ) )
* CDSect ? ) * ETag ) | < TRANSLATION_ERROR >
*/
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));
@SuppressWarnings("unused" )
Node unused = 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));
@SuppressWarnings("unused" )
Node unused2 = new Node.Scriptlet(text, start, parent);
} else {
break ;
}
}
if (!reader.matchesETagWithoutLessThan("jsp:scriptlet" )) {
err.jspError(start, "jsp.error.unterminated" ,
"<jsp:scriptlet>" );
}
}
}
/**
* Param : : = ' & lt ; jsp : param ' S Attributes S ? EmptyBody S ?
*/
private void parseParam(Node parent) throws JasperException {
if (!reader.matches("<jsp:param" )) {
err.jspError(reader.mark(), "jsp.error.paramexpected" );
}
Attributes attrs = parseAttributes();
reader.skipSpaces();
Node paramActionNode = new Node.ParamAction(attrs, start, parent);
parseEmptyBody(paramActionNode, "jsp:param" );
reader.skipSpaces();
}
/*
* For Include : StdActionContent : : = Attributes ParamBody
*
* ParamBody : : = EmptyBody | ( ' > ' S ? ( ' < jsp : attribute ' NamedAttributes ) ? ' < jsp : body '
* ( JspBodyParam | < TRANSLATION_ERROR > ) S ? ETag ) | ( ' > ' S ? Param * ETag )
*
* EmptyBody : : = ' / > ' | ( ' > ' ETag ) | ( ' > ' S ? ' < jsp : attribute '
* NamedAttributes ETag )
*
* JspBodyParam : : = S ? ' > ' Param * ' < / jsp : body > '
*/
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 ? ' < jsp : attribute '
* NamedAttributes ETag )
*/
private void parseEmptyBody(Node parent, String tag) throws JasperException {
if (reader.matches("/>" )) {
// Done
} else if (reader.matches(">" )) {
if (reader.matchesETag(tag)) {
// Done
} else if (reader.matchesOptionalSpacesFollowedBy("<jsp:attribute" )) {
// Parse the one or more named attribute nodes
parseNamedAttributes(parent);
if (!reader.matchesETag(tag)) {
// Body not allowed
err.jspError(reader.mark(),
"jsp.error.jspbody.emptybody.only" , "<" + tag);
}
} else {
err.jspError(reader.mark(), "jsp.error.jspbody.emptybody.only" ,
"<" + tag);
}
} else {
err.jspError(reader.mark(), "jsp.error.unterminated" , "<" + tag);
}
}
/*
* For UseBean : StdActionContent : : = Attributes OptionalBody
*/
private void parseUseBean(Node parent) throws JasperException {
Attributes attrs = parseAttributes();
reader.skipSpaces();
Node useBeanNode = new Node.UseBean(attrs, start, parent);
parseOptionalBody(useBeanNode, "jsp:useBean" , TagInfo.BODY_CONTENT_JSP);
}
/*
* Parses OptionalBody , but also reused to parse bodies for plugin and param
* since the syntax is identical ( the only thing that differs substantially
* is how to process the body , and thus we accept the body type as a
* parameter ) .
*
* OptionalBody : : = EmptyBody | ActionBody
*
* ScriptlessOptionalBody : : = EmptyBody | ScriptlessActionBody
*
* TagDependentOptionalBody : : = EmptyBody | TagDependentActionBody
*
* EmptyBody : : = ' / > ' | ( ' > ' ETag ) | ( ' > ' S ? ' < jsp : attribute '
* NamedAttributes ETag )
*
* ActionBody : : = JspAttributeAndBody | ( ' > ' 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 ? ( ' & lt ; jsp : attribute ' NamedAttributes ) ? ' & lt ; jsp : body ' (
* JspBodyBody | & lt ; TRANSLATION_ERROR > ) S ? ETag )
*/
private boolean parseJspAttributeAndBody(Node parent, String tag,
String bodyType) throws JasperException {
boolean result = false ;
if (reader.matchesOptionalSpacesFollowedBy("<jsp:attribute" )) {
// May be an EmptyBody, depending on whether
// There's a "<jsp:body" before the ETag
// First, parse <jsp:attribute> elements:
parseNamedAttributes(parent);
result = true ;
}
if (reader.matchesOptionalSpacesFollowedBy("<jsp:body" )) {
// ActionBody
parseJspBody(parent, bodyType);
reader.skipSpaces();
if (!reader.matchesETag(tag)) {
err.jspError(reader.mark(), "jsp.error.unterminated" , "<"
+ tag);
}
result = true ;
} else if (result && !reader.matchesETag(tag)) {
// If we have <jsp:attribute> but something other than
// <jsp:body> or the end tag, translation error.
err.jspError(reader.mark(), "jsp.error.jspbody.required" , "<"
+ tag);
}
return result;
}
/*
* Params : : = ` > ' S ? ( ( ` < jsp : body > ' ( ( S ? Param + S ? ` < / jsp : body > ' ) |
* < TRANSLATION_ERROR > ) ) | Param + ) ' < / jsp : params > '
*/
private void parseJspParams(Node parent) throws JasperException {
Node jspParamsNode = new Node.ParamsAction(start, parent);
parseOptionalBody(jspParamsNode, "jsp:params" , JAVAX_BODY_CONTENT_PARAM);
}
/*
* Fallback : : = ' / > ' | ( ` > ' S ? ` < jsp : body > ' ( ( S ? ( Char * - ( Char * ` < / jsp : body > ' ) ) ` < / jsp : body > '
* S ? ) | < TRANSLATION_ERROR > ) ` < / jsp : fallback > ' ) | ( ' > ' ( Char * - (
* Char * ' < / jsp : fallback > ' ) ) ' < / jsp : fallback > ' )
*/
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 ? ( ' < jsp : attribute ' NamedAttributes ) ? ' < jsp : body ' (
* JspBodyPluginTags | < TRANSLATION_ERROR > ) S ? ETag ) | ( ' > ' S ? PluginTags
* ETag )
*
* EmptyBody : : = ' / > ' | ( ' > ' ETag ) | ( ' > ' S ? ' < jsp : attribute '
* NamedAttributes ETag )
*
*/
private void parsePlugin(Node parent) throws JasperException {
Attributes attrs = parseAttributes();
reader.skipSpaces();
Node pluginNode = new Node.PlugIn(attrs, start, parent);
parseOptionalBody(pluginNode, "jsp:plugin" , JAVAX_BODY_CONTENT_PLUGIN);
}
/*
* PluginTags : : = ( ' < jsp : params ' Params S ? ) ? ( ' < jsp : fallback ' Fallback ?
* S ? ) ?
*/
private void parsePluginTags(Node parent) throws JasperException {
reader.skipSpaces();
if (reader.matches("<jsp:params" )) {
parseJspParams(parent);
reader.skipSpaces();
}
if (reader.matches("<jsp:fallback" )) {
parseFallBack(parent);
reader.skipSpaces();
}
}
/*
* StandardAction : : = ' include ' StdActionContent | ' forward '
* StdActionContent | ' invoke ' StdActionContent | ' doBody ' StdActionContent |
* ' getProperty ' StdActionContent | ' setProperty ' StdActionContent |
* ' useBean ' StdActionContent | ' plugin ' StdActionContent | ' element '
* StdActionContent
*/
private void parseStandardAction(Node parent) throws JasperException {
Mark start = reader.mark();
if (reader.matches(INCLUDE_ACTION)) {
parseInclude(parent);
} else if (reader.matches(FORWARD_ACTION)) {
parseForward(parent);
} else if (reader.matches(INVOKE_ACTION)) {
if (!isTagFile) {
err.jspError(reader.mark(), "jsp.error.action.isnottagfile" ,
"<jsp:invoke" );
}
parseInvoke(parent);
} else if (reader.matches(DOBODY_ACTION)) {
if (!isTagFile) {
err.jspError(reader.mark(), "jsp.error.action.isnottagfile" ,
"<jsp:doBody" );
}
parseDoBody(parent);
} else if (reader.matches(GET_PROPERTY_ACTION)) {
parseGetProperty(parent);
} else if (reader.matches(SET_PROPERTY_ACTION)) {
parseSetProperty(parent);
} else if (reader.matches(USE_BEAN_ACTION)) {
parseUseBean(parent);
} else if (reader.matches(PLUGIN_ACTION)) {
parsePlugin(parent);
} else if (reader.matches(ELEMENT_ACTION)) {
parseElement(parent);
} else if (reader.matches(ATTRIBUTE_ACTION)) {
err.jspError(start, "jsp.error.namedAttribute.invalidUse" );
} else if (reader.matches(BODY_ACTION)) {
err.jspError(start, "jsp.error.jspbody.invalidUse" );
} else if (reader.matches(FALLBACK_ACTION)) {
err.jspError(start, "jsp.error.fallback.invalidUse" );
} else if (reader.matches(PARAMS_ACTION)) {
err.jspError(start, "jsp.error.params.invalidUse" );
} else if (reader.matches(PARAM_ACTION)) {
err.jspError(start, "jsp.error.param.invalidUse" );
} else if (reader.matches(OUTPUT_ACTION)) {
err.jspError(start, "jsp.error.jspoutput.invalidUse" );
} else {
err.jspError(start, "jsp.error.badStandardAction" );
}
}
/*
* # ' < ' CustomAction CustomActionBody
*
* CustomAction : : = TagPrefix ' : ' CustomActionName
*
* TagPrefix : : = Name
*
* CustomActionName : : = Name
*
* CustomActionBody : : = ( Attributes CustomActionEnd ) | < TRANSLATION_ERROR >
*
* Attributes : : = ( S Attribute ) * S ?
*
* CustomActionEnd : : = CustomActionTagDependent | CustomActionJSPContent |
* CustomActionScriptlessContent
*
* CustomActionTagDependent : : = TagDependentOptionalBody
*
* CustomActionJSPContent : : = OptionalBody
*
* CustomActionScriptlessContent : : = ScriptlessOptionalBody
*/
@SuppressWarnings("null" ) // tagFileInfo can't be null after initial test
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 (pageInfo.isErrorOnUndeclaredNamespace()) {
err.jspError(start, "jsp.error.undeclared_namespace" , prefix);
} else {
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 ) {
@SuppressWarnings("unused" )
Node unused = new Node.CustomTag(tagName, prefix, shortTagName,
uri, attrs, start, parent, tagInfo, tagHandlerClass);
} else {
@SuppressWarnings("unused" )
Node unused = new Node.CustomTag(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(tagName, prefix, shortTagName, uri,
attrs, start, parent, tagInfo, tagHandlerClass);
} else {
tagNode = new Node.CustomTag(tagName, prefix, shortTagName, uri,
attrs, start, parent, tagFileInfo);
}
parseOptionalBody(tagNode, tagName, bc);
return true ;
}
/*
* Parse for a template text string until ' < ' or " $ { " or " # { " is encountered ,
* recognizing escape sequences " < \ % " , " \ $ " , and " \ # " .
*
* Note : JSP uses ' \ $ ' as an escape for ' $ ' and ' \ # ' for ' # ' whereas EL uses
* ' \ $ { ' for ' $ { ' and ' \ # { ' for ' # { ' . We are processing JSP template
* test here so the JSP escapes apply .
*/
private void parseTemplateText(Node parent) {
if (!reader.hasMoreInput()) {
return ;
}
CharArrayWriter ttext = new CharArrayWriter();
int ch = reader.nextChar();
while (ch != -1 ) {
if (ch == '<' ) {
// Check for "<\%"
if (reader.peekChar(0 ) == '\\' && reader.peekChar(1 ) == '%' ) {
ttext.write(ch);
// Swallow the \
reader.nextChar();
ttext.write(reader.nextChar());
} else {
if (ttext.size() == 0 ) {
ttext.write(ch);
} else {
reader.pushChar();
break ;
}
}
} else if (ch == '\\' && !pageInfo.isELIgnored()) {
int next = reader.peekChar(0 );
if (next == '$' || next == '#' ) {
ttext.write(reader.nextChar());
} else {
ttext.write(ch);
}
} else if ((ch == '$' || ch == '#' && !pageInfo.isDeferredSyntaxAllowedAsLiteral()) &&
!pageInfo.isELIgnored()) {
if (reader.peekChar(0 ) == '{' ) {
reader.pushChar();
break ;
} else {
ttext.write(ch);
}
} else {
ttext.write(ch);
}
ch = reader.nextChar();
}
@SuppressWarnings("unused" )
Node unused = new Node.TemplateText(ttext.toString(), start, parent);
}
/*
* XMLTemplateText : : = ( S ? ' / > ' ) | ( S ? ' > ' ( ( Char * - ( Char * ( ' < ' |
* ' $ { ' ) ) ) ( ' $ { ' ELExpressionBody ) ? CDSect ? ) * ETag ) |
* < TRANSLATION_ERROR >
*/
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();
int ch = reader.nextChar();
while (ch != -1 ) {
if (ch == '<' ) {
// Check for <![CDATA[
if (!reader.matches("![CDATA[" )) {
break ;
}
start = reader.mark();
Mark stop = reader.skipUntil("]]>" );
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 == '\\' ) {
int next = reader.peekChar(0 );
if (next == '$' || next =='#' ) {
ttext.write(reader.nextChar());
} else {
ttext.write('\\' );
}
} else if (ch == '$' || ch == '#' ) {
if (reader.peekChar(0 ) == '{' ) {
// Swallow the '{'
reader.nextChar();
// Create a template text node
@SuppressWarnings("unused" )
Node unused = new Node.TemplateText(
ttext.toString(), start, parent);
// Mark and parse the EL expression and create its node:
parseELExpression(parent, (char ) ch);
start = reader.mark();
ttext.reset();
} else {
ttext.write(ch);
}
} else {
ttext.write(ch);
}
ch = reader.nextChar();
}
@SuppressWarnings("unused" )
Node unused =
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 ) | ( ' < jsp : directive . '
* XMLDirectiveBody ) | ( ' < % ! ' DeclarationBody ) | ( ' < jsp : declaration '
* XMLDeclarationBody ) | ( ' < % = ' ExpressionBody ) | ( ' < jsp : expression '
* XMLExpressionBody ) | ( ' $ { ' ELExpressionBody ) | ( ' < % ' ScriptletBody ) | ( ' < jsp : scriptlet '
* XMLScriptletBody ) | ( ' < jsp : text ' XMLTemplateText ) | ( ' < jsp : '
* StandardAction ) | ( ' < ' CustomAction CustomActionBody ) | TemplateText
*/
private void parseElements(Node parent) throws JasperException {
if (scriptlessCount > 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("<jsp:directive." )) {
parseXMLDirective(parent);
} else if (reader.matches("<%!" )) {
parseDeclaration(parent);
} else if (reader.matches("<jsp:declaration" )) {
parseXMLDeclaration(parent);
} else if (reader.matches("<%=" )) {
parseExpression(parent);
} else if (reader.matches("<jsp:expression" )) {
parseXMLExpression(parent);
} else if (reader.matches("<%" )) {
parseScriptlet(parent);
} else if (reader.matches("<jsp:scriptlet" )) {
parseXMLScriptlet(parent);
} else if (reader.matches("<jsp:text" )) {
parseXMLTemplateText(parent);
} else if (!pageInfo.isELIgnored() && reader.matches("${" )) {
parseELExpression(parent, '$' );
} else if (!pageInfo.isELIgnored()
&& !pageInfo.isDeferredSyntaxAllowedAsLiteral()
&& reader.matches("#{" )) {
parseELExpression(parent, '#' );
} else if (reader.matches("<jsp:" )) {
parseStandardAction(parent);
} else if (!parseCustomTag(parent)) {
checkUnbalancedEndTag();
parseTemplateText(parent);
}
}
/*
* ScriptlessBody : : = ( ' < % - - ' JSPCommentBody ) | ( ' < % @ ' DirectiveBody ) | ( ' < jsp : directive . '
* XMLDirectiveBody ) | ( ' < % ! ' < TRANSLATION_ERROR > ) | ( ' < jsp : declaration '
* < TRANSLATION_ERROR > ) | ( ' < % = ' < TRANSLATION_ERROR > ) | ( ' < jsp : expression '
* < TRANSLATION_ERROR > ) | ( ' < % ' < TRANSLATION_ERROR > ) | ( ' < jsp : scriptlet '
* < TRANSLATION_ERROR > ) | ( ' < jsp : text ' XMLTemplateText ) | ( ' $ { '
* ELExpressionBody ) | ( ' < jsp : ' StandardAction ) | ( ' < ' CustomAction
* CustomActionBody ) | TemplateText
*/
private void parseElementsScriptless(Node parent) throws JasperException {
// Keep track of how many scriptless nodes we've encountered
// so we know whether our child nodes are forced scriptless
scriptlessCount++;
start = reader.mark();
if (reader.matches("<%--" )) {
parseComment(parent);
} else if (reader.matches("<%@" )) {
parseDirective(parent);
} else if (reader.matches("<jsp:directive." )) {
parseXMLDirective(parent);
} else if (reader.matches("<%!" )) {
err.jspError(reader.mark(), "jsp.error.no.scriptlets" );
} else if (reader.matches("<jsp:declaration" )) {
err.jspError(reader.mark(), "jsp.error.no.scriptlets" );
} else if (reader.matches("<%=" )) {
err.jspError(reader.mark(), "jsp.error.no.scriptlets" );
} else if (reader.matches("<jsp:expression" )) {
err.jspError(reader.mark(), "jsp.error.no.scriptlets" );
} else if (reader.matches("<%" )) {
err.jspError(reader.mark(), "jsp.error.no.scriptlets" );
} else if (reader.matches("<jsp:scriptlet" )) {
err.jspError(reader.mark(), "jsp.error.no.scriptlets" );
} else if (reader.matches("<jsp:text" )) {
parseXMLTemplateText(parent);
} else if (!pageInfo.isELIgnored() && reader.matches("${" )) {
parseELExpression(parent, '$' );
} else if (!pageInfo.isELIgnored()
&& !pageInfo.isDeferredSyntaxAllowedAsLiteral()
&& reader.matches("#{" )) {
parseELExpression(parent, '#' );
} else if (reader.matches("<jsp:" )) {
parseStandardAction(parent);
} else if (!parseCustomTag(parent)) {
checkUnbalancedEndTag();
parseTemplateText(parent);
}
scriptlessCount--;
}
/*
* TemplateTextBody : : = ( ' < % - - ' JSPCommentBody ) | ( ' < % @ ' DirectiveBody ) | ( ' < jsp : directive . '
* XMLDirectiveBody ) | ( ' < % ! ' < TRANSLATION_ERROR > ) | ( ' < jsp : declaration '
* < TRANSLATION_ERROR > ) | ( ' < % = ' < TRANSLATION_ERROR > ) | ( ' < jsp : expression '
* < TRANSLATION_ERROR > ) | ( ' < % ' < TRANSLATION_ERROR > ) | ( ' < jsp : scriptlet '
* < TRANSLATION_ERROR > ) | ( ' < jsp : text ' < TRANSLATION_ERROR > ) | ( ' $ { '
* < TRANSLATION_ERROR > ) | ( ' < jsp : ' < TRANSLATION_ERROR > ) | 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("<jsp:directive." )) {
parseXMLDirective(parent);
} else if (reader.matches("<%!" )) {
err.jspError(reader.mark(), "jsp.error.not.in.template" ,
"Declarations" );
} else if (reader.matches("<jsp:declaration" )) {
err.jspError(reader.mark(), "jsp.error.not.in.template" ,
"Declarations" );
} else if (reader.matches("<%=" )) {
err.jspError(reader.mark(), "jsp.error.not.in.template" ,
"Expressions" );
} else if (reader.matches("<jsp:expression" )) {
err.jspError(reader.mark(), "jsp.error.not.in.template" ,
"Expressions" );
} else if (reader.matches("<%" )) {
err.jspError(reader.mark(), "jsp.error.not.in.template" ,
"Scriptlets" );
} else if (reader.matches("<jsp:scriptlet" )) {
err.jspError(reader.mark(), "jsp.error.not.in.template" ,
"Scriptlets" );
} else if (reader.matches("<jsp:text" )) {
err.jspError(reader.mark(), "jsp.error.not.in.template" ,
"<jsp:text" );
} else if (!pageInfo.isELIgnored() && reader.matches("${" )) {
err.jspError(reader.mark(), "jsp.error.not.in.template" ,
"Expression language" );
} else if (!pageInfo.isELIgnored()
&& !pageInfo.isDeferredSyntaxAllowedAsLiteral()
&& reader.matches("#{" )) {
err.jspError(reader.mark(), "jsp.error.not.in.template" ,
"Expression language" );
} else if (reader.matches("<jsp:" )) {
err.jspError(reader.mark(), "jsp.error.not.in.template" ,
"Standard actions" );
} else if (parseCustomTag(parent)) {
err.jspError(reader.mark(), "jsp.error.not.in.template" ,
"Custom actions" );
} else {
checkUnbalancedEndTag();
parseTemplateText(parent);
}
}
/*
* Flag as error if an unbalanced end tag appears by itself .
*/
private void checkUnbalancedEndTag() throws JasperException {
if (!reader.matches("</" )) {
return ;
}
// Check for unbalanced standard actions
if (reader.matches("jsp:" )) {
err.jspError(start, "jsp.error.unbalanced.endtag" , "jsp:" );
}
// Check for unbalanced custom actions
String tagName = reader.parseToken(false );
int i = tagName.indexOf(':' );
if (i == -1 || pageInfo.getURI(tagName.substring(0 , i)) == null ) {
reader.reset(start);
return ;
}
err.jspError(start, "jsp.error.unbalanced.endtag" , tagName);
}
/**
* TagDependentBody : =
*/
private void parseTagDependentBody(Node parent, String tag)
throws JasperException {
Mark bodyStart = reader.mark();
Mark bodyEnd = reader.skipUntilETag(tag);
if (bodyEnd == null ) {
err.jspError(start, "jsp.error.unterminated" , "<" + tag);
}
@SuppressWarnings("unused" )
Node unused = new Node.TemplateText(reader.getText(bodyStart, bodyEnd),
bodyStart, parent);
}
/*
* Parses jsp : body action .
*/
private void parseJspBody(Node parent, String bodyType)
throws JasperException {
Mark start = reader.mark();
Node bodyNode = new Node.JspBody(start, parent);
reader.skipSpaces();
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("<jsp:attribute" )) {
err.jspError(reader.mark(),
"jsp.error.nested.jspattribute" );
} else if (reader.matches("<jsp:body" )) {
err.jspError(reader.mark(), "jsp.error.nested.jspbody" );
}
}
if (bodyType.equalsIgnoreCase(TagInfo.BODY_CONTENT_JSP)) {
parseElements(parent);
} else if (bodyType
.equalsIgnoreCase(TagInfo.BODY_CONTENT_SCRIPTLESS)) {
parseElementsScriptless(parent);
} else if (bodyType == JAVAX_BODY_CONTENT_PARAM) {
// (note the == since we won't recognize JAVAX_*
// from outside this module).
reader.skipSpaces();
parseParam(parent);
} else if (bodyType == JAVAX_BODY_CONTENT_TEMPLATE_TEXT) {
parseElementsTemplateText(parent);
}
}
err.jspError(start, "jsp.error.unterminated" , "<" + tag);
} else {
err.jspError(start, "jasper.error.bad.bodycontent.type" );
}
}
/*
* Parses named attributes .
*/
private void parseNamedAttributes(Node parent) throws JasperException {
do {
Mark start = reader.mark();
Attributes attrs = parseAttributes();
Node.NamedAttribute namedAttributeNode = new Node.NamedAttribute(
attrs, start, parent);
reader.skipSpaces();
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("<jsp:attribute" ));
}
/**
* Determine the body type of & lt ; jsp : attribute > 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 (TagAttributeInfo tldAttr : tldAttrs) {
if (name.equals(tldAttr.getName())) {
if (tldAttr.isFragment()) {
return TagInfo.BODY_CONTENT_SCRIPTLESS;
}
if (tldAttr.canBeRequestTime()) {
return TagInfo.BODY_CONTENT_JSP;
}
}
}
if (tagInfo.hasDynamicAttributes()) {
return TagInfo.BODY_CONTENT_JSP;
}
} else if (n instanceof Node.IncludeAction) {
if ("page" .equals(name)) {
return TagInfo.BODY_CONTENT_JSP;
}
} else if (n instanceof Node.ForwardAction) {
if ("page" .equals(name)) {
return TagInfo.BODY_CONTENT_JSP;
}
} else if (n instanceof Node.SetProperty) {
if ("value" .equals(name)) {
return TagInfo.BODY_CONTENT_JSP;
}
} else if (n instanceof Node.UseBean) {
if ("beanName" .equals(name)) {
return TagInfo.BODY_CONTENT_JSP;
}
} else if (n instanceof Node.PlugIn) {
if ("width" .equals(name) || "height" .equals(name)) {
return TagInfo.BODY_CONTENT_JSP;
}
} else if (n instanceof Node.ParamAction) {
if ("value" .equals(name)) {
return TagInfo.BODY_CONTENT_JSP;
}
} else if (n instanceof Node.JspElement) {
return TagInfo.BODY_CONTENT_JSP;
}
return JAVAX_BODY_CONTENT_TEMPLATE_TEXT;
}
private void parseFileDirectives(Node parent) throws JasperException {
reader.skipUntil("<" );
while (reader.hasMoreInput()) {
start = reader.mark();
if (reader.matches("%--" )) {
// Comment
reader.skipUntil("--%>" );
} else if (reader.matches("%@" )) {
parseDirective(parent);
} else if (reader.matches("jsp:directive." )) {
parseXMLDirective(parent);
} else if (reader.matches("%!" )) {
// Declaration
reader.skipUntil("%>" );
} else if (reader.matches("%=" )) {
// Expression
reader.skipUntil("%>" );
} else if (reader.matches("%" )) {
// Scriptlet
reader.skipUntil("%>" );
}
reader.skipUntil("<" );
}
}
}
Messung V0.5 in Prozent C=94 H=94 G=93
¤ Dauer der Verarbeitung: 0.53 Sekunden
¤
*© Formatika GbR, Deutschland