| /* |
| * 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; |
| } |
| |
| } |
| |