/* * 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;
/** * JspReader is an input buffer for the JSP parser. It should allow * unlimited lookahead and pushback. It also has a bunch of parsing * utility methods for understanding htmlesque thingies. * * @author Anil K. Vijendran * @author Anselm Baird-Smith * @author Harish Prabandham * @author Rajiv Mordani * @author Mandar Raje * @author Danno Ferrin * @author Kin-man Chung * @author Shawn Bayern * @author Mark Roth
*/
class JspReader {
/** * Logger.
*/ privatefinal Log log = LogFactory.getLog(JspReader.class); // must not be static
/** * The current spot in the file.
*/ private Mark current;
/** * The compilation context.
*/ privatefinal JspCompilationContext context;
/** * The Jasper error dispatcher.
*/ privatefinal ErrorDispatcher err;
/** * Constructor. * * @param ctxt The compilation context * @param fname The file name * @param encoding The file encoding * @param jar ? * @param err The error dispatcher * @throws JasperException If a Jasper-internal error occurs * @throws FileNotFoundException If the JSP file is not found (or is unreadable) * @throws IOException If an IO-level error occurs, e.g. reading the file
*/
JspReader(JspCompilationContext ctxt,
String fname,
String encoding,
Jar jar,
ErrorDispatcher err) throws JasperException, FileNotFoundException, IOException {
/** * Constructor: same as above constructor but with initialized reader * to the file given. * * @param ctxt The compilation context * @param fname The file name * @param reader A reader for the JSP source file * @param err The error dispatcher * * @throws JasperException If an error occurs parsing the JSP file
*/
JspReader(JspCompilationContext ctxt,
String fname,
InputStreamReader reader,
ErrorDispatcher err) throws JasperException {
/** * @return JSP compilation context with which this JspReader is * associated
*/
JspCompilationContext getJspCompilationContext() { return context;
}
/** * Checks if the current file has more input. * * @return True if more reading is possible
*/ boolean hasMoreInput() { return current.cursor < current.stream.length;
}
int nextChar() { if (!hasMoreInput()) { return -1;
}
/** * A faster approach than calling {@link #mark()} & {@link #nextChar()}. * However, this approach is only safe if the mark is only used within the * JspReader.
*/ privateint nextChar(Mark mark) { if (!hasMoreInput()) { return -1;
}
/** * Search the given character, If it was found, then mark the current cursor * and the cursor point to next character.
*/ privateBoolean indexOf(char c, Mark mark) { if (!hasMoreInput()) { returnnull;
}
int end = current.stream.length; int ch; int line = current.line; int col = current.col; int i = current.cursor; for(; i < end; i ++) {
ch = current.stream[i];
if (ch == c) {
mark.update(i, line, col);
} if (ch == '\n') {
line++;
col = 0;
} else {
col++;
} if (ch == c) {
current.update(i+1, line, col); returnBoolean.TRUE;
}
}
current.update(i, line, col); returnBoolean.FALSE;
}
/** * Back up the current cursor by one char, assumes current.cursor > 0, * and that the char to be pushed back is not '\n'.
*/ void pushChar() {
current.cursor--;
current.col--;
}
String getText(Mark start, Mark stop) {
Mark oldstart = mark();
reset(start);
CharArrayWriter caw = new CharArrayWriter(); while (!markEquals(stop)) {
caw.write(nextChar());
}
caw.close();
setCurrent(oldstart); return caw.toString();
}
/** * Read ahead one character without moving the cursor. * * @return The next character or -1 if no further input is available
*/ int peekChar() { return peekChar(0);
}
/** * Read ahead the given number of characters without moving the cursor. * * @param readAhead The number of characters to read ahead. NOTE: This is * zero based. * * @return The requested character or -1 if the end of the input is reached * first
*/ int peekChar(int readAhead) { int target = current.cursor + readAhead; if (target < current.stream.length) { return current.stream[target];
} return -1;
}
Mark mark() { returnnew Mark(current);
}
/** * This method avoids a call to {@link #mark()} when doing comparison.
*/ privateboolean markEquals(Mark another) { return another.equals(current);
}
void reset(Mark mark) {
current = new Mark(mark);
}
/** * Similar to {@link #reset(Mark)} but no new Mark will be created. * Therefore, the parameter mark must NOT be used in other places.
*/ privatevoid setCurrent(Mark mark) {
current = mark;
}
/** * search the stream for a match to a string * @param string The string to match * @return <strong>true</strong> is one is found, the current position * in stream is positioned after the search string, <strong> * false</strong> otherwise, position in stream unchanged.
*/ boolean matches(String string) { int len = string.length(); int cursor = current.cursor; int streamSize = current.stream.length; if (cursor + len < streamSize) { //Try to scan in memory int line = current.line; int col = current.col; int ch; int i = 0; for(; i < len; i ++) {
ch = current.stream[i+cursor]; if (string.charAt(i) != ch) { returnfalse;
} if (ch == '\n') {
line ++;
col = 0;
} else {
col++;
}
}
current.update(i+cursor, line, col);
} else {
Mark mark = mark(); int ch = 0; int i = 0; do {
ch = nextChar(); if (((char) ch) != string.charAt(i++)) {
setCurrent(mark); returnfalse;
}
} while (i < len);
} returntrue;
}
boolean matchesETag(String tagName) {
Mark mark = mark();
if (!matches("" + tagName)) { returnfalse;
}
skipSpaces(); if (nextChar() == '>') { returntrue;
}
setCurrent(mark); returnfalse;
}
boolean matchesETagWithoutLessThan(String tagName) {
Mark mark = mark();
if (!matches("/" + tagName)) { returnfalse;
}
skipSpaces(); if (nextChar() == '>') { returntrue;
}
setCurrent(mark); returnfalse;
}
/** * Looks ahead to see if there are optional spaces followed by * the given String. If so, true is returned and those spaces and * characters are skipped. If not, false is returned and the * position is restored to where we were before.
*/ boolean matchesOptionalSpacesFollowedBy(String s) {
Mark mark = mark();
skipSpaces(); boolean result = matches( s ); if( !result ) {
setCurrent(mark);
}
return result;
}
int skipSpaces() { int i = 0; while (hasMoreInput() && isSpace()) {
i++;
nextChar();
} return i;
}
/** * Skip until the given string is matched in the stream. * When returned, the context is positioned past the end of the match. * * @param limit The String to match. * @return A non-null <code>Mark</code> instance (positioned immediately * before the search string) if found, <strong>null</strong> * otherwise.
*/
Mark skipUntil(String limit) {
Mark ret = mark(); int limlen = limit.length(); char firstChar = limit.charAt(0); Boolean result = null;
Mark restart = null;
skip: while((result = indexOf(firstChar, ret)) != null) { if (result.booleanValue()) { if (restart != null) {
restart.init(current, true);
} else {
restart = mark();
} for (int i = 1 ; i < limlen ; i++) { if (peekChar() == limit.charAt(i)) {
nextChar();
} else {
current.init(restart, true); continue skip;
}
} return ret;
}
} returnnull;
}
/** * Skip until the given string is matched in the stream, but ignoring * chars initially escaped by a '\' and any EL expressions. * When returned, the context is positioned past the end of the match. * * @param limit The String to match. * @param ignoreEL <code>true</code> if something that looks like EL should * not be treated as EL. * @return A non-null <code>Mark</code> instance (positioned immediately * before the search string) if found, <strong>null</strong> * otherwise.
*/
Mark skipUntilIgnoreEsc(String limit, boolean ignoreEL) {
Mark ret = mark(); int limlen = limit.length(); int ch; int prev = 'x'; // Doesn't matter char firstChar = limit.charAt(0);
skip: for (ch = nextChar(ret) ; ch != -1 ; prev = ch, ch = nextChar(ret)) { if (ch == '\\' && prev == '\\') {
ch = 0; // Double \ is not an escape char anymore
} elseif (prev == '\\') { continue;
} elseif (!ignoreEL && (ch == '$' || ch == '#') && peekChar() == '{' ) { // Move beyond the '{'
nextChar();
skipELExpression();
} elseif (ch == firstChar) { for (int i = 1 ; i < limlen ; i++) { if (peekChar() == limit.charAt(i)) {
nextChar();
} else { continue skip;
}
} return ret;
}
} returnnull;
}
/** * Skip until the given end tag is matched in the stream. * When returned, the context is positioned past the end of the tag. * * @param tag The name of the tag whose ETag (</tag>) to match. * @return A non-null <code>Mark</code> instance (positioned immediately * before the ETag) if found, <strong>null</strong> otherwise.
*/
Mark skipUntilETag(String tag) {
Mark ret = skipUntil("" + tag); if (ret != null) {
skipSpaces(); if (nextChar() != '>') {
ret = null;
}
} return ret;
}
/** * Parse ELExpressionBody that is a body of ${} or #{} expression. Initial * reader position is expected to be just after '${' or '#{' characters. * <p> * In case of success, this method returns <code>Mark</code> for the last * character before the terminating '}' and reader is positioned just after * the '}' character. If no terminating '}' is encountered, this method * returns <code>null</code>. * <p> * Starting with EL 3.0, nested paired {}s are supported. * * @return Mark for the last character of EL expression or <code>null</code>
*/
Mark skipELExpression() { // ELExpressionBody. // Starts with "#{" or "${". Ends with "}". // May contain quoted "{", "}", '{', or '}' and nested "{...}"
Mark last = mark(); boolean singleQuoted = false; boolean doubleQuoted = false; int nesting = 0; int currentChar; do {
currentChar = nextChar(last); while (currentChar == '\\' && (singleQuoted || doubleQuoted)) { // skip character following '\' within quotes // No need to update 'last', as neither of these characters // can be the closing '}'.
nextChar();
currentChar = nextChar();
} if (currentChar == -1) { returnnull;
} if (currentChar == '"' && !singleQuoted) {
doubleQuoted = !doubleQuoted;
} elseif (currentChar == '\'' && !doubleQuoted) {
singleQuoted = !singleQuoted;
} elseif (currentChar == '{' && !doubleQuoted && !singleQuoted) {
nesting++;
} elseif (currentChar =='}' && !doubleQuoted && !singleQuoted) { // Note: This also matches the terminating '}' at which point // nesting will be set to -1 - hence the test for // while (currentChar != '}' || nesting > -1 ||...) below // to continue the loop until the final '}' is detected
nesting--;
}
} while (currentChar != '}' || singleQuoted || doubleQuoted || nesting > -1);
return last;
}
finalboolean isSpace() { // Note: If this logic changes, also update Node.TemplateText.rtrim() return peekChar() <= ' ';
}
/** * Parse a space delimited token. * If quoted the token will consume all characters up to a matching quote, * otherwise, it consumes up to the first delimiter character. * * @param quoted If <strong>true</strong> accept quoted strings.
*/
String parseToken(boolean quoted) throws JasperException {
StringBuilder StringBuilder = new StringBuilder();
skipSpaces();
StringBuilder.setLength(0);
if (!hasMoreInput()) { return"";
}
int ch = peekChar();
if (quoted) { if (ch == '"' || ch == '\'') {
char endQuote = ch == '"' ? '"' : '\''; // Consume the open quote:
ch = nextChar(); for (ch = nextChar(); ch != -1 && ch != endQuote;
ch = nextChar()) { if (ch == '\\') {
ch = nextChar();
}
StringBuilder.append((char) ch);
} // Check end of quote, skip closing quote: if (ch == -1) {
err.jspError(mark(), "jsp.error.quotes.unterminated");
}
} else {
err.jspError(mark(), "jsp.error.attr.quoted");
}
} else { if (!isDelimiter()) { // Read value until delimiter is found: do {
ch = nextChar(); // Take care of the quoting here. if (ch == '\\') { if (peekChar() == '"' || peekChar() == '\'' ||
peekChar() == '>' || peekChar() == '%') {
ch = nextChar();
}
}
StringBuilder.append((char) ch);
} while (!isDelimiter());
}
}
return StringBuilder.toString();
}
/** * Parse utils - Is current character a token delimiter ? * Delimiters are currently defined to be =, >, <, ", and ' or any * any space character as defined by <code>isSpace</code>. * * @return A boolean.
*/ privateboolean isDelimiter() { if (! isSpace()) { int ch = peekChar(); // Look for a single-char work delimiter: if (ch == '=' || ch == '>' || ch == '"' || ch == '\''
|| ch == '/') { returntrue;
} // Look for an end-of-comment or end-of-tag: if (ch == '-') {
Mark mark = mark(); if (((ch = nextChar()) == '>')
|| ((ch == '-') && (nextChar() == '>'))) {
setCurrent(mark); returntrue;
} else {
setCurrent(mark); returnfalse;
}
} returnfalse;
} else { returntrue;
}
}
}
¤ Dauer der Verarbeitung: 0.1 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.