/*!
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* This program 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.
*
* Copyright (c) 2002-2013 Pentaho Corporation.. All rights reserved.
*/
package org.pentaho.reporting.libraries.formula.util;
import org.pentaho.reporting.libraries.base.util.ArgumentNullException;
import org.pentaho.reporting.libraries.base.util.DebugLog;
import org.pentaho.reporting.libraries.formula.DefaultFormulaContext;
import org.pentaho.reporting.libraries.formula.Formula;
import org.pentaho.reporting.libraries.formula.FormulaContext;
import org.pentaho.reporting.libraries.formula.lvalues.ContextLookup;
import org.pentaho.reporting.libraries.formula.lvalues.FormulaFunction;
import org.pentaho.reporting.libraries.formula.lvalues.LValue;
import org.pentaho.reporting.libraries.formula.lvalues.StaticValue;
import org.pentaho.reporting.libraries.formula.lvalues.Term;
import org.pentaho.reporting.libraries.formula.parser.FormulaParser;
import org.pentaho.reporting.libraries.formula.parser.ParseException;
import java.math.BigDecimal;
import java.util.LinkedHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class FormulaUtil {
private static final char QUOTE_CHAR = '"';
private FormulaUtil() {
}
public static String quoteReference( final String reference ) {
if ( reference == null ) {
throw new NullPointerException();
}
final char[] referenceChars = reference.toCharArray();
if ( isQuotingNeeded( referenceChars ) == false ) {
return '[' + reference + ']';
}
return '[' + quoteString( reference ) + ']';
}
private static boolean isQuotingNeeded( final char[] referenceChars ) {
if ( referenceChars == null ) {
throw new NullPointerException();
}
for ( int i = 0; i < referenceChars.length; i++ ) {
final char c = referenceChars[ i ];
if ( Character.isJavaIdentifierPart( c ) == false ) {
return true;
}
}
return false;
}
public static String quoteString( final String text ) {
if ( text == null ) {
return null;
}
final StringBuilder b = new StringBuilder( text.length() );
final char[] chars = text.toCharArray();
b.append( QUOTE_CHAR );
for ( int i = 0; i < chars.length; i++ ) {
final char c = chars[ i ];
if ( c == QUOTE_CHAR ) {
b.append( QUOTE_CHAR );
}
b.append( c );
}
b.append( QUOTE_CHAR );
return b.toString();
}
public static String[] getReferences( final String formula ) throws ParseException {
if ( formula == null ) {
throw new NullPointerException();
}
final String formulaExpression = extractFormula( formula );
if ( formulaExpression == null ) {
throw new ParseException( "Formula is invalid" );
}
return getReferences( new Formula( formulaExpression ) );
}
public static String[] getReferences( final Formula formula ) {
if ( formula == null ) {
throw new NullPointerException();
}
final LinkedHashMap<String, Boolean> map = new LinkedHashMap<String, Boolean>();
final LValue lValue = formula.getRootReference();
collectReferences( lValue, map );
return map.keySet().toArray( new String[ map.size() ] );
}
private static void collectReferences( final LValue lval, final LinkedHashMap<String, Boolean> map ) {
if ( lval instanceof Term ) {
final Term t = (Term) lval;
final LValue[] childValues = t.getChildValues();
for ( int i = 0; i < childValues.length; i++ ) {
final LValue childValue = childValues[ i ];
collectReferences( childValue, map );
}
} else if ( lval instanceof ContextLookup ) {
final ContextLookup cl = (ContextLookup) lval;
map.put( cl.getName(), Boolean.TRUE );
} else if ( lval instanceof FormulaFunction ){
FormulaFunction ff = FormulaFunction.class.cast(lval);
LValue[] lvals = ff.getChildValues();
for (int i = 0; i < lvals.length; i++) {
collectReferences( lvals[i], map );
}
}
}
public static String extractFormula( final String formula ) {
String[] strings = extractFormulaContext( formula );
return strings[ 1 ];
}
public static String extractStaticTextFromFormula( final String formula ) {
if ( formula == null ) {
return null;
}
final String formulaFragment = extractFormula( formula );
return extractStaticTextFromFormulaFragment( formulaFragment );
}
public static String extractStaticTextFromFormulaFragment( final String formula ) {
if ( formula == null ) {
return null;
}
try {
final FormulaParser parser = new FormulaParser();
final LValue lValue = parser.parse( formula );
if ( lValue.isConstant() ) {
if ( lValue instanceof StaticValue ) {
final StaticValue staticValue = (StaticValue) lValue;
final Object o = staticValue.getValue();
if ( o == null ) {
return null; // NON-NLS
}
return String.valueOf( o );
}
}
return null; // NON-NLS
} catch ( Exception e ) {
return null; // NON-NLS
}
}
public static boolean isValidFormulaFragment( final String formula ) {
try {
final FormulaParser parser = new FormulaParser();
final LValue lValue = parser.parse( formula );
return true;
} catch ( Exception e ) {
return false;
}
}
public static String createCellUITextFromFormula( final String formula ) {
return createCellUITextFromFormula( formula, new DefaultFormulaContext() );
}
public static String createCellUITextFromFormula( final String formula, final FormulaContext context ) {
try {
final FormulaParser parser = new FormulaParser();
final LValue lValue = parser.parse( formula );
lValue.initialize( context );
if ( lValue.isConstant() ) {
if ( lValue instanceof StaticValue ) {
final StaticValue staticValue = (StaticValue) lValue;
final Object o = staticValue.getValue();
if ( o == null ) {
return "=NA()"; // NON-NLS
}
return String.valueOf( o );
}
} else if ( lValue instanceof ContextLookup ) {
ContextLookup l = (ContextLookup) lValue;
return l.toString();
}
final String cellText = formula;
return cellText.startsWith( "=" ) ? cellText : "=" + cellText;
} catch ( Exception e ) {
e.printStackTrace();
return formula;
}
}
public static String createEditorTextFromFormula( final String formula,
final FormulaContext formulaContext ) {
ArgumentNullException.validate( "fomulaContext", formulaContext );
try {
final FormulaParser parser = new FormulaParser();
final LValue lValue = parser.parse( formula );
lValue.initialize( formulaContext );
if ( lValue.isConstant() ) {
if ( lValue instanceof StaticValue ) {
final StaticValue staticValue = (StaticValue) lValue;
final Object o = staticValue.getValue();
if ( o == null ) {
return "=NA()";
}
if ( o instanceof Number ) {
return String.valueOf( o );
}
return '\'' + String.valueOf( o );
}
}
final String cellText = formula;
return cellText.startsWith( "=" ) ? cellText : "=" + cellText;
} catch ( Exception e ) {
return "'" + formula;
}
}
/**
* Creates a formula fragment from the given UI text. Within the reporting engine, the fragment must be prefixed with
* a formula-context selector.
* <p/>
* The input follows the Excel/OpenOffice Calc rules: A leading equals indicates a formula, a leading apostrophe
* indicates a text. Otherwise, if the input is parsable as number it will be formed into a formula that returns that
* static number, and everything else will be treated as text.
* <p/>
* Note that there is no syntax check for formula input - if the user writes garbage after the formula selector, this
* garbage will still be treated as formula.
*
* @param formula the input from the user.
* @return the normalized formula.
*/
public static String createFormulaFromUIText( final String formula ) {
if ( formula.startsWith( "=" ) ) {
// it is a formula ...
return formula.substring( 1 );
}
try {
final BigDecimal bd = new BigDecimal( formula.trim() );
return formula.trim();
} catch ( NumberFormatException nfe ) {
// ignore ..
}
if ( formula.startsWith( "\'" ) ) {
// is a explicit text ..
return FormulaUtil.quoteString( formula.substring( 1 ) );
}
return FormulaUtil.quoteString( formula );
}
public static String[] extractFormulaContext( String formula ) {
String formulaNamespace;
String formulaExpression;
if ( formula == null ) {
formulaNamespace = null;
formulaExpression = null;
} else {
if ( formula.endsWith( ";" ) ) {
DebugLog.log( "A formula with a trailing semicolon is not valid. Auto-correcting the formula." );
formula = formula.substring( 0, formula.length() - 1 );
}
Pattern pattern = Pattern.compile( "^((\\w+):|=)(.*)" );
Matcher matcher = pattern.matcher( formula );
if ( matcher.matches() ) {
formulaNamespace = matcher.group( 2 );
if ( formulaNamespace == null ) {
formulaNamespace = "report";
}
formulaExpression = matcher.group( 3 );
} else {
formulaNamespace = null;
formulaExpression = null;
}
}
return new String[] { formulaNamespace, formulaExpression };
}
}