/* * Copyright 2013 serso aka se.solovyev * * Licensed 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. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * Contact details * * Email: se.solovyev@gmail.com * Site: http://se.solovyev.org */ package org.solovyev.android.calculator; import android.text.SpannableString; import android.text.SpannableStringBuilder; import android.text.Spanned; import jscl.MathContext; import jscl.MathEngine; import jscl.NumeralBase; import jscl.text.DoubleParser; import jscl.text.JsclIntegerParser; import jscl.text.ParseException; import jscl.text.Parser; import org.solovyev.android.calculator.math.MathType; import org.solovyev.android.calculator.text.NumberSpan; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.ArrayList; import java.util.List; public class NumberBuilder extends BaseNumberBuilder { public NumberBuilder(@Nonnull Engine engine) { super(engine); } private static int replaceNumberInText(@Nonnull SpannableStringBuilder sb, @Nullable String oldNumber, int trimmedChars, @Nonnull NumeralBase nb, @Nonnull final MathEngine engine) { if (oldNumber == null) { sb.setSpan(new NumberSpan(nb), sb.length(), sb.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE); return 0; } // in any case remove old number from text final int oldNumberLength = oldNumber.length() + trimmedChars; sb.delete(sb.length() - oldNumberLength, sb.length()); final SpannableString newNumber = new SpannableString(engine.format(oldNumber, nb)); newNumber.setSpan(new NumberSpan(nb), 0, newNumber.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE); sb.append(newNumber); // offset between old number and new number return newNumber.length() - oldNumberLength; } @Nonnull private static Double toDouble(@Nonnull String s, @Nonnull NumeralBase nb, @Nonnull final MathContext mc) throws NumberFormatException { final NumeralBase defaultNb = mc.getNumeralBase(); try { mc.setNumeralBase(nb); final Parser.Parameters p = Parser.Parameters.get(s); try { return JsclIntegerParser.parser.parse(p, null).content().doubleValue(); } catch (ParseException e) { p.exceptionsPool.release(e); try { p.reset(); return DoubleParser.parser.parse(p, null).content().doubleValue(); } catch (ParseException e1) { p.exceptionsPool.release(e1); throw new NumberFormatException(); } } } finally { mc.setNumeralBase(defaultNb); } } /** * Method replaces number in text according to some rules (e.g. formatting) * * @param sb text where number can be replaced * @param result math type result of current token * @return offset between new number length and old number length (newNumberLength - oldNumberLength) */ @Override public int process(@Nonnull SpannableStringBuilder sb, @Nonnull MathType.Result result) { if (canContinue(result)) { // let's continue building number if (numberBuilder == null) { // if new number => create new builder numberBuilder = new StringBuilder(); } if (result.type != MathType.numeral_base) { // just add matching string numberBuilder.append(result.match); } else { // set explicitly numeral base (do not include it into number) nb = NumeralBase.getByPrefix(result.match); } return 0; } else { // process current number (and go to the next one) final int offset = processNumber(sb); if (result.type == MathType.numeral_base) { // if current token is numeral base - update current numeral base nb = NumeralBase.getByPrefix(result.match); } return offset; } } /** * Method replaces number in text according to some rules (e.g. formatting) * * @param sb text where number can be replaced * @return offset between new number length and old number length (newNumberLength - oldNumberLength) */ public int processNumber(@Nonnull SpannableStringBuilder sb) { // total number of trimmed chars int trimmedChars = 0; String number = null; // toXml numeral base (as later it might be replaced) final NumeralBase localNb = getNumeralBase(); if (numberBuilder != null) { try { number = numberBuilder.toString(); // let's get rid of unnecessary characters (grouping separators, + after E) final List<String> tokens = new ArrayList<String>(); tokens.addAll(MathType.grouping_separator.getTokens(engine)); // + after E can be omitted: 10+E = 10E (NOTE: - cannot be omitted ) tokens.add("+"); for (String groupingSeparator : tokens) { final String trimmedNumber = number.replace(groupingSeparator, ""); trimmedChars += number.length() - trimmedNumber.length(); number = trimmedNumber; } // check if number still valid toDouble(number, getNumeralBase(), engine.getMathEngine()); } catch (NumberFormatException e) { // number is not valid => stop number = null; } numberBuilder = null; // must set default numeral base (exit numeral base mode) nb = engine.getMathEngine().getNumeralBase(); } return replaceNumberInText(sb, number, trimmedChars, localNb, engine.getMathEngine()); } }