@@ -5,7 +5,9 @@ import 'package:html/parser.dart';
5
5
6
6
import '../api/model/model.dart' ;
7
7
import '../api/model/submessage.dart' ;
8
+ import '../log.dart' ;
8
9
import 'code_block.dart' ;
10
+ import 'katex.dart' ;
9
11
10
12
/// A node in a parse tree for Zulip message-style content.
11
13
///
@@ -341,22 +343,52 @@ class CodeBlockSpanNode extends ContentNode {
341
343
}
342
344
343
345
class MathBlockNode extends BlockContentNode {
344
- const MathBlockNode ({super .debugHtmlNode, required this .texSource});
346
+ const MathBlockNode ({
347
+ super .debugHtmlNode,
348
+ required this .texSource,
349
+ required this .nodes,
350
+ });
345
351
346
352
final String texSource;
353
+ final List <KatexNode >? nodes;
347
354
348
355
@override
349
- bool operator == (Object other) {
350
- return other is MathBlockNode && other.texSource == texSource;
356
+ void debugFillProperties (DiagnosticPropertiesBuilder properties) {
357
+ super .debugFillProperties (properties);
358
+ properties.add (StringProperty ('texSource' , texSource));
351
359
}
352
360
353
361
@override
354
- int get hashCode => Object .hash ('MathBlockNode' , texSource);
362
+ List <DiagnosticsNode > debugDescribeChildren () {
363
+ return nodes? .map ((node) => node.toDiagnosticsNode ()).toList () ?? const [];
364
+ }
365
+ }
366
+
367
+ class KatexNode extends ContentNode {
368
+ const KatexNode ({
369
+ required this .text,
370
+ required this .nodes,
371
+ super .debugHtmlNode,
372
+ }) : assert ((text != null ) ^ (nodes != null ));
373
+
374
+ /// The text or a single character this KaTeX span contains, generally
375
+ /// observed to be the leaf node in the KaTeX HTML tree.
376
+ /// It will be null if this span has child nodes.
377
+ final String ? text;
378
+
379
+ /// The child nodes of this span in the KaTeX HTML tree.
380
+ /// It will be null if this span is a text node.
381
+ final List <KatexNode >? nodes;
355
382
356
383
@override
357
384
void debugFillProperties (DiagnosticPropertiesBuilder properties) {
358
385
super .debugFillProperties (properties);
359
- properties.add (StringProperty ('texSource' , texSource));
386
+ properties.add (StringProperty ('text' , text));
387
+ }
388
+
389
+ @override
390
+ List <DiagnosticsNode > debugDescribeChildren () {
391
+ return nodes? .map ((node) => node.toDiagnosticsNode ()).toList () ?? const [];
360
392
}
361
393
}
362
394
@@ -822,23 +854,25 @@ class ImageEmojiNode extends EmojiNode {
822
854
}
823
855
824
856
class MathInlineNode extends InlineContentNode {
825
- const MathInlineNode ({super .debugHtmlNode, required this .texSource});
857
+ const MathInlineNode ({
858
+ super .debugHtmlNode,
859
+ required this .texSource,
860
+ required this .nodes,
861
+ });
826
862
827
863
final String texSource;
828
-
829
- @override
830
- bool operator == (Object other) {
831
- return other is MathInlineNode && other.texSource == texSource;
832
- }
833
-
834
- @override
835
- int get hashCode => Object .hash ('MathInlineNode' , texSource);
864
+ final List <KatexNode >? nodes;
836
865
837
866
@override
838
867
void debugFillProperties (DiagnosticPropertiesBuilder properties) {
839
868
super .debugFillProperties (properties);
840
869
properties.add (StringProperty ('texSource' , texSource));
841
870
}
871
+
872
+ @override
873
+ List <DiagnosticsNode > debugDescribeChildren () {
874
+ return nodes? .map ((node) => node.toDiagnosticsNode ()).toList () ?? const [];
875
+ }
842
876
}
843
877
844
878
class GlobalTimeNode extends InlineContentNode {
@@ -864,7 +898,10 @@ class GlobalTimeNode extends InlineContentNode {
864
898
865
899
////////////////////////////////////////////////////////////////
866
900
867
- String ? _parseMath (dom.Element element, {required bool block}) {
901
+ ({List <KatexNode >? spans, String texSource})? _parseMath (
902
+ dom.Element element, {
903
+ required bool block,
904
+ }) {
868
905
final dom.Element katexElement;
869
906
if (! block) {
870
907
assert (element.localName == 'span' && element.className == 'katex' );
@@ -882,16 +919,15 @@ String? _parseMath(dom.Element element, {required bool block}) {
882
919
}
883
920
}
884
921
885
- // Expect two children span.katex-mathml, span.katex-html .
886
- // For now we only care about the .katex-mathml .
887
922
if (katexElement.nodes case [
888
923
dom.Element (localName: 'span' , className: 'katex-mathml' , nodes: [
889
924
dom.Element (
890
925
localName: 'math' ,
891
926
namespaceUri: 'http://www.w3.org/1998/Math/MathML' )
892
927
&& final mathElement,
893
928
]),
894
- ...
929
+ dom.Element (localName: 'span' , className: 'katex-html' , nodes: [...])
930
+ && final katexHtmlElement,
895
931
]) {
896
932
if (mathElement.attributes['display' ] != (block ? 'block' : null )) {
897
933
return null ;
@@ -911,7 +947,15 @@ String? _parseMath(dom.Element element, {required bool block}) {
911
947
} else {
912
948
return null ;
913
949
}
914
- return texSource;
950
+
951
+ List <KatexNode >? spans;
952
+ try {
953
+ spans = KatexParser ().parseKatexHTML (katexHtmlElement);
954
+ } on KatexHtmlParseError catch (e, st) {
955
+ assert (debugLog ('$e \n $st ' ));
956
+ }
957
+
958
+ return (spans: spans, texSource: texSource);
915
959
} else {
916
960
return null ;
917
961
}
@@ -927,9 +971,12 @@ String? _parseMath(dom.Element element, {required bool block}) {
927
971
class _ZulipInlineContentParser {
928
972
InlineContentNode ? parseInlineMath (dom.Element element) {
929
973
final debugHtmlNode = kDebugMode ? element : null ;
930
- final texSource = _parseMath (element, block: false );
931
- if (texSource == null ) return null ;
932
- return MathInlineNode (texSource: texSource, debugHtmlNode: debugHtmlNode);
974
+ final parsed = _parseMath (element, block: false );
975
+ if (parsed == null ) return null ;
976
+ return MathInlineNode (
977
+ texSource: parsed.texSource,
978
+ nodes: parsed.spans,
979
+ debugHtmlNode: debugHtmlNode);
933
980
}
934
981
935
982
UserMentionNode ? parseUserMention (dom.Element element) {
@@ -1631,10 +1678,11 @@ class _ZulipContentParser {
1631
1678
})());
1632
1679
1633
1680
final firstChild = nodes.first as dom.Element ;
1634
- final texSource = _parseMath (firstChild, block: true );
1635
- if (texSource != null ) {
1681
+ final parsed = _parseMath (firstChild, block: true );
1682
+ if (parsed != null ) {
1636
1683
result.add (MathBlockNode (
1637
- texSource: texSource,
1684
+ texSource: parsed.texSource,
1685
+ nodes: parsed.spans,
1638
1686
debugHtmlNode: kDebugMode ? firstChild : null ));
1639
1687
} else {
1640
1688
result.add (UnimplementedBlockContentNode (htmlNode: firstChild));
@@ -1666,10 +1714,11 @@ class _ZulipContentParser {
1666
1714
if (child case dom.Text (text: '\n\n ' )) continue ;
1667
1715
1668
1716
if (child case dom.Element (localName: 'span' , className: 'katex-display' )) {
1669
- final texSource = _parseMath (child, block: true );
1670
- if (texSource != null ) {
1717
+ final parsed = _parseMath (child, block: true );
1718
+ if (parsed != null ) {
1671
1719
result.add (MathBlockNode (
1672
- texSource: texSource,
1720
+ texSource: parsed.texSource,
1721
+ nodes: parsed.spans,
1673
1722
debugHtmlNode: debugHtmlNode));
1674
1723
continue ;
1675
1724
}
0 commit comments