@@ -289,6 +289,26 @@ class CodeBlockSpanNode extends InlineContentNode {
289
289
}
290
290
}
291
291
292
+ class MathBlockNode extends BlockContentNode {
293
+ const MathBlockNode ({super .debugHtmlNode, required this .texSource});
294
+
295
+ final String texSource;
296
+
297
+ @override
298
+ bool operator == (Object other) {
299
+ return other is MathBlockNode && other.texSource == texSource;
300
+ }
301
+
302
+ @override
303
+ int get hashCode => Object .hash ('MathBlockNode' , texSource);
304
+
305
+ @override
306
+ void debugFillProperties (DiagnosticPropertiesBuilder properties) {
307
+ super .debugFillProperties (properties);
308
+ properties.add (StringProperty ('texSource' , texSource));
309
+ }
310
+ }
311
+
292
312
class ImageNode extends BlockContentNode {
293
313
const ImageNode ({super .debugHtmlNode, required this .srcUrl});
294
314
@@ -493,6 +513,26 @@ class ImageEmojiNode extends EmojiNode {
493
513
}
494
514
}
495
515
516
+ class MathInlineNode extends InlineContentNode {
517
+ const MathInlineNode ({super .debugHtmlNode, required this .texSource});
518
+
519
+ final String texSource;
520
+
521
+ @override
522
+ bool operator == (Object other) {
523
+ return other is MathInlineNode && other.texSource == texSource;
524
+ }
525
+
526
+ @override
527
+ int get hashCode => Object .hash ('MathInlineNode' , texSource);
528
+
529
+ @override
530
+ void debugFillProperties (DiagnosticPropertiesBuilder properties) {
531
+ super .debugFillProperties (properties);
532
+ properties.add (StringProperty ('texSource' , texSource));
533
+ }
534
+ }
535
+
496
536
////////////////////////////////////////////////////////////////
497
537
498
538
// Ported from https://github.com/zulip/zulip-mobile/blob/c979530d6804db33310ed7d14a4ac62017432944/src/emoji/data.js#L108-L112
@@ -532,6 +572,60 @@ class _ZulipContentParser {
532
572
/// and should be read or updated only inside an assertion.
533
573
_ParserContext _debugParserContext = _ParserContext .block;
534
574
575
+ String ? parseMath (dom.Element element, {required bool block}) {
576
+ assert (block == (_debugParserContext == _ParserContext .block));
577
+
578
+ final dom.Element katexElement;
579
+ if (! block) {
580
+ assert (element.localName == 'span'
581
+ && element.classes.length == 1
582
+ && element.classes.contains ('katex' ));
583
+
584
+ katexElement = element;
585
+ } else {
586
+ assert (element.localName == 'span'
587
+ && element.classes.length == 1
588
+ && element.classes.contains ('katex-display' ));
589
+
590
+ if (element.nodes.length != 1 ) return null ;
591
+ final child = element.nodes.single;
592
+ if (child is ! dom.Element ) return null ;
593
+ if (child.localName != 'span' ) return null ;
594
+ if (child.classes.length != 1 ) return null ;
595
+ if (! child.classes.contains ('katex' )) return null ;
596
+ katexElement = child;
597
+ }
598
+
599
+ // Expect two children span.katex-mathml, span.katex-html .
600
+ // For now we only care about the .katex-mathml .
601
+ if (katexElement.nodes.isEmpty) return null ;
602
+ final child = katexElement.nodes.first;
603
+ if (child is ! dom.Element ) return null ;
604
+ if (child.localName != 'span' ) return null ;
605
+ if (child.classes.length != 1 ) return null ;
606
+ if (! child.classes.contains ('katex-mathml' )) return null ;
607
+
608
+ if (child.nodes.length != 1 ) return null ;
609
+ final grandchild = child.nodes.single;
610
+ if (grandchild is ! dom.Element ) return null ;
611
+ if (grandchild.localName != 'math' ) return null ;
612
+ if (grandchild.attributes['display' ] != (block ? 'block' : null )) return null ;
613
+ if (grandchild.namespaceUri != 'http://www.w3.org/1998/Math/MathML' ) return null ;
614
+
615
+ if (grandchild.nodes.length != 1 ) return null ;
616
+ final greatgrand = grandchild.nodes.single;
617
+ if (greatgrand is ! dom.Element ) return null ;
618
+ if (greatgrand.localName != 'semantics' ) return null ;
619
+
620
+ if (greatgrand.nodes.isEmpty) return null ;
621
+ final descendant4 = greatgrand.nodes.last;
622
+ if (descendant4 is ! dom.Element ) return null ;
623
+ if (descendant4.localName != 'annotation' ) return null ;
624
+ if (descendant4.attributes['encoding' ] != 'application/x-tex' ) return null ;
625
+
626
+ return descendant4.text.trim ();
627
+ }
628
+
535
629
/// The links found so far in the current block inline container.
536
630
///
537
631
/// Empty is represented as null.
@@ -623,6 +717,14 @@ class _ZulipContentParser {
623
717
return ImageEmojiNode (src: src, alt: alt, debugHtmlNode: debugHtmlNode);
624
718
}
625
719
720
+ if (localName == 'span'
721
+ && classes.length == 1
722
+ && classes.contains ('katex' )) {
723
+ final texSource = parseMath (element, block: false );
724
+ if (texSource == null ) return unimplemented ();
725
+ return MathInlineNode (texSource: texSource, debugHtmlNode: debugHtmlNode);
726
+ }
727
+
626
728
// TODO more types of node
627
729
return unimplemented ();
628
730
}
@@ -792,6 +894,24 @@ class _ZulipContentParser {
792
894
}
793
895
794
896
if (localName == 'p' && classes.isEmpty) {
897
+ // Oddly, the way a math block gets encoded in Zulip HTML is inside a <p>.
898
+ if (element.nodes case [dom.Element (localName: 'span' ) && var child, ...]) {
899
+ if (child.classes.length == 1
900
+ && child.classes.contains ('katex-display' )) {
901
+ if (element.nodes case [_]
902
+ || [_, dom.Element (localName: 'br' ),
903
+ dom.Text (text: "\n " )]) {
904
+ // This might be too specific; we'll find out when we do #190.
905
+ // The case with the `<br>\n` can happen when at the end of a quote;
906
+ // it seems like a glitch in the server's Markdown processing,
907
+ // so hopefully there just aren't any further such glitches.
908
+ final texSource = parseMath (child, block: true );
909
+ if (texSource == null ) return UnimplementedBlockContentNode (htmlNode: node);
910
+ return MathBlockNode (texSource: texSource, debugHtmlNode: debugHtmlNode);
911
+ }
912
+ }
913
+ }
914
+
795
915
final parsed = parseBlockInline (element.nodes);
796
916
return ParagraphNode (debugHtmlNode: debugHtmlNode,
797
917
links: parsed.links,
0 commit comments