Home

Tutorial

Table of contents:

Customizing built-in evaluator

Localizing an evaluator

One can think a mathematical expression doesn't depend on the country where it is written.
It's a mistake.

The most obvious is that some functions are better understood if they are translated.
For example, somme is better that sum for French people.

Translating the functions or constants names can be done by customizing the evaluator's parameters

// Gets the default DoubleEvaluator's parameters
Parameters params = DoubleEvaluator.getDefaultParameters();
// adds the translations
params.setTranslation(DoubleEvaluator.SUM, "somme");

Unfortunatly, this is not enough.
The numbers are not written the same way in all countries. For example, in France, the decimal separator is not a point, but a comma.
This cause another problem: The comma is the default separator for function arguments. In France, we have to replace this separator by another.
Once again, for this last problem, the Parameters instance will give us the solution.

// Change the default function separator
params.setFunctionArgumentSeparator(';');

So, it remains the problem of parsing the numbers expressed in French format.
Fortunately, the evaluator's method that transforms a literal to a number can be overriden.
Then we will use a subclass of DoubleEvaluator with a specialized conversion from String to Double.

// Create a new DoubleEvaluator that supports the French decimal separator
DoubleEvaluator evaluator = new DoubleEvaluator(params) {
  private final DecimalFormat format = (DecimalFormat) DecimalFormat.getInstance(Locale.FRENCH);
  @Override
  protected Double toValue(String literal, Object evaluationContext) {
    // Override the method that converts a literal to a number, in order to match with
    // the French decimal separator
    try {
      return format.parse(literal).doubleValue();
    } catch (ParseException e) {
      // If the number has a wrong format, throw the right exception.
      throw new IllegalArgumentException(literal+" is not a number");
    }
  }
};

Maybe you're wondering why not to simply add a Locale to the parameters ?.
Unfortunately, parsing the numbers is a little bit more complicated than what is done above.
You have to deal with grouping separators and with strange (ugly) things with French thousands separator. This requires ... coding :-(
This stuff is implemented below in the complete working code.


Here is a complete working sample code:

package com.fathzer.soft.javaluator.examples;
 
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Locale;
 
import com.fathzer.soft.javaluator.DoubleEvaluator;
import com.fathzer.soft.javaluator.Parameters;
 
/** An example of how to localize an existing evaluator to match French locale.
 * <br>As a French, I prefer "moyenne" to "avg" and "somme" to "sum".
 * <br>As the default argument function (',') is used as decimal separator in France,
 * I may also change it to ';'.
 * <br> Here is how I can do that very easily.
 */
public class LocalizedEvaluator extends DoubleEvaluator {
  /** Defines the new function (square root).*/
  private static final Parameters PARAMS;
 
  static {
    // Gets the default DoubleEvaluator's parameters
    PARAMS = DoubleEvaluator.getDefaultParameters();
    // adds the translations
    PARAMS.setTranslation(DoubleEvaluator.SUM, "somme");
    PARAMS.setTranslation(DoubleEvaluator.AVERAGE, "moyenne");
    // Change the default function separator
    PARAMS.setFunctionArgumentSeparator(';');
  }
 
  private final NumberFormat format;
  private final char effectiveThousandsSeparator;
 
  public LocalizedEvaluator() {
    super(PARAMS);
    // Create a French number formatter
    format = NumberFormat.getInstance(Locale.FRENCH);
    format.setGroupingUsed(true);
    // Unfortunately, Java treatment of French thousands separator is ... weird:
    // Most French people (like me some years ago) think the separator is a space.
    // But java thought (at least until java 8) it was Non-breaking space. But since
    // (at least Java 17) they changed their mind and now it's "Espace fine insécable".
    // As they have other priorities than the stability of their API, trying to parse
    // with the wrong category of space gives a wrong result.
    // Let's try to be kind with or users: We will replace all kind of spaces by the one java accepts
    effectiveThousandsSeparator = format.format(1000.0).charAt(1);
  }
 
  @Override
  protected Double toValue(String literal, Object evaluationContext) {
    // Override the method that converts a literal to a number, in order to match with
    // the French decimal separator
    try {
      // For a strange reason, Java thinks that only "fine non breaking spaces" or "non breaking spaces" are
      // French thousands separators. So, we will replace all kind of spaces in the literal by the java one
      literal = literal.replace(' ', effectiveThousandsSeparator); 
      literal = literal.replace((char)0x00A0, effectiveThousandsSeparator); 
      literal = literal.replace((char)0x202F, effectiveThousandsSeparator); 
      return format.parse(literal).doubleValue();
    } catch (ParseException e) {
      // If the number has a wrong format, throw the right exception.
      throw new IllegalArgumentException(literal+" is not a number");
    }
  }
 
  public static void main(String[] args) {
    // Test that all this stuff is ok
    LocalizedEvaluator evaluator = new LocalizedEvaluator();
    String expression = "3 000 +moyenne(3 ; somme(1,5 ; 7 ; -3,5))";
    System.out.println (expression+" = "+evaluator.format.format(evaluator.evaluate(expression)));
  }
}
 

The output is 3 000 +moyenne(3 ; somme(1,5 ; 7 ; -3,5)) = 3 004

Advertising

Back to top