Markdown parser fork with extended syntax for personal use.
1//! Turn events into a syntax tree.
2
3use crate::event::{Event, Kind, Name};
4use crate::mdast::{
5 AttributeContent, AttributeValue, AttributeValueExpression, Blockquote, Break, Code,
6 Definition, Delete, Emphasis, FootnoteDefinition, FootnoteReference, Heading, Html, Image,
7 ImageReference, InlineCode, InlineMath, Link, LinkReference, List, ListItem, Math,
8 MdxFlowExpression, MdxJsxAttribute, MdxJsxExpressionAttribute, MdxJsxFlowElement,
9 MdxJsxTextElement, MdxTextExpression, MdxjsEsm, Node, Paragraph, ReferenceKind, Root, Strong,
10 Table, TableCell, TableRow, Text, ThematicBreak, Toml, Yaml,
11};
12use crate::message;
13use crate::unist::{Point, Position};
14use crate::util::{
15 character_reference::{
16 decode as decode_character_reference, parse as parse_character_reference,
17 },
18 infer::{gfm_table_align, list_item_loose, list_loose},
19 mdx_collect::{collect, Result as CollectResult},
20 normalize_identifier::normalize_identifier,
21 slice::{Position as SlicePosition, Slice},
22};
23use alloc::{
24 boxed::Box,
25 format,
26 string::{String, ToString},
27 vec,
28 vec::Vec,
29};
30use core::str;
31
32/// A reference to something.
33#[derive(Debug)]
34struct Reference {
35 #[allow(clippy::struct_field_names)]
36 reference_kind: Option<ReferenceKind>,
37 identifier: String,
38 label: String,
39}
40
41/// Info on a tag.
42///
43/// JSX tags are parsed on their own.
44/// They’re matched together here.
45#[derive(Debug, Clone)]
46struct JsxTag {
47 /// Optional tag name.
48 ///
49 /// `None` means that it’s a fragment.
50 name: Option<String>,
51 /// List of attributes.
52 attributes: Vec<AttributeContent>,
53 /// Whether this is a closing tag.
54 ///
55 /// ```markdown
56 /// > | </a>
57 /// ^
58 /// ```
59 close: bool,
60 /// Whether this is a self-closing tag.
61 ///
62 /// ```markdown
63 /// > | <a/>
64 /// ^
65 /// ```
66 self_closing: bool,
67 /// Starting point.
68 start: Point,
69 /// Ending point.
70 end: Point,
71}
72
73impl Reference {
74 fn new() -> Reference {
75 Reference {
76 // Assume shortcut: removed on a resource, changed on a reference.
77 reference_kind: Some(ReferenceKind::Shortcut),
78 identifier: String::new(),
79 label: String::new(),
80 }
81 }
82}
83
84/// Context used to compile markdown.
85#[allow(clippy::struct_excessive_bools)]
86#[derive(Debug)]
87struct CompileContext<'a> {
88 // Static info.
89 /// List of events.
90 events: &'a [Event],
91 /// List of bytes.
92 bytes: &'a [u8],
93 // Fields used by handlers to track the things they need to track to
94 // compile markdown.
95 character_reference_marker: u8,
96 gfm_table_inside: bool,
97 hard_break_after: bool,
98 heading_setext_text_after: bool,
99 jsx_tag_stack: Vec<JsxTag>,
100 jsx_tag: Option<JsxTag>,
101 media_reference_stack: Vec<Reference>,
102 raw_flow_fence_seen: bool,
103 // Intermediate results.
104 /// Primary tree and buffers.
105 trees: Vec<(Node, Vec<usize>, Vec<usize>)>,
106 /// Current event index.
107 index: usize,
108}
109
110impl<'a> CompileContext<'a> {
111 /// Create a new compile context.
112 fn new(events: &'a [Event], bytes: &'a [u8]) -> CompileContext<'a> {
113 let tree = Node::Root(Root {
114 children: vec![],
115 position: Some(Position {
116 start: if events.is_empty() {
117 Point::new(1, 1, 0)
118 } else {
119 events[0].point.to_unist()
120 },
121 end: if events.is_empty() {
122 Point::new(1, 1, 0)
123 } else {
124 events[events.len() - 1].point.to_unist()
125 },
126 }),
127 });
128
129 CompileContext {
130 events,
131 bytes,
132 character_reference_marker: 0,
133 gfm_table_inside: false,
134 hard_break_after: false,
135 heading_setext_text_after: false,
136 jsx_tag_stack: vec![],
137 jsx_tag: None,
138 media_reference_stack: vec![],
139 raw_flow_fence_seen: false,
140 trees: vec![(tree, vec![], vec![])],
141 index: 0,
142 }
143 }
144
145 /// Push a buffer.
146 fn buffer(&mut self) {
147 self.trees.push((
148 Node::Paragraph(Paragraph {
149 children: vec![],
150 position: None,
151 }),
152 vec![],
153 vec![],
154 ));
155 }
156
157 /// Pop a buffer, returning its value.
158 fn resume(&mut self) -> Node {
159 if let Some((node, stack_a, stack_b)) = self.trees.pop() {
160 debug_assert_eq!(
161 stack_a.len(),
162 0,
163 "expected stack (nodes in tree) to be drained"
164 );
165 debug_assert_eq!(
166 stack_b.len(),
167 0,
168 "expected stack (opening events) to be drained"
169 );
170 node
171 } else {
172 unreachable!("Cannot resume w/o buffer")
173 }
174 }
175
176 fn tail_mut(&mut self) -> &mut Node {
177 let (tree, stack, _) = self.trees.last_mut().expect("Cannot get tail w/o tree");
178 delve_mut(tree, stack)
179 }
180
181 fn tail_penultimate_mut(&mut self) -> &mut Node {
182 let (tree, stack, _) = self.trees.last_mut().expect("Cannot get tail w/o tree");
183 delve_mut(tree, &stack[0..(stack.len() - 1)])
184 }
185
186 fn tail_push(&mut self, mut child: Node) {
187 if child.position().is_none() {
188 child.position_set(Some(position_from_event(&self.events[self.index])));
189 }
190
191 let (tree, stack, event_stack) = self.trees.last_mut().expect("Cannot get tail w/o tree");
192 let node = delve_mut(tree, stack);
193 let children = node.children_mut().expect("Cannot push to non-parent");
194 let index = children.len();
195 children.push(child);
196 stack.push(index);
197 event_stack.push(self.index);
198 }
199
200 fn tail_push_again(&mut self) {
201 let (tree, stack, event_stack) = self.trees.last_mut().expect("Cannot get tail w/o tree");
202 let node = delve_mut(tree, stack);
203 let children = node.children().expect("Cannot push to non-parent");
204 stack.push(children.len() - 1);
205 event_stack.push(self.index);
206 }
207
208 fn tail_pop(&mut self) -> Result<(), message::Message> {
209 let ev = &self.events[self.index];
210 let end = ev.point.to_unist();
211 let (tree, stack, event_stack) = self.trees.last_mut().expect("Cannot get tail w/o tree");
212 let node = delve_mut(tree, stack);
213 let pos = node.position_mut().expect("Cannot pop manually added node");
214 pos.end = end;
215
216 stack.pop().unwrap();
217 let left_index = event_stack.pop().unwrap();
218 let left = &self.events[left_index];
219 if left.name != ev.name {
220 on_mismatch_error(self, Some(ev), left)?;
221 }
222
223 Ok(())
224 }
225}
226
227/// Turn events and bytes into a syntax tree.
228pub fn compile(events: &[Event], bytes: &[u8]) -> Result<Node, message::Message> {
229 let mut context = CompileContext::new(events, bytes);
230
231 let mut index = 0;
232 while index < events.len() {
233 handle(&mut context, index)?;
234 index += 1;
235 }
236
237 debug_assert_eq!(context.trees.len(), 1, "expected 1 final tree");
238 let (tree, _, event_stack) = context.trees.pop().unwrap();
239
240 if let Some(index) = event_stack.last() {
241 let event = &events[*index];
242 on_mismatch_error(&mut context, None, event)?;
243 }
244
245 Ok(tree)
246}
247
248/// Handle the event at `index`.
249fn handle(context: &mut CompileContext, index: usize) -> Result<(), message::Message> {
250 context.index = index;
251
252 if context.events[index].kind == Kind::Enter {
253 enter(context)?;
254 } else {
255 exit(context)?;
256 }
257
258 Ok(())
259}
260
261/// Handle [`Enter`][Kind::Enter].
262fn enter(context: &mut CompileContext) -> Result<(), message::Message> {
263 match context.events[context.index].name {
264 Name::AutolinkEmail
265 | Name::AutolinkProtocol
266 | Name::CharacterEscapeValue
267 | Name::CharacterReference
268 | Name::CodeFlowChunk
269 | Name::CodeTextData
270 | Name::Data
271 | Name::FrontmatterChunk
272 | Name::HtmlFlowData
273 | Name::HtmlTextData
274 | Name::MathFlowChunk
275 | Name::MathTextData
276 | Name::MdxJsxTagAttributeValueLiteralValue => on_enter_data(context),
277 Name::CodeFencedFenceInfo
278 | Name::CodeFencedFenceMeta
279 | Name::DefinitionDestinationString
280 | Name::DefinitionLabelString
281 | Name::DefinitionTitleString
282 | Name::GfmFootnoteDefinitionLabelString
283 | Name::LabelText
284 | Name::MathFlowFenceMeta
285 | Name::MdxJsxTagAttributeValueLiteral
286 | Name::ReferenceString
287 | Name::ResourceDestinationString
288 | Name::ResourceTitleString => on_enter_buffer(context),
289 Name::Autolink => on_enter_autolink(context),
290 Name::BlockQuote => on_enter_block_quote(context),
291 Name::CodeFenced => on_enter_code_fenced(context),
292 Name::CodeIndented => on_enter_code_indented(context),
293 Name::CodeText => on_enter_code_text(context),
294 Name::Definition => on_enter_definition(context),
295 Name::Emphasis => on_enter_emphasis(context),
296 Name::Frontmatter => on_enter_frontmatter(context),
297 Name::GfmAutolinkLiteralEmail
298 | Name::GfmAutolinkLiteralMailto
299 | Name::GfmAutolinkLiteralProtocol
300 | Name::GfmAutolinkLiteralWww
301 | Name::GfmAutolinkLiteralXmpp => on_enter_gfm_autolink_literal(context),
302 Name::GfmFootnoteCall => on_enter_gfm_footnote_call(context),
303 Name::GfmFootnoteDefinition => on_enter_gfm_footnote_definition(context),
304 Name::GfmStrikethrough => on_enter_gfm_strikethrough(context),
305 Name::GfmTable => on_enter_gfm_table(context),
306 Name::GfmTableRow => on_enter_gfm_table_row(context),
307 Name::GfmTableCell => on_enter_gfm_table_cell(context),
308 Name::HardBreakEscape | Name::HardBreakTrailing => on_enter_hard_break(context),
309 Name::HeadingAtx | Name::HeadingSetext => on_enter_heading(context),
310 Name::HtmlFlow | Name::HtmlText => on_enter_html(context),
311 Name::Image => on_enter_image(context),
312 Name::Link => on_enter_link(context),
313 Name::ListItem => on_enter_list_item(context),
314 Name::ListOrdered | Name::ListUnordered => on_enter_list(context),
315 Name::MathFlow => on_enter_math_flow(context),
316 Name::MathText => on_enter_math_text(context),
317 Name::MdxEsm => on_enter_mdx_esm(context),
318 Name::MdxFlowExpression => on_enter_mdx_flow_expression(context),
319 Name::MdxTextExpression => on_enter_mdx_text_expression(context),
320 Name::MdxJsxFlowTag | Name::MdxJsxTextTag => on_enter_mdx_jsx_tag(context),
321 Name::MdxJsxTagClosingMarker => on_enter_mdx_jsx_tag_closing_marker(context)?,
322 Name::MdxJsxTagAttribute => on_enter_mdx_jsx_tag_attribute(context)?,
323 Name::MdxJsxTagAttributeExpression => on_enter_mdx_jsx_tag_attribute_expression(context)?,
324 Name::MdxJsxTagAttributeValueExpression => {
325 on_enter_mdx_jsx_tag_attribute_value_expression(context);
326 }
327 Name::MdxJsxTagSelfClosingMarker => on_enter_mdx_jsx_tag_self_closing_marker(context)?,
328 Name::Paragraph => on_enter_paragraph(context),
329 Name::Reference => on_enter_reference(context),
330 Name::Resource => on_enter_resource(context),
331 Name::Strong => on_enter_strong(context),
332 Name::ThematicBreak => on_enter_thematic_break(context),
333 _ => {}
334 }
335
336 Ok(())
337}
338
339/// Handle [`Exit`][Kind::Exit].
340fn exit(context: &mut CompileContext) -> Result<(), message::Message> {
341 match context.events[context.index].name {
342 Name::Autolink
343 | Name::BlockQuote
344 | Name::CharacterReference
345 | Name::Definition
346 | Name::Emphasis
347 | Name::GfmFootnoteDefinition
348 | Name::GfmStrikethrough
349 | Name::GfmTableRow
350 | Name::GfmTableCell
351 | Name::HeadingAtx
352 | Name::ListOrdered
353 | Name::ListUnordered
354 | Name::Paragraph
355 | Name::Strong
356 | Name::ThematicBreak => {
357 on_exit(context)?;
358 }
359 Name::CharacterEscapeValue
360 | Name::CodeFlowChunk
361 | Name::CodeTextData
362 | Name::Data
363 | Name::FrontmatterChunk
364 | Name::HtmlFlowData
365 | Name::HtmlTextData
366 | Name::MathFlowChunk
367 | Name::MathTextData
368 | Name::MdxJsxTagAttributeValueLiteralValue => {
369 on_exit_data(context)?;
370 }
371 Name::MdxJsxTagAttributeExpression | Name::MdxJsxTagAttributeValueExpression => {
372 on_exit_drop(context);
373 }
374 Name::AutolinkProtocol => on_exit_autolink_protocol(context)?,
375 Name::AutolinkEmail => on_exit_autolink_email(context)?,
376 Name::CharacterReferenceMarker => on_exit_character_reference_marker(context),
377 Name::CharacterReferenceMarkerNumeric => {
378 on_exit_character_reference_marker_numeric(context);
379 }
380 Name::CharacterReferenceMarkerHexadecimal => {
381 on_exit_character_reference_marker_hexadecimal(context);
382 }
383 Name::CharacterReferenceValue => on_exit_character_reference_value(context),
384 Name::CodeFencedFenceInfo => on_exit_code_fenced_fence_info(context),
385 Name::CodeFencedFenceMeta | Name::MathFlowFenceMeta => on_exit_raw_flow_fence_meta(context),
386 Name::CodeFencedFence | Name::MathFlowFence => on_exit_raw_flow_fence(context),
387 Name::CodeFenced | Name::MathFlow => on_exit_raw_flow(context)?,
388 Name::CodeIndented => on_exit_code_indented(context)?,
389 Name::CodeText | Name::MathText => on_exit_raw_text(context)?,
390 Name::DefinitionDestinationString => on_exit_definition_destination_string(context),
391 Name::DefinitionLabelString | Name::GfmFootnoteDefinitionLabelString => {
392 on_exit_definition_id(context);
393 }
394 Name::DefinitionTitleString => on_exit_definition_title_string(context),
395 Name::Frontmatter => on_exit_frontmatter(context)?,
396 Name::GfmAutolinkLiteralEmail
397 | Name::GfmAutolinkLiteralMailto
398 | Name::GfmAutolinkLiteralProtocol
399 | Name::GfmAutolinkLiteralWww
400 | Name::GfmAutolinkLiteralXmpp => on_exit_gfm_autolink_literal(context)?,
401 Name::GfmFootnoteCall | Name::Image | Name::Link => on_exit_media(context)?,
402 Name::GfmTable => on_exit_gfm_table(context)?,
403 Name::GfmTaskListItemValueUnchecked | Name::GfmTaskListItemValueChecked => {
404 on_exit_gfm_task_list_item_value(context);
405 }
406 Name::HardBreakEscape | Name::HardBreakTrailing => on_exit_hard_break(context)?,
407 Name::HeadingAtxSequence => on_exit_heading_atx_sequence(context),
408 Name::HeadingSetext => on_exit_heading_setext(context)?,
409 Name::HeadingSetextUnderlineSequence => on_exit_heading_setext_underline_sequence(context),
410 Name::HeadingSetextText => on_exit_heading_setext_text(context),
411 Name::HtmlFlow | Name::HtmlText => on_exit_html(context)?,
412 Name::LabelText => on_exit_label_text(context),
413 Name::LineEnding => on_exit_line_ending(context)?,
414 Name::ListItem => on_exit_list_item(context)?,
415 Name::ListItemValue => on_exit_list_item_value(context),
416 Name::MdxEsm | Name::MdxFlowExpression | Name::MdxTextExpression => {
417 on_exit_mdx_esm_or_expression(context)?;
418 }
419 Name::MdxJsxFlowTag | Name::MdxJsxTextTag => on_exit_mdx_jsx_tag(context)?,
420 Name::MdxJsxTagClosingMarker => on_exit_mdx_jsx_tag_closing_marker(context),
421 Name::MdxJsxTagNamePrimary => on_exit_mdx_jsx_tag_name_primary(context),
422 Name::MdxJsxTagNameMember => on_exit_mdx_jsx_tag_name_member(context),
423 Name::MdxJsxTagNameLocal => on_exit_mdx_jsx_tag_name_local(context),
424 Name::MdxJsxTagAttributePrimaryName => on_exit_mdx_jsx_tag_attribute_primary_name(context),
425 Name::MdxJsxTagAttributeNameLocal => on_exit_mdx_jsx_tag_attribute_name_local(context),
426 Name::MdxJsxTagAttributeValueLiteral => {
427 on_exit_mdx_jsx_tag_attribute_value_literal(context);
428 }
429 Name::MdxJsxTagSelfClosingMarker => on_exit_mdx_jsx_tag_self_closing_marker(context),
430
431 Name::ReferenceString => on_exit_reference_string(context),
432 Name::ResourceDestinationString => on_exit_resource_destination_string(context),
433 Name::ResourceTitleString => on_exit_resource_title_string(context),
434 _ => {}
435 }
436
437 Ok(())
438}
439
440/// Handle [`Enter`][Kind::Enter]:`*`.
441fn on_enter_buffer(context: &mut CompileContext) {
442 context.buffer();
443}
444
445/// Handle [`Enter`][Kind::Enter]:[`Data`][Name::Data] (and many text things).
446fn on_enter_data(context: &mut CompileContext) {
447 let parent = context.tail_mut();
448 let children = parent.children_mut().expect("expected parent");
449
450 // Add to stack again.
451 if let Some(Node::Text(_)) = children.last_mut() {
452 context.tail_push_again();
453 } else {
454 context.tail_push(Node::Text(Text {
455 value: String::new(),
456 position: None,
457 }));
458 }
459}
460
461/// Handle [`Enter`][Kind::Enter]:[`Autolink`][Name::Autolink].
462fn on_enter_autolink(context: &mut CompileContext) {
463 context.tail_push(Node::Link(Link {
464 url: String::new(),
465 title: None,
466 children: vec![],
467 position: None,
468 }));
469}
470
471/// Handle [`Enter`][Kind::Enter]:[`BlockQuote`][Name::BlockQuote].
472fn on_enter_block_quote(context: &mut CompileContext) {
473 context.tail_push(Node::Blockquote(Blockquote {
474 children: vec![],
475 position: None,
476 }));
477}
478
479/// Handle [`Enter`][Kind::Enter]:[`CodeFenced`][Name::CodeFenced].
480fn on_enter_code_fenced(context: &mut CompileContext) {
481 context.tail_push(Node::Code(Code {
482 lang: None,
483 meta: None,
484 value: String::new(),
485 position: None,
486 }));
487}
488
489/// Handle [`Enter`][Kind::Enter]:[`CodeIndented`][Name::CodeIndented].
490fn on_enter_code_indented(context: &mut CompileContext) {
491 on_enter_code_fenced(context);
492 on_enter_buffer(context);
493}
494
495/// Handle [`Enter`][Kind::Enter]:[`CodeText`][Name::CodeText].
496fn on_enter_code_text(context: &mut CompileContext) {
497 context.tail_push(Node::InlineCode(InlineCode {
498 value: String::new(),
499 position: None,
500 }));
501 context.buffer();
502}
503
504/// Handle [`Enter`][Kind::Enter]:[`MathText`][Name::MathText].
505fn on_enter_math_text(context: &mut CompileContext) {
506 context.tail_push(Node::InlineMath(InlineMath {
507 value: String::new(),
508 position: None,
509 }));
510 context.buffer();
511}
512
513/// Handle [`Enter`][Kind::Enter]:[`MdxEsm`][Name::MdxEsm].
514fn on_enter_mdx_esm(context: &mut CompileContext) {
515 let result = collect(
516 context.events,
517 context.bytes,
518 context.index,
519 &[Name::MdxEsmData, Name::LineEnding],
520 &[Name::MdxEsm],
521 );
522 context.tail_push(Node::MdxjsEsm(MdxjsEsm {
523 value: result.value,
524 position: None,
525 stops: result.stops,
526 }));
527 context.buffer();
528}
529
530/// Handle [`Enter`][Kind::Enter]:[`MdxFlowExpression`][Name::MdxFlowExpression].
531fn on_enter_mdx_flow_expression(context: &mut CompileContext) {
532 let result = collect(
533 context.events,
534 context.bytes,
535 context.index,
536 &[Name::MdxExpressionData, Name::LineEnding],
537 &[Name::MdxFlowExpression],
538 );
539 context.tail_push(Node::MdxFlowExpression(MdxFlowExpression {
540 value: result.value,
541 position: None,
542 stops: result.stops,
543 }));
544 context.buffer();
545}
546
547/// Handle [`Enter`][Kind::Enter]:[`MdxTextExpression`][Name::MdxTextExpression].
548fn on_enter_mdx_text_expression(context: &mut CompileContext) {
549 let result = collect(
550 context.events,
551 context.bytes,
552 context.index,
553 &[Name::MdxExpressionData, Name::LineEnding],
554 &[Name::MdxTextExpression],
555 );
556 context.tail_push(Node::MdxTextExpression(MdxTextExpression {
557 value: result.value,
558 position: None,
559 stops: result.stops,
560 }));
561 context.buffer();
562}
563
564/// Handle [`Enter`][Kind::Enter]:[`Definition`][Name::Definition].
565fn on_enter_definition(context: &mut CompileContext) {
566 context.tail_push(Node::Definition(Definition {
567 url: String::new(),
568 identifier: String::new(),
569 label: None,
570 title: None,
571 position: None,
572 }));
573}
574
575/// Handle [`Enter`][Kind::Enter]:[`Emphasis`][Name::Emphasis].
576fn on_enter_emphasis(context: &mut CompileContext) {
577 context.tail_push(Node::Emphasis(Emphasis {
578 children: vec![],
579 position: None,
580 }));
581}
582
583/// Handle [`Enter`][Kind::Enter]:{[`GfmAutolinkLiteralEmail`][Name::GfmAutolinkLiteralEmail],[`GfmAutolinkLiteralMailto`][Name::GfmAutolinkLiteralMailto],[`GfmAutolinkLiteralProtocol`][Name::GfmAutolinkLiteralProtocol],[`GfmAutolinkLiteralWww`][Name::GfmAutolinkLiteralWww],[`GfmAutolinkLiteralXmpp`][Name::GfmAutolinkLiteralXmpp]}.
584fn on_enter_gfm_autolink_literal(context: &mut CompileContext) {
585 on_enter_autolink(context);
586 on_enter_data(context);
587}
588
589/// Handle [`Enter`][Kind::Enter]:[`GfmFootnoteCall`][Name::GfmFootnoteCall].
590fn on_enter_gfm_footnote_call(context: &mut CompileContext) {
591 context.tail_push(Node::FootnoteReference(FootnoteReference {
592 identifier: String::new(),
593 label: None,
594 position: None,
595 }));
596 context.media_reference_stack.push(Reference::new());
597}
598
599/// Handle [`Enter`][Kind::Enter]:[`GfmFootnoteDefinition`][Name::GfmFootnoteDefinition].
600fn on_enter_gfm_footnote_definition(context: &mut CompileContext) {
601 context.tail_push(Node::FootnoteDefinition(FootnoteDefinition {
602 identifier: String::new(),
603 label: None,
604 children: vec![],
605 position: None,
606 }));
607}
608
609/// Handle [`Enter`][Kind::Enter]:[`GfmStrikethrough`][Name::GfmStrikethrough].
610fn on_enter_gfm_strikethrough(context: &mut CompileContext) {
611 context.tail_push(Node::Delete(Delete {
612 children: vec![],
613 position: None,
614 }));
615}
616
617/// Handle [`Enter`][Kind::Enter]:[`GfmTable`][Name::GfmTable].
618fn on_enter_gfm_table(context: &mut CompileContext) {
619 let align = gfm_table_align(context.events, context.index);
620 context.tail_push(Node::Table(Table {
621 align,
622 children: vec![],
623 position: None,
624 }));
625 context.gfm_table_inside = true;
626}
627
628/// Handle [`Enter`][Kind::Enter]:[`GfmTableRow`][Name::GfmTableRow].
629fn on_enter_gfm_table_row(context: &mut CompileContext) {
630 context.tail_push(Node::TableRow(TableRow {
631 children: vec![],
632 position: None,
633 }));
634}
635
636/// Handle [`Enter`][Kind::Enter]:[`GfmTableCell`][Name::GfmTableCell].
637fn on_enter_gfm_table_cell(context: &mut CompileContext) {
638 context.tail_push(Node::TableCell(TableCell {
639 children: vec![],
640 position: None,
641 }));
642}
643
644/// Handle [`Enter`][Kind::Enter]:[`HardBreakEscape`][Name::HardBreakEscape].
645fn on_enter_hard_break(context: &mut CompileContext) {
646 context.tail_push(Node::Break(Break { position: None }));
647}
648
649/// Handle [`Enter`][Kind::Enter]:[`Frontmatter`][Name::Frontmatter].
650fn on_enter_frontmatter(context: &mut CompileContext) {
651 let index = context.events[context.index].point.index;
652 let byte = context.bytes[index];
653 let node = if byte == b'+' {
654 Node::Toml(Toml {
655 value: String::new(),
656 position: None,
657 })
658 } else {
659 Node::Yaml(Yaml {
660 value: String::new(),
661 position: None,
662 })
663 };
664
665 context.tail_push(node);
666 context.buffer();
667}
668
669/// Handle [`Enter`][Kind::Enter]:[`Reference`][Name::Reference].
670fn on_enter_reference(context: &mut CompileContext) {
671 let reference = context
672 .media_reference_stack
673 .last_mut()
674 .expect("expected reference on media stack");
675 // Assume collapsed.
676 // If there’s a string after it, we set `Full`.
677 reference.reference_kind = Some(ReferenceKind::Collapsed);
678}
679
680/// Handle [`Enter`][Kind::Enter]:[`Resource`][Name::Resource].
681fn on_enter_resource(context: &mut CompileContext) {
682 let reference = context
683 .media_reference_stack
684 .last_mut()
685 .expect("expected reference on media stack");
686 // It’s not a reference.
687 reference.reference_kind = None;
688}
689
690/// Handle [`Enter`][Kind::Enter]:[`Strong`][Name::Strong].
691fn on_enter_strong(context: &mut CompileContext) {
692 context.tail_push(Node::Strong(Strong {
693 children: vec![],
694 position: None,
695 }));
696}
697
698/// Handle [`Enter`][Kind::Enter]:[`ThematicBreak`][Name::ThematicBreak].
699fn on_enter_thematic_break(context: &mut CompileContext) {
700 context.tail_push(Node::ThematicBreak(ThematicBreak { position: None }));
701}
702
703/// Handle [`Enter`][Kind::Enter]:[`HeadingAtx`][Name::HeadingAtx].
704fn on_enter_heading(context: &mut CompileContext) {
705 context.tail_push(Node::Heading(Heading {
706 depth: 0, // Will be set later.
707 children: vec![],
708 position: None,
709 }));
710}
711
712/// Handle [`Enter`][Kind::Enter]:{[`HtmlFlow`][Name::HtmlFlow],[`HtmlText`][Name::HtmlText]}.
713fn on_enter_html(context: &mut CompileContext) {
714 context.tail_push(Node::Html(Html {
715 value: String::new(),
716 position: None,
717 }));
718 context.buffer();
719}
720
721/// Handle [`Enter`][Kind::Enter]:[`Image`][Name::Image].
722fn on_enter_image(context: &mut CompileContext) {
723 context.tail_push(Node::Image(Image {
724 url: String::new(),
725 title: None,
726 alt: String::new(),
727 position: None,
728 }));
729 context.media_reference_stack.push(Reference::new());
730}
731
732/// Handle [`Enter`][Kind::Enter]:[`Link`][Name::Link].
733fn on_enter_link(context: &mut CompileContext) {
734 context.tail_push(Node::Link(Link {
735 url: String::new(),
736 title: None,
737 children: vec![],
738 position: None,
739 }));
740 context.media_reference_stack.push(Reference::new());
741}
742
743/// Handle [`Enter`][Kind::Enter]:{[`ListOrdered`][Name::ListOrdered],[`ListUnordered`][Name::ListUnordered]}.
744fn on_enter_list(context: &mut CompileContext) {
745 let ordered = context.events[context.index].name == Name::ListOrdered;
746 let spread = list_loose(context.events, context.index, false);
747
748 context.tail_push(Node::List(List {
749 ordered,
750 spread,
751 start: None,
752 children: vec![],
753 position: None,
754 }));
755}
756
757/// Handle [`Enter`][Kind::Enter]:[`ListItem`][Name::ListItem].
758fn on_enter_list_item(context: &mut CompileContext) {
759 let spread = list_item_loose(context.events, context.index);
760
761 context.tail_push(Node::ListItem(ListItem {
762 spread,
763 checked: None,
764 children: vec![],
765 position: None,
766 }));
767}
768
769/// Handle [`Enter`][Kind::Enter]:[`MathFlow`][Name::MathFlow].
770fn on_enter_math_flow(context: &mut CompileContext) {
771 context.tail_push(Node::Math(Math {
772 meta: None,
773 value: String::new(),
774 position: None,
775 }));
776}
777
778/// Handle [`Enter`][Kind::Enter]:{[`MdxJsxFlowTag`][Name::MdxJsxFlowTag],[`MdxJsxTextTag`][Name::MdxJsxTextTag]}.
779fn on_enter_mdx_jsx_tag(context: &mut CompileContext) {
780 let point = context.events[context.index].point.to_unist();
781 context.jsx_tag = Some(JsxTag {
782 name: None,
783 attributes: vec![],
784 start: point.clone(),
785 end: point,
786 close: false,
787 self_closing: false,
788 });
789 context.buffer();
790}
791
792/// Handle [`Enter`][Kind::Enter]:[`MdxJsxTagClosingMarker`][Name::MdxJsxTagClosingMarker].
793fn on_enter_mdx_jsx_tag_closing_marker(
794 context: &mut CompileContext,
795) -> Result<(), message::Message> {
796 if context.jsx_tag_stack.is_empty() {
797 let event = &context.events[context.index];
798 Err(message::Message {
799 place: Some(Box::new(message::Place::Point(event.point.to_unist()))),
800 reason: "Unexpected closing slash `/` in tag, expected an open tag first".into(),
801 rule_id: Box::new("unexpected-closing-slash".into()),
802 source: Box::new("markdown-rs".into()),
803 })
804 } else {
805 Ok(())
806 }
807}
808
809/// Handle [`Enter`][Kind::Enter]:{[`MdxJsxTagAttribute`][Name::MdxJsxTagAttribute],[`MdxJsxTagAttributeExpression`][Name::MdxJsxTagAttributeExpression]}.
810fn on_enter_mdx_jsx_tag_any_attribute(
811 context: &mut CompileContext,
812) -> Result<(), message::Message> {
813 if context.jsx_tag.as_ref().expect("expected tag").close {
814 let event = &context.events[context.index];
815 Err(message::Message {
816 place: Some(Box::new(message::Place::Point(event.point.to_unist()))),
817 reason: "Unexpected attribute in closing tag, expected the end of the tag".into(),
818 rule_id: Box::new("unexpected-attribute".into()),
819 source: Box::new("markdown-rs".into()),
820 })
821 } else {
822 Ok(())
823 }
824}
825
826/// Handle [`Enter`][Kind::Enter]:[`MdxJsxTagAttribute`][Name::MdxJsxTagAttribute].
827fn on_enter_mdx_jsx_tag_attribute(context: &mut CompileContext) -> Result<(), message::Message> {
828 on_enter_mdx_jsx_tag_any_attribute(context)?;
829
830 context
831 .jsx_tag
832 .as_mut()
833 .expect("expected tag")
834 .attributes
835 .push(AttributeContent::Property(MdxJsxAttribute {
836 name: String::new(),
837 value: None,
838 }));
839
840 Ok(())
841}
842
843/// Handle [`Enter`][Kind::Enter]:[`MdxJsxTagAttributeExpression`][Name::MdxJsxTagAttributeExpression].
844fn on_enter_mdx_jsx_tag_attribute_expression(
845 context: &mut CompileContext,
846) -> Result<(), message::Message> {
847 on_enter_mdx_jsx_tag_any_attribute(context)?;
848
849 let CollectResult { value, stops } = collect(
850 context.events,
851 context.bytes,
852 context.index,
853 &[Name::MdxExpressionData, Name::LineEnding],
854 &[Name::MdxJsxTagAttributeExpression],
855 );
856 context
857 .jsx_tag
858 .as_mut()
859 .expect("expected tag")
860 .attributes
861 .push(AttributeContent::Expression(MdxJsxExpressionAttribute {
862 value,
863 stops,
864 }));
865
866 context.buffer();
867
868 Ok(())
869}
870
871/// Handle [`Enter`][Kind::Enter]:[`MdxJsxTagAttributeValueExpression`][Name::MdxJsxTagAttributeValueExpression].
872fn on_enter_mdx_jsx_tag_attribute_value_expression(context: &mut CompileContext) {
873 let CollectResult { value, stops } = collect(
874 context.events,
875 context.bytes,
876 context.index,
877 &[Name::MdxExpressionData, Name::LineEnding],
878 &[Name::MdxJsxTagAttributeValueExpression],
879 );
880
881 if let Some(AttributeContent::Property(node)) = context
882 .jsx_tag
883 .as_mut()
884 .expect("expected tag")
885 .attributes
886 .last_mut()
887 {
888 node.value = Some(AttributeValue::Expression(AttributeValueExpression {
889 value,
890 stops,
891 }));
892 } else {
893 unreachable!("expected property")
894 }
895
896 context.buffer();
897}
898
899/// Handle [`Enter`][Kind::Enter]:[`MdxJsxTagSelfClosingMarker`][Name::MdxJsxTagSelfClosingMarker].
900fn on_enter_mdx_jsx_tag_self_closing_marker(
901 context: &mut CompileContext,
902) -> Result<(), message::Message> {
903 let tag = context.jsx_tag.as_ref().expect("expected tag");
904 if tag.close {
905 let event = &context.events[context.index];
906 Err(message::Message {
907 place: Some(Box::new(message::Place::Point(event.point.to_unist()))),
908 reason: "Unexpected self-closing slash `/` in closing tag, expected the end of the tag"
909 .into(),
910 rule_id: Box::new("unexpected-self-closing-slash".into()),
911 source: Box::new("markdown-rs".into()),
912 })
913 } else {
914 Ok(())
915 }
916}
917
918/// Handle [`Enter`][Kind::Enter]:[`Paragraph`][Name::Paragraph].
919fn on_enter_paragraph(context: &mut CompileContext) {
920 context.tail_push(Node::Paragraph(Paragraph {
921 children: vec![],
922 position: None,
923 }));
924}
925
926/// Handle [`Exit`][Kind::Exit]:`*`.
927fn on_exit(context: &mut CompileContext) -> Result<(), message::Message> {
928 context.tail_pop()?;
929 Ok(())
930}
931
932/// Handle [`Exit`][Kind::Exit]:[`AutolinkProtocol`][Name::AutolinkProtocol].
933fn on_exit_autolink_protocol(context: &mut CompileContext) -> Result<(), message::Message> {
934 on_exit_data(context)?;
935 let value = Slice::from_position(
936 context.bytes,
937 &SlicePosition::from_exit_event(context.events, context.index),
938 );
939 if let Node::Link(link) = context.tail_mut() {
940 link.url.push_str(value.as_str());
941 } else {
942 unreachable!("expected link on stack");
943 }
944 Ok(())
945}
946
947/// Handle [`Exit`][Kind::Exit]:[`AutolinkEmail`][Name::AutolinkEmail].
948fn on_exit_autolink_email(context: &mut CompileContext) -> Result<(), message::Message> {
949 on_exit_data(context)?;
950 let value = Slice::from_position(
951 context.bytes,
952 &SlicePosition::from_exit_event(context.events, context.index),
953 );
954 if let Node::Link(link) = context.tail_mut() {
955 link.url.push_str("mailto:");
956 link.url.push_str(value.as_str());
957 } else {
958 unreachable!("expected link on stack");
959 }
960 Ok(())
961}
962
963/// Handle [`Exit`][Kind::Exit]:[`CharacterReferenceMarker`][Name::CharacterReferenceMarker].
964fn on_exit_character_reference_marker(context: &mut CompileContext) {
965 context.character_reference_marker = b'&';
966}
967
968/// Handle [`Exit`][Kind::Exit]:[`CharacterReferenceMarkerHexadecimal`][Name::CharacterReferenceMarkerHexadecimal].
969fn on_exit_character_reference_marker_hexadecimal(context: &mut CompileContext) {
970 context.character_reference_marker = b'x';
971}
972
973/// Handle [`Exit`][Kind::Exit]:[`CharacterReferenceMarkerNumeric`][Name::CharacterReferenceMarkerNumeric].
974fn on_exit_character_reference_marker_numeric(context: &mut CompileContext) {
975 context.character_reference_marker = b'#';
976}
977
978/// Handle [`Exit`][Kind::Exit]:[`CharacterReferenceValue`][Name::CharacterReferenceValue].
979fn on_exit_character_reference_value(context: &mut CompileContext) {
980 let slice = Slice::from_position(
981 context.bytes,
982 &SlicePosition::from_exit_event(context.events, context.index),
983 );
984 let value =
985 decode_character_reference(slice.as_str(), context.character_reference_marker, true)
986 .expect("expected to parse only valid named references");
987
988 if let Node::Text(node) = context.tail_mut() {
989 node.value.push_str(value.as_str());
990 } else {
991 unreachable!("expected text on stack");
992 }
993
994 context.character_reference_marker = 0;
995}
996
997/// Handle [`Exit`][Kind::Exit]:[`CodeFencedFenceInfo`][Name::CodeFencedFenceInfo].
998fn on_exit_code_fenced_fence_info(context: &mut CompileContext) {
999 let value = context.resume().to_string();
1000 if let Node::Code(node) = context.tail_mut() {
1001 node.lang = Some(value);
1002 } else {
1003 unreachable!("expected code on stack");
1004 }
1005}
1006
1007/// Handle [`Exit`][Kind::Exit]:{[`CodeFencedFenceMeta`][Name::CodeFencedFenceMeta],[`MathFlowFenceMeta`][Name::MathFlowFenceMeta]}.
1008fn on_exit_raw_flow_fence_meta(context: &mut CompileContext) {
1009 let value = context.resume().to_string();
1010 match context.tail_mut() {
1011 Node::Code(node) => node.meta = Some(value),
1012 Node::Math(node) => node.meta = Some(value),
1013 _ => {
1014 unreachable!("expected code or math on stack");
1015 }
1016 }
1017}
1018
1019/// Handle [`Exit`][Kind::Exit]:{[`CodeFencedFence`][Name::CodeFencedFence],[`MathFlowFence`][Name::MathFlowFence]}.
1020fn on_exit_raw_flow_fence(context: &mut CompileContext) {
1021 if context.raw_flow_fence_seen {
1022 // Second fence, ignore.
1023 } else {
1024 context.buffer();
1025 context.raw_flow_fence_seen = true;
1026 }
1027}
1028
1029/// Handle [`Exit`][Kind::Exit]:{[`CodeFenced`][Name::CodeFenced],[`MathFlow`][Name::MathFlow]}.
1030fn on_exit_raw_flow(context: &mut CompileContext) -> Result<(), message::Message> {
1031 let value = trim_eol(context.resume().to_string(), true, true);
1032
1033 match context.tail_mut() {
1034 Node::Code(node) => node.value = value,
1035 Node::Math(node) => node.value = value,
1036 _ => unreachable!("expected code or math on stack for value"),
1037 }
1038
1039 on_exit(context)?;
1040 context.raw_flow_fence_seen = false;
1041 Ok(())
1042}
1043
1044/// Handle [`Exit`][Kind::Exit]:[`CodeIndented`][Name::CodeIndented].
1045fn on_exit_code_indented(context: &mut CompileContext) -> Result<(), message::Message> {
1046 let value = context.resume().to_string();
1047
1048 if let Node::Code(node) = context.tail_mut() {
1049 node.value = trim_eol(value, false, true);
1050 } else {
1051 unreachable!("expected code on stack for value");
1052 }
1053 on_exit(context)?;
1054 context.raw_flow_fence_seen = false;
1055 Ok(())
1056}
1057
1058/// Handle [`Exit`][Kind::Exit]:{[`CodeText`][Name::CodeText],[`MathText`][Name::MathText]}.
1059fn on_exit_raw_text(context: &mut CompileContext) -> Result<(), message::Message> {
1060 let mut value = context.resume().to_string();
1061
1062 // To do: share with `to_html`.
1063 // If we are in a GFM table, we need to decode escaped pipes.
1064 // This is a rather weird GFM feature.
1065 if context.gfm_table_inside {
1066 let mut bytes = value.as_bytes().to_vec();
1067 let mut index = 0;
1068 let mut len = bytes.len();
1069 let mut replace = false;
1070
1071 while index < len {
1072 if index + 1 < len && bytes[index] == b'\\' && bytes[index + 1] == b'|' {
1073 replace = true;
1074 bytes.remove(index);
1075 len -= 1;
1076 }
1077
1078 index += 1;
1079 }
1080
1081 if replace {
1082 value = str::from_utf8(&bytes).unwrap().into();
1083 }
1084 }
1085
1086 let value_bytes = value.as_bytes();
1087 if value.len() > 2
1088 && value_bytes[0] == b' '
1089 && value_bytes[value.len() - 1] == b' '
1090 && !value_bytes.iter().all(|b| *b == b' ')
1091 {
1092 value.remove(0);
1093 value.pop();
1094 }
1095
1096 match context.tail_mut() {
1097 Node::InlineCode(node) => node.value = value,
1098 Node::InlineMath(node) => node.value = value,
1099 _ => unreachable!("expected inline code or math on stack for value"),
1100 }
1101
1102 on_exit(context)?;
1103 Ok(())
1104}
1105
1106/// Handle [`Exit`][Kind::Exit]:[`Data`][Name::Data] (and many text things).
1107fn on_exit_data(context: &mut CompileContext) -> Result<(), message::Message> {
1108 let value = Slice::from_position(
1109 context.bytes,
1110 &SlicePosition::from_exit_event(context.events, context.index),
1111 );
1112 if let Node::Text(text) = context.tail_mut() {
1113 text.value.push_str(value.as_str());
1114 } else {
1115 unreachable!("expected text on stack");
1116 }
1117 on_exit(context)?;
1118 Ok(())
1119}
1120
1121/// Handle [`Exit`][Kind::Exit]:[`DefinitionDestinationString`][Name::DefinitionDestinationString].
1122fn on_exit_definition_destination_string(context: &mut CompileContext) {
1123 let value = context.resume().to_string();
1124 if let Node::Definition(node) = context.tail_mut() {
1125 node.url = value;
1126 } else {
1127 unreachable!("expected definition on stack");
1128 }
1129}
1130
1131/// Handle [`Exit`][Kind::Exit]:{[`DefinitionLabelString`][Name::DefinitionLabelString],[`GfmFootnoteDefinitionLabelString`][Name::GfmFootnoteDefinitionLabelString]}.
1132fn on_exit_definition_id(context: &mut CompileContext) {
1133 let label = context.resume().to_string();
1134 let slice = Slice::from_position(
1135 context.bytes,
1136 &SlicePosition::from_exit_event(context.events, context.index),
1137 );
1138 let identifier = normalize_identifier(slice.as_str()).to_lowercase();
1139
1140 match context.tail_mut() {
1141 Node::Definition(node) => {
1142 node.label = Some(label);
1143 node.identifier = identifier;
1144 }
1145 Node::FootnoteDefinition(node) => {
1146 node.label = Some(label);
1147 node.identifier = identifier;
1148 }
1149 _ => unreachable!("expected definition or footnote definition on stack"),
1150 }
1151}
1152
1153/// Handle [`Exit`][Kind::Exit]:[`DefinitionTitleString`][Name::DefinitionTitleString].
1154fn on_exit_definition_title_string(context: &mut CompileContext) {
1155 let value = context.resume().to_string();
1156 if let Node::Definition(node) = context.tail_mut() {
1157 node.title = Some(value);
1158 } else {
1159 unreachable!("expected definition on stack");
1160 }
1161}
1162
1163/// Handle [`Exit`][Kind::Exit]:*, by dropping the current buffer.
1164fn on_exit_drop(context: &mut CompileContext) {
1165 context.resume();
1166}
1167
1168/// Handle [`Exit`][Kind::Exit]:[`Frontmatter`][Name::Frontmatter].
1169fn on_exit_frontmatter(context: &mut CompileContext) -> Result<(), message::Message> {
1170 let value = trim_eol(context.resume().to_string(), true, true);
1171
1172 match context.tail_mut() {
1173 Node::Yaml(node) => node.value = value,
1174 Node::Toml(node) => node.value = value,
1175 _ => unreachable!("expected yaml/toml on stack for value"),
1176 }
1177
1178 on_exit(context)?;
1179 Ok(())
1180}
1181
1182/// Handle [`Exit`][Kind::Exit]:{[`GfmAutolinkLiteralEmail`][Name::GfmAutolinkLiteralEmail],[`GfmAutolinkLiteralMailto`][Name::GfmAutolinkLiteralMailto],[`GfmAutolinkLiteralProtocol`][Name::GfmAutolinkLiteralProtocol],[`GfmAutolinkLiteralWww`][Name::GfmAutolinkLiteralWww],[`GfmAutolinkLiteralXmpp`][Name::GfmAutolinkLiteralXmpp]}.
1183fn on_exit_gfm_autolink_literal(context: &mut CompileContext) -> Result<(), message::Message> {
1184 on_exit_data(context)?;
1185
1186 let value = Slice::from_position(
1187 context.bytes,
1188 &SlicePosition::from_exit_event(context.events, context.index),
1189 );
1190
1191 let prefix = match &context.events[context.index].name {
1192 Name::GfmAutolinkLiteralEmail => Some("mailto:"),
1193 Name::GfmAutolinkLiteralWww => Some("http://"),
1194 // `GfmAutolinkLiteralMailto`, `GfmAutolinkLiteralProtocol`, `GfmAutolinkLiteralXmpp`.
1195 _ => None,
1196 };
1197
1198 if let Node::Link(link) = context.tail_mut() {
1199 if let Some(prefix) = prefix {
1200 link.url.push_str(prefix);
1201 }
1202 link.url.push_str(value.as_str());
1203 } else {
1204 unreachable!("expected link on stack");
1205 }
1206
1207 on_exit(context)?;
1208
1209 Ok(())
1210}
1211
1212/// Handle [`Exit`][Kind::Exit]:[`GfmTable`][Name::GfmTable].
1213fn on_exit_gfm_table(context: &mut CompileContext) -> Result<(), message::Message> {
1214 on_exit(context)?;
1215 context.gfm_table_inside = false;
1216 Ok(())
1217}
1218
1219/// Handle [`Exit`][Kind::Exit]:{[`GfmTaskListItemValueChecked`][Name::GfmTaskListItemValueChecked],[`GfmTaskListItemValueUnchecked`][Name::GfmTaskListItemValueUnchecked]}.
1220fn on_exit_gfm_task_list_item_value(context: &mut CompileContext) {
1221 let checked = context.events[context.index].name == Name::GfmTaskListItemValueChecked;
1222 let ancestor = context.tail_penultimate_mut();
1223
1224 if let Node::ListItem(node) = ancestor {
1225 node.checked = Some(checked);
1226 } else {
1227 unreachable!("expected list item on stack");
1228 }
1229}
1230
1231/// Handle [`Exit`][Kind::Exit]:{[`HardBreakEscape`][Name::HardBreakEscape],[`HardBreakTrailing`][Name::HardBreakTrailing]}.
1232fn on_exit_hard_break(context: &mut CompileContext) -> Result<(), message::Message> {
1233 on_exit(context)?;
1234 context.hard_break_after = true;
1235 Ok(())
1236}
1237
1238/// Handle [`Exit`][Kind::Exit]:[`HeadingAtxSequence`][Name::HeadingAtxSequence].
1239fn on_exit_heading_atx_sequence(context: &mut CompileContext) {
1240 let slice = Slice::from_position(
1241 context.bytes,
1242 &SlicePosition::from_exit_event(context.events, context.index),
1243 );
1244
1245 if let Node::Heading(node) = context.tail_mut() {
1246 if node.depth == 0 {
1247 #[allow(clippy::cast_possible_truncation)]
1248 let depth = slice.len() as u8;
1249 node.depth = depth;
1250 }
1251 } else {
1252 unreachable!("expected heading on stack");
1253 }
1254}
1255
1256/// Handle [`Exit`][Kind::Exit]:[`HeadingSetext`][Name::HeadingSetext].
1257fn on_exit_heading_setext(context: &mut CompileContext) -> Result<(), message::Message> {
1258 context.heading_setext_text_after = false;
1259 on_exit(context)?;
1260 Ok(())
1261}
1262
1263/// Handle [`Exit`][Kind::Exit]:[`HeadingSetextText`][Name::HeadingSetextText].
1264fn on_exit_heading_setext_text(context: &mut CompileContext) {
1265 context.heading_setext_text_after = true;
1266}
1267
1268/// Handle [`Exit`][Kind::Exit]:[`HeadingSetextUnderlineSequence`][Name::HeadingSetextUnderlineSequence].
1269fn on_exit_heading_setext_underline_sequence(context: &mut CompileContext) {
1270 let position = SlicePosition::from_exit_event(context.events, context.index);
1271 let head = context.bytes[position.start.index];
1272 let depth = if head == b'-' { 2 } else { 1 };
1273
1274 if let Node::Heading(node) = context.tail_mut() {
1275 node.depth = depth;
1276 } else {
1277 unreachable!("expected heading on stack");
1278 }
1279}
1280
1281/// Handle [`Exit`][Kind::Exit]:[`LabelText`][Name::LabelText].
1282fn on_exit_label_text(context: &mut CompileContext) {
1283 let mut fragment = context.resume();
1284 let label = fragment.to_string();
1285 let children = fragment.children_mut().unwrap().split_off(0);
1286 let slice = Slice::from_position(
1287 context.bytes,
1288 &SlicePosition::from_exit_event(context.events, context.index),
1289 );
1290 let identifier = normalize_identifier(slice.as_str()).to_lowercase();
1291
1292 let reference = context
1293 .media_reference_stack
1294 .last_mut()
1295 .expect("expected reference on media stack");
1296 reference.label.clone_from(&label);
1297 reference.identifier = identifier;
1298
1299 match context.tail_mut() {
1300 Node::Link(node) => node.children = children,
1301 Node::Image(node) => node.alt = label,
1302 Node::FootnoteReference(_) => {}
1303 _ => unreachable!("expected footnote refereence, image, or link on stack"),
1304 }
1305}
1306
1307/// Handle [`Exit`][Kind::Exit]:[`LineEnding`][Name::LineEnding].
1308fn on_exit_line_ending(context: &mut CompileContext) -> Result<(), message::Message> {
1309 if context.heading_setext_text_after {
1310 // Ignore.
1311 }
1312 // Line ending position after hard break is part of it.
1313 else if context.hard_break_after {
1314 let end = context.events[context.index].point.to_unist();
1315 let node = context.tail_mut();
1316 let tail = node
1317 .children_mut()
1318 .expect("expected parent")
1319 .last_mut()
1320 .expect("expected tail (break)");
1321 tail.position_mut().unwrap().end = end;
1322 context.hard_break_after = false;
1323 }
1324 // Line ending is a part of nodes that accept phrasing.
1325 else if matches!(
1326 context.tail_mut(),
1327 Node::Emphasis(_)
1328 | Node::Heading(_)
1329 | Node::Paragraph(_)
1330 | Node::Strong(_)
1331 | Node::Delete(_)
1332 ) {
1333 context.index -= 1;
1334 on_enter_data(context);
1335 context.index += 1;
1336 on_exit_data(context)?;
1337 }
1338
1339 Ok(())
1340}
1341
1342/// Handle [`Exit`][Kind::Exit]:{[`HtmlFlow`][Name::HtmlFlow],[`HtmlText`][Name::HtmlText]}.
1343fn on_exit_html(context: &mut CompileContext) -> Result<(), message::Message> {
1344 let value = context.resume().to_string();
1345
1346 match context.tail_mut() {
1347 Node::Html(node) => node.value = value,
1348 _ => unreachable!("expected html on stack for value"),
1349 }
1350
1351 on_exit(context)?;
1352 Ok(())
1353}
1354
1355/// Handle [`Exit`][Kind::Exit]:{[`GfmFootnoteCall`][Name::GfmFootnoteCall],[`Image`][Name::Image],[`Link`][Name::Link]}.
1356fn on_exit_media(context: &mut CompileContext) -> Result<(), message::Message> {
1357 let reference = context
1358 .media_reference_stack
1359 .pop()
1360 .expect("expected reference on media stack");
1361 on_exit(context)?;
1362
1363 // It’s a reference.
1364 if let Some(kind) = reference.reference_kind {
1365 let parent = context.tail_mut();
1366 let siblings = parent.children_mut().unwrap();
1367
1368 match siblings.last_mut().unwrap() {
1369 Node::FootnoteReference(node) => {
1370 node.identifier = reference.identifier;
1371 node.label = Some(reference.label);
1372 }
1373 Node::Image(_) => {
1374 // Need to swap it with a reference version of the node.
1375 if let Some(Node::Image(node)) = siblings.pop() {
1376 siblings.push(Node::ImageReference(ImageReference {
1377 reference_kind: kind,
1378 identifier: reference.identifier,
1379 label: Some(reference.label),
1380 alt: node.alt,
1381 position: node.position,
1382 }));
1383 } else {
1384 unreachable!("impossible: it’s an image")
1385 }
1386 }
1387 Node::Link(_) => {
1388 // Need to swap it with a reference version of the node.
1389 if let Some(Node::Link(node)) = siblings.pop() {
1390 siblings.push(Node::LinkReference(LinkReference {
1391 reference_kind: kind,
1392 identifier: reference.identifier,
1393 label: Some(reference.label),
1394 children: node.children,
1395 position: node.position,
1396 }));
1397 } else {
1398 unreachable!("impossible: it’s a link")
1399 }
1400 }
1401 _ => unreachable!("expected footnote reference, image, or link on stack"),
1402 }
1403 }
1404
1405 Ok(())
1406}
1407
1408/// Handle [`Exit`][Kind::Exit]:[`ListItem`][Name::ListItem].
1409fn on_exit_list_item(context: &mut CompileContext) -> Result<(), message::Message> {
1410 if let Node::ListItem(item) = context.tail_mut() {
1411 if item.checked.is_some() {
1412 if let Some(Node::Paragraph(paragraph)) = item.children.first_mut() {
1413 if let Some(Node::Text(text)) = paragraph.children.first_mut() {
1414 let mut point = text.position.as_ref().unwrap().start.clone();
1415 let bytes = text.value.as_bytes();
1416 let mut start = 0;
1417
1418 // Move past eol.
1419 if matches!(bytes[0], b'\t' | b' ') {
1420 point.offset += 1;
1421 point.column += 1;
1422 start += 1;
1423 } else if matches!(bytes[0], b'\r' | b'\n') {
1424 point.line += 1;
1425 point.column = 1;
1426 point.offset += 1;
1427 start += 1;
1428 // Move past the LF of CRLF.
1429 if bytes.len() > 1 && bytes[0] == b'\r' && bytes[1] == b'\n' {
1430 point.offset += 1;
1431 start += 1;
1432 }
1433 }
1434
1435 // The whole text is whitespace: update the text.
1436 if start == bytes.len() {
1437 paragraph.children.remove(0);
1438 } else {
1439 text.value = str::from_utf8(&bytes[start..]).unwrap().into();
1440 text.position.as_mut().unwrap().start = point.clone();
1441 }
1442 paragraph.position.as_mut().unwrap().start = point;
1443 }
1444 }
1445 }
1446 }
1447
1448 on_exit(context)?;
1449
1450 Ok(())
1451}
1452
1453/// Handle [`Exit`][Kind::Exit]:[`ListItemValue`][Name::ListItemValue].
1454fn on_exit_list_item_value(context: &mut CompileContext) {
1455 let start = Slice::from_position(
1456 context.bytes,
1457 &SlicePosition::from_exit_event(context.events, context.index),
1458 )
1459 .as_str()
1460 .parse()
1461 .expect("expected list value up to u8");
1462
1463 if let Node::List(node) = context.tail_penultimate_mut() {
1464 debug_assert!(node.ordered, "expected list to be ordered");
1465 if node.start.is_none() {
1466 node.start = Some(start);
1467 }
1468 } else {
1469 unreachable!("expected list on stack");
1470 }
1471}
1472
1473/// Handle [`Exit`][Kind::Exit]:{[`MdxJsxFlowTag`][Name::MdxJsxFlowTag],[`MdxJsxTextTag`][Name::MdxJsxTextTag]}.
1474fn on_exit_mdx_jsx_tag(context: &mut CompileContext) -> Result<(), message::Message> {
1475 let mut tag = context.jsx_tag.as_ref().expect("expected tag").clone();
1476
1477 // End of a tag, so drop the buffer.
1478 context.resume();
1479 // Set end point.
1480 tag.end = context.events[context.index].point.to_unist();
1481
1482 let stack = &context.jsx_tag_stack;
1483 let tail = stack.last();
1484
1485 if tag.close {
1486 // Unwrap: we crashed earlier if there’s nothing on the stack.
1487 let tail = tail.unwrap();
1488
1489 if tail.name != tag.name {
1490 let label = serialize_abbreviated_tag(&tag);
1491 return Err(
1492 message::Message {
1493 place: Some(Box::new(message::Place::Position(Position {
1494 start: tag.start,
1495 end: tag.end,
1496 }))),
1497 reason: format!(
1498 "Unexpected closing tag `{}`, expected corresponding closing tag for `{}` ({}:{})",
1499 label,
1500 serialize_abbreviated_tag(tail),
1501 tail.start.line,
1502 tail.start.column,
1503 ),
1504 rule_id: Box::new("end-tag-mismatch".into()),
1505 source: Box::new("markdown-rs".into()),
1506 },
1507 );
1508 }
1509
1510 // Remove from our custom stack.
1511 // Note that this does not exit the node.
1512 context.jsx_tag_stack.pop();
1513 } else {
1514 let node = if context.events[context.index].name == Name::MdxJsxFlowTag {
1515 Node::MdxJsxFlowElement(MdxJsxFlowElement {
1516 name: tag.name.clone(),
1517 attributes: tag.attributes.clone(),
1518 children: vec![],
1519 position: Some(Position {
1520 start: tag.start.clone(),
1521 end: tag.end.clone(),
1522 }),
1523 })
1524 } else {
1525 Node::MdxJsxTextElement(MdxJsxTextElement {
1526 name: tag.name.clone(),
1527 attributes: tag.attributes.clone(),
1528 children: vec![],
1529 position: Some(Position {
1530 start: tag.start.clone(),
1531 end: tag.end.clone(),
1532 }),
1533 })
1534 };
1535
1536 context.tail_push(node);
1537 }
1538
1539 if tag.self_closing || tag.close {
1540 context.tail_pop()?;
1541 } else {
1542 context.jsx_tag_stack.push(tag);
1543 }
1544
1545 Ok(())
1546}
1547
1548/// Handle [`Exit`][Kind::Exit]:[`MdxJsxTagClosingMarker`][Name::MdxJsxTagClosingMarker].
1549fn on_exit_mdx_jsx_tag_closing_marker(context: &mut CompileContext) {
1550 context.jsx_tag.as_mut().expect("expected tag").close = true;
1551}
1552
1553/// Handle [`Exit`][Kind::Exit]:[`MdxJsxTagNamePrimary`][Name::MdxJsxTagNamePrimary].
1554fn on_exit_mdx_jsx_tag_name_primary(context: &mut CompileContext) {
1555 let slice = Slice::from_position(
1556 context.bytes,
1557 &SlicePosition::from_exit_event(context.events, context.index),
1558 );
1559 let value = slice.serialize();
1560 context.jsx_tag.as_mut().expect("expected tag").name = Some(value);
1561}
1562
1563/// Handle [`Exit`][Kind::Exit]:[`MdxJsxTagNameMember`][Name::MdxJsxTagNameMember].
1564fn on_exit_mdx_jsx_tag_name_member(context: &mut CompileContext) {
1565 let slice = Slice::from_position(
1566 context.bytes,
1567 &SlicePosition::from_exit_event(context.events, context.index),
1568 );
1569 let name = context
1570 .jsx_tag
1571 .as_mut()
1572 .expect("expected tag")
1573 .name
1574 .as_mut()
1575 .expect("expected primary before member");
1576 name.push('.');
1577 name.push_str(slice.as_str());
1578}
1579
1580/// Handle [`Exit`][Kind::Exit]:[`MdxJsxTagNameLocal`][Name::MdxJsxTagNameLocal].
1581fn on_exit_mdx_jsx_tag_name_local(context: &mut CompileContext) {
1582 let slice = Slice::from_position(
1583 context.bytes,
1584 &SlicePosition::from_exit_event(context.events, context.index),
1585 );
1586 let name = context
1587 .jsx_tag
1588 .as_mut()
1589 .expect("expected tag")
1590 .name
1591 .as_mut()
1592 .expect("expected primary before local");
1593 name.push(':');
1594 name.push_str(slice.as_str());
1595}
1596
1597/// Handle [`Exit`][Kind::Exit]:{[`MdxEsm`][Name::MdxEsm],[`MdxFlowExpression`][Name::MdxFlowExpression],[`MdxTextExpression`][Name::MdxTextExpression]}.
1598fn on_exit_mdx_esm_or_expression(context: &mut CompileContext) -> Result<(), message::Message> {
1599 on_exit_drop(context);
1600 context.tail_pop()?;
1601 Ok(())
1602}
1603
1604/// Handle [`Exit`][Kind::Exit]:[`MdxJsxTagAttributePrimaryName`][Name::MdxJsxTagAttributePrimaryName].
1605fn on_exit_mdx_jsx_tag_attribute_primary_name(context: &mut CompileContext) {
1606 let slice = Slice::from_position(
1607 context.bytes,
1608 &SlicePosition::from_exit_event(context.events, context.index),
1609 );
1610 let value = slice.serialize();
1611
1612 if let Some(AttributeContent::Property(attribute)) = context
1613 .jsx_tag
1614 .as_mut()
1615 .expect("expected tag")
1616 .attributes
1617 .last_mut()
1618 {
1619 attribute.name = value;
1620 } else {
1621 unreachable!("expected property")
1622 }
1623}
1624
1625/// Handle [`Exit`][Kind::Exit]:[`MdxJsxTagAttributeNameLocal`][Name::MdxJsxTagAttributeNameLocal].
1626fn on_exit_mdx_jsx_tag_attribute_name_local(context: &mut CompileContext) {
1627 let slice = Slice::from_position(
1628 context.bytes,
1629 &SlicePosition::from_exit_event(context.events, context.index),
1630 );
1631 if let Some(AttributeContent::Property(attribute)) = context
1632 .jsx_tag
1633 .as_mut()
1634 .expect("expected tag")
1635 .attributes
1636 .last_mut()
1637 {
1638 attribute.name.push(':');
1639 attribute.name.push_str(slice.as_str());
1640 } else {
1641 unreachable!("expected property")
1642 }
1643}
1644
1645/// Handle [`Exit`][Kind::Exit]:[`MdxJsxTagAttributeValueLiteral`][Name::MdxJsxTagAttributeValueLiteral].
1646fn on_exit_mdx_jsx_tag_attribute_value_literal(context: &mut CompileContext) {
1647 let value = context.resume();
1648
1649 if let Some(AttributeContent::Property(node)) = context
1650 .jsx_tag
1651 .as_mut()
1652 .expect("expected tag")
1653 .attributes
1654 .last_mut()
1655 {
1656 node.value = Some(AttributeValue::Literal(parse_character_reference(
1657 &value.to_string(),
1658 )));
1659 } else {
1660 unreachable!("expected property")
1661 }
1662}
1663
1664/// Handle [`Exit`][Kind::Exit]:[`MdxJsxTagSelfClosingMarker`][Name::MdxJsxTagSelfClosingMarker].
1665fn on_exit_mdx_jsx_tag_self_closing_marker(context: &mut CompileContext) {
1666 context.jsx_tag.as_mut().expect("expected tag").self_closing = true;
1667}
1668
1669/// Handle [`Exit`][Kind::Exit]:[`ReferenceString`][Name::ReferenceString].
1670fn on_exit_reference_string(context: &mut CompileContext) {
1671 let label = context.resume().to_string();
1672 let slice = Slice::from_position(
1673 context.bytes,
1674 &SlicePosition::from_exit_event(context.events, context.index),
1675 );
1676 let identifier = normalize_identifier(slice.as_str()).to_lowercase();
1677 let reference = context
1678 .media_reference_stack
1679 .last_mut()
1680 .expect("expected reference on media stack");
1681 reference.reference_kind = Some(ReferenceKind::Full);
1682 reference.label = label;
1683 reference.identifier = identifier;
1684}
1685
1686/// Handle [`Exit`][Kind::Exit]:[`ResourceDestinationString`][Name::ResourceDestinationString].
1687fn on_exit_resource_destination_string(context: &mut CompileContext) {
1688 let value = context.resume().to_string();
1689
1690 match context.tail_mut() {
1691 Node::Link(node) => node.url = value,
1692 Node::Image(node) => node.url = value,
1693 _ => unreachable!("expected link, image on stack"),
1694 }
1695}
1696
1697/// Handle [`Exit`][Kind::Exit]:[`ResourceTitleString`][Name::ResourceTitleString].
1698fn on_exit_resource_title_string(context: &mut CompileContext) {
1699 let value = Some(context.resume().to_string());
1700
1701 match context.tail_mut() {
1702 Node::Link(node) => node.title = value,
1703 Node::Image(node) => node.title = value,
1704 _ => unreachable!("expected link, image on stack"),
1705 }
1706}
1707
1708/// Create a position from an event.
1709fn position_from_event(event: &Event) -> Position {
1710 let end = Point::new(event.point.line, event.point.column, event.point.index);
1711 Position {
1712 start: end.clone(),
1713 end,
1714 }
1715}
1716
1717/// Resolve the current stack on the tree.
1718fn delve_mut<'tree>(mut node: &'tree mut Node, stack: &'tree [usize]) -> &'tree mut Node {
1719 let mut stack_index = 0;
1720 while stack_index < stack.len() {
1721 let index = stack[stack_index];
1722 node = &mut node.children_mut().expect("Cannot delve into non-parent")[index];
1723 stack_index += 1;
1724 }
1725 node
1726}
1727
1728/// Remove initial/final EOLs.
1729fn trim_eol(value: String, at_start: bool, at_end: bool) -> String {
1730 let bytes = value.as_bytes();
1731 let mut start = 0;
1732 let mut end = bytes.len();
1733
1734 if at_start && !bytes.is_empty() {
1735 if bytes[0] == b'\n' {
1736 start += 1;
1737 } else if bytes[0] == b'\r' {
1738 start += 1;
1739 if bytes.len() > 1 && bytes[1] == b'\n' {
1740 start += 1;
1741 }
1742 }
1743 }
1744
1745 if at_end && end > start {
1746 if bytes[end - 1] == b'\n' {
1747 end -= 1;
1748 if end > start && bytes[end - 1] == b'\r' {
1749 end -= 1;
1750 }
1751 } else if bytes[end - 1] == b'\r' {
1752 end -= 1;
1753 }
1754 }
1755
1756 if start > 0 || end < bytes.len() {
1757 str::from_utf8(&bytes[start..end]).unwrap().into()
1758 } else {
1759 value
1760 }
1761}
1762
1763/// Handle a mismatch.
1764///
1765/// Mismatches can occur with MDX JSX tags.
1766fn on_mismatch_error(
1767 context: &mut CompileContext,
1768 left: Option<&Event>,
1769 right: &Event,
1770) -> Result<(), message::Message> {
1771 if right.name == Name::MdxJsxFlowTag || right.name == Name::MdxJsxTextTag {
1772 let stack = &context.jsx_tag_stack;
1773 let tag = stack.last().unwrap();
1774 let point = if let Some(left) = left {
1775 &left.point
1776 } else {
1777 &context.events[context.events.len() - 1].point
1778 };
1779
1780 return Err(message::Message {
1781 place: Some(Box::new(message::Place::Point(point.to_unist()))),
1782 reason: format!(
1783 "Expected a closing tag for `{}` ({}:{}){}",
1784 serialize_abbreviated_tag(tag),
1785 tag.start.line,
1786 tag.start.column,
1787 if let Some(left) = left {
1788 format!(" before the end of `{:?}`", left.name)
1789 } else {
1790 String::new()
1791 }
1792 ),
1793 rule_id: Box::new("end-tag-mismatch".into()),
1794 source: Box::new("markdown-rs".into()),
1795 });
1796 }
1797
1798 if let Some(left) = left {
1799 if left.name == Name::MdxJsxFlowTag || left.name == Name::MdxJsxTextTag {
1800 let tag = context.jsx_tag.as_ref().unwrap();
1801
1802 return Err(
1803 message::Message {
1804 place: Some(Box::new(message::Place::Point(tag.start.clone()))),
1805 reason: format!(
1806 "Expected the closing tag `{}` either before the start of `{:?}` ({}:{}), or another opening tag after that start",
1807 serialize_abbreviated_tag(tag),
1808 &right.name,
1809 &right.point.line,
1810 &right.point.column,
1811 ),
1812 rule_id: Box::new("end-tag-mismatch".into()),
1813 source: Box::new("markdown-rs".into()),
1814 }
1815 );
1816 }
1817 unreachable!("mismatched (non-jsx): {:?} / {:?}", left.name, right.name);
1818 } else {
1819 unreachable!("mismatched (non-jsx): document / {:?}", right.name);
1820 }
1821}
1822
1823/// Format a JSX tag, ignoring its attributes.
1824fn serialize_abbreviated_tag(tag: &JsxTag) -> String {
1825 format!(
1826 "<{}{}>",
1827 if tag.close { "/" } else { "" },
1828 if let Some(name) = &tag.name { name } else { "" },
1829 )
1830}