1use alloc::{borrow::ToOwned, boxed::Box, collections::VecDeque, string::String, vec::Vec};
24use core::{
25 cmp::{max, min},
26 iter::FusedIterator,
27 num::NonZeroUsize,
28 ops::{Index, Range},
29};
30use rustc_hash::FxHashMap;
31use unicase::UniCase;
32
33#[cfg(feature = "mdx")]
34use crate::mdx::*;
35use crate::{
36 firstpass::run_first_pass,
37 linklabel::{scan_link_label_rest, FootnoteLabel, LinkLabel, ReferenceLabel},
38 scanners::*,
39 strings::CowStr,
40 tree::{Tree, TreeIndex},
41 Alignment, BlockQuoteKind, CodeBlockKind, DirectiveKind, Event, HeadingLevel, LinkType,
42 MetadataBlockKind, Options, Tag, TagEnd,
43};
44
45pub(crate) const LINK_MAX_NESTED_PARENS: usize = 32;
51
52#[derive(Debug, Default, Clone, Copy)]
53pub(crate) struct Item {
54 pub start: usize,
55 pub end: usize,
56 pub body: ItemBody,
57}
58
59#[derive(Debug, PartialEq, Clone, Copy, Default)]
60pub(crate) enum ItemBody {
61 MaybeEmphasis(usize, bool, bool),
65 MaybeMath(bool, u8),
67 MaybeSmartQuote(u8, bool, bool),
69 MaybeCode(usize, bool), MaybeHtml,
71 MaybeLinkOpen,
72 MaybeLinkClose(bool),
74 MaybeImage,
75
76 Emphasis,
78 Strong,
79 Strikethrough,
80 Superscript,
81 Subscript,
82 Math(CowIndex, bool), Code(CowIndex),
84 Link(LinkIndex),
85 Image(LinkIndex),
86 FootnoteReference(CowIndex),
87 TaskListMarker(bool), InlineHtml,
91 OwnedInlineHtml(CowIndex),
92 SynthesizeText(CowIndex),
93 SynthesizeChar(char),
94 Html,
95 Text {
96 backslash_escaped: bool,
97 },
98 SoftBreak,
99 HardBreak(bool),
101
102 #[default]
104 Root,
105
106 Paragraph,
108 TightParagraph,
109 Rule,
110 Heading(HeadingLevel, Option<HeadingIndex>), FencedCodeBlock(CowIndex),
112 MathBlock(CowIndex), IndentCodeBlock(bool),
117 HtmlBlock(bool), BlockQuote(Option<BlockQuoteKind>),
121 ContainerDirective(u8, DirectiveIndex), LeafDirective(DirectiveIndex),
123 TextDirective(DirectiveIndex),
124 DirectiveLabel,
128 List(bool, u8, u64), ListItem(usize, bool), FootnoteDefinition(CowIndex),
131 MetadataBlock(MetadataBlockKind),
132
133 DefinitionList(bool), MaybeDefinitionListTitle,
138 DefinitionListTitle,
139 DefinitionListDefinition(usize),
140
141 Table(AlignmentIndex),
143 TableHead,
144 TableRow,
145 TableCell,
146
147 #[cfg(feature = "mdx")]
149 MdxJsxFlowElement(JsxElementIndex),
150 #[cfg(feature = "mdx")]
151 MdxJsxTextElement(JsxElementIndex),
152 #[cfg(feature = "mdx")]
153 MdxFlowExpression(CowIndex),
154 #[cfg(feature = "mdx")]
155 MdxTextExpression(CowIndex),
156 #[cfg(feature = "mdx")]
157 MdxEsm(CowIndex),
158}
159
160impl ItemBody {
161 pub(crate) fn is_maybe_inline(&self) -> bool {
162 use ItemBody::*;
163 matches!(
164 *self,
165 MaybeEmphasis(..)
166 | MaybeMath(..)
167 | MaybeSmartQuote(..)
168 | MaybeCode(..)
169 | MaybeHtml
170 | MaybeLinkOpen
171 | MaybeLinkClose(..)
172 | MaybeImage
173 )
174 }
175 pub(crate) fn is_block_level(&self) -> bool {
176 !self.is_inline() && !matches!(self, ItemBody::Root)
177 }
178 fn is_inline(&self) -> bool {
179 use ItemBody::*;
180 matches!(
181 *self,
182 MaybeEmphasis(..)
183 | MaybeMath(..)
184 | MaybeSmartQuote(..)
185 | MaybeCode(..)
186 | MaybeHtml
187 | MaybeLinkOpen
188 | MaybeLinkClose(..)
189 | MaybeImage
190 | Emphasis
191 | Strong
192 | Strikethrough
193 | Math(..)
194 | Code(..)
195 | Link(..)
196 | Image(..)
197 | FootnoteReference(..)
198 | TaskListMarker(..)
199 | InlineHtml
200 | OwnedInlineHtml(..)
201 | SynthesizeText(..)
202 | SynthesizeChar(..)
203 | Html
204 | Text { .. }
205 | SoftBreak
206 | HardBreak(..)
207 )
208 }
209}
210
211#[derive(Debug)]
212pub struct BrokenLink<'a> {
213 pub span: core::ops::Range<usize>,
214 pub link_type: LinkType,
215 pub reference: CowStr<'a>,
216}
217
218pub struct Parser<'input, CB = DefaultParserCallbacks> {
220 callbacks: CB,
221 inner: ParserInner<'input>,
222}
223
224pub(crate) struct ParserInner<'input> {
227 pub(crate) text: &'input str,
228 pub(crate) options: Options,
229 pub(crate) tree: Tree<Item>,
230 pub(crate) allocs: Allocations<'input>,
231 html_scan_guard: HtmlScanGuard,
232
233 link_ref_expansion_limit: usize,
250
251 pub(crate) mdx_errors: Vec<(usize, String)>,
253
254 inline_stack: InlineStack,
256 link_stack: LinkStack,
257 wikilink_stack: LinkStack,
258 code_delims: CodeDelims,
259 math_delims: MathDelims,
260}
261
262impl<'input, CB> core::fmt::Debug for Parser<'input, CB> {
263 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
264 f.debug_struct("Parser")
266 .field("text", &self.inner.text)
267 .field("options", &self.inner.options)
268 .field("callbacks", &..)
269 .finish()
270 }
271}
272
273impl<'a> BrokenLink<'a> {
274 pub fn into_static(self) -> BrokenLink<'static> {
278 BrokenLink {
279 span: self.span.clone(),
280 link_type: self.link_type,
281 reference: self.reference.into_string().into(),
282 }
283 }
284}
285
286impl<'input> Parser<'input, DefaultParserCallbacks> {
287 pub fn new(text: &'input str) -> Self {
289 Self::new_ext(text, Options::empty())
290 }
291
292 pub fn new_ext(text: &'input str, options: Options) -> Self {
294 Self::new_with_callbacks(text, options, DefaultParserCallbacks)
295 }
296}
297
298impl<'input, CB: ParserCallbacks<'input>> Parser<'input, CB> {
299 pub fn new_with_callbacks(text: &'input str, options: Options, callbacks: CB) -> Self {
324 let (mut tree, allocs, _firstpass_mdx_errors) = run_first_pass(text, options);
325 tree.reset();
326 let inline_stack = Default::default();
327 let link_stack = Default::default();
328 let wikilink_stack = Default::default();
329 let html_scan_guard = Default::default();
330 Parser {
331 callbacks,
332
333 inner: ParserInner {
334 text,
335 options,
336 tree,
337 allocs,
338 inline_stack,
339 link_stack,
340 wikilink_stack,
341 html_scan_guard,
342 link_ref_expansion_limit: text.len().max(100_000),
344 mdx_errors: Vec::new(),
345 code_delims: CodeDelims::new(),
346 math_delims: MathDelims::new(),
347 },
348 }
349 }
350
351 pub fn reference_definitions(&self) -> &RefDefs<'_> {
354 &self.inner.allocs.refdefs
355 }
356
357 pub fn mdx_errors(&self) -> &[(usize, String)] {
360 &self.inner.mdx_errors
361 }
362
363 pub fn into_offset_iter(self) -> OffsetIter<'input, CB> {
367 OffsetIter { parser: self }
368 }
369}
370
371impl<'input, F> Parser<'input, BrokenLinkCallback<F>> {
372 pub fn new_with_broken_link_callback(
381 text: &'input str,
382 options: Options,
383 broken_link_callback: Option<F>,
384 ) -> Self
385 where
386 F: FnMut(BrokenLink<'input>) -> Option<(CowStr<'input>, CowStr<'input>)>,
387 {
388 Self::new_with_callbacks(text, options, BrokenLinkCallback(broken_link_callback))
389 }
390}
391
392impl<'input> ParserInner<'input> {
393 pub(crate) fn new(text: &'input str, options: Options) -> Self {
394 let (mut tree, allocs, firstpass_mdx_errors) = run_first_pass(text, options);
395 tree.reset();
396 ParserInner {
397 text,
398 options,
399 tree,
400 allocs,
401 inline_stack: Default::default(),
402 link_stack: Default::default(),
403 wikilink_stack: Default::default(),
404 html_scan_guard: Default::default(),
405 link_ref_expansion_limit: text.len().max(100_000),
406 mdx_errors: firstpass_mdx_errors,
407 code_delims: CodeDelims::new(),
408 math_delims: MathDelims::new(),
409 }
410 }
411
412 fn fetch_link_type_url_title(
431 &mut self,
432 link_label: CowStr<'input>,
433 span: Range<usize>,
434 link_type: LinkType,
435 callbacks: &mut dyn ParserCallbacks<'input>,
436 ) -> Option<(LinkType, CowStr<'input>, CowStr<'input>)> {
437 if self.link_ref_expansion_limit == 0 {
438 return None;
439 }
440
441 let (link_type, url, title) = self
442 .allocs
443 .refdefs
444 .get(link_label.as_ref())
445 .map(|matching_def| {
446 let title = matching_def
448 .title
449 .as_ref()
450 .cloned()
451 .unwrap_or_else(|| "".into());
452 let url = matching_def.dest.clone();
453 (link_type, url, title)
454 })
455 .or_else(|| {
456 let broken_link = BrokenLink {
458 span,
459 link_type,
460 reference: link_label,
461 };
462
463 callbacks
464 .handle_broken_link(broken_link)
465 .map(|(url, title)| (link_type.to_unknown(), url, title))
466 })?;
467
468 self.link_ref_expansion_limit = self
472 .link_ref_expansion_limit
473 .saturating_sub(url.len() + title.len());
474
475 Some((link_type, url, title))
476 }
477
478 pub(crate) fn handle_inline(&mut self, callbacks: &mut dyn ParserCallbacks<'input>) {
485 self.handle_inline_pass1(callbacks);
486 let st_enabled = self.options.contains(Options::ENABLE_STRIKETHROUGH)
501 || self.options.contains(Options::ENABLE_SUBSCRIPT)
502 || self.options.contains(Options::ENABLE_SUPERSCRIPT);
503 if !st_enabled {
504 self.handle_emphasis_pass();
505 return;
506 }
507 let scope_first = self
512 .tree
513 .peek_up()
514 .and_then(|p| self.tree[p].child)
515 .or_else(|| self.tree.cur());
516 let strikethrough_first = matches!(
517 self.first_inline_marker_char(scope_first),
518 Some(b'~') | Some(b'^')
519 );
520 self.resolve_inline_scope(self.tree.cur(), strikethrough_first);
521 }
522
523 fn resolve_inline_scope(&mut self, start: Option<TreeIndex>, strikethrough_first: bool) {
527 if strikethrough_first {
528 self.resolve_tildes_carets_in_scope(start, false);
529 self.resolve_emphasis_at_scope(start);
530 } else {
531 self.resolve_emphasis_at_scope(start);
532 self.resolve_tildes_carets_in_scope(start, false);
533 }
534 let mut cur = start;
535 while let Some(cur_ix) = cur {
536 let next = self.tree[cur_ix].next;
537 if matches!(
538 self.tree[cur_ix].item.body,
539 ItemBody::Emphasis
540 | ItemBody::Strong
541 | ItemBody::Strikethrough
542 | ItemBody::Subscript
543 | ItemBody::Superscript
544 | ItemBody::Link(_)
545 | ItemBody::Image(_)
546 ) {
547 let child = self.tree[cur_ix].child;
548 self.resolve_inline_scope(child, true);
549 }
550 cur = next;
551 }
552 }
553
554 fn resolve_emphasis_at_scope(&mut self, start: Option<TreeIndex>) {
557 let saved = core::mem::take(&mut self.inline_stack);
558 self.handle_emphasis_in_scope(start);
559 self.inline_stack = saved;
560 }
561
562 fn first_inline_marker_char(&self, start: Option<TreeIndex>) -> Option<u8> {
571 let tilde = self.options.contains(Options::ENABLE_STRIKETHROUGH)
576 || self.options.contains(Options::ENABLE_SUBSCRIPT);
577 let caret = self.options.contains(Options::ENABLE_SUPERSCRIPT);
578 let is_marker =
579 |c: u8| matches!(c, b'*' | b'_') || (c == b'~' && tilde) || (c == b'^' && caret);
580 let bytes = self.text.as_bytes();
581 let mut cur = start;
582 while let Some(cur_ix) = cur {
583 match self.tree[cur_ix].item.body {
584 ItemBody::MaybeEmphasis(..) => {
585 let c = bytes[self.tree[cur_ix].item.start];
586 if is_marker(c) {
587 return Some(c);
588 }
589 }
590 ItemBody::Text { backslash_escaped } => {
591 let item = &self.tree[cur_ix].item;
592 let from = item.start + usize::from(backslash_escaped);
595 if let Some(off) = bytes[from..item.end].iter().position(|&c| is_marker(c)) {
596 return Some(bytes[from + off]);
597 }
598 }
599 _ => {}
600 }
601 cur = self.tree[cur_ix].next;
602 }
603 None
604 }
605
606 fn handle_emphasis_pass(&mut self) {
611 let start = self.tree.cur();
612 self.resolve_emphasis_recursive(start);
613 }
614
615 fn resolve_emphasis_recursive(&mut self, start: Option<TreeIndex>) {
616 let saved = core::mem::take(&mut self.inline_stack);
620 self.handle_emphasis_in_scope(start);
621 self.inline_stack = saved;
622
623 let mut cur = start;
624 while let Some(cur_ix) = cur {
625 let next = self.tree[cur_ix].next;
626 match self.tree[cur_ix].item.body {
627 ItemBody::Emphasis
628 | ItemBody::Strong
629 | ItemBody::Strikethrough
630 | ItemBody::Subscript
631 | ItemBody::Superscript
632 | ItemBody::Link(_)
633 | ItemBody::Image(_) => {
634 let child = self.tree[cur_ix].child;
635 self.resolve_emphasis_recursive(child);
636 }
637 _ => {}
638 }
639 cur = next;
640 }
641 }
642
643 fn handle_inline_pass1(&mut self, callbacks: &mut dyn ParserCallbacks<'input>) {
649 let mut cur = self.tree.cur();
650 let mut prev = None;
651
652 let block_end = self.tree[self.tree.peek_up().unwrap()].item.end;
653 let block_text = &self.text[..block_end];
654
655 while let Some(mut cur_ix) = cur {
656 match self.tree[cur_ix].item.body {
657 ItemBody::MaybeHtml => {
658 #[cfg(feature = "mdx")]
660 if self.options.contains(Options::ENABLE_MDX) {
661 let start = self.tree[cur_ix].item.start;
662 let next_byte = block_text.as_bytes().get(start + 1).copied();
663
664 if next_byte == Some(b'!') {
666 self.mdx_errors.push((
667 start,
668 "Unexpected character `!` (U+0021) before name, expected a \
669 character that can start a name, such as a letter, `$`, or `_` \
670 (note: to create a comment in MDX, use `{/* text */}`)"
671 .to_string(),
672 ));
673 self.tree[cur_ix].item.body = ItemBody::Text {
674 backslash_escaped: false,
675 };
676 prev = cur;
677 cur = self.tree[cur_ix].next;
678 continue;
679 }
680
681 if let Some(total_len) =
682 scan_mdx_inline_jsx(&block_text.as_bytes()[start..])
683 {
684 let end = start + total_len;
685 let node = scan_nodes_to_ix(&self.tree, self.tree[cur_ix].next, end);
686 let raw = &block_text[start..end];
687 let col = crate::mdx::column_at(block_text.as_bytes(), start);
688 let jsx_data = crate::mdx::parse_jsx_tag_with_column(raw, col, 0);
689 let mut allocator = oxc_allocator::Allocator::default();
690 crate::mdx::validate_jsx_expressions(
691 raw,
692 &jsx_data.attrs,
693 |rel| start + rel,
694 &mut allocator,
695 &mut self.mdx_errors,
696 );
697 let jsx_ix = self.allocs.allocate_jsx_element(jsx_data);
698 self.tree[cur_ix].item.body = ItemBody::MdxJsxTextElement(jsx_ix);
699 self.tree[cur_ix].item.end = end;
700 self.tree[cur_ix].next = node;
701 prev = cur;
702 cur = node;
703 if let Some(node_ix) = cur {
704 self.tree[node_ix].item.start =
705 max(self.tree[node_ix].item.start, end);
706 }
707 continue;
708 }
709
710 let bytes_block = block_text.as_bytes();
725 let is_text_fallback = match next_byte {
726 Some(b' ' | b'\t') => true,
727 Some(b'\n' | b'\r') => {
728 let bq_depth = self
734 .tree
735 .walk_spine()
736 .filter(|&&ix| {
737 matches!(self.tree[ix].item.body, ItemBody::BlockQuote(..))
738 })
739 .count();
740 let mut probe = start + 1;
741 loop {
742 while probe < bytes_block.len()
743 && matches!(
744 bytes_block[probe],
745 b' ' | b'\t' | b'\n' | b'\r'
746 )
747 {
748 probe += 1;
749 }
750 if bq_depth == 0
751 || probe >= bytes_block.len()
752 || bytes_block[probe] != b'>'
753 {
754 break;
755 }
756 let mut consumed = 0;
757 while consumed < bq_depth
758 && probe < bytes_block.len()
759 && bytes_block[probe] == b'>'
760 {
761 probe += 1;
762 if probe < bytes_block.len() && bytes_block[probe] == b' ' {
763 probe += 1;
764 }
765 consumed += 1;
766 }
767 }
768 if probe >= bytes_block.len() || bytes_block[probe] == b'>' {
769 false
770 } else {
771 let underline_char = bytes_block[probe];
781 if !matches!(underline_char, b'-' | b'=') {
782 true
783 } else {
784 let mut q = probe;
785 while q < bytes_block.len()
786 && bytes_block[q] == underline_char
787 {
788 q += 1;
789 }
790 while q < bytes_block.len()
791 && matches!(bytes_block[q], b' ' | b'\t')
792 {
793 q += 1;
794 }
795 let at_eol = q >= bytes_block.len()
796 || matches!(bytes_block[q], b'\n' | b'\r');
797 if !at_eol {
798 true
799 } else {
800 let mut ls = start;
819 while ls > 0
820 && !matches!(bytes_block[ls - 1], b'\n' | b'\r')
821 {
822 ls -= 1;
823 }
824 let mut k = ls;
825 let mut sp = 0;
826 while k < start && bytes_block[k] == b' ' && sp < 3 {
827 k += 1;
828 sp += 1;
829 }
830 if k < start && bytes_block[k] == b'>' {
831 true
832 } else {
833 let mut us = probe;
835 while us > 0
836 && !matches!(bytes_block[us - 1], b'\n' | b'\r')
837 {
838 us -= 1;
839 }
840 let mut underline_col = 0;
841 let mut uk = us;
842 while uk < probe && bytes_block[uk] == b' ' {
843 uk += 1;
844 underline_col += 1;
845 }
846 let listitem_indent = self
847 .tree
848 .walk_spine()
849 .filter_map(|&ix| {
850 match self.tree[ix].item.body {
851 ItemBody::ListItem(indent, _) => {
852 Some(indent)
853 }
854 _ => None,
855 }
856 })
857 .next();
858 let in_blockquote =
859 self.tree.walk_spine().any(|&ix| {
860 matches!(
861 self.tree[ix].item.body,
862 ItemBody::BlockQuote(..)
863 )
864 });
865 let bq_lazy = if in_blockquote {
875 underline_col < 1
876 || !bytes_block[us..probe].contains(&b'>')
877 } else {
878 false
879 };
880 matches!(listitem_indent, Some(i) if underline_col < i)
881 || bq_lazy
882 }
883 }
884 }
885 }
886 }
887 _ => false,
888 };
889 if !is_text_fallback {
890 self.mdx_errors.push((
891 start,
892 "Unexpected character after `<`, expected a valid JSX tag \
893 (note: to create a link in MDX, use `[text](url)`)"
894 .to_string(),
895 ));
896 }
897
898 self.tree[cur_ix].item.body = ItemBody::Text {
899 backslash_escaped: false,
900 };
901 prev = cur;
902 cur = self.tree[cur_ix].next;
903 continue;
904 }
905
906 let next = self.tree[cur_ix].next;
907 let autolink = if let Some(next_ix) = next {
908 scan_autolink(block_text, self.tree[next_ix].item.start)
909 } else {
910 None
911 };
912
913 if let Some((ix, uri, link_type)) = autolink {
914 let node = scan_nodes_to_ix(&self.tree, next, ix);
915 let text_node = self.tree.create_node(Item {
916 start: self.tree[cur_ix].item.start + 1,
917 end: ix - 1,
918 body: ItemBody::Text {
919 backslash_escaped: false,
920 },
921 });
922 let link_ix =
923 self.allocs
924 .allocate_link(link_type, uri, "".into(), "".into());
925 self.tree[cur_ix].item.body = ItemBody::Link(link_ix);
926 self.tree[cur_ix].item.end = ix;
927 self.tree[cur_ix].next = node;
928 self.tree[cur_ix].child = Some(text_node);
929 prev = cur;
930 cur = node;
931 if let Some(node_ix) = cur {
932 let orig_start = self.tree[node_ix].item.start;
933 let new_start = max(orig_start, ix);
934 self.tree[node_ix].item.start = new_start;
935 if new_start > orig_start {
942 if let ItemBody::Text { backslash_escaped } =
943 &mut self.tree[node_ix].item.body
944 {
945 *backslash_escaped = false;
946 }
947 }
948 }
949 continue;
950 } else {
951 let inline_html = next.and_then(|next_ix| {
952 self.scan_inline_html(
953 block_text.as_bytes(),
954 self.tree[next_ix].item.start,
955 )
956 });
957 if let Some((span, ix)) = inline_html {
958 let node = scan_nodes_to_ix(&self.tree, next, ix);
959 self.tree[cur_ix].item.body = if !span.is_empty() {
960 let converted_string =
961 String::from_utf8(span).expect("invalid utf8");
962 ItemBody::OwnedInlineHtml(
963 self.allocs.allocate_cow(converted_string.into()),
964 )
965 } else {
966 ItemBody::InlineHtml
967 };
968 self.tree[cur_ix].item.end = ix;
969 self.tree[cur_ix].next = node;
970 prev = cur;
971 cur = node;
972 if let Some(node_ix) = cur {
973 let orig_start = self.tree[node_ix].item.start;
974 let new_start = max(orig_start, ix);
975 self.tree[node_ix].item.start = new_start;
976 if new_start > orig_start {
982 if let ItemBody::Text { backslash_escaped } =
983 &mut self.tree[node_ix].item.body
984 {
985 *backslash_escaped = false;
986 }
987 }
988 }
989 continue;
990 }
991 }
992 self.tree[cur_ix].item.body = ItemBody::Text {
993 backslash_escaped: false,
994 };
995 }
996 ItemBody::MaybeMath(preceded_by_backslash, _brace_context) => {
997 if preceded_by_backslash {
998 self.tree[cur_ix].item.body = ItemBody::Text {
999 backslash_escaped: true,
1000 };
1001 prev = cur;
1002 cur = self.tree[cur_ix].next;
1003 continue;
1004 }
1005 let mut open_count = 1usize;
1007 let mut open_end = cur_ix;
1008 {
1009 let mut peek = self.tree[cur_ix].next;
1010 while let Some(peek_ix) = peek {
1011 if matches!(self.tree[peek_ix].item.body, ItemBody::MaybeMath(..))
1012 && self.tree[peek_ix].item.start == self.tree[open_end].item.end
1013 {
1014 open_count += 1;
1015 open_end = peek_ix;
1016 peek = self.tree[peek_ix].next;
1017 } else {
1018 break;
1019 }
1020 }
1021 }
1022
1023 let count_enabled = if open_count == 1 {
1029 self.options.contains(Options::ENABLE_MATH_SINGLE_DOLLAR)
1030 } else {
1031 self.options.contains(Options::ENABLE_MATH_MULTI_DOLLAR)
1032 };
1033 if !count_enabled {
1034 let mut text_ix = cur_ix;
1035 loop {
1036 self.tree[text_ix].item.body = ItemBody::Text {
1037 backslash_escaped: false,
1038 };
1039 if text_ix == open_end {
1040 break;
1041 }
1042 match self.tree[text_ix].next {
1043 Some(next) => text_ix = next,
1044 None => break,
1045 }
1046 }
1047 prev = cur;
1048 cur = self.tree[cur_ix].next;
1049 continue;
1050 }
1051
1052 let mut scan = self.tree[open_end].next;
1054 let mut close_ix = None;
1055 while let Some(scan_ix) = scan {
1056 if matches!(self.tree[scan_ix].item.body, ItemBody::MaybeMath(..)) {
1057 let mut run = 1usize;
1058 let mut run_end = scan_ix;
1059 let mut peek = self.tree[scan_ix].next;
1060 while let Some(peek_ix) = peek {
1061 if matches!(self.tree[peek_ix].item.body, ItemBody::MaybeMath(..))
1062 && self.tree[peek_ix].item.start == self.tree[run_end].item.end
1063 {
1064 run += 1;
1065 run_end = peek_ix;
1066 peek = self.tree[peek_ix].next;
1067 } else {
1068 break;
1069 }
1070 }
1071 if run == open_count {
1072 close_ix = Some(scan_ix);
1073 break;
1074 }
1075 scan = self.tree[run_end].next;
1077 continue;
1078 }
1079 scan = self.tree[scan_ix].next;
1080 }
1081
1082 if let Some(scan_ix) = close_ix {
1083 self.make_math_span(cur_ix, scan_ix);
1084 } else {
1085 let mut fail_ix = cur_ix;
1086 loop {
1087 self.tree[fail_ix].item.body = ItemBody::Text {
1088 backslash_escaped: false,
1089 };
1090 if fail_ix == open_end {
1091 break;
1092 }
1093 if let Some(next) = self.tree[fail_ix].next {
1094 fail_ix = next;
1095 } else {
1096 break;
1097 }
1098 }
1099 }
1100 }
1101 ItemBody::MaybeCode(mut search_count, preceded_by_backslash) => {
1102 if preceded_by_backslash {
1103 search_count -= 1;
1104 if search_count == 0 {
1105 self.tree[cur_ix].item.body = ItemBody::Text {
1106 backslash_escaped: true,
1107 };
1108 prev = cur;
1109 cur = self.tree[cur_ix].next;
1110 continue;
1111 }
1112 }
1113
1114 if self.code_delims.is_populated() {
1115 if let Some(scan_ix) = self.code_delims.find(cur_ix, search_count) {
1118 self.make_code_span(cur_ix, scan_ix, preceded_by_backslash);
1119 } else {
1120 self.tree[cur_ix].item.body = ItemBody::Text {
1121 backslash_escaped: preceded_by_backslash,
1122 };
1123 }
1124 } else {
1125 let mut scan = if search_count > 0 {
1128 self.tree[cur_ix].next
1129 } else {
1130 None
1131 };
1132 while let Some(scan_ix) = scan {
1133 if let ItemBody::MaybeCode(delim_count, _) =
1134 self.tree[scan_ix].item.body
1135 {
1136 if search_count == delim_count {
1137 self.make_code_span(cur_ix, scan_ix, preceded_by_backslash);
1138 self.code_delims.clear();
1139 break;
1140 } else {
1141 self.code_delims.insert(delim_count, scan_ix);
1142 }
1143 }
1144 scan = self.tree[scan_ix].next;
1145 }
1146 if scan.is_none() {
1147 self.tree[cur_ix].item.body = ItemBody::Text {
1148 backslash_escaped: preceded_by_backslash,
1149 };
1150 }
1151 }
1152 }
1153 ItemBody::MaybeLinkOpen => {
1154 self.tree[cur_ix].item.body = ItemBody::Text {
1155 backslash_escaped: false,
1156 };
1157 let link_open_doubled = self.tree[cur_ix]
1158 .next
1159 .map(|ix| self.tree[ix].item.body == ItemBody::MaybeLinkOpen)
1160 .unwrap_or(false);
1161 if self.options.contains(Options::ENABLE_WIKILINKS) && link_open_doubled {
1162 self.wikilink_stack.push(LinkStackEl {
1163 node: cur_ix,
1164 ty: LinkStackTy::Link,
1165 });
1166 }
1167 self.link_stack.push(LinkStackEl {
1168 node: cur_ix,
1169 ty: LinkStackTy::Link,
1170 });
1171 }
1172 ItemBody::MaybeImage => {
1173 self.tree[cur_ix].item.body = ItemBody::Text {
1174 backslash_escaped: false,
1175 };
1176 let link_open_doubled = self.tree[cur_ix]
1177 .next
1178 .map(|ix| self.tree[ix].item.body == ItemBody::MaybeLinkOpen)
1179 .unwrap_or(false);
1180 if self.options.contains(Options::ENABLE_WIKILINKS) && link_open_doubled {
1181 self.wikilink_stack.push(LinkStackEl {
1182 node: cur_ix,
1183 ty: LinkStackTy::Image,
1184 });
1185 }
1186 self.link_stack.push(LinkStackEl {
1187 node: cur_ix,
1188 ty: LinkStackTy::Image,
1189 });
1190 }
1191 ItemBody::MaybeLinkClose(could_be_ref) => {
1192 self.tree[cur_ix].item.body = ItemBody::Text {
1193 backslash_escaped: false,
1194 };
1195 let tos_link = self.link_stack.pop();
1196 if self.options.contains(Options::ENABLE_WIKILINKS)
1197 && self.tree[cur_ix]
1198 .next
1199 .map(|ix| {
1200 matches!(self.tree[ix].item.body, ItemBody::MaybeLinkClose(..))
1201 })
1202 .unwrap_or(false)
1203 {
1204 if let Some(node) = self.handle_wikilink(block_text, cur_ix, prev) {
1205 cur = self.tree[node].next;
1206 continue;
1207 }
1208 }
1209 if let Some(tos) = tos_link {
1210 if tos.ty != LinkStackTy::Image
1213 && matches!(
1214 self.tree[self.tree.peek_up().unwrap()].item.body,
1215 ItemBody::Link(..)
1216 )
1217 {
1218 continue;
1219 }
1220 if tos.ty == LinkStackTy::Disabled {
1221 continue;
1222 }
1223 let next = self.tree[cur_ix].next;
1224 if let Some((next_ix, url, title)) =
1225 self.scan_inline_link(block_text, self.tree[cur_ix].item.end, next)
1226 {
1227 let next_node = scan_nodes_to_ix(&self.tree, next, next_ix);
1228 if let Some(prev_ix) = prev {
1229 self.tree[prev_ix].next = None;
1230 }
1231 cur = Some(tos.node);
1232 cur_ix = tos.node;
1233 let link_ix =
1234 self.allocs
1235 .allocate_link(LinkType::Inline, url, title, "".into());
1236 self.tree[cur_ix].item.body = if tos.ty == LinkStackTy::Image {
1237 ItemBody::Image(link_ix)
1238 } else {
1239 ItemBody::Link(link_ix)
1240 };
1241 self.tree[cur_ix].child = self.tree[cur_ix].next;
1242 self.tree[cur_ix].next = next_node;
1243 self.tree[cur_ix].item.end = next_ix;
1244 if let Some(next_node_ix) = next_node {
1245 let orig_start = self.tree[next_node_ix].item.start;
1246 let new_start = max(orig_start, next_ix);
1247 self.tree[next_node_ix].item.start = new_start;
1248 if new_start > orig_start {
1257 if let ItemBody::Text { backslash_escaped } =
1258 &mut self.tree[next_node_ix].item.body
1259 {
1260 *backslash_escaped = false;
1261 }
1262 }
1263 }
1264
1265 if tos.ty == LinkStackTy::Link {
1266 self.disable_all_links();
1267 }
1268 } else {
1269 let first_bracket_start = self.tree[tos.node].item.start;
1276 let first_bracket_end = self.tree[cur_ix].item.end;
1277 let first_bracket_text =
1278 &self.text[first_bracket_start..first_bracket_end];
1279 if let Some((_, ReferenceLabel::Footnote(footlabel))) =
1280 scan_link_label(&self.tree, first_bracket_text, self.options)
1281 {
1282 if self.allocs.footdefs.contains(&footlabel) {
1283 let footref = self.allocs.allocate_cow(footlabel);
1284 if let Some(def) = self
1285 .allocs
1286 .footdefs
1287 .get_mut(self.allocs.cows[footref.0].to_owned())
1288 {
1289 def.use_count += 1;
1290 }
1291 let footnote_ix = if tos.ty == LinkStackTy::Image {
1292 self.tree[tos.node].next = Some(cur_ix);
1293 self.tree[tos.node].child = None;
1294 self.tree[tos.node].item.body =
1295 ItemBody::SynthesizeChar('!');
1296 self.tree[cur_ix].item.start =
1297 self.tree[tos.node].item.start + 1;
1298 self.tree[tos.node].item.end =
1299 self.tree[tos.node].item.start + 1;
1300 cur_ix
1301 } else {
1302 tos.node
1303 };
1304 self.tree[footnote_ix].next = next;
1305 self.tree[footnote_ix].child = None;
1306 self.tree[footnote_ix].item.body =
1307 ItemBody::FootnoteReference(footref);
1308 self.tree[footnote_ix].item.end = first_bracket_end;
1309 prev = Some(footnote_ix);
1310 cur = next;
1311 self.link_stack.clear();
1312 continue;
1313 }
1314 }
1315 let scan_result =
1318 scan_reference(&self.tree, block_text, next, self.options);
1319 let (node_after_link, link_type) = match scan_result {
1320 RefScan::LinkLabel(_, end_ix) => {
1322 let reference_close_node = if let Some(node) =
1327 scan_nodes_to_ix(&self.tree, next, end_ix - 1)
1328 {
1329 node
1330 } else {
1331 continue;
1332 };
1333 self.tree[reference_close_node].item.body =
1334 ItemBody::MaybeLinkClose(false);
1335 let next_node = self.tree[reference_close_node].next;
1336
1337 (next_node, LinkType::Reference)
1338 }
1339 RefScan::Collapsed(next_node) => {
1341 if !could_be_ref {
1344 continue;
1345 }
1346 (next_node, LinkType::Collapsed)
1347 }
1348 RefScan::UnexpectedFootnote => continue,
1355 RefScan::FailedInvalidLabel => continue,
1361 RefScan::Failed => {
1365 if !could_be_ref {
1366 continue;
1367 }
1368 (next, LinkType::Shortcut)
1369 }
1370 };
1371
1372 let label: Option<(ReferenceLabel<'input>, usize)> = match scan_result {
1377 RefScan::LinkLabel(l, end_ix) => {
1378 Some((ReferenceLabel::Link(l), end_ix))
1379 }
1380 RefScan::Collapsed(..)
1381 | RefScan::Failed
1382 | RefScan::FailedInvalidLabel
1383 | RefScan::UnexpectedFootnote => {
1384 let label_start = self.tree[tos.node].item.end - 1;
1386 let label_end = self.tree[cur_ix].item.end;
1387 scan_link_label(
1388 &self.tree,
1389 &self.text[label_start..label_end],
1390 self.options,
1391 )
1392 .map(|(ix, label)| (label, label_start + ix))
1393 .filter(|(_, end)| *end == label_end)
1394 }
1395 };
1396
1397 let id = match &label {
1398 Some(
1399 (ReferenceLabel::Link(l), _) | (ReferenceLabel::Footnote(l), _),
1400 ) => l.clone(),
1401 None => "".into(),
1402 };
1403
1404 if let Some((ReferenceLabel::Footnote(l), end)) = label {
1406 let footref = self.allocs.allocate_cow(l);
1407 if let Some(def) = self
1408 .allocs
1409 .footdefs
1410 .get_mut(self.allocs.cows[footref.0].to_owned())
1411 {
1412 def.use_count += 1;
1413 }
1414 if self.allocs.footdefs.contains(&self.allocs.cows[footref.0]) {
1415 let footnote_ix = if tos.ty == LinkStackTy::Image {
1418 self.tree[tos.node].next = Some(cur_ix);
1419 self.tree[tos.node].child = None;
1420 self.tree[tos.node].item.body =
1421 ItemBody::SynthesizeChar('!');
1422 self.tree[cur_ix].item.start =
1423 self.tree[tos.node].item.start + 1;
1424 self.tree[tos.node].item.end =
1425 self.tree[tos.node].item.start + 1;
1426 cur_ix
1427 } else {
1428 tos.node
1429 };
1430 self.tree[footnote_ix].next = next;
1434 self.tree[footnote_ix].child = None;
1435 self.tree[footnote_ix].item.body =
1436 ItemBody::FootnoteReference(footref);
1437 self.tree[footnote_ix].item.end = end;
1438 prev = Some(footnote_ix);
1439 cur = next;
1440 self.link_stack.clear();
1441 continue;
1442 }
1443 } else if let Some((ReferenceLabel::Link(link_label), end)) = label {
1444 if let Some((def_link_type, url, title)) = self
1445 .fetch_link_type_url_title(
1446 link_label,
1447 (self.tree[tos.node].item.start)..end,
1448 link_type,
1449 callbacks,
1450 )
1451 {
1452 let link_ix =
1453 self.allocs.allocate_link(def_link_type, url, title, id);
1454 self.tree[tos.node].item.body = if tos.ty == LinkStackTy::Image
1455 {
1456 ItemBody::Image(link_ix)
1457 } else {
1458 ItemBody::Link(link_ix)
1459 };
1460 let label_node = self.tree[tos.node].next;
1461
1462 self.tree[tos.node].next = node_after_link;
1465
1466 if label_node != cur {
1468 self.tree[tos.node].child = label_node;
1469
1470 if let Some(prev_ix) = prev {
1472 self.tree[prev_ix].next = None;
1473 }
1474 }
1475
1476 self.tree[tos.node].item.end = end;
1477
1478 cur = Some(tos.node);
1480 cur_ix = tos.node;
1481
1482 if tos.ty == LinkStackTy::Link {
1483 self.disable_all_links();
1484 }
1485 }
1486 }
1487 }
1488 }
1489 }
1490 _ => {}
1491 }
1492 prev = cur;
1493 cur = self.tree[cur_ix].next;
1494 }
1495 self.link_stack.clear();
1496 self.wikilink_stack.clear();
1497 self.code_delims.clear();
1498 self.math_delims.clear();
1499 }
1500
1501 fn handle_wikilink(
1507 &mut self,
1508 block_text: &'input str,
1509 cur_ix: TreeIndex,
1510 prev: Option<TreeIndex>,
1511 ) -> Option<TreeIndex> {
1512 let next_ix = self.tree[cur_ix].next.unwrap();
1513 if let Some(tos) = self.wikilink_stack.pop() {
1516 if tos.ty == LinkStackTy::Disabled {
1517 return None;
1518 }
1519 let Some(body_node) = self.tree[tos.node].next.and_then(|ix| self.tree[ix].next) else {
1521 return None;
1523 };
1524 let start_ix = self.tree[body_node].item.start;
1525 let end_ix = self.tree[cur_ix].item.start;
1526 let wikilink = match scan_wikilink_pipe(
1527 block_text,
1528 start_ix, end_ix - start_ix,
1530 ) {
1531 Some((rest, wikitext)) => {
1532 if wikitext.is_empty() {
1534 return None;
1535 }
1536 let body_node = scan_nodes_to_ix(&self.tree, Some(body_node), rest);
1538 if let Some(body_node) = body_node {
1539 self.tree[body_node].item.start = rest;
1542 Some((true, body_node, wikitext))
1543 } else {
1544 None
1545 }
1546 }
1547 None => {
1548 let wikitext = &block_text[start_ix..end_ix];
1549 if wikitext.is_empty() {
1551 return None;
1552 }
1553 let body_node = self.tree.create_node(Item {
1554 start: start_ix,
1555 end: end_ix,
1556 body: ItemBody::Text {
1557 backslash_escaped: false,
1558 },
1559 });
1560 Some((false, body_node, wikitext))
1561 }
1562 };
1563
1564 if let Some((has_pothole, body_node, wikiname)) = wikilink {
1565 let link_ix = self.allocs.allocate_link(
1566 LinkType::WikiLink { has_pothole },
1567 wikiname.into(),
1568 "".into(),
1569 "".into(),
1570 );
1571 if let Some(prev_ix) = prev {
1572 self.tree[prev_ix].next = None;
1573 }
1574 if tos.ty == LinkStackTy::Image {
1575 self.tree[tos.node].item.body = ItemBody::Image(link_ix);
1576 } else {
1577 self.tree[tos.node].item.body = ItemBody::Link(link_ix);
1578 }
1579 self.tree[tos.node].child = Some(body_node);
1580 self.tree[tos.node].next = self.tree[next_ix].next;
1581 self.tree[tos.node].item.end = end_ix + 2;
1582 self.disable_all_links();
1583 return Some(tos.node);
1584 }
1585 }
1586
1587 None
1588 }
1589
1590 fn handle_emphasis_in_scope(&mut self, start: Option<TreeIndex>) {
1591 let mut prev = None;
1592 let mut prev_ix: TreeIndex;
1593 let mut cur = start;
1594
1595 let mut single_quote_open: Option<TreeIndex> = None;
1596 let mut double_quote_open: bool = false;
1597
1598 while let Some(mut cur_ix) = cur {
1599 match self.tree[cur_ix].item.body {
1600 ItemBody::MaybeEmphasis(mut count, can_open, can_close) => {
1601 let run_length = count;
1602 let c = self.text.as_bytes()[self.tree[cur_ix].item.start];
1603 let both = can_open && can_close;
1604 if c == b'~' || c == b'^' {
1612 prev_ix = cur_ix + count - 1;
1613 prev = Some(prev_ix);
1614 cur = self.tree[prev_ix].next;
1615 continue;
1616 }
1617 if can_close {
1618 while let Some(el) =
1619 self.inline_stack
1620 .find_match(&mut self.tree, c, run_length, count, both)
1621 {
1622 if let Some(prev_ix) = prev {
1624 self.tree[prev_ix].next = None;
1625 }
1626 let match_count = min(2, min(count, el.count));
1635 let mut end = cur_ix - 1;
1637 let mut start = el.start + el.count;
1638
1639 while start > el.start + el.count - match_count {
1641 let inc = if start > el.start + el.count - match_count + 1 {
1642 2
1643 } else {
1644 1
1645 };
1646 let ty = if c == b'~' {
1647 if inc == 2 {
1648 if self.options.contains(Options::ENABLE_STRIKETHROUGH) {
1649 ItemBody::Strikethrough
1650 } else {
1651 ItemBody::Text {
1652 backslash_escaped: false,
1653 }
1654 }
1655 } else if self.options.contains(Options::ENABLE_SUBSCRIPT) {
1656 ItemBody::Subscript
1657 } else if self.options.contains(Options::ENABLE_STRIKETHROUGH) {
1658 ItemBody::Strikethrough
1659 } else {
1660 ItemBody::Text {
1661 backslash_escaped: false,
1662 }
1663 }
1664 } else if c == b'^' {
1665 if self.options.contains(Options::ENABLE_SUPERSCRIPT) {
1666 ItemBody::Superscript
1667 } else {
1668 ItemBody::Text {
1669 backslash_escaped: false,
1670 }
1671 }
1672 } else if inc == 2 {
1673 ItemBody::Strong
1674 } else {
1675 ItemBody::Emphasis
1676 };
1677
1678 let root = start - inc;
1679 end = end + inc;
1680 self.tree[root].item.body = ty;
1681 self.tree[root].item.end = self.tree[end].item.end;
1682 self.tree[root].child = Some(start);
1683 self.tree[root].next = None;
1684 start = root;
1685 }
1686
1687 prev_ix = el.start + el.count - match_count;
1689 prev = Some(prev_ix);
1690 cur = self.tree[cur_ix + match_count - 1].next;
1691 self.tree[prev_ix].next = cur;
1692
1693 if el.count > match_count {
1694 self.inline_stack.push(InlineEl {
1695 start: el.start,
1696 count: el.count - match_count,
1697 run_length: el.run_length,
1698 c: el.c,
1699 both: el.both,
1700 })
1701 }
1702 count -= match_count;
1703 if count > 0 {
1704 cur_ix = cur.unwrap();
1705 } else {
1706 break;
1707 }
1708 }
1709 }
1710 if count > 0 {
1711 if can_open {
1712 self.inline_stack.push(InlineEl {
1713 start: cur_ix,
1714 run_length,
1715 count,
1716 c,
1717 both,
1718 });
1719 } else {
1720 for i in 0..count {
1721 self.tree[cur_ix + i].item.body = ItemBody::Text {
1722 backslash_escaped: false,
1723 };
1724 }
1725 }
1726 prev_ix = cur_ix + count - 1;
1727 prev = Some(prev_ix);
1728 cur = self.tree[prev_ix].next;
1729 }
1730 }
1731 ItemBody::MaybeSmartQuote(c, can_open, can_close) => {
1732 self.tree[cur_ix].item.body = match c {
1733 b'\'' => {
1734 if let (Some(open_ix), true) = (single_quote_open, can_close) {
1735 self.tree[open_ix].item.body = ItemBody::SynthesizeChar('‘');
1736 single_quote_open = None;
1737 } else if can_open {
1738 single_quote_open = Some(cur_ix);
1739 }
1740 ItemBody::SynthesizeChar('’')
1741 }
1742 _ => {
1743 if can_close && double_quote_open {
1744 double_quote_open = false;
1745 ItemBody::SynthesizeChar('”')
1746 } else {
1747 if can_open && !double_quote_open {
1748 double_quote_open = true;
1749 }
1750 ItemBody::SynthesizeChar('“')
1751 }
1752 }
1753 };
1754 prev = cur;
1755 cur = self.tree[cur_ix].next;
1756 }
1757 ItemBody::HardBreak(true) => {
1758 if self.tree[cur_ix].next.is_none() {
1759 self.tree[cur_ix].item.body = ItemBody::SynthesizeChar('\\');
1760 }
1761 prev = cur;
1762 cur = self.tree[cur_ix].next;
1763 }
1764 _ => {
1765 prev = cur;
1766 cur = self.tree[cur_ix].next;
1767 }
1768 }
1769 }
1770 self.inline_stack.pop_all(&mut self.tree);
1771 }
1772
1773 fn resolve_tildes_carets_in_scope(&mut self, start: Option<TreeIndex>, descend: bool) {
1784 let mut stack: Vec<InlineEl> = Vec::new();
1785 let mut cur = start;
1786 let mut prev: Option<TreeIndex> = None;
1787 while let Some(mut cur_ix) = cur {
1788 match self.tree[cur_ix].item.body {
1789 ItemBody::MaybeEmphasis(count, can_open, can_close) => {
1790 let c = self.text.as_bytes()[self.tree[cur_ix].item.start];
1791 if c != b'~' && c != b'^' {
1792 prev = Some(cur_ix);
1793 cur = self.tree[cur_ix].next;
1794 continue;
1795 }
1796 let run_length = count;
1797 let mut remaining = count;
1798 if can_close {
1799 while remaining > 0 {
1800 let res = stack
1801 .iter()
1802 .enumerate()
1803 .rfind(|(_, el)| el.c == c && el.run_length == run_length);
1804 let Some((matching_ix, matching_el)) = res else {
1805 break;
1806 };
1807 let matching_el = *matching_el;
1808 if let Some(prev_ix) = prev {
1809 self.tree[prev_ix].next = None;
1810 }
1811 for el in &stack[(matching_ix + 1)..] {
1814 for i in 0..el.count {
1815 self.tree[el.start + i].item.body = ItemBody::Text {
1816 backslash_escaped: false,
1817 };
1818 }
1819 }
1820 stack.truncate(matching_ix);
1821 let match_count =
1822 core::cmp::min(2, core::cmp::min(remaining, matching_el.count));
1823 let mut end = cur_ix - 1;
1824 let mut sub_start = matching_el.start + matching_el.count;
1825 while sub_start > matching_el.start + matching_el.count - match_count {
1826 let inc = if sub_start
1827 > matching_el.start + matching_el.count - match_count + 1
1828 {
1829 2
1830 } else {
1831 1
1832 };
1833 let ty = if c == b'~' {
1834 if inc == 2 {
1835 if self.options.contains(Options::ENABLE_STRIKETHROUGH) {
1836 ItemBody::Strikethrough
1837 } else {
1838 ItemBody::Text {
1839 backslash_escaped: false,
1840 }
1841 }
1842 } else if self.options.contains(Options::ENABLE_SUBSCRIPT) {
1843 ItemBody::Subscript
1844 } else if self.options.contains(Options::ENABLE_STRIKETHROUGH) {
1845 ItemBody::Strikethrough
1846 } else {
1847 ItemBody::Text {
1848 backslash_escaped: false,
1849 }
1850 }
1851 } else if self.options.contains(Options::ENABLE_SUPERSCRIPT) {
1852 ItemBody::Superscript
1853 } else {
1854 ItemBody::Text {
1855 backslash_escaped: false,
1856 }
1857 };
1858 let root = sub_start - inc;
1859 end = end + inc;
1860 self.tree[root].item.body = ty;
1861 self.tree[root].item.end = self.tree[end].item.end;
1862 self.tree[root].child = Some(sub_start);
1863 self.tree[root].next = None;
1864 sub_start = root;
1865 }
1866 let new_prev_ix = matching_el.start + matching_el.count - match_count;
1867 let new_cur = self.tree[cur_ix + match_count - 1].next;
1868 self.tree[new_prev_ix].next = new_cur;
1869 prev = Some(new_prev_ix);
1870 if matching_el.count > match_count {
1871 stack.push(InlineEl {
1872 start: matching_el.start,
1873 count: matching_el.count - match_count,
1874 run_length: matching_el.run_length,
1875 c: matching_el.c,
1876 both: matching_el.both,
1877 });
1878 }
1879 remaining -= match_count;
1880 if remaining > 0 {
1881 let Some(next_cur) = new_cur else { break };
1882 cur_ix = next_cur;
1883 } else {
1884 break;
1885 }
1886 }
1887 }
1888 if remaining > 0 {
1889 if can_open {
1890 stack.push(InlineEl {
1891 start: cur_ix,
1892 count: remaining,
1893 run_length,
1894 c,
1895 both: can_open && can_close,
1896 });
1897 } else {
1898 for i in 0..remaining {
1899 self.tree[cur_ix + i].item.body = ItemBody::Text {
1900 backslash_escaped: false,
1901 };
1902 }
1903 }
1904 let prev_ix = cur_ix + remaining - 1;
1905 prev = Some(prev_ix);
1906 cur = self.tree[prev_ix].next;
1907 } else {
1908 cur = self.tree[prev.unwrap()].next;
1909 }
1910 continue;
1911 }
1912 ItemBody::Emphasis
1913 | ItemBody::Strong
1914 | ItemBody::Strikethrough
1915 | ItemBody::Subscript
1916 | ItemBody::Superscript
1917 | ItemBody::Link(_)
1918 | ItemBody::Image(_)
1919 if descend =>
1920 {
1921 let child = self.tree[cur_ix].child;
1922 self.resolve_tildes_carets_in_scope(child, true);
1923 }
1924 _ => {}
1925 }
1926 prev = Some(cur_ix);
1927 cur = self.tree[cur_ix].next;
1928 }
1929 for el in stack {
1931 for i in 0..el.count {
1932 self.tree[el.start + i].item.body = ItemBody::Text {
1933 backslash_escaped: false,
1934 };
1935 }
1936 }
1937 }
1938
1939 fn disable_all_links(&mut self) {
1940 self.link_stack.disable_all_links();
1941 self.wikilink_stack.disable_all_links();
1942 }
1943
1944 fn scan_inline_link(
1946 &self,
1947 underlying: &'input str,
1948 mut ix: usize,
1949 node: Option<TreeIndex>,
1950 ) -> Option<(usize, CowStr<'input>, CowStr<'input>)> {
1951 if underlying.as_bytes().get(ix) != Some(&b'(') {
1952 return None;
1953 }
1954 ix += 1;
1955
1956 let scan_separator = |ix: &mut usize| {
1957 *ix += scan_while(&underlying.as_bytes()[*ix..], is_ascii_whitespace_no_nl);
1958 if let Some(bl) = scan_eol(&underlying.as_bytes()[*ix..]) {
1959 *ix += bl;
1960 *ix += skip_container_prefixes(
1961 &self.tree,
1962 &underlying.as_bytes()[*ix..],
1963 self.options,
1964 );
1965 }
1966 *ix += scan_while(&underlying.as_bytes()[*ix..], is_ascii_whitespace_no_nl);
1967 };
1968
1969 scan_separator(&mut ix);
1970
1971 let (dest_length, dest) = scan_link_dest(underlying, ix, LINK_MAX_NESTED_PARENS)?;
1972 let dest = unescape(dest, self.tree.is_in_table());
1973 ix += dest_length;
1974
1975 scan_separator(&mut ix);
1976
1977 let title = if let Some((bytes_scanned, t)) = self.scan_link_title(underlying, ix, node) {
1978 ix += bytes_scanned;
1979 scan_separator(&mut ix);
1980 t
1981 } else {
1982 "".into()
1983 };
1984 if underlying.as_bytes().get(ix) != Some(&b')') {
1985 return None;
1986 }
1987 ix += 1;
1988
1989 Some((ix, dest, title))
1990 }
1991
1992 fn scan_link_title(
1994 &self,
1995 text: &'input str,
1996 start_ix: usize,
1997 node: Option<TreeIndex>,
1998 ) -> Option<(usize, CowStr<'input>)> {
1999 let bytes = text.as_bytes();
2000 let open = match bytes.get(start_ix) {
2001 Some(b @ b'\'') | Some(b @ b'\"') | Some(b @ b'(') => *b,
2002 _ => return None,
2003 };
2004 let close = if open == b'(' { b')' } else { open };
2005
2006 let mut title = String::new();
2007 let mut mark = start_ix + 1;
2008 let mut i = start_ix + 1;
2009
2010 while i < bytes.len() {
2011 let c = bytes[i];
2012
2013 if c == close {
2014 let cow = if title.is_empty() {
2015 (i - start_ix + 1, text[mark..i].into())
2016 } else {
2017 title.push_str(&text[mark..i]);
2018 (i - start_ix + 1, title.into())
2019 };
2020
2021 return Some(cow);
2022 }
2023 if c == open {
2024 return None;
2025 }
2026
2027 if c == b'\n' || c == b'\r' {
2028 if let Some(node_ix) = scan_nodes_to_ix(&self.tree, node, i + 1) {
2029 if self.tree[node_ix].item.start > i {
2030 title.push_str(&text[mark..i]);
2031 title.push('\n');
2032 i = self.tree[node_ix].item.start;
2033 mark = i;
2034 continue;
2035 }
2036 }
2037 }
2038 if c == b'&' {
2039 if let (n, Some(value)) = scan_entity(&bytes[i..]) {
2040 title.push_str(&text[mark..i]);
2041 title.push_str(&value);
2042 i += n;
2043 mark = i;
2044 continue;
2045 }
2046 }
2047 if self.tree.is_in_table()
2048 && c == b'\\'
2049 && i + 2 < bytes.len()
2050 && bytes[i + 1] == b'\\'
2051 && bytes[i + 2] == b'|'
2052 {
2053 title.push_str(&text[mark..i]);
2056 i += 2;
2057 mark = i;
2058 }
2059 if c == b'\\' && i + 1 < bytes.len() && is_ascii_punctuation(bytes[i + 1]) {
2060 title.push_str(&text[mark..i]);
2061 i += 1;
2062 mark = i;
2063 }
2064
2065 i += 1;
2066 }
2067
2068 None
2069 }
2070
2071 fn make_math_span(&mut self, open: TreeIndex, close: TreeIndex) {
2072 let mut open_end = open;
2074 {
2075 let mut peek = self.tree[open].next;
2076 while let Some(peek_ix) = peek {
2077 if matches!(self.tree[peek_ix].item.body, ItemBody::MaybeMath(..))
2078 && self.tree[peek_ix].item.start == self.tree[open_end].item.end
2079 && peek_ix != close
2080 {
2081 open_end = peek_ix;
2082 peek = self.tree[peek_ix].next;
2083 } else {
2084 break;
2085 }
2086 }
2087 }
2088 let mut close_end = close;
2090 {
2091 let mut peek = self.tree[close].next;
2092 while let Some(peek_ix) = peek {
2093 if matches!(self.tree[peek_ix].item.body, ItemBody::MaybeMath(..))
2094 && self.tree[peek_ix].item.start == self.tree[close_end].item.end
2095 {
2096 close_end = peek_ix;
2097 peek = self.tree[peek_ix].next;
2098 } else {
2099 break;
2100 }
2101 }
2102 }
2103
2104 let span_start = self.tree[open_end].item.end;
2105 let span_end = self.tree[close].item.start;
2106
2107 if span_start > span_end {
2108 self.tree[open].item.body = ItemBody::Text {
2109 backslash_escaped: false,
2110 };
2111 return;
2112 }
2113
2114 let spanned_text = &self.text[span_start..span_end];
2115 let spanned_bytes = spanned_text.as_bytes();
2116 let mut buf: Option<String> = None;
2117
2118 let mut start_ix = 0;
2119 let mut ix = 0;
2120 while ix < spanned_bytes.len() {
2121 let c = spanned_bytes[ix];
2122 if c == b'\r' || c == b'\n' {
2123 ix += 1;
2124 let buf = buf.get_or_insert_with(|| String::with_capacity(spanned_bytes.len()));
2125 buf.push_str(&spanned_text[start_ix..ix]);
2126 let from = span_start + ix;
2135 let (scanned, leftover) = skip_container_prefixes_with_remaining(
2136 &self.tree,
2137 &self.text.as_bytes()[from..],
2138 self.options,
2139 );
2140 let scanned = scanned.min(spanned_bytes.len() - ix);
2141 ix += scanned;
2142 start_ix = ix;
2143 for _ in 0..leftover {
2147 buf.push(' ');
2148 }
2149 } else if c == b'\\'
2150 && spanned_bytes.get(ix + 1) == Some(&b'|')
2151 && self.tree.is_in_table()
2152 {
2153 let buf = buf.get_or_insert_with(|| String::with_capacity(spanned_bytes.len()));
2154 buf.push_str(&spanned_text[start_ix..ix]);
2155 buf.push('|');
2156 ix += 2;
2157 start_ix = ix;
2158 } else {
2159 ix += 1;
2160 }
2161 }
2162
2163 let (opening, closing, all_spaces) = {
2164 let s = if let Some(buf) = &mut buf {
2165 buf.push_str(&spanned_text[start_ix..]);
2166 &buf[..]
2167 } else {
2168 spanned_text
2169 };
2170 (
2171 matches!(s.as_bytes().first(), Some(b' ' | b'\n')),
2172 matches!(s.as_bytes().last(), Some(b' ' | b'\n')),
2173 s.bytes().all(|b| b == b' ' || b == b'\n'),
2174 )
2175 };
2176
2177 let cow: CowStr<'input> = if !all_spaces && opening && closing {
2178 if let Some(mut buf) = buf {
2179 if !buf.is_empty() {
2180 buf.remove(0);
2181 buf.pop();
2182 }
2183 buf.into()
2184 } else {
2185 spanned_text[1..(spanned_text.len() - 1).max(1)].into()
2186 }
2187 } else if let Some(buf) = buf {
2188 buf.into()
2189 } else {
2190 spanned_text.into()
2191 };
2192
2193 self.tree[open].item.body = ItemBody::Math(self.allocs.allocate_cow(cow), false);
2194 self.tree[open].item.end = self.tree[close_end].item.end;
2195 self.tree[open].next = self.tree[close_end].next;
2196 }
2197
2198 fn make_code_span(&mut self, open: TreeIndex, close: TreeIndex, preceding_backslash: bool) {
2202 let span_start = self.tree[open].item.end;
2203 let span_end = self.tree[close].item.start;
2204 let mut buf: Option<String> = None;
2205
2206 let spanned_text = &self.text[span_start..span_end];
2207 let spanned_bytes = spanned_text.as_bytes();
2208 let mut start_ix = 0;
2209 let mut ix = 0;
2210 while ix < spanned_bytes.len() {
2211 let c = spanned_bytes[ix];
2212 if c == b'\r' || c == b'\n' {
2213 let buf = buf.get_or_insert_with(|| String::with_capacity(spanned_bytes.len()));
2214 buf.push_str(&spanned_text[start_ix..ix]);
2215 buf.push('\n');
2216 ix += 1;
2217 if c == b'\r' && spanned_bytes.get(ix) == Some(&b'\n') {
2218 ix += 1;
2219 }
2220 let from = span_start + ix;
2229 let (scanned, leftover) = skip_container_prefixes_with_remaining(
2230 &self.tree,
2231 &self.text.as_bytes()[from..],
2232 self.options,
2233 );
2234 let scanned = scanned.min(spanned_bytes.len() - ix);
2235 ix += scanned;
2236 start_ix = ix;
2237 for _ in 0..leftover {
2241 buf.push(' ');
2242 }
2243 } else if c == b'\\'
2244 && spanned_bytes.get(ix + 1) == Some(&b'|')
2245 && self.tree.is_in_table()
2246 {
2247 let buf = buf.get_or_insert_with(|| String::with_capacity(spanned_bytes.len()));
2248 buf.push_str(&spanned_text[start_ix..ix]);
2249 buf.push('|');
2250 ix += 2;
2251 start_ix = ix;
2252 } else {
2253 ix += 1;
2254 }
2255 }
2256
2257 let (opening, closing, all_spaces) = {
2258 let s = if let Some(buf) = &mut buf {
2259 buf.push_str(&spanned_text[start_ix..]);
2260 &buf[..]
2261 } else {
2262 spanned_text
2263 };
2264 (
2265 matches!(s.as_bytes().first(), Some(b' ' | b'\n')),
2266 matches!(s.as_bytes().last(), Some(b' ' | b'\n')),
2267 s.bytes().all(|b| b == b' ' || b == b'\n'),
2268 )
2269 };
2270
2271 let cow: CowStr<'input> = if !all_spaces && opening && closing {
2272 if let Some(mut buf) = buf {
2273 if !buf.is_empty() {
2274 buf.remove(0);
2275 buf.pop();
2276 }
2277 buf.into()
2278 } else {
2279 spanned_text[1..(spanned_text.len() - 1).max(1)].into()
2280 }
2281 } else if let Some(buf) = buf {
2282 buf.into()
2283 } else {
2284 spanned_text.into()
2285 };
2286
2287 if preceding_backslash {
2288 self.tree[open].item.body = ItemBody::Text {
2289 backslash_escaped: true,
2290 };
2291 self.tree[open].item.end = self.tree[open].item.start + 1;
2292 self.tree[open].next = Some(close);
2293 self.tree[close].item.body = ItemBody::Code(self.allocs.allocate_cow(cow));
2294 self.tree[close].item.start = self.tree[open].item.start + 1;
2295 } else {
2296 self.tree[open].item.body = ItemBody::Code(self.allocs.allocate_cow(cow));
2297 self.tree[open].item.end = self.tree[close].item.end;
2298 self.tree[open].next = self.tree[close].next;
2299 }
2300
2301 if !self.mdx_errors.is_empty() {
2304 self.mdx_errors
2305 .retain(|(offset, _)| *offset < span_start || *offset >= span_end);
2306 }
2307 }
2308
2309 fn scan_inline_html(&mut self, bytes: &[u8], ix: usize) -> Option<(Vec<u8>, usize)> {
2313 let c = *bytes.get(ix)?;
2314 if c == b'!' {
2315 Some((
2316 vec![],
2317 scan_inline_html_comment(bytes, ix + 1, &mut self.html_scan_guard)?,
2318 ))
2319 } else if c == b'?' {
2320 Some((
2321 vec![],
2322 scan_inline_html_processing(bytes, ix + 1, &mut self.html_scan_guard)?,
2323 ))
2324 } else {
2325 let (span, i) = scan_html_block_inner(
2326 &bytes[(ix - 1)..],
2328 Some(&|bytes| skip_container_prefixes(&self.tree, bytes, self.options)),
2329 )?;
2330 Some((span, i + ix - 1))
2331 }
2332 }
2333}
2334
2335pub(crate) fn scan_containers(
2337 tree: &Tree<Item>,
2338 line_start: &mut LineStart<'_>,
2339 options: Options,
2340) -> usize {
2341 let mut i = 0;
2342 for &node_ix in tree.walk_spine() {
2343 match tree[node_ix].item.body {
2344 ItemBody::BlockQuote(..) => {
2345 let save = line_start.clone();
2346 if options.contains(Options::ENABLE_MDX) {
2351 line_start.scan_all_space();
2352 } else {
2353 let _ = line_start.scan_space(3);
2354 }
2355 if !line_start.scan_blockquote_marker() {
2356 *line_start = save;
2357 break;
2358 }
2359 }
2360 ItemBody::ListItem(indent, _) => {
2361 let save = line_start.clone();
2362 if !line_start.scan_space(indent) && !line_start.is_at_eol() {
2363 *line_start = save;
2364 break;
2365 }
2366 }
2367 ItemBody::DefinitionListDefinition(indent) => {
2368 let save = line_start.clone();
2369 if !line_start.scan_space(indent) && !line_start.is_at_eol() {
2370 *line_start = save;
2371 break;
2372 }
2373 }
2374 ItemBody::FootnoteDefinition(..) if options.contains(Options::ENABLE_FOOTNOTES) => {
2375 let save = line_start.clone();
2376 if !line_start.scan_space(4) && !line_start.is_at_eol() {
2377 *line_start = save;
2378 break;
2379 }
2380 }
2381 _ => (),
2382 }
2383 i += 1;
2384 }
2385 i
2386}
2387
2388pub(crate) fn skip_container_prefixes(tree: &Tree<Item>, bytes: &[u8], options: Options) -> usize {
2389 let mut line_start = LineStart::new(bytes);
2390 let _ = scan_containers(tree, &mut line_start, options);
2391 line_start.bytes_scanned()
2392}
2393
2394fn skip_container_prefixes_with_remaining(
2401 tree: &Tree<Item>,
2402 bytes: &[u8],
2403 options: Options,
2404) -> (usize, usize) {
2405 let mut line_start = LineStart::new(bytes);
2406 let _ = scan_containers(tree, &mut line_start, options);
2407 (line_start.bytes_scanned(), line_start.remaining_space())
2408}
2409
2410impl Tree<Item> {
2411 pub(crate) fn append_text(&mut self, start: usize, end: usize, backslash_escaped: bool) {
2412 if end > start {
2413 if let Some(ix) = self.cur() {
2414 if matches!(self[ix].item.body, ItemBody::Text { .. }) && self[ix].item.end == start
2415 {
2416 self[ix].item.end = end;
2417 return;
2418 }
2419 }
2420 self.append(Item {
2421 start,
2422 end,
2423 body: ItemBody::Text { backslash_escaped },
2424 });
2425 }
2426 }
2427 pub(crate) fn is_in_table(&self) -> bool {
2434 fn might_be_in_table(item: &Item) -> bool {
2435 item.body.is_inline()
2436 || matches!(item.body, |ItemBody::TableHead| ItemBody::TableRow
2437 | ItemBody::TableCell)
2438 }
2439 for &ix in self.walk_spine().rev() {
2440 if matches!(self[ix].item.body, ItemBody::Table(_)) {
2441 return true;
2442 }
2443 if !might_be_in_table(&self[ix].item) {
2444 return false;
2445 }
2446 }
2447 false
2448 }
2449}
2450
2451#[derive(Copy, Clone, Debug)]
2452struct InlineEl {
2453 start: TreeIndex,
2455 count: usize,
2457 run_length: usize,
2459 c: u8,
2461 both: bool,
2463}
2464
2465#[derive(Debug, Clone, Default)]
2466struct InlineStack {
2467 stack: Vec<InlineEl>,
2468 lower_bounds: [usize; 10],
2473}
2474
2475impl InlineStack {
2476 const UNDERSCORE_NOT_BOTH: usize = 0;
2480 const ASTERISK_NOT_BOTH: usize = 1;
2481 const ASTERISK_BASE: usize = 2;
2482 const TILDES: usize = 5;
2483 const UNDERSCORE_BASE: usize = 6;
2484 const CIRCUMFLEXES: usize = 9;
2485
2486 fn pop_all(&mut self, tree: &mut Tree<Item>) {
2487 for el in self.stack.drain(..) {
2488 for i in 0..el.count {
2489 tree[el.start + i].item.body = ItemBody::Text {
2490 backslash_escaped: false,
2491 };
2492 }
2493 }
2494 self.lower_bounds = [0; 10];
2495 }
2496
2497 fn get_lowerbound(&self, c: u8, count: usize, both: bool) -> usize {
2498 if c == b'_' {
2499 let mod3_lower = self.lower_bounds[InlineStack::UNDERSCORE_BASE + count % 3];
2500 if both {
2501 mod3_lower
2502 } else {
2503 min(
2504 mod3_lower,
2505 self.lower_bounds[InlineStack::UNDERSCORE_NOT_BOTH],
2506 )
2507 }
2508 } else if c == b'*' {
2509 let mod3_lower = self.lower_bounds[InlineStack::ASTERISK_BASE + count % 3];
2510 if both {
2511 mod3_lower
2512 } else {
2513 min(
2514 mod3_lower,
2515 self.lower_bounds[InlineStack::ASTERISK_NOT_BOTH],
2516 )
2517 }
2518 } else if c == b'^' {
2519 self.lower_bounds[InlineStack::CIRCUMFLEXES]
2520 } else {
2521 self.lower_bounds[InlineStack::TILDES]
2522 }
2523 }
2524
2525 fn set_lowerbound(&mut self, c: u8, count: usize, both: bool, new_bound: usize) {
2526 if c == b'_' {
2527 if both {
2528 self.lower_bounds[InlineStack::UNDERSCORE_BASE + count % 3] = new_bound;
2529 } else {
2530 self.lower_bounds[InlineStack::UNDERSCORE_NOT_BOTH] = new_bound;
2531 }
2532 } else if c == b'*' {
2533 self.lower_bounds[InlineStack::ASTERISK_BASE + count % 3] = new_bound;
2534 if !both {
2535 self.lower_bounds[InlineStack::ASTERISK_NOT_BOTH] = new_bound;
2536 }
2537 } else if c == b'^' {
2538 self.lower_bounds[InlineStack::CIRCUMFLEXES] = new_bound;
2539 } else {
2540 self.lower_bounds[InlineStack::TILDES] = new_bound;
2541 }
2542 }
2543
2544 fn truncate(&mut self, new_bound: usize) {
2545 self.stack.truncate(new_bound);
2546 for lower_bound in &mut self.lower_bounds {
2547 if *lower_bound > new_bound {
2548 *lower_bound = new_bound;
2549 }
2550 }
2551 }
2552
2553 fn find_match(
2566 &mut self,
2567 tree: &mut Tree<Item>,
2568 c: u8,
2569 run_length: usize,
2570 current_count: usize,
2571 both: bool,
2572 ) -> Option<InlineEl> {
2573 let lowerbound = min(
2583 self.stack.len(),
2584 self.get_lowerbound(c, current_count, both),
2585 );
2586 let res = self.stack[lowerbound..]
2587 .iter()
2588 .cloned()
2589 .enumerate()
2590 .rfind(|(_, el)| {
2591 if (c == b'~' || c == b'^') && run_length != el.run_length {
2592 return false;
2593 }
2594 el.c == c
2599 && (!both && !el.both
2600 || !(current_count + el.count).is_multiple_of(3)
2601 || current_count.is_multiple_of(3))
2602 });
2603
2604 if let Some((matching_ix, matching_el)) = res {
2605 let matching_ix = matching_ix + lowerbound;
2606 for el in &self.stack[(matching_ix + 1)..] {
2607 for i in 0..el.count {
2608 tree[el.start + i].item.body = ItemBody::Text {
2609 backslash_escaped: false,
2610 };
2611 }
2612 }
2613 self.truncate(matching_ix);
2614 Some(matching_el)
2615 } else {
2616 if c != b'~' && c != b'^' {
2626 self.set_lowerbound(c, current_count, both, self.stack.len());
2627 }
2628 None
2629 }
2630 }
2631
2632 fn trim_lower_bound(&mut self, ix: usize) {
2633 self.lower_bounds[ix] = self.lower_bounds[ix].min(self.stack.len());
2634 }
2635
2636 fn push(&mut self, el: InlineEl) {
2637 if el.c == b'~' {
2638 self.trim_lower_bound(InlineStack::TILDES);
2639 } else if el.c == b'^' {
2640 self.trim_lower_bound(InlineStack::CIRCUMFLEXES);
2641 }
2642 self.stack.push(el)
2643 }
2644}
2645
2646#[derive(Debug, Clone)]
2647enum RefScan<'a> {
2648 LinkLabel(CowStr<'a>, usize),
2650 Collapsed(Option<TreeIndex>),
2652 UnexpectedFootnote,
2653 Failed,
2654 FailedInvalidLabel,
2659}
2660
2661fn scan_nodes_to_ix(
2664 tree: &Tree<Item>,
2665 mut node: Option<TreeIndex>,
2666 ix: usize,
2667) -> Option<TreeIndex> {
2668 while let Some(node_ix) = node {
2669 if tree[node_ix].item.end <= ix {
2670 node = tree[node_ix].next;
2671 } else {
2672 break;
2673 }
2674 }
2675 node
2676}
2677
2678fn scan_link_label<'text>(
2681 tree: &Tree<Item>,
2682 text: &'text str,
2683 options: Options,
2684) -> Option<(usize, ReferenceLabel<'text>)> {
2685 let bytes = text.as_bytes();
2686 if bytes.len() < 2 || bytes[0] != b'[' {
2687 return None;
2688 }
2689 let linebreak_handler = |bytes: &[u8]| Some(skip_container_prefixes(tree, bytes, options));
2690 if options.contains(Options::ENABLE_FOOTNOTES)
2691 && b'^' == bytes[1]
2692 && bytes.get(2) != Some(&b']')
2693 {
2694 let linebreak_handler: &dyn Fn(&[u8]) -> Option<usize> = &|_| None;
2696 if let Some((byte_index, cow)) =
2697 scan_link_label_rest(&text[2..], linebreak_handler, tree.is_in_table())
2698 {
2699 return Some((byte_index + 2, ReferenceLabel::Footnote(cow)));
2700 }
2701 }
2702 let (byte_index, cow) =
2703 scan_link_label_rest(&text[1..], &linebreak_handler, tree.is_in_table())?;
2704 Some((byte_index + 1, ReferenceLabel::Link(cow)))
2705}
2706
2707fn scan_reference<'b>(
2708 tree: &Tree<Item>,
2709 text: &'b str,
2710 cur: Option<TreeIndex>,
2711 options: Options,
2712) -> RefScan<'b> {
2713 let cur_ix = match cur {
2714 None => return RefScan::Failed,
2715 Some(cur_ix) => cur_ix,
2716 };
2717 let start = tree[cur_ix].item.start;
2718 let tail = &text.as_bytes()[start..];
2719
2720 if tail.first() == Some(&b'[') && start > 0 {
2727 let src = text.as_bytes();
2728 let mut backslashes = 0usize;
2729 let mut j = start;
2730 while j > 0 && src[j - 1] == b'\\' {
2731 backslashes += 1;
2732 j -= 1;
2733 }
2734 if backslashes % 2 == 1 {
2735 return RefScan::Failed;
2736 }
2737 }
2738
2739 if tail.starts_with(b"[]") {
2740 let Some(closing_node) = tree[cur_ix].next else {
2745 return RefScan::Failed;
2746 };
2747 RefScan::Collapsed(tree[closing_node].next)
2748 } else {
2749 let label = scan_link_label(tree, &text[start..], options);
2750 match label {
2751 Some((ix, ReferenceLabel::Link(label))) => RefScan::LinkLabel(label, start + ix),
2752 Some((_ix, ReferenceLabel::Footnote(_label))) => RefScan::UnexpectedFootnote,
2753 None => {
2754 if tail.starts_with(b"[") {
2759 RefScan::FailedInvalidLabel
2760 } else {
2761 RefScan::Failed
2762 }
2763 }
2764 }
2765 }
2766}
2767
2768#[derive(Clone, Default)]
2769struct LinkStack {
2770 inner: Vec<LinkStackEl>,
2771 disabled_ix: usize,
2772}
2773
2774impl LinkStack {
2775 fn push(&mut self, el: LinkStackEl) {
2776 self.inner.push(el);
2777 }
2778
2779 fn pop(&mut self) -> Option<LinkStackEl> {
2780 let el = self.inner.pop();
2781 self.disabled_ix = core::cmp::min(self.disabled_ix, self.inner.len());
2782 el
2783 }
2784
2785 fn clear(&mut self) {
2786 self.inner.clear();
2787 self.disabled_ix = 0;
2788 }
2789
2790 fn disable_all_links(&mut self) {
2791 for el in &mut self.inner[self.disabled_ix..] {
2792 if el.ty == LinkStackTy::Link {
2793 el.ty = LinkStackTy::Disabled;
2794 }
2795 }
2796 self.disabled_ix = self.inner.len();
2797 }
2798}
2799
2800#[derive(Clone, Debug)]
2801struct LinkStackEl {
2802 node: TreeIndex,
2803 ty: LinkStackTy,
2804}
2805
2806#[derive(PartialEq, Clone, Debug)]
2807enum LinkStackTy {
2808 Link,
2809 Image,
2810 Disabled,
2811}
2812
2813#[derive(Clone, Debug)]
2815pub struct LinkDef<'a> {
2816 pub dest: CowStr<'a>,
2817 pub title: Option<CowStr<'a>>,
2818 pub span: Range<usize>,
2819}
2820
2821impl<'a> LinkDef<'a> {
2822 pub fn into_static(self) -> LinkDef<'static> {
2823 LinkDef {
2824 dest: self.dest.into_static(),
2825 title: self.title.map(|s| s.into_static()),
2826 span: self.span,
2827 }
2828 }
2829}
2830
2831#[derive(Clone, Debug)]
2833pub struct FootnoteDef {
2834 pub use_count: usize,
2835}
2836
2837struct CodeDelims {
2840 inner: FxHashMap<usize, VecDeque<TreeIndex>>,
2841 seen_first: bool,
2842}
2843
2844impl CodeDelims {
2845 fn new() -> Self {
2846 Self {
2847 inner: Default::default(),
2848 seen_first: false,
2849 }
2850 }
2851
2852 fn insert(&mut self, count: usize, ix: TreeIndex) {
2853 if self.seen_first {
2854 self.inner.entry(count).or_default().push_back(ix);
2855 } else {
2856 self.seen_first = true;
2859 }
2860 }
2861
2862 fn is_populated(&self) -> bool {
2863 !self.inner.is_empty()
2864 }
2865
2866 fn find(&mut self, open_ix: TreeIndex, count: usize) -> Option<TreeIndex> {
2867 while let Some(ix) = self.inner.get_mut(&count)?.pop_front() {
2868 if ix > open_ix {
2869 return Some(ix);
2870 }
2871 }
2872 None
2873 }
2874
2875 fn clear(&mut self) {
2876 self.inner.clear();
2877 self.seen_first = false;
2878 }
2879}
2880
2881struct MathDelims {
2884 inner: FxHashMap<u8, VecDeque<(TreeIndex, bool, bool)>>,
2885}
2886
2887impl MathDelims {
2888 fn new() -> Self {
2889 Self {
2890 inner: Default::default(),
2891 }
2892 }
2893
2894 fn clear(&mut self) {
2895 self.inner.clear();
2896 }
2897}
2898
2899#[derive(Copy, Clone, PartialEq, Eq, Debug)]
2900pub(crate) struct LinkIndex(usize);
2901
2902#[derive(Copy, Clone, PartialEq, Eq, Debug)]
2903pub(crate) struct CowIndex(usize);
2904
2905#[derive(Copy, Clone, PartialEq, Eq, Debug)]
2906pub(crate) struct AlignmentIndex(usize);
2907
2908#[derive(Copy, Clone, PartialEq, Eq, Debug)]
2909pub(crate) struct HeadingIndex(NonZeroUsize);
2910
2911#[cfg(feature = "mdx")]
2912#[derive(Copy, Clone, PartialEq, Eq, Debug)]
2913pub(crate) struct JsxElementIndex(usize);
2914
2915#[derive(Copy, Clone, PartialEq, Eq, Debug)]
2916pub(crate) struct DirectiveIndex(usize);
2917
2918#[cfg(feature = "mdx")]
2920#[derive(Debug, Clone)]
2921pub(crate) enum JsxAttr<'a> {
2922 Boolean(CowStr<'a>),
2923 Literal(CowStr<'a>, CowStr<'a>),
2924 Expression(CowStr<'a>, CowStr<'a>, usize, usize),
2928 Spread(CowStr<'a>, usize, usize),
2931}
2932
2933#[cfg(feature = "mdx")]
2934impl<'a> JsxAttr<'a> {
2935 pub fn into_static(self) -> JsxAttr<'static> {
2936 match self {
2937 JsxAttr::Boolean(n) => JsxAttr::Boolean(n.into_static()),
2938 JsxAttr::Literal(n, v) => JsxAttr::Literal(n.into_static(), v.into_static()),
2939 JsxAttr::Expression(n, v, start, end) => {
2940 JsxAttr::Expression(n.into_static(), v.into_static(), start, end)
2941 }
2942 JsxAttr::Spread(v, start, end) => JsxAttr::Spread(v.into_static(), start, end),
2943 }
2944 }
2945}
2946
2947#[cfg(feature = "mdx")]
2949#[derive(Debug, Clone)]
2950pub(crate) struct JsxElementData<'a> {
2951 pub name: CowStr<'a>,
2952 pub attrs: Vec<JsxAttr<'a>>,
2953 pub raw: CowStr<'a>,
2954 pub is_closing: bool,
2955 pub is_self_closing: bool,
2956}
2957
2958#[cfg(feature = "mdx")]
2959impl<'a> JsxElementData<'a> {
2960 pub fn into_static(self) -> JsxElementData<'static> {
2961 JsxElementData {
2962 name: self.name.into_static(),
2963 attrs: self.attrs.into_iter().map(|a| a.into_static()).collect(),
2964 raw: self.raw.into_static(),
2965 is_closing: self.is_closing,
2966 is_self_closing: self.is_self_closing,
2967 }
2968 }
2969}
2970
2971#[derive(Debug, Clone)]
2972pub(crate) struct DirectiveAttrData<'a> {
2973 pub name: CowStr<'a>,
2974 pub attributes: Vec<(CowStr<'a>, CowStr<'a>)>,
2975 pub label_start: usize,
2976 pub label_end: usize,
2977 pub initial_size: u8,
2983}
2984
2985#[derive(Clone)]
2986pub(crate) struct Allocations<'a> {
2987 pub refdefs: RefDefs<'a>,
2988 pub refdefs_all: Vec<(LinkLabel<'a>, LinkDef<'a>)>,
2993 pub footdefs: FootnoteDefs<'a>,
2994 links: Vec<(LinkType, CowStr<'a>, CowStr<'a>, CowStr<'a>)>,
2995 cows: Vec<CowStr<'a>>,
2996 alignments: Vec<Vec<Alignment>>,
2997 headings: Vec<HeadingAttributes<'a>>,
2998 #[cfg(feature = "mdx")]
2999 jsx_elements: Vec<JsxElementData<'a>>,
3000 directives: Vec<DirectiveAttrData<'a>>,
3001}
3002
3003#[derive(Clone)]
3005pub(crate) struct HeadingAttributes<'a> {
3006 pub id: Option<CowStr<'a>>,
3007 pub classes: Vec<CowStr<'a>>,
3008 pub attrs: Vec<(CowStr<'a>, Option<CowStr<'a>>)>,
3009}
3010
3011#[derive(Clone, Default, Debug)]
3013pub struct RefDefs<'input>(pub(crate) FxHashMap<LinkLabel<'input>, LinkDef<'input>>);
3014
3015#[derive(Clone, Default, Debug)]
3017pub struct FootnoteDefs<'input>(pub(crate) FxHashMap<FootnoteLabel<'input>, FootnoteDef>);
3018
3019impl<'input, 'b, 's> RefDefs<'input>
3020where
3021 's: 'b,
3022{
3023 pub fn get(&'s self, key: &'b str) -> Option<&'b LinkDef<'input>> {
3025 self.0.get(&UniCase::new(key.into()))
3026 }
3027
3028 pub fn iter(&'s self) -> impl Iterator<Item = (&'s str, &'s LinkDef<'input>)> {
3030 self.0.iter().map(|(k, v)| (k.as_ref(), v))
3031 }
3032}
3033
3034impl<'input, 'b, 's> FootnoteDefs<'input>
3035where
3036 's: 'b,
3037{
3038 pub fn contains(&'s self, key: &'b str) -> bool {
3040 self.0.contains_key(&UniCase::new(key.into()))
3041 }
3042 pub fn get_mut(&'s mut self, key: CowStr<'input>) -> Option<&'s mut FootnoteDef> {
3044 self.0.get_mut(&UniCase::new(key))
3045 }
3046}
3047
3048impl<'a> Allocations<'a> {
3049 pub fn new() -> Self {
3050 Self {
3051 refdefs: RefDefs::default(),
3052 refdefs_all: Vec::new(),
3053 footdefs: FootnoteDefs::default(),
3054 links: Vec::with_capacity(128),
3055 cows: Vec::new(),
3056 alignments: Vec::new(),
3057 headings: Vec::new(),
3058 #[cfg(feature = "mdx")]
3059 jsx_elements: Vec::new(),
3060 directives: Vec::new(),
3061 }
3062 }
3063
3064 pub fn allocate_cow(&mut self, cow: CowStr<'a>) -> CowIndex {
3065 let ix = self.cows.len();
3066 self.cows.push(cow);
3067 CowIndex(ix)
3068 }
3069
3070 pub fn allocate_link(
3071 &mut self,
3072 ty: LinkType,
3073 url: CowStr<'a>,
3074 title: CowStr<'a>,
3075 id: CowStr<'a>,
3076 ) -> LinkIndex {
3077 let ix = self.links.len();
3078 self.links.push((ty, url, title, id));
3079 LinkIndex(ix)
3080 }
3081
3082 pub fn allocate_alignment(&mut self, alignment: Vec<Alignment>) -> AlignmentIndex {
3083 let ix = self.alignments.len();
3084 self.alignments.push(alignment);
3085 AlignmentIndex(ix)
3086 }
3087
3088 pub fn allocate_heading(&mut self, attrs: HeadingAttributes<'a>) -> HeadingIndex {
3089 let ix = self.headings.len();
3090 self.headings.push(attrs);
3091 let ix_nonzero = NonZeroUsize::new(ix.wrapping_add(1)).expect("too many headings");
3094 HeadingIndex(ix_nonzero)
3095 }
3096
3097 pub fn take_cow(&mut self, ix: CowIndex) -> CowStr<'a> {
3098 core::mem::replace(&mut self.cows[ix.0], "".into())
3099 }
3100
3101 pub fn take_link(&mut self, ix: LinkIndex) -> (LinkType, CowStr<'a>, CowStr<'a>, CowStr<'a>) {
3102 let default_link = (LinkType::ShortcutUnknown, "".into(), "".into(), "".into());
3103 core::mem::replace(&mut self.links[ix.0], default_link)
3104 }
3105
3106 pub fn take_alignment(&mut self, ix: AlignmentIndex) -> Vec<Alignment> {
3107 core::mem::take(&mut self.alignments[ix.0])
3108 }
3109
3110 #[cfg(feature = "mdx")]
3111 pub fn allocate_jsx_element(&mut self, data: JsxElementData<'a>) -> JsxElementIndex {
3112 let ix = self.jsx_elements.len();
3113 self.jsx_elements.push(data);
3114 JsxElementIndex(ix)
3115 }
3116
3117 pub fn allocate_directive(&mut self, data: DirectiveAttrData<'a>) -> DirectiveIndex {
3118 let ix = self.directives.len();
3119 self.directives.push(data);
3120 DirectiveIndex(ix)
3121 }
3122
3123 pub fn take_directive(&mut self, ix: DirectiveIndex) -> DirectiveAttrData<'a> {
3124 core::mem::replace(
3125 &mut self.directives[ix.0],
3126 DirectiveAttrData {
3127 name: "".into(),
3128 attributes: Vec::new(),
3129 label_start: 0,
3130 label_end: 0,
3131 initial_size: 0,
3132 },
3133 )
3134 }
3135
3136 pub fn directive_ref(&self, ix: DirectiveIndex) -> &DirectiveAttrData<'a> {
3137 &self.directives[ix.0]
3138 }
3139
3140 #[cfg(feature = "mdx")]
3141 pub fn take_jsx_element(&mut self, ix: JsxElementIndex) -> JsxElementData<'a> {
3142 core::mem::replace(
3143 &mut self.jsx_elements[ix.0],
3144 JsxElementData {
3145 name: "".into(),
3146 attrs: Vec::new(),
3147 raw: "".into(),
3148 is_closing: false,
3149 is_self_closing: false,
3150 },
3151 )
3152 }
3153}
3154
3155impl<'a> Index<CowIndex> for Allocations<'a> {
3156 type Output = CowStr<'a>;
3157
3158 fn index(&self, ix: CowIndex) -> &Self::Output {
3159 self.cows.index(ix.0)
3160 }
3161}
3162
3163impl<'a> Index<LinkIndex> for Allocations<'a> {
3164 type Output = (LinkType, CowStr<'a>, CowStr<'a>, CowStr<'a>);
3165
3166 fn index(&self, ix: LinkIndex) -> &Self::Output {
3167 self.links.index(ix.0)
3168 }
3169}
3170
3171impl<'a> Index<AlignmentIndex> for Allocations<'a> {
3172 type Output = Vec<Alignment>;
3173
3174 fn index(&self, ix: AlignmentIndex) -> &Self::Output {
3175 self.alignments.index(ix.0)
3176 }
3177}
3178
3179impl<'a> Index<HeadingIndex> for Allocations<'a> {
3180 type Output = HeadingAttributes<'a>;
3181
3182 fn index(&self, ix: HeadingIndex) -> &Self::Output {
3183 self.headings.index(ix.0.get() - 1)
3184 }
3185}
3186
3187#[derive(Clone, Default)]
3193pub(crate) struct HtmlScanGuard {
3194 pub cdata: usize,
3195 pub processing: usize,
3196 pub declaration: usize,
3197 pub comment: usize,
3198}
3199
3200pub trait ParserCallbacks<'input> {
3204 fn handle_broken_link(
3212 &mut self,
3213 #[allow(unused_variables)] link: BrokenLink<'input>,
3214 ) -> Option<(CowStr<'input>, CowStr<'input>)> {
3215 None
3216 }
3217}
3218
3219#[allow(missing_debug_implementations)]
3223pub struct BrokenLinkCallback<F>(Option<F>);
3224
3225impl<'input, F> ParserCallbacks<'input> for BrokenLinkCallback<F>
3226where
3227 F: FnMut(BrokenLink<'input>) -> Option<(CowStr<'input>, CowStr<'input>)>,
3228{
3229 fn handle_broken_link(
3230 &mut self,
3231 link: BrokenLink<'input>,
3232 ) -> Option<(CowStr<'input>, CowStr<'input>)> {
3233 self.0.as_mut().and_then(|cb| cb(link))
3234 }
3235}
3236
3237impl<'input> ParserCallbacks<'input> for Box<dyn ParserCallbacks<'input>> {
3238 fn handle_broken_link(
3239 &mut self,
3240 link: BrokenLink<'input>,
3241 ) -> Option<(CowStr<'input>, CowStr<'input>)> {
3242 (**self).handle_broken_link(link)
3243 }
3244}
3245
3246#[allow(missing_debug_implementations)]
3250pub struct DefaultParserCallbacks;
3251
3252impl<'input> ParserCallbacks<'input> for DefaultParserCallbacks {}
3253
3254#[derive(Debug)]
3262pub struct OffsetIter<'a, CB> {
3263 parser: Parser<'a, CB>,
3264}
3265
3266impl<'a, CB: ParserCallbacks<'a>> OffsetIter<'a, CB> {
3267 pub fn reference_definitions(&self) -> &RefDefs<'_> {
3269 self.parser.reference_definitions()
3270 }
3271
3272 pub fn mdx_errors(&self) -> &[(usize, String)] {
3274 self.parser.mdx_errors()
3275 }
3276}
3277
3278impl<'a, CB: ParserCallbacks<'a>> Iterator for OffsetIter<'a, CB> {
3279 type Item = (Event<'a>, Range<usize>);
3280
3281 fn next(&mut self) -> Option<Self::Item> {
3282 self.parser
3283 .inner
3284 .next_event_range(&mut self.parser.callbacks)
3285 }
3286}
3287
3288impl<'a, CB: ParserCallbacks<'a>> Iterator for Parser<'a, CB> {
3289 type Item = Event<'a>;
3290
3291 fn next(&mut self) -> Option<Event<'a>> {
3292 self.inner
3293 .next_event_range(&mut self.callbacks)
3294 .map(|(event, _range)| event)
3295 }
3296}
3297
3298impl<'a, CB: ParserCallbacks<'a>> FusedIterator for Parser<'a, CB> {}
3299
3300impl<'input> ParserInner<'input> {
3301 fn next_event_range(
3302 &mut self,
3303 callbacks: &mut dyn ParserCallbacks<'input>,
3304 ) -> Option<(Event<'input>, Range<usize>)> {
3305 match self.tree.cur() {
3306 None => {
3307 let ix = self.tree.pop()?;
3308 let ix = if matches!(self.tree[ix].item.body, ItemBody::TightParagraph) {
3309 self.tree.next_sibling(ix);
3311 return self.next_event_range(callbacks);
3312 } else {
3313 ix
3314 };
3315 let tag_end = body_to_tag_end(&self.tree[ix].item.body);
3316 self.tree.next_sibling(ix);
3317 let span = self.tree[ix].item.start..self.tree[ix].item.end;
3318 debug_assert!(span.start <= span.end);
3319 Some((Event::End(tag_end), span))
3320 }
3321 Some(cur_ix) => {
3322 let cur_ix = if matches!(self.tree[cur_ix].item.body, ItemBody::TightParagraph) {
3323 self.tree.push();
3325 self.tree.cur().unwrap()
3326 } else {
3327 cur_ix
3328 };
3329 if self.tree[cur_ix].item.body.is_maybe_inline() {
3330 self.handle_inline(callbacks);
3331 }
3332
3333 let node = self.tree[cur_ix];
3334 let item = node.item;
3335 let event = item_to_event(item, self.text, &mut self.allocs);
3336 if let Event::Start(..) = event {
3337 self.tree.push();
3338 } else {
3339 self.tree.next_sibling(cur_ix);
3340 }
3341 debug_assert!(item.start <= item.end);
3342 Some((event, item.start..item.end))
3343 }
3344 }
3345 }
3346}
3347
3348fn body_to_tag_end(body: &ItemBody) -> TagEnd {
3349 match *body {
3350 ItemBody::Paragraph => TagEnd::Paragraph,
3351 ItemBody::Emphasis => TagEnd::Emphasis,
3352 ItemBody::Superscript => TagEnd::Superscript,
3353 ItemBody::Subscript => TagEnd::Subscript,
3354 ItemBody::Strong => TagEnd::Strong,
3355 ItemBody::Strikethrough => TagEnd::Strikethrough,
3356 ItemBody::Link(..) => TagEnd::Link,
3357 ItemBody::Image(..) => TagEnd::Image,
3358 ItemBody::Heading(level, _) => TagEnd::Heading(level),
3359 ItemBody::IndentCodeBlock(..) | ItemBody::FencedCodeBlock(..) | ItemBody::MathBlock(..) => {
3360 TagEnd::CodeBlock
3361 }
3362 ItemBody::ContainerDirective(..) => TagEnd::Directive(DirectiveKind::Container),
3363 ItemBody::LeafDirective(..) => TagEnd::Directive(DirectiveKind::Leaf),
3364 ItemBody::TextDirective(..) => TagEnd::Directive(DirectiveKind::Text),
3365 ItemBody::BlockQuote(kind) => TagEnd::BlockQuote(kind),
3366 ItemBody::HtmlBlock(_) => TagEnd::HtmlBlock,
3367 ItemBody::List(_, c, _) => {
3368 let is_ordered = c == b'.' || c == b')';
3369 TagEnd::List(is_ordered)
3370 }
3371 ItemBody::ListItem(_, _) => TagEnd::Item,
3372 ItemBody::TableHead => TagEnd::TableHead,
3373 ItemBody::TableCell => TagEnd::TableCell,
3374 ItemBody::TableRow => TagEnd::TableRow,
3375 ItemBody::Table(..) => TagEnd::Table,
3376 ItemBody::FootnoteDefinition(..) => TagEnd::FootnoteDefinition,
3377 ItemBody::MetadataBlock(kind) => TagEnd::MetadataBlock(kind),
3378 ItemBody::DefinitionList(_) => TagEnd::DefinitionList,
3379 ItemBody::DefinitionListTitle => TagEnd::DefinitionListTitle,
3380 ItemBody::DefinitionListDefinition(_) => TagEnd::DefinitionListDefinition,
3381 #[cfg(feature = "mdx")]
3382 ItemBody::MdxJsxFlowElement(..) => TagEnd::MdxJsxFlowElement,
3383 #[cfg(feature = "mdx")]
3384 ItemBody::MdxJsxTextElement(..) => TagEnd::MdxJsxTextElement,
3385 _ => panic!("unexpected item body {:?}", body),
3386 }
3387}
3388
3389fn item_to_event<'a>(item: Item, text: &'a str, allocs: &mut Allocations<'a>) -> Event<'a> {
3390 let tag = match item.body {
3391 ItemBody::Text { .. } => return Event::Text(text[item.start..item.end].into()),
3392 ItemBody::Code(cow_ix) => return Event::Code(allocs.take_cow(cow_ix)),
3393 ItemBody::SynthesizeText(cow_ix) => return Event::Text(allocs.take_cow(cow_ix)),
3394 ItemBody::SynthesizeChar(c) => return Event::Text(c.into()),
3395 ItemBody::HtmlBlock(_) => Tag::HtmlBlock,
3396 ItemBody::Html => return Event::Html(text[item.start..item.end].into()),
3397 ItemBody::InlineHtml => return Event::InlineHtml(text[item.start..item.end].into()),
3398 ItemBody::OwnedInlineHtml(cow_ix) => return Event::InlineHtml(allocs.take_cow(cow_ix)),
3399 ItemBody::SoftBreak => return Event::SoftBreak,
3400 ItemBody::HardBreak(_) => return Event::HardBreak,
3401 ItemBody::FootnoteReference(cow_ix) => {
3402 return Event::FootnoteReference(allocs.take_cow(cow_ix))
3403 }
3404 ItemBody::TaskListMarker(checked) => return Event::TaskListMarker(checked),
3405 ItemBody::Rule => return Event::Rule,
3406 ItemBody::Paragraph => Tag::Paragraph,
3407 ItemBody::Emphasis => Tag::Emphasis,
3408 ItemBody::Superscript => Tag::Superscript,
3409 ItemBody::Subscript => Tag::Subscript,
3410 ItemBody::Strong => Tag::Strong,
3411 ItemBody::Strikethrough => Tag::Strikethrough,
3412 ItemBody::Link(link_ix) => {
3413 let (link_type, dest_url, title, id) = allocs.take_link(link_ix);
3414 Tag::Link {
3415 link_type,
3416 dest_url,
3417 title,
3418 id,
3419 }
3420 }
3421 ItemBody::Image(link_ix) => {
3422 let (link_type, dest_url, title, id) = allocs.take_link(link_ix);
3423 Tag::Image {
3424 link_type,
3425 dest_url,
3426 title,
3427 id,
3428 }
3429 }
3430 ItemBody::Heading(level, Some(heading_ix)) => {
3431 let HeadingAttributes { id, classes, attrs } = allocs.index(heading_ix);
3432 Tag::Heading {
3433 level,
3434 id: id.clone(),
3435 classes: classes.clone(),
3436 attrs: attrs.clone(),
3437 }
3438 }
3439 ItemBody::Heading(level, None) => Tag::Heading {
3440 level,
3441 id: None,
3442 classes: Vec::new(),
3443 attrs: Vec::new(),
3444 },
3445 ItemBody::MathBlock(cow_ix) => {
3446 Tag::CodeBlock(CodeBlockKind::Fenced(allocs.take_cow(cow_ix)))
3447 }
3448 ItemBody::FencedCodeBlock(cow_ix) => {
3449 Tag::CodeBlock(CodeBlockKind::Fenced(allocs.take_cow(cow_ix)))
3450 }
3451 ItemBody::IndentCodeBlock(..) => Tag::CodeBlock(CodeBlockKind::Indented),
3452 ItemBody::ContainerDirective(_, dir_ix)
3453 | ItemBody::LeafDirective(dir_ix)
3454 | ItemBody::TextDirective(dir_ix) => {
3455 let kind = match item.body {
3456 ItemBody::ContainerDirective(..) => DirectiveKind::Container,
3457 ItemBody::LeafDirective(..) => DirectiveKind::Leaf,
3458 _ => DirectiveKind::Text,
3459 };
3460 let dir = allocs.take_directive(dir_ix);
3461 Tag::Directive {
3462 kind,
3463 name: dir.name,
3464 attributes: dir.attributes,
3465 }
3466 }
3467 ItemBody::BlockQuote(kind) => Tag::BlockQuote(kind),
3468 ItemBody::List(is_tight, c, listitem_start) => {
3469 if c == b'.' || c == b')' {
3470 Tag::List(Some(listitem_start), is_tight)
3471 } else {
3472 Tag::List(None, is_tight)
3473 }
3474 }
3475 ItemBody::ListItem(_, _) => Tag::Item,
3476 ItemBody::TableHead => Tag::TableHead,
3477 ItemBody::TableCell => Tag::TableCell,
3478 ItemBody::TableRow => Tag::TableRow,
3479 ItemBody::Table(alignment_ix) => Tag::Table(allocs.take_alignment(alignment_ix)),
3480 ItemBody::FootnoteDefinition(cow_ix) => Tag::FootnoteDefinition(allocs.take_cow(cow_ix)),
3481 ItemBody::MetadataBlock(kind) => Tag::MetadataBlock(kind),
3482 ItemBody::Math(cow_ix, is_display) => {
3483 return if is_display {
3484 Event::DisplayMath(allocs.take_cow(cow_ix))
3485 } else {
3486 Event::InlineMath(allocs.take_cow(cow_ix))
3487 }
3488 }
3489 ItemBody::DefinitionList(_) => Tag::DefinitionList,
3490 ItemBody::DefinitionListTitle => Tag::DefinitionListTitle,
3491 ItemBody::DefinitionListDefinition(_) => Tag::DefinitionListDefinition,
3492 #[cfg(feature = "mdx")]
3493 ItemBody::MdxJsxFlowElement(jsx_ix) => {
3494 let jsx = allocs.take_jsx_element(jsx_ix);
3495 Tag::MdxJsxFlowElement(jsx.raw)
3496 }
3497 #[cfg(feature = "mdx")]
3498 ItemBody::MdxJsxTextElement(jsx_ix) => {
3499 let jsx = allocs.take_jsx_element(jsx_ix);
3500 Tag::MdxJsxTextElement(jsx.raw)
3501 }
3502 #[cfg(feature = "mdx")]
3503 ItemBody::MdxFlowExpression(cow_ix) => {
3504 return Event::MdxFlowExpression(allocs.take_cow(cow_ix))
3505 }
3506 #[cfg(feature = "mdx")]
3507 ItemBody::MdxTextExpression(cow_ix) => {
3508 return Event::MdxTextExpression(allocs.take_cow(cow_ix))
3509 }
3510 #[cfg(feature = "mdx")]
3511 ItemBody::MdxEsm(cow_ix) => return Event::MdxEsm(allocs.take_cow(cow_ix)),
3512 _ => panic!("unexpected item body {:?}", item.body),
3513 };
3514
3515 Event::Start(tag)
3516}
3517
3518#[cfg(test)]
3519mod test {
3520 use alloc::{borrow::ToOwned, string::ToString, vec::Vec};
3521
3522 use super::*;
3523 use crate::tree::Node;
3524
3525 fn parser_with_extensions(text: &str) -> Parser<'_> {
3528 let mut opts = Options::empty();
3529 opts.insert(Options::ENABLE_TABLES);
3530 opts.insert(Options::ENABLE_FOOTNOTES);
3531 opts.insert(Options::ENABLE_STRIKETHROUGH);
3532 opts.insert(Options::ENABLE_SUPERSCRIPT);
3533 opts.insert(Options::ENABLE_SUBSCRIPT);
3534 opts.insert(Options::ENABLE_TASKLISTS);
3535
3536 Parser::new_ext(text, opts)
3537 }
3538
3539 #[test]
3540 #[cfg(target_pointer_width = "64")]
3541 fn node_size() {
3542 let node_size = core::mem::size_of::<Node<Item>>();
3543 assert_eq!(48, node_size);
3544 }
3545
3546 #[test]
3547 #[cfg(target_pointer_width = "64")]
3548 fn body_size() {
3549 let body_size = core::mem::size_of::<ItemBody>();
3550 assert_eq!(16, body_size);
3551 }
3552
3553 #[test]
3554 fn single_open_fish_bracket() {
3555 assert_eq!(3, Parser::new("<").count());
3557 }
3558
3559 #[test]
3560 fn lone_hashtag() {
3561 assert_eq!(2, Parser::new("#").count());
3563 }
3564
3565 #[test]
3566 fn lots_of_backslashes() {
3567 Parser::new("\\\\\r\r").count();
3569 Parser::new("\\\r\r\\.\\\\\r\r\\.\\").count();
3570 }
3571
3572 #[test]
3573 fn issue_1030() {
3574 let mut opts = Options::empty();
3575 opts.insert(Options::ENABLE_WIKILINKS);
3576
3577 let parser = Parser::new_ext("For a new ferrari, [[Wikientry|click here]]!", opts);
3578
3579 let offsets = parser
3580 .into_offset_iter()
3581 .map(|(_ev, range)| range)
3582 .collect::<Vec<_>>();
3583 let expected_offsets = vec![
3584 (0..44), (0..19), (19..43), (31..41), (19..43), (43..44), (0..44), ];
3592 assert_eq!(offsets, expected_offsets);
3593 }
3594
3595 #[test]
3596 fn issue_320() {
3597 parser_with_extensions(":\r\t> |\r:\r\t> |\r").count();
3599 }
3600
3601 #[test]
3602 fn issue_319() {
3603 parser_with_extensions("|\r-]([^|\r-]([^").count();
3605 parser_with_extensions("|\r\r=][^|\r\r=][^car").count();
3606 }
3607
3608 #[test]
3609 fn issue_303() {
3610 parser_with_extensions("[^\r\ra]").count();
3612 parser_with_extensions("\r\r]Z[^\x00\r\r]Z[^\x00").count();
3613 }
3614
3615 #[test]
3616 fn issue_313() {
3617 parser_with_extensions("*]0[^\r\r*]0[^").count();
3619 parser_with_extensions("[^\r> `][^\r> `][^\r> `][").count();
3620 }
3621
3622 #[test]
3623 fn issue_311() {
3624 parser_with_extensions("\\\u{0d}-\u{09}\\\u{0d}-\u{09}").count();
3626 }
3627
3628 #[test]
3629 fn issue_283() {
3630 let input = core::str::from_utf8(b"\xf0\x9b\xb2\x9f<td:^\xf0\x9b\xb2\x9f").unwrap();
3631 parser_with_extensions(input).count();
3633 }
3634
3635 #[test]
3636 fn issue_289() {
3637 parser_with_extensions("> - \\\n> - ").count();
3639 parser_with_extensions("- \n\n").count();
3640 }
3641
3642 #[test]
3643 fn issue_306() {
3644 parser_with_extensions("*\r_<__*\r_<__*\r_<__*\r_<__").count();
3646 }
3647
3648 #[test]
3649 fn issue_305() {
3650 parser_with_extensions("_6**6*_*").count();
3652 }
3653
3654 #[test]
3655 fn another_emphasis_panic() {
3656 parser_with_extensions("*__#_#__*").count();
3657 }
3658
3659 #[test]
3660 fn offset_iter() {
3661 let event_offsets: Vec<_> = Parser::new("*hello* world")
3662 .into_offset_iter()
3663 .map(|(_ev, range)| range)
3664 .collect();
3665 let expected_offsets = vec![(0..13), (0..7), (1..6), (0..7), (7..13), (0..13)];
3666 assert_eq!(expected_offsets, event_offsets);
3667 }
3668
3669 #[test]
3670 fn reference_link_offsets() {
3671 let range =
3672 Parser::new("# H1\n[testing][Some reference]\n\n[Some reference]: https://github.com")
3673 .into_offset_iter()
3674 .filter_map(|(ev, range)| match ev {
3675 Event::Start(
3676 Tag::Link {
3677 link_type: LinkType::Reference,
3678 ..
3679 },
3680 ..,
3681 ) => Some(range),
3682 _ => None,
3683 })
3684 .next()
3685 .unwrap();
3686 assert_eq!(5..30, range);
3687 }
3688
3689 #[test]
3690 fn footnote_offsets() {
3691 let range = parser_with_extensions("Testing this[^1] out.\n\n[^1]: Footnote.")
3692 .into_offset_iter()
3693 .filter_map(|(ev, range)| match ev {
3694 Event::FootnoteReference(..) => Some(range),
3695 _ => None,
3696 })
3697 .next()
3698 .unwrap();
3699 assert_eq!(12..16, range);
3700 }
3701
3702 #[test]
3703 fn footnote_offsets_exclamation() {
3704 let mut immediately_before_footnote = None;
3705 let range = parser_with_extensions("Testing this![^1] out.\n\n[^1]: Footnote.")
3706 .into_offset_iter()
3707 .filter_map(|(ev, range)| match ev {
3708 Event::FootnoteReference(..) => Some(range),
3709 _ => {
3710 immediately_before_footnote = Some((ev, range));
3711 None
3712 }
3713 })
3714 .next()
3715 .unwrap();
3716 assert_eq!(13..17, range);
3717 if let (Event::Text(exclamation), range_exclamation) =
3718 immediately_before_footnote.as_ref().unwrap()
3719 {
3720 assert_eq!("!", &exclamation[..]);
3721 assert_eq!(&(12..13), range_exclamation);
3722 } else {
3723 panic!("what came first, then? {immediately_before_footnote:?}");
3724 }
3725 }
3726
3727 #[test]
3728 fn table_offset() {
3729 let markdown = "a\n\nTesting|This|Outtt\n--|:--:|--:\nSome Data|Other data|asdf";
3730 let event_offset = parser_with_extensions(markdown)
3731 .into_offset_iter()
3732 .map(|(_ev, range)| range)
3733 .nth(3)
3734 .unwrap();
3735 let expected_offset = 3..59;
3736 assert_eq!(expected_offset, event_offset);
3737 }
3738
3739 #[test]
3740 fn table_cell_span() {
3741 let markdown = "a|b|c\n--|--|--\na| |c";
3742 let event_offset = parser_with_extensions(markdown)
3743 .into_offset_iter()
3744 .filter_map(|(ev, span)| match ev {
3745 Event::Start(Tag::TableCell) => Some(span),
3746 _ => None,
3747 })
3748 .nth(4)
3749 .unwrap();
3750 let expected_offset_start = "a|b|c\n--|--|--\na".len();
3752 assert_eq!(
3753 expected_offset_start..(expected_offset_start + 3),
3754 event_offset
3755 );
3756 }
3757
3758 #[test]
3759 fn offset_iter_issue_378() {
3760 let event_offsets: Vec<_> = Parser::new("a [b](c) d")
3761 .into_offset_iter()
3762 .map(|(_ev, range)| range)
3763 .collect();
3764 let expected_offsets = vec![(0..10), (0..2), (2..8), (3..4), (2..8), (8..10), (0..10)];
3765 assert_eq!(expected_offsets, event_offsets);
3766 }
3767
3768 #[test]
3769 fn offset_iter_issue_404() {
3770 let event_offsets: Vec<_> = Parser::new("###\n")
3771 .into_offset_iter()
3772 .map(|(_ev, range)| range)
3773 .collect();
3774 let expected_offsets = vec![(0..4), (0..4)];
3775 assert_eq!(expected_offsets, event_offsets);
3776 }
3777
3778 #[test]
3779 fn broken_links_called_only_once() {
3780 for &(markdown, expected) in &[
3781 ("See also [`g()`][crate::g].", 1),
3782 ("See also [`g()`][crate::g][].", 1),
3783 ("[brokenlink1] some other node [brokenlink2]", 2),
3784 ] {
3785 let mut times_called = 0;
3786 let callback = &mut |_broken_link: BrokenLink| {
3787 times_called += 1;
3788 None
3789 };
3790 let parser =
3791 Parser::new_with_broken_link_callback(markdown, Options::empty(), Some(callback));
3792 for _ in parser {}
3793 assert_eq!(times_called, expected);
3794 }
3795 }
3796
3797 #[test]
3798 fn simple_broken_link_callback() {
3799 let test_str = "This is a link w/o def: [hello][world]";
3800 let mut callback = |broken_link: BrokenLink| {
3801 assert_eq!("world", broken_link.reference.as_ref());
3802 assert_eq!(&test_str[broken_link.span], "[hello][world]");
3803 let url = "YOLO".into();
3804 let title = "SWAG".to_owned().into();
3805 Some((url, title))
3806 };
3807 let parser =
3808 Parser::new_with_broken_link_callback(test_str, Options::empty(), Some(&mut callback));
3809 let mut link_tag_count = 0;
3810 for (typ, url, title, id) in parser.filter_map(|event| match event {
3811 Event::Start(Tag::Link {
3812 link_type,
3813 dest_url,
3814 title,
3815 id,
3816 }) => Some((link_type, dest_url, title, id)),
3817 _ => None,
3818 }) {
3819 link_tag_count += 1;
3820 assert_eq!(typ, LinkType::ReferenceUnknown);
3821 assert_eq!(url.as_ref(), "YOLO");
3822 assert_eq!(title.as_ref(), "SWAG");
3823 assert_eq!(id.as_ref(), "world");
3824 }
3825 assert!(link_tag_count > 0);
3826 }
3827
3828 #[test]
3829 fn code_block_kind_check_fenced() {
3830 let parser = Parser::new("hello\n```test\ntadam\n```");
3831 let mut found = 0;
3832 for (ev, _range) in parser.into_offset_iter() {
3833 if let Event::Start(Tag::CodeBlock(CodeBlockKind::Fenced(syntax))) = ev {
3834 assert_eq!(syntax.as_ref(), "test");
3835 found += 1;
3836 }
3837 }
3838 assert_eq!(found, 1);
3839 }
3840
3841 #[test]
3842 fn code_block_kind_check_indented() {
3843 let parser = Parser::new("hello\n\n ```test\n tadam\nhello");
3844 let mut found = 0;
3845 for (ev, _range) in parser.into_offset_iter() {
3846 if let Event::Start(Tag::CodeBlock(CodeBlockKind::Indented)) = ev {
3847 found += 1;
3848 }
3849 }
3850 assert_eq!(found, 1);
3851 }
3852
3853 #[test]
3854 fn ref_defs() {
3855 let input = r###"[a B c]: http://example.com
3856[another]: https://google.com
3857
3858text
3859
3860[final ONE]: http://wikipedia.org
3861"###;
3862 let mut parser = Parser::new(input);
3863
3864 assert!(parser.reference_definitions().get("a b c").is_some());
3865 assert!(parser.reference_definitions().get("nope").is_none());
3866
3867 if let Some(_event) = parser.next() {
3868 let s = "final one".to_owned();
3870 let link_def = parser.reference_definitions().get(&s).unwrap();
3871 let span = &input[link_def.span.clone()];
3872 assert_eq!(span, "[final ONE]: http://wikipedia.org");
3873 }
3874 }
3875
3876 #[test]
3877 #[allow(clippy::extra_unused_lifetimes)]
3878 fn common_lifetime_patterns_allowed<'b>() {
3879 let temporary_str = String::from("xyz");
3880
3881 let mut closure = |link: BrokenLink<'b>| Some(("#".into(), link.reference));
3885
3886 fn function(link: BrokenLink<'_>) -> Option<(CowStr<'_>, CowStr<'_>)> {
3887 Some(("#".into(), link.reference))
3888 }
3889
3890 for _ in Parser::new_with_broken_link_callback(
3891 "static lifetime",
3892 Options::empty(),
3893 Some(&mut closure),
3894 ) {}
3895 for _ in Parser::new_with_broken_link_callback(
3904 "static lifetime",
3905 Options::empty(),
3906 Some(&mut function),
3907 ) {}
3908 for _ in Parser::new_with_broken_link_callback(
3909 &temporary_str,
3910 Options::empty(),
3911 Some(&mut function),
3912 ) {}
3913 }
3914
3915 #[test]
3916 fn inline_html_inside_blockquote() {
3917 let input = "> <foo\n> bar>";
3919 let events: Vec<_> = Parser::new(input).collect();
3920 let expected = [
3921 Event::Start(Tag::BlockQuote(None)),
3922 Event::Start(Tag::Paragraph),
3923 Event::InlineHtml(CowStr::Boxed("<foo\nbar>".to_string().into())),
3924 Event::End(TagEnd::Paragraph),
3925 Event::End(TagEnd::BlockQuote(None)),
3926 ];
3927 assert_eq!(&events, &expected);
3928 }
3929
3930 #[test]
3931 fn wikilink_has_pothole() {
3932 let input = "[[foo]] [[bar|baz]]";
3933 let events: Vec<_> = Parser::new_ext(input, Options::ENABLE_WIKILINKS).collect();
3934 let expected = [
3935 Event::Start(Tag::Paragraph),
3936 Event::Start(Tag::Link {
3937 link_type: LinkType::WikiLink { has_pothole: false },
3938 dest_url: CowStr::Borrowed("foo"),
3939 title: CowStr::Borrowed(""),
3940 id: CowStr::Borrowed(""),
3941 }),
3942 Event::Text(CowStr::Borrowed("foo")),
3943 Event::End(TagEnd::Link),
3944 Event::Text(CowStr::Borrowed(" ")),
3945 Event::Start(Tag::Link {
3946 link_type: LinkType::WikiLink { has_pothole: true },
3947 dest_url: CowStr::Borrowed("bar"),
3948 title: CowStr::Borrowed(""),
3949 id: CowStr::Borrowed(""),
3950 }),
3951 Event::Text(CowStr::Borrowed("baz")),
3952 Event::End(TagEnd::Link),
3953 Event::End(TagEnd::Paragraph),
3954 ];
3955 assert_eq!(&events, &expected);
3956 }
3957
3958 #[cfg(feature = "mdx")]
3959 fn mdx_parser(text: &str) -> Parser<'_> {
3960 Parser::new_ext(text, Options::ENABLE_MDX)
3961 }
3962
3963 #[cfg(feature = "mdx")]
3964 #[test]
3965 fn mdx_esm_import() {
3966 let events: Vec<_> = mdx_parser("import {Chart} from './chart.js'\n").collect();
3967 assert_eq!(events.len(), 1);
3968 assert!(matches!(&events[0], Event::MdxEsm(s) if s.contains("import")));
3969 }
3970
3971 #[cfg(feature = "mdx")]
3972 #[test]
3973 fn mdx_esm_export() {
3974 let events: Vec<_> = mdx_parser("export const meta = {}\n").collect();
3975 assert_eq!(events.len(), 1);
3976 assert!(matches!(&events[0], Event::MdxEsm(s) if s.contains("export")));
3977 }
3978
3979 #[cfg(feature = "mdx")]
3980 #[test]
3981 fn mdx_flow_expression() {
3982 let events: Vec<_> = mdx_parser("{1 + 1}\n").collect();
3983 assert_eq!(events.len(), 1);
3984 assert!(matches!(&events[0], Event::MdxFlowExpression(s) if s.as_ref() == "1 + 1"));
3985 }
3986
3987 #[cfg(feature = "mdx")]
3988 #[test]
3989 fn mdx_jsx_flow_self_closing() {
3990 let events: Vec<_> = mdx_parser("<Chart values={[1,2,3]} />\n").collect();
3991 assert!(!events.is_empty());
3992 assert!(
3993 matches!(&events[0], Event::Start(Tag::MdxJsxFlowElement(s)) if s.contains("Chart"))
3994 );
3995 }
3996
3997 #[cfg(feature = "mdx")]
3998 #[test]
3999 fn mdx_jsx_flow_fragment() {
4000 let events: Vec<_> = mdx_parser("<>\n").collect();
4001 assert!(!events.is_empty());
4002 assert!(matches!(
4003 &events[0],
4004 Event::Start(Tag::MdxJsxFlowElement(_))
4005 ));
4006 }
4007
4008 #[cfg(feature = "mdx")]
4009 #[test]
4010 fn mdx_inline_expression() {
4011 let events: Vec<_> = mdx_parser("hello {name} world\n").collect();
4012 let has_expr = events
4013 .iter()
4014 .any(|e| matches!(e, Event::MdxTextExpression(s) if s.as_ref() == "name"));
4015 assert!(
4016 has_expr,
4017 "Expected inline MDX expression, got: {:?}",
4018 events
4019 );
4020 }
4021
4022 #[cfg(feature = "mdx")]
4023 #[test]
4024 fn mdx_inline_jsx() {
4025 let events: Vec<_> = mdx_parser("hello <Badge /> world\n").collect();
4026 let has_jsx = events
4027 .iter()
4028 .any(|e| matches!(e, Event::Start(Tag::MdxJsxTextElement(s)) if s.contains("Badge")));
4029 assert!(has_jsx, "Expected inline MDX JSX, got: {:?}", events);
4030 }
4031
4032 #[cfg(feature = "mdx")]
4033 #[test]
4034 fn mdx_all_tags_are_jsx() {
4035 let events: Vec<_> = mdx_parser("hello <em>world</em>\n").collect();
4037 let has_jsx = events
4038 .iter()
4039 .any(|e| matches!(e, Event::Start(Tag::MdxJsxTextElement(_))));
4040 assert!(has_jsx, "In MDX mode, <em> should be JSX: {:?}", events);
4041 }
4042
4043 #[test]
4044 fn mdx_does_not_interfere_without_flag() {
4045 let events: Vec<_> = Parser::new("import foo from 'bar'\n").collect();
4047 assert!(events
4049 .iter()
4050 .any(|e| matches!(e, Event::Start(Tag::Paragraph))));
4051 }
4052
4053 #[cfg(feature = "mdx")]
4054 #[test]
4055 fn mdx_expression_in_heading() {
4056 let events: Vec<_> = mdx_parser("# {title}\n").collect();
4057 let has_heading = events
4058 .iter()
4059 .any(|e| matches!(e, Event::Start(Tag::Heading { .. })));
4060 assert!(has_heading, "Should have a heading");
4061 let has_expr = events
4062 .iter()
4063 .any(|e| matches!(e, Event::MdxTextExpression(s) if s.as_ref() == "title"));
4064 assert!(
4065 has_expr,
4066 "Heading should contain MdxTextExpression, got: {:?}",
4067 events
4068 );
4069 }
4070
4071 #[cfg(feature = "mdx")]
4072 #[test]
4073 fn mdx_expression_mixed_text_in_heading() {
4074 let events: Vec<_> = mdx_parser("## Hello {name}\n").collect();
4075 let has_text = events
4076 .iter()
4077 .any(|e| matches!(e, Event::Text(s) if s.contains("Hello")));
4078 let has_expr = events
4079 .iter()
4080 .any(|e| matches!(e, Event::MdxTextExpression(s) if s.as_ref() == "name"));
4081 assert!(has_text, "Should have text, got: {:?}", events);
4082 assert!(has_expr, "Should have expression, got: {:?}", events);
4083 }
4084}