/*
GeoGebra - Dynamic Mathematics for Everyone
http://www.geogebra.org
This file is part of GeoGebra.
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation.
*/
package org.geogebra.common.kernel.algos;
import org.geogebra.common.awt.GColor;
import org.geogebra.common.kernel.Construction;
import org.geogebra.common.kernel.StringTemplate;
import org.geogebra.common.kernel.commands.Commands;
import org.geogebra.common.kernel.geos.GeoElement;
import org.geogebra.common.kernel.geos.GeoList;
import org.geogebra.common.kernel.geos.GeoNumberValue;
import org.geogebra.common.kernel.geos.GeoText;
import org.geogebra.common.kernel.geos.TextProperties;
import org.geogebra.common.util.StringUtil;
import com.google.gwt.regexp.shared.MatchResult;
import com.google.gwt.regexp.shared.RegExp;
/**
* Algo for TableText[matrix], TableText[matrix,args]
*
*/
public class AlgoTableText extends AlgoElement implements TableAlgo {
private GeoList geoList; // input
private GeoText text; // output
private GeoText args; // input
private GeoList[] geoLists;
private StringBuffer sb = new StringBuffer();
private enum Alignment {
VERTICAL, HORIZONTAL
}
// style variables
private Alignment alignment;
private boolean verticalLines, horizontalLines;
private StringBuilder verticalLinesArray = null,
horizontalLinesArray = null;
private boolean verticalLinesJustEdges, horizontalLinesJustEdges;
private String justification, openBracket, closeBracket, openString,
closeString;
private int columns;
private int rows;
// getters for style variables (used by EuclidianStyleBar)
/**
* @return "v" or "h" for vertical / horizontal alignment
*/
public char getAlignment() {
return alignment.equals(Alignment.VERTICAL) ? 'v' : 'h';
}
/**
* @return whether table has vertical lines
*/
public boolean isVerticalLines() {
return verticalLines;
}
/**
* @return whether table has horizontal lines
*/
public boolean isHorizontalLines() {
return horizontalLines;
}
/**
* @return justification l, r or c (just of first column if they have
* different alignment)
*/
public String getJustification() {
if (justification == null) {
parseArgs();
}
return justification;
}
/**
* @return opening bracket for matrices
*/
public String getOpenSymbol() {
return openString;
}
/**
* @return closing bracket for matrices
*/
public String getCloseSymbol() {
return closeString;
}
/**
* @param cons
* construction
* @param label
* label for output
* @param geoList
* input matrix
* @param args
* table formating, see parseArgs()
*/
public AlgoTableText(Construction cons, String label, GeoList geoList,
GeoText args) {
this(cons, geoList, args);
text.setLabel(label);
}
/**
* @param cons
* construction
* @param geoList
* input matrix
* @param args
* table formating, see parseArgs()
*/
AlgoTableText(Construction cons, GeoList geoList, GeoText args) {
super(cons);
this.geoList = geoList;
this.args = args;
text = new GeoText(cons);
text.setAbsoluteScreenLoc(0, 0);
text.setAbsoluteScreenLocActive(true);
text.setLaTeX(true, false);
text.setIsTextCommand(true); // stop editing as text
setInputOutput();
compute();
// set sans-serif LaTeX default
text.setSerifFont(false);
}
@Override
public Commands getClassName() {
return Commands.TableText;
}
@Override
protected void setInputOutput() {
if (args == null) {
input = new GeoElement[1];
input[0] = geoList;
} else {
input = new GeoElement[2];
input[0] = geoList;
input[1] = args;
}
super.setOutputLength(1);
super.setOutput(0, text);
setDependencies(); // done by AlgoElement
}
/**
* @return resulting text
*/
public GeoText getResult() {
return text;
}
// get the lrc.a%p from middle of ABCDlrc.a%pEFGH
private final static RegExp matchLRC = RegExp
.compile("([^.%lrcap]*)([.%lrcap]*)([^.%lrcap]*)");
private void parseArgs() {
int tableColumns = geoList.size();
// set defaults
alignment = Alignment.HORIZONTAL;
verticalLines = false;
horizontalLines = false;
justification = "l";
// need an open & close together, so can't use ""
openBracket = "\\left.";
closeBracket = "\\right.";
if (args != null) {
String optionsStr = args.getTextString();
if (optionsStr.indexOf("v") > -1) {
alignment = Alignment.VERTICAL; // vertical table
}
int pos;
if ((pos = optionsStr.indexOf("|")) > -1
&& optionsStr.indexOf("||") == -1) {
verticalLines = true;
verticalLinesArray = new StringBuilder();
for (int i = pos + 1; i < optionsStr.length(); i++) {
char ch = charAt(optionsStr, i);
if (ch == '0' || ch == '1') {
verticalLinesArray.append(ch);
} else {
break;
}
}
// Log.debug("verticalLinesArray = "
// + verticalLinesArray.toString());
}
if ((pos = optionsStr.indexOf("_")) > -1) {
horizontalLines = true; // vertical table
horizontalLinesArray = new StringBuilder();
for (int i = pos + 1; i < optionsStr.length(); i++) {
char ch = charAt(optionsStr, i);
if (ch == '0' || ch == '1') {
horizontalLinesArray.append(ch);
} else {
break;
}
}
// Log.debug("horizontalLinesArray = "
// + horizontalLinesArray.toString());
}
verticalLinesJustEdges = optionsStr.indexOf("/") > -1;
horizontalLinesJustEdges = optionsStr.indexOf("-") > -1;
MatchResult matcher = matchLRC.exec(optionsStr);
justification = matcher.getGroup(2);
if ("".equals(justification)) {
justification = "l";
}
if (optionsStr.indexOf("||||") > -1) {
openBracket = "\\left| \\left|";
closeBracket = "\\right| \\right|";
openString = "||";
closeString = "||";
} else if (optionsStr.indexOf("||") > -1) {
openBracket = "\\left|";
closeBracket = "\\right|";
openString = "|";
closeString = "|";
} else if (optionsStr.indexOf('(') > -1) {
openBracket = "\\left(";
openString = "(";
} else if (optionsStr.indexOf('[') > -1) {
openBracket = "\\left[";
openString = "[";
} else if (optionsStr.indexOf('{') > -1) {
openBracket = "\\left\\{";
openString = "{";
}
if (optionsStr.indexOf(')') > -1) {
closeBracket = "\\right)";
closeString = ")";
} else if (optionsStr.indexOf(']') > -1) {
closeBracket = "\\right]";
closeString = "]";
} else if (optionsStr.indexOf('}') > -1) {
closeBracket = "\\right\\}";
closeString = "}";
}
} else if (geoList.get(tableColumns - 1).isGeoText()) {
// support for older files before the fix
GeoText options = (GeoText) geoList.get(tableColumns - 1);
String optionsStr = options.getTextString();
if (optionsStr.indexOf("h") > -1) {
alignment = Alignment.HORIZONTAL; // horizontal table
}
MatchResult matcher = matchLRC.exec(optionsStr);
justification = matcher.getGroup(2);
if ("".equals(justification)) {
justification = "l";
}
}
if ("\\left.".equals(openBracket) && "\\right.".equals(closeBracket)) {
openBracket = "";
closeBracket = "";
}
}
// default: return '1'
private static char charAt(Object o, int i) {
if (o == null) {
return '1';
}
String str = o.toString();
if (i < 0 || i >= str.length()) {
return '1';
}
return str.charAt(i);
}
@Override
public final void compute() {
columns = geoList.size();
if (!geoList.isDefined() || columns == 0) {
text.setTextString("");
return;
// throw new MyError(app, app.getError("InvalidInput"));
}
parseArgs();
// support for older files before the fix
if (geoList.get(columns - 1).isGeoText()) {
columns--;
}
if (columns == 0) {
text.setTextString("");
return;
// throw new MyError(app, app.getError("InvalidInput"));
}
if (geoLists == null || geoLists.length < columns) {
geoLists = new GeoList[columns];
}
rows = 0;
for (int c = 0; c < columns; c++) {
GeoElement geo = geoList.get(c);
if (!geo.isGeoList()) {
text.setTextString("");
return;
// throw new MyError(app,
// loc.getPlain("SyntaxErrorAisNotAList",geo.toValueString()));
}
geoLists[c] = (GeoList) geoList.get(c);
if (geoLists[c].size() > rows) {
rows = geoLists[c].size();
}
}
if (rows == 0) {
text.setTextString("");
return;
// throw new MyError(app, app.getError("InvalidInput"));
}
sb.setLength(0);
StringTemplate tpl = text.getStringTemplate();
latex(tpl);
// Application.debug(sb.toString());
text.setTextString(sb.toString());
}
private void latex(StringTemplate tpl) {
// surround in { } to make eg this work:
// FormulaText["\bgcolor{ff0000}"+TableText[matrix1]]
sb.append('{');
sb.append(openBracket);
sb.append("\\begin{array}{");
if (alignment == Alignment.VERTICAL) {
for (int c = 0; c < columns; c++) {
if (verticalLines && (!verticalLinesJustEdges || c == 0)
&& charAt(verticalLinesArray, c) == '1') {
sb.append("|");
}
sb.append(getJustificationLaTeX(c)); // "l", "r" or "c"
}
if (verticalLines && charAt(verticalLinesArray, columns) == '1') {
sb.append("|");
}
sb.append("}");
if (horizontalLines && charAt(horizontalLinesArray, 0) == '1') {
sb.append("\\hline ");
}
for (int r = 0; r < rows; r++) {
for (int c = 0; c < columns; c++) {
boolean finalCell = (c == columns - 1);
addCellLaTeX(c, r, finalCell, tpl, getJustification(c));
}
sb.append(" \\\\ "); // newline in LaTeX ie \\
if (horizontalLines
&& (!horizontalLinesJustEdges || r + 1 == rows)
&& charAt(horizontalLinesArray, r + 1) == '1') {
sb.append("\\hline ");
}
}
} else { // alignment == HORIZONTAL
for (int c = 0; c < rows; c++) {
if (verticalLines && (!verticalLinesJustEdges || c == 0)
&& charAt(verticalLinesArray, c) == '1') {
sb.append("|");
}
sb.append(getJustificationLaTeX(c)); // "l", "r" or "c"
}
if (verticalLines && charAt(verticalLinesArray, rows) == '1') {
sb.append("|");
}
sb.append("}");
if (horizontalLines && charAt(horizontalLinesArray, 0) == '1') {
sb.append("\\hline ");
}
// TableText[{11.1,322,3.11},{4,55,666,7777,88888},{6.11,7.99,8.01,9.81},{(1,2)},"c()"]
for (int c = 0; c < columns; c++) {
for (int r = 0; r < rows; r++) {
boolean finalCell = (r == rows - 1);
addCellLaTeX(c, r, finalCell, tpl, getJustification(c));
}
sb.append(" \\\\ "); // newline in LaTeX ie \\
if (horizontalLines
&& (!horizontalLinesJustEdges || c + 1 == columns)
&& charAt(horizontalLinesArray, c + 1) == '1') {
sb.append("\\hline ");
}
}
}
sb.append("\\end{array}");
sb.append(closeBracket);
// surround in { } to make eg this work:
// FormulaText["\bgcolor{ff0000}"+TableText[matrix1]]
sb.append('}');
}
/**
*
* @param c
* column/row
* @return 'l', 'r', 'c' for left/right/center
*/
private char getJustification(int c) {
if (c < 0 || c >= justification.length()) {
// default, if user passes "c" then use for all columns
return justification.charAt(0);
}
return justification.charAt(c);
}
/**
*
* @param c
* column/row
* @return 'l', 'r', 'c' for left/right/center
*/
private char getJustificationLaTeX(int c) {
char j = getJustification(c);
switch (j) {
case 'r':
case 'c':
case 'l':
return j;
// for 'a', '.', '%"
default:
return 'r';
}
}
private void addCellLaTeX(int c, int r, boolean finalCell,
StringTemplate tpl, char justification1) {
if (geoLists[c].size() > r) { // check list has an element at this
// position
GeoElement geo1 = geoLists[c].get(r);
GColor col = geo1.getObjectColor();
GColor bgCol = geo1.getBackgroundColor();
// check isLabelSet() so that eg TableText[{{1, 2, 3}}] isn't green
if (GColor.BLACK.equals(col) || !geo1.isLabelSet()) {
col = null;
}
if (bgCol != null
&& (!geo1.isLabelSet() || bgCol.getAlpha() == 0)) {
bgCol = null;
}
if (bgCol != null) {
sb.append("\\cellcolor{#");
sb.append(StringUtil.toHexString(bgCol));
sb.append("}{");
}
if (col != null) {
sb.append("\\textcolor{#");
sb.append(StringUtil.toHexString(col));
sb.append("}{");
}
// replace " " and "" with a hard space (allow blank columns/rows)
String text1 = geo1.toLaTeXString(false, tpl);
if (geo1.isGeoText() && !((GeoText) geo1).isLaTeX()) {
text1 = text1.replace("$", "\\dollar");
}
switch (justification1) {
default:
// do nothing
break;
case '.':
case 'a':
if (geo1 instanceof GeoNumberValue) {
double num = ((GeoNumberValue) geo1).getDouble();
text1 = kernel.format(num, tpl);
}
text1 = tpl.padZerosAfterDecimalPoint(text1,
justification1 == '.', kernel.getPrintDecimals(), "");
break;
case '%':
case 'p':
if (geo1 instanceof GeoNumberValue) {
double num = ((GeoNumberValue) geo1).getDouble();
String numStr = kernel.format(num * 100, tpl);
text1 = tpl.padZerosAfterDecimalPoint(numStr,
justification1 == '%', kernel.getPrintDecimals(),
"%");
}
}
if (" ".equals(text1) || "".equals(text1)) {
text1 = "\\;"; // problem with JLaTeXMath, was "\u00a0";
}
// make sure latex isn't wrapped in \text{}
if (((geo1 instanceof TextProperties
&& !((TextProperties) geo1).isLaTeXTextCommand())
&& (!(geo1 instanceof GeoText)
|| !((GeoText) geo1).isLaTeX()))
// check for "raw" LaTeX
// eg TableText[{"\frac{2}{3}","2","3"},{"4","5","6"}]
&& text1.indexOf(" ") > -1 && text1.indexOf("^") == -1
&& text1.indexOf("{") == -1 && text1.indexOf("}") == -1
&& text1.indexOf("+") == -1 && text1.indexOf("-") == -1
&& text1.indexOf("=") == -1 && text1.indexOf("\\") == -1
) {
sb.append("\\text{"); // preserve spaces
sb.append(text1);
sb.append("}");
} else {
sb.append(text1);
}
if (col != null) {
sb.append('}');
}
if (bgCol != null) {
sb.append('}');
}
}
if (!finalCell)
{
sb.append("&"); // separate columns
}
}
@Override
public boolean isLaTeXTextCommand() {
return true;
}
}