package org.geogebra.common.kernel; import org.geogebra.common.export.MathmlTemplate; import org.geogebra.common.factories.FormatFactory; import org.geogebra.common.kernel.arithmetic.Equation; import org.geogebra.common.kernel.arithmetic.ExpressionNode; import org.geogebra.common.kernel.arithmetic.ExpressionNodeConstants; import org.geogebra.common.kernel.arithmetic.ExpressionValue; import org.geogebra.common.kernel.arithmetic.MySpecialDouble; import org.geogebra.common.kernel.arithmetic.MyVecNDNode; import org.geogebra.common.kernel.arithmetic.NumberValue; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.main.Localization; import org.geogebra.common.plugin.Operation; import org.geogebra.common.util.NumberFormatAdapter; import org.geogebra.common.util.ScientificFormatAdapter; import org.geogebra.common.util.StringUtil; import org.geogebra.common.util.debug.Log; import org.geogebra.common.util.lang.Unicode; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; /** * StringTemplate provides a container for all settings we might need when * serializing ExpressionValues to screen / XML / CAS input / export. * * @author Zbynek Konecny */ public class StringTemplate implements ExpressionNodeConstants { // rounding hack, see Kernel.format() private static final double ROUND_HALF_UP_FACTOR = 1.0 + 1E-15; private final String name; private StringType stringType; private boolean internationalizeDigits; // form to serialise "pi" to private String printFormPI; // form to serialise sqrt(-1) to private String printFormImaginary; private ScientificFormatAdapter sf; private NumberFormatAdapter nf; private boolean forceSF; private boolean forceNF; private boolean allowMoreDigits; private boolean useRealLabels; private boolean localizeCmds; private boolean usePrefix; private boolean hideLHS = false; private boolean questionMarkForNaN = true; private boolean numeric = true; /** * Default template, but do not localize commands */ public static final StringTemplate noLocalDefault = new StringTemplate( "nonLocalDefault"); static { noLocalDefault.localizeCmds = false; } /** * Template which prints numbers with maximal precision and adds prefix to * variables (ggbtmpvar) */ public static final StringTemplate prefixedDefault = new StringTemplate( "prefixedDefault") { @Override public double getRoundHalfUpFactor(double abs, NumberFormatAdapter nf2, ScientificFormatAdapter sf2, boolean useSF) { return 1; } }; /** * @return whether line breaks are allowed */ public boolean isInsertLineBreaks() { return false; } static { prefixedDefault.localizeCmds = false; prefixedDefault.internationalizeDigits = false; prefixedDefault.forceNF = true; prefixedDefault.usePrefix = true; prefixedDefault.nf = FormatFactory.getPrototype().getNumberFormat(15); } /** * GeoGebra string type, internationalize digits */ public static final StringTemplate defaultTemplate = new StringTemplate( "defaultTemplate"); /** * Template which prints original construction's labels */ public static final StringTemplate realTemplate = new StringTemplate( "realTemplate"); static { realTemplate.useRealLabels = true; } /** * LaTeX string type, do not internationalize digits */ public static final StringTemplate latexTemplate = new StringTemplate( "latexTemplate"); static { latexTemplate.setType(StringType.LATEX); } /** * JLaTeXMath latex template */ public static final StringTemplate latexTemplateJLM = new StringTemplate( "latexTemplate") { @Override public String escapeString(String string) { String value = string.replaceAll("\\\\", "\\\\backslash ") .replaceAll("([&%$#{}_])", "\\\\$1") .replaceAll("~", "\u223C ") .replaceAll("\\^", "\\\\^{\\ } "); return value; } }; static { latexTemplateJLM.setType(StringType.LATEX); } /** * LaTeX template for CAS; like LaTeX template but do not substitute * 3.1415926535 by pi */ public static final StringTemplate latexTemplateCAS = new StringTemplate( "latexTemplate"); static { latexTemplateCAS.setType(StringType.LATEX); latexTemplateCAS.allowPiHack = false; } /** * MathML string type, do not internationalize digits */ public static final StringTemplate mathmlTemplate = new StringTemplate( "mathmlTemplate"); static { mathmlTemplate.setType(StringType.CONTENT_MATHML); } /** * LibreOffice string type, do not internationalize digits */ public static final StringTemplate libreofficeTemplate = new StringTemplate( "libreOfficeTemplate"); static { libreofficeTemplate.setType(StringType.LIBRE_OFFICE); } /** * giac string type, do not internationalize digits */ public static final StringTemplate giacTemplate = new StringTemplate( "giacTemplate"); static { giacTemplate.internationalizeDigits = false; giacTemplate.numeric = false; giacTemplate.usePrefix = false; giacTemplate.forceNF = true; giacTemplate.localizeCmds = false; giacTemplate.setType(StringType.GIAC); giacTemplate.nf = FormatFactory.getPrototype().getNumberFormat(15); } /** * Same as giacTemplate. We check object equality to this in * StringUtil.wrapInExact which is just a hack. TODO */ public static final StringTemplate giacTemplateInternal = new StringTemplate( "giacTemplateMini"); static { giacTemplateInternal.internationalizeDigits = false; giacTemplateInternal.numeric = false; giacTemplateInternal.usePrefix = false; giacTemplateInternal.forceNF = true; giacTemplateInternal.localizeCmds = false; giacTemplateInternal.setType(StringType.GIAC); giacTemplateInternal.nf = FormatFactory.getPrototype() .getNumberFormat(15); } /** * XML string type, do not internationalize digits */ public static final StringTemplate xmlTemplate = new StringTemplate( "xmlTemplate") { @Override public int getCoordStyle(int coordStyle) { return Kernel.COORD_STYLE_DEFAULT; } }; static { xmlTemplate.forceSF = true; xmlTemplate.allowMoreDigits = true; xmlTemplate.internationalizeDigits = false; xmlTemplate.setType(StringType.GEOGEBRA_XML); xmlTemplate.localizeCmds = false; xmlTemplate.sf = FormatFactory.getPrototype().getScientificFormat(15, 20, false); xmlTemplate.questionMarkForNaN = false; } /** * XML string type, do not internationalize digits */ public static final StringTemplate casCopyTemplate = new StringTemplate( "casCopyTemplate") { @Override public int getCoordStyle(int coordStyle) { return Kernel.COORD_STYLE_DEFAULT; } }; static { casCopyTemplate.forceSF = true; casCopyTemplate.allowMoreDigits = true; casCopyTemplate.internationalizeDigits = false; casCopyTemplate.setType(StringType.GEOGEBRA_XML); casCopyTemplate.localizeCmds = false; casCopyTemplate.sf = FormatFactory.getPrototype() .getScientificFormat(15, 20, false); casCopyTemplate.questionMarkForNaN = true; } /** * for input bar; same as default, but increases precision to * MIN_EDITING_PRINT_PRECISION */ public static final StringTemplate editTemplate = new StringTemplate( "editTemplate"); /** * for input bar; same as default, but adds some extra helper symbols */ public static final StringTemplate editorTemplate = new StringTemplate( "editorTemplate"); /** * For simplicity make this static now and see in the future whether we will * need more engines in one app */ static { editTemplate.sf = FormatFactory.getPrototype().getScientificFormat( GeoElement.MIN_EDITING_PRINT_PRECISION, 20, false); editTemplate.nf = FormatFactory.getPrototype() .getNumberFormat(GeoElement.MIN_EDITING_PRINT_PRECISION); editTemplate.allowMoreDigits = true; editTemplate.hideLHS = true; editorTemplate.sf = editTemplate.sf; editorTemplate.nf = editTemplate.nf; editorTemplate.allowMoreDigits = editTemplate.allowMoreDigits; editorTemplate.hideLHS = editTemplate.hideLHS; } /** * Template for regression: uses 6 figures or 6 sig digits based on Kernel * settings, string type is XML */ public static final StringTemplate regression = new StringTemplate( "regression"); static { regression.sf = FormatFactory.getPrototype().getScientificFormat(6, 20, false); regression.nf = FormatFactory.getPrototype().getNumberFormat(6); regression.forceSF = true; regression.setType(StringType.GEOGEBRA_XML); } /** * OGP string type */ public static final StringTemplate ogpTemplate = new StringTemplate( "ogpTemplate"); static { ogpTemplate.forceSF = false; ogpTemplate.internationalizeDigits = false; ogpTemplate.setType(StringType.OGP); ogpTemplate.localizeCmds = false; ogpTemplate.nf = FormatFactory.getPrototype().getNumberFormat(0); } /** * Default template, just increases precision to max */ public static final StringTemplate maxPrecision = new StringTemplate( "maxPrecision"); static { maxPrecision.sf = FormatFactory.getPrototype().getScientificFormat(15, 20, false); maxPrecision.allowMoreDigits = true; maxPrecision.forceSF = true; maxPrecision.localizeCmds = false; } public static final StringTemplate maxDecimals = new StringTemplate( "maxPrecision"); static { maxPrecision.nf = FormatFactory.getPrototype().getNumberFormat(15); maxPrecision.allowMoreDigits = false; maxPrecision.forceNF = true; maxPrecision.localizeCmds = false; } /** * Default template, just increases precision to max 13 not 15 so that when * sent to Giac is treated as a double, not a multi-precision float (MPFR). * #5130 */ public static final StringTemplate maxPrecision13 = new StringTemplate( "maxPrecision13"); static { maxPrecision13.sf = FormatFactory.getPrototype().getScientificFormat(13, 20, false); maxPrecision13.allowMoreDigits = true; maxPrecision13.forceSF = true; maxPrecision13.localizeCmds = false; // don't want to use exact value otherwise Giac will do an exact // calculation when we want approx // eg Integral[sin(x) / (1 + a^2 - 2a cos(x)), 0, pi] in the Algebra // View // #5129, #5130 maxPrecision13.printFormPI = "3.141592653590"; } /** * Default template, just allow bigger precision for Numeric command */ public static final StringTemplate numericDefault = new StringTemplate( "numericDefault"); static { numericDefault.allowMoreDigits = true; } /** * Not localized template, allow bigger precision for Numeric command */ public static final StringTemplate numericNoLocal = new StringTemplate( "numericNoLocal"); static { numericNoLocal.allowMoreDigits = true; numericNoLocal.localizeCmds = false; } /** * Default LaTeX template, just allow bigger precision for Numeric command */ public static final StringTemplate numericLatex = new StringTemplate( "numericLatex"); static { numericLatex.stringType = StringType.LATEX; numericLatex.allowMoreDigits = true; numericLatex.useRealLabels = true; } /** Generic template for CAS tests */ public static final StringTemplate testTemplate = new StringTemplate( "testTemplate"); static { testTemplate.internationalizeDigits = false; testTemplate.setType(StringType.GEOGEBRA_XML); // testTemplate.localizeCmds = false; testTemplate.sf = FormatFactory.getPrototype().getScientificFormat(15, 20, false); } /** * No localized digits, max precision */ public static final StringTemplate testTemplateJSON = new StringTemplate( "testTemplate"); static { testTemplate.internationalizeDigits = false; // testTemplate.localizeCmds = false; testTemplate.sf = FormatFactory.getPrototype().getScientificFormat(15, 20, false); } /** Template for CAS tests involving Numeric command */ public static final StringTemplate testNumeric = new StringTemplate( "testNumeric"); static { testNumeric.internationalizeDigits = false; testNumeric.setType(StringType.GEOGEBRA_XML); // testNumeric.localizeCmds = false; testNumeric.allowMoreDigits = true; testNumeric.sf = FormatFactory.getPrototype().getScientificFormat(15, 20, false); } /** * Creates default string template * * @param name * name for debugging */ protected StringTemplate(String name) { internationalizeDigits = true; localizeCmds = true; setType(StringType.GEOGEBRA); this.name = name; } /** * Returns string type of resulting text * * @return string type */ public StringType getStringType() { return this.stringType; } /** * Disables international digits, e.g. for CAS and XML * * @return true if we want to allow e.g. arabic digits in output */ public boolean internationalizeDigits() { return this.internationalizeDigits; } /** * * @return string representation of PI in this template */ public String getPi() { return printFormPI; } /** * * @return string representation of PI in this template */ public String getImaginary() { return printFormImaginary; } /** * Creates new string template with given type * * @param t * string type * @return template */ public static StringTemplate get(StringType t) { if (t == null || t.equals(StringType.GEOGEBRA)) { return defaultTemplate; } StringTemplate tpl = new StringTemplate("TemplateFor:" + t); tpl.setType(t); return tpl; } private void setType(StringType t) { stringType = t; switch (t) { case GIAC: printFormPI = "%pi"; printFormImaginary = "i"; break; case GEOGEBRA_XML: printFormPI = "pi"; printFormImaginary = Unicode.IMAGINARY; break; case LATEX: printFormPI = "\\pi "; printFormImaginary = "i"; break; case LIBRE_OFFICE: printFormPI = "%pi"; printFormImaginary = "i"; break; default: // #5129 // #5130 printFormPI = Unicode.PI_STRING; printFormImaginary = Unicode.IMAGINARY; } } /** * Returns whether scientific format (sig digits) should be used (default * templates return the input) * * @param kernelUsesSF * kernel setting of SF * @return whether scientific format (sig digits) should be used */ public boolean useScientific(boolean kernelUsesSF) { return forceSF || (kernelUsesSF && !forceNF); } /** * Convenience method instead of getStringType().equals() * * @param t * string type * @return true if this template uses given type equals */ public boolean hasType(StringType t) { return stringType.equals(t); } /** * @param type * string type * @param decimals * number of decimal places * @param allowMore * true to use kernel's precision, if it's higher * @return template */ public static StringTemplate printDecimals(StringType type, int decimals, boolean allowMore) { StringTemplate tpl = new StringTemplate("TemplateFor:" + type + ",Decimals:" + decimals + "," + allowMore); tpl.forceNF = true; tpl.allowMoreDigits = allowMore; tpl.setType(type); tpl.nf = FormatFactory.getPrototype().getNumberFormat(decimals); return tpl; } /** * @param type * string type * @param decimals * figures * @param allowMore * true to use kernel's precision, if it's higher * @return template with given parameters */ public static StringTemplate printFigures(StringType type, int decimals, boolean allowMore) { StringTemplate tpl = new StringTemplate("TemplateFor:" + type + ",Figures:" + decimals + "," + allowMore); tpl.forceSF = true; tpl.allowMoreDigits = allowMore; tpl.setType(type); tpl.sf = FormatFactory.getPrototype().getScientificFormat(decimals, 20, false); return tpl; } /** * Prints the number to full double precision without using E notation * * @param type * string type * @return template with given parameters */ public static StringTemplate fullFigures(StringType type) { StringTemplate tpl = new StringTemplate("FullFiguresFor:" + type); tpl.forceSF = true; tpl.allowMoreDigits = true; tpl.setType(type); // 308 doesn't seem to work for 1E-300, 350 seems OK tpl.sf = FormatFactory.getPrototype().getScientificFormat(16, 350, false); return tpl; } /** * Scientific Notation (eg 2.3 * 4 ^ 5) * * @param type * string type * @param decimals * figures * @param allowMore * true to use kernel's precision, if it's higher * @return template with given parameters */ public static StringTemplate printScientific(StringType type, int decimals, boolean allowMore) { StringTemplate tpl = new StringTemplate("TemplateForScientific:" + type + ",Decimals:" + decimals + "," + allowMore); tpl.forceSF = true; tpl.allowMoreDigits = allowMore; tpl.setType(type); tpl.sf = FormatFactory.getPrototype().getScientificFormat(decimals, 20, true); return tpl; } /** * Receives default SF and returns SF to be used * * @param sfk * default * @return SF to be used */ public ScientificFormatAdapter getSF(ScientificFormatAdapter sfk) { return sf == null || (allowMoreDigits && sfk.getSigDigits() > sf.getSigDigits()) ? sfk : sf; } /** * Receives default NF and returns NF to be used * * @param nfk * default * @return NF to be used */ public NumberFormatAdapter getNF(NumberFormatAdapter nfk) { return nf == null || (allowMoreDigits && nfk .getMaximumFractionDigits() > nf.getMaximumFractionDigits()) ? nfk : nf; } /** * Returns whether we need to localize commands * * @return true for localized, false for internal */ public boolean isPrintLocalizedCommandNames() { return localizeCmds; } /** * Receives default style and returns style that should be actually used * * @param coordStyle * Kernel.COORD_STYLE_* * @return new style */ public int getCoordStyle(int coordStyle) { return coordStyle; } /** * @return true if variable prefix should be used */ public boolean isUseTempVariablePrefix() { return usePrefix; } /** * Returns whether round hack is allowed for given number * * @param abs * absolute value of number * @param nf2 * kernel's number format * @param sf2 * kernel's scientific format * @param useSF * round to significant figuress or decimal places * @return factor to multiply (either 1 or 1+1E-15) */ public double getRoundHalfUpFactor(double abs, NumberFormatAdapter nf2, ScientificFormatAdapter sf2, boolean useSF) { int digits = useSF ? sf2.getSigDigits() : nf2.getMaximumFractionDigits(); // eg make sure 1.2 not displayed as 1.2000000000001 when rounding set // to 15sf if (digits >= 15) { return 1; } if (abs < 1000) { return ROUND_HALF_UP_FACTOR; } if (abs > 10E7) { return 1; } if (useSF) { if (getSF(sf2) != null && getSF(sf2).getSigDigits() < 10) { return ROUND_HALF_UP_FACTOR; } } else { if (getNF(nf2) != null && getNF(nf2).getMaximumFractionDigits() < 10) { return ROUND_HALF_UP_FACTOR; } } return 1; } /** * @return true if more digits than what is set by this template are allowed * in output */ public boolean allowMoreDigits() { return allowMoreDigits; } private static final double[] precisions = new double[] { 1, 1E-1, 1E-2, 1E-3, 1E-4, 1E-5, 1E-6, 1E-7, 1E-8, 1E-9, 1E-10, 1E-11, 1E-12, 1E-13, 1E-14, 1E-15, 1E-16 }; private boolean allowPiHack = true; private boolean supportsFractions = true; /** * Least positive number with given precision * * @param nf2 * kernel's number format * @return 10^(-number of digits) */ public double getPrecision(NumberFormatAdapter nf2) { int digits = getNF(nf2).getMaximumFractionDigits(); return digits <= 16 ? precisions[digits] : Math.pow(10, -digits); } /** * Objects in macros have two different labels. * * @return whether label within original (true) or current (false) * construction should be used */ public boolean isUseRealLabels() { return useRealLabels; } /** * @return copy of this template that prints real labels */ public StringTemplate deriveReal() { StringTemplate copy = copy(); copy.useRealLabels = true; return copy; } private StringTemplate copy() { StringTemplate result = new StringTemplate("CopyOf:" + name); result.stringType = stringType; result.nf = nf; result.sf = sf; result.usePrefix = usePrefix; result.allowMoreDigits = allowMoreDigits; result.printFormPI = printFormPI; result.internationalizeDigits = internationalizeDigits; result.useRealLabels = useRealLabels; result.localizeCmds = localizeCmds; result.forceNF = forceNF; result.forceSF = forceSF; result.supportsFractions = supportsFractions; return result; } @Override public String toString() { return name; } /** * Returns the label depending on the current print form. When sending * variables to the underlying CAS, we need to make sure that we don't * overwrite variable names there, so we add the prefix * ExpressionNodeConstants.GGBCAS_VARIABLE_PREFIX. * * @param label * raw label without prefixes * @return label depending on given string type */ public String printVariableName(final String label) { String ret; if (isUseTempVariablePrefix()) { ret = addTempVariablePrefix(label); } ret = printVariableName(getStringType(), label); if (ret != null && ret.length() == 1 && "l".equals(ret) && hasType(StringType.LATEX)) { ret = "\\ell"; } return ret; } final private String printVariableName(final StringType printForm, final String label) { switch (printForm) { case GIAC: // make sure we don't interfer with reserved names // or command names in the underlying CAS // see http://www.geogebra.org/trac/ticket/1051 return addTempVariablePrefix(label.replace("$", "")); default: // standard case return label; } } /** * Returns ExpressionNodeConstants.TMP_VARIABLE_PREFIX + label. * * important eg i -> ggbtmpvari, e -> ggbtmpvare so that they aren't * confused with the constants */ private String addTempVariablePrefix(final String label) { // keep x, y, z so that x^2+y^2=1 works in Giac if (getStringType().isGiac() && ("x".equals(label) || "y".equals(label) || "y'".equals(label) || "y''".equals(label) || "z".equals(label))) { return label; } StringBuilder sb = new StringBuilder(); // TMP_VARIABLE_PREFIX + label sb.append(Kernel.TMP_VARIABLE_PREFIX); // make sure gbtmpvarp' not interpreted as derivative // #3607 sb.append(label.replaceAll("'", "unicode39u")); return sb.toString(); } /** * @return copy of this, with string type set to StringType.MATHML */ public StringTemplate deriveMathMLTemplate() { if (stringType.equals(StringType.CONTENT_MATHML)) { return this; } StringTemplate ret = this.copy(); ret.setType(StringType.CONTENT_MATHML); return ret; } /** * @return copy of this, with string type set to StringType.LATEX */ public StringTemplate deriveLaTeXTemplate() { if (stringType.equals(StringType.LATEX)) { return this; } StringTemplate ret = this.copy(); ret.setType(StringType.LATEX); return ret; } /** * @return copy of this, with isNumeric() returning true */ public StringTemplate deriveNumericGiac() { if (this.numeric) { return this; } StringTemplate ret = this.copy(); ret.numeric = true; return ret; } /** * @return whether stringType is for a CAS (ie Giac) */ public boolean hasCASType() { return stringType.isGiac(); } /** * @param v * expression value * @return whether it's a vector (2D or 3D) */ protected boolean isNDvector(ExpressionValue v) { return v.evaluatesToNonComplex2DVector() || v.evaluatesTo3DVector(); } /** * @param l * left subtree * @param r * right subtree * @param leftStr * left subtree as string * @param rightStr * right subtree as string * @param valueForm * whether to show values rather than names * @return l+r as string */ public String plusString(ExpressionValue l, ExpressionValue r, String leftStr, String rightStr, boolean valueForm) { StringBuilder sb = new StringBuilder(); // make sure A:=(1,2) B:=(3,4) A+B works // MyVecNode wrapped in ExpressionNode ExpressionValue left = l.unwrap(); ExpressionValue right = r.unwrap(); final Operation operation = Operation.PLUS; switch (stringType) { case CONTENT_MATHML: MathmlTemplate.mathml(sb, "<plus/>", leftStr, rightStr); break; case GIAC: // don't use isNumberValue(), isListValue as those lead to an // evaluate() if (left.evaluatesToList() && (right.evaluatesToNumber(false) || right instanceof NumberValue)) { // eg {1,2,3} + 10 sb.append("map("); sb.append(leftStr); sb.append(",ggx->ggx+("); sb.append(rightStr); sb.append("))"); // don't use isNumberValue(), isListValue as those lead to an // evaluate() } else if ((left.evaluatesToNumber(false) || left instanceof NumberValue) && right.evaluatesToList()) { // eg 10 + {1,2,3} sb.append("map("); sb.append(rightStr); sb.append(",ggx->ggx+("); sb.append(leftStr); sb.append("))"); // instanceof VectorValue rather than isVectorValue() as // ExpressionNode can return true // don't use isNumberValue(), isListValue as those lead to an // evaluate() } else if ((left.evaluatesToNumber(false) || left instanceof NumberValue) && right.evaluatesToNonComplex2DVector()) { // Log.debug(leftStr+" "+left.getClass()); // Log.debug(rightStr+" "+right.getClass()); // eg 10 + (1,2) sb.append("point(real("); sb.append(rightStr); sb.append("[1])+"); sb.append(leftStr); sb.append(",im("); sb.append(rightStr); sb.append("[1])+"); sb.append(leftStr); sb.append(')'); // instanceof VectorValue rather than isVectorValue() as // ExpressionNode can return true } else if ((right.evaluatesToNumber(false) || right instanceof NumberValue) && left.evaluatesToNonComplex2DVector()) { // Log.debug(left.getClass()+" "+right.getClass()); // eg (1,2) + 10 sb.append("point(real("); sb.append(leftStr); sb.append("[1])+"); sb.append(rightStr); sb.append(",im("); sb.append(leftStr); sb.append("[1])+"); sb.append(rightStr); sb.append(')'); // don't use isNumberValue() as that leads to an evaluate() } else if ((left.evaluatesToNumber(false) || left instanceof NumberValue) && right.evaluatesTo3DVector()) { // Log.debug(left.getClass()+" "+right.getClass()); // eg 10 + (1,2,3) sb.append("(("); sb.append(rightStr); sb.append(")[0]+"); sb.append(leftStr); sb.append(",("); sb.append(rightStr); sb.append(")[1]+"); sb.append(leftStr); sb.append(",("); sb.append(rightStr); sb.append(")[2]+"); sb.append(leftStr); sb.append(')'); // don't use isNumberValue() as that leads to an evaluate() } else if (left.evaluatesTo3DVector() && (right.evaluatesToNumber(false) || right instanceof NumberValue)) { // Log.debug(left.getClass()+" "+right.getClass()); // eg (1,2,3) + 10 sb.append("(("); sb.append(leftStr); sb.append(")[0]+"); sb.append(rightStr); sb.append(",("); sb.append(leftStr); sb.append(")[1]+"); sb.append(rightStr); sb.append(",("); sb.append(leftStr); sb.append(")[2]+"); sb.append(rightStr); sb.append(')'); } else if (left.evaluatesToVectorNotPoint() && right.evaluatesToVectorNotPoint()) { // Log.debug(left.getClass()+" "+right.getClass()); // eg vectors (1,2)+(3,4) sb.append(leftStr); sb.append("+"); sb.append(rightStr); } else if (right instanceof MyVecNDNode && left instanceof MyVecNDNode) { MyVecNDNode leftVN = (MyVecNDNode) left; MyVecNDNode rightVN = (MyVecNDNode) right; // Log.debug(left.getClass()+" "+right.getClass()); boolean leftIsVector = leftVN.isCASVector(); boolean rightIsVector = rightVN.isCASVector(); if (leftIsVector && rightIsVector) { // Vector + Vector sb.append(leftStr); sb.append("+"); sb.append(rightStr); } else if (!leftIsVector && !rightIsVector) { // Point + Point sb.append("point("); sb.append(leftStr); sb.append("+"); sb.append(rightStr); sb.append(")"); } else { if (leftVN.getDimension() == 3 || rightVN.getDimension() == 3) { sb.append("point(xcoord("); sb.append(leftStr); sb.append(')'); sb.append("+xcoord("); sb.append(rightStr); sb.append("),ycoord("); sb.append(leftStr); sb.append(")+ycoord("); sb.append(rightStr); sb.append("),zcoord("); sb.append(leftStr); sb.append(")+zcoord("); sb.append(rightStr); sb.append("))"); } else { sb.append("point("); sb.append(leftStr); sb.append("+"); sb.append(rightStr); sb.append(")"); } } } else if (right.evaluatesToNonComplex2DVector() && left.evaluatesToNonComplex2DVector()) { // eg f: (x, y) = (3, 2) + t (5, 1) sb.append("point("); sb.append(leftStr); sb.append("+"); sb.append(rightStr); sb.append(')'); } else if (isNDvector(right) && isNDvector(left)) { // eg Evaluate[(1,2,3)+Vector[(10,20,30)]] // eg f: (x, y, z) = (3, 2, 1) + t (5, 1, -3) sb.append("point(xcoord("); sb.append(leftStr); sb.append(")+xcoord("); sb.append(rightStr); sb.append("),ycoord("); sb.append(leftStr); sb.append(")+ycoord("); sb.append(rightStr); sb.append("),zcoord("); sb.append(leftStr); sb.append(")+zcoord("); sb.append(rightStr); sb.append("))"); } else { // Log.debug("default method" + // left.getClass()+" "+right.getClass()); sb.append('('); sb.append(leftStr); sb.append(")+("); sb.append(rightStr); sb.append(')'); } break; default: // check for 0 if (valueForm) { if (ExpressionNode.isEqualString(left, 0, !valueForm)) { append(sb, rightStr, right, operation); break; } else if (ExpressionNode.isEqualString(right, 0, !valueForm)) { append(sb, leftStr, left, operation); break; } } int leftop = ExpressionNode.opID(left); if (left instanceof Equation || (leftop >= 0 && leftop < Operation.PLUS.ordinal())) { sb.append(leftBracket()); sb.append(leftStr); sb.append(rightBracket()); } else { sb.append(leftStr); } // we need parantheses around right text // if right is not a leaf expression or // it is a leaf GeoElement without a label (i.e. it is // calculated somehow) if (left.evaluatesToText() && (!right.isLeaf() || (right.isGeoElement() && !((GeoElement) right).isLabelSet()))) { if (stringType.equals(StringType.LATEX) && isInsertLineBreaks()) { sb.append(" \\-+ "); } else { sb.append(" + "); } sb.append(leftBracket()); sb.append(rightStr); sb.append(rightBracket()); } else { if (rightStr.charAt(0) == '-') { // convert + - to - if (stringType.equals(StringType.LATEX) && isInsertLineBreaks()) { sb.append(" \\-- "); } else { sb.append(" - "); } sb.append(rightStr.substring(1)); } else if (rightStr .startsWith(Unicode.RightToLeftUnaryMinusSign)) { // Arabic // convert // + // - // to // - if (stringType.equals(StringType.LATEX) && isInsertLineBreaks()) { sb.append(" \\-- "); } else { sb.append(" - "); } append(sb, rightStr.substring(3), right, Operation.PLUS); } else { if (stringType.equals(StringType.LATEX) && isInsertLineBreaks()) { sb.append(" \\-+ "); } else { sb.append(" + "); } append(sb, rightStr, right, Operation.PLUS); } } break; } return sb.toString(); } public String leftBracket() { /* * if (stringType.equals(StringType.LATEX)) return " \\left( "; else if * (stringType.equals(StringType.LIBRE_OFFICE)) return " left ( "; else * return "("; */ return left() + "("; } public String rightBracket() { /* * if (stringType.equals(StringType.LATEX)) return " \\right)"; else if * (stringType.equals(StringType.LIBRE_OFFICE)) return " right )"; else * return ")"; */ return right() + ")"; } public String leftSquareBracket() { return left() + "["; } public String rightSquareBracket() { return right() + "]"; } private String right() { if (stringType.equals(StringType.LATEX)) { return " \\right"; } else if (stringType.equals(StringType.LIBRE_OFFICE)) { return " right "; } else { return ""; } } private String left() { if (stringType.equals(StringType.LATEX)) { return " \\left"; } else if (stringType.equals(StringType.LIBRE_OFFICE)) { return " left "; } else { return ""; } } public String minusString(ExpressionValue l, ExpressionValue r, String leftStr, String rightStr, boolean valueForm, Localization loc) { // make sure A:=(1,2) B:=(3,4) A-B works // MyVecNode wrapped in ExpressionNode ExpressionValue left = l.unwrap(); ExpressionValue right = r.unwrap(); StringBuilder sb = new StringBuilder(); switch (stringType) { case CONTENT_MATHML: MathmlTemplate.mathml(sb, "<minus/>", leftStr, rightStr); break; case GIAC: if (left.evaluatesToList() && (right.evaluatesToNumber(false) || right instanceof NumberValue)) { // eg {1,2,3} + 10 sb.append("map("); sb.append(leftStr); sb.append(",ggx->ggx-("); sb.append(rightStr); sb.append("))"); } else if ((left.evaluatesToNumber(false) || left instanceof NumberValue) && right.evaluatesToList()) { // eg 10 + {1,2,3} sb.append("map("); sb.append(rightStr); sb.append(",ggx->"); sb.append(leftStr); sb.append("-ggx)"); } else if ((left.evaluatesToNumber(false) || left instanceof NumberValue) && right.evaluatesToNonComplex2DVector()) { // eg 10 - (1,2) sb.append("point("); sb.append(leftStr); sb.append("-real("); sb.append(rightStr); sb.append("[1])"); sb.append(","); sb.append(leftStr); sb.append("-im("); sb.append(rightStr); sb.append("[1]))"); } else if ((right.evaluatesToNumber(false) || right instanceof NumberValue) && left.evaluatesToNonComplex2DVector()) { // eg (1,2) - 10 sb.append("point(real("); sb.append(leftStr); sb.append("[1])-("); sb.append(rightStr); sb.append("),im("); sb.append(leftStr); sb.append("[1])-("); sb.append(rightStr); sb.append("))"); } else if ((left.evaluatesToNumber(false) || left instanceof NumberValue) && right.evaluatesTo3DVector()) { // eg 10 - (1,2,3) sb.append("("); sb.append(leftStr); sb.append("-("); sb.append(rightStr); sb.append(")[0],"); sb.append(leftStr); sb.append("-("); sb.append(rightStr); sb.append(")[1],"); sb.append(leftStr); sb.append("-("); sb.append(rightStr); sb.append(")[2])"); // don't use isNumberValue(), isListValue as those lead to an // evaluate() } else if (left.evaluatesTo3DVector() && (right.evaluatesToNumber(false) || right instanceof NumberValue)) { // eg (1,2,3) - 10 sb.append("(("); sb.append(leftStr); sb.append(")[0]-("); sb.append(rightStr); sb.append("),("); sb.append(leftStr); sb.append(")[1]-("); sb.append(rightStr); sb.append("),("); sb.append(leftStr); sb.append(")[2]-("); sb.append(rightStr); sb.append("))"); } else if (left.evaluatesToVectorNotPoint() && right.evaluatesToVectorNotPoint()) { // Log.debug(left.getClass()+" "+right.getClass()); // eg Vectors (1,2)-(3,4) sb.append('('); sb.append(leftStr); sb.append("-("); sb.append(rightStr); sb.append("))"); } else if (right instanceof MyVecNDNode && left instanceof MyVecNDNode) { MyVecNDNode leftVN = (MyVecNDNode) left; MyVecNDNode rightVN = (MyVecNDNode) right; // Log.debug(left.getClass()+" "+right.getClass()); boolean leftIsVector = leftVN.isCASVector(); boolean rightIsVector = rightVN.isCASVector(); if (leftIsVector && rightIsVector) { // Vector - Vector sb.append('('); sb.append(leftStr); sb.append("-("); sb.append(rightStr); sb.append("))"); } else if (!leftIsVector && !rightIsVector) { // Point + Point sb.append("point("); sb.append(leftStr); sb.append("-"); sb.append(rightStr); sb.append(")"); } else { if (leftVN.getDimension() == 3 || rightVN.getDimension() == 3) { sb.append("point(xcoord("); sb.append(leftStr); sb.append(")-xcoord("); sb.append(rightStr); sb.append("),ycoord("); sb.append(leftStr); sb.append(")-ycoord("); sb.append(rightStr); sb.append("),zcoord("); sb.append(leftStr); sb.append(")-zcoord("); sb.append(rightStr); sb.append("))"); } else { sb.append("point("); sb.append(leftStr); sb.append("-"); sb.append(rightStr); sb.append(")"); } } } else if (right.evaluatesToNonComplex2DVector() && left.evaluatesToNonComplex2DVector()) { // eg f: (x, y) = (3, 2) - t (5, 1) sb.append("point("); sb.append(leftStr); sb.append("-"); sb.append(rightStr); sb.append(')'); } else if (isNDvector(right) && isNDvector(left)) { // Log.debug(left.getClass()+" "+right.getClass()); // eg (1,2)-(3,4) // eg f: (x, y, z) = (3, 2, 1) - t (5, 1, -3) sb.append("point(xcoord("); sb.append(leftStr); sb.append(")-xcoord("); sb.append(rightStr); sb.append("),ycoord("); sb.append(leftStr); sb.append(")-ycoord("); sb.append(rightStr); sb.append("),zcoord("); sb.append(leftStr); sb.append(")-zcoord("); sb.append(rightStr); sb.append("))"); } else { sb.append('('); sb.append(leftStr); sb.append(")-("); sb.append(rightStr); sb.append(')'); } break; default: if (left instanceof Equation) { sb.append(leftBracket()); sb.append(leftStr); sb.append(rightBracket()); } else { sb.append(leftStr); } // check for 0 at right if (valueForm && rightStr.equals(loc.unicodeZero + "")) { break; } if (right.isLeaf() || (ExpressionNode .opID(right) >= Operation.MULTIPLY.ordinal())) { // not // +, // - if (rightStr.charAt(0) == '-') { // convert - - to + if (stringType.equals(StringType.LATEX) && isInsertLineBreaks()) { sb.append(" \\-+ "); } else { sb.append(" + "); } sb.append(rightStr.substring(1)); } else if (rightStr .startsWith(Unicode.RightToLeftUnaryMinusSign)) { // Arabic // convert // - // - // to // + if (stringType.equals(StringType.LATEX) && isInsertLineBreaks()) { sb.append(" \\-+ "); } else { sb.append(" + "); } sb.append(rightStr.substring(3)); } else { if (stringType.equals(StringType.LATEX) && isInsertLineBreaks()) { sb.append(" \\-- "); } else { sb.append(" - "); } sb.append(rightStr); } } else { // fix for changing height in Algebra View plus / minus if (stringType.equals(StringType.LATEX) && isInsertLineBreaks()) { sb.append(" \\-- "); } else { sb.append(" - "); } sb.append(leftBracket()); sb.append(rightStr); sb.append(rightBracket()); } break; } return sb.toString(); } public String multiplyString(ExpressionValue left, ExpressionValue right, String leftStr, String rightStr, boolean valueForm, Localization loc) { StringBuilder sb = new StringBuilder(); Operation operation = Operation.MULTIPLY; switch (stringType) { case CONTENT_MATHML: MathmlTemplate.mathml(sb, "<times/>", leftStr, rightStr); break; default: // check for 1 at left if (ExpressionNode.isEqualString(left, 1, !valueForm)) { append(sb, rightStr, right, operation); break; } // check for 1 at right else if (ExpressionNode.isEqualString(right, 1, !valueForm)) { append(sb, leftStr, left, operation); break; } // removed 0 handling due to problems with functions, // e.g 0 * x + 1 becomes 0 + 1 and no longer is a function // // check for 0 at left // else if (valueForm && isEqualString(left, 0, !valueForm)) { // sb.append("0"); // break; // } // // check for 0 at right // else if (valueForm && isEqualString(right, 0, !valueForm)) { // sb.append("0"); // break; // } // check for degree sign or 1degree or degree1 (eg for Arabic) else if ((rightStr.length() == 2 && ((rightStr.charAt(0) == Unicode.DEGREE_CHAR && rightStr.charAt(1) == (loc.unicodeZero + 1)) || (rightStr.charAt(1) == Unicode.DEGREE_CHAR && rightStr.charAt(0) == loc.unicodeZero + 1))) || rightStr.equals(Unicode.DEGREE)) { boolean rtl = loc.isRightToLeftDigits(this); if (rtl) { sb.append(Unicode.DEGREE); } if (!left.isLeaf()) { sb.append('('); // needed for eg (a+b)\u00b0 } sb.append(leftStr); if (!left.isLeaf()) { sb.append(')'); // needed for eg (a+b)\u00b0 } if (!rtl) { sb.append(Unicode.DEGREE); } break; } case LATEX: case LIBRE_OFFICE: boolean nounary = true; // vector * (matrix * vector) needs brackets; always use brackets // for internal templates if (!isPrintLocalizedCommandNames() || (left.evaluatesToList() && isNDvector(right))) { sb.append(leftBracket()); } // left wing if (left.isLeaf() || (ExpressionNode .opID(left) >= Operation.MULTIPLY.ordinal())) { // not // +, // - if (ExpressionNode.isEqualString(left, -1, !valueForm)) { // unary // minus nounary = false; sb.append('-'); } else { if (leftStr.startsWith(Unicode.RightToLeftUnaryMinusSign)) { // brackets needed for eg Arabic digits sb.append(Unicode.RightToLeftMark); sb.append(leftBracket()); sb.append(leftStr); sb.append(rightBracket()); sb.append(Unicode.RightToLeftMark); } else { sb.append(leftStr); } } } else { sb.append(leftBracket()); sb.append(leftStr); sb.append(rightBracket()); } // right wing int opIDright = ExpressionNode.opID(right); if (right.isLeaf() || (opIDright >= Operation.MULTIPLY.ordinal())) { // not // +, // - boolean showMultiplicationSign = false; boolean multiplicationSpaceNeeded = true; if (nounary) { switch (stringType) { case PGF: case PSTRICKS: case GEOGEBRA_XML: case GIAC: showMultiplicationSign = true; break; case LIBRE_OFFICE: case LATEX: // check if we need a multiplication sign, see #414 // digit-digit, e.g. 3 * 5 // digit-fraction, e.g. 3 * \frac{5}{2} char lastLeft = leftStr.charAt(leftStr.length() - 1); char firstRight = rightStr.charAt(0); showMultiplicationSign = // left is digit or ends with }, e.g. exponent, // fraction (StringUtil.isDigit(lastLeft) || (lastLeft == '}')) && // right is digit or fraction (StringUtil.isDigit(firstRight) || rightStr .startsWith("\\frac")); multiplicationSpaceNeeded = !(right instanceof MySpecialDouble && Unicode.DEGREE.equals( right.toString(defaultTemplate))); break; default: // GeoGebra syntax char firstLeft = leftStr.charAt(0); lastLeft = leftStr.charAt(leftStr.length() - 1); firstRight = rightStr.charAt(0); // check if we need a multiplication sign, see #414 // digit-digit, e.g. 3 * 5 showMultiplicationSign = Character.isDigit(lastLeft) && (StringUtil.isDigit(firstRight) // 3*E23AB can't be written 3E23AB || (rightStr.charAt(0) == 'E')); // check if we need a multiplication space: multiplicationSpaceNeeded = showMultiplicationSign; if (!multiplicationSpaceNeeded) { // check if we need a multiplication space: // it's needed except for number * character, // e.g. 23x // need to check start and end for eg A1 * A2 boolean leftIsNumber = left.isLeaf() && (StringUtil.isDigit(firstLeft) || (firstLeft == '-')) && StringUtil.isDigit(lastLeft); // check if we need a multiplication space: // all cases except number * character, e.g. 3x multiplicationSpaceNeeded = showMultiplicationSign || !(leftIsNumber && !Character.isDigit(firstRight)); } } if (stringType.equals(StringType.LATEX) && isInsertLineBreaks()) { sb.append("\\-"); } if (showMultiplicationSign) { sb.append(multiplicationSign()); } else if (multiplicationSpaceNeeded) { // space instead of multiplication sign sb.append(multiplicationSpace()); } } boolean rtlMinus; // show parentheses around these cases if (((rtlMinus = rightStr .startsWith(Unicode.RightToLeftUnaryMinusSign)) || (rightStr.charAt(0) == '-')) // 2 (-5) or -(-5) || (!nounary && !right.isLeaf() && (opIDright <= Operation.DIVIDE.ordinal() // -(x // * // a) // or // -(x // / // a) )) || (showMultiplicationSign && stringType.equals(StringType.GEOGEBRA))) // 3 // (5) { if (rtlMinus) { sb.append(Unicode.RightToLeftMark); } sb.append(leftBracket()); sb.append(rightStr); sb.append(rightBracket()); if (rtlMinus) { sb.append(Unicode.RightToLeftMark); } } else { // -1.0 * 5 becomes "-5" sb.append(rightStr); } } else { // right is + or - tree if (nounary) { switch (stringType) { case PGF: case PSTRICKS: case GEOGEBRA_XML: case GIAC: sb.append(multiplicationSign()); break; default: // space instead of multiplication sign sb.append(multiplicationSpace()); } } sb.append(leftBracket()); sb.append(rightStr); sb.append(rightBracket()); } // vector * (matrix * vector) needs brackets; always use brackets // for internal templates if (!isPrintLocalizedCommandNames() || (left.evaluatesToList() && isNDvector(right))) { sb.append(rightBracket()); } break; case GIAC: // Log.debug(left.getClass()+" "+right.getClass()); // Log.debug(leftStr+" "+rightStr); if (right instanceof ExpressionNode && ((ExpressionNode) right).getOperation().isInequality() && left.evaluatesToNumber(false)) { // eg 3(x<4) // MySpecialDouble shouldn't be negative, but just in case: boolean reverse = left.evaluateDouble() < 0; sb.append('('); sb.append(leftStr); sb.append(")*("); sb.append(expToString(((ExpressionNode) right).getLeft(), valueForm)); sb.append(')'); sb.append(op((ExpressionNode) right, reverse)); sb.append('('); sb.append(leftStr); sb.append(")*("); sb.append(expToString(((ExpressionNode) right).getRight(), valueForm)); sb.append(')'); } else if (left instanceof ExpressionNode && ((ExpressionNode) left).getOperation().isInequality() && right.evaluatesToNumber(false)) { // eg 3(x<4) // MySpecialDouble shouldn't be negative, but just in case: boolean reverse = right.evaluateDouble() < 0; sb.append('('); sb.append(rightStr); sb.append(")*("); sb.append(expToString(((ExpressionNode) left).getLeft(), valueForm)); sb.append(')'); sb.append(op((ExpressionNode) left, reverse)); sb.append('('); sb.append(rightStr); sb.append(")*("); sb.append(expToString(((ExpressionNode) left).getRight(), valueForm)); sb.append(')'); } else if (ExpressionNode.isEqualString(left, -1, !valueForm)) { sb.append("-("); sb.append(rightStr); sb.append(')'); } else { sb.append("("); sb.append(leftStr); sb.append(")*("); sb.append(rightStr); sb.append(")"); break; } break; } return sb.toString(); } protected String expToString(ExpressionValue v, boolean valueMode) { return valueMode ? v.toValueString(this) : v.toString(this); } private static String op(ExpressionNode right, boolean reverse) { switch (right.getOperation()) { case LESS: return reverse ? ">" : "<"; case LESS_EQUAL: return reverse ? ">=" : "<="; case GREATER_EQUAL: return reverse ? "<=" : ">="; case GREATER: return reverse ? "<" : ">"; } return null; } protected String multiplicationSign() { switch (stringType) { case LATEX: return " \\cdot "; case LIBRE_OFFICE: return " cdot "; case GEOGEBRA: return " "; // space for multiplication default: return " * "; } } protected String multiplicationSpace() { // wide space for multiplicatoin space in LaTeX return (stringType.equals(StringType.LATEX)) ? " \\; " : " "; } public void append(StringBuilder sb, String str, ExpressionValue ev, Operation op) { if (ev.isLeaf() || (ExpressionNode.opID(ev) >= op.ordinal()) && (!ExpressionNode.chainedBooleanOp(op) || !ExpressionNode .chainedBooleanOp(ev.wrap().getOperation()))) { sb.append(str); } else { sb.append(leftBracket()); sb.append(str); sb.append(rightBracket()); } } public String divideString(ExpressionValue left, ExpressionValue right, String leftStr, String rightStr, boolean valueForm) { StringBuilder sb = new StringBuilder(); switch (stringType) { case CONTENT_MATHML: MathmlTemplate.mathml(sb, "<divide/>", leftStr, rightStr); break; case LATEX: if ((leftStr.charAt(0) == '-') && (left.isLeaf() || (left instanceof ExpressionNode && ExpressionNode .isMultiplyOrDivide((ExpressionNode) left)))) { sb.append("-\\frac{"); sb.append(leftStr.substring(1)); sb.append("}{"); sb.append(rightStr); sb.append("}"); } else { sb.append("\\frac{"); sb.append(leftStr); sb.append("}{"); sb.append(rightStr); sb.append("}"); } break; case LIBRE_OFFICE: sb.append("{ "); sb.append(leftStr); sb.append(" } over { "); sb.append(rightStr); sb.append(" }"); break; case GIAC: sb.append("("); sb.append(leftStr); sb.append(")/("); sb.append(rightStr); sb.append(')'); break; default: // check for 1 in denominator // #5396 if (left.isLeaf() && ExpressionNode.isEqualString(right, 1, !valueForm)) { sb.append(leftStr); break; } // left wing // put parentheses around +, -, * if (left.isExpressionNode() && ((ExpressionNode) left) .getOperation() == Operation.MULTIPLY && !((ExpressionNode) left).hasBrackets() && ExpressionNode.isConstantDouble( ((ExpressionNode) left).getRight(), Math.PI)) { sb.append(leftStr); } else { append(sb, leftStr, left, Operation.DIVIDE); } sb.append(" / "); // right wing append(sb, rightStr, right, Operation.POWER); // not // +, // -, // *, // / } return sb.toString(); } public String notString(ExpressionValue left, String leftStr) { StringBuilder sb = new StringBuilder(); if (stringType.equals(StringType.CONTENT_MATHML)) { MathmlTemplate.mathml(sb, "<not/>", leftStr, null); } else { switch (stringType) { case CONTENT_MATHML: break; case LATEX: sb.append("\\neg "); break; case LIBRE_OFFICE: sb.append("neg "); break; default: sb.append(strNOT); } if (left.isLeaf()) { sb.append(leftStr); } else { sb.append(leftBracket()); sb.append(leftStr); sb.append(rightBracket()); } } return sb.toString(); } /** * @param sb * builder * @param string * binary op name * @param leftStr * first argument * @param rightStr * second argument */ public static void appendOp(StringBuilder sb, String string, String leftStr, String rightStr) { sb.append(string); sb.append('('); sb.append(leftStr); sb.append(','); sb.append(rightStr); sb.append(')'); } /** * @param left * left expression * @param right * right expression * @param leftStr * left string * @param rightStr * right string * @return leftStr || rightStr for this string type */ public String orString(ExpressionValue left, ExpressionValue right, String leftStr, String rightStr) { StringBuilder sb = new StringBuilder(); if (stringType.equals(StringType.CONTENT_MATHML)) { MathmlTemplate.mathml(sb, "<or/>", leftStr, rightStr); } else { append(sb, leftStr, left, Operation.OR); sb.append(' '); switch (stringType) { case LATEX: if (isInsertLineBreaks()) { sb.append("\\-"); } sb.append("\\vee"); break; case LIBRE_OFFICE: sb.append("or"); break; case GIAC: sb.append("||"); break; default: sb.append(strOR); } sb.append(' '); append(sb, rightStr, right, Operation.OR); // sb.append(rightStr); } return sb.toString(); } public String geqSign() { switch (getStringType()) { case LATEX: if (isInsertLineBreaks()) { return "\\-\\geq"; } return "\\geq"; case LIBRE_OFFICE: case GIAC: return ">="; default: return strGREATER_EQUAL; } } public String leqSign() { switch (getStringType()) { case LATEX: if (isInsertLineBreaks()) { return "\\-\\leq"; } return "\\leq"; case LIBRE_OFFICE: case GIAC: return "<="; default: return strLESS_EQUAL; } } /** * @return > for this string type */ public String greaterSign() { if (hasType(StringType.LATEX) && isInsertLineBreaks()) { return "\\->"; } return ">"; } /** * @return < for this string type */ public String lessSign() { if (hasType(StringType.LATEX) && isInsertLineBreaks()) { return "\\-<"; } return " < "; } public String strictSubsetSign() { switch (getStringType()) { case LATEX: if (isInsertLineBreaks()) { return "\\-\\subset"; } return "\\subset"; case LIBRE_OFFICE: return "subset"; default: return strIS_SUBSET_OF_STRICT; } } public String subsetSign() { switch (getStringType()) { case LATEX: if (isInsertLineBreaks()) { return "\\-\\subseteq"; } return "\\subseteq"; case LIBRE_OFFICE: return "subseteq"; default: return strIS_SUBSET_OF; } } public String notEqualSign() { switch (getStringType()) { case LATEX: if (isInsertLineBreaks()) { return "\\-\\neq"; } return "\\neq"; case LIBRE_OFFICE: return "<>"; case GIAC: return "!="; default: return strNOT_EQUAL; } } public String equalSign() { switch (getStringType()) { case LATEX: if (isInsertLineBreaks()) { return "\\-\\stackrel{ \\small ?}{=} "; } // #4068 changed from \stackrel{ \small ?}{=} return "\\stackrel{ \\small ?}{=} "; case LIBRE_OFFICE: case GIAC: return "="; default: return strEQUAL_BOOLEAN; } } public String perpSign() { switch (getStringType()) { case LATEX: if (isInsertLineBreaks()) { return "\\-\\perp"; } return "\\perp"; case LIBRE_OFFICE: return "ortho"; default: return strPERPENDICULAR; } } public String parallelSign() { switch (getStringType()) { case LATEX: if (isInsertLineBreaks()) { return "\\-\\parallel"; } return "\\parallel"; case LIBRE_OFFICE: return "parallel"; default: return strPARALLEL; } } public void infixBinary(StringBuilder sb, ExpressionValue left, ExpressionValue right, Operation operation, String leftStr, String rightStr, StringTemplate tpl, String operationString) { tpl.append(sb, leftStr, left, operation); sb.append(' '); sb.append(operationString); sb.append(' '); tpl.append(sb, rightStr, right, operation); } public String andIntervalString(ExpressionValue left, ExpressionValue right, String leftStr, String rightStr, boolean valueForm) { StringBuilder sb = new StringBuilder(); if (stringType.equals(StringType.CONTENT_MATHML) || stringType.isGiac()) { return andString(left, right, leftStr, rightStr); } if (right.isExpressionNode()) { sb.append(left.wrap().getCASstring(this, !valueForm)); sb.append(' '); switch (((ExpressionNode) right).getOperation()) { case LESS: sb.append(lessSign()); break; case LESS_EQUAL: sb.append(leqSign()); break; case GREATER: sb.append(greaterSign()); break; case EQUAL_BOOLEAN: sb.append(equalSign()); break; case NOT_EQUAL: sb.append(notEqualSign()); break; case GREATER_EQUAL: sb.append(geqSign()); break; case IS_SUBSET_OF: sb.append(subsetSign()); break; case IS_SUBSET_OF_STRICT: sb.append(strictSubsetSign()); break; case PARALLEL: sb.append(parallelSign()); break; case PERPENDICULAR: sb.append(perpSign()); break; default: Log.debug(((ExpressionNode) right).getOperation() + " invalid in chain"); } sb.append(' '); sb.append(((ExpressionNode) right).getRightTree().getCASstring(this, !valueForm)); return sb.toString(); } return andString(left, right, leftStr, rightStr); } public String andString(ExpressionValue left, ExpressionValue right, String leftStr, String rightStr) { StringBuilder sb = new StringBuilder(); if (stringType.equals(StringType.CONTENT_MATHML)) { MathmlTemplate.mathml(sb, "<and/>", leftStr, rightStr); } else if (stringType.isGiac()) { sb.append('('); sb.append(leftStr); sb.append(" && "); sb.append(rightStr); sb.append(')'); } else { append(sb, leftStr, left, Operation.AND); sb.append(' '); switch (stringType) { case LATEX: if (isInsertLineBreaks()) { sb.append("\\-"); } sb.append("\\wedge"); break; case LIBRE_OFFICE: sb.append("and"); break; case GIAC: sb.append("&&"); break; default: sb.append(strAND); } sb.append(' '); append(sb, rightStr, right, Operation.AND); } return sb.toString(); } @SuppressFBWarnings({ "SF_SWITCH_FALLTHROUGH", "missing break is deliberate" }) public String powerString(ExpressionValue left, ExpressionValue right, String leftStr, String rightStr, boolean valueForm) { StringBuilder sb = new StringBuilder(); /* * support for sin^2(x) for display, too slow and hacky if * (STRING_TYPE.equals(StringType.GEOGEBRA && * leftStr.startsWith("sin(")) { //&& "2".equals(rightStr)) { int index; * try { index = Integer.parseInt(rightStr); } catch * (NumberFormatException nfe) { index = Integer.MAX_VALUE; } * * if (index > 0 && index != Integer.MAX_VALUE) { sb.append("sin"); * sb.append(Unicode.numberToIndex(index)); * sb.append(leftStr.substring(3)); // everying except the "sin" break; * } * * }// */ if (stringType.equals(StringType.CONTENT_MATHML)) { MathmlTemplate.mathml(sb, "<power/>", leftStr, rightStr); } else { // everything else boolean finished = false; // support for sin^2(x) for LaTeX, eg FormulaText[] if (stringType.equals(StringType.LATEX) && left.isExpressionNode()) { switch (((ExpressionNode) left).getOperation()) { // #1592 case SIN: case COS: case TAN: case SEC: case CSC: case COT: case SINH: case COSH: case TANH: case SECH: case CSCH: case COTH: double indexD = right.evaluateDouble(); int index = (int) Math.round(indexD); // only positive integers // sin^-1(x) is arcsin // sin^-2(x) not standard notation if (!(Double.isInfinite(indexD) || Double.isNaN(indexD)) && (index > 0)) { String leftStrTrimmed = leftStr.trim(); int spaceIndex = leftStrTrimmed.trim().indexOf(' '); sb.append(leftStrTrimmed.substring(0, spaceIndex)); sb.append(" ^{"); sb.append(rightStr); sb.append("}"); // alternative using Unicode // sb.append(Unicode.numberToIndex(index)); // everything except the "\\sin " sb.append(leftStrTrimmed.substring(spaceIndex + 1)); finished = true; break; } default: // fall through } if (finished) { return sb.toString(); } } switch (stringType) { case GIAC: // if user types e^(ln(4.93)/1.14) // ie not Unicode.EULER_STRING // then it's ggbtmpvare here // Unicode.EULER_STRING is changed to just e // check for Unicode.EULER_STRING just in case if ("e".equals(leftStr) || Unicode.EULER_STRING.equals(leftStr)) { sb.append("exp("); sb.append(rightStr); sb.append(")"); break; } if (right.isExpressionNode() && ((ExpressionNode) right) .getOperation() == Operation.DIVIDE) { ExpressionNode enR = (ExpressionNode) right; // was simplify(surd, causes problems // GGB-321 sb.append("surd("); sb.append(leftStr); sb.append(','); // #4186: make sure we send value string to CAS sb.append(expToString(enR.getRight(), valueForm)); sb.append(")"); sb.append("^("); sb.append(expToString(enR.getLeft(), valueForm)); sb.append(")"); } else { sb.append("("); sb.append(leftStr); // Log.debug(left.evaluatesToList()); // Log.debug(left instanceof ListValue); // Log.debug(((ListValue)left).getListElement(0).evaluatesToList()); // if list && !matrix if (left.evaluatesToList() && left.getListDepth() != 2) { // make sure {1,2,3}^2 gives {1,4,9} rather than 14 sb.append(").^("); } else { sb.append(")^("); } sb.append(rightStr); sb.append(")"); } break; case LATEX: // checks if the basis is leaf and if so // omits the brackets if (left.isLeaf() && (leftStr.charAt(0) != '-')) { sb.append(leftStr); break; } // else fall through case LIBRE_OFFICE: default: /* * removed Michael Borcherds 2009-02-08 doesn't work eg m=1 g(x) * = (x - 1)^m (x - 3) * * * // check for 1 in exponent if (isEqualString(right, 1, * !valueForm)) { sb.append(leftStr); break; } // */ // left wing if ((leftStr.charAt(0) != '-') && // no unary (left.isLeaf() || ((ExpressionNode .opID(left) > Operation.POWER.ordinal()) && (ExpressionNode.opID(left) != Operation.EXP .ordinal())))) { // not +, -, *, /, ^, // e^x // we might need more brackets here #4764 sb.append(leftStr); } else { sb.append(leftBracket()); sb.append(leftStr); sb.append(rightBracket()); } break; } // right wing switch (stringType) { case LATEX: case LIBRE_OFFICE: // print x^1 as x if ("1".equals(rightStr)) { break; } sb.append('^'); // add brackets for eg a^b^c -> a^(b^c) boolean addParentheses = (right.isExpressionNode() && ((ExpressionNode) right).getOperation() .equals(Operation.POWER)); sb.append('{'); if (addParentheses) { sb.append(leftBracket()); } sb.append(rightStr); if (addParentheses) { sb.append(rightBracket()); } sb.append('}'); break; // rightStr already done in Giac case GIAC: break; case PSTRICKS: case PGF: case GEOGEBRA_XML: sb.append('^'); sb.append('('); sb.append(rightStr); sb.append(')'); break; default: if (right.isLeaf() || ((ExpressionNode .opID(right) > Operation.POWER.ordinal()) && (ExpressionNode.opID(right) != Operation.EXP .ordinal()))) { // not // +, // -, // *, // /, // ^, // e^x // Michael Borcherds 2008-05-14 // display powers over 9 as unicode superscript try { int i = Integer.parseInt(rightStr); exponent(sb, i); } catch (RuntimeException e) { sb.append('^'); sb.append(rightStr); } } else { sb.append('^'); sb.append(leftBracket()); sb.append(rightStr); sb.append(rightBracket()); } } } return sb.toString(); } private static void exponent(StringBuilder sb, int i0) { int i = i0; String index = ""; if (i < 0) { sb.append('\u207B'); // superscript minus sign i = -i; } if (i == 0) { sb.append('\u2070'); // zero } else { while (i > 0) { switch (i % 10) { default: case 0: index = "\u2070" + index; break; case 1: index = "\u00b9" + index; break; case 2: index = "\u00b2" + index; break; case 3: index = "\u00b3" + index; break; case 4: index = "\u2074" + index; break; case 5: index = "\u2075" + index; break; case 6: index = "\u2076" + index; break; case 7: index = "\u2077" + index; break; case 8: index = "\u2078" + index; break; case 9: index = "\u2079" + index; break; } i = i / 10; } } sb.append(index); } /** * Converts 5.1E-20 to 5.1*10^(-20) or 5.1 \cdot 10^{-20} depending on * current print form * * @param scientificStr * string in scientific notation * @param tpl * string template for output * @return formated string in scientific notation (except for Giac) */ public String convertScientificNotation(String scientificStr) { // for Giac, don't want 3E3 or 3*10^3 if (hasCASType()) { return convertScientificNotationGiac(scientificStr); } // in XML we write the original to avoid brackets and priority problems // #4764 if (hasType(StringType.GEOGEBRA_XML)) { return scientificStr; } StringBuilder sb = new StringBuilder(scientificStr.length() * 2); boolean Efound = false; for (int i = 0; i < scientificStr.length(); i++) { char ch = scientificStr.charAt(i); if (ch == 'E') { if (hasType(StringType.LATEX)) { sb.append(" \\cdot 10^{"); } else { sb.append("*10^("); } Efound = true; } else if (ch != '+') { sb.append(ch); } } if (Efound) { if (hasType(StringType.LATEX)) { sb.append("}"); } else { sb.append(")"); } } if (Efound && !this.isPrintLocalizedCommandNames()) { sb.insert(0, '('); sb.append(')'); } return sb.toString(); } /* * convert 3E3 to 3000 convert 3.33 to 333/100 convert 3E-3 to 3/1000 */ public String convertScientificNotationGiac(String originalString) { if (isNumeric()) { return originalString.replace('E', 'e'); } if (originalString.indexOf("E-") > -1) { String[] s = originalString.split("E-"); int i = Integer.parseInt(s[1]); int dotIndex = s[0].indexOf('.'); if (dotIndex > -1) { // eg 2.22E-100 i += s[0].length() - dotIndex - 1; s[0] = s[0].replace(".", ""); } // brackets just in case // 2^2.2E-1 is different to 2^22/100 return "(" + s[0] + "/1" + StringUtil.repeat('0', i) + ")"; } else if (originalString.indexOf("E") > -1) { String[] s = originalString.split("E"); int i = Integer.parseInt(s[1]); int dotIndex = s[0].indexOf('.'); if (dotIndex > -1) { // eg 2.22E100 need i=98 i -= s[0].length() - dotIndex - 1; s[0] = s[0].replace(".", ""); } // c: -5116.91572736879x^2 - 15556.1551078899x y - // 11496.6010564053y^2 // - 2234610.47543873x - 3369532.76964123y = 243297252.338397 // d: -19182.5685338018x^2 - 7781.50649444574x y - // 639.272043575625y^2 // - 5784784.13901330x - 1154843.72376044y = 435372862.870553 // Intersect[c, d] // eg 4.35372862870553E8 // need to add decimal point back in if (i < 0) { return s[0].substring(0, s[0].length() + i) + "." + s[0].substring(s[0].length() + i); } if (i == 0) { return s[0]; } return s[0] + StringUtil.repeat('0', i); } int dotIndex = originalString.indexOf('.'); if (dotIndex > -1) { // eg 4.4% if (originalString.endsWith("%")) { return "(" + originalString .substring(0, originalString.length() - 1) .replace(".", "") + "/1" + StringUtil.repeat('0', originalString.length() - dotIndex) + ")"; } // eg 2.22 -> (222/100) or 02.22 -> (222/100) return "(" + (originalString.replace(".", "")).replaceFirst("^0+(?!$)", "") + "/1" + StringUtil.repeat('0', originalString.length() - dotIndex - 1) + ")"; } // #5500 %], %%), %%%) have special meanings in Giac, eg [[a:=3%],a][1] // doesn't work // so wrap in brackets with a space just to make sure if (originalString.endsWith("%")) { return "(" + originalString + " )"; } // simple integer, no need to change return originalString; } public boolean isHideLHS() { return this.hideLHS; } public boolean allowPiHack() { return this.allowPiHack; } public static String[] printLimitedWidth(double decimal, Kernel kernel, String[] parts) { if (Math.abs(decimal) < 1E4 && (Math.abs(decimal) > 1E-4 || Kernel.isZero(decimal))) { parts[0] = kernel.format(decimal, defaultTemplate); parts[1] = null; return parts; } StringTemplate stl = StringTemplate.printScientific(StringType.GEOGEBRA, 2, false); // returns string like 3456E-7 String str = kernel.format(decimal, stl); String[] strs = str.split("E"); return strs; } public String escapeString(String string) { return string; } public boolean hasQuestionMarkForNaN() { return this.questionMarkForNaN; } public void leftCurlyBracket(StringBuilder sb) { if (hasType(StringType.LATEX)) { sb.append("\\left\\{"); } else { sb.append("{"); } } public void rightCurlyBracket(StringBuilder sb) { if (hasType(StringType.LATEX)) { sb.append("\\right\\}"); } else { sb.append("}"); } } /** * @return true if numeric rather than exact is required eg 1.23 for * NSolve[] in the CAS View rather than changing to 123/100 for most * other commands */ public boolean isNumeric() { return numeric; } public boolean supportsFractions() { return supportsFractions; } public StringTemplate deriveWithFractions(boolean fractions) { if (supportsFractions == fractions) { return this; } StringTemplate ret = this.copy(); ret.supportsFractions = fractions; return ret; } /** * * GGB-1106 * * @param s * number to pad * @param phantom * whether to make extra digits invisible (just for padding) * @param defaultDigits * default if not set explicitly for the GeoText * @param suffix * string suffix eg % * @return number padded, eg 1 padded to 1.00 (2dp) and wrapped in \texttt * (monospace font) */ public String padZerosAfterDecimalPoint(String s, boolean phantom, int defaultDigits, String suffix) { if (!StringUtil.isNumber(s)) { if (!phantom) { return wrapInTexttt(s); } return wrapInTexttt(s + wrapInPhantom( "." + StringUtil.string("0", defaultDigits), "")); } int length = s.length(); int pointPos = length - s.indexOf('.') - 1; int digits = nf == null ? defaultDigits : nf.getMaximumFractionDigits(); int zerosToAdd = digits - (pointPos); if (pointPos >= length) { if (digits == 0) { return wrapInTexttt(s + suffix); } if (phantom) { return wrapInTexttt(s + wrapInPhantom( "." + StringUtil.string("0", digits), suffix)); } return wrapInTexttt( s + "." + StringUtil.string("0", digits) + suffix); } if (zerosToAdd == 0) { return wrapInTexttt(s + suffix); } if (zerosToAdd < 0) { Log.error("problem in TableText[] " + s); } if (phantom) { return wrapInTexttt(s + wrapInPhantom( StringUtil.string("0", zerosToAdd), suffix)); } return wrapInTexttt(s + StringUtil.string("0", zerosToAdd) + suffix); } /** * @param s * input * @return input wrapped in texttt{} */ private static String wrapInTexttt(String s) { return "\\texttt{" + s + "}"; } /** * GGB-1106 \texttt{\phantom{}} doesn't seem to work, * \texttt{\phantom{\texttt{}}} seems OK * * @param s * string to wrap * @return wrapped string */ private static String wrapInPhantom(String s, String prefix) { return prefix + "\\phantom{\\texttt{" + s + "}}"; } }