服务:Spring Boot 3.2 + docx4j 8.3.9,POST /api/v3/server_office_file/file/parse
接受 .docx,返回 HTML(公式 → LaTeX,图片 → RustFS 外链)
公式支持三类来源:
- OMML(Word 2007+ 公式) → Python omml_mathml2latex
- MathType OLE 对象 → Ruby mathtype2mathml → MathML → LaTeX
- Word EQ 域(老教辅主力) → EqToLatexUtil(本次新增)
本次改动:
1. 新增 java-svc/.../core/EqToLatexUtil.java:
移植自 jby backend-services office-core,EQ 语法 → LaTeX
(\f→\dfrac、\r→\sqrt、\i→\int/\sum/\prod、\s→^{}/_{}、\b→\left\right、
\a→array、\x→\boxed/\overline、\o\al 堆叠、圈码、化学符号、极限、连等式)
品牌标记 JBYU* → WPU*,与采集端一致
2. CtoMathFinder:接上 EQ 分支(原为 not-ported 桩),域代码经
EqToLatexUtil.convert 转 LaTeX 写入 eqLatexMap(WPEQNUM 占位符)
3. WordParseUtil.dealPlaceholder:EQ 的 LaTeX 同样加 \( \) 包裹
(原先因 WPEQNUM 例外不加包裹,前端/MathJax 不渲染)
修前症状:教辅 Word 里分式/方程组/上下标整段丢失(如 {−1/3, 1} 变成 {-,1})
修后实测:46 份教辅 2908 处 EQ 域全部还原,公式总数 2954 处、零未包裹残留
安全:application.yml(含 RustFS 密钥)不入库,提供 application.yml.example;
OssService 的 @Value 默认密钥已清空
1035 lines
46 KiB
Java
1035 lines
46 KiB
Java
/*
|
|
* Decompiled with CFR 0.152.
|
|
*
|
|
* Could not load the following classes:
|
|
* org.apache.commons.lang3.StringUtils
|
|
* org.slf4j.Logger
|
|
* org.slf4j.LoggerFactory
|
|
*/
|
|
package com.jby.officecore.utils;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.regex.Matcher;
|
|
import java.util.regex.Pattern;
|
|
import java.util.stream.Collectors;
|
|
import org.apache.commons.lang3.StringUtils;
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
public class EqToLatexUtil {
|
|
private static final Logger log = LoggerFactory.getLogger(EqToLatexUtil.class);
|
|
private static final Pattern LONG_EQUALS_PATTERN = Pattern.compile("^=+$");
|
|
private static final int LONG_EQUALS_MIN_LENGTH = 3;
|
|
private static final Map<String, CommandHandler> COMMAND_HANDLERS = new HashMap<String, CommandHandler>();
|
|
|
|
public static String sanitizeInput(String input) {
|
|
if (input == null) {
|
|
return "";
|
|
}
|
|
HashMap<String, String> replacements = new HashMap<String, String>();
|
|
replacements.put("\uff08", "(");
|
|
replacements.put("\uff09", ")");
|
|
replacements.put("\uff5b", "{");
|
|
replacements.put("\uff5d", "}");
|
|
replacements.put("\uff0b", "+");
|
|
replacements.put("\uff0d", "-");
|
|
replacements.put("\u2212", "-");
|
|
replacements.put("\u00d7", " \\times ");
|
|
replacements.put("\u00f7", " \\div ");
|
|
replacements.put("\uff1d", "=");
|
|
replacements.put("\u2260", "\\ne ");
|
|
replacements.put("\u2248", " \\approx ");
|
|
replacements.put("\u2264", "\\le ");
|
|
replacements.put("\u2265", "\\ge ");
|
|
replacements.put("\uff1c", "<");
|
|
replacements.put("\uff1e", "> ");
|
|
replacements.put("\u00b7", " \\cdot ");
|
|
replacements.put("\u2026", "\\dots ");
|
|
replacements.put("\uff5e", "~");
|
|
replacements.put("\uff1b", ";");
|
|
replacements.put("\uff1a", ":");
|
|
replacements.put("\uf028", "(");
|
|
replacements.put("\uf029", ")");
|
|
replacements.put("~", "\\sim");
|
|
replacements.put("\u221d", "\\propto");
|
|
String sanitized = input;
|
|
for (Map.Entry entry : replacements.entrySet()) {
|
|
sanitized = sanitized.replace((CharSequence)entry.getKey(), (CharSequence)entry.getValue());
|
|
}
|
|
sanitized = sanitized.replaceAll("(?i)eq", "EQ ");
|
|
return sanitized.trim();
|
|
}
|
|
|
|
private static String getCharFromOpt(String opt, int startIdx, String defaultChar) {
|
|
if (opt.length() <= startIdx) {
|
|
return defaultChar;
|
|
}
|
|
String charPart = opt.substring(startIdx).trim();
|
|
if (charPart.isEmpty()) {
|
|
return defaultChar;
|
|
}
|
|
if (charPart.startsWith("\\")) {
|
|
return charPart.substring(1);
|
|
}
|
|
return charPart;
|
|
}
|
|
|
|
private static String escapeChar(String ch) {
|
|
String specialChars = "\\{}$&#%";
|
|
if ("\\{}$&#%".contains(ch)) {
|
|
return "\\" + ch;
|
|
}
|
|
if ("~".equals(ch)) {
|
|
return "\\ ";
|
|
}
|
|
return ch;
|
|
}
|
|
|
|
public static String convert(String eqField) {
|
|
if (eqField == null) {
|
|
throw new IllegalArgumentException("\u8f93\u5165\u5fc5\u987b\u662f\u5b57\u7b26\u4e32\u7c7b\u578b");
|
|
}
|
|
if (eqField.trim().isEmpty()) {
|
|
return "";
|
|
}
|
|
try {
|
|
String sanitizedField = EqToLatexUtil.sanitizeInput(EqToLatexUtil.stripFormattingMarkers(eqField));
|
|
String content = sanitizedField.replaceAll("^\\s*\\{?\\s*EQ\\s*|\\s*EQ\\s*|\\s*}\\s*$", "").trim();
|
|
String result = EqToLatexUtil.parseInstructions(content, new HashMap<String, Object>());
|
|
result = EqToLatexUtil.replaceJbyuWithMeasurements(result);
|
|
result = result.replaceAll("JBYU2BEGIN", "\\\\underline{").replaceAll("JBYU2END", "}");
|
|
result = result.replaceAll("\u2192", "\\\\to ");
|
|
log.info("\u539fEQ\u516c\u5f0f\uff1a{} \u8f6c\u6362\u540e\u7684Latex\u4ee3\u7801\uff1a{}", (Object)eqField, (Object)result);
|
|
return result;
|
|
}
|
|
catch (Exception error) {
|
|
System.err.println("\u8f6c\u6362\u8fc7\u7a0b\u4e2d\u51fa\u73b0\u9519\u8bef: " + error.getMessage());
|
|
error.printStackTrace();
|
|
return "\\text{\u8f6c\u6362\u9519\u8bef: " + error.getMessage() + "}";
|
|
}
|
|
}
|
|
|
|
private static String stripFormattingMarkers(String input) {
|
|
return input.replaceAll("\\\\(?:\\s*\\\\?JBYU\\s*2(?:BEGIN|END))+\\s*(?!(?:[eE][qQ])\\b)(?=[A-Za-z])", Matcher.quoteReplacement("\\")).replaceAll("\\\\?JBYU\\s*2BEGIN", "").replaceAll("\\\\?JBYU\\s*2END", "");
|
|
}
|
|
|
|
private static String parseInstructions(String str, Map<String, Object> context) {
|
|
ArrayList<String> output = new ArrayList<String>();
|
|
int index = 0;
|
|
while (index < str.length()) {
|
|
int textEnd;
|
|
char ch = str.charAt(index);
|
|
if (ch == '\\') {
|
|
String lastOutput;
|
|
ParseResult result = EqToLatexUtil.parseCommand(str, index, context);
|
|
String trimmedOutput = result.output.trim();
|
|
if ((trimmedOutput.startsWith("^") || trimmedOutput.startsWith("_")) && !output.isEmpty()) {
|
|
lastOutput = (String)output.get(output.size() - 1);
|
|
if (EqToLatexUtil.isConsecutiveScript(lastOutput, trimmedOutput)) {
|
|
output.set(output.size() - 1, EqToLatexUtil.mergeConsecutiveScripts(lastOutput, trimmedOutput));
|
|
} else {
|
|
output.set(output.size() - 1, lastOutput + result.output);
|
|
}
|
|
} else if ((trimmedOutput.startsWith("\\^") || trimmedOutput.startsWith("\\_")) && !output.isEmpty()) {
|
|
lastOutput = (String)output.get(output.size() - 1);
|
|
if (EqToLatexUtil.isConsecutiveScript(lastOutput, trimmedOutput)) {
|
|
output.set(output.size() - 1, EqToLatexUtil.mergeConsecutiveScripts(lastOutput, trimmedOutput));
|
|
} else {
|
|
output.set(output.size() - 1, lastOutput + result.output);
|
|
}
|
|
} else if (EqToLatexUtil.isSpecialScriptMarker(trimmedOutput) && !output.isEmpty() && EqToLatexUtil.isSpecialScriptMarker((String)output.get(output.size() - 1))) {
|
|
String merged = EqToLatexUtil.mergeSpecialScriptMarkers((String)output.get(output.size() - 1), trimmedOutput);
|
|
output.set(output.size() - 1, merged);
|
|
} else {
|
|
output.add(result.output);
|
|
}
|
|
index = result.newIndex;
|
|
continue;
|
|
}
|
|
if (ch == '{' || ch == '}') {
|
|
++index;
|
|
continue;
|
|
}
|
|
for (textEnd = index; textEnd < str.length() && "\\{}".indexOf(str.charAt(textEnd)) == -1; ++textEnd) {
|
|
}
|
|
String text = str.substring(index, textEnd);
|
|
output.add(text.replaceAll("[&%$#_]", "\\\\$0"));
|
|
index = textEnd;
|
|
}
|
|
String result = String.join((CharSequence)" ", output).replaceAll("\\s+", " ").trim();
|
|
return EqToLatexUtil.postProcessConsecutiveScripts(result);
|
|
}
|
|
|
|
private static ParseResult parseCommand(String str, int startIndex, Map<String, Object> context) {
|
|
int argParenStartIndex;
|
|
String cmdName;
|
|
int currentIndex = startIndex;
|
|
ArrayList<String> commandChain = new ArrayList<String>();
|
|
ArrayList<Integer> commandEndIndices = new ArrayList<Integer>();
|
|
while (currentIndex < str.length()) {
|
|
int cmdNameEnd;
|
|
int tempIndex;
|
|
for (tempIndex = currentIndex; tempIndex < str.length() && Character.isWhitespace(str.charAt(tempIndex)); ++tempIndex) {
|
|
}
|
|
if (tempIndex >= str.length() || str.charAt(tempIndex) != '\\') {
|
|
currentIndex = tempIndex;
|
|
break;
|
|
}
|
|
currentIndex = tempIndex;
|
|
for (cmdNameEnd = currentIndex + 1; cmdNameEnd < str.length() && Character.isLetter(str.charAt(cmdNameEnd)); ++cmdNameEnd) {
|
|
}
|
|
String cmdName2 = str.substring(currentIndex + 1, cmdNameEnd);
|
|
String token = "\\" + cmdName2;
|
|
int argStartIndex = cmdNameEnd;
|
|
List<String> numericArgCmds = Arrays.asList("co", "vs", "hs", "fo", "ba", "up", "do", "ai", "di");
|
|
if (numericArgCmds.contains(cmdName2.toLowerCase())) {
|
|
int numEnd;
|
|
for (numEnd = argStartIndex; numEnd < str.length() && Character.isDigit(str.charAt(numEnd)); ++numEnd) {
|
|
}
|
|
if (numEnd > argStartIndex) {
|
|
token = token + str.substring(argStartIndex, numEnd);
|
|
argStartIndex = numEnd;
|
|
}
|
|
} else if (Arrays.asList("lc", "rc", "bc", "fc", "vc").contains(cmdName2.toLowerCase()) && argStartIndex < str.length()) {
|
|
if (str.charAt(argStartIndex) == '\\' && argStartIndex + 1 < str.length()) {
|
|
token = token + str.substring(argStartIndex, argStartIndex + 2);
|
|
argStartIndex += 2;
|
|
} else {
|
|
token = token + str.charAt(argStartIndex);
|
|
++argStartIndex;
|
|
}
|
|
}
|
|
commandChain.add(token);
|
|
commandEndIndices.add(argStartIndex);
|
|
currentIndex = argStartIndex;
|
|
}
|
|
if (commandChain.isEmpty()) {
|
|
return new ParseResult(EqToLatexUtil.escapeChar(String.valueOf(str.charAt(startIndex))), startIndex + 1);
|
|
}
|
|
CommandHandler mainCommandHandler = null;
|
|
String mainCommandName = "";
|
|
String firstToken = (String)commandChain.get(0);
|
|
Matcher matcher = Pattern.compile("^\\\\([a-zA-Z]+)").matcher(firstToken);
|
|
if (matcher.find() && COMMAND_HANDLERS.containsKey(cmdName = matcher.group(1).toLowerCase())) {
|
|
mainCommandHandler = COMMAND_HANDLERS.get(cmdName);
|
|
mainCommandName = cmdName;
|
|
}
|
|
if (mainCommandHandler == null) {
|
|
return new ParseResult((String)commandChain.get(0), (Integer)commandEndIndices.get(0));
|
|
}
|
|
ArrayList<String> args = new ArrayList<String>();
|
|
for (argParenStartIndex = currentIndex; argParenStartIndex < str.length() && Character.isWhitespace(str.charAt(argParenStartIndex)); ++argParenStartIndex) {
|
|
}
|
|
if (argParenStartIndex < str.length() && (str.charAt(argParenStartIndex) == '(' || str.charAt(argParenStartIndex) == '[')) {
|
|
ArgumentResult argsResult = EqToLatexUtil.extractArguments(str, argParenStartIndex);
|
|
if (argsResult.success) {
|
|
List<String> rawArgs = EqToLatexUtil.splitArguments(argsResult.content);
|
|
HashMap<String, Object> childContext = new HashMap<String, Object>();
|
|
childContext.put("parentCommand", mainCommandName);
|
|
for (String arg : rawArgs) {
|
|
args.add(EqToLatexUtil.parseInstructions(arg, childContext));
|
|
}
|
|
currentIndex = argsResult.endIndex;
|
|
} else {
|
|
throw new RuntimeException("Mismatched parentheses starting at index " + argParenStartIndex);
|
|
}
|
|
}
|
|
String output = mainCommandHandler.handle(commandChain, args, mainCommandName, context);
|
|
return new ParseResult(output, currentIndex);
|
|
}
|
|
|
|
private static ArgumentResult extractArguments(String str, int startIndex) {
|
|
char openParen = str.charAt(startIndex);
|
|
char closeParen = openParen == '(' ? (char)')' : ']';
|
|
int depth = 1;
|
|
StringBuilder content = new StringBuilder();
|
|
int index = startIndex + 1;
|
|
while (index < str.length() && depth > 0) {
|
|
char ch = str.charAt(index);
|
|
if (ch == '\\' && index + 1 < str.length()) {
|
|
content.append(ch).append(str.charAt(index + 1));
|
|
index += 2;
|
|
continue;
|
|
}
|
|
if (ch == openParen) {
|
|
if (++depth > 1) {
|
|
content.append(ch);
|
|
}
|
|
++index;
|
|
continue;
|
|
}
|
|
if (ch == closeParen) {
|
|
if (--depth > 0) {
|
|
content.append(ch);
|
|
}
|
|
++index;
|
|
continue;
|
|
}
|
|
if (depth > 0) {
|
|
content.append(ch);
|
|
}
|
|
++index;
|
|
}
|
|
return new ArgumentResult(depth == 0, content.toString(), index);
|
|
}
|
|
|
|
private static List<String> splitArguments(String str) {
|
|
ArrayList<String> args = new ArrayList<String>();
|
|
StringBuilder current = new StringBuilder();
|
|
int depth = 0;
|
|
int i = 0;
|
|
while (i < str.length()) {
|
|
char ch = str.charAt(i);
|
|
if (ch == '\\' && i + 1 < str.length()) {
|
|
current.append(ch).append(str.charAt(i + 1));
|
|
i += 2;
|
|
continue;
|
|
}
|
|
if (ch == '(') {
|
|
++depth;
|
|
} else if (ch == ')') {
|
|
--depth;
|
|
}
|
|
if (ch == ',' && depth == 0) {
|
|
args.add(current.toString().trim());
|
|
current = new StringBuilder();
|
|
} else {
|
|
current.append(ch);
|
|
}
|
|
++i;
|
|
}
|
|
if (current.length() > 0) {
|
|
args.add(current.toString().trim());
|
|
}
|
|
return args;
|
|
}
|
|
|
|
private static String handleFraction(List<String> options, List<String> args, String mainCommandName, Map<String, Object> context) {
|
|
if (args.size() != 2) {
|
|
return "\\text{\u9519\u8bef\u7684\u5206\u6570\u53c2\u6570}";
|
|
}
|
|
return String.format("\\dfrac{%s}{%s}", args.get(0), args.get(1));
|
|
}
|
|
|
|
private static String handleRoot(List<String> options, List<String> args, String mainCommandName, Map<String, Object> context) {
|
|
if (args.isEmpty()) {
|
|
return "";
|
|
}
|
|
if (args.size() == 1) {
|
|
return String.format("\\sqrt{%s}", args.get(0));
|
|
}
|
|
return String.format("\\sqrt[%s]{%s}", args.get(0), args.get(1));
|
|
}
|
|
|
|
private static String handleIntegral(List<String> options, List<String> args, String mainCommandName, Map<String, Object> context) {
|
|
String symbol = "\\int";
|
|
if (options.stream().anyMatch(o -> o.toLowerCase().startsWith("\\su"))) {
|
|
symbol = "\\sum";
|
|
} else if (options.stream().anyMatch(o -> o.toLowerCase().startsWith("\\pr"))) {
|
|
symbol = "\\prod";
|
|
} else {
|
|
String fc = options.stream().filter(o -> o.toLowerCase().startsWith("\\fc")).findFirst().orElse(null);
|
|
String vc = options.stream().filter(o -> o.toLowerCase().startsWith("\\vc")).findFirst().orElse(null);
|
|
if (fc != null) {
|
|
symbol = EqToLatexUtil.escapeChar(EqToLatexUtil.getCharFromOpt(fc, 3, ""));
|
|
} else if (vc != null) {
|
|
symbol = EqToLatexUtil.escapeChar(EqToLatexUtil.getCharFromOpt(vc, 3, ""));
|
|
}
|
|
}
|
|
if (args.isEmpty()) {
|
|
return symbol;
|
|
}
|
|
String lower = args.get(0);
|
|
String upper = args.size() > 1 ? args.get(1) : "";
|
|
String integrand = args.size() > 2 ? args.subList(2, args.size()).stream().collect(Collectors.joining(" ")) : "";
|
|
String limits = !lower.isEmpty() || !upper.isEmpty() ? String.format("^{%s}_{%s}", upper, lower) : "";
|
|
return String.format("%s%s %s", symbol, limits, integrand);
|
|
}
|
|
|
|
private static String handleScript(List<String> options, List<String> args, String mainCommandName, Map<String, Object> context) {
|
|
ArrayList<String> argsCopy = new ArrayList<String>(args);
|
|
if (argsCopy.size() > 2) {
|
|
String arrayContent = String.join((CharSequence)"\\\\", argsCopy);
|
|
return String.format("\\begin{array}{l}%s\\end{array}", arrayContent);
|
|
}
|
|
String up = "";
|
|
String down = "";
|
|
boolean doOptExists = options.stream().anyMatch(o -> o.toLowerCase().startsWith("\\do"));
|
|
if (argsCopy.size() == 2) {
|
|
up = (String)argsCopy.remove(0);
|
|
down = (String)argsCopy.remove(0);
|
|
} else if (argsCopy.size() == 1) {
|
|
if (doOptExists) {
|
|
down = (String)argsCopy.remove(0);
|
|
} else {
|
|
up = (String)argsCopy.remove(0);
|
|
}
|
|
}
|
|
if ("o".equals(context.get("parentCommand"))) {
|
|
String upTrimmed = up.trim();
|
|
if ("\u2192".equals(upTrimmed) || "\\rightarrow".equals(upTrimmed)) {
|
|
return "OVERSET_ARROW";
|
|
}
|
|
if ("^".equals(upTrimmed) || "IS_HAT".equals(upTrimmed)) {
|
|
return "OVERSET_HAT";
|
|
}
|
|
if (!up.isEmpty() && !down.isEmpty()) {
|
|
return "OVER_UNDER_SCRIPT:" + up + "," + down;
|
|
}
|
|
if (!up.isEmpty()) {
|
|
return "OVER_SCRIPT:" + up;
|
|
}
|
|
if (!down.isEmpty()) {
|
|
return "UNDER_SCRIPT:" + down;
|
|
}
|
|
}
|
|
StringBuilder output = new StringBuilder();
|
|
if (!up.isEmpty()) {
|
|
output.append(String.format("^{%s}", up));
|
|
}
|
|
if (!down.isEmpty()) {
|
|
output.append(String.format("_{%s}", down));
|
|
}
|
|
return output.toString();
|
|
}
|
|
|
|
private static String handleBrackets(List<String> options, List<String> args, String mainCommandName, Map<String, Object> context) {
|
|
String content = String.join((CharSequence)",", args);
|
|
HashMap<String, String> delimiterPairMap = new HashMap<String, String>();
|
|
delimiterPairMap.put("(", ")");
|
|
delimiterPairMap.put("[", "]");
|
|
delimiterPairMap.put("{", "}");
|
|
delimiterPairMap.put("<", ">");
|
|
String left = null;
|
|
String right = null;
|
|
String bcOpt = options.stream().filter(o -> o.startsWith("\\bc")).findFirst().orElse(null);
|
|
String lcOpt = options.stream().filter(o -> o.startsWith("\\lc")).findFirst().orElse(null);
|
|
String rcOpt = options.stream().filter(o -> o.startsWith("\\rc")).findFirst().orElse(null);
|
|
if (bcOpt != null) {
|
|
String charStr = EqToLatexUtil.getCharFromOpt(bcOpt, 3, "|");
|
|
left = EqToLatexUtil.getDelimiter(charStr);
|
|
right = EqToLatexUtil.getDelimiter(delimiterPairMap.getOrDefault(charStr, charStr));
|
|
} else {
|
|
if (lcOpt != null) {
|
|
left = EqToLatexUtil.getDelimiter(EqToLatexUtil.getCharFromOpt(lcOpt, 3, "("));
|
|
}
|
|
if (rcOpt != null) {
|
|
right = EqToLatexUtil.getDelimiter(EqToLatexUtil.getCharFromOpt(rcOpt, 3, ")"));
|
|
}
|
|
}
|
|
if (bcOpt == null && lcOpt == null && rcOpt == null) {
|
|
left = "(";
|
|
right = ")";
|
|
}
|
|
return String.format("\\left%s %s \\right%s", StringUtils.isNotBlank((CharSequence)left) ? left : ".", content, StringUtils.isNotBlank((CharSequence)right) ? right : ".");
|
|
}
|
|
|
|
private static String getDelimiter(String ch) {
|
|
return "{".equals(ch) || "}".equals(ch) ? "\\" + ch : ch;
|
|
}
|
|
|
|
private static String handleArray(List<String> options, List<String> args, String mainCommandName, Map<String, Object> context) {
|
|
if (args.isEmpty()) {
|
|
return "";
|
|
}
|
|
if (args.size() == 1) {
|
|
return args.get(0);
|
|
}
|
|
int cols = 1;
|
|
String colAlign = "c";
|
|
for (String opt : options) {
|
|
Matcher colMatch = Pattern.compile("^\\\\co(\\d+)$").matcher(opt);
|
|
if (colMatch.matches()) {
|
|
cols = Integer.parseInt(colMatch.group(1));
|
|
}
|
|
if ("\\al".equals(opt)) {
|
|
colAlign = "l";
|
|
continue;
|
|
}
|
|
if ("\\ac".equals(opt)) {
|
|
colAlign = "c";
|
|
continue;
|
|
}
|
|
if (!"\\ar".equals(opt)) continue;
|
|
colAlign = "r";
|
|
}
|
|
StringBuilder alignSpec = new StringBuilder();
|
|
for (int i = 0; i < cols; ++i) {
|
|
alignSpec.append(colAlign);
|
|
}
|
|
ArrayList<String> matrixRows = new ArrayList<String>();
|
|
int i = 0;
|
|
while ((double)i < Math.ceil((double)args.size() / (double)cols)) {
|
|
ArrayList<String> row = new ArrayList<String>(args.subList(i * cols, Math.min((i + 1) * cols, args.size())));
|
|
while (row.size() < cols) {
|
|
row.add("");
|
|
}
|
|
matrixRows.add(String.join((CharSequence)" & ", row));
|
|
++i;
|
|
}
|
|
return String.format("\\begin{array}{%s}%s\\end{array}", alignSpec, String.join((CharSequence)"\\\\", matrixRows));
|
|
}
|
|
|
|
private static String handleBox(List<String> options, List<String> args, String mainCommandName, Map<String, Object> context) {
|
|
if (options.stream().anyMatch(o -> o.equalsIgnoreCase("\\to"))) {
|
|
return args.isEmpty() ? "\\overline{}" : String.format("\\overline{%s}", args.get(0));
|
|
}
|
|
String content = String.join((CharSequence)",", args);
|
|
return args.isEmpty() ? "\\boxed{}" : String.format("\\boxed{%s}", content);
|
|
}
|
|
|
|
private static String handleList(List<String> options, List<String> args, String mainCommandName, Map<String, Object> context) {
|
|
return String.join((CharSequence)",\\ ", args);
|
|
}
|
|
|
|
private static String handleOverstrike(List<String> options, List<String> args, String mainCommandName, Map<String, Object> context) {
|
|
String limitResult;
|
|
String firstArg;
|
|
if (args.isEmpty()) {
|
|
return "";
|
|
}
|
|
if (args.size() >= 1 && ("lim".equals(firstArg = args.get(0).trim().toLowerCase()) || "limit".equals(firstArg)) && (limitResult = EqToLatexUtil.handleLimitExpression(args)) != null) {
|
|
return limitResult;
|
|
}
|
|
String chemResult = EqToLatexUtil.handleChemicalSymbols(args, context);
|
|
if (chemResult != null) {
|
|
return chemResult;
|
|
}
|
|
boolean allArgsAreScriptMarkers = true;
|
|
for (String arg : args) {
|
|
if (EqToLatexUtil.isScriptMarker(arg)) continue;
|
|
allArgsAreScriptMarkers = false;
|
|
break;
|
|
}
|
|
if (allArgsAreScriptMarkers) {
|
|
boolean hasAlignCenter = options.stream().anyMatch(o -> "\\ac".equalsIgnoreCase((String)o));
|
|
if (hasAlignCenter) {
|
|
ArrayList<String> scriptContents = new ArrayList<String>();
|
|
for (String script2 : args) {
|
|
if (!script2.startsWith("OVER_SCRIPT:")) continue;
|
|
scriptContents.add(script2.substring("OVER_SCRIPT:".length()).trim());
|
|
}
|
|
if (!scriptContents.isEmpty()) {
|
|
String result = (String)scriptContents.get(scriptContents.size() - 1);
|
|
for (int i = scriptContents.size() - 2; i >= 0; --i) {
|
|
result = String.format("\\rlap{%s}{%s}", scriptContents.get(i), result);
|
|
}
|
|
return String.format("^{%s}", result);
|
|
}
|
|
}
|
|
StringBuilder up = new StringBuilder();
|
|
StringBuilder down = new StringBuilder();
|
|
args.forEach(script -> {
|
|
if (script.startsWith("OVER_UNDER_SCRIPT:")) {
|
|
String[] parts = script.substring("OVER_UNDER_SCRIPT:".length()).split(",", 2);
|
|
up.append(up.length() > 0 ? " " : "").append(parts[0]);
|
|
down.append(down.length() > 0 ? " " : "").append(parts[1]);
|
|
} else if (script.startsWith("OVER_SCRIPT:")) {
|
|
up.append(up.length() > 0 ? " " : "").append(script.substring("OVER_SCRIPT:".length()));
|
|
} else if (script.startsWith("UNDER_SCRIPT:")) {
|
|
down.append(down.length() > 0 ? " " : "").append(script.substring("UNDER_SCRIPT:".length()));
|
|
} else if ("OVERSET_ARROW".equals(script)) {
|
|
up.append(up.length() > 0 ? " " : "").append("\\rightarrow");
|
|
} else if ("OVERSET_HAT".equals(script)) {
|
|
up.append(up.length() > 0 ? " " : "").append("^");
|
|
} else {
|
|
MathJaxScripts extracted = EqToLatexUtil.extractMathJaxScripts(script);
|
|
if (!extracted.up.isEmpty()) {
|
|
up.append(up.length() > 0 ? " " : "").append(extracted.up);
|
|
}
|
|
if (!extracted.down.isEmpty()) {
|
|
down.append(down.length() > 0 ? " " : "").append(extracted.down);
|
|
}
|
|
}
|
|
});
|
|
String newUp = up.toString().replaceAll("\\s?OVER_SCRIPT:|\\s?UNDER_SCRIPT:", "");
|
|
String newDown = down.toString().replaceAll("\\s?OVER_SCRIPT:|\\s?UNDER_SCRIPT:", "");
|
|
StringBuilder output = new StringBuilder();
|
|
if (up.length() > 0) {
|
|
output.append(String.format("^{%s}", newUp));
|
|
}
|
|
if (down.length() > 0) {
|
|
output.append(String.format("_{%s}", newDown));
|
|
}
|
|
return output.toString();
|
|
}
|
|
int baseIndex = -1;
|
|
for (int i = 0; i < args.size(); ++i) {
|
|
if (EqToLatexUtil.isScriptMarker(args.get(i))) continue;
|
|
baseIndex = i;
|
|
break;
|
|
}
|
|
if (baseIndex == -1) {
|
|
String result = args.get(0);
|
|
for (int i = 1; i < args.size(); ++i) {
|
|
result = String.format("\\rlap{%s}{%s}", args.get(i), result);
|
|
}
|
|
return result;
|
|
}
|
|
String base = args.get(baseIndex);
|
|
base = EqToLatexUtil.resolveNestedScripts(base);
|
|
ArrayList<String> otherArgs = new ArrayList<String>();
|
|
for (int i = 0; i < args.size(); ++i) {
|
|
if (i == baseIndex) continue;
|
|
otherArgs.add(args.get(i));
|
|
}
|
|
boolean otherArgsAreScriptMarkers = true;
|
|
for (String arg : otherArgs) {
|
|
if (EqToLatexUtil.isScriptMarker(arg)) continue;
|
|
otherArgsAreScriptMarkers = false;
|
|
break;
|
|
}
|
|
if (!otherArgs.isEmpty() && otherArgsAreScriptMarkers) {
|
|
StringBuilder over = new StringBuilder();
|
|
StringBuilder under = new StringBuilder();
|
|
otherArgs.forEach(script -> {
|
|
if ("OVERSET_ARROW".equals(script)) {
|
|
over.append("\\rightarrow");
|
|
} else if ("OVERSET_HAT".equals(script)) {
|
|
over.append("^");
|
|
} else if (script.startsWith("OVER_UNDER_SCRIPT:")) {
|
|
String[] parts = script.substring("OVER_UNDER_SCRIPT:".length()).split(",", 2);
|
|
over.append(over.length() > 0 ? " " : "").append(parts[0]);
|
|
under.append(under.length() > 0 ? " " : "").append(parts[1]);
|
|
} else if (script.startsWith("OVER_SCRIPT:")) {
|
|
over.append(over.length() > 0 ? " " : "").append(script.substring("OVER_SCRIPT:".length()));
|
|
} else if (script.startsWith("UNDER_SCRIPT:")) {
|
|
under.append(under.length() > 0 ? " " : "").append(script.substring("UNDER_SCRIPT:".length()));
|
|
}
|
|
});
|
|
if ("^".equals(over.toString())) {
|
|
return String.format("\\hat{%s}", base);
|
|
}
|
|
String cleanOver = over.toString().replace(" ", "").replace("\\to", "\\rightarrow").replace("\u2192", "\\rightarrow");
|
|
if ("\\rightarrow".equals(cleanOver) || cleanOver.contains("\\rightarrow") && (cleanOver.matches(".*[\u2014\u2013\u2212\u2015\u2012\u2010\\-].*") || cleanOver.contains("OVERSET_ARROW"))) {
|
|
return String.format("\\overrightarrow{%s}", base);
|
|
}
|
|
if ("\\rightarrow".equals(over.toString())) {
|
|
return String.format("\\overrightarrow{%s}", base);
|
|
}
|
|
String result = base;
|
|
if (over.length() > 0) {
|
|
result = String.format("\\overset{%s}{%s}", over.toString(), result);
|
|
}
|
|
if (under.length() > 0) {
|
|
result = String.format("\\underset{%s}{%s}", under.toString(), result);
|
|
}
|
|
return result;
|
|
}
|
|
String result = base;
|
|
for (int i = otherArgs.size() - 1; i >= 0; --i) {
|
|
result = String.format("\\rlap{%s}{%s}", otherArgs.get(i), result);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private static String handleChemicalSymbols(List<String> args, Map<String, Object> context) {
|
|
if (args.isEmpty()) {
|
|
return null;
|
|
}
|
|
String firstArg = args.get(0).trim();
|
|
if (LONG_EQUALS_PATTERN.matcher(firstArg).matches() && firstArg.length() >= 3) {
|
|
return EqToLatexUtil.handleLongEqual(args);
|
|
}
|
|
if ((firstArg.contains("\u2192") || firstArg.contains("\\rightarrow")) && !firstArg.contains("\u2190") && !firstArg.contains("\\leftarrow")) {
|
|
return EqToLatexUtil.handleRightArrow(args);
|
|
}
|
|
if (firstArg.matches("^\u2190[\u2015\uff0d\u2014\\-]+$") || firstArg.contains("\u2190") || firstArg.contains("\\leftarrow")) {
|
|
return EqToLatexUtil.handleLeftArrow(args);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private static String handleLongEqual(List<String> args) {
|
|
String upper = "";
|
|
String lower = "";
|
|
for (int i = 1; i < args.size(); ++i) {
|
|
String content;
|
|
String arg = args.get(i);
|
|
if (arg.startsWith("OVER_SCRIPT:")) {
|
|
upper = (upper + " " + arg.substring("OVER_SCRIPT:".length())).trim();
|
|
continue;
|
|
}
|
|
if (arg.startsWith("UNDER_SCRIPT:")) {
|
|
lower = (lower + " " + arg.substring("UNDER_SCRIPT:".length())).trim();
|
|
continue;
|
|
}
|
|
if (arg.startsWith("OVER_UNDER_SCRIPT:")) {
|
|
String[] parts = arg.substring("OVER_UNDER_SCRIPT:".length()).split(",", 2);
|
|
upper = (upper + " " + parts[0]).trim();
|
|
lower = (lower + " " + (parts.length > 1 ? parts[1] : "")).trim();
|
|
continue;
|
|
}
|
|
if (arg.startsWith("^{") && arg.endsWith("}")) {
|
|
content = arg.substring(2, arg.length() - 1);
|
|
upper = (upper + " " + content).trim();
|
|
continue;
|
|
}
|
|
if (!arg.startsWith("_{") || !arg.endsWith("}")) continue;
|
|
content = arg.substring(2, arg.length() - 1);
|
|
lower = (lower + " " + content).trim();
|
|
}
|
|
if (!upper.isEmpty() && !lower.isEmpty()) {
|
|
return String.format("\\xlongequal[%s]{%s}", lower, upper);
|
|
}
|
|
if (!upper.isEmpty()) {
|
|
return String.format("\\xlongequal{%s}", upper);
|
|
}
|
|
return "\\xlongequal{}";
|
|
}
|
|
|
|
private static String handleRightArrow(List<String> args) {
|
|
String upper = "";
|
|
String lower = "";
|
|
for (int i = 1; i < args.size(); ++i) {
|
|
String content;
|
|
String arg = args.get(i);
|
|
if (arg.startsWith("OVER_SCRIPT:")) {
|
|
upper = (upper + " " + arg.substring("OVER_SCRIPT:".length())).trim();
|
|
continue;
|
|
}
|
|
if (arg.startsWith("UNDER_SCRIPT:")) {
|
|
lower = (lower + " " + arg.substring("UNDER_SCRIPT:".length())).trim();
|
|
continue;
|
|
}
|
|
if (arg.startsWith("OVER_UNDER_SCRIPT:")) {
|
|
String[] parts = arg.substring("OVER_UNDER_SCRIPT:".length()).split(",", 2);
|
|
upper = (upper + " " + parts[0]).trim();
|
|
lower = (lower + " " + (parts.length > 1 ? parts[1] : "")).trim();
|
|
continue;
|
|
}
|
|
if (arg.startsWith("^{") && arg.endsWith("}")) {
|
|
content = arg.substring(2, arg.length() - 1);
|
|
upper = (upper + " " + content).trim();
|
|
continue;
|
|
}
|
|
if (!arg.startsWith("_{") || !arg.endsWith("}")) continue;
|
|
content = arg.substring(2, arg.length() - 1);
|
|
lower = (lower + " " + content).trim();
|
|
}
|
|
if (!upper.isEmpty() && !lower.isEmpty()) {
|
|
return String.format("\\xrightarrow[%s]{%s}", lower, upper);
|
|
}
|
|
if (!upper.isEmpty()) {
|
|
return String.format("\\xrightarrow{%s}", upper);
|
|
}
|
|
if (!lower.isEmpty()) {
|
|
return String.format("\\xrightarrow[%s]{}", lower);
|
|
}
|
|
return "\\xrightarrow{}";
|
|
}
|
|
|
|
private static String handleLeftArrow(List<String> args) {
|
|
String upper = "";
|
|
String lower = "";
|
|
for (int i = 1; i < args.size(); ++i) {
|
|
String content;
|
|
String arg = args.get(i);
|
|
if (arg.startsWith("OVER_SCRIPT:")) {
|
|
upper = (upper + " " + arg.substring("OVER_SCRIPT:".length())).trim();
|
|
continue;
|
|
}
|
|
if (arg.startsWith("UNDER_SCRIPT:")) {
|
|
lower = (lower + " " + arg.substring("UNDER_SCRIPT:".length())).trim();
|
|
continue;
|
|
}
|
|
if (arg.startsWith("OVER_UNDER_SCRIPT:")) {
|
|
String[] parts = arg.substring("OVER_UNDER_SCRIPT:".length()).split(",", 2);
|
|
upper = (upper + " " + parts[0]).trim();
|
|
lower = (lower + " " + (parts.length > 1 ? parts[1] : "")).trim();
|
|
continue;
|
|
}
|
|
if (arg.startsWith("^{") && arg.endsWith("}")) {
|
|
content = arg.substring(2, arg.length() - 1);
|
|
upper = (upper + " " + content).trim();
|
|
continue;
|
|
}
|
|
if (!arg.startsWith("_{") || !arg.endsWith("}")) continue;
|
|
content = arg.substring(2, arg.length() - 1);
|
|
lower = (lower + " " + content).trim();
|
|
}
|
|
if (!upper.isEmpty() && !lower.isEmpty()) {
|
|
return String.format("\\xleftarrow[%s]{%s}", lower, upper);
|
|
}
|
|
if (!upper.isEmpty()) {
|
|
return String.format("\\xleftarrow{%s}", upper);
|
|
}
|
|
if (!lower.isEmpty()) {
|
|
return String.format("\\xleftarrow[%s]{}", lower);
|
|
}
|
|
return "\\xleftarrow{}";
|
|
}
|
|
|
|
private static String handleDisplacement(List<String> options, List<String> args, String mainCommandName, Map<String, Object> context) {
|
|
if (args.isEmpty()) {
|
|
return "";
|
|
}
|
|
return String.join((CharSequence)" ", args);
|
|
}
|
|
|
|
private static String handleUp(List<String> options, List<String> args, String mainCommandName, Map<String, Object> context) {
|
|
return String.format("^{%s}", String.join((CharSequence)",", args));
|
|
}
|
|
|
|
private static String handleDo(List<String> options, List<String> args, String mainCommandName, Map<String, Object> context) {
|
|
return String.format("_{%s}", String.join((CharSequence)",", args));
|
|
}
|
|
|
|
public static String replaceJbyuWithMeasurements(String input) {
|
|
String pattern = "(JBYU1JBYU)+";
|
|
Matcher matcher = Pattern.compile(pattern).matcher(input);
|
|
StringBuffer result = new StringBuffer();
|
|
while (matcher.find()) {
|
|
String match = matcher.group();
|
|
int count = match.length() / 4;
|
|
int groups = (count + 4) / 5;
|
|
StringBuilder replacement = new StringBuilder();
|
|
for (int i = 0; i < groups; ++i) {
|
|
replacement.append("\\\\underline{\\\\qquad}");
|
|
}
|
|
matcher.appendReplacement(result, replacement.toString());
|
|
}
|
|
matcher.appendTail(result);
|
|
return result.toString();
|
|
}
|
|
|
|
private static String handleLimitExpression(List<String> args) {
|
|
Matcher m;
|
|
if (args.size() < 2) {
|
|
return null;
|
|
}
|
|
String limitArg = args.get(1).trim();
|
|
Pattern nestedDoPattern = Pattern.compile("\\\\s\\\\do4\\(\\\\s\\\\do4\\(([^)]+)\\)\\)");
|
|
Matcher match = nestedDoPattern.matcher(limitArg);
|
|
if (match.find()) {
|
|
return String.format("\\lim_{%s}", match.group(1));
|
|
}
|
|
Pattern singleDoPattern = Pattern.compile("\\\\s\\\\do4\\(([^)]+)\\)");
|
|
Matcher singleMatch = singleDoPattern.matcher(limitArg);
|
|
if (singleMatch.find()) {
|
|
return String.format("\\lim_{%s}", singleMatch.group(1));
|
|
}
|
|
if (limitArg.startsWith("_{") && limitArg.endsWith("}")) {
|
|
return String.format("\\lim_{%s}", limitArg.substring(2, limitArg.length() - 1));
|
|
}
|
|
if (limitArg.matches("^_\\{_\\{([^}]+)}}$") && (m = Pattern.compile("^_\\{_\\{([^}]+)}}$").matcher(limitArg)).find()) {
|
|
return String.format("\\lim_{%s}", m.group(1));
|
|
}
|
|
Pattern anySubPattern = Pattern.compile("_\\{([^}]+)}");
|
|
Matcher anySubMatch = anySubPattern.matcher(limitArg);
|
|
if (anySubMatch.find()) {
|
|
Matcher inner;
|
|
String limitContent = anySubMatch.group(1);
|
|
if (limitContent.contains("_{") && limitContent.contains("}") && (inner = Pattern.compile("_\\{([^}]+)}").matcher(limitContent)).find()) {
|
|
limitContent = inner.group(1);
|
|
}
|
|
return String.format("\\lim_{%s}", limitContent);
|
|
}
|
|
if (!limitArg.isEmpty() && !limitArg.contains("\\")) {
|
|
return String.format("\\lim_{%s}", limitArg);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private static boolean isConsecutiveScript(String first, String second) {
|
|
if (first == null || second == null) {
|
|
return false;
|
|
}
|
|
String firstTrimmed = first.trim();
|
|
String secondTrimmed = second.trim();
|
|
boolean firstIsSuper = firstTrimmed.matches(".*\\\\?\\^\\{\\s*[^}]*\\s*}$");
|
|
boolean secondIsSuper = secondTrimmed.matches("^\\s*\\\\?\\^\\{.*");
|
|
boolean firstIsSub = firstTrimmed.matches(".*\\\\?_\\{\\s*[^}]*\\s*}$");
|
|
boolean secondIsSub = secondTrimmed.matches("^\\s*\\\\?_\\{.*");
|
|
return firstIsSuper && secondIsSuper || firstIsSub && secondIsSub;
|
|
}
|
|
|
|
private static String mergeConsecutiveScripts(String first, String second) {
|
|
if (EqToLatexUtil.isConsecutiveScript(first, second)) {
|
|
Matcher firstMatch = Pattern.compile("(\\\\?)(\\^|_)\\{([^}]*)}$").matcher(first.trim());
|
|
if (!firstMatch.find()) {
|
|
return first + second;
|
|
}
|
|
String firstBackslash = firstMatch.group(1);
|
|
String scriptType = firstMatch.group(2);
|
|
String firstContent = firstMatch.group(3);
|
|
Matcher secondMatch = Pattern.compile("^\\s*(\\\\?)(\\^|_)\\{([^}]*)}").matcher(second.trim());
|
|
if (!secondMatch.find() || !secondMatch.group(2).equals(scriptType)) {
|
|
return first + second;
|
|
}
|
|
String secondContent = secondMatch.group(3);
|
|
String mergedContent = firstContent + secondContent;
|
|
String beforeScript = first.replaceAll("(\\\\?)(\\^|_)\\{[^}]*}$", "");
|
|
return beforeScript + firstBackslash + scriptType + "{" + mergedContent + "}";
|
|
}
|
|
return first + second;
|
|
}
|
|
|
|
private static String postProcessConsecutiveScripts(String text) {
|
|
String previousText;
|
|
String result = text;
|
|
do {
|
|
previousText = result;
|
|
result = result.replaceAll("(\\\\?)\\^\\{([^}]*?)}\\s+(\\\\?)\\^\\{([^}]*?)}", "$1^{$2$4}");
|
|
} while (!(result = result.replaceAll("(\\\\?)_\\{([^}]*?)}\\s+(\\\\?)_\\{([^}]*?)}", "$1_{$2$4}")).equals(previousText));
|
|
return result;
|
|
}
|
|
|
|
private static boolean isScriptMarker(String str) {
|
|
if (str == null) {
|
|
return false;
|
|
}
|
|
return str.equals("OVERSET_ARROW") || str.equals("OVERSET_HAT") || str.startsWith("OVER_UNDER_SCRIPT:") || str.startsWith("OVER_SCRIPT:") || str.startsWith("UNDER_SCRIPT:") || str.matches("^\\^\\{.*}$") || str.matches("^_\\{.*}$") || str.matches("^(\\^\\{[^}]*})+$") || str.matches("^(_\\{[^}]*})+$") || str.matches("^(\\^\\{[^}]*}|_\\{[^}]*})+$");
|
|
}
|
|
|
|
private static MathJaxScripts extractMathJaxScripts(String mathjaxScript) {
|
|
MathJaxScripts result = new MathJaxScripts();
|
|
Matcher upMatcher = Pattern.compile("\\^\\{([^}]*)}").matcher(mathjaxScript);
|
|
while (upMatcher.find()) {
|
|
result.up = result.up + upMatcher.group(1);
|
|
}
|
|
Matcher downMatcher = Pattern.compile("_\\{([^}]*)}").matcher(mathjaxScript);
|
|
while (downMatcher.find()) {
|
|
result.down = result.down + downMatcher.group(1);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private static String resolveNestedScripts(String text) {
|
|
if (text == null) {
|
|
return null;
|
|
}
|
|
String result = text;
|
|
result = result.replaceAll("OVERSET_ARROW", "\\\\rightarrow");
|
|
result = result.replaceAll("OVERSET_HAT", "^");
|
|
Pattern p = Pattern.compile("OVER_UNDER_SCRIPT:([^ ]+)");
|
|
Matcher m = p.matcher(result);
|
|
StringBuffer sb = new StringBuffer();
|
|
while (m.find()) {
|
|
String content = m.group(1);
|
|
String[] parts = content.split(",", 2);
|
|
String up = parts[0].trim();
|
|
String down = (parts.length > 1 ? parts[1] : "").trim();
|
|
m.appendReplacement(sb, "^{" + up + "}_{" + down + "}");
|
|
}
|
|
m.appendTail(sb);
|
|
result = sb.toString();
|
|
result = result.replaceAll("OVER_SCRIPT:([^ ]+)", "^{$1}");
|
|
result = result.replaceAll("UNDER_SCRIPT:([^ ]+)", "_{$1}");
|
|
return result;
|
|
}
|
|
|
|
private static boolean isSpecialScriptMarker(String str) {
|
|
return EqToLatexUtil.isScriptMarker(str);
|
|
}
|
|
|
|
private static String mergeSpecialScriptMarkers(String first, String second) {
|
|
String upFirst = "";
|
|
String downFirst = "";
|
|
if (first.startsWith("OVER_SCRIPT:")) {
|
|
upFirst = first.substring("OVER_SCRIPT:".length());
|
|
} else if (first.startsWith("UNDER_SCRIPT:")) {
|
|
downFirst = first.substring("UNDER_SCRIPT:".length());
|
|
} else if (first.startsWith("OVER_UNDER_SCRIPT:")) {
|
|
String[] parts = first.substring("OVER_UNDER_SCRIPT:".length()).split(",", 2);
|
|
upFirst = parts[0];
|
|
downFirst = parts.length > 1 ? parts[1] : "";
|
|
}
|
|
String upSecond = "";
|
|
String downSecond = "";
|
|
if (second.startsWith("OVER_SCRIPT:")) {
|
|
upSecond = second.substring("OVER_SCRIPT:".length());
|
|
} else if (second.startsWith("UNDER_SCRIPT:")) {
|
|
downSecond = second.substring("UNDER_SCRIPT:".length());
|
|
} else if (second.startsWith("OVER_UNDER_SCRIPT:")) {
|
|
String[] parts = second.substring("OVER_UNDER_SCRIPT:".length()).split(",", 2);
|
|
upSecond = parts[0];
|
|
downSecond = parts.length > 1 ? parts[1] : "";
|
|
}
|
|
String upMerged = (upFirst + upSecond).trim();
|
|
String downMerged = (downFirst + downSecond).trim();
|
|
if (!upMerged.isEmpty() && !downMerged.isEmpty()) {
|
|
return "OVER_UNDER_SCRIPT:" + upMerged + "," + downMerged;
|
|
}
|
|
if (!upMerged.isEmpty()) {
|
|
return "OVER_SCRIPT:" + upMerged;
|
|
}
|
|
if (!downMerged.isEmpty()) {
|
|
return "UNDER_SCRIPT:" + downMerged;
|
|
}
|
|
return first;
|
|
}
|
|
|
|
public static void main(String[] args) {
|
|
EqToLatexUtil.expect("P1-1", "eq \\o\\al(\\s\\up4(\u00b7),\\s\\do4(\u00b7))", "^{\\cdot}_{\\cdot}");
|
|
EqToLatexUtil.expect("P1-2", "eq \\o(Cl,\\s\\up6(\\s\\up4(\u00b7\u00b7)),\\s\\do4(\\s\\do4(\u00b7\u00b7)))", "\\underset{_{\\cdot \\cdot}}{\\overset{^{\\cdot \\cdot}}{Cl}}");
|
|
EqToLatexUtil.expect("P1-3", "eq \\o\\al(\\s\\up4(\u00b7),\\s\\do4(\u00b7))", "^{\\cdot}_{\\cdot}");
|
|
EqToLatexUtil.expect("P3-1", "\\JBYU2BEGINeq \\\\JBYU2END\\JBYU2BEGINo\\al(\\JBYU2END\\JBYU2BEGIN\\s\\up4(\u00b7)\\JBYU2END,\\JBYU2BEGIN\\s\\do4(\u00b7)\\JBYU2END)\\JBYU2END", "^{\\cdot}_{\\cdot}");
|
|
EqToLatexUtil.expect("P3-2", "\\JBYU2BEGINeq \\\\JBYU2END\\JBYU2BEGINo(Cl,\\s\\up6(\\JBYU2BEGIN\\s\\up4(\u00b7\u00b7)\\JBYU2END),\\s\\do4(\\JBYU2BEGIN\\s\\do4(\u00b7\u00b7)\\JBYU2END))\\JBYU2END", "\\underset{_{\\cdot \\cdot}}{\\overset{^{\\cdot \\cdot}}{Cl}}");
|
|
EqToLatexUtil.expect("P3-3", "\\JBYU2BEGINeq \\\\JBYU2END\\JBYU2BEGINo\\al(\\JBYU2END\\JBYU2BEGIN\\s\\up4(\u00b7)\\JBYU2END,\\JBYU2BEGIN\\s\\do4(\u00b7)\\JBYU2END)\\JBYU2END", "^{\\cdot}_{\\cdot}");
|
|
System.out.println("OK");
|
|
}
|
|
|
|
private static void expect(String label, String input, String expected) {
|
|
String actual = EqToLatexUtil.convert(input);
|
|
if (!expected.equals(actual)) {
|
|
throw new AssertionError((Object)(label + " expected " + expected + " but got " + actual));
|
|
}
|
|
}
|
|
|
|
static {
|
|
COMMAND_HANDLERS.put("f", EqToLatexUtil::handleFraction);
|
|
COMMAND_HANDLERS.put("r", EqToLatexUtil::handleRoot);
|
|
COMMAND_HANDLERS.put("i", EqToLatexUtil::handleIntegral);
|
|
COMMAND_HANDLERS.put("s", EqToLatexUtil::handleScript);
|
|
COMMAND_HANDLERS.put("b", EqToLatexUtil::handleBrackets);
|
|
COMMAND_HANDLERS.put("a", EqToLatexUtil::handleArray);
|
|
COMMAND_HANDLERS.put("x", EqToLatexUtil::handleBox);
|
|
COMMAND_HANDLERS.put("l", EqToLatexUtil::handleList);
|
|
COMMAND_HANDLERS.put("o", EqToLatexUtil::handleOverstrike);
|
|
COMMAND_HANDLERS.put("d", EqToLatexUtil::handleDisplacement);
|
|
COMMAND_HANDLERS.put("up", EqToLatexUtil::handleUp);
|
|
COMMAND_HANDLERS.put("do", EqToLatexUtil::handleDo);
|
|
}
|
|
|
|
private static class MathJaxScripts {
|
|
String up = "";
|
|
String down = "";
|
|
|
|
private MathJaxScripts() {
|
|
}
|
|
}
|
|
|
|
private static class ArgumentResult {
|
|
boolean success;
|
|
String content;
|
|
int endIndex;
|
|
|
|
ArgumentResult(boolean success, String content, int endIndex) {
|
|
this.success = success;
|
|
this.content = content;
|
|
this.endIndex = endIndex;
|
|
}
|
|
}
|
|
|
|
private static class ParseResult {
|
|
String output;
|
|
int newIndex;
|
|
|
|
ParseResult(String output, int newIndex) {
|
|
this.output = output;
|
|
this.newIndex = newIndex;
|
|
}
|
|
}
|
|
|
|
@FunctionalInterface
|
|
static interface CommandHandler {
|
|
public String handle(List<String> var1, List<String> var2, String var3, Map<String, Object> var4);
|
|
}
|
|
}
|
|
|