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