/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2002-2008, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotools.renderer.style;
import java.util.ArrayList;
import java.util.List;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.filter.text.cql2.CQL;
import org.geotools.filter.text.cql2.CQLException;
import org.opengis.filter.FilterFactory;
import org.opengis.filter.expression.Expression;
/**
* Helper class that allows the extraction of CQL expressions out of a plain
* text string using special separators. Parsing rules are:
* <ul>
* <li>whatever is between <code>${</code> and <code>}</code> is considered
* a CQL expression</li>
* <li><code>$</code> and <code>}</code> can be used stand alone only
* escaped with <code>\</code> (e.g. <code>\$</code> and <code>\}</code>)</li>
* <li><code>\</code> can be used stand alone only escaped with another
* <code>\</code></li> (e.g. <code>\\</code>)
* </ul>
*
* Examples of valid expressions:
* <ul>
* <li>"one two three \} \$ \\" (simple literal with special chars escaped)</li>
* <li>"My name is ${name}" (a simple attribute reference)</li>
* <li>"Hi this is ${(intAtt + 2) * 10}" (cql using attribute and math expressions)</li>
* </ul>
*
* Examples of non valid expressions:
* <ul>
* <li>"bla ${myAttName" (unclosed expression section)</li>
* <li>"bla } bla" (<code>}</code> is reserved, should have been escaped)</li>
*
*
* @author Andrea Aime - TOPP
*
*
* @source $URL$
*/
public class ExpressionExtractor {
static final FilterFactory ff = CommonFactoryFinder.getFilterFactory(null);
/**
* Parses the original string and returns an array or parsed expressions, in
* particular, the result of parsing each embedded cql expression and string
* literals in between the cql expressions, in the order they appear in the
* original string
*
* @param expression
* @return
*/
static List<Expression> splitCqlExpressions(String expression) {
boolean inCqlExpression = false;
List<Expression> result = new ArrayList<Expression>();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < expression.length(); i++) {
final char curr = expression.charAt(i);
final boolean last = (i == expression.length() - 1);
final char next = last ? 0 : expression.charAt(i + 1);
if(curr == '\\') {
if(last)
throw new IllegalArgumentException("Unescaped \\ at position " + (i + 1));
if(next == '\\')
sb.append('\\');
else if(next == '$')
sb.append('$');
else if(next == '}')
sb.append('}');
else
throw new IllegalArgumentException("Unescaped \\ at position " + (i + 1));
// skip the next character
i++;
} else if(curr == '$') {
if(last || next != '{')
throw new IllegalArgumentException("Unescaped $ at position " + (i + 1));
if(inCqlExpression)
throw new IllegalArgumentException("Already found a ${ sequence before the one at " + (i + 1));
// if we extracted a literal in between two expressions, add it to the result
if(sb.length() > 0) {
result.add(ff.literal(sb.toString()));
sb.setLength(0);
}
// mark the beginning and skip the next character
inCqlExpression = true;
i++;
} else if(curr == '}') {
if(!inCqlExpression)
throw new IllegalArgumentException("Already found a ${ sequence before the one at " + (i + 1));
if(sb.length() == 0)
throw new IllegalArgumentException("Invalid empty cql expression ${} at " + (i - 1));
try {
result.add(CQL.toExpression(sb.toString()));
sb.setLength(0);
} catch(CQLException e) {
throw new IllegalArgumentException("Invalid cql expression '" + sb + "'", e);
}
inCqlExpression = false;
} else {
sb.append(curr);
}
}
// when done, if we are still in a CQL expression, it means it hasn't been closed
if(inCqlExpression) {
throw new IllegalArgumentException("Unclosed CQL expression '" + sb + "'");
} else if(sb.length() > 0){
result.add(ff.literal(sb.toString()));
}
return result;
}
/**
* Given an expression list will catenate it using
* @param expression
* @return
*/
static Expression catenateExpressions(List<Expression> expressions) {
if(expressions == null || expressions.size() == 0)
throw new IllegalArgumentException("You should provide at least one expression in the list");
Expression result = expressions.get(0);
for (int i = 1; i < expressions.size(); i++) {
result = ff.function("strConcat", result, expressions.get(i));
}
return result;
}
public static Expression extractCqlExpressions(String expression) {
return catenateExpressions(splitCqlExpressions(expression));
}
}