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((_, ReferenceLabel::Footnote(footlabel))) =
1407 scan_link_label(&self.tree, first_bracket_text, self.options)
1408 && self.allocs.footdefs.contains(&footlabel)
1409 {
1410 let footref = self.allocs.allocate_cow(footlabel);
1411 if let Some(def) = self
1412 .allocs
1413 .footdefs
1414 .get_mut(self.allocs.cows[footref.0].to_owned())
1415 {
1416 def.use_count += 1;
1417 }
1418 let footnote_ix = if tos.ty == LinkStackTy::Image {
1419 self.tree[tos.node].next = Some(cur_ix);
1420 self.tree[tos.node].child = None;
1421 self.tree[tos.node].item.body = ItemBody::SynthesizeChar('!');
1422 self.tree[cur_ix].item.start =
1423 self.tree[tos.node].item.start + 1;
1424 self.tree[tos.node].item.end =
1425 self.tree[tos.node].item.start + 1;
1426 cur_ix
1427 } else {
1428 tos.node
1429 };
1430 self.tree[footnote_ix].next = next;
1431 self.tree[footnote_ix].child = None;
1432 self.tree[footnote_ix].item.body =
1433 ItemBody::FootnoteReference(footref);
1434 self.tree[footnote_ix].item.end = first_bracket_end;
1435 prev = Some(footnote_ix);
1436 cur = next;
1437 self.link_stack.clear();
1438 continue;
1439 }
1440 let scan_result =
1443 scan_reference(&self.tree, block_text, next, self.options);
1444 let (node_after_link, link_type) = match scan_result {
1445 RefScan::LinkLabel(_, end_ix) => {
1447 let reference_close_node = if let Some(node) =
1452 scan_nodes_to_ix(&self.tree, next, end_ix - 1)
1453 {
1454 node
1455 } else {
1456 continue;
1457 };
1458 self.tree[reference_close_node].item.body =
1459 ItemBody::MaybeLinkClose(false);
1460 let close_end = self.tree[reference_close_node].item.end;
1467 let next_node = if close_end > end_ix {
1468 self.tree[reference_close_node].item.end = end_ix;
1469 let tail = self.tree.create_node(Item {
1470 start: end_ix,
1471 end: close_end,
1472 body: ItemBody::Text {
1473 backslash_escaped: false,
1474 },
1475 });
1476 self.tree[tail].next = self.tree[reference_close_node].next;
1477 self.tree[reference_close_node].next = Some(tail);
1478 Some(tail)
1479 } else {
1480 self.tree[reference_close_node].next
1481 };
1482
1483 (next_node, LinkType::Reference)
1484 }
1485 RefScan::Collapsed(next_node) => {
1487 if !could_be_ref {
1490 continue;
1491 }
1492 (next_node, LinkType::Collapsed)
1493 }
1494 RefScan::UnexpectedFootnote => continue,
1501 RefScan::FailedInvalidLabel => continue,
1507 RefScan::Failed => {
1511 if !could_be_ref {
1512 continue;
1513 }
1514 (next, LinkType::Shortcut)
1515 }
1516 };
1517
1518 let label: Option<(ReferenceLabel<'input>, usize)> = match scan_result {
1523 RefScan::LinkLabel(l, end_ix) => {
1524 Some((ReferenceLabel::Link(l), end_ix))
1525 }
1526 RefScan::Collapsed(..)
1527 | RefScan::Failed
1528 | RefScan::FailedInvalidLabel
1529 | RefScan::UnexpectedFootnote => {
1530 let label_start = self.tree[tos.node].item.end - 1;
1532 let label_end = self.tree[cur_ix].item.end;
1533 scan_link_label(
1534 &self.tree,
1535 &self.text[label_start..label_end],
1536 self.options,
1537 )
1538 .map(|(ix, label)| (label, label_start + ix))
1539 .filter(|(_, end)| *end == label_end)
1540 }
1541 };
1542
1543 let id = match &label {
1544 Some(
1545 (ReferenceLabel::Link(l), _) | (ReferenceLabel::Footnote(l), _),
1546 ) => l.clone(),
1547 None => "".into(),
1548 };
1549
1550 if let Some((ReferenceLabel::Footnote(l), end)) = label {
1552 let footref = self.allocs.allocate_cow(l);
1553 if let Some(def) = self
1554 .allocs
1555 .footdefs
1556 .get_mut(self.allocs.cows[footref.0].to_owned())
1557 {
1558 def.use_count += 1;
1559 }
1560 if self.allocs.footdefs.contains(&self.allocs.cows[footref.0]) {
1561 let footnote_ix = if tos.ty == LinkStackTy::Image {
1564 self.tree[tos.node].next = Some(cur_ix);
1565 self.tree[tos.node].child = None;
1566 self.tree[tos.node].item.body =
1567 ItemBody::SynthesizeChar('!');
1568 self.tree[cur_ix].item.start =
1569 self.tree[tos.node].item.start + 1;
1570 self.tree[tos.node].item.end =
1571 self.tree[tos.node].item.start + 1;
1572 cur_ix
1573 } else {
1574 tos.node
1575 };
1576 self.tree[footnote_ix].next = next;
1580 self.tree[footnote_ix].child = None;
1581 self.tree[footnote_ix].item.body =
1582 ItemBody::FootnoteReference(footref);
1583 self.tree[footnote_ix].item.end = end;
1584 prev = Some(footnote_ix);
1585 cur = next;
1586 self.link_stack.clear();
1587 continue;
1588 }
1589 } else if let Some((ReferenceLabel::Link(link_label), end)) = label
1590 && let Some((def_link_type, url, title)) = self
1591 .fetch_link_type_url_title(
1592 link_label,
1593 (self.tree[tos.node].item.start)..end,
1594 link_type,
1595 callbacks,
1596 )
1597 {
1598 let link_ix =
1599 self.allocs.allocate_link(def_link_type, url, title, id);
1600 self.tree[tos.node].item.body = if tos.ty == LinkStackTy::Image {
1601 ItemBody::Image(link_ix)
1602 } else {
1603 ItemBody::Link(link_ix)
1604 };
1605 let label_node = self.tree[tos.node].next;
1606
1607 self.tree[tos.node].next = node_after_link;
1610
1611 if label_node != cur {
1613 self.tree[tos.node].child = label_node;
1614
1615 if let Some(prev_ix) = prev {
1617 self.tree[prev_ix].next = None;
1618 }
1619 }
1620
1621 self.tree[tos.node].item.end = end;
1622 debug_assert!(
1629 node_after_link.is_none_or(|node_after_ix| {
1630 self.tree[node_after_ix].item.start >= end
1631 }),
1632 "reference splice must not overrun its successor",
1633 );
1634
1635 cur = Some(tos.node);
1637 cur_ix = tos.node;
1638
1639 if tos.ty == LinkStackTy::Link {
1640 self.disable_all_links();
1641 }
1642 }
1643 }
1644 }
1645 }
1646 _ => {}
1647 }
1648 prev = cur;
1649 cur = self.tree[cur_ix].next;
1650 }
1651 self.link_stack.clear();
1652 self.wikilink_stack.clear();
1653 self.code_delims.clear();
1654 self.math_delims.clear();
1655 }
1656
1657 fn repair_construct_after_url_end(&mut self, cand_end: usize, node_ix: TreeIndex) {
1662 let item = self.tree[node_ix].item;
1663 if item.start != cand_end {
1664 return;
1665 }
1666 if let ItemBody::MaybeEmphasisEscaped(count, can_open, can_close) = item.body {
1667 self.tree[node_ix].item.body = ItemBody::MaybeEmphasis(count, can_open, can_close);
1668 let mut scan = self.tree[node_ix].next;
1671 for _ in 1..count {
1672 let Some(next_ix) = scan else { break };
1673 if let ItemBody::MaybeEmphasis(_, open, close) = &mut self.tree[next_ix].item.body {
1674 *open = can_open;
1675 *close = can_close;
1676 }
1677 scan = self.tree[next_ix].next;
1678 }
1679 return;
1680 }
1681 if !matches!(item.body, ItemBody::Text { .. })
1682 || self.text.as_bytes().get(cand_end) != Some(&b'&')
1683 {
1684 return;
1685 }
1686 let (n, Some(value)) = scan_entity(&self.text.as_bytes()[cand_end..]) else {
1687 return;
1688 };
1689 if cand_end + n > item.end {
1690 return;
1691 }
1692 let cow_ix = self.allocs.allocate_cow(value);
1693 if cand_end + n < item.end {
1694 let tail = self.tree.create_node(Item {
1695 start: cand_end + n,
1696 end: item.end,
1697 body: ItemBody::Text {
1698 backslash_escaped: false,
1699 },
1700 });
1701 self.tree[tail].next = self.tree[node_ix].next;
1702 self.tree[node_ix].next = Some(tail);
1703 }
1704 self.tree[node_ix].item.end = cand_end + n;
1705 self.tree[node_ix].item.body = ItemBody::SynthesizeText(cow_ix);
1706 }
1707
1708 fn handle_wikilink(
1714 &mut self,
1715 block_text: &'input str,
1716 cur_ix: TreeIndex,
1717 prev: Option<TreeIndex>,
1718 ) -> Option<TreeIndex> {
1719 let next_ix = self.tree[cur_ix].next.unwrap();
1720 if let Some(tos) = self.wikilink_stack.pop() {
1723 if tos.ty == LinkStackTy::Disabled {
1724 return None;
1725 }
1726 let Some(body_node) = self.tree[tos.node].next.and_then(|ix| self.tree[ix].next) else {
1728 return None;
1730 };
1731 let start_ix = self.tree[body_node].item.start;
1732 let end_ix = self.tree[cur_ix].item.start;
1733 let wikilink = match scan_wikilink_pipe(
1734 block_text,
1735 start_ix, end_ix - start_ix,
1737 ) {
1738 Some((rest, wikitext)) => {
1739 if wikitext.is_empty() {
1741 return None;
1742 }
1743 let body_node = scan_nodes_to_ix(&self.tree, Some(body_node), rest);
1745 if let Some(body_node) = body_node {
1746 self.tree[body_node].item.start = rest;
1749 Some((true, body_node, wikitext))
1750 } else {
1751 None
1752 }
1753 }
1754 None => {
1755 let wikitext = &block_text[start_ix..end_ix];
1756 if wikitext.is_empty() {
1758 return None;
1759 }
1760 let body_node = self.tree.create_node(Item {
1761 start: start_ix,
1762 end: end_ix,
1763 body: ItemBody::Text {
1764 backslash_escaped: false,
1765 },
1766 });
1767 Some((false, body_node, wikitext))
1768 }
1769 };
1770
1771 if let Some((has_pothole, body_node, wikiname)) = wikilink {
1772 let link_ix = self.allocs.allocate_link(
1773 LinkType::WikiLink { has_pothole },
1774 wikiname.into(),
1775 "".into(),
1776 "".into(),
1777 );
1778 if let Some(prev_ix) = prev {
1779 self.tree[prev_ix].next = None;
1780 }
1781 if tos.ty == LinkStackTy::Image {
1782 self.tree[tos.node].item.body = ItemBody::Image(link_ix);
1783 } else {
1784 self.tree[tos.node].item.body = ItemBody::Link(link_ix);
1785 }
1786 self.tree[tos.node].child = Some(body_node);
1787 self.tree[tos.node].next = self.tree[next_ix].next;
1788 self.tree[tos.node].item.end = end_ix + 2;
1789 self.disable_all_links();
1790 return Some(tos.node);
1791 }
1792 }
1793
1794 None
1795 }
1796
1797 fn handle_emphasis_in_scope(&mut self, start: Option<TreeIndex>) {
1798 let mut prev = None;
1799 let mut prev_ix: TreeIndex;
1800 let mut cur = start;
1801
1802 let mut single_quote_open: Option<TreeIndex> = None;
1803 let mut double_quote_open: bool = false;
1804
1805 while let Some(mut cur_ix) = cur {
1806 match self.tree[cur_ix].item.body {
1807 ItemBody::MaybeEmphasis(mut count, can_open, can_close) => {
1808 let run_length = count;
1809 let c = self.text.as_bytes()[self.tree[cur_ix].item.start];
1810 let both = can_open && can_close;
1811 if c == b'~' || c == b'^' {
1819 prev_ix = cur_ix + count - 1;
1820 prev = Some(prev_ix);
1821 cur = self.tree[prev_ix].next;
1822 continue;
1823 }
1824 if can_close {
1825 while let Some(el) =
1826 self.inline_stack
1827 .find_match(&mut self.tree, c, run_length, count, both)
1828 {
1829 if let Some(prev_ix) = prev {
1831 self.tree[prev_ix].next = None;
1832 }
1833 let match_count = min(2, min(count, el.count));
1842 let mut end = cur_ix - 1;
1844 let mut start = el.start + el.count;
1845
1846 while start > el.start + el.count - match_count {
1848 let inc = if start > el.start + el.count - match_count + 1 {
1849 2
1850 } else {
1851 1
1852 };
1853 let ty = if c == b'~' {
1854 if inc == 2 {
1855 if self.options.contains(Options::ENABLE_STRIKETHROUGH) {
1856 ItemBody::Strikethrough
1857 } else {
1858 ItemBody::Text {
1859 backslash_escaped: false,
1860 }
1861 }
1862 } else if self.options.contains(Options::ENABLE_SUBSCRIPT) {
1863 ItemBody::Subscript
1864 } else if self.options.contains(Options::ENABLE_STRIKETHROUGH) {
1865 ItemBody::Strikethrough
1866 } else {
1867 ItemBody::Text {
1868 backslash_escaped: false,
1869 }
1870 }
1871 } else if c == b'^' {
1872 if self.options.contains(Options::ENABLE_SUPERSCRIPT) {
1873 ItemBody::Superscript
1874 } else {
1875 ItemBody::Text {
1876 backslash_escaped: false,
1877 }
1878 }
1879 } else if inc == 2 {
1880 ItemBody::Strong
1881 } else {
1882 ItemBody::Emphasis
1883 };
1884
1885 let root = start - inc;
1886 end = end + inc;
1887 self.tree[root].item.body = ty;
1888 self.tree[root].item.end = self.tree[end].item.end;
1889 self.tree[root].child = Some(start);
1890 self.tree[root].next = None;
1891 start = root;
1892 }
1893
1894 prev_ix = el.start + el.count - match_count;
1896 prev = Some(prev_ix);
1897 cur = self.tree[cur_ix + match_count - 1].next;
1898 self.tree[prev_ix].next = cur;
1899
1900 if el.count > match_count {
1901 self.inline_stack.push(InlineEl {
1902 start: el.start,
1903 count: el.count - match_count,
1904 run_length: el.run_length,
1905 c: el.c,
1906 both: el.both,
1907 })
1908 }
1909 count -= match_count;
1910 if count > 0 {
1911 cur_ix = cur.unwrap();
1912 } else {
1913 break;
1914 }
1915 }
1916 }
1917 if count > 0 {
1918 if can_open {
1919 self.inline_stack.push(InlineEl {
1920 start: cur_ix,
1921 run_length,
1922 count,
1923 c,
1924 both,
1925 });
1926 } else {
1927 for i in 0..count {
1928 self.tree[cur_ix + i].item.body = ItemBody::Text {
1929 backslash_escaped: false,
1930 };
1931 }
1932 }
1933 prev_ix = cur_ix + count - 1;
1934 prev = Some(prev_ix);
1935 cur = self.tree[prev_ix].next;
1936 }
1937 }
1938 ItemBody::MaybeSmartQuote(c, can_open, can_close) => {
1939 self.tree[cur_ix].item.body = match c {
1940 b'\'' => {
1941 if let (Some(open_ix), true) = (single_quote_open, can_close) {
1942 self.tree[open_ix].item.body = ItemBody::SynthesizeChar('‘');
1943 single_quote_open = None;
1944 } else if can_open {
1945 single_quote_open = Some(cur_ix);
1946 }
1947 ItemBody::SynthesizeChar('’')
1948 }
1949 _ => {
1950 if can_close && double_quote_open {
1951 double_quote_open = false;
1952 ItemBody::SynthesizeChar('”')
1953 } else if can_open {
1954 double_quote_open = true;
1955 ItemBody::SynthesizeChar('“')
1956 } else if can_close {
1957 ItemBody::SynthesizeChar('”')
1960 } else {
1961 ItemBody::SynthesizeChar('“')
1963 }
1964 }
1965 };
1966 prev = cur;
1967 cur = self.tree[cur_ix].next;
1968 }
1969 ItemBody::HardBreak(true) => {
1970 if self.tree[cur_ix].next.is_none() {
1971 self.tree[cur_ix].item.body = ItemBody::SynthesizeChar('\\');
1972 }
1973 prev = cur;
1974 cur = self.tree[cur_ix].next;
1975 }
1976 _ => {
1977 prev = cur;
1978 cur = self.tree[cur_ix].next;
1979 }
1980 }
1981 }
1982 self.inline_stack.pop_all(&mut self.tree);
1983 }
1984
1985 fn resolve_tildes_carets_in_scope(&mut self, start: Option<TreeIndex>, descend: bool) {
1996 let mut stack: Vec<InlineEl> = Vec::new();
1997 let mut cur = start;
1998 let mut prev: Option<TreeIndex> = None;
1999 while let Some(mut cur_ix) = cur {
2000 match self.tree[cur_ix].item.body {
2001 ItemBody::MaybeEmphasis(count, can_open, can_close) => {
2002 let c = self.text.as_bytes()[self.tree[cur_ix].item.start];
2003 if c != b'~' && c != b'^' {
2004 prev = Some(cur_ix);
2005 cur = self.tree[cur_ix].next;
2006 continue;
2007 }
2008 let run_length = count;
2009 let mut remaining = count;
2010 if can_close {
2011 while remaining > 0 {
2012 let res = stack
2013 .iter()
2014 .enumerate()
2015 .rfind(|(_, el)| el.c == c && el.run_length == run_length);
2016 let Some((matching_ix, matching_el)) = res else {
2017 break;
2018 };
2019 let matching_el = *matching_el;
2020 if let Some(prev_ix) = prev {
2021 self.tree[prev_ix].next = None;
2022 }
2023 for el in &stack[(matching_ix + 1)..] {
2026 for i in 0..el.count {
2027 self.tree[el.start + i].item.body = ItemBody::Text {
2028 backslash_escaped: false,
2029 };
2030 }
2031 }
2032 stack.truncate(matching_ix);
2033 let match_count =
2034 core::cmp::min(2, core::cmp::min(remaining, matching_el.count));
2035 let mut end = cur_ix - 1;
2036 let mut sub_start = matching_el.start + matching_el.count;
2037 while sub_start > matching_el.start + matching_el.count - match_count {
2038 let inc = if sub_start
2039 > matching_el.start + matching_el.count - match_count + 1
2040 {
2041 2
2042 } else {
2043 1
2044 };
2045 let ty = if c == b'~' {
2046 if inc == 2 {
2047 if self.options.contains(Options::ENABLE_STRIKETHROUGH) {
2048 ItemBody::Strikethrough
2049 } else {
2050 ItemBody::Text {
2051 backslash_escaped: false,
2052 }
2053 }
2054 } else if self.options.contains(Options::ENABLE_SUBSCRIPT) {
2055 ItemBody::Subscript
2056 } else if self.options.contains(Options::ENABLE_STRIKETHROUGH) {
2057 ItemBody::Strikethrough
2058 } else {
2059 ItemBody::Text {
2060 backslash_escaped: false,
2061 }
2062 }
2063 } else if self.options.contains(Options::ENABLE_SUPERSCRIPT) {
2064 ItemBody::Superscript
2065 } else {
2066 ItemBody::Text {
2067 backslash_escaped: false,
2068 }
2069 };
2070 let root = sub_start - inc;
2071 end = end + inc;
2072 self.tree[root].item.body = ty;
2073 self.tree[root].item.end = self.tree[end].item.end;
2074 self.tree[root].child = Some(sub_start);
2075 self.tree[root].next = None;
2076 sub_start = root;
2077 }
2078 let new_prev_ix = matching_el.start + matching_el.count - match_count;
2079 let new_cur = self.tree[cur_ix + match_count - 1].next;
2080 self.tree[new_prev_ix].next = new_cur;
2081 prev = Some(new_prev_ix);
2082 if matching_el.count > match_count {
2083 stack.push(InlineEl {
2084 start: matching_el.start,
2085 count: matching_el.count - match_count,
2086 run_length: matching_el.run_length,
2087 c: matching_el.c,
2088 both: matching_el.both,
2089 });
2090 }
2091 remaining -= match_count;
2092 if remaining > 0 {
2093 let Some(next_cur) = new_cur else { break };
2094 cur_ix = next_cur;
2095 } else {
2096 break;
2097 }
2098 }
2099 }
2100 if remaining > 0 {
2101 if can_open {
2102 stack.push(InlineEl {
2103 start: cur_ix,
2104 count: remaining,
2105 run_length,
2106 c,
2107 both: can_open && can_close,
2108 });
2109 } else {
2110 for i in 0..remaining {
2111 self.tree[cur_ix + i].item.body = ItemBody::Text {
2112 backslash_escaped: false,
2113 };
2114 }
2115 }
2116 let prev_ix = cur_ix + remaining - 1;
2117 prev = Some(prev_ix);
2118 cur = self.tree[prev_ix].next;
2119 } else {
2120 cur = self.tree[prev.unwrap()].next;
2121 }
2122 continue;
2123 }
2124 ItemBody::Emphasis
2125 | ItemBody::Strong
2126 | ItemBody::Strikethrough
2127 | ItemBody::Subscript
2128 | ItemBody::Superscript
2129 | ItemBody::Link(_)
2130 | ItemBody::Image(_)
2131 if descend =>
2132 {
2133 let child = self.tree[cur_ix].child;
2134 self.resolve_tildes_carets_in_scope(child, true);
2135 }
2136 _ => {}
2137 }
2138 prev = Some(cur_ix);
2139 cur = self.tree[cur_ix].next;
2140 }
2141 for el in stack {
2143 for i in 0..el.count {
2144 self.tree[el.start + i].item.body = ItemBody::Text {
2145 backslash_escaped: false,
2146 };
2147 }
2148 }
2149 }
2150
2151 fn disable_all_links(&mut self) {
2152 self.link_stack.disable_all_links();
2153 self.wikilink_stack.disable_all_links();
2154 }
2155
2156 fn defined_footnote_label(&self, open_ix: TreeIndex, close_ix: TreeIndex) -> bool {
2157 let start = self.tree[open_ix].item.start;
2158 if !self.options.contains(Options::ENABLE_FOOTNOTES)
2159 || self.text.as_bytes().get(start + 1) != Some(&b'^')
2160 {
2161 return false;
2162 }
2163 let label_text = &self.text[start..self.tree[close_ix].item.end];
2164 let Some((len, ReferenceLabel::Footnote(label))) =
2165 scan_link_label(&self.tree, label_text, self.options)
2166 else {
2167 return false;
2168 };
2169 !label_text.as_bytes()[..len]
2171 .iter()
2172 .any(|b| matches!(b, b' ' | b'\t' | b'\n' | b'\r'))
2173 && self.allocs.footdefs.contains(&label)
2174 }
2175
2176 fn scan_inline_link(
2178 &self,
2179 underlying: &'input str,
2180 mut ix: usize,
2181 node: Option<TreeIndex>,
2182 ) -> Option<(usize, CowStr<'input>, CowStr<'input>)> {
2183 if underlying.as_bytes().get(ix) != Some(&b'(') {
2184 return None;
2185 }
2186 ix += 1;
2187
2188 let scan_separator = |ix: &mut usize| {
2189 *ix += scan_while(&underlying.as_bytes()[*ix..], is_space_or_tab);
2190 if let Some(bl) = scan_eol(&underlying.as_bytes()[*ix..]) {
2191 *ix += bl;
2192 *ix += skip_container_prefixes(
2193 &self.tree,
2194 &underlying.as_bytes()[*ix..],
2195 self.options,
2196 );
2197 }
2198 *ix += scan_while(&underlying.as_bytes()[*ix..], is_space_or_tab);
2199 };
2200
2201 scan_separator(&mut ix);
2202
2203 let (dest_length, dest) = scan_link_dest(underlying, ix, LINK_MAX_NESTED_PARENS)?;
2204 let dest = unescape(dest, self.tree.is_in_table());
2205 ix += dest_length;
2206
2207 let dest_end = ix;
2208 scan_separator(&mut ix);
2209
2210 let title = if ix > dest_end
2212 && let Some((bytes_scanned, t)) = self.scan_link_title(underlying, ix, node)
2213 {
2214 ix += bytes_scanned;
2215 scan_separator(&mut ix);
2216 t
2217 } else {
2218 "".into()
2219 };
2220 if underlying.as_bytes().get(ix) != Some(&b')') {
2221 return None;
2222 }
2223 ix += 1;
2224
2225 Some((ix, dest, title))
2226 }
2227
2228 fn scan_link_title(
2230 &self,
2231 text: &'input str,
2232 start_ix: usize,
2233 node: Option<TreeIndex>,
2234 ) -> Option<(usize, CowStr<'input>)> {
2235 let bytes = text.as_bytes();
2236 let open = match bytes.get(start_ix) {
2237 Some(b @ b'\'') | Some(b @ b'\"') | Some(b @ b'(') => *b,
2238 _ => return None,
2239 };
2240 if open == b'(' && start_ix >= self.unclosed_paren_title_floor.get() {
2241 return None;
2242 }
2243 let close = if open == b'(' { b')' } else { open };
2245
2246 let mut title = String::new();
2247 let mut mark = start_ix + 1;
2248 let mut i = start_ix + 1;
2249
2250 while i < bytes.len() {
2251 let c = bytes[i];
2252
2253 if c == close {
2254 let cow = if title.is_empty() {
2255 (i - start_ix + 1, text[mark..i].into())
2256 } else {
2257 title.push_str(&text[mark..i]);
2258 (i - start_ix + 1, title.into())
2259 };
2260
2261 return Some(cow);
2262 }
2263
2264 if (c == b'\n' || c == b'\r')
2265 && let Some(node_ix) = scan_nodes_to_ix(&self.tree, node, i + 1)
2266 && self.tree[node_ix].item.start > i
2267 {
2268 title.push_str(&text[mark..i]);
2269 title.push(c as char);
2271 if c == b'\r' && bytes.get(i + 1) == Some(&b'\n') {
2272 title.push('\n');
2273 }
2274 i = self.tree[node_ix].item.start;
2275 mark = i;
2276 continue;
2277 }
2278 if c == b'&'
2279 && let (n, Some(value)) = scan_entity(&bytes[i..])
2280 {
2281 title.push_str(&text[mark..i]);
2282 title.push_str(&value);
2283 i += n;
2284 mark = i;
2285 continue;
2286 }
2287 if self.tree.is_in_table()
2288 && c == b'\\'
2289 && i + 2 < bytes.len()
2290 && bytes[i + 1] == b'\\'
2291 && bytes[i + 2] == b'|'
2292 {
2293 title.push_str(&text[mark..i]);
2296 i += 2;
2297 mark = i;
2298 }
2299 if c == b'\\' && i + 1 < bytes.len() && is_ascii_punctuation(bytes[i + 1]) {
2300 title.push_str(&text[mark..i]);
2301 i += 1;
2302 mark = i;
2303 }
2304
2305 i += 1;
2306 }
2307
2308 if open == b'(' {
2309 let floor = self.unclosed_paren_title_floor.get();
2310 self.unclosed_paren_title_floor.set(floor.min(start_ix));
2311 }
2312 None
2313 }
2314
2315 fn make_math_span(&mut self, open: TreeIndex, close: TreeIndex) {
2316 let mut open_end = open;
2318 {
2319 let mut peek = self.tree[open].next;
2320 while let Some(peek_ix) = peek {
2321 if matches!(self.tree[peek_ix].item.body, ItemBody::MaybeMath(..))
2322 && self.tree[peek_ix].item.start == self.tree[open_end].item.end
2323 && peek_ix != close
2324 {
2325 open_end = peek_ix;
2326 peek = self.tree[peek_ix].next;
2327 } else {
2328 break;
2329 }
2330 }
2331 }
2332 let mut close_end = close;
2334 {
2335 let mut peek = self.tree[close].next;
2336 while let Some(peek_ix) = peek {
2337 if matches!(self.tree[peek_ix].item.body, ItemBody::MaybeMath(..))
2338 && self.tree[peek_ix].item.start == self.tree[close_end].item.end
2339 {
2340 close_end = peek_ix;
2341 peek = self.tree[peek_ix].next;
2342 } else {
2343 break;
2344 }
2345 }
2346 }
2347
2348 let span_start = self.tree[open_end].item.end;
2349 let span_end = self.tree[close].item.start;
2350
2351 if span_start > span_end {
2352 self.tree[open].item.body = ItemBody::Text {
2353 backslash_escaped: false,
2354 };
2355 return;
2356 }
2357
2358 let spanned_text = &self.text[span_start..span_end];
2359 let spanned_bytes = spanned_text.as_bytes();
2360 let mut buf: Option<String> = None;
2361
2362 let mut start_ix = 0;
2363 let mut ix = 0;
2364 while ix < spanned_bytes.len() {
2365 let c = spanned_bytes[ix];
2366 if c == b'\r' || c == b'\n' {
2367 ix += 1;
2368 let buf = buf.get_or_insert_with(|| String::with_capacity(spanned_bytes.len()));
2369 buf.push_str(&spanned_text[start_ix..ix]);
2370 let from = span_start + ix;
2379 let (scanned, leftover) = skip_container_prefixes_with_remaining(
2380 &self.tree,
2381 &self.text.as_bytes()[from..],
2382 self.options,
2383 );
2384 let scanned = scanned.min(spanned_bytes.len() - ix);
2385 ix += scanned;
2386 start_ix = ix;
2387 for _ in 0..leftover {
2391 buf.push(' ');
2392 }
2393 } else if c == b'\\'
2394 && spanned_bytes.get(ix + 1) == Some(&b'|')
2395 && self.tree.is_in_table()
2396 {
2397 let buf = buf.get_or_insert_with(|| String::with_capacity(spanned_bytes.len()));
2398 buf.push_str(&spanned_text[start_ix..ix]);
2399 buf.push('|');
2400 ix += 2;
2401 start_ix = ix;
2402 } else {
2403 ix += 1;
2404 }
2405 }
2406
2407 if let Some(buf) = &mut buf {
2408 buf.push_str(&spanned_text[start_ix..]);
2409 }
2410 let cow: CowStr<'input> = strip_span_padding(buf, spanned_text);
2411
2412 self.tree[open].item.body = ItemBody::Math(self.allocs.allocate_cow(cow), false);
2413 self.tree[open].item.end = self.tree[close_end].item.end;
2414 self.tree[open].next = self.tree[close_end].next;
2415 }
2416
2417 fn make_code_span(&mut self, open: TreeIndex, close: TreeIndex, preceding_backslash: bool) {
2421 let span_start = self.tree[open].item.end;
2422 let span_end = self.tree[close].item.start;
2423 let mut buf: Option<String> = None;
2424
2425 let spanned_text = &self.text[span_start..span_end];
2426 let spanned_bytes = spanned_text.as_bytes();
2427 let mut start_ix = 0;
2428 let mut ix = 0;
2429 while ix < spanned_bytes.len() {
2430 let c = spanned_bytes[ix];
2431 if c == b'\r' || c == b'\n' {
2432 let buf = buf.get_or_insert_with(|| String::with_capacity(spanned_bytes.len()));
2433 buf.push_str(&spanned_text[start_ix..ix]);
2436 buf.push(c as char);
2437 ix += 1;
2438 if c == b'\r' && spanned_bytes.get(ix) == Some(&b'\n') {
2439 buf.push('\n');
2440 ix += 1;
2441 }
2442 let from = span_start + ix;
2451 let (scanned, leftover) = skip_container_prefixes_with_remaining(
2452 &self.tree,
2453 &self.text.as_bytes()[from..],
2454 self.options,
2455 );
2456 let scanned = scanned.min(spanned_bytes.len() - ix);
2457 ix += scanned;
2458 start_ix = ix;
2459 for _ in 0..leftover {
2463 buf.push(' ');
2464 }
2465 } else if c == b'\\'
2466 && spanned_bytes.get(ix + 1) == Some(&b'|')
2467 && self.tree.is_in_table()
2468 {
2469 let buf = buf.get_or_insert_with(|| String::with_capacity(spanned_bytes.len()));
2470 buf.push_str(&spanned_text[start_ix..ix]);
2471 buf.push('|');
2472 ix += 2;
2473 start_ix = ix;
2474 } else {
2475 ix += 1;
2476 }
2477 }
2478
2479 if let Some(buf) = &mut buf {
2480 buf.push_str(&spanned_text[start_ix..]);
2481 }
2482 let cow: CowStr<'input> = strip_span_padding(buf, spanned_text);
2483
2484 if preceding_backslash {
2485 self.tree[open].item.body = ItemBody::Text {
2486 backslash_escaped: true,
2487 };
2488 self.tree[open].item.end = self.tree[open].item.start + 1;
2489 self.tree[open].next = Some(close);
2490 self.tree[close].item.body = ItemBody::Code(self.allocs.allocate_cow(cow));
2491 self.tree[close].item.start = self.tree[open].item.start + 1;
2492 } else {
2493 self.tree[open].item.body = ItemBody::Code(self.allocs.allocate_cow(cow));
2494 self.tree[open].item.end = self.tree[close].item.end;
2495 self.tree[open].next = self.tree[close].next;
2496 }
2497
2498 if !self.mdx_errors.is_empty() {
2501 self.mdx_errors
2502 .retain(|(offset, _)| *offset < span_start || *offset >= span_end);
2503 }
2504 }
2505
2506 fn scan_inline_html(&mut self, bytes: &[u8], ix: usize) -> Option<(Vec<u8>, usize)> {
2510 let c = *bytes.get(ix)?;
2511 if c == b'!' {
2512 Some((
2513 vec![],
2514 scan_inline_html_comment(bytes, ix + 1, &mut self.html_scan_guard)?,
2515 ))
2516 } else if c == b'?' {
2517 Some((
2518 vec![],
2519 scan_inline_html_processing(bytes, ix + 1, &mut self.html_scan_guard)?,
2520 ))
2521 } else {
2522 let (span, i) = scan_html_block_inner(
2523 &bytes[(ix - 1)..],
2525 Some(&|bytes| skip_container_prefixes(&self.tree, bytes, self.options)),
2526 )?;
2527 Some((span, i + ix - 1))
2528 }
2529 }
2530}
2531
2532pub(crate) fn scan_containers(
2534 tree: &Tree<Item>,
2535 line_start: &mut LineStart<'_>,
2536 options: Options,
2537) -> usize {
2538 let mut i = 0;
2539 for &node_ix in tree.walk_spine() {
2540 match tree[node_ix].item.body {
2541 ItemBody::BlockQuote(..) => {
2542 let save = line_start.clone();
2543 if options.contains(Options::ENABLE_MDX) {
2548 line_start.scan_all_space();
2549 } else {
2550 let _ = line_start.scan_space(3);
2551 }
2552 if !line_start.scan_blockquote_marker() {
2553 *line_start = save;
2554 break;
2555 }
2556 }
2557 ItemBody::ListItem(indent, _) => {
2558 let save = line_start.clone();
2559 if !line_start.scan_space(indent) && !line_start.is_at_eol() {
2560 *line_start = save;
2561 break;
2562 }
2563 }
2564 ItemBody::DefinitionListDefinition(indent, _) => {
2565 let save = line_start.clone();
2566 if !line_start.scan_space(indent) && !line_start.is_at_eol() {
2567 *line_start = save;
2568 break;
2569 }
2570 }
2571 ItemBody::FootnoteDefinition(..) if options.contains(Options::ENABLE_FOOTNOTES) => {
2572 let save = line_start.clone();
2573 if !line_start.scan_space(4) && !line_start.is_at_eol() {
2574 *line_start = save;
2575 break;
2576 }
2577 }
2578 _ => (),
2579 }
2580 i += 1;
2581 }
2582 i
2583}
2584
2585fn strip_span_padding<'input>(buf: Option<String>, spanned_text: &'input str) -> CowStr<'input> {
2592 let s = buf.as_deref().unwrap_or(spanned_text);
2593 let lead = if s.starts_with("\r\n") {
2594 2
2595 } else {
2596 usize::from(matches!(s.as_bytes().first(), Some(b' ' | b'\n' | b'\r')))
2597 };
2598 let trail = if s.ends_with("\r\n") {
2599 2
2600 } else {
2601 usize::from(matches!(s.as_bytes().last(), Some(b' ' | b'\n' | b'\r')))
2602 };
2603 let all_spaces = s.bytes().all(|b| matches!(b, b' ' | b'\n' | b'\r'));
2604
2605 if !all_spaces && lead > 0 && trail > 0 {
2606 if let Some(mut buf) = buf {
2607 if !buf.is_empty() {
2608 buf.truncate(buf.len() - trail);
2609 buf.replace_range(..lead, "");
2610 }
2611 buf.into()
2612 } else {
2613 spanned_text[lead..(spanned_text.len() - trail).max(lead)].into()
2614 }
2615 } else if let Some(buf) = buf {
2616 buf.into()
2617 } else {
2618 spanned_text.into()
2619 }
2620}
2621
2622pub(crate) fn skip_container_prefixes(tree: &Tree<Item>, bytes: &[u8], options: Options) -> usize {
2623 let mut line_start = LineStart::new(bytes);
2624 let _ = scan_containers(tree, &mut line_start, options);
2625 line_start.bytes_scanned()
2626}
2627
2628fn skip_container_prefixes_with_remaining(
2635 tree: &Tree<Item>,
2636 bytes: &[u8],
2637 options: Options,
2638) -> (usize, usize) {
2639 let mut line_start = LineStart::new(bytes);
2640 let _ = scan_containers(tree, &mut line_start, options);
2641 (line_start.bytes_scanned(), line_start.remaining_space())
2642}
2643
2644impl Tree<Item> {
2645 pub(crate) fn append_text(&mut self, start: usize, end: usize, backslash_escaped: bool) {
2646 if end > start {
2647 if let Some(ix) = self.cur()
2648 && matches!(self[ix].item.body, ItemBody::Text { .. })
2649 && self[ix].item.end == start
2650 {
2651 self[ix].item.end = end;
2652 return;
2653 }
2654 self.append(Item {
2655 start,
2656 end,
2657 body: ItemBody::Text { backslash_escaped },
2658 });
2659 }
2660 }
2661 pub(crate) fn is_in_table(&self) -> bool {
2668 fn might_be_in_table(item: &Item) -> bool {
2669 item.body.is_inline()
2670 || matches!(item.body, |ItemBody::TableHead| ItemBody::TableRow
2671 | ItemBody::TableCell)
2672 }
2673 for &ix in self.walk_spine().rev() {
2674 if matches!(self[ix].item.body, ItemBody::Table(_)) {
2675 return true;
2676 }
2677 if !might_be_in_table(&self[ix].item) {
2678 return false;
2679 }
2680 }
2681 false
2682 }
2683}
2684
2685#[derive(Copy, Clone, Debug)]
2686struct InlineEl {
2687 start: TreeIndex,
2689 count: usize,
2691 run_length: usize,
2693 c: u8,
2695 both: bool,
2697}
2698
2699#[derive(Debug, Clone, Default)]
2700struct InlineStack {
2701 stack: Vec<InlineEl>,
2702 lower_bounds: [usize; 10],
2707}
2708
2709impl InlineStack {
2710 const UNDERSCORE_NOT_BOTH: usize = 0;
2714 const ASTERISK_NOT_BOTH: usize = 1;
2715 const ASTERISK_BASE: usize = 2;
2716 const TILDES: usize = 5;
2717 const UNDERSCORE_BASE: usize = 6;
2718 const CIRCUMFLEXES: usize = 9;
2719
2720 fn pop_all(&mut self, tree: &mut Tree<Item>) {
2721 for el in self.stack.drain(..) {
2722 for i in 0..el.count {
2723 tree[el.start + i].item.body = ItemBody::Text {
2724 backslash_escaped: false,
2725 };
2726 }
2727 }
2728 self.lower_bounds = [0; 10];
2729 }
2730
2731 fn get_lowerbound(&self, c: u8, count: usize, both: bool) -> usize {
2732 if c == b'_' {
2733 let mod3_lower = self.lower_bounds[InlineStack::UNDERSCORE_BASE + count % 3];
2734 if both {
2735 mod3_lower
2736 } else {
2737 min(
2738 mod3_lower,
2739 self.lower_bounds[InlineStack::UNDERSCORE_NOT_BOTH],
2740 )
2741 }
2742 } else if c == b'*' {
2743 let mod3_lower = self.lower_bounds[InlineStack::ASTERISK_BASE + count % 3];
2744 if both {
2745 mod3_lower
2746 } else {
2747 min(
2748 mod3_lower,
2749 self.lower_bounds[InlineStack::ASTERISK_NOT_BOTH],
2750 )
2751 }
2752 } else if c == b'^' {
2753 self.lower_bounds[InlineStack::CIRCUMFLEXES]
2754 } else {
2755 self.lower_bounds[InlineStack::TILDES]
2756 }
2757 }
2758
2759 fn set_lowerbound(&mut self, c: u8, count: usize, both: bool, new_bound: usize) {
2760 if c == b'_' {
2761 if both {
2762 self.lower_bounds[InlineStack::UNDERSCORE_BASE + count % 3] = new_bound;
2763 } else {
2764 self.lower_bounds[InlineStack::UNDERSCORE_NOT_BOTH] = new_bound;
2765 }
2766 } else if c == b'*' {
2767 self.lower_bounds[InlineStack::ASTERISK_BASE + count % 3] = new_bound;
2768 if !both {
2769 self.lower_bounds[InlineStack::ASTERISK_NOT_BOTH] = new_bound;
2770 }
2771 } else if c == b'^' {
2772 self.lower_bounds[InlineStack::CIRCUMFLEXES] = new_bound;
2773 } else {
2774 self.lower_bounds[InlineStack::TILDES] = new_bound;
2775 }
2776 }
2777
2778 fn truncate(&mut self, new_bound: usize) {
2779 self.stack.truncate(new_bound);
2780 for lower_bound in &mut self.lower_bounds {
2781 if *lower_bound > new_bound {
2782 *lower_bound = new_bound;
2783 }
2784 }
2785 }
2786
2787 fn find_match(
2800 &mut self,
2801 tree: &mut Tree<Item>,
2802 c: u8,
2803 run_length: usize,
2804 current_count: usize,
2805 both: bool,
2806 ) -> Option<InlineEl> {
2807 let lowerbound = min(
2817 self.stack.len(),
2818 self.get_lowerbound(c, current_count, both),
2819 );
2820 let res = self.stack[lowerbound..]
2821 .iter()
2822 .cloned()
2823 .enumerate()
2824 .rfind(|(_, el)| {
2825 if (c == b'~' || c == b'^') && run_length != el.run_length {
2826 return false;
2827 }
2828 el.c == c
2833 && (!both && !el.both
2834 || !(current_count + el.count).is_multiple_of(3)
2835 || current_count.is_multiple_of(3))
2836 });
2837
2838 if let Some((matching_ix, matching_el)) = res {
2839 let matching_ix = matching_ix + lowerbound;
2840 for el in &self.stack[(matching_ix + 1)..] {
2841 for i in 0..el.count {
2842 tree[el.start + i].item.body = ItemBody::Text {
2843 backslash_escaped: false,
2844 };
2845 }
2846 }
2847 self.truncate(matching_ix);
2848 Some(matching_el)
2849 } else {
2850 if c != b'~' && c != b'^' {
2860 self.set_lowerbound(c, current_count, both, self.stack.len());
2861 }
2862 None
2863 }
2864 }
2865
2866 fn trim_lower_bound(&mut self, ix: usize) {
2867 self.lower_bounds[ix] = self.lower_bounds[ix].min(self.stack.len());
2868 }
2869
2870 fn push(&mut self, el: InlineEl) {
2871 if el.c == b'~' {
2872 self.trim_lower_bound(InlineStack::TILDES);
2873 } else if el.c == b'^' {
2874 self.trim_lower_bound(InlineStack::CIRCUMFLEXES);
2875 }
2876 self.stack.push(el)
2877 }
2878}
2879
2880#[derive(Debug, Clone)]
2881enum RefScan<'a> {
2882 LinkLabel(CowStr<'a>, usize),
2884 Collapsed(Option<TreeIndex>),
2886 UnexpectedFootnote,
2887 Failed,
2888 FailedInvalidLabel,
2893}
2894
2895fn scan_nodes_to_ix(
2898 tree: &Tree<Item>,
2899 mut node: Option<TreeIndex>,
2900 ix: usize,
2901) -> Option<TreeIndex> {
2902 while let Some(node_ix) = node {
2903 let item = tree[node_ix].item;
2904 if item.end <= ix && item.start < ix {
2907 node = tree[node_ix].next;
2908 } else {
2909 break;
2910 }
2911 }
2912 node
2913}
2914
2915fn scan_link_label<'text>(
2918 tree: &Tree<Item>,
2919 text: &'text str,
2920 options: Options,
2921) -> Option<(usize, ReferenceLabel<'text>)> {
2922 let bytes = text.as_bytes();
2923 if bytes.len() < 2 || bytes[0] != b'[' {
2924 return None;
2925 }
2926 let linebreak_handler = |bytes: &[u8]| Some(skip_container_prefixes(tree, bytes, options));
2927 if options.contains(Options::ENABLE_FOOTNOTES)
2928 && b'^' == bytes[1]
2929 && bytes.get(2) != Some(&b']')
2930 {
2931 let linebreak_handler: &dyn Fn(&[u8]) -> Option<usize> = &|_| None;
2933 if let Some((byte_index, cow)) =
2934 scan_link_label_rest(&text[2..], linebreak_handler, tree.is_in_table())
2935 {
2936 return Some((byte_index + 2, ReferenceLabel::Footnote(cow)));
2937 }
2938 }
2939 let (byte_index, cow) =
2940 scan_link_label_rest(&text[1..], &linebreak_handler, tree.is_in_table())?;
2941 Some((byte_index + 1, ReferenceLabel::Link(cow)))
2942}
2943
2944fn scan_reference<'b>(
2945 tree: &Tree<Item>,
2946 text: &'b str,
2947 cur: Option<TreeIndex>,
2948 options: Options,
2949) -> RefScan<'b> {
2950 let cur_ix = match cur {
2951 None => return RefScan::Failed,
2952 Some(cur_ix) => cur_ix,
2953 };
2954 let start = tree[cur_ix].item.start;
2955 let tail = &text.as_bytes()[start..];
2956
2957 if tail.first() == Some(&b'[') && start > 0 {
2964 let src = text.as_bytes();
2965 let mut backslashes = 0usize;
2966 let mut j = start;
2967 while j > 0 && src[j - 1] == b'\\' {
2968 backslashes += 1;
2969 j -= 1;
2970 }
2971 if backslashes % 2 == 1 {
2972 return RefScan::Failed;
2973 }
2974 }
2975
2976 if tail.starts_with(b"[]") {
2977 let Some(closing_node) = tree[cur_ix].next else {
2982 return RefScan::Failed;
2983 };
2984 RefScan::Collapsed(tree[closing_node].next)
2985 } else {
2986 let label = scan_link_label(tree, &text[start..], options);
2987 match label {
2988 Some((ix, ReferenceLabel::Link(label))) => RefScan::LinkLabel(label, start + ix),
2989 Some((_ix, ReferenceLabel::Footnote(_label))) => RefScan::UnexpectedFootnote,
2990 None => {
2991 if tail.starts_with(b"[") {
2996 RefScan::FailedInvalidLabel
2997 } else {
2998 RefScan::Failed
2999 }
3000 }
3001 }
3002 }
3003}
3004
3005#[derive(Clone, Default)]
3006struct LinkStack {
3007 inner: Vec<LinkStackEl>,
3008 disabled_ix: usize,
3009}
3010
3011impl LinkStack {
3012 fn is_empty(&self) -> bool {
3013 self.inner.is_empty()
3014 }
3015
3016 fn push(&mut self, el: LinkStackEl) {
3017 self.inner.push(el);
3018 }
3019
3020 fn pop(&mut self) -> Option<LinkStackEl> {
3021 let el = self.inner.pop();
3022 self.disabled_ix = core::cmp::min(self.disabled_ix, self.inner.len());
3023 el
3024 }
3025
3026 fn clear(&mut self) {
3027 self.inner.clear();
3028 self.disabled_ix = 0;
3029 }
3030
3031 fn disable_all_links(&mut self) {
3032 for el in &mut self.inner[self.disabled_ix..] {
3033 if el.ty == LinkStackTy::Link {
3034 el.ty = LinkStackTy::Disabled;
3035 }
3036 }
3037 self.disabled_ix = self.inner.len();
3038 }
3039}
3040
3041#[derive(Clone, Debug)]
3042struct LinkStackEl {
3043 node: TreeIndex,
3044 ty: LinkStackTy,
3045}
3046
3047#[derive(PartialEq, Clone, Debug)]
3048enum LinkStackTy {
3049 Link,
3050 Image,
3051 Disabled,
3052}
3053
3054#[derive(Clone, Debug)]
3056pub struct LinkDef<'a> {
3057 pub dest: CowStr<'a>,
3058 pub title: Option<CowStr<'a>>,
3059 pub span: Range<usize>,
3060}
3061
3062impl<'a> LinkDef<'a> {
3063 pub fn into_static(self) -> LinkDef<'static> {
3064 LinkDef {
3065 dest: self.dest.into_static(),
3066 title: self.title.map(|s| s.into_static()),
3067 span: self.span,
3068 }
3069 }
3070}
3071
3072#[derive(Clone, Debug)]
3074pub struct FootnoteDef {
3075 pub use_count: usize,
3076}
3077
3078struct CodeDelims {
3081 inner: FxHashMap<usize, VecDeque<TreeIndex>>,
3082 seen_first: bool,
3083}
3084
3085impl CodeDelims {
3086 fn new() -> Self {
3087 Self {
3088 inner: Default::default(),
3089 seen_first: false,
3090 }
3091 }
3092
3093 fn insert(&mut self, count: usize, ix: TreeIndex) {
3094 if self.seen_first {
3095 self.inner.entry(count).or_default().push_back(ix);
3096 } else {
3097 self.seen_first = true;
3100 }
3101 }
3102
3103 fn is_populated(&self) -> bool {
3104 !self.inner.is_empty()
3105 }
3106
3107 fn find(&mut self, open_ix: TreeIndex, count: usize) -> Option<TreeIndex> {
3108 while let Some(ix) = self.inner.get_mut(&count)?.pop_front() {
3109 if ix > open_ix {
3110 return Some(ix);
3111 }
3112 }
3113 None
3114 }
3115
3116 fn clear(&mut self) {
3117 self.inner.clear();
3118 self.seen_first = false;
3119 }
3120}
3121
3122struct MathDelims {
3125 inner: FxHashMap<u8, VecDeque<(TreeIndex, bool, bool)>>,
3126}
3127
3128impl MathDelims {
3129 fn new() -> Self {
3130 Self {
3131 inner: Default::default(),
3132 }
3133 }
3134
3135 fn clear(&mut self) {
3136 self.inner.clear();
3137 }
3138}
3139
3140#[derive(Copy, Clone, PartialEq, Eq, Debug)]
3141pub(crate) struct LinkIndex(usize);
3142
3143#[derive(Copy, Clone, PartialEq, Eq, Debug)]
3144pub(crate) struct CowIndex(usize);
3145
3146#[derive(Copy, Clone, PartialEq, Eq, Debug)]
3147pub(crate) struct AlignmentIndex(usize);
3148
3149#[derive(Copy, Clone, PartialEq, Eq, Debug)]
3150pub(crate) struct HeadingIndex(NonZeroUsize);
3151
3152#[cfg(feature = "mdx")]
3153#[derive(Copy, Clone, PartialEq, Eq, Debug)]
3154pub(crate) struct JsxElementIndex(usize);
3155
3156#[derive(Copy, Clone, PartialEq, Eq, Debug)]
3157pub(crate) struct DirectiveIndex(usize);
3158
3159#[derive(Copy, Clone, PartialEq, Eq, Debug)]
3160pub(crate) struct AutolinkCandidateIndex(usize);
3161
3162#[derive(Copy, Clone, Debug)]
3165pub(crate) struct AutolinkCandidate {
3166 pub start: usize,
3169 pub end: usize,
3170 pub link: LinkIndex,
3171}
3172
3173#[cfg(feature = "mdx")]
3175#[derive(Debug, Clone)]
3176pub(crate) enum JsxAttr<'a> {
3177 Boolean(CowStr<'a>),
3178 Literal(CowStr<'a>, CowStr<'a>),
3179 Expression(CowStr<'a>, CowStr<'a>, usize, usize),
3183 Spread(CowStr<'a>, usize, usize),
3186}
3187
3188#[cfg(feature = "mdx")]
3189impl<'a> JsxAttr<'a> {
3190 pub fn into_static(self) -> JsxAttr<'static> {
3191 match self {
3192 JsxAttr::Boolean(n) => JsxAttr::Boolean(n.into_static()),
3193 JsxAttr::Literal(n, v) => JsxAttr::Literal(n.into_static(), v.into_static()),
3194 JsxAttr::Expression(n, v, start, end) => {
3195 JsxAttr::Expression(n.into_static(), v.into_static(), start, end)
3196 }
3197 JsxAttr::Spread(v, start, end) => JsxAttr::Spread(v.into_static(), start, end),
3198 }
3199 }
3200}
3201
3202#[cfg(feature = "mdx")]
3204#[derive(Debug, Clone)]
3205pub(crate) struct JsxElementData<'a> {
3206 pub name: CowStr<'a>,
3207 pub attrs: Vec<JsxAttr<'a>>,
3208 pub raw: CowStr<'a>,
3209 pub is_closing: bool,
3210 pub is_self_closing: bool,
3211}
3212
3213#[cfg(feature = "mdx")]
3214impl<'a> JsxElementData<'a> {
3215 pub fn into_static(self) -> JsxElementData<'static> {
3216 JsxElementData {
3217 name: self.name.into_static(),
3218 attrs: self.attrs.into_iter().map(|a| a.into_static()).collect(),
3219 raw: self.raw.into_static(),
3220 is_closing: self.is_closing,
3221 is_self_closing: self.is_self_closing,
3222 }
3223 }
3224}
3225
3226#[derive(Debug, Clone)]
3227pub(crate) struct DirectiveAttrData<'a> {
3228 pub name: CowStr<'a>,
3229 pub attributes: Vec<(CowStr<'a>, CowStr<'a>)>,
3230 pub label_start: usize,
3231 pub label_end: usize,
3232 pub initial_size: u8,
3238}
3239
3240#[derive(Clone)]
3241pub(crate) struct Allocations<'a> {
3242 pub refdefs: RefDefs<'a>,
3243 pub refdefs_all: Vec<(LinkLabel<'a>, LinkDef<'a>)>,
3248 pub footdefs: FootnoteDefs<'a>,
3249 links: Vec<(LinkType, CowStr<'a>, CowStr<'a>, CowStr<'a>)>,
3250 cows: Vec<CowStr<'a>>,
3251 alignments: Vec<Vec<Alignment>>,
3252 headings: Vec<HeadingAttributes<'a>>,
3253 #[cfg(feature = "mdx")]
3254 jsx_elements: Vec<JsxElementData<'a>>,
3255 directives: Vec<DirectiveAttrData<'a>>,
3256 autolink_candidates: Vec<AutolinkCandidate>,
3257}
3258
3259#[derive(Clone)]
3261pub(crate) struct HeadingAttributes<'a> {
3262 pub id: Option<CowStr<'a>>,
3263 pub classes: Vec<CowStr<'a>>,
3264 pub attrs: Vec<(CowStr<'a>, Option<CowStr<'a>>)>,
3265}
3266
3267#[derive(Clone, Default, Debug)]
3269pub struct RefDefs<'input>(pub(crate) FxHashMap<LinkLabel<'input>, LinkDef<'input>>);
3270
3271#[derive(Clone, Default, Debug)]
3273pub struct FootnoteDefs<'input>(pub(crate) FxHashMap<FootnoteLabel<'input>, FootnoteDef>);
3274
3275impl<'input, 'b, 's> RefDefs<'input>
3276where
3277 's: 'b,
3278{
3279 pub fn get(&'s self, key: &'b str) -> Option<&'b LinkDef<'input>> {
3281 self.0.get(&UniCase::new(key.into()))
3282 }
3283
3284 pub fn iter(
3286 &'s self,
3287 ) -> impl Iterator<Item = (&'s str, &'s LinkDef<'input>)> + use<'s, 'input> {
3288 self.0.iter().map(|(k, v)| (k.as_ref(), v))
3289 }
3290}
3291
3292impl<'input, 'b, 's> FootnoteDefs<'input>
3293where
3294 's: 'b,
3295{
3296 pub fn contains(&'s self, key: &'b str) -> bool {
3298 self.0.contains_key(&UniCase::new(key.into()))
3299 }
3300 pub fn get_mut(&'s mut self, key: CowStr<'input>) -> Option<&'s mut FootnoteDef> {
3302 self.0.get_mut(&UniCase::new(key))
3303 }
3304}
3305
3306impl<'a> Allocations<'a> {
3307 pub fn new() -> Self {
3308 Self {
3309 refdefs: RefDefs::default(),
3310 refdefs_all: Vec::new(),
3311 footdefs: FootnoteDefs::default(),
3312 links: Vec::with_capacity(128),
3313 cows: Vec::new(),
3314 alignments: Vec::new(),
3315 headings: Vec::new(),
3316 #[cfg(feature = "mdx")]
3317 jsx_elements: Vec::new(),
3318 directives: Vec::new(),
3319 autolink_candidates: Vec::new(),
3320 }
3321 }
3322
3323 pub fn allocate_autolink_candidate(
3324 &mut self,
3325 candidate: AutolinkCandidate,
3326 ) -> AutolinkCandidateIndex {
3327 let ix = self.autolink_candidates.len();
3328 self.autolink_candidates.push(candidate);
3329 AutolinkCandidateIndex(ix)
3330 }
3331
3332 pub fn allocate_cow(&mut self, cow: CowStr<'a>) -> CowIndex {
3333 let ix = self.cows.len();
3334 self.cows.push(cow);
3335 CowIndex(ix)
3336 }
3337
3338 pub fn allocate_link(
3339 &mut self,
3340 ty: LinkType,
3341 url: CowStr<'a>,
3342 title: CowStr<'a>,
3343 id: CowStr<'a>,
3344 ) -> LinkIndex {
3345 let ix = self.links.len();
3346 self.links.push((ty, url, title, id));
3347 LinkIndex(ix)
3348 }
3349
3350 pub fn allocate_alignment(&mut self, alignment: Vec<Alignment>) -> AlignmentIndex {
3351 let ix = self.alignments.len();
3352 self.alignments.push(alignment);
3353 AlignmentIndex(ix)
3354 }
3355
3356 pub fn allocate_heading(&mut self, attrs: HeadingAttributes<'a>) -> HeadingIndex {
3357 let ix = self.headings.len();
3358 self.headings.push(attrs);
3359 let ix_nonzero = NonZeroUsize::new(ix.wrapping_add(1)).expect("too many headings");
3362 HeadingIndex(ix_nonzero)
3363 }
3364
3365 pub fn take_cow(&mut self, ix: CowIndex) -> CowStr<'a> {
3366 core::mem::replace(&mut self.cows[ix.0], "".into())
3367 }
3368
3369 pub fn take_link(&mut self, ix: LinkIndex) -> (LinkType, CowStr<'a>, CowStr<'a>, CowStr<'a>) {
3370 let default_link = (LinkType::ShortcutUnknown, "".into(), "".into(), "".into());
3371 core::mem::replace(&mut self.links[ix.0], default_link)
3372 }
3373
3374 pub fn take_alignment(&mut self, ix: AlignmentIndex) -> Vec<Alignment> {
3375 core::mem::take(&mut self.alignments[ix.0])
3376 }
3377
3378 #[cfg(feature = "mdx")]
3379 pub fn allocate_jsx_element(&mut self, data: JsxElementData<'a>) -> JsxElementIndex {
3380 let ix = self.jsx_elements.len();
3381 self.jsx_elements.push(data);
3382 JsxElementIndex(ix)
3383 }
3384
3385 pub fn allocate_directive(&mut self, data: DirectiveAttrData<'a>) -> DirectiveIndex {
3386 let ix = self.directives.len();
3387 self.directives.push(data);
3388 DirectiveIndex(ix)
3389 }
3390
3391 pub fn take_directive(&mut self, ix: DirectiveIndex) -> DirectiveAttrData<'a> {
3392 core::mem::replace(
3393 &mut self.directives[ix.0],
3394 DirectiveAttrData {
3395 name: "".into(),
3396 attributes: Vec::new(),
3397 label_start: 0,
3398 label_end: 0,
3399 initial_size: 0,
3400 },
3401 )
3402 }
3403
3404 pub fn directive_ref(&self, ix: DirectiveIndex) -> &DirectiveAttrData<'a> {
3405 &self.directives[ix.0]
3406 }
3407
3408 #[cfg(feature = "mdx")]
3409 pub fn take_jsx_element(&mut self, ix: JsxElementIndex) -> JsxElementData<'a> {
3410 core::mem::replace(
3411 &mut self.jsx_elements[ix.0],
3412 JsxElementData {
3413 name: "".into(),
3414 attrs: Vec::new(),
3415 raw: "".into(),
3416 is_closing: false,
3417 is_self_closing: false,
3418 },
3419 )
3420 }
3421}
3422
3423impl<'a> Index<CowIndex> for Allocations<'a> {
3424 type Output = CowStr<'a>;
3425
3426 fn index(&self, ix: CowIndex) -> &Self::Output {
3427 self.cows.index(ix.0)
3428 }
3429}
3430
3431impl<'a> Index<LinkIndex> for Allocations<'a> {
3432 type Output = (LinkType, CowStr<'a>, CowStr<'a>, CowStr<'a>);
3433
3434 fn index(&self, ix: LinkIndex) -> &Self::Output {
3435 self.links.index(ix.0)
3436 }
3437}
3438
3439impl<'a> Index<AutolinkCandidateIndex> for Allocations<'a> {
3440 type Output = AutolinkCandidate;
3441
3442 fn index(&self, ix: AutolinkCandidateIndex) -> &Self::Output {
3443 self.autolink_candidates.index(ix.0)
3444 }
3445}
3446
3447impl<'a> Index<AlignmentIndex> for Allocations<'a> {
3448 type Output = Vec<Alignment>;
3449
3450 fn index(&self, ix: AlignmentIndex) -> &Self::Output {
3451 self.alignments.index(ix.0)
3452 }
3453}
3454
3455impl<'a> Index<HeadingIndex> for Allocations<'a> {
3456 type Output = HeadingAttributes<'a>;
3457
3458 fn index(&self, ix: HeadingIndex) -> &Self::Output {
3459 self.headings.index(ix.0.get() - 1)
3460 }
3461}
3462
3463#[derive(Clone, Default)]
3469pub(crate) struct HtmlScanGuard {
3470 pub cdata: usize,
3471 pub processing: usize,
3472 pub declaration: usize,
3473 pub comment: usize,
3474}
3475
3476pub trait ParserCallbacks<'input> {
3480 fn handle_broken_link(
3488 &mut self,
3489 #[allow(unused_variables)] link: BrokenLink<'input>,
3490 ) -> Option<(CowStr<'input>, CowStr<'input>)> {
3491 None
3492 }
3493}
3494
3495#[allow(missing_debug_implementations)]
3499pub struct BrokenLinkCallback<F>(Option<F>);
3500
3501impl<'input, F> ParserCallbacks<'input> for BrokenLinkCallback<F>
3502where
3503 F: FnMut(BrokenLink<'input>) -> Option<(CowStr<'input>, CowStr<'input>)>,
3504{
3505 fn handle_broken_link(
3506 &mut self,
3507 link: BrokenLink<'input>,
3508 ) -> Option<(CowStr<'input>, CowStr<'input>)> {
3509 self.0.as_mut().and_then(|cb| cb(link))
3510 }
3511}
3512
3513impl<'input> ParserCallbacks<'input> for Box<dyn ParserCallbacks<'input>> {
3514 fn handle_broken_link(
3515 &mut self,
3516 link: BrokenLink<'input>,
3517 ) -> Option<(CowStr<'input>, CowStr<'input>)> {
3518 (**self).handle_broken_link(link)
3519 }
3520}
3521
3522#[allow(missing_debug_implementations)]
3526pub struct DefaultParserCallbacks;
3527
3528impl<'input> ParserCallbacks<'input> for DefaultParserCallbacks {}
3529
3530#[derive(Debug)]
3538pub struct OffsetIter<'a, CB> {
3539 parser: Parser<'a, CB>,
3540}
3541
3542impl<'a, CB: ParserCallbacks<'a>> OffsetIter<'a, CB> {
3543 pub fn reference_definitions(&self) -> &RefDefs<'_> {
3545 self.parser.reference_definitions()
3546 }
3547
3548 pub fn mdx_errors(&self) -> &[(usize, String)] {
3550 self.parser.mdx_errors()
3551 }
3552}
3553
3554impl<'a, CB: ParserCallbacks<'a>> Iterator for OffsetIter<'a, CB> {
3555 type Item = (Event<'a>, Range<usize>);
3556
3557 fn next(&mut self) -> Option<Self::Item> {
3558 self.parser
3559 .inner
3560 .next_event_range(&mut self.parser.callbacks)
3561 }
3562}
3563
3564impl<'a, CB: ParserCallbacks<'a>> Iterator for Parser<'a, CB> {
3565 type Item = Event<'a>;
3566
3567 fn next(&mut self) -> Option<Event<'a>> {
3568 self.inner
3569 .next_event_range(&mut self.callbacks)
3570 .map(|(event, _range)| event)
3571 }
3572}
3573
3574impl<'a, CB: ParserCallbacks<'a>> FusedIterator for Parser<'a, CB> {}
3575
3576impl<'input> ParserInner<'input> {
3577 fn next_event_range(
3578 &mut self,
3579 callbacks: &mut dyn ParserCallbacks<'input>,
3580 ) -> Option<(Event<'input>, Range<usize>)> {
3581 match self.tree.cur() {
3582 None => {
3583 let ix = self.tree.pop()?;
3584 let ix = if matches!(self.tree[ix].item.body, ItemBody::TightParagraph) {
3585 self.tree.next_sibling(ix);
3587 return self.next_event_range(callbacks);
3588 } else {
3589 ix
3590 };
3591 let tag_end = body_to_tag_end(&self.tree[ix].item.body);
3592 self.tree.next_sibling(ix);
3593 let span = self.tree[ix].item.start..self.tree[ix].item.end;
3594 debug_assert!(span.start <= span.end);
3595 Some((Event::End(tag_end), span))
3596 }
3597 Some(cur_ix) => {
3598 let cur_ix = if matches!(self.tree[cur_ix].item.body, ItemBody::TightParagraph) {
3599 self.tree.push();
3601 self.tree.cur().unwrap()
3602 } else {
3603 cur_ix
3604 };
3605 if self.tree[cur_ix].item.body.is_maybe_inline() {
3606 self.handle_inline(callbacks);
3607 }
3608
3609 let node = self.tree[cur_ix];
3610 let item = node.item;
3611 let event = item_to_event(item, self.text, &mut self.allocs);
3612 if let Event::Start(..) = event {
3613 self.tree.push();
3614 } else {
3615 self.tree.next_sibling(cur_ix);
3616 }
3617 debug_assert!(item.start <= item.end);
3618 Some((event, item.start..item.end))
3619 }
3620 }
3621 }
3622}
3623
3624fn body_to_tag_end(body: &ItemBody) -> TagEnd {
3625 match *body {
3626 ItemBody::Paragraph => TagEnd::Paragraph,
3627 ItemBody::Emphasis => TagEnd::Emphasis,
3628 ItemBody::Superscript => TagEnd::Superscript,
3629 ItemBody::Subscript => TagEnd::Subscript,
3630 ItemBody::Strong => TagEnd::Strong,
3631 ItemBody::Strikethrough => TagEnd::Strikethrough,
3632 ItemBody::Link(..) => TagEnd::Link,
3633 ItemBody::Image(..) => TagEnd::Image,
3634 ItemBody::Heading(level, _) => TagEnd::Heading(level),
3635 ItemBody::IndentCodeBlock(..) | ItemBody::FencedCodeBlock(..) | ItemBody::MathBlock(..) => {
3636 TagEnd::CodeBlock
3637 }
3638 ItemBody::ContainerDirective(..) => TagEnd::Directive(DirectiveKind::Container),
3639 ItemBody::LeafDirective(..) => TagEnd::Directive(DirectiveKind::Leaf),
3640 ItemBody::TextDirective(..) => TagEnd::Directive(DirectiveKind::Text),
3641 ItemBody::BlockQuote(kind) => TagEnd::BlockQuote(kind),
3642 ItemBody::HtmlBlock(_) => TagEnd::HtmlBlock,
3643 ItemBody::List(_, c, _) => {
3644 let is_ordered = c == b'.' || c == b')';
3645 TagEnd::List(is_ordered)
3646 }
3647 ItemBody::ListItem(_, _) => TagEnd::Item,
3648 ItemBody::TableHead => TagEnd::TableHead,
3649 ItemBody::TableCell => TagEnd::TableCell,
3650 ItemBody::TableRow => TagEnd::TableRow,
3651 ItemBody::Table(..) => TagEnd::Table,
3652 ItemBody::FootnoteDefinition(..) => TagEnd::FootnoteDefinition,
3653 ItemBody::MetadataBlock(kind) => TagEnd::MetadataBlock(kind),
3654 ItemBody::DefinitionList(_) => TagEnd::DefinitionList,
3655 ItemBody::DefinitionListTitle => TagEnd::DefinitionListTitle,
3656 ItemBody::DefinitionListDefinition(..) => TagEnd::DefinitionListDefinition,
3657 #[cfg(feature = "mdx")]
3658 ItemBody::MdxJsxFlowElement(..) => TagEnd::MdxJsxFlowElement,
3659 #[cfg(feature = "mdx")]
3660 ItemBody::MdxJsxTextElement(..) => TagEnd::MdxJsxTextElement,
3661 _ => panic!("unexpected item body {:?}", body),
3662 }
3663}
3664
3665fn item_to_event<'a>(item: Item, text: &'a str, allocs: &mut Allocations<'a>) -> Event<'a> {
3666 let tag = match item.body {
3667 ItemBody::Text { .. } => return Event::Text(text[item.start..item.end].into()),
3668 ItemBody::Code(cow_ix) => return Event::Code(allocs.take_cow(cow_ix)),
3669 ItemBody::SynthesizeText(cow_ix) => return Event::Text(allocs.take_cow(cow_ix)),
3670 ItemBody::SynthesizeChar(c) => return Event::Text(c.into()),
3671 ItemBody::HtmlBlock(_) => Tag::HtmlBlock,
3672 ItemBody::Html => return Event::Html(text[item.start..item.end].into()),
3673 ItemBody::InlineHtml => return Event::InlineHtml(text[item.start..item.end].into()),
3674 ItemBody::OwnedInlineHtml(cow_ix) => return Event::InlineHtml(allocs.take_cow(cow_ix)),
3675 ItemBody::SoftBreak => return Event::SoftBreak,
3676 ItemBody::HardBreak(_) => return Event::HardBreak,
3677 ItemBody::FootnoteReference(cow_ix) => {
3678 return Event::FootnoteReference(allocs.take_cow(cow_ix));
3679 }
3680 ItemBody::TaskListMarker(checked) => return Event::TaskListMarker(checked),
3681 ItemBody::Rule => return Event::Rule,
3682 ItemBody::Paragraph => Tag::Paragraph,
3683 ItemBody::Emphasis => Tag::Emphasis,
3684 ItemBody::Superscript => Tag::Superscript,
3685 ItemBody::Subscript => Tag::Subscript,
3686 ItemBody::Strong => Tag::Strong,
3687 ItemBody::Strikethrough => Tag::Strikethrough,
3688 ItemBody::Link(link_ix) => {
3689 let (link_type, dest_url, title, id) = allocs.take_link(link_ix);
3690 Tag::Link {
3691 link_type,
3692 dest_url,
3693 title,
3694 id,
3695 }
3696 }
3697 ItemBody::Image(link_ix) => {
3698 let (link_type, dest_url, title, id) = allocs.take_link(link_ix);
3699 Tag::Image {
3700 link_type,
3701 dest_url,
3702 title,
3703 id,
3704 }
3705 }
3706 ItemBody::Heading(level, Some(heading_ix)) => {
3707 let HeadingAttributes { id, classes, attrs } = allocs.index(heading_ix);
3708 Tag::Heading {
3709 level,
3710 id: id.clone(),
3711 classes: classes.clone(),
3712 attrs: attrs.clone(),
3713 }
3714 }
3715 ItemBody::Heading(level, None) => Tag::Heading {
3716 level,
3717 id: None,
3718 classes: Vec::new(),
3719 attrs: Vec::new(),
3720 },
3721 ItemBody::MathBlock(cow_ix) => {
3722 Tag::CodeBlock(CodeBlockKind::Fenced(allocs.take_cow(cow_ix)))
3723 }
3724 ItemBody::FencedCodeBlock(cow_ix, _) => {
3725 Tag::CodeBlock(CodeBlockKind::Fenced(allocs.take_cow(cow_ix)))
3726 }
3727 ItemBody::IndentCodeBlock(..) => Tag::CodeBlock(CodeBlockKind::Indented),
3728 ItemBody::ContainerDirective(_, dir_ix)
3729 | ItemBody::LeafDirective(dir_ix)
3730 | ItemBody::TextDirective(dir_ix) => {
3731 let kind = match item.body {
3732 ItemBody::ContainerDirective(..) => DirectiveKind::Container,
3733 ItemBody::LeafDirective(..) => DirectiveKind::Leaf,
3734 _ => DirectiveKind::Text,
3735 };
3736 let dir = allocs.take_directive(dir_ix);
3737 Tag::Directive {
3738 kind,
3739 name: dir.name,
3740 attributes: dir.attributes,
3741 }
3742 }
3743 ItemBody::BlockQuote(kind) => Tag::BlockQuote(kind),
3744 ItemBody::List(is_tight, c, listitem_start) => {
3745 if c == b'.' || c == b')' {
3746 Tag::List(Some(listitem_start), is_tight)
3747 } else {
3748 Tag::List(None, is_tight)
3749 }
3750 }
3751 ItemBody::ListItem(_, _) => Tag::Item,
3752 ItemBody::TableHead => Tag::TableHead,
3753 ItemBody::TableCell => Tag::TableCell,
3754 ItemBody::TableRow => Tag::TableRow,
3755 ItemBody::Table(alignment_ix) => Tag::Table(allocs.take_alignment(alignment_ix)),
3756 ItemBody::FootnoteDefinition(cow_ix) => Tag::FootnoteDefinition(allocs.take_cow(cow_ix)),
3757 ItemBody::MetadataBlock(kind) => Tag::MetadataBlock(kind),
3758 ItemBody::Math(cow_ix, is_display) => {
3759 return if is_display {
3760 Event::DisplayMath(allocs.take_cow(cow_ix))
3761 } else {
3762 Event::InlineMath(allocs.take_cow(cow_ix))
3763 };
3764 }
3765 ItemBody::DefinitionList(_) => Tag::DefinitionList,
3766 ItemBody::DefinitionListTitle => Tag::DefinitionListTitle,
3767 ItemBody::DefinitionListDefinition(..) => Tag::DefinitionListDefinition,
3768 #[cfg(feature = "mdx")]
3769 ItemBody::MdxJsxFlowElement(jsx_ix) => {
3770 let jsx = allocs.take_jsx_element(jsx_ix);
3771 Tag::MdxJsxFlowElement(jsx.raw)
3772 }
3773 #[cfg(feature = "mdx")]
3774 ItemBody::MdxJsxTextElement(jsx_ix) => {
3775 let jsx = allocs.take_jsx_element(jsx_ix);
3776 Tag::MdxJsxTextElement(jsx.raw)
3777 }
3778 #[cfg(feature = "mdx")]
3779 ItemBody::MdxFlowExpression(cow_ix) => {
3780 return Event::MdxFlowExpression(allocs.take_cow(cow_ix));
3781 }
3782 #[cfg(feature = "mdx")]
3783 ItemBody::MdxTextExpression(cow_ix) => {
3784 return Event::MdxTextExpression(allocs.take_cow(cow_ix));
3785 }
3786 #[cfg(feature = "mdx")]
3787 ItemBody::MdxEsm(cow_ix) => return Event::MdxEsm(allocs.take_cow(cow_ix)),
3788 _ => panic!("unexpected item body {:?}", item.body),
3789 };
3790
3791 Event::Start(tag)
3792}
3793
3794#[cfg(test)]
3795mod test {
3796 use alloc::{borrow::ToOwned, string::ToString, vec::Vec};
3797
3798 use super::*;
3799 use crate::tree::Node;
3800
3801 fn parser_with_extensions(text: &str) -> Parser<'_> {
3804 let mut opts = Options::empty();
3805 opts.insert(Options::ENABLE_TABLES);
3806 opts.insert(Options::ENABLE_FOOTNOTES);
3807 opts.insert(Options::ENABLE_STRIKETHROUGH);
3808 opts.insert(Options::ENABLE_SUPERSCRIPT);
3809 opts.insert(Options::ENABLE_SUBSCRIPT);
3810 opts.insert(Options::ENABLE_TASKLISTS);
3811
3812 Parser::new_ext(text, opts)
3813 }
3814
3815 #[test]
3816 #[cfg(target_pointer_width = "64")]
3817 fn node_size() {
3818 let node_size = core::mem::size_of::<Node<Item>>();
3819 assert_eq!(48, node_size);
3820 }
3821
3822 #[test]
3823 #[cfg(target_pointer_width = "64")]
3824 fn body_size() {
3825 let body_size = core::mem::size_of::<ItemBody>();
3826 assert_eq!(16, body_size);
3827 }
3828
3829 #[test]
3830 fn single_open_fish_bracket() {
3831 assert_eq!(3, Parser::new("<").count());
3833 }
3834
3835 #[test]
3836 fn lone_hashtag() {
3837 assert_eq!(2, Parser::new("#").count());
3839 }
3840
3841 #[test]
3842 fn lots_of_backslashes() {
3843 Parser::new("\\\\\r\r").count();
3845 Parser::new("\\\r\r\\.\\\\\r\r\\.\\").count();
3846 }
3847
3848 #[test]
3849 fn issue_1030() {
3850 let mut opts = Options::empty();
3851 opts.insert(Options::ENABLE_WIKILINKS);
3852
3853 let parser = Parser::new_ext("For a new ferrari, [[Wikientry|click here]]!", opts);
3854
3855 let offsets = parser
3856 .into_offset_iter()
3857 .map(|(_ev, range)| range)
3858 .collect::<Vec<_>>();
3859 let expected_offsets = vec![
3860 (0..44), (0..19), (19..43), (31..41), (19..43), (43..44), (0..44), ];
3868 assert_eq!(offsets, expected_offsets);
3869 }
3870
3871 #[test]
3872 fn issue_320() {
3873 parser_with_extensions(":\r\t> |\r:\r\t> |\r").count();
3875 }
3876
3877 #[test]
3878 fn issue_319() {
3879 parser_with_extensions("|\r-]([^|\r-]([^").count();
3881 parser_with_extensions("|\r\r=][^|\r\r=][^car").count();
3882 }
3883
3884 #[test]
3885 fn issue_303() {
3886 parser_with_extensions("[^\r\ra]").count();
3888 parser_with_extensions("\r\r]Z[^\x00\r\r]Z[^\x00").count();
3889 }
3890
3891 #[test]
3892 fn issue_313() {
3893 parser_with_extensions("*]0[^\r\r*]0[^").count();
3895 parser_with_extensions("[^\r> `][^\r> `][^\r> `][").count();
3896 }
3897
3898 #[test]
3899 fn issue_311() {
3900 parser_with_extensions("\\\u{0d}-\u{09}\\\u{0d}-\u{09}").count();
3902 }
3903
3904 #[test]
3905 fn issue_283() {
3906 let input = core::str::from_utf8(b"\xf0\x9b\xb2\x9f<td:^\xf0\x9b\xb2\x9f").unwrap();
3907 parser_with_extensions(input).count();
3909 }
3910
3911 #[test]
3912 fn issue_289() {
3913 parser_with_extensions("> - \\\n> - ").count();
3915 parser_with_extensions("- \n\n").count();
3916 }
3917
3918 #[test]
3919 fn issue_306() {
3920 parser_with_extensions("*\r_<__*\r_<__*\r_<__*\r_<__").count();
3922 }
3923
3924 #[test]
3925 fn issue_305() {
3926 parser_with_extensions("_6**6*_*").count();
3928 }
3929
3930 #[test]
3931 fn another_emphasis_panic() {
3932 parser_with_extensions("*__#_#__*").count();
3933 }
3934
3935 #[test]
3936 fn offset_iter() {
3937 let event_offsets: Vec<_> = Parser::new("*hello* world")
3938 .into_offset_iter()
3939 .map(|(_ev, range)| range)
3940 .collect();
3941 let expected_offsets = vec![(0..13), (0..7), (1..6), (0..7), (7..13), (0..13)];
3942 assert_eq!(expected_offsets, event_offsets);
3943 }
3944
3945 #[test]
3946 fn reference_link_offsets() {
3947 let range =
3948 Parser::new("# H1\n[testing][Some reference]\n\n[Some reference]: https://github.com")
3949 .into_offset_iter()
3950 .filter_map(|(ev, range)| match ev {
3951 Event::Start(
3952 Tag::Link {
3953 link_type: LinkType::Reference,
3954 ..
3955 },
3956 ..,
3957 ) => Some(range),
3958 _ => None,
3959 })
3960 .next()
3961 .unwrap();
3962 assert_eq!(5..30, range);
3963 }
3964
3965 #[test]
3966 fn footnote_offsets() {
3967 let range = parser_with_extensions("Testing this[^1] out.\n\n[^1]: Footnote.")
3968 .into_offset_iter()
3969 .filter_map(|(ev, range)| match ev {
3970 Event::FootnoteReference(..) => Some(range),
3971 _ => None,
3972 })
3973 .next()
3974 .unwrap();
3975 assert_eq!(12..16, range);
3976 }
3977
3978 #[test]
3979 fn footnote_offsets_exclamation() {
3980 let mut immediately_before_footnote = None;
3981 let range = parser_with_extensions("Testing this![^1] out.\n\n[^1]: Footnote.")
3982 .into_offset_iter()
3983 .filter_map(|(ev, range)| match ev {
3984 Event::FootnoteReference(..) => Some(range),
3985 _ => {
3986 immediately_before_footnote = Some((ev, range));
3987 None
3988 }
3989 })
3990 .next()
3991 .unwrap();
3992 assert_eq!(13..17, range);
3993 if let (Event::Text(exclamation), range_exclamation) =
3994 immediately_before_footnote.as_ref().unwrap()
3995 {
3996 assert_eq!("!", &exclamation[..]);
3997 assert_eq!(&(12..13), range_exclamation);
3998 } else {
3999 panic!("what came first, then? {immediately_before_footnote:?}");
4000 }
4001 }
4002
4003 #[test]
4004 fn table_offset() {
4005 let markdown = "a\n\nTesting|This|Outtt\n--|:--:|--:\nSome Data|Other data|asdf";
4006 let event_offset = parser_with_extensions(markdown)
4007 .into_offset_iter()
4008 .map(|(_ev, range)| range)
4009 .nth(3)
4010 .unwrap();
4011 let expected_offset = 3..59;
4012 assert_eq!(expected_offset, event_offset);
4013 }
4014
4015 #[test]
4016 fn table_cell_span() {
4017 let markdown = "a|b|c\n--|--|--\na| |c";
4018 let event_offset = parser_with_extensions(markdown)
4019 .into_offset_iter()
4020 .filter_map(|(ev, span)| match ev {
4021 Event::Start(Tag::TableCell) => Some(span),
4022 _ => None,
4023 })
4024 .nth(4)
4025 .unwrap();
4026 let expected_offset_start = "a|b|c\n--|--|--\na".len();
4028 assert_eq!(
4029 expected_offset_start..(expected_offset_start + 3),
4030 event_offset
4031 );
4032 }
4033
4034 #[test]
4035 fn offset_iter_issue_378() {
4036 let event_offsets: Vec<_> = Parser::new("a [b](c) d")
4037 .into_offset_iter()
4038 .map(|(_ev, range)| range)
4039 .collect();
4040 let expected_offsets = vec![(0..10), (0..2), (2..8), (3..4), (2..8), (8..10), (0..10)];
4041 assert_eq!(expected_offsets, event_offsets);
4042 }
4043
4044 #[test]
4045 fn offset_iter_issue_404() {
4046 let event_offsets: Vec<_> = Parser::new("###\n")
4047 .into_offset_iter()
4048 .map(|(_ev, range)| range)
4049 .collect();
4050 let expected_offsets = vec![(0..4), (0..4)];
4051 assert_eq!(expected_offsets, event_offsets);
4052 }
4053
4054 #[test]
4055 fn broken_links_called_only_once() {
4056 for &(markdown, expected) in &[
4057 ("See also [`g()`][crate::g].", 1),
4058 ("See also [`g()`][crate::g][].", 1),
4059 ("[brokenlink1] some other node [brokenlink2]", 2),
4060 ] {
4061 let mut times_called = 0;
4062 let callback = &mut |_broken_link: BrokenLink| {
4063 times_called += 1;
4064 None
4065 };
4066 let parser =
4067 Parser::new_with_broken_link_callback(markdown, Options::empty(), Some(callback));
4068 for _ in parser {}
4069 assert_eq!(times_called, expected);
4070 }
4071 }
4072
4073 #[test]
4074 fn simple_broken_link_callback() {
4075 let test_str = "This is a link w/o def: [hello][world]";
4076 let mut callback = |broken_link: BrokenLink| {
4077 assert_eq!("world", broken_link.reference.as_ref());
4078 assert_eq!(&test_str[broken_link.span], "[hello][world]");
4079 let url = "YOLO".into();
4080 let title = "SWAG".to_owned().into();
4081 Some((url, title))
4082 };
4083 let parser =
4084 Parser::new_with_broken_link_callback(test_str, Options::empty(), Some(&mut callback));
4085 let mut link_tag_count = 0;
4086 for (typ, url, title, id) in parser.filter_map(|event| match event {
4087 Event::Start(Tag::Link {
4088 link_type,
4089 dest_url,
4090 title,
4091 id,
4092 }) => Some((link_type, dest_url, title, id)),
4093 _ => None,
4094 }) {
4095 link_tag_count += 1;
4096 assert_eq!(typ, LinkType::ReferenceUnknown);
4097 assert_eq!(url.as_ref(), "YOLO");
4098 assert_eq!(title.as_ref(), "SWAG");
4099 assert_eq!(id.as_ref(), "world");
4100 }
4101 assert!(link_tag_count > 0);
4102 }
4103
4104 #[test]
4105 fn code_block_kind_check_fenced() {
4106 let parser = Parser::new("hello\n```test\ntadam\n```");
4107 let mut found = 0;
4108 for (ev, _range) in parser.into_offset_iter() {
4109 if let Event::Start(Tag::CodeBlock(CodeBlockKind::Fenced(syntax))) = ev {
4110 assert_eq!(syntax.as_ref(), "test");
4111 found += 1;
4112 }
4113 }
4114 assert_eq!(found, 1);
4115 }
4116
4117 #[test]
4118 fn code_block_kind_check_indented() {
4119 let parser = Parser::new("hello\n\n ```test\n tadam\nhello");
4120 let mut found = 0;
4121 for (ev, _range) in parser.into_offset_iter() {
4122 if let Event::Start(Tag::CodeBlock(CodeBlockKind::Indented)) = ev {
4123 found += 1;
4124 }
4125 }
4126 assert_eq!(found, 1);
4127 }
4128
4129 #[test]
4130 fn ref_defs() {
4131 let input = r###"[a B c]: http://example.com
4132[another]: https://google.com
4133
4134text
4135
4136[final ONE]: http://wikipedia.org
4137"###;
4138 let mut parser = Parser::new(input);
4139
4140 assert!(parser.reference_definitions().get("a b c").is_some());
4141 assert!(parser.reference_definitions().get("nope").is_none());
4142
4143 if let Some(_event) = parser.next() {
4144 let s = "final one".to_owned();
4146 let link_def = parser.reference_definitions().get(&s).unwrap();
4147 let span = &input[link_def.span.clone()];
4148 assert_eq!(span, "[final ONE]: http://wikipedia.org");
4149 }
4150 }
4151
4152 #[test]
4153 #[allow(clippy::extra_unused_lifetimes)]
4154 fn common_lifetime_patterns_allowed<'b>() {
4155 let temporary_str = String::from("xyz");
4156
4157 let mut closure = |link: BrokenLink<'b>| Some(("#".into(), link.reference));
4161
4162 fn function(link: BrokenLink<'_>) -> Option<(CowStr<'_>, CowStr<'_>)> {
4163 Some(("#".into(), link.reference))
4164 }
4165
4166 for _ in Parser::new_with_broken_link_callback(
4167 "static lifetime",
4168 Options::empty(),
4169 Some(&mut closure),
4170 ) {}
4171 for _ in Parser::new_with_broken_link_callback(
4180 "static lifetime",
4181 Options::empty(),
4182 Some(&mut function),
4183 ) {}
4184 for _ in Parser::new_with_broken_link_callback(
4185 &temporary_str,
4186 Options::empty(),
4187 Some(&mut function),
4188 ) {}
4189 }
4190
4191 #[test]
4192 fn inline_html_inside_blockquote() {
4193 let input = "> <foo\n> bar>";
4195 let events: Vec<_> = Parser::new(input).collect();
4196 let expected = [
4197 Event::Start(Tag::BlockQuote(None)),
4198 Event::Start(Tag::Paragraph),
4199 Event::InlineHtml(CowStr::Boxed("<foo\nbar>".to_string().into())),
4200 Event::End(TagEnd::Paragraph),
4201 Event::End(TagEnd::BlockQuote(None)),
4202 ];
4203 assert_eq!(&events, &expected);
4204 }
4205
4206 #[test]
4207 fn wikilink_has_pothole() {
4208 let input = "[[foo]] [[bar|baz]]";
4209 let events: Vec<_> = Parser::new_ext(input, Options::ENABLE_WIKILINKS).collect();
4210 let expected = [
4211 Event::Start(Tag::Paragraph),
4212 Event::Start(Tag::Link {
4213 link_type: LinkType::WikiLink { has_pothole: false },
4214 dest_url: CowStr::Borrowed("foo"),
4215 title: CowStr::Borrowed(""),
4216 id: CowStr::Borrowed(""),
4217 }),
4218 Event::Text(CowStr::Borrowed("foo")),
4219 Event::End(TagEnd::Link),
4220 Event::Text(CowStr::Borrowed(" ")),
4221 Event::Start(Tag::Link {
4222 link_type: LinkType::WikiLink { has_pothole: true },
4223 dest_url: CowStr::Borrowed("bar"),
4224 title: CowStr::Borrowed(""),
4225 id: CowStr::Borrowed(""),
4226 }),
4227 Event::Text(CowStr::Borrowed("baz")),
4228 Event::End(TagEnd::Link),
4229 Event::End(TagEnd::Paragraph),
4230 ];
4231 assert_eq!(&events, &expected);
4232 }
4233
4234 #[cfg(feature = "mdx")]
4235 fn mdx_parser(text: &str) -> Parser<'_> {
4236 Parser::new_ext(text, Options::ENABLE_MDX)
4237 }
4238
4239 #[cfg(feature = "mdx")]
4240 #[test]
4241 fn mdx_esm_import() {
4242 let events: Vec<_> = mdx_parser("import {Chart} from './chart.js'\n").collect();
4243 assert_eq!(events.len(), 1);
4244 assert!(matches!(&events[0], Event::MdxEsm(s) if s.contains("import")));
4245 }
4246
4247 #[cfg(feature = "mdx")]
4248 #[test]
4249 fn mdx_esm_export() {
4250 let events: Vec<_> = mdx_parser("export const meta = {}\n").collect();
4251 assert_eq!(events.len(), 1);
4252 assert!(matches!(&events[0], Event::MdxEsm(s) if s.contains("export")));
4253 }
4254
4255 #[cfg(feature = "mdx")]
4256 #[test]
4257 fn mdx_flow_expression() {
4258 let events: Vec<_> = mdx_parser("{1 + 1}\n").collect();
4259 assert_eq!(events.len(), 1);
4260 assert!(matches!(&events[0], Event::MdxFlowExpression(s) if s.as_ref() == "1 + 1"));
4261 }
4262
4263 #[cfg(feature = "mdx")]
4264 #[test]
4265 fn mdx_jsx_flow_self_closing() {
4266 let events: Vec<_> = mdx_parser("<Chart values={[1,2,3]} />\n").collect();
4267 assert!(!events.is_empty());
4268 assert!(
4269 matches!(&events[0], Event::Start(Tag::MdxJsxFlowElement(s)) if s.contains("Chart"))
4270 );
4271 }
4272
4273 #[cfg(feature = "mdx")]
4274 #[test]
4275 fn mdx_jsx_flow_fragment() {
4276 let events: Vec<_> = mdx_parser("<>\n").collect();
4277 assert!(!events.is_empty());
4278 assert!(matches!(
4279 &events[0],
4280 Event::Start(Tag::MdxJsxFlowElement(_))
4281 ));
4282 }
4283
4284 #[cfg(feature = "mdx")]
4285 #[test]
4286 fn mdx_inline_expression() {
4287 let events: Vec<_> = mdx_parser("hello {name} world\n").collect();
4288 let has_expr = events
4289 .iter()
4290 .any(|e| matches!(e, Event::MdxTextExpression(s) if s.as_ref() == "name"));
4291 assert!(
4292 has_expr,
4293 "Expected inline MDX expression, got: {:?}",
4294 events
4295 );
4296 }
4297
4298 #[cfg(feature = "mdx")]
4299 #[test]
4300 fn mdx_inline_jsx() {
4301 let events: Vec<_> = mdx_parser("hello <Badge /> world\n").collect();
4302 let has_jsx = events
4303 .iter()
4304 .any(|e| matches!(e, Event::Start(Tag::MdxJsxTextElement(s)) if s.contains("Badge")));
4305 assert!(has_jsx, "Expected inline MDX JSX, got: {:?}", events);
4306 }
4307
4308 #[cfg(feature = "mdx")]
4309 #[test]
4310 fn mdx_all_tags_are_jsx() {
4311 let events: Vec<_> = mdx_parser("hello <em>world</em>\n").collect();
4313 let has_jsx = events
4314 .iter()
4315 .any(|e| matches!(e, Event::Start(Tag::MdxJsxTextElement(_))));
4316 assert!(has_jsx, "In MDX mode, <em> should be JSX: {:?}", events);
4317 }
4318
4319 #[test]
4320 fn mdx_does_not_interfere_without_flag() {
4321 let events: Vec<_> = Parser::new("import foo from 'bar'\n").collect();
4323 assert!(
4325 events
4326 .iter()
4327 .any(|e| matches!(e, Event::Start(Tag::Paragraph)))
4328 );
4329 }
4330
4331 #[cfg(feature = "mdx")]
4332 #[test]
4333 fn mdx_expression_in_heading() {
4334 let events: Vec<_> = mdx_parser("# {title}\n").collect();
4335 let has_heading = events
4336 .iter()
4337 .any(|e| matches!(e, Event::Start(Tag::Heading { .. })));
4338 assert!(has_heading, "Should have a heading");
4339 let has_expr = events
4340 .iter()
4341 .any(|e| matches!(e, Event::MdxTextExpression(s) if s.as_ref() == "title"));
4342 assert!(
4343 has_expr,
4344 "Heading should contain MdxTextExpression, got: {:?}",
4345 events
4346 );
4347 }
4348
4349 #[cfg(feature = "mdx")]
4350 #[test]
4351 fn mdx_expression_mixed_text_in_heading() {
4352 let events: Vec<_> = mdx_parser("## Hello {name}\n").collect();
4353 let has_text = events
4354 .iter()
4355 .any(|e| matches!(e, Event::Text(s) if s.contains("Hello")));
4356 let has_expr = events
4357 .iter()
4358 .any(|e| matches!(e, Event::MdxTextExpression(s) if s.as_ref() == "name"));
4359 assert!(has_text, "Should have text, got: {:?}", events);
4360 assert!(has_expr, "Should have expression, got: {:?}", events);
4361 }
4362}