package org.geogebra.common.io.latex; import org.geogebra.common.util.debug.Log; import org.geogebra.common.util.lang.Unicode; import com.himamis.retex.editor.share.model.MathArray; import com.himamis.retex.editor.share.model.MathCharacter; import com.himamis.retex.editor.share.model.MathComponent; import com.himamis.retex.editor.share.model.MathFormula; import com.himamis.retex.editor.share.model.MathFunction; import com.himamis.retex.editor.share.model.MathSequence; import com.himamis.retex.editor.share.serializer.Serializer; /** * Serializes internal formulas representation into GeoGebra string * */ public class GeoGebraSerializer implements Serializer { @Override public String serialize(MathFormula formula) { MathSequence sequence = formula.getRootComponent(); StringBuilder stringBuilder = new StringBuilder(); serialize(sequence, stringBuilder); return stringBuilder.toString(); } private static void serialize(MathComponent mathComponent, MathSequence parent, int index, StringBuilder stringBuilder) { if (mathComponent instanceof MathCharacter) { serialize((MathCharacter) mathComponent, parent, index, stringBuilder); } else if (mathComponent instanceof MathFunction) { serialize((MathFunction) mathComponent, stringBuilder); } else if (mathComponent instanceof MathArray) { serialize((MathArray) mathComponent, stringBuilder); } else if (mathComponent instanceof MathSequence) { serialize((MathSequence) mathComponent, stringBuilder); } } /** * @param c * math formula fragment * @return string */ public static String serialize(MathComponent c) { StringBuilder sb = new StringBuilder(); GeoGebraSerializer.serialize(c, null, 0, sb); return sb.toString(); } private static void serialize(MathCharacter mathCharacter, MathSequence parent, int index, StringBuilder stringBuilder) { if (mathCharacter.getUnicode() == MathCharacter.ZERO_SPACE) { if (parent != null && index + 1 < parent.size()) { if (parent.getArgument(index + 1) instanceof MathArray) { stringBuilder.append(" "); } } return; } stringBuilder.append(mathCharacter.getUnicode()); } private static void serialize(MathFunction mathFunction, StringBuilder stringBuilder) { String mathFunctionName = mathFunction.getName(); if ("^".equals(mathFunctionName)) { stringBuilder.append(mathFunctionName + '('); serialize(mathFunction.getArgument(0), stringBuilder); stringBuilder.append(')'); } else if ("_".equals(mathFunction.getName())) { stringBuilder.append(mathFunctionName + '{'); serialize(mathFunction.getArgument(0), stringBuilder); // a_{1}sin(x) should be a_{1} sin(x) stringBuilder.append("}"); } else if ("frac".equals(mathFunctionName)) { stringBuilder.append('('); serialize(mathFunction.getArgument(0), stringBuilder); stringBuilder.append(")/("); serialize(mathFunction.getArgument(1), stringBuilder); stringBuilder.append(")"); } else if ("sqrt".equals(mathFunctionName)) { maybeInsertTimes(mathFunction, stringBuilder); stringBuilder.append("sqrt("); serialize(mathFunction.getArgument(0), stringBuilder); stringBuilder.append(')'); } else if ("nroot".equals(mathFunctionName)) { maybeInsertTimes(mathFunction, stringBuilder); stringBuilder.append("nroot("); serialize(mathFunction.getArgument(1), stringBuilder); stringBuilder.append(","); serialize(mathFunction.getArgument(0), stringBuilder); stringBuilder.append(')'); } // Strict control of available functions is needed, so that SUM/ and // Prod doesn't work else if ("sum".equals(mathFunctionName)) { stringBuilder.append("Sum"); serializeArgs(mathFunction, stringBuilder, new int[] { 3, 0, 1, 2 }); } else if ("prod".equals(mathFunctionName)) { stringBuilder.append("Product"); serializeArgs(mathFunction, stringBuilder, new int[] { 3, 0, 1, 2 }); } else if ("int".equals(mathFunctionName)) { stringBuilder.append("Integral"); serializeArgs(mathFunction, stringBuilder, new int[] { 2, 0, 1 }); } else if ("lim".equals(mathFunctionName)) { stringBuilder.append("Limit"); serializeArgs(mathFunction, stringBuilder, new int[] { 2, 3, 0, 1 }); } else { // some general function maybeInsertTimes(mathFunction, stringBuilder); stringBuilder.append(mathFunctionName); stringBuilder.append('('); for (int i = 0; i < mathFunction.size(); i++) { serialize(mathFunction.getArgument(i), stringBuilder); stringBuilder.append(','); } if (mathFunction.size() > 0) { stringBuilder.deleteCharAt(stringBuilder.length() - 1); } stringBuilder.append(')'); } } private static void serializeArgs(MathFunction mathFunction, StringBuilder stringBuilder, int[] order) { for (int i = 0; i < order.length; i++) { stringBuilder.append(i == 0 ? "((" : ",("); serialize(mathFunction.getArgument(order[i]), stringBuilder); stringBuilder.append(")"); } stringBuilder.append(")"); } private static void maybeInsertTimes(MathFunction mathFunction, StringBuilder stringBuilder) { MathSequence mathSequence = mathFunction.getParent(); if (mathSequence != null && mathFunction.getParentIndex() > 0) { MathComponent mathComponent = mathSequence .getArgument(mathFunction.getParentIndex() - 1); if (mathComponent instanceof MathCharacter) { MathCharacter mathCharacter = (MathCharacter) mathComponent; if (mathCharacter.isCharacter() && mathCharacter .getUnicode() != MathCharacter.ZERO_SPACE) { stringBuilder.append("*"); } } if (mathComponent instanceof MathFunction) { MathFunction mathCharacter = (MathFunction) mathComponent; if ("_".equals(mathCharacter.getName())) { stringBuilder.append("*"); } } } } private static void serialize(MathArray mathArray, StringBuilder stringBuilder) { String open = mathArray.getOpen().getKey() + ""; String close = mathArray.getClose().getKey() + ""; String field = mathArray.getField().getKey() + ""; String row = mathArray.getRow().getKey() + ""; if ((Unicode.LFLOOR + "").equals(open)) { open = "floor("; close = ")"; } else if ((Unicode.LCEIL + "").equals(open)) { open = "ceil("; close = ")"; } if (mathArray.isMatrix()) { stringBuilder.append(open); } for (int i = 0; i < mathArray.rows(); i++) { stringBuilder.append(open); for (int j = 0; j < mathArray.columns(); j++) { serialize(mathArray.getArgument(i, j), stringBuilder); stringBuilder.append(field); } stringBuilder.deleteCharAt(stringBuilder.length() - field.length()); stringBuilder.append(close); stringBuilder.append(row); } stringBuilder.deleteCharAt(stringBuilder.length() - row.length()); if (mathArray.isMatrix()) { stringBuilder.append(close); } } private static void serialize(MathSequence mathSequence, StringBuilder stringBuilder) { if (mathSequence == null) { return; } for (int i = 0; i < mathSequence.size(); i++) { serialize(mathSequence.getArgument(i), mathSequence, i, stringBuilder); } } /** * @param formula * original formula * @return formula after stringify + parse */ public static MathFormula reparse(MathFormula formula) { Parser parser = new Parser(formula.getMetaModel()); MathFormula formula1 = null; try { formula1 = parser.parse(serialize(formula.getRootComponent())); } catch (ParseException e) { Log.warn("Problem parsing: " + formula.getRootComponent()); e.printStackTrace(); } return formula1 == null ? formula : formula1; } }