@@ -6,6 +6,7 @@ import 'package:html/parser.dart';
66import '../api/model/model.dart' ;
77import '../api/model/submessage.dart' ;
88import 'code_block.dart' ;
9+ import 'katex.dart' ;
910
1011/// A node in a parse tree for Zulip message-style content.
1112///
@@ -341,22 +342,52 @@ class CodeBlockSpanNode extends ContentNode {
341342}
342343
343344class MathBlockNode extends BlockContentNode {
344- const MathBlockNode ({super .debugHtmlNode, required this .texSource});
345+ const MathBlockNode ({
346+ super .debugHtmlNode,
347+ required this .texSource,
348+ required this .nodes,
349+ });
345350
346351 final String texSource;
352+ final List <KatexNode >? nodes;
347353
348354 @override
349- bool operator == (Object other) {
350- return other is MathBlockNode && other.texSource == texSource;
355+ void debugFillProperties (DiagnosticPropertiesBuilder properties) {
356+ super .debugFillProperties (properties);
357+ properties.add (StringProperty ('texSource' , texSource));
351358 }
352359
353360 @override
354- int get hashCode => Object .hash ('MathBlockNode' , texSource);
361+ List <DiagnosticsNode > debugDescribeChildren () {
362+ return nodes? .map ((node) => node.toDiagnosticsNode ()).toList () ?? const [];
363+ }
364+ }
365+
366+ class KatexNode extends ContentNode {
367+ const KatexNode ({
368+ required this .text,
369+ required this .nodes,
370+ super .debugHtmlNode,
371+ }) : assert ((text != null ) ^ (nodes != null ));
372+
373+ /// The text or a single character this KaTeX node contains, generally
374+ /// observed to be the leaf node in the KaTeX HTML tree.
375+ /// It will be null if [nodes] is non-null.
376+ final String ? text;
377+
378+ /// The child nodes of this node in the KaTeX HTML tree.
379+ /// It will be null if [text] is non-null.
380+ final List <KatexNode >? nodes;
355381
356382 @override
357383 void debugFillProperties (DiagnosticPropertiesBuilder properties) {
358384 super .debugFillProperties (properties);
359- properties.add (StringProperty ('texSource' , texSource));
385+ properties.add (StringProperty ('text' , text));
386+ }
387+
388+ @override
389+ List <DiagnosticsNode > debugDescribeChildren () {
390+ return nodes? .map ((node) => node.toDiagnosticsNode ()).toList () ?? const [];
360391 }
361392}
362393
@@ -822,23 +853,25 @@ class ImageEmojiNode extends EmojiNode {
822853}
823854
824855class MathInlineNode extends InlineContentNode {
825- const MathInlineNode ({super .debugHtmlNode, required this .texSource});
856+ const MathInlineNode ({
857+ super .debugHtmlNode,
858+ required this .texSource,
859+ required this .nodes,
860+ });
826861
827862 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);
863+ final List <KatexNode >? nodes;
836864
837865 @override
838866 void debugFillProperties (DiagnosticPropertiesBuilder properties) {
839867 super .debugFillProperties (properties);
840868 properties.add (StringProperty ('texSource' , texSource));
841869 }
870+
871+ @override
872+ List <DiagnosticsNode > debugDescribeChildren () {
873+ return nodes? .map ((node) => node.toDiagnosticsNode ()).toList () ?? const [];
874+ }
842875}
843876
844877class GlobalTimeNode extends InlineContentNode {
@@ -864,59 +897,6 @@ class GlobalTimeNode extends InlineContentNode {
864897
865898////////////////////////////////////////////////////////////////
866899
867- String ? _parseMath (dom.Element element, {required bool block}) {
868- final dom.Element katexElement;
869- if (! block) {
870- assert (element.localName == 'span' && element.className == 'katex' );
871-
872- katexElement = element;
873- } else {
874- assert (element.localName == 'span' && element.className == 'katex-display' );
875-
876- if (element.nodes case [
877- dom.Element (localName: 'span' , className: 'katex' ) && final child,
878- ]) {
879- katexElement = child;
880- } else {
881- return null ;
882- }
883- }
884-
885- // Expect two children span.katex-mathml, span.katex-html .
886- // For now we only care about the .katex-mathml .
887- if (katexElement.nodes case [
888- dom.Element (localName: 'span' , className: 'katex-mathml' , nodes: [
889- dom.Element (
890- localName: 'math' ,
891- namespaceUri: 'http://www.w3.org/1998/Math/MathML' )
892- && final mathElement,
893- ]),
894- ...
895- ]) {
896- if (mathElement.attributes['display' ] != (block ? 'block' : null )) {
897- return null ;
898- }
899-
900- final String texSource;
901- if (mathElement.nodes case [
902- dom.Element (localName: 'semantics' , nodes: [
903- ...,
904- dom.Element (
905- localName: 'annotation' ,
906- attributes: {'encoding' : 'application/x-tex' },
907- : final text),
908- ]),
909- ]) {
910- texSource = text.trim ();
911- } else {
912- return null ;
913- }
914- return texSource;
915- } else {
916- return null ;
917- }
918- }
919-
920900/// Parser for the inline-content subtrees within Zulip content HTML.
921901///
922902/// The only entry point to this class is [parseBlockInline] .
@@ -927,9 +907,12 @@ String? _parseMath(dom.Element element, {required bool block}) {
927907class _ZulipInlineContentParser {
928908 InlineContentNode ? parseInlineMath (dom.Element element) {
929909 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);
910+ final parsed = parseMath (element, block: false );
911+ if (parsed == null ) return null ;
912+ return MathInlineNode (
913+ texSource: parsed.texSource,
914+ nodes: parsed.nodes,
915+ debugHtmlNode: debugHtmlNode);
933916 }
934917
935918 UserMentionNode ? parseUserMention (dom.Element element) {
@@ -1631,10 +1614,11 @@ class _ZulipContentParser {
16311614 })());
16321615
16331616 final firstChild = nodes.first as dom.Element ;
1634- final texSource = _parseMath (firstChild, block: true );
1635- if (texSource != null ) {
1617+ final parsed = parseMath (firstChild, block: true );
1618+ if (parsed != null ) {
16361619 result.add (MathBlockNode (
1637- texSource: texSource,
1620+ texSource: parsed.texSource,
1621+ nodes: parsed.nodes,
16381622 debugHtmlNode: kDebugMode ? firstChild : null ));
16391623 } else {
16401624 result.add (UnimplementedBlockContentNode (htmlNode: firstChild));
@@ -1666,10 +1650,11 @@ class _ZulipContentParser {
16661650 if (child case dom.Text (text: '\n\n ' )) continue ;
16671651
16681652 if (child case dom.Element (localName: 'span' , className: 'katex-display' )) {
1669- final texSource = _parseMath (child, block: true );
1670- if (texSource != null ) {
1653+ final parsed = parseMath (child, block: true );
1654+ if (parsed != null ) {
16711655 result.add (MathBlockNode (
1672- texSource: texSource,
1656+ texSource: parsed.texSource,
1657+ nodes: parsed.nodes,
16731658 debugHtmlNode: debugHtmlNode));
16741659 continue ;
16751660 }
0 commit comments