Skip to content

Commit b11f758

Browse files
rajveermalviyagnprice
authored andcommitted
content: Allow KaTeX parser to report failure reasons
1 parent 88ebff2 commit b11f758

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
@@ -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');
@@ -123,7 +171,10 @@ class _KatexParser {
123171
if (node case dom.Element(localName: 'span')) {
124172
return _parseSpan(node);
125173
} else {
126-
throw _KatexHtmlParseError();
174+
throw _KatexHtmlParseError(
175+
node is dom.Element
176+
? 'unsupported html node: ${node.localName}'
177+
: 'unsupported html node');
127178
}
128179
}));
129180
}
@@ -374,6 +425,7 @@ class _KatexParser {
374425

375426
default:
376427
assert(debugLog('KaTeX: Unsupported CSS class: $spanClass'));
428+
unsupportedCssClasses.add(spanClass);
377429
_hasError = true;
378430
}
379431
}
@@ -427,6 +479,7 @@ class _KatexParser {
427479
// TODO handle more CSS properties
428480
assert(debugLog('KaTeX: Unsupported CSS expression:'
429481
' ${expression.toDebugString()}'));
482+
unsupportedInlineCssProperties.add(property);
430483
_hasError = true;
431484
} else {
432485
throw _KatexHtmlParseError();

0 commit comments

Comments
 (0)