1use alloc::{borrow::ToOwned, boxed::Box, collections::VecDeque, string::String, vec::Vec};
24use core::{
25 cell::Cell,
26 cmp::{max, min},
27 iter::FusedIterator,
28 num::NonZeroUsize,
29 ops::{Index, Range},
30};
31use rustc_hash::FxHashMap;
32use unicase::UniCase;
33
34#[cfg(feature = "mdx")]
35use crate::mdx::*;
36use crate::{
37 Alignment, BlockQuoteKind, CodeBlockKind, DirectiveKind, Event, HeadingLevel, LinkType,
38 MetadataBlockKind, Options, Tag, TagEnd,
39 firstpass::run_first_pass,
40 linklabel::{FootnoteLabel, LinkLabel, ReferenceLabel, scan_link_label_rest},
41 scanners::*,
42 strings::CowStr,
43 tree::{Tree, TreeIndex},
44};
45
46pub(crate) const LINK_MAX_NESTED_PARENS: usize = 32;
52
53#[derive(Debug, Default, Clone, Copy)]
54pub(crate) struct Item {
55 pub start: usize,
56 pub end: usize,
57 pub body: ItemBody,
58}
59
60#[derive(Debug, PartialEq, Clone, Copy, Default)]
61pub(crate) enum ItemBody {
62 MaybeEmphasis(usize, bool, bool),
66 MaybeEmphasisEscaped(usize, bool, bool),
71 MaybeMath(bool, u8),
73 MaybeSmartQuote(u8, bool, bool),
75 MaybeCode(usize, bool), MaybeHtml(bool), MaybeLinkOpen,
78 MaybeLinkClose(bool),
80 MaybeImage,
81 MaybeAutolink(AutolinkCandidateIndex),
85
86 Emphasis,
88 Strong,
89 Strikethrough,
90 Superscript,
91 Subscript,
92 Math(CowIndex, bool), Code(CowIndex),
94 Link(LinkIndex),
95 Image(LinkIndex),
96 FootnoteReference(CowIndex),
97 TaskListMarker(bool), InlineHtml,
101 OwnedInlineHtml(CowIndex),
102 SynthesizeText(CowIndex),
103 SynthesizeChar(char),
104 Html,
105 Text {
106 backslash_escaped: bool,
107 },
108 SoftBreak,
109 HardBreak(bool),
111
112 #[default]
114 Root,
115
116 Paragraph,
118 TightParagraph,
119 Rule,
120 Heading(HeadingLevel, Option<HeadingIndex>), FencedCodeBlock(CowIndex, u32),
125 MathBlock(CowIndex), IndentCodeBlock(bool),
130 HtmlBlock(bool), BlockQuote(Option<BlockQuoteKind>),
134 ContainerDirective(u8, DirectiveIndex), LeafDirective(DirectiveIndex),
136 TextDirective(DirectiveIndex),
137 DirectiveLabel,
141 List(bool, u8, u64), ListItem(usize, bool), FootnoteDefinition(CowIndex),
144 MetadataBlock(MetadataBlockKind),
145
146 DefinitionList(bool), MaybeDefinitionListTitle,
151 DefinitionListTitle,
152 DefinitionListDefinition(usize, bool), Table(AlignmentIndex),
156 TableHead,
157 TableRow,
158 TableCell,
159
160 #[cfg(feature = "mdx")]
162 MdxJsxFlowElement(JsxElementIndex),
163 #[cfg(feature = "mdx")]
164 MdxJsxTextElement(JsxElementIndex),
165 #[cfg(feature = "mdx")]
166 MdxFlowExpression(CowIndex),
167 #[cfg(feature = "mdx")]
168 MdxTextExpression(CowIndex),
169 #[cfg(feature = "mdx")]
170 MdxEsm(CowIndex),
171}
172
173impl ItemBody {
174 pub(crate) fn is_maybe_inline(&self) -> bool {
175 use ItemBody::*;
176 matches!(
177 *self,
178 MaybeEmphasis(..)
179 | MaybeEmphasisEscaped(..)
180 | MaybeMath(..)
181 | MaybeSmartQuote(..)
182 | MaybeCode(..)
183 | MaybeHtml(..)
184 | MaybeLinkOpen
185 | MaybeLinkClose(..)
186 | MaybeImage
187 | MaybeAutolink(..)
188 )
189 }
190 pub(crate) fn is_block_level(&self) -> bool {
191 !self.is_inline() && !matches!(self, ItemBody::Root)
192 }
193 fn is_inline(&self) -> bool {
194 use ItemBody::*;
195 matches!(
196 *self,
197 MaybeEmphasis(..)
198 | MaybeEmphasisEscaped(..)
199 | MaybeMath(..)
200 | MaybeSmartQuote(..)
201 | MaybeCode(..)
202 | MaybeHtml(..)
203 | MaybeLinkOpen
204 | MaybeLinkClose(..)
205 | MaybeImage
206 | MaybeAutolink(..)
207 | Emphasis
208 | Strong
209 | Strikethrough
210 | Math(..)
211 | Code(..)
212 | Link(..)
213 | Image(..)
214 | FootnoteReference(..)
215 | TaskListMarker(..)
216 | InlineHtml
217 | OwnedInlineHtml(..)
218 | SynthesizeText(..)
219 | SynthesizeChar(..)
220 | Html
221 | Text { .. }
222 | SoftBreak
223 | HardBreak(..)
224 )
225 }
226}
227
228#[derive(Debug)]
229pub struct BrokenLink<'a> {
230 pub span: core::ops::Range<usize>,
231 pub link_type: LinkType,
232 pub reference: CowStr<'a>,
233}
234
235pub struct Parser<'input, CB = DefaultParserCallbacks> {
237 callbacks: CB,
238 inner: ParserInner<'input>,
239}
240
241pub(crate) struct ParserInner<'input> {
244 pub(crate) text: &'input str,
245 pub(crate) options: Options,
246 pub(crate) tree: Tree<Item>,
247 pub(crate) allocs: Allocations<'input>,
248 html_scan_guard: HtmlScanGuard,
249
250 link_ref_expansion_limit: usize,
267
268 unclosed_paren_title_floor: Cell<usize>,
270
271 pub(crate) mdx_errors: Vec<(usize, String)>,
273
274 inline_stack: InlineStack,
276 link_stack: LinkStack,
277 wikilink_stack: LinkStack,
278 code_delims: CodeDelims,
279 math_delims: MathDelims,
280}
281
282impl<'input, CB> core::fmt::Debug for Parser<'input, CB> {
283 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
284 f.debug_struct("Parser")
286 .field("text", &self.inner.text)
287 .field("options", &self.inner.options)
288 .field("callbacks", &..)
289 .finish()
290 }
291}
292
293impl<'a> BrokenLink<'a> {
294 pub fn into_static(self) -> BrokenLink<'static> {
298 BrokenLink {
299 span: self.span.clone(),
300 link_type: self.link_type,
301 reference: self.reference.into_string().into(),
302 }
303 }
304}
305
306impl<'input> Parser<'input, DefaultParserCallbacks> {
307 pub fn new(text: &'input str) -> Self {
309 Self::new_ext(text, Options::empty())
310 }
311
312 pub fn new_ext(text: &'input str, options: Options) -> Self {
314 Self::new_with_callbacks(text, options, DefaultParserCallbacks)
315 }
316}
317
318impl<'input, CB: ParserCallbacks<'input>> Parser<'input, CB> {
319 pub fn new_with_callbacks(text: &'input str, options: Options, callbacks: CB) -> Self {
344 let text = crate::strip_leading_bom(text);
345 let (mut tree, allocs, _firstpass_mdx_errors) = run_first_pass(text, options);
346 tree.reset();
347 let inline_stack = Default::default();
348 let link_stack = Default::default();
349 let wikilink_stack = Default::default();
350 let html_scan_guard = Default::default();
351 Parser {
352 callbacks,
353
354 inner: ParserInner {
355 text,
356 options,
357 tree,
358 allocs,
359 inline_stack,
360 link_stack,
361 wikilink_stack,
362 html_scan_guard,
363 link_ref_expansion_limit: text.len().max(100_000),
365 unclosed_paren_title_floor: Cell::new(usize::MAX),
366 mdx_errors: Vec::new(),
367 code_delims: CodeDelims::new(),
368 math_delims: MathDelims::new(),
369 },
370 }
371 }
372
373 pub fn reference_definitions(&self) -> &RefDefs<'_> {
376 &self.inner.allocs.refdefs
377 }
378
379 pub fn mdx_errors(&self) -> &[(usize, String)] {
382 &self.inner.mdx_errors
383 }
384
385 pub fn into_offset_iter(self) -> OffsetIter<'input, CB> {
389 OffsetIter { parser: self }
390 }
391}
392
393impl<'input, F> Parser<'input, BrokenLinkCallback<F>> {
394 pub fn new_with_broken_link_callback(
403 text: &'input str,
404 options: Options,
405 broken_link_callback: Option<F>,
406 ) -> Self
407 where
408 F: FnMut(BrokenLink<'input>) -> Option<(CowStr<'input>, CowStr<'input>)>,
409 {
410 Self::new_with_callbacks(text, options, BrokenLinkCallback(broken_link_callback))
411 }
412}
413
414impl<'input> ParserInner<'input> {
415 pub(crate) fn new(text: &'input str, options: Options) -> Self {
416 let (mut tree, allocs, firstpass_mdx_errors) = run_first_pass(text, options);
417 tree.reset();
418 ParserInner {
419 text,
420 options,
421 tree,
422 allocs,
423 inline_stack: Default::default(),
424 link_stack: Default::default(),
425 wikilink_stack: Default::default(),
426 html_scan_guard: Default::default(),
427 link_ref_expansion_limit: text.len().max(100_000),
428 unclosed_paren_title_floor: Cell::new(usize::MAX),
429 mdx_errors: firstpass_mdx_errors,
430 code_delims: CodeDelims::new(),
431 math_delims: MathDelims::new(),
432 }
433 }
434
435 fn fetch_link_type_url_title(
454 &mut self,
455 link_label: CowStr<'input>,
456 span: Range<usize>,
457 link_type: LinkType,
458 callbacks: &mut dyn ParserCallbacks<'input>,
459 ) -> Option<(LinkType, CowStr<'input>, CowStr<'input>)> {
460 if self.link_ref_expansion_limit == 0 {
461 return None;
462 }
463
464 let (link_type, url, title) = self
465 .allocs
466 .refdefs
467 .get(link_label.as_ref())
468 .map(|matching_def| {
469 let title = matching_def
471 .title
472 .as_ref()
473 .cloned()
474 .unwrap_or_else(|| "".into());
475 let url = matching_def.dest.clone();
476 (link_type, url, title)
477 })
478 .or_else(|| {
479 let broken_link = BrokenLink {
481 span,
482 link_type,
483 reference: link_label,
484 };
485
486 callbacks
487 .handle_broken_link(broken_link)
488 .map(|(url, title)| (link_type.to_unknown(), url, title))
489 })?;
490
491 self.link_ref_expansion_limit = self
495 .link_ref_expansion_limit
496 .saturating_sub(url.len() + title.len());
497
498 Some((link_type, url, title))
499 }
500
501 pub(crate) fn handle_inline(&mut self, callbacks: &mut dyn ParserCallbacks<'input>) {
508 self.handle_inline_pass1(callbacks);
509 let st_enabled = self.options.contains(Options::ENABLE_STRIKETHROUGH)
524 || self.options.contains(Options::ENABLE_SUBSCRIPT)
525 || self.options.contains(Options::ENABLE_SUPERSCRIPT);
526 if !st_enabled {
527 self.handle_emphasis_pass();
528 return;
529 }
530 let scope_first = self
535 .tree
536 .peek_up()
537 .and_then(|p| self.tree[p].child)
538 .or_else(|| self.tree.cur());
539 let strikethrough_first = matches!(
540 self.first_inline_marker_char(scope_first),
541 Some(b'~') | Some(b'^')
542 );
543 self.resolve_inline_scope(self.tree.cur(), strikethrough_first);
544 }
545
546 fn resolve_inline_scope(&mut self, start: Option<TreeIndex>, strikethrough_first: bool) {
550 if strikethrough_first {
551 self.resolve_tildes_carets_in_scope(start, false);
552 self.resolve_emphasis_at_scope(start);
553 } else {
554 self.resolve_emphasis_at_scope(start);
555 self.resolve_tildes_carets_in_scope(start, false);
556 }
557 let mut cur = start;
558 while let Some(cur_ix) = cur {
559 let next = self.tree[cur_ix].next;
560 if matches!(
561 self.tree[cur_ix].item.body,
562 ItemBody::Emphasis
563 | ItemBody::Strong
564 | ItemBody::Strikethrough
565 | ItemBody::Subscript
566 | ItemBody::Superscript
567 | ItemBody::Link(_)
568 | ItemBody::Image(_)
569 ) {
570 let child = self.tree[cur_ix].child;
571 self.resolve_inline_scope(child, true);
572 }
573 cur = next;
574 }
575 }
576
577 fn resolve_emphasis_at_scope(&mut self, start: Option<TreeIndex>) {
580 let saved = core::mem::take(&mut self.inline_stack);
581 self.handle_emphasis_in_scope(start);
582 self.inline_stack = saved;
583 }
584
585 fn first_inline_marker_char(&self, start: Option<TreeIndex>) -> Option<u8> {
594 let tilde = self.options.contains(Options::ENABLE_STRIKETHROUGH)
599 || self.options.contains(Options::ENABLE_SUBSCRIPT);
600 let caret = self.options.contains(Options::ENABLE_SUPERSCRIPT);
601 let is_marker =
602 |c: u8| matches!(c, b'*' | b'_') || (c == b'~' && tilde) || (c == b'^' && caret);
603 let bytes = self.text.as_bytes();
604 let mut cur = start;
605 while let Some(cur_ix) = cur {
606 match self.tree[cur_ix].item.body {
607 ItemBody::MaybeEmphasis(..) => {
608 let c = bytes[self.tree[cur_ix].item.start];
609 if is_marker(c) {
610 return Some(c);
611 }
612 }
613 ItemBody::Text { backslash_escaped } => {
614 let item = &self.tree[cur_ix].item;
615 let from = item.start + usize::from(backslash_escaped);
618 if let Some(off) = bytes[from..item.end].iter().position(|&c| is_marker(c)) {
619 return Some(bytes[from + off]);
620 }
621 }
622 _ => {}
623 }
624 cur = self.tree[cur_ix].next;
625 }
626 None
627 }
628
629 fn handle_emphasis_pass(&mut self) {
634 let start = self.tree.cur();
635 self.resolve_emphasis_recursive(start);
636 }
637
638 fn resolve_emphasis_recursive(&mut self, start: Option<TreeIndex>) {
639 let saved = core::mem::take(&mut self.inline_stack);
643 self.handle_emphasis_in_scope(start);
644 self.inline_stack = saved;
645
646 let mut cur = start;
647 while let Some(cur_ix) = cur {
648 let next = self.tree[cur_ix].next;
649 match self.tree[cur_ix].item.body {
650 ItemBody::Emphasis
651 | ItemBody::Strong
652 | ItemBody::Strikethrough
653 | ItemBody::Subscript
654 | ItemBody::Superscript
655 | ItemBody::Link(_)
656 | ItemBody::Image(_) => {
657 let child = self.tree[cur_ix].child;
658 self.resolve_emphasis_recursive(child);
659 }
660 _ => {}
661 }
662 cur = next;
663 }
664 }
665
666 fn handle_inline_pass1(&mut self, callbacks: &mut dyn ParserCallbacks<'input>) {
672 let mut cur = self.tree.cur();
673 let mut prev = None;
674
675 let block_end = self.tree[self.tree.peek_up().unwrap()].item.end;
676 let block_text = &self.text[..block_end];
677 self.unclosed_paren_title_floor.set(usize::MAX);
678
679 while let Some(mut cur_ix) = cur {
680 match self.tree[cur_ix].item.body {
681 ItemBody::MaybeHtml(preceded_by_backslash) => {
682 if preceded_by_backslash {
683 self.tree[cur_ix].item.body = ItemBody::Text {
685 backslash_escaped: true,
686 };
687 prev = cur;
688 cur = self.tree[cur_ix].next;
689 continue;
690 }
691 #[cfg(feature = "mdx")]
693 if self.options.contains(Options::ENABLE_MDX) {
694 let start = self.tree[cur_ix].item.start;
695 let next_byte = block_text.as_bytes().get(start + 1).copied();
696
697 if next_byte == Some(b'!') {
699 self.mdx_errors.push((
700 start,
701 "Unexpected character `!` (U+0021) before name, expected a \
702 character that can start a name, such as a letter, `$`, or `_` \
703 (note: to create a comment in MDX, use `{/* text */}`)"
704 .to_string(),
705 ));
706 self.tree[cur_ix].item.body = ItemBody::Text {
707 backslash_escaped: false,
708 };
709 prev = cur;
710 cur = self.tree[cur_ix].next;
711 continue;
712 }
713
714 if let Some(total_len) =
715 scan_mdx_inline_jsx(&block_text.as_bytes()[start..])
716 {
717 let end = start + total_len;
718 let node = scan_nodes_to_ix(&self.tree, self.tree[cur_ix].next, end);
719 let raw = &block_text[start..end];
720 let col = crate::mdx::column_at(block_text.as_bytes(), start);
721 let jsx_data = crate::mdx::parse_jsx_tag_with_column(raw, col, 0);
722 let mut allocator = oxc_allocator::Allocator::default();
723 crate::mdx::validate_jsx_expressions(
724 raw,
725 &jsx_data.attrs,
726 |rel| start + rel,
727 &mut allocator,
728 &mut self.mdx_errors,
729 );
730 let jsx_ix = self.allocs.allocate_jsx_element(jsx_data);
731 self.tree[cur_ix].item.body = ItemBody::MdxJsxTextElement(jsx_ix);
732 self.tree[cur_ix].item.end = end;
733 self.tree[cur_ix].next = node;
734 prev = cur;
735 cur = node;
736 if let Some(node_ix) = cur {
737 self.tree[node_ix].item.start =
738 max(self.tree[node_ix].item.start, end);
739 }
740 continue;
741 }
742
743 let bytes_block = block_text.as_bytes();
758 let is_text_fallback = match next_byte {
759 Some(b' ' | b'\t') => true,
760 Some(b'\n' | b'\r') => {
761 let bq_depth = self
767 .tree
768 .walk_spine()
769 .filter(|&&ix| {
770 matches!(self.tree[ix].item.body, ItemBody::BlockQuote(..))
771 })
772 .count();
773 let mut probe = start + 1;
774 loop {
775 while probe < bytes_block.len()
776 && matches!(
777 bytes_block[probe],
778 b' ' | b'\t' | b'\n' | b'\r'
779 )
780 {
781 probe += 1;
782 }
783 if bq_depth == 0
784 || probe >= bytes_block.len()
785 || bytes_block[probe] != b'>'
786 {
787 break;
788 }
789 let mut consumed = 0;
790 while consumed < bq_depth
791 && probe < bytes_block.len()
792 && bytes_block[probe] == b'>'
793 {
794 probe += 1;
795 if probe < bytes_block.len() && bytes_block[probe] == b' ' {
796 probe += 1;
797 }
798 consumed += 1;
799 }
800 }
801 if probe >= bytes_block.len() || bytes_block[probe] == b'>' {
802 false
803 } else {
804 let underline_char = bytes_block[probe];
814 if !matches!(underline_char, b'-' | b'=') {
815 true
816 } else {
817 let mut q = probe;
818 while q < bytes_block.len()
819 && bytes_block[q] == underline_char
820 {
821 q += 1;
822 }
823 while q < bytes_block.len()
824 && matches!(bytes_block[q], b' ' | b'\t')
825 {
826 q += 1;
827 }
828 let at_eol = q >= bytes_block.len()
829 || matches!(bytes_block[q], b'\n' | b'\r');
830 if !at_eol {
831 true
832 } else {
833 let mut ls = start;
852 while ls > 0
853 && !matches!(bytes_block[ls - 1], b'\n' | b'\r')
854 {
855 ls -= 1;
856 }
857 let mut k = ls;
858 let mut sp = 0;
859 while k < start && bytes_block[k] == b' ' && sp < 3 {
860 k += 1;
861 sp += 1;
862 }
863 if k < start && bytes_block[k] == b'>' {
864 true
865 } else {
866 let mut us = probe;
868 while us > 0
869 && !matches!(bytes_block[us - 1], b'\n' | b'\r')
870 {
871 us -= 1;
872 }
873 let mut underline_col = 0;
874 let mut uk = us;
875 while uk < probe && bytes_block[uk] == b' ' {
876 uk += 1;
877 underline_col += 1;
878 }
879 let listitem_indent = self
880 .tree
881 .walk_spine()
882 .filter_map(|&ix| {
883 match self.tree[ix].item.body {
884 ItemBody::ListItem(indent, _) => {
885 Some(indent)
886 }
887 _ => None,
888 }
889 })
890 .next();
891 let in_blockquote =
892 self.tree.walk_spine().any(|&ix| {
893 matches!(
894 self.tree[ix].item.body,
895 ItemBody::BlockQuote(..)
896 )
897 });
898 let bq_lazy = if in_blockquote {
908 underline_col < 1
909 || !bytes_block[us..probe].contains(&b'>')
910 } else {
911 false
912 };
913 matches!(listitem_indent, Some(i) if underline_col < i)
914 || bq_lazy
915 }
916 }
917 }
918 }
919 }
920 _ => false,
921 };
922 if !is_text_fallback {
923 self.mdx_errors.push((
924 start,
925 "Unexpected character after `<`, expected a valid JSX tag \
926 (note: to create a link in MDX, use `[text](url)`)"
927 .to_string(),
928 ));
929 }
930
931 self.tree[cur_ix].item.body = ItemBody::Text {
932 backslash_escaped: false,
933 };
934 prev = cur;
935 cur = self.tree[cur_ix].next;
936 continue;
937 }
938
939 let next = self.tree[cur_ix].next;
940 let autolink = if let Some(next_ix) = next {
941 scan_autolink(block_text, self.tree[next_ix].item.start)
942 } else {
943 None
944 };
945
946 if let Some((ix, uri, link_type)) = autolink {
947 let node = scan_nodes_to_ix(&self.tree, next, ix);
948 let text_node = self.tree.create_node(Item {
949 start: self.tree[cur_ix].item.start + 1,
950 end: ix - 1,
951 body: ItemBody::Text {
952 backslash_escaped: false,
953 },
954 });
955 let link_ix =
956 self.allocs
957 .allocate_link(link_type, uri, "".into(), "".into());
958 self.tree[cur_ix].item.body = ItemBody::Link(link_ix);
959 self.tree[cur_ix].item.end = ix;
960 self.tree[cur_ix].next = node;
961 self.tree[cur_ix].child = Some(text_node);
962 prev = cur;
963 cur = node;
964 if let Some(node_ix) = cur {
965 let orig_start = self.tree[node_ix].item.start;
966 let new_start = max(orig_start, ix);
967 self.tree[node_ix].item.start = new_start;
968 if new_start > orig_start
975 && let ItemBody::Text { backslash_escaped } =
976 &mut self.tree[node_ix].item.body
977 {
978 *backslash_escaped = false;
979 }
980 }
981 continue;
982 } else {
983 let inline_html = next.and_then(|next_ix| {
984 self.scan_inline_html(
985 block_text.as_bytes(),
986 self.tree[next_ix].item.start,
987 )
988 });
989 if let Some((span, ix)) = inline_html {
990 let node = scan_nodes_to_ix(&self.tree, next, ix);
991 self.tree[cur_ix].item.body = if !span.is_empty() {
992 let converted_string =
993 String::from_utf8(span).expect("invalid utf8");
994 ItemBody::OwnedInlineHtml(
995 self.allocs.allocate_cow(converted_string.into()),
996 )
997 } else {
998 ItemBody::InlineHtml
999 };
1000 self.tree[cur_ix].item.end = ix;
1001 self.tree[cur_ix].next = node;
1002 prev = cur;
1003 cur = node;
1004 if let Some(node_ix) = cur {
1005 let orig_start = self.tree[node_ix].item.start;
1006 let new_start = max(orig_start, ix);
1007 self.tree[node_ix].item.start = new_start;
1008 if new_start > orig_start
1014 && let ItemBody::Text { backslash_escaped } =
1015 &mut self.tree[node_ix].item.body
1016 {
1017 *backslash_escaped = false;
1018 }
1019 }
1020 continue;
1021 }
1022 }
1023 self.tree[cur_ix].item.body = ItemBody::Text {
1024 backslash_escaped: false,
1025 };
1026 }
1027 ItemBody::MaybeMath(preceded_by_backslash, _brace_context) => {
1028 if preceded_by_backslash {
1029 self.tree[cur_ix].item.body = ItemBody::Text {
1030 backslash_escaped: true,
1031 };
1032 prev = cur;
1033 cur = self.tree[cur_ix].next;
1034 continue;
1035 }
1036 let mut open_count = 1usize;
1038 let mut open_end = cur_ix;
1039 {
1040 let mut peek = self.tree[cur_ix].next;
1041 while let Some(peek_ix) = peek {
1042 if matches!(self.tree[peek_ix].item.body, ItemBody::MaybeMath(..))
1043 && self.tree[peek_ix].item.start == self.tree[open_end].item.end
1044 {
1045 open_count += 1;
1046 open_end = peek_ix;
1047 peek = self.tree[peek_ix].next;
1048 } else {
1049 break;
1050 }
1051 }
1052 }
1053
1054 let count_enabled = if open_count == 1 {
1060 self.options.contains(Options::ENABLE_MATH_SINGLE_DOLLAR)
1061 } else {
1062 self.options.contains(Options::ENABLE_MATH_MULTI_DOLLAR)
1063 };
1064 if !count_enabled {
1065 let mut text_ix = cur_ix;
1066 loop {
1067 self.tree[text_ix].item.body = ItemBody::Text {
1068 backslash_escaped: false,
1069 };
1070 if text_ix == open_end {
1071 break;
1072 }
1073 match self.tree[text_ix].next {
1074 Some(next) => text_ix = next,
1075 None => break,
1076 }
1077 }
1078 prev = cur;
1079 cur = self.tree[cur_ix].next;
1080 continue;
1081 }
1082
1083 let mut scan = self.tree[open_end].next;
1085 let mut close_ix = None;
1086 while let Some(scan_ix) = scan {
1087 if matches!(self.tree[scan_ix].item.body, ItemBody::MaybeMath(..)) {
1088 let mut run = 1usize;
1089 let mut run_end = scan_ix;
1090 let mut peek = self.tree[scan_ix].next;
1091 while let Some(peek_ix) = peek {
1092 if matches!(self.tree[peek_ix].item.body, ItemBody::MaybeMath(..))
1093 && self.tree[peek_ix].item.start == self.tree[run_end].item.end
1094 {
1095 run += 1;
1096 run_end = peek_ix;
1097 peek = self.tree[peek_ix].next;
1098 } else {
1099 break;
1100 }
1101 }
1102 if run == open_count {
1103 close_ix = Some(scan_ix);
1104 break;
1105 }
1106 scan = self.tree[run_end].next;
1108 continue;
1109 }
1110 scan = self.tree[scan_ix].next;
1111 }
1112
1113 if let Some(scan_ix) = close_ix {
1114 self.make_math_span(cur_ix, scan_ix);
1115 } else {
1116 let mut fail_ix = cur_ix;
1117 loop {
1118 self.tree[fail_ix].item.body = ItemBody::Text {
1119 backslash_escaped: false,
1120 };
1121 if fail_ix == open_end {
1122 break;
1123 }
1124 if let Some(next) = self.tree[fail_ix].next {
1125 fail_ix = next;
1126 } else {
1127 break;
1128 }
1129 }
1130 }
1131 }
1132 ItemBody::MaybeCode(mut search_count, preceded_by_backslash) => {
1133 if preceded_by_backslash {
1134 search_count -= 1;
1135 if search_count == 0 {
1136 self.tree[cur_ix].item.body = ItemBody::Text {
1137 backslash_escaped: true,
1138 };
1139 prev = cur;
1140 cur = self.tree[cur_ix].next;
1141 continue;
1142 }
1143 }
1144
1145 if self.code_delims.is_populated() {
1146 if let Some(scan_ix) = self.code_delims.find(cur_ix, search_count) {
1149 self.make_code_span(cur_ix, scan_ix, preceded_by_backslash);
1150 } else {
1151 self.tree[cur_ix].item.body = ItemBody::Text {
1152 backslash_escaped: preceded_by_backslash,
1153 };
1154 }
1155 } else {
1156 let mut scan = if search_count > 0 {
1159 self.tree[cur_ix].next
1160 } else {
1161 None
1162 };
1163 while let Some(scan_ix) = scan {
1164 if let ItemBody::MaybeCode(delim_count, _) =
1165 self.tree[scan_ix].item.body
1166 {
1167 if search_count == delim_count {
1168 self.make_code_span(cur_ix, scan_ix, preceded_by_backslash);
1169 self.code_delims.clear();
1170 break;
1171 } else {
1172 self.code_delims.insert(delim_count, scan_ix);
1173 }
1174 }
1175 scan = self.tree[scan_ix].next;
1176 }
1177 if scan.is_none() {
1178 self.tree[cur_ix].item.body = ItemBody::Text {
1179 backslash_escaped: preceded_by_backslash,
1180 };
1181 }
1182 }
1183 }
1184 ItemBody::MaybeAutolink(cand_ix) => {
1185 let next = self.tree[cur_ix].next;
1188 if !self.link_stack.is_empty() {
1189 self.tree[cur_ix].item.body = ItemBody::Text {
1193 backslash_escaped: false,
1194 };
1195 prev = cur;
1196 cur = next;
1197 continue;
1198 }
1199 let cand = self.allocs[cand_ix];
1202 let node_after = scan_nodes_to_ix(&self.tree, next, cand.end);
1203 let text_child = self.tree.create_node(Item {
1204 start: cand.start,
1205 end: cand.end,
1206 body: ItemBody::Text {
1207 backslash_escaped: false,
1208 },
1209 });
1210 self.tree[cur_ix].item = Item {
1211 start: cand.start,
1212 end: cand.end,
1213 body: ItemBody::Link(cand.link),
1214 };
1215 self.tree[cur_ix].child = Some(text_child);
1216 self.tree[cur_ix].next = node_after;
1217 if let Some(node_after_ix) = node_after {
1218 let orig_start = self.tree[node_after_ix].item.start;
1219 let new_start = max(orig_start, cand.end);
1220 if orig_start < cand.end
1223 && matches!(
1224 self.tree[node_after_ix].item.body,
1225 ItemBody::HardBreak(true)
1226 )
1227 {
1228 self.tree[node_after_ix].item.body = ItemBody::SoftBreak;
1229 }
1230 if orig_start < cand.end
1234 && matches!(
1235 self.tree[node_after_ix].item.body,
1236 ItemBody::SynthesizeText(..)
1237 )
1238 {
1239 self.tree[node_after_ix].item.body = ItemBody::Text {
1240 backslash_escaped: false,
1241 };
1242 }
1243 self.tree[node_after_ix].item.start = new_start;
1244 if orig_start <= cand.end {
1249 match &mut self.tree[node_after_ix].item.body {
1250 ItemBody::Text { backslash_escaped }
1251 | ItemBody::MaybeHtml(backslash_escaped) => {
1252 *backslash_escaped = false;
1253 }
1254 _ => {}
1255 }
1256 }
1257 self.repair_construct_after_url_end(cand.end, node_after_ix);
1258 }
1259 }
1260 ItemBody::MaybeEmphasisEscaped(count, ..) => {
1261 self.tree[cur_ix].item.body = ItemBody::Text {
1264 backslash_escaped: true,
1265 };
1266 let c = self.text.as_bytes()[self.tree[cur_ix].item.start];
1267 if !crate::firstpass::delim_run_is_valid(c, count - 1, self.options) {
1268 let mut scan = self.tree[cur_ix].next;
1269 for _ in 1..count {
1270 let Some(next_ix) = scan else { break };
1271 self.tree[next_ix].item.body = ItemBody::Text {
1272 backslash_escaped: false,
1273 };
1274 scan = self.tree[next_ix].next;
1275 }
1276 }
1277 }
1278 ItemBody::MaybeLinkOpen => {
1279 self.tree[cur_ix].item.body = ItemBody::Text {
1280 backslash_escaped: false,
1281 };
1282 let link_open_doubled = self.tree[cur_ix]
1283 .next
1284 .map(|ix| self.tree[ix].item.body == ItemBody::MaybeLinkOpen)
1285 .unwrap_or(false);
1286 if self.options.contains(Options::ENABLE_WIKILINKS) && link_open_doubled {
1287 self.wikilink_stack.push(LinkStackEl {
1288 node: cur_ix,
1289 ty: LinkStackTy::Link,
1290 });
1291 }
1292 self.link_stack.push(LinkStackEl {
1293 node: cur_ix,
1294 ty: LinkStackTy::Link,
1295 });
1296 }
1297 ItemBody::MaybeImage => {
1298 self.tree[cur_ix].item.body = ItemBody::Text {
1299 backslash_escaped: false,
1300 };
1301 let link_open_doubled = self.tree[cur_ix]
1302 .next
1303 .map(|ix| self.tree[ix].item.body == ItemBody::MaybeLinkOpen)
1304 .unwrap_or(false);
1305 if self.options.contains(Options::ENABLE_WIKILINKS) && link_open_doubled {
1306 self.wikilink_stack.push(LinkStackEl {
1307 node: cur_ix,
1308 ty: LinkStackTy::Image,
1309 });
1310 }
1311 self.link_stack.push(LinkStackEl {
1312 node: cur_ix,
1313 ty: LinkStackTy::Image,
1314 });
1315 }
1316 ItemBody::MaybeLinkClose(could_be_ref) => {
1317 self.tree[cur_ix].item.body = ItemBody::Text {
1318 backslash_escaped: false,
1319 };
1320 let tos_link = self.link_stack.pop();
1321 if self.options.contains(Options::ENABLE_WIKILINKS)
1322 && self.tree[cur_ix]
1323 .next
1324 .map(|ix| {
1325 matches!(self.tree[ix].item.body, ItemBody::MaybeLinkClose(..))
1326 })
1327 .unwrap_or(false)
1328 && let Some(node) = self.handle_wikilink(block_text, cur_ix, prev)
1329 {
1330 cur = self.tree[node].next;
1331 continue;
1332 }
1333 if let Some(tos) = tos_link {
1334 if tos.ty != LinkStackTy::Image
1337 && matches!(
1338 self.tree[self.tree.peek_up().unwrap()].item.body,
1339 ItemBody::Link(..)
1340 )
1341 {
1342 continue;
1343 }
1344 if tos.ty == LinkStackTy::Disabled {
1345 continue;
1346 }
1347 let next = self.tree[cur_ix].next;
1348 let footnote_first = tos.ty == LinkStackTy::Link
1350 && self.defined_footnote_label(tos.node, cur_ix);
1351 if !footnote_first
1352 && let Some((next_ix, url, title)) =
1353 self.scan_inline_link(block_text, self.tree[cur_ix].item.end, next)
1354 {
1355 let next_node = scan_nodes_to_ix(&self.tree, next, next_ix);
1356 if let Some(prev_ix) = prev {
1357 self.tree[prev_ix].next = None;
1358 }
1359 cur = Some(tos.node);
1360 cur_ix = tos.node;
1361 let link_ix =
1362 self.allocs
1363 .allocate_link(LinkType::Inline, url, title, "".into());
1364 self.tree[cur_ix].item.body = if tos.ty == LinkStackTy::Image {
1365 ItemBody::Image(link_ix)
1366 } else {
1367 ItemBody::Link(link_ix)
1368 };
1369 self.tree[cur_ix].child = self.tree[cur_ix].next;
1370 self.tree[cur_ix].next = next_node;
1371 self.tree[cur_ix].item.end = next_ix;
1372 if let Some(next_node_ix) = next_node {
1373 let orig_start = self.tree[next_node_ix].item.start;
1374 let new_start = max(orig_start, next_ix);
1375 self.tree[next_node_ix].item.start = new_start;
1376 if new_start > orig_start
1385 && let ItemBody::Text { backslash_escaped } =
1386 &mut self.tree[next_node_ix].item.body
1387 {
1388 *backslash_escaped = false;
1389 }
1390 }
1391
1392 if tos.ty == LinkStackTy::Link {
1393 self.disable_all_links();
1394 }
1395 } else {
1396 let first_bracket_start = self.tree[tos.node].item.start;
1403 let first_bracket_end = self.tree[cur_ix].item.end;
1404 let first_bracket_text =
1405 &self.text[first_bracket_start..first_bracket_end];
1406 if let Some((label_len, ReferenceLabel::Footnote(footlabel))) =
1407 scan_link_label(&self.tree, first_bracket_text, self.options)
1408 && label_len == first_bracket_text.len()
1410 && self.allocs.footdefs.contains(&footlabel)
1411 {
1412 let footref = self.allocs.allocate_cow(footlabel);
1413 if let Some(def) = self
1414 .allocs
1415 .footdefs
1416 .get_mut(self.allocs.cows[footref.0].to_owned())
1417 {
1418 def.use_count += 1;
1419 }
1420 let footnote_ix = if tos.ty == LinkStackTy::Image {
1421 self.tree[tos.node].next = Some(cur_ix);
1422 self.tree[tos.node].child = None;
1423 self.tree[tos.node].item.body = ItemBody::SynthesizeChar('!');
1424 self.tree[cur_ix].item.start =
1425 self.tree[tos.node].item.start + 1;
1426 self.tree[tos.node].item.end =
1427 self.tree[tos.node].item.start + 1;
1428 cur_ix
1429 } else {
1430 tos.node
1431 };
1432 self.tree[footnote_ix].next = next;
1433 self.tree[footnote_ix].child = None;
1434 self.tree[footnote_ix].item.body =
1435 ItemBody::FootnoteReference(footref);
1436 self.tree[footnote_ix].item.end = first_bracket_end;
1437 prev = Some(footnote_ix);
1438 cur = next;
1439 self.link_stack.clear();
1440 continue;
1441 }
1442 let scan_result =
1445 scan_reference(&self.tree, block_text, next, self.options);
1446 let (node_after_link, link_type) = match scan_result {
1447 RefScan::LinkLabel(_, end_ix) => {
1449 let reference_close_node = if let Some(node) =
1454 scan_nodes_to_ix(&self.tree, next, end_ix - 1)
1455 {
1456 node
1457 } else {
1458 continue;
1459 };
1460 self.tree[reference_close_node].item.body =
1461 ItemBody::MaybeLinkClose(false);
1462 let close_end = self.tree[reference_close_node].item.end;
1469 let next_node = if close_end > end_ix {
1470 self.tree[reference_close_node].item.end = end_ix;
1471 let tail = self.tree.create_node(Item {
1472 start: end_ix,
1473 end: close_end,
1474 body: ItemBody::Text {
1475 backslash_escaped: false,
1476 },
1477 });
1478 self.tree[tail].next = self.tree[reference_close_node].next;
1479 self.tree[reference_close_node].next = Some(tail);
1480 Some(tail)
1481 } else {
1482 self.tree[reference_close_node].next
1483 };
1484
1485 (next_node, LinkType::Reference)
1486 }
1487 RefScan::Collapsed(next_node) => {
1489 if !could_be_ref {
1492 continue;
1493 }
1494 (next_node, LinkType::Collapsed)
1495 }
1496 RefScan::UnexpectedFootnote => continue,
1503 RefScan::FailedInvalidLabel => continue,
1509 RefScan::Failed => {
1513 if !could_be_ref {
1514 continue;
1515 }
1516 (next, LinkType::Shortcut)
1517 }
1518 };
1519
1520 let label: Option<(ReferenceLabel<'input>, usize)> = match scan_result {
1525 RefScan::LinkLabel(l, end_ix) => {
1526 Some((ReferenceLabel::Link(l), end_ix))
1527 }
1528 RefScan::Collapsed(..)
1529 | RefScan::Failed
1530 | RefScan::FailedInvalidLabel
1531 | RefScan::UnexpectedFootnote => {
1532 let label_start = self.tree[tos.node].item.end - 1;
1534 let label_end = self.tree[cur_ix].item.end;
1535 scan_link_label(
1536 &self.tree,
1537 &self.text[label_start..label_end],
1538 self.options,
1539 )
1540 .map(|(ix, label)| (label, label_start + ix))
1541 .filter(|(_, end)| *end == label_end)
1542 }
1543 };
1544
1545 let id = match &label {
1546 Some(
1547 (ReferenceLabel::Link(l), _) | (ReferenceLabel::Footnote(l), _),
1548 ) => l.clone(),
1549 None => "".into(),
1550 };
1551
1552 if let Some((ReferenceLabel::Footnote(l), end)) = label {
1554 let footref = self.allocs.allocate_cow(l);
1555 if let Some(def) = self
1556 .allocs
1557 .footdefs
1558 .get_mut(self.allocs.cows[footref.0].to_owned())
1559 {
1560 def.use_count += 1;
1561 }
1562 if self.allocs.footdefs.contains(&self.allocs.cows[footref.0]) {
1563 let footnote_ix = if tos.ty == LinkStackTy::Image {
1566 self.tree[tos.node].next = Some(cur_ix);
1567 self.tree[tos.node].child = None;
1568 self.tree[tos.node].item.body =
1569 ItemBody::SynthesizeChar('!');
1570 self.tree[cur_ix].item.start =
1571 self.tree[tos.node].item.start + 1;
1572 self.tree[tos.node].item.end =
1573 self.tree[tos.node].item.start + 1;
1574 cur_ix
1575 } else {
1576 tos.node
1577 };
1578 self.tree[footnote_ix].next = next;
1582 self.tree[footnote_ix].child = None;
1583 self.tree[footnote_ix].item.body =
1584 ItemBody::FootnoteReference(footref);
1585 self.tree[footnote_ix].item.end = end;
1586 prev = Some(footnote_ix);
1587 cur = next;
1588 self.link_stack.clear();
1589 continue;
1590 }
1591 } else if let Some((ReferenceLabel::Link(link_label), end)) = label
1592 && let Some((def_link_type, url, title)) = self
1593 .fetch_link_type_url_title(
1594 link_label,
1595 (self.tree[tos.node].item.start)..end,
1596 link_type,
1597 callbacks,
1598 )
1599 {
1600 let link_ix =
1601 self.allocs.allocate_link(def_link_type, url, title, id);
1602 self.tree[tos.node].item.body = if tos.ty == LinkStackTy::Image {
1603 ItemBody::Image(link_ix)
1604 } else {
1605 ItemBody::Link(link_ix)
1606 };
1607 let label_node = self.tree[tos.node].next;
1608
1609 self.tree[tos.node].next = node_after_link;
1612
1613 if label_node != cur {
1615 self.tree[tos.node].child = label_node;
1616
1617 if let Some(prev_ix) = prev {
1619 self.tree[prev_ix].next = None;
1620 }
1621 }
1622
1623 self.tree[tos.node].item.end = end;
1624 debug_assert!(
1631 node_after_link.is_none_or(|node_after_ix| {
1632 self.tree[node_after_ix].item.start >= end
1633 }),
1634 "reference splice must not overrun its successor",
1635 );
1636
1637 cur = Some(tos.node);
1639 cur_ix = tos.node;
1640
1641 if tos.ty == LinkStackTy::Link {
1642 self.disable_all_links();
1643 }
1644 }
1645 }
1646 }
1647 }
1648 _ => {}
1649 }
1650 prev = cur;
1651 cur = self.tree[cur_ix].next;
1652 }
1653 self.link_stack.clear();
1654 self.wikilink_stack.clear();
1655 self.code_delims.clear();
1656 self.math_delims.clear();
1657 }
1658
1659 fn repair_construct_after_url_end(&mut self, cand_end: usize, node_ix: TreeIndex) {
1664 let item = self.tree[node_ix].item;
1665 if item.start != cand_end {
1666 return;
1667 }
1668 if let ItemBody::MaybeEmphasisEscaped(count, can_open, can_close) = item.body {
1669 self.tree[node_ix].item.body = ItemBody::MaybeEmphasis(count, can_open, can_close);
1670 let mut scan = self.tree[node_ix].next;
1673 for _ in 1..count {
1674 let Some(next_ix) = scan else { break };
1675 if let ItemBody::MaybeEmphasis(_, open, close) = &mut self.tree[next_ix].item.body {
1676 *open = can_open;
1677 *close = can_close;
1678 }
1679 scan = self.tree[next_ix].next;
1680 }
1681 return;
1682 }
1683 if !matches!(item.body, ItemBody::Text { .. })
1684 || self.text.as_bytes().get(cand_end) != Some(&b'&')
1685 {
1686 return;
1687 }
1688 let (n, Some(value)) = scan_entity(&self.text.as_bytes()[cand_end..]) else {
1689 return;
1690 };
1691 if cand_end + n > item.end {
1692 return;
1693 }
1694 let cow_ix = self.allocs.allocate_cow(value);
1695 if cand_end + n < item.end {
1696 let tail = self.tree.create_node(Item {
1697 start: cand_end + n,
1698 end: item.end,
1699 body: ItemBody::Text {
1700 backslash_escaped: false,
1701 },
1702 });
1703 self.tree[tail].next = self.tree[node_ix].next;
1704 self.tree[node_ix].next = Some(tail);
1705 }
1706 self.tree[node_ix].item.end = cand_end + n;
1707 self.tree[node_ix].item.body = ItemBody::SynthesizeText(cow_ix);
1708 }
1709
1710 fn handle_wikilink(
1716 &mut self,
1717 block_text: &'input str,
1718 cur_ix: TreeIndex,
1719 prev: Option<TreeIndex>,
1720 ) -> Option<TreeIndex> {
1721 let next_ix = self.tree[cur_ix].next.unwrap();
1722 if let Some(tos) = self.wikilink_stack.pop() {
1725 if tos.ty == LinkStackTy::Disabled {
1726 return None;
1727 }
1728 let Some(body_node) = self.tree[tos.node].next.and_then(|ix| self.tree[ix].next) else {
1730 return None;
1732 };
1733 let start_ix = self.tree[body_node].item.start;
1734 let end_ix = self.tree[cur_ix].item.start;
1735 let wikilink = match scan_wikilink_pipe(
1736 block_text,
1737 start_ix, end_ix - start_ix,
1739 ) {
1740 Some((rest, wikitext)) => {
1741 if wikitext.is_empty() {
1743 return None;
1744 }
1745 let body_node = scan_nodes_to_ix(&self.tree, Some(body_node), rest);
1747 if let Some(body_node) = body_node {
1748 self.tree[body_node].item.start = rest;
1751 Some((true, body_node, wikitext))
1752 } else {
1753 None
1754 }
1755 }
1756 None => {
1757 let wikitext = &block_text[start_ix..end_ix];
1758 if wikitext.is_empty() {
1760 return None;
1761 }
1762 let body_node = self.tree.create_node(Item {
1763 start: start_ix,
1764 end: end_ix,
1765 body: ItemBody::Text {
1766 backslash_escaped: false,
1767 },
1768 });
1769 Some((false, body_node, wikitext))
1770 }
1771 };
1772
1773 if let Some((has_pothole, body_node, wikiname)) = wikilink {
1774 let link_ix = self.allocs.allocate_link(
1775 LinkType::WikiLink { has_pothole },
1776 wikiname.into(),
1777 "".into(),
1778 "".into(),
1779 );
1780 if let Some(prev_ix) = prev {
1781 self.tree[prev_ix].next = None;
1782 }
1783 if tos.ty == LinkStackTy::Image {
1784 self.tree[tos.node].item.body = ItemBody::Image(link_ix);
1785 } else {
1786 self.tree[tos.node].item.body = ItemBody::Link(link_ix);
1787 }
1788 self.tree[tos.node].child = Some(body_node);
1789 self.tree[tos.node].next = self.tree[next_ix].next;
1790 self.tree[tos.node].item.end = end_ix + 2;
1791 self.disable_all_links();
1792 return Some(tos.node);
1793 }
1794 }
1795
1796 None
1797 }
1798
1799 fn handle_emphasis_in_scope(&mut self, start: Option<TreeIndex>) {
1800 let mut prev = None;
1801 let mut prev_ix: TreeIndex;
1802 let mut cur = start;
1803
1804 let mut single_quote_open: Option<TreeIndex> = None;
1805 let mut double_quote_open: bool = false;
1806
1807 while let Some(mut cur_ix) = cur {
1808 match self.tree[cur_ix].item.body {
1809 ItemBody::MaybeEmphasis(mut count, can_open, can_close) => {
1810 let run_length = count;
1811 let c = self.text.as_bytes()[self.tree[cur_ix].item.start];
1812 let both = can_open && can_close;
1813 if c == b'~' || c == b'^' {
1821 prev_ix = cur_ix + count - 1;
1822 prev = Some(prev_ix);
1823 cur = self.tree[prev_ix].next;
1824 continue;
1825 }
1826 if can_close {
1827 while let Some(el) =
1828 self.inline_stack
1829 .find_match(&mut self.tree, c, run_length, count, both)
1830 {
1831 if let Some(prev_ix) = prev {
1833 self.tree[prev_ix].next = None;
1834 }
1835 let match_count = min(2, min(count, el.count));
1844 let mut end = cur_ix - 1;
1846 let mut start = el.start + el.count;
1847
1848 while start > el.start + el.count - match_count {
1850 let inc = if start > el.start + el.count - match_count + 1 {
1851 2
1852 } else {
1853 1
1854 };
1855 let ty = if c == b'~' {
1856 if inc == 2 {
1857 if self.options.contains(Options::ENABLE_STRIKETHROUGH) {
1858 ItemBody::Strikethrough
1859 } else {
1860 ItemBody::Text {
1861 backslash_escaped: false,
1862 }
1863 }
1864 } else if self.options.contains(Options::ENABLE_SUBSCRIPT) {
1865 ItemBody::Subscript
1866 } else if self.options.contains(Options::ENABLE_STRIKETHROUGH) {
1867 ItemBody::Strikethrough
1868 } else {
1869 ItemBody::Text {
1870 backslash_escaped: false,
1871 }
1872 }
1873 } else if c == b'^' {
1874 if self.options.contains(Options::ENABLE_SUPERSCRIPT) {
1875 ItemBody::Superscript
1876 } else {
1877 ItemBody::Text {
1878 backslash_escaped: false,
1879 }
1880 }
1881 } else if inc == 2 {
1882 ItemBody::Strong
1883 } else {
1884 ItemBody::Emphasis
1885 };
1886
1887 let root = start - inc;
1888 end = end + inc;
1889 self.tree[root].item.body = ty;
1890 self.tree[root].item.end = self.tree[end].item.end;
1891 self.tree[root].child = Some(start);
1892 self.tree[root].next = None;
1893 start = root;
1894 }
1895
1896 prev_ix = el.start + el.count - match_count;
1898 prev = Some(prev_ix);
1899 cur = self.tree[cur_ix + match_count - 1].next;
1900 self.tree[prev_ix].next = cur;
1901
1902 if el.count > match_count {
1903 self.inline_stack.push(InlineEl {
1904 start: el.start,
1905 count: el.count - match_count,
1906 run_length: el.run_length,
1907 c: el.c,
1908 both: el.both,
1909 })
1910 }
1911 count -= match_count;
1912 if count > 0 {
1913 cur_ix = cur.unwrap();
1914 } else {
1915 break;
1916 }
1917 }
1918 }
1919 if count > 0 {
1920 if can_open {
1921 self.inline_stack.push(InlineEl {
1922 start: cur_ix,
1923 run_length,
1924 count,
1925 c,
1926 both,
1927 });
1928 } else {
1929 for i in 0..count {
1930 self.tree[cur_ix + i].item.body = ItemBody::Text {
1931 backslash_escaped: false,
1932 };
1933 }
1934 }
1935 prev_ix = cur_ix + count - 1;
1936 prev = Some(prev_ix);
1937 cur = self.tree[prev_ix].next;
1938 }
1939 }
1940 ItemBody::MaybeSmartQuote(c, can_open, can_close) => {
1941 self.tree[cur_ix].item.body = match c {
1942 b'\'' => {
1943 if let (Some(open_ix), true) = (single_quote_open, can_close) {
1944 self.tree[open_ix].item.body = ItemBody::SynthesizeChar('‘');
1945 single_quote_open = None;
1946 } else if can_open {
1947 single_quote_open = Some(cur_ix);
1948 }
1949 ItemBody::SynthesizeChar('’')
1950 }
1951 _ => {
1952 if can_close && double_quote_open {
1953 double_quote_open = false;
1954 ItemBody::SynthesizeChar('”')
1955 } else if can_open {
1956 double_quote_open = true;
1957 ItemBody::SynthesizeChar('“')
1958 } else if can_close {
1959 ItemBody::SynthesizeChar('”')
1962 } else {
1963 ItemBody::SynthesizeChar('“')
1965 }
1966 }
1967 };
1968 prev = cur;
1969 cur = self.tree[cur_ix].next;
1970 }
1971 ItemBody::HardBreak(true) => {
1972 if self.tree[cur_ix].next.is_none() {
1973 self.tree[cur_ix].item.body = ItemBody::SynthesizeChar('\\');
1974 }
1975 prev = cur;
1976 cur = self.tree[cur_ix].next;
1977 }
1978 _ => {
1979 prev = cur;
1980 cur = self.tree[cur_ix].next;
1981 }
1982 }
1983 }
1984 self.inline_stack.pop_all(&mut self.tree);
1985 }
1986
1987 fn resolve_tildes_carets_in_scope(&mut self, start: Option<TreeIndex>, descend: bool) {
1998 let mut stack: Vec<InlineEl> = Vec::new();
1999 let mut cur = start;
2000 let mut prev: Option<TreeIndex> = None;
2001 while let Some(mut cur_ix) = cur {
2002 match self.tree[cur_ix].item.body {
2003 ItemBody::MaybeEmphasis(count, can_open, can_close) => {
2004 let c = self.text.as_bytes()[self.tree[cur_ix].item.start];
2005 if c != b'~' && c != b'^' {
2006 prev = Some(cur_ix);
2007 cur = self.tree[cur_ix].next;
2008 continue;
2009 }
2010 let run_length = count;
2011 let mut remaining = count;
2012 if can_close {
2013 while remaining > 0 {
2014 let res = stack
2015 .iter()
2016 .enumerate()
2017 .rfind(|(_, el)| el.c == c && el.run_length == run_length);
2018 let Some((matching_ix, matching_el)) = res else {
2019 break;
2020 };
2021 let matching_el = *matching_el;
2022 if let Some(prev_ix) = prev {
2023 self.tree[prev_ix].next = None;
2024 }
2025 for el in &stack[(matching_ix + 1)..] {
2028 for i in 0..el.count {
2029 self.tree[el.start + i].item.body = ItemBody::Text {
2030 backslash_escaped: false,
2031 };
2032 }
2033 }
2034 stack.truncate(matching_ix);
2035 let match_count =
2036 core::cmp::min(2, core::cmp::min(remaining, matching_el.count));
2037 let mut end = cur_ix - 1;
2038 let mut sub_start = matching_el.start + matching_el.count;
2039 while sub_start > matching_el.start + matching_el.count - match_count {
2040 let inc = if sub_start
2041 > matching_el.start + matching_el.count - match_count + 1
2042 {
2043 2
2044 } else {
2045 1
2046 };
2047 let ty = if c == b'~' {
2048 if inc == 2 {
2049 if self.options.contains(Options::ENABLE_STRIKETHROUGH) {
2050 ItemBody::Strikethrough
2051 } else {
2052 ItemBody::Text {
2053 backslash_escaped: false,
2054 }
2055 }
2056 } else if self.options.contains(Options::ENABLE_SUBSCRIPT) {
2057 ItemBody::Subscript
2058 } else if self.options.contains(Options::ENABLE_STRIKETHROUGH) {
2059 ItemBody::Strikethrough
2060 } else {
2061 ItemBody::Text {
2062 backslash_escaped: false,
2063 }
2064 }
2065 } else if self.options.contains(Options::ENABLE_SUPERSCRIPT) {
2066 ItemBody::Superscript
2067 } else {
2068 ItemBody::Text {
2069 backslash_escaped: false,
2070 }
2071 };
2072 let root = sub_start - inc;
2073 end = end + inc;
2074 self.tree[root].item.body = ty;
2075 self.tree[root].item.end = self.tree[end].item.end;
2076 self.tree[root].child = Some(sub_start);
2077 self.tree[root].next = None;
2078 sub_start = root;
2079 }
2080 let new_prev_ix = matching_el.start + matching_el.count - match_count;
2081 let new_cur = self.tree[cur_ix + match_count - 1].next;
2082 self.tree[new_prev_ix].next = new_cur;
2083 prev = Some(new_prev_ix);
2084 if matching_el.count > match_count {
2085 stack.push(InlineEl {
2086 start: matching_el.start,
2087 count: matching_el.count - match_count,
2088 run_length: matching_el.run_length,
2089 c: matching_el.c,
2090 both: matching_el.both,
2091 });
2092 }
2093 remaining -= match_count;
2094 if remaining > 0 {
2095 let Some(next_cur) = new_cur else { break };
2096 cur_ix = next_cur;
2097 } else {
2098 break;
2099 }
2100 }
2101 }
2102 if remaining > 0 {
2103 if can_open {
2104 stack.push(InlineEl {
2105 start: cur_ix,
2106 count: remaining,
2107 run_length,
2108 c,
2109 both: can_open && can_close,
2110 });
2111 } else {
2112 for i in 0..remaining {
2113 self.tree[cur_ix + i].item.body = ItemBody::Text {
2114 backslash_escaped: false,
2115 };
2116 }
2117 }
2118 let prev_ix = cur_ix + remaining - 1;
2119 prev = Some(prev_ix);
2120 cur = self.tree[prev_ix].next;
2121 } else {
2122 cur = self.tree[prev.unwrap()].next;
2123 }
2124 continue;
2125 }
2126 ItemBody::Emphasis
2127 | ItemBody::Strong
2128 | ItemBody::Strikethrough
2129 | ItemBody::Subscript
2130 | ItemBody::Superscript
2131 | ItemBody::Link(_)
2132 | ItemBody::Image(_)
2133 if descend =>
2134 {
2135 let child = self.tree[cur_ix].child;
2136 self.resolve_tildes_carets_in_scope(child, true);
2137 }
2138 _ => {}
2139 }
2140 prev = Some(cur_ix);
2141 cur = self.tree[cur_ix].next;
2142 }
2143 for el in stack {
2145 for i in 0..el.count {
2146 self.tree[el.start + i].item.body = ItemBody::Text {
2147 backslash_escaped: false,
2148 };
2149 }
2150 }
2151 }
2152
2153 fn disable_all_links(&mut self) {
2154 self.link_stack.disable_all_links();
2155 self.wikilink_stack.disable_all_links();
2156 }
2157
2158 fn defined_footnote_label(&self, open_ix: TreeIndex, close_ix: TreeIndex) -> bool {
2159 let start = self.tree[open_ix].item.start;
2160 if !self.options.contains(Options::ENABLE_FOOTNOTES)
2161 || self.text.as_bytes().get(start + 1) != Some(&b'^')
2162 {
2163 return false;
2164 }
2165 let label_text = &self.text[start..self.tree[close_ix].item.end];
2166 let Some((len, ReferenceLabel::Footnote(label))) =
2167 scan_link_label(&self.tree, label_text, self.options)
2168 else {
2169 return false;
2170 };
2171 len == label_text.len()
2173 && !label_text.as_bytes()[..len]
2174 .iter()
2175 .any(|b| matches!(b, b' ' | b'\t' | b'\n' | b'\r'))
2176 && self.allocs.footdefs.contains(&label)
2177 }
2178
2179 fn scan_inline_link(
2181 &self,
2182 underlying: &'input str,
2183 mut ix: usize,
2184 node: Option<TreeIndex>,
2185 ) -> Option<(usize, CowStr<'input>, CowStr<'input>)> {
2186 if underlying.as_bytes().get(ix) != Some(&b'(') {
2187 return None;
2188 }
2189 ix += 1;
2190
2191 let scan_separator = |ix: &mut usize| {
2192 *ix += scan_while(&underlying.as_bytes()[*ix..], is_space_or_tab);
2193 if let Some(bl) = scan_eol(&underlying.as_bytes()[*ix..]) {
2194 *ix += bl;
2195 *ix += skip_container_prefixes(
2196 &self.tree,
2197 &underlying.as_bytes()[*ix..],
2198 self.options,
2199 );
2200 }
2201 *ix += scan_while(&underlying.as_bytes()[*ix..], is_space_or_tab);
2202 };
2203
2204 scan_separator(&mut ix);
2205
2206 let (dest_length, dest) = scan_link_dest(underlying, ix, LINK_MAX_NESTED_PARENS)?;
2207 let dest = unescape(dest, self.tree.is_in_table());
2208 ix += dest_length;
2209
2210 let dest_end = ix;
2211 scan_separator(&mut ix);
2212
2213 let title = if ix > dest_end
2215 && let Some((bytes_scanned, t)) = self.scan_link_title(underlying, ix, node)
2216 {
2217 ix += bytes_scanned;
2218 scan_separator(&mut ix);
2219 t
2220 } else {
2221 "".into()
2222 };
2223 if underlying.as_bytes().get(ix) != Some(&b')') {
2224 return None;
2225 }
2226 ix += 1;
2227
2228 Some((ix, dest, title))
2229 }
2230
2231 fn scan_link_title(
2233 &self,
2234 text: &'input str,
2235 start_ix: usize,
2236 node: Option<TreeIndex>,
2237 ) -> Option<(usize, CowStr<'input>)> {
2238 let bytes = text.as_bytes();
2239 let open = match bytes.get(start_ix) {
2240 Some(b @ b'\'') | Some(b @ b'\"') | Some(b @ b'(') => *b,
2241 _ => return None,
2242 };
2243 if open == b'(' && start_ix >= self.unclosed_paren_title_floor.get() {
2244 return None;
2245 }
2246 let close = if open == b'(' { b')' } else { open };
2248
2249 let mut title = String::new();
2250 let mut mark = start_ix + 1;
2251 let mut i = start_ix + 1;
2252
2253 while i < bytes.len() {
2254 let c = bytes[i];
2255
2256 if c == close {
2257 let cow = if title.is_empty() {
2258 (i - start_ix + 1, text[mark..i].into())
2259 } else {
2260 title.push_str(&text[mark..i]);
2261 (i - start_ix + 1, title.into())
2262 };
2263
2264 return Some(cow);
2265 }
2266
2267 if (c == b'\n' || c == b'\r')
2268 && let Some(node_ix) = scan_nodes_to_ix(&self.tree, node, i + 1)
2269 && self.tree[node_ix].item.start > i
2270 {
2271 title.push_str(&text[mark..i]);
2272 title.push(c as char);
2274 if c == b'\r' && bytes.get(i + 1) == Some(&b'\n') {
2275 title.push('\n');
2276 }
2277 i = self.tree[node_ix].item.start;
2278 mark = i;
2279 continue;
2280 }
2281 if c == b'&'
2282 && let (n, Some(value)) = scan_entity(&bytes[i..])
2283 {
2284 title.push_str(&text[mark..i]);
2285 title.push_str(&value);
2286 i += n;
2287 mark = i;
2288 continue;
2289 }
2290 if self.tree.is_in_table()
2291 && c == b'\\'
2292 && i + 2 < bytes.len()
2293 && bytes[i + 1] == b'\\'
2294 && bytes[i + 2] == b'|'
2295 {
2296 title.push_str(&text[mark..i]);
2299 i += 2;
2300 mark = i;
2301 }
2302 if c == b'\\' && i + 1 < bytes.len() && is_ascii_punctuation(bytes[i + 1]) {
2303 title.push_str(&text[mark..i]);
2304 i += 1;
2305 mark = i;
2306 }
2307
2308 i += 1;
2309 }
2310
2311 if open == b'(' {
2312 let floor = self.unclosed_paren_title_floor.get();
2313 self.unclosed_paren_title_floor.set(floor.min(start_ix));
2314 }
2315 None
2316 }
2317
2318 fn make_math_span(&mut self, open: TreeIndex, close: TreeIndex) {
2319 let mut open_end = open;
2321 {
2322 let mut peek = self.tree[open].next;
2323 while let Some(peek_ix) = peek {
2324 if matches!(self.tree[peek_ix].item.body, ItemBody::MaybeMath(..))
2325 && self.tree[peek_ix].item.start == self.tree[open_end].item.end
2326 && peek_ix != close
2327 {
2328 open_end = peek_ix;
2329 peek = self.tree[peek_ix].next;
2330 } else {
2331 break;
2332 }
2333 }
2334 }
2335 let mut close_end = close;
2337 {
2338 let mut peek = self.tree[close].next;
2339 while let Some(peek_ix) = peek {
2340 if matches!(self.tree[peek_ix].item.body, ItemBody::MaybeMath(..))
2341 && self.tree[peek_ix].item.start == self.tree[close_end].item.end
2342 {
2343 close_end = peek_ix;
2344 peek = self.tree[peek_ix].next;
2345 } else {
2346 break;
2347 }
2348 }
2349 }
2350
2351 let span_start = self.tree[open_end].item.end;
2352 let span_end = self.tree[close].item.start;
2353
2354 if span_start > span_end {
2355 self.tree[open].item.body = ItemBody::Text {
2356 backslash_escaped: false,
2357 };
2358 return;
2359 }
2360
2361 let spanned_text = &self.text[span_start..span_end];
2362 let spanned_bytes = spanned_text.as_bytes();
2363 let mut buf: Option<String> = None;
2364
2365 let mut start_ix = 0;
2366 let mut ix = 0;
2367 while ix < spanned_bytes.len() {
2368 let c = spanned_bytes[ix];
2369 if c == b'\r' || c == b'\n' {
2370 ix += 1;
2371 let buf = buf.get_or_insert_with(|| String::with_capacity(spanned_bytes.len()));
2372 buf.push_str(&spanned_text[start_ix..ix]);
2373 let from = span_start + ix;
2382 let (scanned, leftover) = skip_container_prefixes_with_remaining(
2383 &self.tree,
2384 &self.text.as_bytes()[from..],
2385 self.options,
2386 );
2387 let scanned = scanned.min(spanned_bytes.len() - ix);
2388 ix += scanned;
2389 start_ix = ix;
2390 for _ in 0..leftover {
2394 buf.push(' ');
2395 }
2396 } else if c == b'\\'
2397 && spanned_bytes.get(ix + 1) == Some(&b'|')
2398 && self.tree.is_in_table()
2399 {
2400 let buf = buf.get_or_insert_with(|| String::with_capacity(spanned_bytes.len()));
2401 buf.push_str(&spanned_text[start_ix..ix]);
2402 buf.push('|');
2403 ix += 2;
2404 start_ix = ix;
2405 } else {
2406 ix += 1;
2407 }
2408 }
2409
2410 if let Some(buf) = &mut buf {
2411 buf.push_str(&spanned_text[start_ix..]);
2412 }
2413 let cow: CowStr<'input> = strip_span_padding(buf, spanned_text);
2414
2415 self.tree[open].item.body = ItemBody::Math(self.allocs.allocate_cow(cow), false);
2416 self.tree[open].item.end = self.tree[close_end].item.end;
2417 self.tree[open].next = self.tree[close_end].next;
2418 }
2419
2420 fn make_code_span(&mut self, open: TreeIndex, close: TreeIndex, preceding_backslash: bool) {
2424 let span_start = self.tree[open].item.end;
2425 let span_end = self.tree[close].item.start;
2426 let mut buf: Option<String> = None;
2427
2428 let spanned_text = &self.text[span_start..span_end];
2429 let spanned_bytes = spanned_text.as_bytes();
2430 let mut start_ix = 0;
2431 let mut ix = 0;
2432 while ix < spanned_bytes.len() {
2433 let c = spanned_bytes[ix];
2434 if c == b'\r' || c == b'\n' {
2435 let buf = buf.get_or_insert_with(|| String::with_capacity(spanned_bytes.len()));
2436 buf.push_str(&spanned_text[start_ix..ix]);
2439 buf.push(c as char);
2440 ix += 1;
2441 if c == b'\r' && spanned_bytes.get(ix) == Some(&b'\n') {
2442 buf.push('\n');
2443 ix += 1;
2444 }
2445 let from = span_start + ix;
2454 let (scanned, leftover) = skip_container_prefixes_with_remaining(
2455 &self.tree,
2456 &self.text.as_bytes()[from..],
2457 self.options,
2458 );
2459 let scanned = scanned.min(spanned_bytes.len() - ix);
2460 ix += scanned;
2461 start_ix = ix;
2462 for _ in 0..leftover {
2466 buf.push(' ');
2467 }
2468 } else if c == b'\\'
2469 && spanned_bytes.get(ix + 1) == Some(&b'|')
2470 && self.tree.is_in_table()
2471 {
2472 let buf = buf.get_or_insert_with(|| String::with_capacity(spanned_bytes.len()));
2473 buf.push_str(&spanned_text[start_ix..ix]);
2474 buf.push('|');
2475 ix += 2;
2476 start_ix = ix;
2477 } else {
2478 ix += 1;
2479 }
2480 }
2481
2482 if let Some(buf) = &mut buf {
2483 buf.push_str(&spanned_text[start_ix..]);
2484 }
2485 let cow: CowStr<'input> = strip_span_padding(buf, spanned_text);
2486
2487 if preceding_backslash {
2488 self.tree[open].item.body = ItemBody::Text {
2489 backslash_escaped: true,
2490 };
2491 self.tree[open].item.end = self.tree[open].item.start + 1;
2492 self.tree[open].next = Some(close);
2493 self.tree[close].item.body = ItemBody::Code(self.allocs.allocate_cow(cow));
2494 self.tree[close].item.start = self.tree[open].item.start + 1;
2495 } else {
2496 self.tree[open].item.body = ItemBody::Code(self.allocs.allocate_cow(cow));
2497 self.tree[open].item.end = self.tree[close].item.end;
2498 self.tree[open].next = self.tree[close].next;
2499 }
2500
2501 if !self.mdx_errors.is_empty() {
2504 self.mdx_errors
2505 .retain(|(offset, _)| *offset < span_start || *offset >= span_end);
2506 }
2507 }
2508
2509 fn scan_inline_html(&mut self, bytes: &[u8], ix: usize) -> Option<(Vec<u8>, usize)> {
2513 let c = *bytes.get(ix)?;
2514 if c == b'!' {
2515 Some((
2516 vec![],
2517 scan_inline_html_comment(bytes, ix + 1, &mut self.html_scan_guard)?,
2518 ))
2519 } else if c == b'?' {
2520 Some((
2521 vec![],
2522 scan_inline_html_processing(bytes, ix + 1, &mut self.html_scan_guard)?,
2523 ))
2524 } else {
2525 let (span, i) = scan_html_block_inner(
2526 &bytes[(ix - 1)..],
2528 Some(&|bytes| skip_container_prefixes(&self.tree, bytes, self.options)),
2529 )?;
2530 Some((span, i + ix - 1))
2531 }
2532 }
2533}
2534
2535pub(crate) fn scan_containers(
2537 tree: &Tree<Item>,
2538 line_start: &mut LineStart<'_>,
2539 options: Options,
2540) -> usize {
2541 let mut i = 0;
2542 for &node_ix in tree.walk_spine() {
2543 match tree[node_ix].item.body {
2544 ItemBody::BlockQuote(..) => {
2545 let save = line_start.clone();
2546 if options.contains(Options::ENABLE_MDX) {
2551 line_start.scan_all_space();
2552 } else {
2553 let _ = line_start.scan_space(3);
2554 }
2555 if !line_start.scan_blockquote_marker() {
2556 *line_start = save;
2557 break;
2558 }
2559 }
2560 ItemBody::ListItem(indent, _) => {
2561 let save = line_start.clone();
2562 if !line_start.scan_space(indent) && !line_start.is_at_eol() {
2563 *line_start = save;
2564 break;
2565 }
2566 }
2567 ItemBody::DefinitionListDefinition(indent, _) => {
2568 let save = line_start.clone();
2569 if !line_start.scan_space(indent) && !line_start.is_at_eol() {
2570 *line_start = save;
2571 break;
2572 }
2573 }
2574 ItemBody::FootnoteDefinition(..) if options.contains(Options::ENABLE_FOOTNOTES) => {
2575 let save = line_start.clone();
2576 if !line_start.scan_space(4) && !line_start.is_at_eol() {
2577 *line_start = save;
2578 break;
2579 }
2580 }
2581 _ => (),
2582 }
2583 i += 1;
2584 }
2585 i
2586}
2587
2588fn strip_span_padding<'input>(buf: Option<String>, spanned_text: &'input str) -> CowStr<'input> {
2595 let s = buf.as_deref().unwrap_or(spanned_text);
2596 let lead = if s.starts_with("\r\n") {
2597 2
2598 } else {
2599 usize::from(matches!(s.as_bytes().first(), Some(b' ' | b'\n' | b'\r')))
2600 };
2601 let trail = if s.ends_with("\r\n") {
2602 2
2603 } else {
2604 usize::from(matches!(s.as_bytes().last(), Some(b' ' | b'\n' | b'\r')))
2605 };
2606 let all_spaces = s.bytes().all(|b| matches!(b, b' ' | b'\n' | b'\r'));
2607
2608 if !all_spaces && lead > 0 && trail > 0 {
2609 if let Some(mut buf) = buf {
2610 if !buf.is_empty() {
2611 buf.truncate(buf.len() - trail);
2612 buf.replace_range(..lead, "");
2613 }
2614 buf.into()
2615 } else {
2616 spanned_text[lead..(spanned_text.len() - trail).max(lead)].into()
2617 }
2618 } else if let Some(buf) = buf {
2619 buf.into()
2620 } else {
2621 spanned_text.into()
2622 }
2623}
2624
2625pub(crate) fn skip_container_prefixes(tree: &Tree<Item>, bytes: &[u8], options: Options) -> usize {
2626 let mut line_start = LineStart::new(bytes);
2627 let _ = scan_containers(tree, &mut line_start, options);
2628 line_start.bytes_scanned()
2629}
2630
2631fn skip_container_prefixes_with_remaining(
2638 tree: &Tree<Item>,
2639 bytes: &[u8],
2640 options: Options,
2641) -> (usize, usize) {
2642 let mut line_start = LineStart::new(bytes);
2643 let _ = scan_containers(tree, &mut line_start, options);
2644 (line_start.bytes_scanned(), line_start.remaining_space())
2645}
2646
2647impl Tree<Item> {
2648 pub(crate) fn append_text(&mut self, start: usize, end: usize, backslash_escaped: bool) {
2649 if end > start {
2650 if let Some(ix) = self.cur()
2651 && matches!(self[ix].item.body, ItemBody::Text { .. })
2652 && self[ix].item.end == start
2653 {
2654 self[ix].item.end = end;
2655 return;
2656 }
2657 self.append(Item {
2658 start,
2659 end,
2660 body: ItemBody::Text { backslash_escaped },
2661 });
2662 }
2663 }
2664 pub(crate) fn is_in_table(&self) -> bool {
2671 fn might_be_in_table(item: &Item) -> bool {
2672 item.body.is_inline()
2673 || matches!(item.body, |ItemBody::TableHead| ItemBody::TableRow
2674 | ItemBody::TableCell)
2675 }
2676 for &ix in self.walk_spine().rev() {
2677 if matches!(self[ix].item.body, ItemBody::Table(_)) {
2678 return true;
2679 }
2680 if !might_be_in_table(&self[ix].item) {
2681 return false;
2682 }
2683 }
2684 false
2685 }
2686}
2687
2688#[derive(Copy, Clone, Debug)]
2689struct InlineEl {
2690 start: TreeIndex,
2692 count: usize,
2694 run_length: usize,
2696 c: u8,
2698 both: bool,
2700}
2701
2702#[derive(Debug, Clone, Default)]
2703struct InlineStack {
2704 stack: Vec<InlineEl>,
2705 lower_bounds: [usize; 10],
2710}
2711
2712impl InlineStack {
2713 const UNDERSCORE_NOT_BOTH: usize = 0;
2717 const ASTERISK_NOT_BOTH: usize = 1;
2718 const ASTERISK_BASE: usize = 2;
2719 const TILDES: usize = 5;
2720 const UNDERSCORE_BASE: usize = 6;
2721 const CIRCUMFLEXES: usize = 9;
2722
2723 fn pop_all(&mut self, tree: &mut Tree<Item>) {
2724 for el in self.stack.drain(..) {
2725 for i in 0..el.count {
2726 tree[el.start + i].item.body = ItemBody::Text {
2727 backslash_escaped: false,
2728 };
2729 }
2730 }
2731 self.lower_bounds = [0; 10];
2732 }
2733
2734 fn get_lowerbound(&self, c: u8, count: usize, both: bool) -> usize {
2735 if c == b'_' {
2736 let mod3_lower = self.lower_bounds[InlineStack::UNDERSCORE_BASE + count % 3];
2737 if both {
2738 mod3_lower
2739 } else {
2740 min(
2741 mod3_lower,
2742 self.lower_bounds[InlineStack::UNDERSCORE_NOT_BOTH],
2743 )
2744 }
2745 } else if c == b'*' {
2746 let mod3_lower = self.lower_bounds[InlineStack::ASTERISK_BASE + count % 3];
2747 if both {
2748 mod3_lower
2749 } else {
2750 min(
2751 mod3_lower,
2752 self.lower_bounds[InlineStack::ASTERISK_NOT_BOTH],
2753 )
2754 }
2755 } else if c == b'^' {
2756 self.lower_bounds[InlineStack::CIRCUMFLEXES]
2757 } else {
2758 self.lower_bounds[InlineStack::TILDES]
2759 }
2760 }
2761
2762 fn set_lowerbound(&mut self, c: u8, count: usize, both: bool, new_bound: usize) {
2763 if c == b'_' {
2764 if both {
2765 self.lower_bounds[InlineStack::UNDERSCORE_BASE + count % 3] = new_bound;
2766 } else {
2767 self.lower_bounds[InlineStack::UNDERSCORE_NOT_BOTH] = new_bound;
2768 }
2769 } else if c == b'*' {
2770 self.lower_bounds[InlineStack::ASTERISK_BASE + count % 3] = new_bound;
2771 if !both {
2772 self.lower_bounds[InlineStack::ASTERISK_NOT_BOTH] = new_bound;
2773 }
2774 } else if c == b'^' {
2775 self.lower_bounds[InlineStack::CIRCUMFLEXES] = new_bound;
2776 } else {
2777 self.lower_bounds[InlineStack::TILDES] = new_bound;
2778 }
2779 }
2780
2781 fn truncate(&mut self, new_bound: usize) {
2782 self.stack.truncate(new_bound);
2783 for lower_bound in &mut self.lower_bounds {
2784 if *lower_bound > new_bound {
2785 *lower_bound = new_bound;
2786 }
2787 }
2788 }
2789
2790 fn find_match(
2803 &mut self,
2804 tree: &mut Tree<Item>,
2805 c: u8,
2806 run_length: usize,
2807 current_count: usize,
2808 both: bool,
2809 ) -> Option<InlineEl> {
2810 let lowerbound = min(
2820 self.stack.len(),
2821 self.get_lowerbound(c, current_count, both),
2822 );
2823 let res = self.stack[lowerbound..]
2824 .iter()
2825 .cloned()
2826 .enumerate()
2827 .rfind(|(_, el)| {
2828 if (c == b'~' || c == b'^') && run_length != el.run_length {
2829 return false;
2830 }
2831 el.c == c
2836 && (!both && !el.both
2837 || !(current_count + el.count).is_multiple_of(3)
2838 || current_count.is_multiple_of(3))
2839 });
2840
2841 if let Some((matching_ix, matching_el)) = res {
2842 let matching_ix = matching_ix + lowerbound;
2843 for el in &self.stack[(matching_ix + 1)..] {
2844 for i in 0..el.count {
2845 tree[el.start + i].item.body = ItemBody::Text {
2846 backslash_escaped: false,
2847 };
2848 }
2849 }
2850 self.truncate(matching_ix);
2851 Some(matching_el)
2852 } else {
2853 if c != b'~' && c != b'^' {
2863 self.set_lowerbound(c, current_count, both, self.stack.len());
2864 }
2865 None
2866 }
2867 }
2868
2869 fn trim_lower_bound(&mut self, ix: usize) {
2870 self.lower_bounds[ix] = self.lower_bounds[ix].min(self.stack.len());
2871 }
2872
2873 fn push(&mut self, el: InlineEl) {
2874 if el.c == b'~' {
2875 self.trim_lower_bound(InlineStack::TILDES);
2876 } else if el.c == b'^' {
2877 self.trim_lower_bound(InlineStack::CIRCUMFLEXES);
2878 }
2879 self.stack.push(el)
2880 }
2881}
2882
2883#[derive(Debug, Clone)]
2884enum RefScan<'a> {
2885 LinkLabel(CowStr<'a>, usize),
2887 Collapsed(Option<TreeIndex>),
2889 UnexpectedFootnote,
2890 Failed,
2891 FailedInvalidLabel,
2896}
2897
2898fn scan_nodes_to_ix(
2901 tree: &Tree<Item>,
2902 mut node: Option<TreeIndex>,
2903 ix: usize,
2904) -> Option<TreeIndex> {
2905 while let Some(node_ix) = node {
2906 let item = tree[node_ix].item;
2907 if item.end <= ix && item.start < ix {
2910 node = tree[node_ix].next;
2911 } else {
2912 break;
2913 }
2914 }
2915 node
2916}
2917
2918fn scan_link_label<'text>(
2921 tree: &Tree<Item>,
2922 text: &'text str,
2923 options: Options,
2924) -> Option<(usize, ReferenceLabel<'text>)> {
2925 let bytes = text.as_bytes();
2926 if bytes.len() < 2 || bytes[0] != b'[' {
2927 return None;
2928 }
2929 let linebreak_handler = |bytes: &[u8]| Some(skip_container_prefixes(tree, bytes, options));
2930 if options.contains(Options::ENABLE_FOOTNOTES)
2931 && b'^' == bytes[1]
2932 && bytes.get(2) != Some(&b']')
2933 {
2934 let linebreak_handler: &dyn Fn(&[u8]) -> Option<usize> = &|_| None;
2936 if let Some((byte_index, cow)) =
2937 scan_link_label_rest(&text[2..], linebreak_handler, tree.is_in_table())
2938 {
2939 return Some((byte_index + 2, ReferenceLabel::Footnote(cow)));
2940 }
2941 }
2942 let (byte_index, cow) =
2943 scan_link_label_rest(&text[1..], &linebreak_handler, tree.is_in_table())?;
2944 Some((byte_index + 1, ReferenceLabel::Link(cow)))
2945}
2946
2947fn scan_reference<'b>(
2948 tree: &Tree<Item>,
2949 text: &'b str,
2950 cur: Option<TreeIndex>,
2951 options: Options,
2952) -> RefScan<'b> {
2953 let cur_ix = match cur {
2954 None => return RefScan::Failed,
2955 Some(cur_ix) => cur_ix,
2956 };
2957 let start = tree[cur_ix].item.start;
2958 let tail = &text.as_bytes()[start..];
2959
2960 if tail.first() == Some(&b'[') && start > 0 {
2967 let src = text.as_bytes();
2968 let mut backslashes = 0usize;
2969 let mut j = start;
2970 while j > 0 && src[j - 1] == b'\\' {
2971 backslashes += 1;
2972 j -= 1;
2973 }
2974 if backslashes % 2 == 1 {
2975 return RefScan::Failed;
2976 }
2977 }
2978
2979 if tail.starts_with(b"[]") {
2980 let Some(closing_node) = tree[cur_ix].next else {
2985 return RefScan::Failed;
2986 };
2987 RefScan::Collapsed(tree[closing_node].next)
2988 } else {
2989 let label = scan_link_label(tree, &text[start..], options);
2990 match label {
2991 Some((ix, ReferenceLabel::Link(label))) => RefScan::LinkLabel(label, start + ix),
2992 Some((_ix, ReferenceLabel::Footnote(_label))) => RefScan::UnexpectedFootnote,
2993 None => {
2994 if tail.starts_with(b"[") {
2999 RefScan::FailedInvalidLabel
3000 } else {
3001 RefScan::Failed
3002 }
3003 }
3004 }
3005 }
3006}
3007
3008#[derive(Clone, Default)]
3009struct LinkStack {
3010 inner: Vec<LinkStackEl>,
3011 disabled_ix: usize,
3012}
3013
3014impl LinkStack {
3015 fn is_empty(&self) -> bool {
3016 self.inner.is_empty()
3017 }
3018
3019 fn push(&mut self, el: LinkStackEl) {
3020 self.inner.push(el);
3021 }
3022
3023 fn pop(&mut self) -> Option<LinkStackEl> {
3024 let el = self.inner.pop();
3025 self.disabled_ix = core::cmp::min(self.disabled_ix, self.inner.len());
3026 el
3027 }
3028
3029 fn clear(&mut self) {
3030 self.inner.clear();
3031 self.disabled_ix = 0;
3032 }
3033
3034 fn disable_all_links(&mut self) {
3035 for el in &mut self.inner[self.disabled_ix..] {
3036 if el.ty == LinkStackTy::Link {
3037 el.ty = LinkStackTy::Disabled;
3038 }
3039 }
3040 self.disabled_ix = self.inner.len();
3041 }
3042}
3043
3044#[derive(Clone, Debug)]
3045struct LinkStackEl {
3046 node: TreeIndex,
3047 ty: LinkStackTy,
3048}
3049
3050#[derive(PartialEq, Clone, Debug)]
3051enum LinkStackTy {
3052 Link,
3053 Image,
3054 Disabled,
3055}
3056
3057#[derive(Clone, Debug)]
3059pub struct LinkDef<'a> {
3060 pub dest: CowStr<'a>,
3061 pub title: Option<CowStr<'a>>,
3062 pub span: Range<usize>,
3063}
3064
3065impl<'a> LinkDef<'a> {
3066 pub fn into_static(self) -> LinkDef<'static> {
3067 LinkDef {
3068 dest: self.dest.into_static(),
3069 title: self.title.map(|s| s.into_static()),
3070 span: self.span,
3071 }
3072 }
3073}
3074
3075#[derive(Clone, Debug)]
3077pub struct FootnoteDef {
3078 pub use_count: usize,
3079}
3080
3081struct CodeDelims {
3084 inner: FxHashMap<usize, VecDeque<TreeIndex>>,
3085 seen_first: bool,
3086}
3087
3088impl CodeDelims {
3089 fn new() -> Self {
3090 Self {
3091 inner: Default::default(),
3092 seen_first: false,
3093 }
3094 }
3095
3096 fn insert(&mut self, count: usize, ix: TreeIndex) {
3097 if self.seen_first {
3098 self.inner.entry(count).or_default().push_back(ix);
3099 } else {
3100 self.seen_first = true;
3103 }
3104 }
3105
3106 fn is_populated(&self) -> bool {
3107 !self.inner.is_empty()
3108 }
3109
3110 fn find(&mut self, open_ix: TreeIndex, count: usize) -> Option<TreeIndex> {
3111 while let Some(ix) = self.inner.get_mut(&count)?.pop_front() {
3112 if ix > open_ix {
3113 return Some(ix);
3114 }
3115 }
3116 None
3117 }
3118
3119 fn clear(&mut self) {
3120 self.inner.clear();
3121 self.seen_first = false;
3122 }
3123}
3124
3125struct MathDelims {
3128 inner: FxHashMap<u8, VecDeque<(TreeIndex, bool, bool)>>,
3129}
3130
3131impl MathDelims {
3132 fn new() -> Self {
3133 Self {
3134 inner: Default::default(),
3135 }
3136 }
3137
3138 fn clear(&mut self) {
3139 self.inner.clear();
3140 }
3141}
3142
3143#[derive(Copy, Clone, PartialEq, Eq, Debug)]
3144pub(crate) struct LinkIndex(usize);
3145
3146#[derive(Copy, Clone, PartialEq, Eq, Debug)]
3147pub(crate) struct CowIndex(usize);
3148
3149#[derive(Copy, Clone, PartialEq, Eq, Debug)]
3150pub(crate) struct AlignmentIndex(usize);
3151
3152#[derive(Copy, Clone, PartialEq, Eq, Debug)]
3153pub(crate) struct HeadingIndex(NonZeroUsize);
3154
3155#[cfg(feature = "mdx")]
3156#[derive(Copy, Clone, PartialEq, Eq, Debug)]
3157pub(crate) struct JsxElementIndex(usize);
3158
3159#[derive(Copy, Clone, PartialEq, Eq, Debug)]
3160pub(crate) struct DirectiveIndex(usize);
3161
3162#[derive(Copy, Clone, PartialEq, Eq, Debug)]
3163pub(crate) struct AutolinkCandidateIndex(usize);
3164
3165#[derive(Copy, Clone, Debug)]
3168pub(crate) struct AutolinkCandidate {
3169 pub start: usize,
3172 pub end: usize,
3173 pub link: LinkIndex,
3174}
3175
3176#[cfg(feature = "mdx")]
3178#[derive(Debug, Clone)]
3179pub(crate) enum JsxAttr<'a> {
3180 Boolean(CowStr<'a>),
3181 Literal(CowStr<'a>, CowStr<'a>),
3182 Expression(CowStr<'a>, CowStr<'a>, usize, usize),
3186 Spread(CowStr<'a>, usize, usize),
3189}
3190
3191#[cfg(feature = "mdx")]
3192impl<'a> JsxAttr<'a> {
3193 pub fn into_static(self) -> JsxAttr<'static> {
3194 match self {
3195 JsxAttr::Boolean(n) => JsxAttr::Boolean(n.into_static()),
3196 JsxAttr::Literal(n, v) => JsxAttr::Literal(n.into_static(), v.into_static()),
3197 JsxAttr::Expression(n, v, start, end) => {
3198 JsxAttr::Expression(n.into_static(), v.into_static(), start, end)
3199 }
3200 JsxAttr::Spread(v, start, end) => JsxAttr::Spread(v.into_static(), start, end),
3201 }
3202 }
3203}
3204
3205#[cfg(feature = "mdx")]
3207#[derive(Debug, Clone)]
3208pub(crate) struct JsxElementData<'a> {
3209 pub name: CowStr<'a>,
3210 pub attrs: Vec<JsxAttr<'a>>,
3211 pub raw: CowStr<'a>,
3212 pub is_closing: bool,
3213 pub is_self_closing: bool,
3214}
3215
3216#[cfg(feature = "mdx")]
3217impl<'a> JsxElementData<'a> {
3218 pub fn into_static(self) -> JsxElementData<'static> {
3219 JsxElementData {
3220 name: self.name.into_static(),
3221 attrs: self.attrs.into_iter().map(|a| a.into_static()).collect(),
3222 raw: self.raw.into_static(),
3223 is_closing: self.is_closing,
3224 is_self_closing: self.is_self_closing,
3225 }
3226 }
3227}
3228
3229#[derive(Debug, Clone)]
3230pub(crate) struct DirectiveAttrData<'a> {
3231 pub name: CowStr<'a>,
3232 pub attributes: Vec<(CowStr<'a>, CowStr<'a>)>,
3233 pub label_start: usize,
3234 pub label_end: usize,
3235 pub initial_size: u8,
3241}
3242
3243#[derive(Clone)]
3244pub(crate) struct Allocations<'a> {
3245 pub refdefs: RefDefs<'a>,
3246 pub refdefs_all: Vec<(LinkLabel<'a>, LinkDef<'a>)>,
3251 pub footdefs: FootnoteDefs<'a>,
3252 links: Vec<(LinkType, CowStr<'a>, CowStr<'a>, CowStr<'a>)>,
3253 cows: Vec<CowStr<'a>>,
3254 alignments: Vec<Vec<Alignment>>,
3255 headings: Vec<HeadingAttributes<'a>>,
3256 #[cfg(feature = "mdx")]
3257 jsx_elements: Vec<JsxElementData<'a>>,
3258 directives: Vec<DirectiveAttrData<'a>>,
3259 autolink_candidates: Vec<AutolinkCandidate>,
3260}
3261
3262#[derive(Clone)]
3264pub(crate) struct HeadingAttributes<'a> {
3265 pub id: Option<CowStr<'a>>,
3266 pub classes: Vec<CowStr<'a>>,
3267 pub attrs: Vec<(CowStr<'a>, Option<CowStr<'a>>)>,
3268}
3269
3270#[derive(Clone, Default, Debug)]
3272pub struct RefDefs<'input>(pub(crate) FxHashMap<LinkLabel<'input>, LinkDef<'input>>);
3273
3274#[derive(Clone, Default, Debug)]
3276pub struct FootnoteDefs<'input>(pub(crate) FxHashMap<FootnoteLabel<'input>, FootnoteDef>);
3277
3278impl<'input, 'b, 's> RefDefs<'input>
3279where
3280 's: 'b,
3281{
3282 pub fn get(&'s self, key: &'b str) -> Option<&'b LinkDef<'input>> {
3284 self.0.get(&UniCase::new(key.into()))
3285 }
3286
3287 pub fn iter(
3289 &'s self,
3290 ) -> impl Iterator<Item = (&'s str, &'s LinkDef<'input>)> + use<'s, 'input> {
3291 self.0.iter().map(|(k, v)| (k.as_ref(), v))
3292 }
3293}
3294
3295impl<'input, 'b, 's> FootnoteDefs<'input>
3296where
3297 's: 'b,
3298{
3299 pub fn contains(&'s self, key: &'b str) -> bool {
3301 self.0.contains_key(&UniCase::new(key.into()))
3302 }
3303 pub fn get_mut(&'s mut self, key: CowStr<'input>) -> Option<&'s mut FootnoteDef> {
3305 self.0.get_mut(&UniCase::new(key))
3306 }
3307}
3308
3309impl<'a> Allocations<'a> {
3310 pub fn new() -> Self {
3311 Self {
3312 refdefs: RefDefs::default(),
3313 refdefs_all: Vec::new(),
3314 footdefs: FootnoteDefs::default(),
3315 links: Vec::with_capacity(128),
3316 cows: Vec::new(),
3317 alignments: Vec::new(),
3318 headings: Vec::new(),
3319 #[cfg(feature = "mdx")]
3320 jsx_elements: Vec::new(),
3321 directives: Vec::new(),
3322 autolink_candidates: Vec::new(),
3323 }
3324 }
3325
3326 pub fn allocate_autolink_candidate(
3327 &mut self,
3328 candidate: AutolinkCandidate,
3329 ) -> AutolinkCandidateIndex {
3330 let ix = self.autolink_candidates.len();
3331 self.autolink_candidates.push(candidate);
3332 AutolinkCandidateIndex(ix)
3333 }
3334
3335 pub fn allocate_cow(&mut self, cow: CowStr<'a>) -> CowIndex {
3336 let ix = self.cows.len();
3337 self.cows.push(cow);
3338 CowIndex(ix)
3339 }
3340
3341 pub fn allocate_link(
3342 &mut self,
3343 ty: LinkType,
3344 url: CowStr<'a>,
3345 title: CowStr<'a>,
3346 id: CowStr<'a>,
3347 ) -> LinkIndex {
3348 let ix = self.links.len();
3349 self.links.push((ty, url, title, id));
3350 LinkIndex(ix)
3351 }
3352
3353 pub fn allocate_alignment(&mut self, alignment: Vec<Alignment>) -> AlignmentIndex {
3354 let ix = self.alignments.len();
3355 self.alignments.push(alignment);
3356 AlignmentIndex(ix)
3357 }
3358
3359 pub fn allocate_heading(&mut self, attrs: HeadingAttributes<'a>) -> HeadingIndex {
3360 let ix = self.headings.len();
3361 self.headings.push(attrs);
3362 let ix_nonzero = NonZeroUsize::new(ix.wrapping_add(1)).expect("too many headings");
3365 HeadingIndex(ix_nonzero)
3366 }
3367
3368 pub fn take_cow(&mut self, ix: CowIndex) -> CowStr<'a> {
3369 core::mem::replace(&mut self.cows[ix.0], "".into())
3370 }
3371
3372 pub fn take_link(&mut self, ix: LinkIndex) -> (LinkType, CowStr<'a>, CowStr<'a>, CowStr<'a>) {
3373 let default_link = (LinkType::ShortcutUnknown, "".into(), "".into(), "".into());
3374 core::mem::replace(&mut self.links[ix.0], default_link)
3375 }
3376
3377 pub fn take_alignment(&mut self, ix: AlignmentIndex) -> Vec<Alignment> {
3378 core::mem::take(&mut self.alignments[ix.0])
3379 }
3380
3381 #[cfg(feature = "mdx")]
3382 pub fn allocate_jsx_element(&mut self, data: JsxElementData<'a>) -> JsxElementIndex {
3383 let ix = self.jsx_elements.len();
3384 self.jsx_elements.push(data);
3385 JsxElementIndex(ix)
3386 }
3387
3388 pub fn allocate_directive(&mut self, data: DirectiveAttrData<'a>) -> DirectiveIndex {
3389 let ix = self.directives.len();
3390 self.directives.push(data);
3391 DirectiveIndex(ix)
3392 }
3393
3394 pub fn take_directive(&mut self, ix: DirectiveIndex) -> DirectiveAttrData<'a> {
3395 core::mem::replace(
3396 &mut self.directives[ix.0],
3397 DirectiveAttrData {
3398 name: "".into(),
3399 attributes: Vec::new(),
3400 label_start: 0,
3401 label_end: 0,
3402 initial_size: 0,
3403 },
3404 )
3405 }
3406
3407 pub fn directive_ref(&self, ix: DirectiveIndex) -> &DirectiveAttrData<'a> {
3408 &self.directives[ix.0]
3409 }
3410
3411 #[cfg(feature = "mdx")]
3412 pub fn take_jsx_element(&mut self, ix: JsxElementIndex) -> JsxElementData<'a> {
3413 core::mem::replace(
3414 &mut self.jsx_elements[ix.0],
3415 JsxElementData {
3416 name: "".into(),
3417 attrs: Vec::new(),
3418 raw: "".into(),
3419 is_closing: false,
3420 is_self_closing: false,
3421 },
3422 )
3423 }
3424}
3425
3426impl<'a> Index<CowIndex> for Allocations<'a> {
3427 type Output = CowStr<'a>;
3428
3429 fn index(&self, ix: CowIndex) -> &Self::Output {
3430 self.cows.index(ix.0)
3431 }
3432}
3433
3434impl<'a> Index<LinkIndex> for Allocations<'a> {
3435 type Output = (LinkType, CowStr<'a>, CowStr<'a>, CowStr<'a>);
3436
3437 fn index(&self, ix: LinkIndex) -> &Self::Output {
3438 self.links.index(ix.0)
3439 }
3440}
3441
3442impl<'a> Index<AutolinkCandidateIndex> for Allocations<'a> {
3443 type Output = AutolinkCandidate;
3444
3445 fn index(&self, ix: AutolinkCandidateIndex) -> &Self::Output {
3446 self.autolink_candidates.index(ix.0)
3447 }
3448}
3449
3450impl<'a> Index<AlignmentIndex> for Allocations<'a> {
3451 type Output = Vec<Alignment>;
3452
3453 fn index(&self, ix: AlignmentIndex) -> &Self::Output {
3454 self.alignments.index(ix.0)
3455 }
3456}
3457
3458impl<'a> Index<HeadingIndex> for Allocations<'a> {
3459 type Output = HeadingAttributes<'a>;
3460
3461 fn index(&self, ix: HeadingIndex) -> &Self::Output {
3462 self.headings.index(ix.0.get() - 1)
3463 }
3464}
3465
3466#[derive(Clone, Default)]
3472pub(crate) struct HtmlScanGuard {
3473 pub cdata: usize,
3474 pub processing: usize,
3475 pub declaration: usize,
3476 pub comment: usize,
3477}
3478
3479pub trait ParserCallbacks<'input> {
3483 fn handle_broken_link(
3491 &mut self,
3492 #[allow(unused_variables)] link: BrokenLink<'input>,
3493 ) -> Option<(CowStr<'input>, CowStr<'input>)> {
3494 None
3495 }
3496}
3497
3498#[allow(missing_debug_implementations)]
3502pub struct BrokenLinkCallback<F>(Option<F>);
3503
3504impl<'input, F> ParserCallbacks<'input> for BrokenLinkCallback<F>
3505where
3506 F: FnMut(BrokenLink<'input>) -> Option<(CowStr<'input>, CowStr<'input>)>,
3507{
3508 fn handle_broken_link(
3509 &mut self,
3510 link: BrokenLink<'input>,
3511 ) -> Option<(CowStr<'input>, CowStr<'input>)> {
3512 self.0.as_mut().and_then(|cb| cb(link))
3513 }
3514}
3515
3516impl<'input> ParserCallbacks<'input> for Box<dyn ParserCallbacks<'input>> {
3517 fn handle_broken_link(
3518 &mut self,
3519 link: BrokenLink<'input>,
3520 ) -> Option<(CowStr<'input>, CowStr<'input>)> {
3521 (**self).handle_broken_link(link)
3522 }
3523}
3524
3525#[allow(missing_debug_implementations)]
3529pub struct DefaultParserCallbacks;
3530
3531impl<'input> ParserCallbacks<'input> for DefaultParserCallbacks {}
3532
3533#[derive(Debug)]
3541pub struct OffsetIter<'a, CB> {
3542 parser: Parser<'a, CB>,
3543}
3544
3545impl<'a, CB: ParserCallbacks<'a>> OffsetIter<'a, CB> {
3546 pub fn reference_definitions(&self) -> &RefDefs<'_> {
3548 self.parser.reference_definitions()
3549 }
3550
3551 pub fn mdx_errors(&self) -> &[(usize, String)] {
3553 self.parser.mdx_errors()
3554 }
3555}
3556
3557impl<'a, CB: ParserCallbacks<'a>> Iterator for OffsetIter<'a, CB> {
3558 type Item = (Event<'a>, Range<usize>);
3559
3560 fn next(&mut self) -> Option<Self::Item> {
3561 self.parser
3562 .inner
3563 .next_event_range(&mut self.parser.callbacks)
3564 }
3565}
3566
3567impl<'a, CB: ParserCallbacks<'a>> Iterator for Parser<'a, CB> {
3568 type Item = Event<'a>;
3569
3570 fn next(&mut self) -> Option<Event<'a>> {
3571 self.inner
3572 .next_event_range(&mut self.callbacks)
3573 .map(|(event, _range)| event)
3574 }
3575}
3576
3577impl<'a, CB: ParserCallbacks<'a>> FusedIterator for Parser<'a, CB> {}
3578
3579impl<'input> ParserInner<'input> {
3580 fn next_event_range(
3581 &mut self,
3582 callbacks: &mut dyn ParserCallbacks<'input>,
3583 ) -> Option<(Event<'input>, Range<usize>)> {
3584 match self.tree.cur() {
3585 None => {
3586 let ix = self.tree.pop()?;
3587 let ix = if matches!(self.tree[ix].item.body, ItemBody::TightParagraph) {
3588 self.tree.next_sibling(ix);
3590 return self.next_event_range(callbacks);
3591 } else {
3592 ix
3593 };
3594 let tag_end = body_to_tag_end(&self.tree[ix].item.body);
3595 self.tree.next_sibling(ix);
3596 let span = self.tree[ix].item.start..self.tree[ix].item.end;
3597 debug_assert!(span.start <= span.end);
3598 Some((Event::End(tag_end), span))
3599 }
3600 Some(cur_ix) => {
3601 let cur_ix = if matches!(self.tree[cur_ix].item.body, ItemBody::TightParagraph) {
3602 self.tree.push();
3604 self.tree.cur().unwrap()
3605 } else {
3606 cur_ix
3607 };
3608 if self.tree[cur_ix].item.body.is_maybe_inline() {
3609 self.handle_inline(callbacks);
3610 }
3611
3612 let node = self.tree[cur_ix];
3613 let item = node.item;
3614 let event = item_to_event(item, self.text, &mut self.allocs);
3615 if let Event::Start(..) = event {
3616 self.tree.push();
3617 } else {
3618 self.tree.next_sibling(cur_ix);
3619 }
3620 debug_assert!(item.start <= item.end);
3621 Some((event, item.start..item.end))
3622 }
3623 }
3624 }
3625}
3626
3627fn body_to_tag_end(body: &ItemBody) -> TagEnd {
3628 match *body {
3629 ItemBody::Paragraph => TagEnd::Paragraph,
3630 ItemBody::Emphasis => TagEnd::Emphasis,
3631 ItemBody::Superscript => TagEnd::Superscript,
3632 ItemBody::Subscript => TagEnd::Subscript,
3633 ItemBody::Strong => TagEnd::Strong,
3634 ItemBody::Strikethrough => TagEnd::Strikethrough,
3635 ItemBody::Link(..) => TagEnd::Link,
3636 ItemBody::Image(..) => TagEnd::Image,
3637 ItemBody::Heading(level, _) => TagEnd::Heading(level),
3638 ItemBody::IndentCodeBlock(..) | ItemBody::FencedCodeBlock(..) | ItemBody::MathBlock(..) => {
3639 TagEnd::CodeBlock
3640 }
3641 ItemBody::ContainerDirective(..) => TagEnd::Directive(DirectiveKind::Container),
3642 ItemBody::LeafDirective(..) => TagEnd::Directive(DirectiveKind::Leaf),
3643 ItemBody::TextDirective(..) => TagEnd::Directive(DirectiveKind::Text),
3644 ItemBody::BlockQuote(kind) => TagEnd::BlockQuote(kind),
3645 ItemBody::HtmlBlock(_) => TagEnd::HtmlBlock,
3646 ItemBody::List(_, c, _) => {
3647 let is_ordered = c == b'.' || c == b')';
3648 TagEnd::List(is_ordered)
3649 }
3650 ItemBody::ListItem(_, _) => TagEnd::Item,
3651 ItemBody::TableHead => TagEnd::TableHead,
3652 ItemBody::TableCell => TagEnd::TableCell,
3653 ItemBody::TableRow => TagEnd::TableRow,
3654 ItemBody::Table(..) => TagEnd::Table,
3655 ItemBody::FootnoteDefinition(..) => TagEnd::FootnoteDefinition,
3656 ItemBody::MetadataBlock(kind) => TagEnd::MetadataBlock(kind),
3657 ItemBody::DefinitionList(_) => TagEnd::DefinitionList,
3658 ItemBody::DefinitionListTitle => TagEnd::DefinitionListTitle,
3659 ItemBody::DefinitionListDefinition(..) => TagEnd::DefinitionListDefinition,
3660 #[cfg(feature = "mdx")]
3661 ItemBody::MdxJsxFlowElement(..) => TagEnd::MdxJsxFlowElement,
3662 #[cfg(feature = "mdx")]
3663 ItemBody::MdxJsxTextElement(..) => TagEnd::MdxJsxTextElement,
3664 _ => panic!("unexpected item body {:?}", body),
3665 }
3666}
3667
3668fn item_to_event<'a>(item: Item, text: &'a str, allocs: &mut Allocations<'a>) -> Event<'a> {
3669 let tag = match item.body {
3670 ItemBody::Text { .. } => return Event::Text(text[item.start..item.end].into()),
3671 ItemBody::Code(cow_ix) => return Event::Code(allocs.take_cow(cow_ix)),
3672 ItemBody::SynthesizeText(cow_ix) => return Event::Text(allocs.take_cow(cow_ix)),
3673 ItemBody::SynthesizeChar(c) => return Event::Text(c.into()),
3674 ItemBody::HtmlBlock(_) => Tag::HtmlBlock,
3675 ItemBody::Html => return Event::Html(text[item.start..item.end].into()),
3676 ItemBody::InlineHtml => return Event::InlineHtml(text[item.start..item.end].into()),
3677 ItemBody::OwnedInlineHtml(cow_ix) => return Event::InlineHtml(allocs.take_cow(cow_ix)),
3678 ItemBody::SoftBreak => return Event::SoftBreak,
3679 ItemBody::HardBreak(_) => return Event::HardBreak,
3680 ItemBody::FootnoteReference(cow_ix) => {
3681 return Event::FootnoteReference(allocs.take_cow(cow_ix));
3682 }
3683 ItemBody::TaskListMarker(checked) => return Event::TaskListMarker(checked),
3684 ItemBody::Rule => return Event::Rule,
3685 ItemBody::Paragraph => Tag::Paragraph,
3686 ItemBody::Emphasis => Tag::Emphasis,
3687 ItemBody::Superscript => Tag::Superscript,
3688 ItemBody::Subscript => Tag::Subscript,
3689 ItemBody::Strong => Tag::Strong,
3690 ItemBody::Strikethrough => Tag::Strikethrough,
3691 ItemBody::Link(link_ix) => {
3692 let (link_type, dest_url, title, id) = allocs.take_link(link_ix);
3693 Tag::Link {
3694 link_type,
3695 dest_url,
3696 title,
3697 id,
3698 }
3699 }
3700 ItemBody::Image(link_ix) => {
3701 let (link_type, dest_url, title, id) = allocs.take_link(link_ix);
3702 Tag::Image {
3703 link_type,
3704 dest_url,
3705 title,
3706 id,
3707 }
3708 }
3709 ItemBody::Heading(level, Some(heading_ix)) => {
3710 let HeadingAttributes { id, classes, attrs } = allocs.index(heading_ix);
3711 Tag::Heading {
3712 level,
3713 id: id.clone(),
3714 classes: classes.clone(),
3715 attrs: attrs.clone(),
3716 }
3717 }
3718 ItemBody::Heading(level, None) => Tag::Heading {
3719 level,
3720 id: None,
3721 classes: Vec::new(),
3722 attrs: Vec::new(),
3723 },
3724 ItemBody::MathBlock(cow_ix) => {
3725 Tag::CodeBlock(CodeBlockKind::Fenced(allocs.take_cow(cow_ix)))
3726 }
3727 ItemBody::FencedCodeBlock(cow_ix, _) => {
3728 Tag::CodeBlock(CodeBlockKind::Fenced(allocs.take_cow(cow_ix)))
3729 }
3730 ItemBody::IndentCodeBlock(..) => Tag::CodeBlock(CodeBlockKind::Indented),
3731 ItemBody::ContainerDirective(_, dir_ix)
3732 | ItemBody::LeafDirective(dir_ix)
3733 | ItemBody::TextDirective(dir_ix) => {
3734 let kind = match item.body {
3735 ItemBody::ContainerDirective(..) => DirectiveKind::Container,
3736 ItemBody::LeafDirective(..) => DirectiveKind::Leaf,
3737 _ => DirectiveKind::Text,
3738 };
3739 let dir = allocs.take_directive(dir_ix);
3740 Tag::Directive {
3741 kind,
3742 name: dir.name,
3743 attributes: dir.attributes,
3744 }
3745 }
3746 ItemBody::BlockQuote(kind) => Tag::BlockQuote(kind),
3747 ItemBody::List(is_tight, c, listitem_start) => {
3748 if c == b'.' || c == b')' {
3749 Tag::List(Some(listitem_start), is_tight)
3750 } else {
3751 Tag::List(None, is_tight)
3752 }
3753 }
3754 ItemBody::ListItem(_, _) => Tag::Item,
3755 ItemBody::TableHead => Tag::TableHead,
3756 ItemBody::TableCell => Tag::TableCell,
3757 ItemBody::TableRow => Tag::TableRow,
3758 ItemBody::Table(alignment_ix) => Tag::Table(allocs.take_alignment(alignment_ix)),
3759 ItemBody::FootnoteDefinition(cow_ix) => Tag::FootnoteDefinition(allocs.take_cow(cow_ix)),
3760 ItemBody::MetadataBlock(kind) => Tag::MetadataBlock(kind),
3761 ItemBody::Math(cow_ix, is_display) => {
3762 return if is_display {
3763 Event::DisplayMath(allocs.take_cow(cow_ix))
3764 } else {
3765 Event::InlineMath(allocs.take_cow(cow_ix))
3766 };
3767 }
3768 ItemBody::DefinitionList(_) => Tag::DefinitionList,
3769 ItemBody::DefinitionListTitle => Tag::DefinitionListTitle,
3770 ItemBody::DefinitionListDefinition(..) => Tag::DefinitionListDefinition,
3771 #[cfg(feature = "mdx")]
3772 ItemBody::MdxJsxFlowElement(jsx_ix) => {
3773 let jsx = allocs.take_jsx_element(jsx_ix);
3774 Tag::MdxJsxFlowElement(jsx.raw)
3775 }
3776 #[cfg(feature = "mdx")]
3777 ItemBody::MdxJsxTextElement(jsx_ix) => {
3778 let jsx = allocs.take_jsx_element(jsx_ix);
3779 Tag::MdxJsxTextElement(jsx.raw)
3780 }
3781 #[cfg(feature = "mdx")]
3782 ItemBody::MdxFlowExpression(cow_ix) => {
3783 return Event::MdxFlowExpression(allocs.take_cow(cow_ix));
3784 }
3785 #[cfg(feature = "mdx")]
3786 ItemBody::MdxTextExpression(cow_ix) => {
3787 return Event::MdxTextExpression(allocs.take_cow(cow_ix));
3788 }
3789 #[cfg(feature = "mdx")]
3790 ItemBody::MdxEsm(cow_ix) => return Event::MdxEsm(allocs.take_cow(cow_ix)),
3791 _ => panic!("unexpected item body {:?}", item.body),
3792 };
3793
3794 Event::Start(tag)
3795}
3796
3797#[cfg(test)]
3798mod test {
3799 use alloc::{borrow::ToOwned, string::ToString, vec::Vec};
3800
3801 use super::*;
3802 use crate::tree::Node;
3803
3804 fn parser_with_extensions(text: &str) -> Parser<'_> {
3807 let mut opts = Options::empty();
3808 opts.insert(Options::ENABLE_TABLES);
3809 opts.insert(Options::ENABLE_FOOTNOTES);
3810 opts.insert(Options::ENABLE_STRIKETHROUGH);
3811 opts.insert(Options::ENABLE_SUPERSCRIPT);
3812 opts.insert(Options::ENABLE_SUBSCRIPT);
3813 opts.insert(Options::ENABLE_TASKLISTS);
3814
3815 Parser::new_ext(text, opts)
3816 }
3817
3818 #[test]
3819 #[cfg(target_pointer_width = "64")]
3820 fn node_size() {
3821 let node_size = core::mem::size_of::<Node<Item>>();
3822 assert_eq!(48, node_size);
3823 }
3824
3825 #[test]
3826 #[cfg(target_pointer_width = "64")]
3827 fn body_size() {
3828 let body_size = core::mem::size_of::<ItemBody>();
3829 assert_eq!(16, body_size);
3830 }
3831
3832 #[test]
3833 fn single_open_fish_bracket() {
3834 assert_eq!(3, Parser::new("<").count());
3836 }
3837
3838 #[test]
3839 fn lone_hashtag() {
3840 assert_eq!(2, Parser::new("#").count());
3842 }
3843
3844 #[test]
3845 fn lots_of_backslashes() {
3846 Parser::new("\\\\\r\r").count();
3848 Parser::new("\\\r\r\\.\\\\\r\r\\.\\").count();
3849 }
3850
3851 #[test]
3852 fn issue_1030() {
3853 let mut opts = Options::empty();
3854 opts.insert(Options::ENABLE_WIKILINKS);
3855
3856 let parser = Parser::new_ext("For a new ferrari, [[Wikientry|click here]]!", opts);
3857
3858 let offsets = parser
3859 .into_offset_iter()
3860 .map(|(_ev, range)| range)
3861 .collect::<Vec<_>>();
3862 let expected_offsets = vec![
3863 (0..44), (0..19), (19..43), (31..41), (19..43), (43..44), (0..44), ];
3871 assert_eq!(offsets, expected_offsets);
3872 }
3873
3874 #[test]
3875 fn issue_320() {
3876 parser_with_extensions(":\r\t> |\r:\r\t> |\r").count();
3878 }
3879
3880 #[test]
3881 fn issue_319() {
3882 parser_with_extensions("|\r-]([^|\r-]([^").count();
3884 parser_with_extensions("|\r\r=][^|\r\r=][^car").count();
3885 }
3886
3887 #[test]
3888 fn issue_303() {
3889 parser_with_extensions("[^\r\ra]").count();
3891 parser_with_extensions("\r\r]Z[^\x00\r\r]Z[^\x00").count();
3892 }
3893
3894 #[test]
3895 fn issue_313() {
3896 parser_with_extensions("*]0[^\r\r*]0[^").count();
3898 parser_with_extensions("[^\r> `][^\r> `][^\r> `][").count();
3899 }
3900
3901 #[test]
3902 fn issue_311() {
3903 parser_with_extensions("\\\u{0d}-\u{09}\\\u{0d}-\u{09}").count();
3905 }
3906
3907 #[test]
3908 fn issue_283() {
3909 let input = core::str::from_utf8(b"\xf0\x9b\xb2\x9f<td:^\xf0\x9b\xb2\x9f").unwrap();
3910 parser_with_extensions(input).count();
3912 }
3913
3914 #[test]
3915 fn issue_289() {
3916 parser_with_extensions("> - \\\n> - ").count();
3918 parser_with_extensions("- \n\n").count();
3919 }
3920
3921 #[test]
3922 fn issue_306() {
3923 parser_with_extensions("*\r_<__*\r_<__*\r_<__*\r_<__").count();
3925 }
3926
3927 #[test]
3928 fn issue_305() {
3929 parser_with_extensions("_6**6*_*").count();
3931 }
3932
3933 #[test]
3934 fn another_emphasis_panic() {
3935 parser_with_extensions("*__#_#__*").count();
3936 }
3937
3938 #[test]
3939 fn offset_iter() {
3940 let event_offsets: Vec<_> = Parser::new("*hello* world")
3941 .into_offset_iter()
3942 .map(|(_ev, range)| range)
3943 .collect();
3944 let expected_offsets = vec![(0..13), (0..7), (1..6), (0..7), (7..13), (0..13)];
3945 assert_eq!(expected_offsets, event_offsets);
3946 }
3947
3948 #[test]
3949 fn reference_link_offsets() {
3950 let range =
3951 Parser::new("# H1\n[testing][Some reference]\n\n[Some reference]: https://github.com")
3952 .into_offset_iter()
3953 .filter_map(|(ev, range)| match ev {
3954 Event::Start(
3955 Tag::Link {
3956 link_type: LinkType::Reference,
3957 ..
3958 },
3959 ..,
3960 ) => Some(range),
3961 _ => None,
3962 })
3963 .next()
3964 .unwrap();
3965 assert_eq!(5..30, range);
3966 }
3967
3968 #[test]
3969 fn footnote_offsets() {
3970 let range = parser_with_extensions("Testing this[^1] out.\n\n[^1]: Footnote.")
3971 .into_offset_iter()
3972 .filter_map(|(ev, range)| match ev {
3973 Event::FootnoteReference(..) => Some(range),
3974 _ => None,
3975 })
3976 .next()
3977 .unwrap();
3978 assert_eq!(12..16, range);
3979 }
3980
3981 #[test]
3982 fn footnote_offsets_exclamation() {
3983 let mut immediately_before_footnote = None;
3984 let range = parser_with_extensions("Testing this![^1] out.\n\n[^1]: Footnote.")
3985 .into_offset_iter()
3986 .filter_map(|(ev, range)| match ev {
3987 Event::FootnoteReference(..) => Some(range),
3988 _ => {
3989 immediately_before_footnote = Some((ev, range));
3990 None
3991 }
3992 })
3993 .next()
3994 .unwrap();
3995 assert_eq!(13..17, range);
3996 if let (Event::Text(exclamation), range_exclamation) =
3997 immediately_before_footnote.as_ref().unwrap()
3998 {
3999 assert_eq!("!", &exclamation[..]);
4000 assert_eq!(&(12..13), range_exclamation);
4001 } else {
4002 panic!("what came first, then? {immediately_before_footnote:?}");
4003 }
4004 }
4005
4006 #[test]
4007 fn table_offset() {
4008 let markdown = "a\n\nTesting|This|Outtt\n--|:--:|--:\nSome Data|Other data|asdf";
4009 let event_offset = parser_with_extensions(markdown)
4010 .into_offset_iter()
4011 .map(|(_ev, range)| range)
4012 .nth(3)
4013 .unwrap();
4014 let expected_offset = 3..59;
4015 assert_eq!(expected_offset, event_offset);
4016 }
4017
4018 #[test]
4019 fn table_cell_span() {
4020 let markdown = "a|b|c\n--|--|--\na| |c";
4021 let event_offset = parser_with_extensions(markdown)
4022 .into_offset_iter()
4023 .filter_map(|(ev, span)| match ev {
4024 Event::Start(Tag::TableCell) => Some(span),
4025 _ => None,
4026 })
4027 .nth(4)
4028 .unwrap();
4029 let expected_offset_start = "a|b|c\n--|--|--\na".len();
4031 assert_eq!(
4032 expected_offset_start..(expected_offset_start + 3),
4033 event_offset
4034 );
4035 }
4036
4037 #[test]
4038 fn offset_iter_issue_378() {
4039 let event_offsets: Vec<_> = Parser::new("a [b](c) d")
4040 .into_offset_iter()
4041 .map(|(_ev, range)| range)
4042 .collect();
4043 let expected_offsets = vec![(0..10), (0..2), (2..8), (3..4), (2..8), (8..10), (0..10)];
4044 assert_eq!(expected_offsets, event_offsets);
4045 }
4046
4047 #[test]
4048 fn offset_iter_issue_404() {
4049 let event_offsets: Vec<_> = Parser::new("###\n")
4050 .into_offset_iter()
4051 .map(|(_ev, range)| range)
4052 .collect();
4053 let expected_offsets = vec![(0..4), (0..4)];
4054 assert_eq!(expected_offsets, event_offsets);
4055 }
4056
4057 #[test]
4058 fn broken_links_called_only_once() {
4059 for &(markdown, expected) in &[
4060 ("See also [`g()`][crate::g].", 1),
4061 ("See also [`g()`][crate::g][].", 1),
4062 ("[brokenlink1] some other node [brokenlink2]", 2),
4063 ] {
4064 let mut times_called = 0;
4065 let callback = &mut |_broken_link: BrokenLink| {
4066 times_called += 1;
4067 None
4068 };
4069 let parser =
4070 Parser::new_with_broken_link_callback(markdown, Options::empty(), Some(callback));
4071 for _ in parser {}
4072 assert_eq!(times_called, expected);
4073 }
4074 }
4075
4076 #[test]
4077 fn simple_broken_link_callback() {
4078 let test_str = "This is a link w/o def: [hello][world]";
4079 let mut callback = |broken_link: BrokenLink| {
4080 assert_eq!("world", broken_link.reference.as_ref());
4081 assert_eq!(&test_str[broken_link.span], "[hello][world]");
4082 let url = "YOLO".into();
4083 let title = "SWAG".to_owned().into();
4084 Some((url, title))
4085 };
4086 let parser =
4087 Parser::new_with_broken_link_callback(test_str, Options::empty(), Some(&mut callback));
4088 let mut link_tag_count = 0;
4089 for (typ, url, title, id) in parser.filter_map(|event| match event {
4090 Event::Start(Tag::Link {
4091 link_type,
4092 dest_url,
4093 title,
4094 id,
4095 }) => Some((link_type, dest_url, title, id)),
4096 _ => None,
4097 }) {
4098 link_tag_count += 1;
4099 assert_eq!(typ, LinkType::ReferenceUnknown);
4100 assert_eq!(url.as_ref(), "YOLO");
4101 assert_eq!(title.as_ref(), "SWAG");
4102 assert_eq!(id.as_ref(), "world");
4103 }
4104 assert!(link_tag_count > 0);
4105 }
4106
4107 #[test]
4108 fn code_block_kind_check_fenced() {
4109 let parser = Parser::new("hello\n```test\ntadam\n```");
4110 let mut found = 0;
4111 for (ev, _range) in parser.into_offset_iter() {
4112 if let Event::Start(Tag::CodeBlock(CodeBlockKind::Fenced(syntax))) = ev {
4113 assert_eq!(syntax.as_ref(), "test");
4114 found += 1;
4115 }
4116 }
4117 assert_eq!(found, 1);
4118 }
4119
4120 #[test]
4121 fn code_block_kind_check_indented() {
4122 let parser = Parser::new("hello\n\n ```test\n tadam\nhello");
4123 let mut found = 0;
4124 for (ev, _range) in parser.into_offset_iter() {
4125 if let Event::Start(Tag::CodeBlock(CodeBlockKind::Indented)) = ev {
4126 found += 1;
4127 }
4128 }
4129 assert_eq!(found, 1);
4130 }
4131
4132 #[test]
4133 fn ref_defs() {
4134 let input = r###"[a B c]: http://example.com
4135[another]: https://google.com
4136
4137text
4138
4139[final ONE]: http://wikipedia.org
4140"###;
4141 let mut parser = Parser::new(input);
4142
4143 assert!(parser.reference_definitions().get("a b c").is_some());
4144 assert!(parser.reference_definitions().get("nope").is_none());
4145
4146 if let Some(_event) = parser.next() {
4147 let s = "final one".to_owned();
4149 let link_def = parser.reference_definitions().get(&s).unwrap();
4150 let span = &input[link_def.span.clone()];
4151 assert_eq!(span, "[final ONE]: http://wikipedia.org");
4152 }
4153 }
4154
4155 #[test]
4156 #[allow(clippy::extra_unused_lifetimes)]
4157 fn common_lifetime_patterns_allowed<'b>() {
4158 let temporary_str = String::from("xyz");
4159
4160 let mut closure = |link: BrokenLink<'b>| Some(("#".into(), link.reference));
4164
4165 fn function(link: BrokenLink<'_>) -> Option<(CowStr<'_>, CowStr<'_>)> {
4166 Some(("#".into(), link.reference))
4167 }
4168
4169 for _ in Parser::new_with_broken_link_callback(
4170 "static lifetime",
4171 Options::empty(),
4172 Some(&mut closure),
4173 ) {}
4174 for _ in Parser::new_with_broken_link_callback(
4183 "static lifetime",
4184 Options::empty(),
4185 Some(&mut function),
4186 ) {}
4187 for _ in Parser::new_with_broken_link_callback(
4188 &temporary_str,
4189 Options::empty(),
4190 Some(&mut function),
4191 ) {}
4192 }
4193
4194 #[test]
4195 fn inline_html_inside_blockquote() {
4196 let input = "> <foo\n> bar>";
4198 let events: Vec<_> = Parser::new(input).collect();
4199 let expected = [
4200 Event::Start(Tag::BlockQuote(None)),
4201 Event::Start(Tag::Paragraph),
4202 Event::InlineHtml(CowStr::Boxed("<foo\nbar>".to_string().into())),
4203 Event::End(TagEnd::Paragraph),
4204 Event::End(TagEnd::BlockQuote(None)),
4205 ];
4206 assert_eq!(&events, &expected);
4207 }
4208
4209 #[test]
4210 fn wikilink_has_pothole() {
4211 let input = "[[foo]] [[bar|baz]]";
4212 let events: Vec<_> = Parser::new_ext(input, Options::ENABLE_WIKILINKS).collect();
4213 let expected = [
4214 Event::Start(Tag::Paragraph),
4215 Event::Start(Tag::Link {
4216 link_type: LinkType::WikiLink { has_pothole: false },
4217 dest_url: CowStr::Borrowed("foo"),
4218 title: CowStr::Borrowed(""),
4219 id: CowStr::Borrowed(""),
4220 }),
4221 Event::Text(CowStr::Borrowed("foo")),
4222 Event::End(TagEnd::Link),
4223 Event::Text(CowStr::Borrowed(" ")),
4224 Event::Start(Tag::Link {
4225 link_type: LinkType::WikiLink { has_pothole: true },
4226 dest_url: CowStr::Borrowed("bar"),
4227 title: CowStr::Borrowed(""),
4228 id: CowStr::Borrowed(""),
4229 }),
4230 Event::Text(CowStr::Borrowed("baz")),
4231 Event::End(TagEnd::Link),
4232 Event::End(TagEnd::Paragraph),
4233 ];
4234 assert_eq!(&events, &expected);
4235 }
4236
4237 #[cfg(feature = "mdx")]
4238 fn mdx_parser(text: &str) -> Parser<'_> {
4239 Parser::new_ext(text, Options::ENABLE_MDX)
4240 }
4241
4242 #[cfg(feature = "mdx")]
4243 #[test]
4244 fn mdx_esm_import() {
4245 let events: Vec<_> = mdx_parser("import {Chart} from './chart.js'\n").collect();
4246 assert_eq!(events.len(), 1);
4247 assert!(matches!(&events[0], Event::MdxEsm(s) if s.contains("import")));
4248 }
4249
4250 #[cfg(feature = "mdx")]
4251 #[test]
4252 fn mdx_esm_export() {
4253 let events: Vec<_> = mdx_parser("export const meta = {}\n").collect();
4254 assert_eq!(events.len(), 1);
4255 assert!(matches!(&events[0], Event::MdxEsm(s) if s.contains("export")));
4256 }
4257
4258 #[cfg(feature = "mdx")]
4259 #[test]
4260 fn mdx_flow_expression() {
4261 let events: Vec<_> = mdx_parser("{1 + 1}\n").collect();
4262 assert_eq!(events.len(), 1);
4263 assert!(matches!(&events[0], Event::MdxFlowExpression(s) if s.as_ref() == "1 + 1"));
4264 }
4265
4266 #[cfg(feature = "mdx")]
4267 #[test]
4268 fn mdx_jsx_flow_self_closing() {
4269 let events: Vec<_> = mdx_parser("<Chart values={[1,2,3]} />\n").collect();
4270 assert!(!events.is_empty());
4271 assert!(
4272 matches!(&events[0], Event::Start(Tag::MdxJsxFlowElement(s)) if s.contains("Chart"))
4273 );
4274 }
4275
4276 #[cfg(feature = "mdx")]
4277 #[test]
4278 fn mdx_jsx_flow_fragment() {
4279 let events: Vec<_> = mdx_parser("<>\n").collect();
4280 assert!(!events.is_empty());
4281 assert!(matches!(
4282 &events[0],
4283 Event::Start(Tag::MdxJsxFlowElement(_))
4284 ));
4285 }
4286
4287 #[cfg(feature = "mdx")]
4288 #[test]
4289 fn mdx_inline_expression() {
4290 let events: Vec<_> = mdx_parser("hello {name} world\n").collect();
4291 let has_expr = events
4292 .iter()
4293 .any(|e| matches!(e, Event::MdxTextExpression(s) if s.as_ref() == "name"));
4294 assert!(
4295 has_expr,
4296 "Expected inline MDX expression, got: {:?}",
4297 events
4298 );
4299 }
4300
4301 #[cfg(feature = "mdx")]
4302 #[test]
4303 fn mdx_inline_jsx() {
4304 let events: Vec<_> = mdx_parser("hello <Badge /> world\n").collect();
4305 let has_jsx = events
4306 .iter()
4307 .any(|e| matches!(e, Event::Start(Tag::MdxJsxTextElement(s)) if s.contains("Badge")));
4308 assert!(has_jsx, "Expected inline MDX JSX, got: {:?}", events);
4309 }
4310
4311 #[cfg(feature = "mdx")]
4312 #[test]
4313 fn mdx_all_tags_are_jsx() {
4314 let events: Vec<_> = mdx_parser("hello <em>world</em>\n").collect();
4316 let has_jsx = events
4317 .iter()
4318 .any(|e| matches!(e, Event::Start(Tag::MdxJsxTextElement(_))));
4319 assert!(has_jsx, "In MDX mode, <em> should be JSX: {:?}", events);
4320 }
4321
4322 #[test]
4323 fn mdx_does_not_interfere_without_flag() {
4324 let events: Vec<_> = Parser::new("import foo from 'bar'\n").collect();
4326 assert!(
4328 events
4329 .iter()
4330 .any(|e| matches!(e, Event::Start(Tag::Paragraph)))
4331 );
4332 }
4333
4334 #[cfg(feature = "mdx")]
4335 #[test]
4336 fn mdx_expression_in_heading() {
4337 let events: Vec<_> = mdx_parser("# {title}\n").collect();
4338 let has_heading = events
4339 .iter()
4340 .any(|e| matches!(e, Event::Start(Tag::Heading { .. })));
4341 assert!(has_heading, "Should have a heading");
4342 let has_expr = events
4343 .iter()
4344 .any(|e| matches!(e, Event::MdxTextExpression(s) if s.as_ref() == "title"));
4345 assert!(
4346 has_expr,
4347 "Heading should contain MdxTextExpression, got: {:?}",
4348 events
4349 );
4350 }
4351
4352 #[cfg(feature = "mdx")]
4353 #[test]
4354 fn mdx_expression_mixed_text_in_heading() {
4355 let events: Vec<_> = mdx_parser("## Hello {name}\n").collect();
4356 let has_text = events
4357 .iter()
4358 .any(|e| matches!(e, Event::Text(s) if s.contains("Hello")));
4359 let has_expr = events
4360 .iter()
4361 .any(|e| matches!(e, Event::MdxTextExpression(s) if s.as_ref() == "name"));
4362 assert!(has_text, "Should have text, got: {:?}", events);
4363 assert!(has_expr, "Should have expression, got: {:?}", events);
4364 }
4365}