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