package org.geogebra.common.util;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Locale;
import java.util.Set;
import java.util.Stack;
import java.util.TreeSet;
import org.geogebra.common.awt.GColor;
import org.geogebra.common.awt.GFont;
import org.geogebra.common.kernel.Kernel;
import org.geogebra.common.kernel.StringTemplate;
import org.geogebra.common.kernel.arithmetic.MyDouble;
import org.geogebra.common.main.Localization;
import org.geogebra.common.util.debug.Log;
import org.geogebra.common.util.lang.Unicode;
public class StringUtil {
/**
* @param data
* to convert
* @return data as a hex String
*/
public static String convertToHex(byte[] data) {
StringBuilder buf = new StringBuilder();
for (int i = 0; i < data.length; i++) {
buf.append(Character.forDigit((data[i] >> 4) & 0xF, 16));
buf.append(Character.forDigit((data[i] & 0xF), 16));
}
return buf.toString();
}
/**
* converts Color to hex String with RGB values
*
* @return
*/
final public static String toHexString(char c) {
int i = c + 0;
StringBuilder hexSB = new StringBuilder(8);
hexSB.append("\\u");
hexSB.append(hexChar[(i & 0xf000) >>> 12]);
hexSB.append(hexChar[(i & 0x0f00) >> 8]); // look up low nibble char
hexSB.append(hexChar[(i & 0xf0) >>> 4]);
hexSB.append(hexChar[i & 0x0f]); // look up low nibble char
return hexSB.toString();
}
final public static String toHexString(int i) {
StringBuilder hexSB = new StringBuilder(16);
hexSB.append(hexChar[(i & 0xf0000000) >>> 28]);
hexSB.append(hexChar[(i & 0xf000000) >>> 24]);
hexSB.append(hexChar[(i & 0xf00000) >>> 20]);
hexSB.append(hexChar[(i & 0xf0000) >>> 16]);
hexSB.append(hexChar[(i & 0xf000) >>> 12]);
hexSB.append(hexChar[(i & 0x0f00) >> 8]);
hexSB.append(hexChar[(i & 0xf0) >>> 4]);
hexSB.append(hexChar[i & 0x0f]);
return hexSB.toString();
}
final public static String toHexString(GColor col) {
byte r = (byte) col.getRed();
byte g = (byte) col.getGreen();
byte b = (byte) col.getBlue();
return toHexString(r, g, b);
}
final public static String toHexString(byte r, byte g, byte b) {
StringBuilder hexSB = new StringBuilder(8);
// RED
hexSB.append(hexChar[(r & 0xf0) >>> 4]);
// look up high nibble char
hexSB.append(hexChar[r & 0x0f]); // look up low nibble char
// GREEN
hexSB.append(hexChar[(g & 0xf0) >>> 4]);
// look up high nibble char
hexSB.append(hexChar[g & 0x0f]); // look up low nibble char
// BLUE
hexSB.append(hexChar[(b & 0xf0) >>> 4]);
// look up high nibble char
hexSB.append(hexChar[b & 0x0f]); // look up low nibble char
return hexSB.toString();
}
final public static String toHexString(String s) {
StringBuilder sb = new StringBuilder(s.length() * 6);
for (int i = 0; i < s.length(); i++) {
sb.append(toHexString(s.charAt(i)));
}
return sb.toString();
}
// table to convert a nibble to a hex char.
private static char[] hexChar = { '0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
public static String toHTMLString(String title) {
return toHTMLString(title, true);
}
/**
* Converts the given unicode string to an html string where special
* characters are converted to <code>xx;</code> sequences (xxx is the
* unicode value of the character)
*
* @author Markus Hohenwarter
*/
final public static String toHTMLString(String str, boolean encodeLTGT) {
if (str == null) {
return null;
}
StringBuilder sb = new StringBuilder();
// convert every single character and append it to sb
int len = str.length();
for (int i = 0; i < len; i++) {
char c = str.charAt(i);
int code = c;
// standard characters have code 32 to 126
if ((code >= 32 && code <= 126)) {
if (!encodeLTGT) {
sb.append(c);
} else {
switch (code) {
case 60:
sb.append("<");
break; // <
case 62:
sb.append(">");
break; // >
default:
// do not convert
sb.append(c);
}
}
}
// special characters
else {
switch (code) {
case 10:
case 13: // replace LF or CR with <br/>
sb.append("<br/>\n");
break;
case 9: // replace TAB with space
sb.append(" "); // space
break;
default:
// convert special character to escaped HTML
sb.append("");
sb.append(code);
sb.append(';');
}
}
}
return sb.toString();
}
/**
* Converts the given unicode string to a string where special characters
* are converted to <code>encoding;</code> sequences . The resulting
* string can be used in XML files.
*/
public static String encodeXML(String str) {
StringBuilder sb = new StringBuilder(str.length());
encodeXML(sb, str);
return sb.toString();
}
/**
* Converts the given unicode string to a string where special characters
* are converted to <code>encoding;</code> sequences . The resulting
* string can be used in XML files.
*/
public static void encodeXML(StringBuilder sb, String str) {
if (str == null) {
return;
}
// convert every single character and append it to sb
int len = str.length();
for (int i = 0; i < len; i++) {
char c = str.charAt(i);
if (c <= '\u001f') {
// #2399 all apart from U+0009, U+000A, U+000D are invalid in
// XML
// none should appear anyway, but encode to be safe
// eg
sb.append("");
sb.append(((int) c));
sb.append(';');
if (c != '\n' && c != 13) {
Log.warn("Control character being written to XML: "
+ ((int) c));
}
} else {
switch (c) {
case '>':
sb.append(">");
break;
case '<':
sb.append("<");
break;
case '"':
sb.append(""");
break;
case '\'':
sb.append("'");
break;
case '&':
sb.append("&");
break;
default:
sb.append(c);
}
}
}
}
/**
* Default implementation does not work, overriden in desktop TODO make sure
* we override this in Web as well
*
* @param c
* character
* @return whether it's left to right Unicode character
*/
protected boolean isRightToLeftChar(char c) {
return false;
}
private static StringUtil prototype;
private static final Object lock = new Object();
public static StringUtil getPrototype() {
return prototype;
}
public static void setPrototypeIfNull(StringUtil p) {
synchronized (lock) {
if (prototype == null) {
prototype = p;
}
}
}
/**
* Replaces special unicode letters (e.g. greek letters) in str by LaTeX
* strings.
*/
public static synchronized String toLaTeXString(String str,
boolean convertGreekLetters) {
int length = str.length();
sbReplaceExp.setLength(0);
char c = 0;
char previousChar;
for (int i = 0; i < length; i++) {
previousChar = c;
c = str.charAt(i);
// Guy Hed 30.8.2009
// Fix Hebrew 'undefined' problem in Latex text.
if (prototype.isRightToLeftChar(c)) {
int j = i;
while (j < length && (prototype.isRightToLeftChar(str.charAt(j))
|| str.charAt(j) == '\u00a0')) {
j++;
}
for (int k = j - 1; k >= i; k--) {
sbReplaceExp.append(str.charAt(k));
}
sbReplaceExp.append(' ');
i = j - 1;
continue;
}
// Guy Hed 30.8.2009
switch (c) {
/*
* case '(': sbReplaceExp.append("\\left("); break;
*
* case ')': sbReplaceExp.append("\\right)"); break;
*/
case '%': // % -> \%
if (previousChar != '\\') {
sbReplaceExp.append("\\");
}
sbReplaceExp.append("%");
break;
/*
* not needed for JLaTeXMath and in fact it doesn't work inside
* \text{} // Exponents // added by Loic Le Coq 2009/11/04 case
* '\u2070': // ^0 sbReplaceExp.append("^0"); break;
*
* case '\u00b9': // ^1 sbReplaceExp.append("^1"); break; // end
* Loic case '\u00b2': // ^2 sbReplaceExp.append("^2"); break;
*
* case '\u00b3': // ^3 sbReplaceExp.append("^3"); break;
*
* case '\u2074': // ^4 sbReplaceExp.append("^4"); break;
*
* case '\u2075': // ^5 sbReplaceExp.append("^5"); break;
*
* case '\u2076': // ^6 sbReplaceExp.append("^6"); break; // added
* by Loic Le Coq 2009/11/04 case '\u2077': // ^7
* sbReplaceExp.append("^7"); break;
*
* case '\u2078': // ^8 sbReplaceExp.append("^8"); break;
*
* case '\u2079': // ^9 sbReplaceExp.append("^9"); break; // end
* Loic Le Coq
*/
default:
if (!convertGreekLetters) {
sbReplaceExp.append(c);
} else {
switch (c) {
// greek letters
case Unicode.alpha:
sbReplaceExp.append("\\alpha");
break;
case Unicode.beta:
sbReplaceExp.append("\\beta");
break;
case Unicode.gamma:
sbReplaceExp.append("\\gamma");
break;
case Unicode.delta:
sbReplaceExp.append("\\delta");
break;
case Unicode.epsilon:
sbReplaceExp.append("\\varepsilon");
break;
case Unicode.zeta:
sbReplaceExp.append("\\zeta");
break;
case Unicode.eta:
sbReplaceExp.append("\\eta");
break;
case Unicode.theta:
sbReplaceExp.append("\\theta");
break;
case Unicode.iota:
sbReplaceExp.append("\\iota");
break;
case Unicode.kappa:
sbReplaceExp.append("\\kappa");
break;
case Unicode.lambda:
sbReplaceExp.append("\\lambda");
break;
case Unicode.mu:
sbReplaceExp.append("\\mu");
break;
case Unicode.nu:
sbReplaceExp.append("\\nu");
break;
case Unicode.xi:
sbReplaceExp.append("\\xi");
break;
case Unicode.omicron:
sbReplaceExp.append("\\omicron");
break;
case Unicode.pi:
sbReplaceExp.append("\\pi");
break;
case Unicode.rho:
sbReplaceExp.append("\\rho");
break;
case Unicode.sigma:
sbReplaceExp.append("\\sigma");
break;
case Unicode.tau:
sbReplaceExp.append("\\tau");
break;
case Unicode.upsilon:
sbReplaceExp.append("\\upsilon");
break;
case Unicode.phi_symbol:
sbReplaceExp.append("\\phi");
break;
case Unicode.phi:
sbReplaceExp.append("\\varphi");
break;
case Unicode.chi:
sbReplaceExp.append("\\chi");
break;
case Unicode.psi:
sbReplaceExp.append("\\psi");
break;
case Unicode.omega:
sbReplaceExp.append("\\omega");
break;
// GREEK upper case letters
case Unicode.Alpha:
sbReplaceExp.append("\\Alpha");
break;
case Unicode.Beta:
sbReplaceExp.append("\\Beta");
break;
case Unicode.Gamma:
sbReplaceExp.append("\\Gamma");
break;
case Unicode.Delta:
sbReplaceExp.append("\\Delta");
break;
case Unicode.Epsilon:
sbReplaceExp.append("\\Epsilon");
break;
case Unicode.Zeta:
sbReplaceExp.append("\\Zeta");
break;
case Unicode.Eta:
sbReplaceExp.append("\\Eta");
break;
case Unicode.Theta:
sbReplaceExp.append("\\Theta");
break;
case Unicode.Iota:
sbReplaceExp.append("\\Iota");
break;
case Unicode.Kappa:
sbReplaceExp.append("\\Kappa");
break;
case Unicode.Lambda:
sbReplaceExp.append("\\Lambda");
break;
case Unicode.Mu:
sbReplaceExp.append("\\Mu");
break;
case Unicode.Nu:
sbReplaceExp.append("\\Nu");
break;
case Unicode.Xi:
sbReplaceExp.append("\\Xi");
break;
case Unicode.Omicron:
sbReplaceExp.append("\\Omicron");
break;
case Unicode.Pi:
sbReplaceExp.append("\\Pi");
break;
case Unicode.Rho:
sbReplaceExp.append("\\Rho");
break;
case Unicode.Sigma:
sbReplaceExp.append("\\Sigma");
break;
case Unicode.Tau:
sbReplaceExp.append("\\Tau");
break;
case Unicode.Upsilon:
sbReplaceExp.append("\\Upsilon");
break;
case Unicode.Phi:
sbReplaceExp.append("\\Phi");
break;
case Unicode.Chi:
sbReplaceExp.append("\\Chi");
break;
case Unicode.Psi:
sbReplaceExp.append("\\Psi");
break;
case Unicode.Omega:
sbReplaceExp.append("\\Omega");
break;
default:
sbReplaceExp.append(c);
}
}
}
}
return sbReplaceExp.toString();
}
/*
* returns a string with n instances of s eg string("hello",2) ->
* "hellohello";
*/
public static String string(String s, int n) {
if (n == 1) {
return s; // most common, check first
}
if (n < 1) {
return "";
}
StringBuilder sb = new StringBuilder(s.length() * n);
for (int i = 0; i < n; i++) {
sb.append(s);
}
return sb.toString();
}
public static String removeSpaces(String str) {
if (str == null || str.length() == 0) {
return "";
}
StringBuilder sb = new StringBuilder(str.length());
char c;
for (int i = 0; i < str.length(); i++) {
c = str.charAt(i);
if (c != ' ') {
sb.append(c);
}
}
return sb.toString();
}
/**
* Removes spaces from the start and end Not the same as trim - it removes
* ASCII control chars eg tab Michael Borcherds 2007-11-23
*
* @param str
*/
public static String trimSpaces(String str) {
int len = str.length();
if (len == 0) {
return "";
}
int start = 0;
while (str.charAt(start) == ' ' && start < len - 1) {
start++;
}
int end = len;
while (str.charAt(end - 1) == ' ' && end > start) {
end--;
}
if (start == end) {
return "";
}
return str.substring(start, end);
}
private static StringBuilder sbReplaceExp = new StringBuilder(200);
public static StringBuilder resetStringBuilder(StringBuilder high) {
if (high == null) {
return new StringBuilder();
}
high.setLength(0);
return high;
}
public static boolean isNumber(String text) {
if (text == null || "".equals(text)) {
return false;
}
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
if (!isDigit(c) && c != '.' && c != Unicode.ArabicComma && c != '-') {
return false;
}
}
return true;
}
/**
* important to use this rather than String.toLowerCase() as this is
* overridden in desktop.Application so that it uses
* String.toLowerCase(Locale.US) so that the behaviour is well defined
* whatever language we are running in NB does cause problems eg in Turkish
*
* @param s
* input string
* @return the <code>String</code>, converted to lowercase.
* @see java.lang.String#toUpperCase(Locale)
*/
public static String toLowerCase(String s) {
return s.toLowerCase(Locale.US);
}
/**
* important to use this rather than String.toLowerCase() as this is
* overridden in desktop.Application so that it uses
* String.toLowerCase(Locale.US) so that the behavior is well defined
* whatever language we are running in NB does cause problems eg in Turkish
*
* @param s
* input string
* @return the <code>String</code>, converted to lowercase.
* @see java.lang.String#toUpperCase(Locale)
*/
public static String toUpperCase(String s) {
return s.toUpperCase(Locale.US);
}
public static double parseDouble(String s) {
if ("NaN".equals(s) || "undefined".equals(s) || "null".equals(s)) {
return Double.NaN;
} else if ("Infinity".equals(s)) {
return Double.POSITIVE_INFINITY;
} else if ("-Infinity".equals(s)) {
return Double.NEGATIVE_INFINITY;
}
return Double.parseDouble(s);
}
public static String repeat(char c, int count) {
StringBuilder ret = new StringBuilder();
for (int i = 0; i < count; i++) {
ret.append(c);
}
return ret.toString();
}
/**
* Character.isLetterOrDigit() doesn't work in GWT, see
* http://code.google.com/p/google-web-toolkit/issues/detail?id=1983
*/
public static boolean isLetterOrDigit(char c) {
if (isDigit(c)) {
return true;
}
return isLetter(c);
}
public static boolean isLetterOrDigitOrUnderscore(final char character) {
switch (character) {
case '_': // allow underscore as a valid letter in an autocompletion
// word
return true;
default:
return isLetterOrDigit(character);
}
}
/**
* Character.isDigit() doesn't work in GWT, see
* http://code.google.com/p/google-web-toolkit/issues/detail?id=1983
*
* see also MyDouble.parseDouble()
*/
public static boolean isDigit(char ch) {
// TODO: Maybe this could be more efficient
// check roman first (most common)
if ((ch >= '\u0030' && ch <= '\u0039')
|| (ch >= '\u0660' && ch <= '\u0669')
|| (ch >= '\u06f0' && ch <= '\u06f9')
|| (ch >= '\u0966' && ch <= '\u096f')
|| (ch >= '\u09e6' && ch <= '\u09ef')
|| (ch >= '\u0a66' && ch <= '\u0a6f')
|| (ch >= '\u0ae6' && ch <= '\u0aef')
|| (ch >= '\u0b66' && ch <= '\u0b6f')
|| (ch >= '\u0be6' && ch <= '\u0bef') // Java (5?) bug: \u0BE6
// not recognized by
// Character.isDigit()
|| (ch >= '\u0c66' && ch <= '\u0c6f')
|| (ch >= '\u0ce6' && ch <= '\u0cef')
|| (ch >= '\u0d66' && ch <= '\u0d6f')
|| (ch >= '\u0e50' && ch <= '\u0e59')
|| (ch >= '\u0ed0' && ch <= '\u0ed9')
|| (ch >= '\u0f20' && ch <= '\u0f29')
|| (ch >= '\u1040' && ch <= '\u1049')
|| (ch >= '\u17e0' && ch <= '\u17e9')
|| (ch >= '\u1810' && ch <= '\u1819')
|| (ch >= '\u1b50' && ch <= '\u1b59') // not recognized by
// Java's version of
// Character.isDigit() !
|| (ch >= '\u1bb0' && ch <= '\u1bb9') // not recognized by
// Java's version of
// Character.isDigit() !
|| (ch >= '\u1c40' && ch <= '\u1c49') // not recognized by
// Java's version of
// Character.isDigit() !
|| (ch >= '\u1c50' && ch <= '\u1c59') // not recognized by
// Java's version of
// Character.isDigit() !
|| (ch >= '\ua8d0' && ch <= '\ua8d9') // not recognized by
// Java's version of
// Character.isDigit() !
// following not handled by GeoGebra's parser
// || (ch >= 0x1369 && ch <= 0x1371) // Ethiopic
// || (ch >= 0x1946 && ch <= 0x194F) // Limbu
// || (ch >= 0xFF10 && ch <= 0xFF19) //"FULL WIDTH" digits
) {
return true;
}
return false;
}
// public static void main(String [] args) {
// System.out.println("starting test");
//
//
// for (int cc = 0; cc < 65536; ++cc) {
// char c = (char)cc;
// if (Character.isLetter(c) != isLetter(c)) {
// System.out.println("isLetter failed "+c +
// " "+toHexString(c)+Character.isLetter(c)+" "+isLetter(c));
// }
// }
// for (int cc = 0; cc < 65536; ++cc) {
// char c = (char)cc;
// if (Character.isDigit(c) != isDigit(c)) {
// System.out.println("isDigit failed "+c +
// " "+toHexString(c)+Character.isDigit(c)+" "+isDigit(c));
// }
// }
// for (int cc = 0; cc < 65536; ++cc) {
// char c = (char)cc;
// if (Character.isWhitespace(c) != isWhitespace(c)) {
// System.out.println("isWhitespace failed "+c +
// " "+toHexString(c)+Character.isWhitespace(c)+" "+isWhitespace(c));
// }
// }
//
// boolean start = true;
//
// for (int cc = 0; cc < 65536-1; ++cc) {
// char c = (char)cc;
// char c2 = (char)(cc+1);
// if (Character.isLetter(c) != Character.isLetter(c2)) {
// if (start) {
// System.out.print(toHexString(c2));
// } else {
// System.out.println(" "+toHexString(c));
// }
// start = !start;
// }
// }
//
// System.out.println("ending test");
//
// }
/**
* Character.isLetter() doesn't work in GWT, see
* http://code.google.com/p/google-web-toolkit/issues/detail?id=1983
*/
public static boolean isLetter(char c) {
// From Parser.jj, compatibility with internationalized Unicode
// characters
// TODO: Maybe this could be more efficient
if ((c >= '\u0041' && c <= '\u005a') || // upper case (A-Z)
(c >= '\u0061' && c <= '\u007a') || // lower case (a-z)
(c == '\u00b7') || // middle dot (for Catalan)
(c >= '\u00c0' && c <= '\u00d6') || // accentuated letters
(c >= '\u00d8' && c <= '\u00f6') || // accentuated letters
(c >= '\u00f8' && c <= '\u01bf') || // accentuated letters
(c >= '\u01c4' && c <= '\u02a8') || // accentuated letters
(c >= '\u0391' && c <= '\u03f3') || // Greek
(c >= '\u0401' && c <= '\u0481') || // Cyrillic
(c >= '\u0490' && c <= '\u04f9') || // Cyrillic
(c >= '\u0531' && c <= '\u1ffc') || // a lot of signs (Arabic,
// accentuated, ...)
(c >= '\u3041' && c <= '\u3357') || // Asian letters
(c >= '\u4e00' && c <= '\ud7a3') || // Asian letters
(c >= '\uf71d' && c <= '\ufa2d') || // Asian letters
(c >= '\ufb13' && c <= '\ufdfb') || // Armenian, Hebrew, Arabic
(c >= '\ufe80' && c <= '\ufefc') || // Arabic
(c >= '\uff66' && c <= '\uff9d') || // Katakana
(c >= '\uffa1' && c <= '\uffdc') // Hangul
) {
return true;
}
return false;
}
/**
* @param str
* String
* @return true if str matches one of "!=", "<>", Unicode.NOTEQUAL
*/
public static boolean isNotEqual(String str) {
return "!=".equals(str) || "<>".equals(str)
|| Unicode.NOTEQUAL.equals(str);
}
/**
* @param str
* String
* @return true if str matches one of "<", ">", "!=", "<>", Unicode.NOTEQUAL
*/
public static boolean isInequality(String str) {
return "<".equals(str) || ">".equals(str) || isNotEqual(str);
}
/**
* Since a_{{{{{{{5}=2 is correct expression, we replace the index by Xs to
* obtain a_{XXXXXXX}=2
*
* @param text
* text
* @return text with replaced {s
*/
public static String ignoreIndices(String text) {
if (text == null) {
return null;
}
StringBuilder sb = new StringBuilder(80);
boolean ignore = false;
boolean underscore = false;
boolean comment = false;
for (int i = 0; i < text.length(); i++) {
char ch = text.charAt(i);
if (comment && ch != '"') {
sb.append(ch);
continue;
}
if (ch == '"' && !underscore) {
sb.append(ch);
comment = !comment;
continue;
}
if (ignore && ch == '}') {
ignore = false;
}
if (!ignore) {
sb.append(ch);
} else {
sb.append('X');
}
if (underscore && ch == '{') {
ignore = true;
} else if (!ignore) {
underscore = ch == '_';
}
}
return sb.toString();
}
public static int checkBracketsBackward(String parseString) {
int curly = 0;
int square = 0;
int round = 0;
Stack<Integer> closingBrackets = new Stack<Integer>();
boolean comment = false;
for (int i = parseString.length() - 1; i >= 0; i--) {
char ch = parseString.charAt(i);
if (comment && ch != '"') {
continue;
}
switch (ch) {
default:
// do nothing
break;
case '"':
comment = !comment;
break;
case '}':
closingBrackets.add(i);
curly++;
break;
case '{':
curly--;
if (curly < 0) {
return i;
}
closingBrackets.pop();
break;
case ']':
square++;
closingBrackets.add(i);
break;
case '[':
square--;
if (square < 0) {
return i;
}
closingBrackets.pop();
break;
case ')':
round++;
closingBrackets.add(i);
break;
case '(':
round--;
if (round < 0) {
return i;
}
closingBrackets.pop();
break;
}
}
if (!closingBrackets.isEmpty()) {
return closingBrackets.pop();
}
return -1;
}
public static String fixVerticalBars(String parseString0) {
String ignoredIndices = ignoreIndices(parseString0);
StringBuilder sbFix = new StringBuilder();
// When we have <splitter> || , we know that we should separate
// these bars (i. e., we want absolute value, not OR)
Set<Character> splitters = new TreeSet<Character>(
Arrays.asList(new Character[] { Unicode.SQUARE_ROOT, '+', '-',
'*', '/', '^', '=' }));
// first we iterate from left to right, and then backward
int topLevelBars = 0;
String parseString = parseString0;
for (int dir = 0; dir < 2; dir++) {
boolean comment = false;
int bars = 0;
Character lastNonWhitespace = ' ';
int level = 0;
if (dir == 1) {
if (MyDouble.isOdd(topLevelBars)) {
int lPos = sbFix.lastIndexOf("|");
sbFix.replace(lPos, lPos + 1, ")");
sbFix.insert(0, "(");
}
parseString = sbFix.reverse().toString();
ignoredIndices = ignoreIndices(parseString);
sbFix = new StringBuilder();
splitters = new TreeSet<Character>(
Arrays.asList(new Character[] { '*', '/', '^', '=',
Unicode.Superscript_0, Unicode.Superscript_1,
Unicode.Superscript_2, Unicode.Superscript_3,
Unicode.Superscript_4, Unicode.Superscript_5,
Unicode.Superscript_6, Unicode.Superscript_7,
Unicode.Superscript_8, Unicode.Superscript_9,
Unicode.Superscript_Minus }));
}
int len = ignoredIndices.length();
for (int i = 0; i < len; i++) {
Character ch = ignoredIndices.charAt(i);
if (!comment && dir == 0 && sbFix.length() > 0 && ch.equals('.')
&& (sbFix.charAt(sbFix.length() - 1) == '.'
|| sbFix.charAt(sbFix.length()
- 1) == Unicode.ellipsis)) {
sbFix.setLength(sbFix.length() - 1);
sbFix.append(Unicode.ellipsis);
} else {
char write = parseString.charAt(i);
if (write == Unicode.micro) {
sbFix.append(Unicode.mu);
} else {
sbFix.append(write);
}
}
if (StringUtil.isWhitespace(ch)
|| (comment && !ch.equals('"'))) {
continue;
}
if (ch.equals('"')) {
comment = !comment;
} else if (ch.equals('{') || ch.equals('(') || ch.equals('[')) {
level++;
} else if (ch.equals('}') || ch.equals(')') || ch.equals(']')) {
level--;
}
if (ch.equals('|')) {
// We separate bars if the previous symbol was in splitters
// or we have ||| and there were an odd number of bars so
// far
if (i == 0
|| (MyDouble.isOdd(bars) && i < len - 2
&& ignoredIndices.charAt(i + 1) == '|'
&& ignoredIndices.charAt(i + 2) == '|')
|| (i < len - 1
&& ignoredIndices.charAt(i + 1) == '|'
&& splitters.contains(lastNonWhitespace))) {
sbFix.append(' ');
}
bars++;
if (level == 0) {
topLevelBars++;
}
}
lastNonWhitespace = ch;
}
}
return sbFix.reverse().toString();
}
/**
* Checks whether the text may represent two expressions separated by comma.
* Simple check for comma is not possible as (1,1)+{1,1} is a simple
* expression.
*
* @param evalText
* text to be analyzed
* @return true if the text is of the form expression,expression
*/
public static boolean representsMultipleExpressions(String evalText) {
String text = ignoreIndices(evalText);
int brackets = 0;
boolean comment = false;
for (int i = text.length() - 1; i >= 0; i--) {
char ch = text.charAt(i);
if (comment && ch != '"') {
continue;
}
switch (ch) {
case '}':
case ')':
case ']':
brackets--;
break;
case '{':
case '(':
case '[':
brackets++;
break;
case ',':
if (brackets == 0) {
return true;
}
}
}
return false;
}
/**
* @param label
* label, may contain bold, italic, indices
* @return ratio of estimated string length and font size
*/
public double estimateLengthHTML(String label, GFont font) {
String str = label;
boolean bold = false;
if (str.startsWith("<i>") && str.endsWith("</i>")) {
str = str.substring(3, label.length() - 4);
}
if (str.startsWith("<b>") && str.endsWith("</b>")) {
str = str.substring(3, str.length() - 4);
bold = true;
}
if (str.startsWith("<i>") && str.endsWith("</i>")) {
str = str.substring(3, str.length() - 4);
}
return estimateLength(label, bold ? font.deriveFont(GFont.BOLD) : font);
}
public double estimateLength(String label, GFont font) {
String str = label;
boolean bold = font.isBold();
double visibleChars = 0;
boolean index = false;
double indexSize = 0.7;
for (int i = 0; i < str.length(); i++) {
if (str.charAt(i) == '_') {
if (i < str.length() - 1 && str.charAt(i + 1) == '{') {
i++;
index = true;
} else {
visibleChars -= (1 - indexSize); // penalty for 1char index
}
} else if (str.charAt(i) == '}') {
index = false;
} else {
visibleChars += index ? indexSize : 1;
}
}
return bold ? visibleChars * 0.6 * font.getSize()
: visibleChars * 0.5 * font.getSize();
}
public double estimateHeight(String string, GFont font) {
if (font == null) {
return 0;
}
return string.indexOf('_') > -1 ? font.getSize() * 1.8
: font.getSize() * 1.4;
}
public static Object format(String sub, double x0, double x1, double x2,
double x3) {
return sub.replaceAll("%0", x0 + "").replaceAll("%1", x1 + "")
.replaceAll("%2", x2 + "").replace("%3", x3 + "");
}
/**
* @param s
* String to wrap
* @param tpl
* String template
* @return "exact(" + s + ")" if necessary (for Giac)
*/
public static String wrapInExact(String s, StringTemplate tpl) {
return wrapInExact(0, s, tpl, null);
}
/**
* @param x
* the number to convert
* @param s0
* String to wrap (String representation of x)
* @param tpl
* String template (Giac or GiacInternal)
* @param kernel
* kernel
* @return "exact(" + s + ")" if necessary (for Giac) or convert double into
* a fraction internally (for GiacInternal)
*/
public static String wrapInExact(double x, String s0, StringTemplate tpl,
Kernel kernel) {
if (s0.startsWith("exact(")) {
// nothing to do
return s0;
}
String s = s0;
// if we have eg 6.048554268711413E7
// convert to exact(6.048554268711413e7)
if (s.indexOf("E") > -1) {
s = s.replace("E", "e");
}
if ("?".equals(s) || "undef".equals(s)) {
return "undef";
}
if (tpl.isNumeric()) {
return s;
}
if ("inf".equals(s)) {
return s;
}
if ("-inf".equals(s)) {
return s;
}
StringBuilder sb1 = new StringBuilder();
if (tpl == StringTemplate.giacTemplateInternal && kernel != null) {
sb1.append("(");
long[] l = kernel.doubleToRational(x);
sb1.append(l[0] + "/" + l[1]);
sb1.append(')');
} else {
sb1.append("exact(");
sb1.append(s);
sb1.append(')');
}
return sb1.toString();
}
/**
* @param filename
* input filename
* @return filename without leading slash
*/
public static String removeLeadingSlash(String filename) {
if (filename != null && filename.length() != 0
&& filename.charAt(0) == '/') {
return filename.substring(1);
}
return filename;
}
public static String toHtmlColor(GColor color) {
return "#" + toHexString(color);
}
/**
* Tokenize a string so that every even indices (e.g. 0) of the returned
* array should contain a String not containing any letters (or digits), and
* every odd incides (e.g. 1) of it should contain a String having only
* letters (or digits).
*
* @param input
* the input String
* @return the tokenized String
*/
public static ArrayList<String> wholeWordTokenize(String input) {
// ArrayList is easier for now as we don't know
// the length of the returned String yet
ArrayList<String> ret = new ArrayList<String>();
Character actChar;
String actWord = "";
// 1st, 2nd, 3rd, 4th, ...
// 0, 1, 2, 3, ...
// parity of the number of elements already in the ret Array
// or in other words, whether we are going to add a word
// in the next step
boolean odd = false;
for (int i = 0; i < input.length(); i++) {
actChar = input.charAt(i);
// although the syntax might not allow whole words
// starting with digits, we're going to allow them
// here, as it is easier and will not change the outcome
if (isLetterOrDigitOrUnderscore(actChar)) {
if (odd) {
actWord += actChar;
} else {
ret.add(actWord);
actWord = "" + actChar;
odd = true;
}
} else {
if (odd) {
ret.add(actWord);
actWord = "" + actChar;
odd = false;
} else {
actWord += actChar;
}
}
}
ret.add(actWord);
// the last one should always be a non-word, like the first one
// but odd should have changed sign in the previous command
if (odd) {
ret.add("");
}
return ret;
}
/**
* Join tokens which are in a similar format as StringUtil.wholeWordTokenize
* produces... delimiter can be null, or can be a glue string
*
* @param tokens
* the input
* @param delimiter
* the glue string (optional)
* @return the joined String
*/
public static String joinTokens(Iterable<String> tokens, String delimiter) {
StringBuilder ret = new StringBuilder();
Iterator<String> it = tokens.iterator();
if (it.hasNext()) {
ret.append(it.next());
}
while (it.hasNext()) {
if (delimiter != null) {
ret.append(delimiter);
}
ret.append(it.next());
}
return ret.toString();
}
public static String cannonicNumber(String str) {
boolean zerosNeedRemoving = true;
int index = str.indexOf(".");
if (index >= 0) {
for (int k = index + 1; k < str.length(); k++) {
if (str.charAt(k) != '0') {
zerosNeedRemoving = false;
break;
}
}
} else {
zerosNeedRemoving = false;
}
if (zerosNeedRemoving) {
return index == 0 ? "0" : str.substring(0, index);
}
// Reduce can't handle .5*8
return index == 0 ? "0" + str : str;
}
// Log.debug(StringUtil.cannonicNumber2("4.3E20"));
// Log.debug(StringUtil.cannonicNumber2("1.203"));
// Log.debug(StringUtil.cannonicNumber2("1.23000000"));
// Log.debug(StringUtil.cannonicNumber2("1000"));
// Log.debug(StringUtil.cannonicNumber2("1.20000000E20"));
// Log.debug(StringUtil.cannonicNumber2("-4.3E20"));
// Log.debug(StringUtil.cannonicNumber2("-1.203"));
// Log.debug(StringUtil.cannonicNumber2("-1.23000000"));
// Log.debug(StringUtil.cannonicNumber2("-1000"));
// Log.debug(StringUtil.cannonicNumber2("-1.20000000E20"));
// Log.debug(StringUtil.cannonicNumber2(".23000000000"));
/*
* convert 1.200000000 into 1.2 convert .23 into 0.23 convert 1.23000E20
* into 1.23E20
*/
public static String cannonicNumber2(String str) {
String num = str;
String exponent = "";
if (str.indexOf('E') > 0) {
String[] split = num.split("E");
exponent = "E" + split[1];
num = split[0];
}
// .23 to 0.23
if (num.startsWith(".")) {
num = "0" + num;
}
// remove trailing zeros if there's a decimal point
if (num.indexOf(".") > 0) {
while (num.endsWith("0")) {
num = num.substring(0, num.length() - 1);
}
}
return num + exponent;
}
/**
* @param c
* @return emulation of Character.isWhiteSpace
*/
public static boolean isWhitespace(char c) {
return c == ' ' || c == '\u0009' || /* , HORIZONTAL TABULATION. */
c == '\n' || /* LINE FEED. */
c == '\u000B' || /* VERTICAL TABULATION. */
c == '\u000C' || /* FORM FEED. */
c == '\r' || /* CARRIAGE RETURN. */
c == '\u001C' || /* FILE SEPARATOR. */
c == '\u001D' || /* GROUP SEPARATOR. */
c == '\u001E' || /* RECORD SEPARATOR. */
c == '\u001F' || /* UNIT SEPARATOR. */
c == '\u1680' || c == '\u180E' || c == '\u2000' || c == '\u2001'
|| c == '\u2002' || c == '\u2003' || c == '\u2004'
|| c == '\u2005' || c == '\u2006' || c == '\u2008'
|| c == '\u2009' || c == '\u200A' || c == '\u2028'
|| c == '\u2029' || c == '\u205F' || c == '\u3000';
}
/**
* Used in DynamicTextProcessor and DynamicTextInputPane
*
* @param sb
* output
* @param content
* input
* @param ret
* alternate between open and closed
*/
public static char processQuotes(StringBuilder sb, String content,
char ret) {
char currentQuote = ret;
if (content.indexOf("\"") == -1) {
sb.append(content);
return currentQuote;
}
for (int i = 0; i < content.length(); i++) {
char c = content.charAt(i);
if (c == '\"') {
sb.append(currentQuote);
// flip open <-> closed
if (currentQuote == Unicode.OPEN_DOUBLE_QUOTE) {
currentQuote = Unicode.CLOSE_DOUBLE_QUOTE;
} else {
currentQuote = Unicode.OPEN_DOUBLE_QUOTE;
}
} else {
// RadioButtonTreeItem uses strings with more than one
// character for the first time, so this part of code
// only applies to it (yet)
sb.append(c);
}
}
return currentQuote;
}
final public static String toJavaString(String str) {
if (str == null) {
return null;
}
StringBuilder sb = new StringBuilder();
// convert every single character and append it to sb
int len = str.length();
for (int i = 0; i < len; i++) {
char c = str.charAt(i);
int code = c;
// standard characters have code 32 to 126
if ((code >= 32 && code <= 126)) {
switch (code) {
case '"':
// replace " with \"
sb.append("\\\"");
break;
case '\'':
// replace ' with \'
sb.append("\\'");
break;
case '\\':
// replace \ with \\
sb.append("\\\\");
break;
default:
// do not convert
sb.append(c);
}
}
// special characters
else {
switch (code) {
case 10: // CR
sb.append("\\n");
break;
case 13: // LF
sb.append("\\r");
break;
case 9: // replace TAB
sb.append("\\t"); // space
break;
default:
// convert special character to \u0123 format
sb.append(toHexString(c));
}
}
}
return sb.toString();
}
/**
* @param fileName
* eg "file.png"
* @return file extension in lower case eg "png" or "" if there isn't one
*/
public static String getFileExtensionStr(String fileName) {
int dotPos = fileName.lastIndexOf('.');
if ((dotPos <= 0) || (dotPos == (fileName.length() - 1))) {
return "";
}
return toLowerCase(fileName.substring(dotPos + 1));
}
public static FileExtensions getFileExtension(String fileName) {
String ext = getFileExtensionStr(fileName);
return FileExtensions.get(ext);
}
/**
* @param fileName
* eg "file.gif"
* @return changes eg "file.gif" to "file"
*/
public static String removeFileExtension(String fileName) {
if (fileName == null) {
return null;
}
int dotPos = fileName.lastIndexOf('.');
if (dotPos <= 0) {
return fileName;
}
return fileName.substring(0, dotPos);
}
/**
* @param fileName
* eg "file.gif"
* @param extension
* eg PNG
* @return changes eg "file.gif" to "file.png"
*/
public static String changeFileExtension(String fileName,
FileExtensions extension) {
if (fileName == null) {
return null;
}
return removeFileExtension(fileName) + "." + extension.toString();
}
/**
* @param str
* input
* @return true if null or empty
*/
public static boolean empty(String str) {
return str == null || str.isEmpty();
}
public static String addDegreeSignIfNumber(char c, String inputText) {
// return unless digit typed
if (!StringUtil.isDigit(c)) {
return inputText;
}
// if text already contains degree symbol or variable
for (int i = 0; i < inputText.length(); i++) {
if (!StringUtil.isDigit(inputText.charAt(i))) {
return inputText;
}
}
return (inputText + Unicode.DEGREE);
}
public static String getGrayString(char c, Localization loc) {
switch (c) {
case '0':
return loc.getColor("white");
case '1':
return loc.getPlain("AGray", Unicode.fraction1_8);
case '2':
return loc.getPlain("AGray", Unicode.fraction1_4); // silver
case '3':
return loc.getPlain("AGray", Unicode.fraction3_8);
case '4':
return loc.getPlain("AGray", Unicode.fraction1_2);
case '5':
return loc.getPlain("AGray", Unicode.fraction5_8);
case '6':
return loc.getPlain("AGray", Unicode.fraction3_4);
case '7':
return loc.getPlain("AGray", Unicode.fraction7_8);
default:
return loc.getColor("black");
}
}
/**
* check if string contains LaTeX codes. If so then wrapping in \text{}
* probably isn't desirable eg https://www.geogebra.org/m/FH6NkgCN which has
* \int_{0}^{12.03} 2 \; \sqrt{x}\cdot dx = 55.66
*
* @param str
* string to check
* @return true if str contains any of \ ^ _
*/
public static boolean containsLaTeX(String str) {
if (str == null) {
return false;
}
if (str.contains("\\") || str.contains("^") || str.contains("_")) {
return true;
}
return false;
}
/**
* @param str
* input
* @return whether input is null or empty after trim
*/
public static boolean emptyTrim(String str) {
if (str == null) {
return true;
}
for (int i = 0; i < str.length(); i++) {
if (!isWhitespace(str.charAt(i))) {
return false;
}
}
return true;
}
}