Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/SharpEquations/Internal/LaTeX/LaTeXReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1432,6 +1432,10 @@ private string Convert(string latex)
}
if (symbol.Length > 0 && Js.Ch(symbol, 0) != "\"")
uniTeX.Append(symbol);
// a control word not known ends where its letters do: a space ends it for UnicodeMath too, which
// would otherwise read the letters or digits after it as part of it (\sgn r, \sgn2)
else if (symbol.Length > 0)
uniTeX.Append('\\').Append(cw).Append(tex[i] != null && tex[i].Length == 1 && IsAsciiLetterOrDigit(tex[i][0]) ? " " : "");
else if (cw.Length > 0)
uniTeX.Append('\\').Append(cw);
break;
Expand Down
6 changes: 4 additions & 2 deletions src/SharpEquations/Internal/UnicodeMath/UnicodeMathInput.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,9 @@ private static int ClosingQuote(List<string> chars, int open)

/// <summary>resolveCW: replaces control words (\alpha) with the characters they stand for.</summary>
/// <param name="unknown">Called with each control word that is not one (as \foo), which is kept as text.</param>
public static string ResolveControlWords(string unicodemath, Action<string> unknown = null)
/// <param name="tex">Whether the text was read from LaTeX, where the space after a control word only ends it and
/// is not kept in the text of one not known.</param>
public static string ResolveControlWords(string unicodemath, Action<string> unknown = null, bool tex = false)
{
string previous = "";
return ControlWord().Replace(unicodemath, match =>
Expand Down Expand Up @@ -215,7 +217,7 @@ public static string ResolveControlWords(string unicodemath, Action<string> unkn
return GetUnicodeFraction(Js.Ch(cw, 4), Js.Ch(cw, 5));
// not a control word: upright text
unknown?.Invoke("\\" + cw);
return "\"" + match.Value + "\"";
return "\"" + (tex ? "\\" + cw : match.Value) + "\"";
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ private static string ToMathML(string unicodemath, bool display, bool alignmentM
unicodemath = unicodemath.Trim();
var resolved = UnicodeMathInput.ResolveControlWords(unicodemath, strict
? unknown => throw new FormatException("Unknown control word " + unknown + (tex ? " in the LaTeX." : "."))
: null);
: null, tex);
// macros (\def, \newcommand: ⓜ) apply in the equation that defines them
if (!tex && LaTeXReader.HasMacros(resolved))
resolved = LaTeXReader.ExpandMacros(resolved);
Expand Down
23 changes: 22 additions & 1 deletion tests/SharpEquations.Tests/LaTeX/LaTeXReaderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -355,14 +355,35 @@ public async Task RejectsEnvironmentsThatExpandForever()
}

[Test]
[Arguments(@"\foo x", @"Unknown control word \foox in the LaTeX.")]
[Arguments(@"\foo x", @"Unknown control word \foo in the LaTeX.")]
[Arguments(@"a+\undefinedcommand", @"Unknown control word \undefinedcommand in the LaTeX.")]
[Arguments(@"\sgn r", @"Unknown control word \sgn in the LaTeX.")]
[Arguments(@"\lg d", @"Unknown control word \lg in the LaTeX.")]
[Arguments(@"\liminf v", @"Unknown control word \liminf in the LaTeX.")]
[Arguments(@"a \backslash b", @"Unknown control word \backslash in the LaTeX.")]
[Arguments(@"\sgn2", @"Unknown control word \sgn in the LaTeX.")]
public async Task RejectsUnknownControlWordsWhenStrict(string latex, string message)
{
await Assert.That(() => Equation.Parse(latex, EquationFormat.LaTeX, Strict)).Throws<FormatException>().WithMessage(message);
await Assert.That(Equation.Parse(latex, EquationFormat.LaTeX).ToMathML().Descendants(MathMLNamespaces.MathML + "mtext")).IsNotEmpty();
}

[Test]
[Arguments(@"\sgn r", @"\sgn", "r")]
[Arguments(@"\lg d", @"\lg", "d")]
[Arguments(@"\liminf v", @"\liminf", "v")]
[Arguments(@"a \backslash b", @"\backslash", "b")]
[Arguments(@"\sgn r", @"\sgn", "r")]
[Arguments(@"\sgn{r}", @"\sgn", "r")]
[Arguments(@"\text{\sgn} r", @"\sgn", "r")]
[Arguments(@"\sgn2", @"\sgn", "2")]
public async Task EndsUnknownControlWordsAtTheirLetters(string latex, string text, string after)
{
var math = Equation.Parse(latex, EquationFormat.LaTeX).ToMathML();
await Assert.That(math.Descendants(MathMLNamespaces.MathML + "mtext").Select(e => e.Value).ToList()).IsEquivalentTo(new[] { text });
await Assert.That(math.Descendants().Where(e => e.Name.LocalName is "mi" or "mn").Select(e => e.Value).Last()).IsEqualTo(after);
}

[Test]
public async Task KeepsKnownControlWordsWhenStrict()
{
Expand Down
Loading