Skip to content

Commit 867b8f3

Browse files
content: Allow KaTeX parser to report failure reasons
1 parent e6c0caf commit 867b8f3

File tree

2 files changed

+73
-5
lines changed

2 files changed

+73
-5
lines changed

lib/model/content.dart

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,8 @@ sealed class MathNode extends ContentNode {
346346
super.debugHtmlNode,
347347
required this.texSource,
348348
required this.nodes,
349+
this.debugHardFailReason,
350+
this.debugSoftFailReason,
349351
});
350352

351353
final String texSource;
@@ -357,6 +359,9 @@ sealed class MathNode extends ContentNode {
357359
/// fallback instead.
358360
final List<KatexNode>? nodes;
359361

362+
final KatexParserHardFailReason? debugHardFailReason;
363+
final KatexParserSoftFailReason? debugSoftFailReason;
364+
360365
@override
361366
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
362367
super.debugFillProperties(properties);
@@ -411,6 +416,8 @@ class MathBlockNode extends MathNode implements BlockContentNode {
411416
super.debugHtmlNode,
412417
required super.texSource,
413418
required super.nodes,
419+
super.debugHardFailReason,
420+
super.debugSoftFailReason,
414421
});
415422
}
416423

@@ -880,6 +887,8 @@ class MathInlineNode extends MathNode implements InlineContentNode {
880887
super.debugHtmlNode,
881888
required super.texSource,
882889
required super.nodes,
890+
super.debugHardFailReason,
891+
super.debugSoftFailReason,
883892
});
884893
}
885894

@@ -921,7 +930,9 @@ class _ZulipInlineContentParser {
921930
return MathInlineNode(
922931
texSource: parsed.texSource,
923932
nodes: parsed.nodes,
924-
debugHtmlNode: debugHtmlNode);
933+
debugHtmlNode: debugHtmlNode,
934+
debugHardFailReason: kDebugMode ? parsed.hardFailReason : null,
935+
debugSoftFailReason: kDebugMode ? parsed.softFailReason : null);
925936
}
926937

927938
UserMentionNode? parseUserMention(dom.Element element) {
@@ -1628,7 +1639,9 @@ class _ZulipContentParser {
16281639
result.add(MathBlockNode(
16291640
texSource: parsed.texSource,
16301641
nodes: parsed.nodes,
1631-
debugHtmlNode: kDebugMode ? firstChild : null));
1642+
debugHtmlNode: kDebugMode ? firstChild : null,
1643+
debugHardFailReason: kDebugMode ? parsed.hardFailReason : null,
1644+
debugSoftFailReason: kDebugMode ? parsed.softFailReason : null));
16321645
} else {
16331646
result.add(UnimplementedBlockContentNode(htmlNode: firstChild));
16341647
}
@@ -1664,7 +1677,9 @@ class _ZulipContentParser {
16641677
result.add(MathBlockNode(
16651678
texSource: parsed.texSource,
16661679
nodes: parsed.nodes,
1667-
debugHtmlNode: debugHtmlNode));
1680+
debugHtmlNode: debugHtmlNode,
1681+
debugHardFailReason: kDebugMode ? parsed.hardFailReason : null,
1682+
debugSoftFailReason: kDebugMode ? parsed.softFailReason : null));
16681683
continue;
16691684
}
16701685
}

lib/model/katex.dart

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,40 @@ import 'binding.dart';
88
import 'content.dart';
99
import 'settings.dart';
1010

11+
/// The failure reason in case the KaTeX parser encountered a
12+
/// `_KatexHtmlParseError` exception.
13+
///
14+
/// Generally this means that parser encountered an unexpected HTML structure,
15+
/// an unsupported HTML node, or an unexpected inline CSS style or CSS class on
16+
/// a specific node.
17+
class KatexParserHardFailReason {
18+
const KatexParserHardFailReason({
19+
required this.error,
20+
required this.stackTrace,
21+
});
22+
23+
final String error;
24+
final StackTrace stackTrace;
25+
}
26+
27+
/// The failure reason in case the KaTeX parser found an unsupported
28+
/// CSS class or unsupported inline CSS style property.
29+
class KatexParserSoftFailReason {
30+
const KatexParserSoftFailReason({
31+
this.unsupportedCssClasses = const [],
32+
this.unsupportedInlineCssProperties = const [],
33+
});
34+
35+
final List<String> unsupportedCssClasses;
36+
final List<String> unsupportedInlineCssProperties;
37+
}
38+
1139
class MathParserResult {
1240
const MathParserResult({
1341
required this.texSource,
1442
required this.nodes,
43+
this.hardFailReason,
44+
this.softFailReason,
1545
});
1646

1747
final String texSource;
@@ -22,6 +52,9 @@ class MathParserResult {
2252
/// CSS style, indicating that the widget should render the [texSource] as a
2353
/// fallback instead.
2454
final List<KatexNode>? nodes;
55+
56+
final KatexParserHardFailReason? hardFailReason;
57+
final KatexParserSoftFailReason? softFailReason;
2558
}
2659

2760
/// Parses the HTML spans containing KaTeX HTML tree.
@@ -87,21 +120,33 @@ MathParserResult? parseMath(dom.Element element, { required bool block }) {
87120
final flagForceRenderKatex =
88121
globalSettings.getBool(BoolGlobalSetting.forceRenderKatex);
89122

123+
KatexParserHardFailReason? hardFailReason;
124+
KatexParserSoftFailReason? softFailReason;
90125
List<KatexNode>? nodes;
91126
if (flagRenderKatex) {
92127
final parser = _KatexParser();
93128
try {
94129
nodes = parser.parseKatexHtml(katexHtmlElement);
95130
} on _KatexHtmlParseError catch (e, st) {
96131
assert(debugLog('$e\n$st'));
132+
hardFailReason = KatexParserHardFailReason(
133+
error: e.message ?? 'unknown',
134+
stackTrace: st);
97135
}
98136

99137
if (parser.hasError && !flagForceRenderKatex) {
100138
nodes = null;
139+
softFailReason = KatexParserSoftFailReason(
140+
unsupportedCssClasses: parser.unsupportedCssClasses,
141+
unsupportedInlineCssProperties: parser.unsupportedInlineCssProperties);
101142
}
102143
}
103144

104-
return MathParserResult(nodes: nodes, texSource: texSource);
145+
return MathParserResult(
146+
nodes: nodes,
147+
texSource: texSource,
148+
hardFailReason: hardFailReason,
149+
softFailReason: softFailReason);
105150
} else {
106151
return null;
107152
}
@@ -111,6 +156,9 @@ class _KatexParser {
111156
bool get hasError => _hasError;
112157
bool _hasError = false;
113158

159+
final unsupportedCssClasses = <String>[];
160+
final unsupportedInlineCssProperties = <String>[];
161+
114162
List<KatexNode> parseKatexHtml(dom.Element element) {
115163
assert(element.localName == 'span');
116164
assert(element.className == 'katex-html');
@@ -122,7 +170,10 @@ class _KatexParser {
122170
if (node case dom.Element(localName: 'span')) {
123171
return _parseSpan(node);
124172
} else {
125-
throw _KatexHtmlParseError();
173+
throw _KatexHtmlParseError(
174+
node is dom.Element
175+
? 'unsupported html node: ${node.localName}'
176+
: 'unsupported html node');
126177
}
127178
}));
128179
}
@@ -357,6 +408,7 @@ class _KatexParser {
357408

358409
default:
359410
assert(debugLog('KaTeX: Unsupported CSS class: $spanClass'));
411+
unsupportedCssClasses.add(spanClass);
360412
_hasError = true;
361413
}
362414
}
@@ -410,6 +462,7 @@ class _KatexParser {
410462
// TODO handle more CSS properties
411463
assert(debugLog('KaTeX: Unsupported CSS expression:'
412464
' ${expression.toDebugString()}'));
465+
unsupportedInlineCssProperties.add(property);
413466
_hasError = true;
414467
} else {
415468
throw _KatexHtmlParseError();

0 commit comments

Comments
 (0)