/* * 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.cocoon.components.treeprocessor.variables; import org.apache.cocoon.sitemap.PatternException; /** * Parses "Text {module:{module:attribute}} more text {variable}" types of * expressions. Supports escaping of braces with '\' character, and nested * expressions. * * @version CVS $Id$ */ public final class VariableExpressionTokenizer { /** * Callback for tokenizer */ public interface TokenReciever { int OPEN = -2; int CLOSE = -3; int COLON = -4; int TEXT = -5; int MODULE = -6; int VARIABLE = -8; /** * Reports parsed tokens. */ void addToken(int type, String value) throws PatternException; } /** * Tokenizes specified expression. Passes tokens to the * reciever. * * @throws PatternException if expression is not valid */ public static void tokenize(String expression, TokenReciever reciever) throws PatternException { int lastTokenType = 0; int openCount = 0; int closeCount = 0; int pos = 0; int i; boolean escape = false; for (i = 0; i < expression.length(); i++) { final char c = expression.charAt(i); if (escape) { escape = false; } else if (c == '\\' && i < expression.length()) { char nextChar = expression.charAt(i + 1); if (nextChar == '{' || nextChar == '}') { expression = expression.substring(0, i) + expression.substring(i + 1); escape = true; i--; } } else if (c == '{') { if (i > pos) { reciever.addToken(lastTokenType = TokenReciever.TEXT, expression.substring(pos, i)); } openCount++; reciever.addToken(lastTokenType = TokenReciever.OPEN, null); int colonPos = indexOf(expression, ':', i); int closePos = indexOf(expression, '}', i); int openPos = indexOf(expression, '{', i); if (openPos < colonPos && openPos < closePos) { throw new PatternException("Invalid '{' at position " + i + " in expression \"" + expression + "\""); } if (colonPos < closePos) { // we've found a module String module = expression.substring(i + 1, colonPos); reciever.addToken(lastTokenType = TokenReciever.MODULE, module); i = colonPos - 1; } else { // Unprefixed name: variable reciever.addToken(lastTokenType = TokenReciever.VARIABLE, expression.substring(i + 1, closePos)); i = closePos - 1; } pos = i + 1; } else if (c == '}') { if (i > 0 && expression.charAt(i - 1) == '\\') { continue; } if (i > pos) { reciever.addToken(lastTokenType = TokenReciever.TEXT, expression.substring(pos, i)); } closeCount++; reciever.addToken(lastTokenType = TokenReciever.CLOSE, null); pos = i + 1; } else if (c == ':') { if (lastTokenType != TokenReciever.MODULE || i != pos) { // this colon isn't part of a module reference continue; } reciever.addToken(lastTokenType = TokenReciever.COLON, null); pos = i + 1; } } if (i > pos) { reciever.addToken(lastTokenType = TokenReciever.TEXT, expression.substring(pos, i)); } if (openCount != closeCount) { throw new PatternException("Mismatching braces in expression \"" + expression + "\""); } } private static int indexOf(String expression, char chr, int pos) { int location; return (location = expression.indexOf(chr, pos + 1)) != -1? location : expression.length(); } }