Skip to content

Commit b0ad5a0

Browse files
content: Allow KaTeX parser to report failure reasons
1 parent cd2f916 commit b0ad5a0

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);
@@ -482,6 +487,8 @@ class MathBlockNode extends MathNode implements BlockContentNode {
482487
super.debugHtmlNode,
483488
required super.texSource,
484489
required super.nodes,
490+
super.debugHardFailReason,
491+
super.debugSoftFailReason,
485492
});
486493
}
487494

@@ -951,6 +958,8 @@ class MathInlineNode extends MathNode implements InlineContentNode {
951958
super.debugHtmlNode,
952959
required super.texSource,
953960
required super.nodes,
961+
super.debugHardFailReason,
962+
super.debugSoftFailReason,
954963
});
955964
}
956965

@@ -992,7 +1001,9 @@ class _ZulipInlineContentParser {
9921001
return MathInlineNode(
9931002
texSource: parsed.texSource,
9941003
nodes: parsed.nodes,
995-
debugHtmlNode: debugHtmlNode);
1004+
debugHtmlNode: debugHtmlNode,
1005+
debugHardFailReason: kDebugMode ? parsed.hardFailReason : null,
1006+
debugSoftFailReason: kDebugMode ? parsed.softFailReason : null);
9961007
}
9971008

9981009
UserMentionNode? parseUserMention(dom.Element element) {
@@ -1699,7 +1710,9 @@ class _ZulipContentParser {
16991710
result.add(MathBlockNode(
17001711
texSource: parsed.texSource,
17011712
nodes: parsed.nodes,
1702-
debugHtmlNode: kDebugMode ? firstChild : null));
1713+
debugHtmlNode: kDebugMode ? firstChild : null,
1714+
debugHardFailReason: kDebugMode ? parsed.hardFailReason : null,
1715+
debugSoftFailReason: kDebugMode ? parsed.softFailReason : null));
17031716
} else {
17041717
result.add(UnimplementedBlockContentNode(htmlNode: firstChild));
17051718
}
@@ -1735,7 +1748,9 @@ class _ZulipContentParser {
17351748
result.add(MathBlockNode(
17361749
texSource: parsed.texSource,
17371750
nodes: parsed.nodes,
1738-
debugHtmlNode: debugHtmlNode));
1751+
debugHtmlNode: debugHtmlNode,
1752+
debugHardFailReason: kDebugMode ? parsed.hardFailReason : null,
1753+
debugSoftFailReason: kDebugMode ? parsed.softFailReason : null));
17391754
continue;
17401755
}
17411756
}

lib/model/katex.dart

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

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

1848
final String texSource;
@@ -23,6 +53,9 @@ class MathParserResult {
2353
/// CSS style, indicating that the widget should render the [texSource] as a
2454
/// fallback instead.
2555
final List<KatexNode>? nodes;
56+
57+
final KatexParserHardFailReason? hardFailReason;
58+
final KatexParserSoftFailReason? softFailReason;
2659
}
2760

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

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

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

105-
return MathParserResult(nodes: nodes, texSource: texSource);
146+
return MathParserResult(
147+
nodes: nodes,
148+
texSource: texSource,
149+
hardFailReason: hardFailReason,
150+
softFailReason: softFailReason);
106151
} else {
107152
return null;
108153
}
@@ -112,6 +157,9 @@ class _KatexParser {
112157
bool get hasError => _hasError;
113158
bool _hasError = false;
114159

160+
final unsupportedCssClasses = <String>[];
161+
final unsupportedInlineCssProperties = <String>[];
162+
115163
List<KatexNode> parseKatexHtml(dom.Element element) {
116164
assert(element.localName == 'span');
117165
assert(element.className == 'katex-html');
@@ -122,7 +170,10 @@ class _KatexParser {
122170
var resultSpans = QueueList<KatexNode>();
123171
for (final node in nodes.reversed) {
124172
if (node is! dom.Element || node.localName != 'span') {
125-
throw _KatexHtmlParseError();
173+
throw _KatexHtmlParseError(
174+
node is dom.Element
175+
? 'unsupported html node: ${node.localName}'
176+
: 'unsupported html node');
126177
}
127178

128179
final span = _parseSpan(node);
@@ -518,6 +569,7 @@ class _KatexParser {
518569

519570
default:
520571
assert(debugLog('KaTeX: Unsupported CSS class: $spanClass'));
572+
unsupportedCssClasses.add(spanClass);
521573
_hasError = true;
522574
}
523575
}
@@ -591,6 +643,7 @@ class _KatexParser {
591643
// TODO handle more CSS properties
592644
assert(debugLog('KaTeX: Unsupported CSS expression:'
593645
' ${expression.toDebugString()}'));
646+
unsupportedInlineCssProperties.add(property);
594647
_hasError = true;
595648
} else {
596649
throw _KatexHtmlParseError();

0 commit comments

Comments
 (0)