Markdown parser fork with extended syntax for personal use.
at hack 1830 lines 64 kB view raw
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}