blob: 8e278156a743221e5e7f6c57cf49e8bf118432f0 [file] [log] [blame]
/*
* 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.
*/
/* $Id$ */
package org.apache.fop.fo.expr;
/**
* Class to tokenize XSL FO property expression.
* This class is heavily based on the epxression tokenizer in James Clark's
* XT, an XSLT processor.
*/
class PropertyTokenizer {
static final int TOK_EOF = 0;
static final int TOK_NCNAME = 1;
static final int TOK_MULTIPLY = TOK_NCNAME + 1;
static final int TOK_LPAR = TOK_MULTIPLY + 1;
static final int TOK_RPAR = TOK_LPAR + 1;
static final int TOK_LITERAL = TOK_RPAR + 1;
static final int TOK_NUMBER = TOK_LITERAL + 1;
static final int TOK_FUNCTION_LPAR = TOK_NUMBER + 1;
static final int TOK_PLUS = TOK_FUNCTION_LPAR + 1;
static final int TOK_MINUS = TOK_PLUS + 1;
static final int TOK_MOD = TOK_MINUS + 1;
static final int TOK_DIV = TOK_MOD + 1;
static final int TOK_NUMERIC = TOK_DIV + 1;
static final int TOK_COMMA = TOK_NUMERIC + 1;
static final int TOK_PERCENT = TOK_COMMA + 1;
static final int TOK_COLORSPEC = TOK_PERCENT + 1;
static final int TOK_FLOAT = TOK_COLORSPEC + 1;
static final int TOK_INTEGER = TOK_FLOAT + 1;
protected int currentToken = TOK_EOF;
protected String currentTokenValue;
protected int currentUnitLength;
private int currentTokenStartIndex;
private /* final */ String expr;
private int exprIndex;
private int exprLength;
/**
* Construct a new PropertyTokenizer object to tokenize the passed
* String.
* @param s The Property expressio to tokenize.
*/
PropertyTokenizer(String s) {
this.expr = s;
this.exprLength = s.length();
}
/**
* Parse the next token in the expression string.
* This sets the following package visible variables:
* currentToken An enumerated value identifying the recognized token
* currentTokenValue A String containing the token contents
* currentUnitLength If currentToken = TOK_NUMERIC, the number of
* characters in the unit name.
* @throws PropertyException If un unrecognized token is encountered.
*/
void next() throws PropertyException {
currentTokenValue = null;
currentTokenStartIndex = exprIndex;
boolean bSawDecimal;
while (true) {
if (exprIndex >= exprLength) {
currentToken = TOK_EOF;
return;
}
char c = expr.charAt(exprIndex++);
switch (c) {
case ' ':
case '\t':
case '\r':
case '\n':
currentTokenStartIndex = exprIndex;
break;
case ',':
currentToken = TOK_COMMA;
return;
case '+':
currentToken = TOK_PLUS;
return;
case '-':
currentToken = TOK_MINUS;
return;
case '(':
currentToken = TOK_LPAR;
return;
case ')':
currentToken = TOK_RPAR;
return;
case '"':
case '\'':
exprIndex = expr.indexOf(c, exprIndex);
if (exprIndex < 0) {
exprIndex = currentTokenStartIndex + 1;
throw new PropertyException("missing quote");
}
currentTokenValue = expr.substring(currentTokenStartIndex
+ 1, exprIndex++);
currentToken = TOK_LITERAL;
return;
case '*':
/*
* if (currentMaybeOperator) {
* recognizeOperator = false;
*/
currentToken = TOK_MULTIPLY;
/*
* }
* else
* throw new PropertyException("illegal operator *");
*/
return;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
scanDigits();
if (exprIndex < exprLength && expr.charAt(exprIndex) == '.') {
exprIndex++;
bSawDecimal = true;
if (exprIndex < exprLength
&& isDigit(expr.charAt(exprIndex))) {
exprIndex++;
scanDigits();
}
} else {
bSawDecimal = false;
}
if (exprIndex < exprLength && expr.charAt(exprIndex) == '%') {
exprIndex++;
currentToken = TOK_PERCENT;
} else {
// Check for possible unit name following number
currentUnitLength = exprIndex;
scanName();
currentUnitLength = exprIndex - currentUnitLength;
currentToken = (currentUnitLength > 0) ? TOK_NUMERIC
: (bSawDecimal ? TOK_FLOAT : TOK_INTEGER);
}
currentTokenValue = expr.substring(currentTokenStartIndex,
exprIndex);
return;
case '.':
nextDecimalPoint();
return;
case '#': // Start of color value
nextColor();
return;
default:
--exprIndex;
scanName();
if (exprIndex == currentTokenStartIndex) {
throw new PropertyException("illegal character");
}
currentTokenValue = expr.substring(currentTokenStartIndex, exprIndex);
if (currentTokenValue.equals("mod")) {
currentToken = TOK_MOD;
return;
} else if (currentTokenValue.equals("div")) {
currentToken = TOK_DIV;
return;
}
if (followingParen()) {
currentToken = TOK_FUNCTION_LPAR;
} else {
currentToken = TOK_NCNAME;
}
return;
}
}
}
private void nextDecimalPoint() throws PropertyException {
if (exprIndex < exprLength
&& isDigit(expr.charAt(exprIndex))) {
++exprIndex;
scanDigits();
if (exprIndex < exprLength
&& expr.charAt(exprIndex) == '%') {
exprIndex++;
currentToken = TOK_PERCENT;
} else {
// Check for possible unit name following number
currentUnitLength = exprIndex;
scanName();
currentUnitLength = exprIndex - currentUnitLength;
currentToken = (currentUnitLength > 0) ? TOK_NUMERIC
: TOK_FLOAT;
}
currentTokenValue = expr.substring(currentTokenStartIndex,
exprIndex);
return;
}
throw new PropertyException("illegal character '.'");
}
private void nextColor() throws PropertyException {
if (exprIndex < exprLength) {
++exprIndex;
scanHexDigits();
int len = exprIndex - currentTokenStartIndex - 1;
if (len % 3 == 0 || len == 8) {
currentToken = TOK_COLORSPEC;
} else {
//Actually not a color at all, but an NCNAME starting with "#"
scanRestOfName();
currentToken = TOK_NCNAME;
}
currentTokenValue = expr.substring(currentTokenStartIndex,
exprIndex);
return;
} else {
throw new PropertyException("illegal character '#'");
}
}
/**
* Attempt to recognize a valid NAME token in the input expression.
*/
private void scanName() {
if (exprIndex < exprLength && isNameStartChar(expr.charAt(exprIndex))) {
scanRestOfName();
}
}
private void scanRestOfName() {
while (++exprIndex < exprLength) {
if (!isNameChar(expr.charAt(exprIndex))) {
break;
}
}
}
/**
* Attempt to recognize a valid sequence of decimal DIGITS in the
* input expression.
*/
private void scanDigits() {
while (exprIndex < exprLength && isDigit(expr.charAt(exprIndex))) {
exprIndex++;
}
}
/**
* Attempt to recognize a valid sequence of hexadecimal DIGITS in the
* input expression.
*/
private void scanHexDigits() {
while (exprIndex < exprLength && isHexDigit(expr.charAt(exprIndex))) {
exprIndex++;
}
}
/**
* Return a boolean value indicating whether the following non-whitespace
* character is an opening parenthesis.
*/
private boolean followingParen() {
for (int i = exprIndex; i < exprLength; i++) {
switch (expr.charAt(i)) {
case '(':
exprIndex = i + 1;
return true;
case ' ':
case '\r':
case '\n':
case '\t':
break;
default:
return false;
}
}
return false;
}
private static final String NAME_START_CHARS
= "_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final String NAME_CHARS = ".-0123456789";
private static final String DIGITS = "0123456789";
private static final String HEX_CHARS = DIGITS + "abcdefABCDEF";
/**
* Return a boolean value indicating whether the argument is a
* decimal digit (0-9).
* @param c The character to check
*/
private static boolean isDigit(char c) {
return DIGITS.indexOf(c) >= 0;
}
/**
* Return a boolean value indicating whether the argument is a
* hexadecimal digit (0-9, A-F, a-f).
* @param c The character to check
*/
private static boolean isHexDigit(char c) {
return HEX_CHARS.indexOf(c) >= 0;
}
/**
* Return a boolean value indicating whether the argument is whitespace
* as defined by XSL (space, newline, CR, tab).
* @param c The character to check
*/
/* not used
private static boolean isSpace(char c) {
switch (c) {
case ' ':
case '\r':
case '\n':
case '\t':
return true;
default:
return false;
}
}
*/
/**
* Return a boolean value indicating whether the argument is a valid name
* start character, ie. can start a NAME as defined by XSL.
* @param c The character to check
*/
private static boolean isNameStartChar(char c) {
return NAME_START_CHARS.indexOf(c) >= 0 || c >= 0x80;
}
/**
* Return a boolean value indicating whether the argument is a valid name
* character, ie. can occur in a NAME as defined by XSL.
* @param c The character to check
*/
private static boolean isNameChar(char c) {
return NAME_START_CHARS.indexOf(c) >= 0 || NAME_CHARS.indexOf(c) >= 0
|| c >= 0x80;
}
}