Markdown parser fork with extended syntax for personal use.
at hack 838 lines 28 kB view raw
1//! Label end occurs in the [text][] content type. 2//! 3//! ## Grammar 4//! 5//! Label end forms with the following BNF 6//! (<small>see [construct][crate::construct] for character groups</small>): 7//! 8//! ```bnf 9//! label_end ::= ']' [resource | reference_full | reference_collapsed] 10//! 11//! resource ::= '(' [space_or_tab_eol] destination [space_or_tab_eol title] [space_or_tab_eol] ')' 12//! reference_full ::= '[' label ']' 13//! reference_collapsed ::= '[' ']' 14//! 15//! ; See the `destination`, `title`, and `label` constructs for the BNF of 16//! ; those parts. 17//! ``` 18//! 19//! See [`destination`][destination], [`label`][label], and [`title`][title] 20//! for grammar, notes, and recommendations on each part. 21//! 22//! In the case of a resource, the destination and title are given directly 23//! with the label end. 24//! In the case of a reference, this information is provided by a matched 25//! [definition][]. 26//! Full references (`[x][y]`) match to definitions through their explicit, 27//! second, label (`y`). 28//! Collapsed references (`[x][]`) and shortcut references (`[x]`) match by 29//! interpreting the text provided between the first, implicit, label (`x`). 30//! To match, the effective label of the reference must be equal to the label 31//! of the definition after normalizing with 32//! [`normalize_identifier`][]. 33//! 34//! Importantly, while the label of a full reference *can* include [string][] 35//! content, and in case of collapsed and shortcut references even [text][] 36//! content, that content is not considered when matching. 37//! To illustrate, neither label matches the definition: 38//! 39//! ```markdown 40//! [a&b]: https://example.com 41//! 42//! [x][a&amp;b], [a\&b][] 43//! ``` 44//! 45//! When the resource or reference matches, the destination forms the `href` 46//! attribute in case of a [label start (link)][label_start_link], and an 47//! `src` attribute in case of a [label start (image)][label_start_image]. 48//! The title is formed, optionally, on either `<a>` or `<img>`. 49//! When matched with a [gfm label start (footnote)][gfm_label_start_footnote], 50//! no reference or resource can follow the label end. 51//! 52//! For info on how to encode characters in URLs, see 53//! [`destination`][destination]. 54//! For info on how characters are encoded as `href` on `<a>` or `src` on 55//! `<img>` when compiling, see 56//! [`sanitize_uri`][sanitize_uri]. 57//! 58//! In case of a matched [gfm label start (footnote)][gfm_label_start_footnote], 59//! a counter is injected. 60//! In case of a matched [label start (link)][label_start_link], the interpreted 61//! content between it and the label end, is placed between the opening and 62//! closing tags. 63//! In case of a matched [label start (image)][label_start_image], the text is 64//! also interpreted, but used *without* the resulting tags: 65//! 66//! ```markdown 67//! [a *b* c](#) 68//! 69//! ![a *b* c](#) 70//! ``` 71//! 72//! Yields: 73//! 74//! ```html 75//! <p><a href="#">a <em>b</em> c</a></p> 76//! <p><img src="#" alt="a b c" /></p> 77//! ``` 78//! 79//! It is possible to use images in links. 80//! It’s somewhat possible to have links in images (the text will be used, not 81//! the HTML, see above). 82//! But it’s not possible to use links (or footnotes, which result in links) 83//! in links. 84//! The “deepest” link (or footnote) wins. 85//! To illustrate: 86//! 87//! ```markdown 88//! a [b [c](#) d](#) e 89//! ``` 90//! 91//! Yields: 92//! 93//! ```html 94//! <p>a [b <a href="#">c</a> d](#) e</p> 95//! ``` 96//! 97//! This limitation is imposed because links in links is invalid according to 98//! HTML. 99//! Technically though, in markdown it is still possible to construct them by 100//! using an [autolink][] in a link. 101//! You definitely should not do that. 102//! 103//! ## HTML 104//! 105//! Label end does not, on its own, relate to anything in HTML. 106//! When matched with a [label start (link)][label_start_link], they together 107//! relate to the `<a>` element in HTML. 108//! See [*§ 4.5.1 The `a` element*][html_a] in the HTML spec for more info. 109//! It can also match with [label start (image)][label_start_image], in which 110//! case they form an `<img>` element. 111//! See [*§ 4.8.3 The `img` element*][html_img] in the HTML spec for more info. 112//! It can also match with [gfm label start (footnote)][gfm_label_start_footnote], 113//! in which case they form `<sup>` and `<a>` elements in HTML. 114//! See [*§ 4.5.19 The `sub` and `sup` elements*][html_sup] and 115//! [*§ 4.5.1 The `a` element*][html_a] in the HTML spec for more info. 116//! 117//! ## Recommendation 118//! 119//! It is recommended to use labels for links instead of [autolinks][autolink]. 120//! Labels allow more characters in URLs, and allow relative URLs and `www.` 121//! URLs. 122//! They also allow for descriptive text to explain the URL in prose. 123//! 124//! In footnotes, it’s recommended to use words instead of numbers (or letters 125//! or anything with an order) as calls. 126//! That makes it easier to reuse and reorder footnotes. 127//! 128//! ## Tokens 129//! 130//! * [`Data`][Name::Data] 131//! * [`GfmFootnoteCall`][Name::GfmFootnoteCall] 132//! * [`Image`][Name::Image] 133//! * [`Label`][Name::Label] 134//! * [`LabelEnd`][Name::LabelEnd] 135//! * [`LabelMarker`][Name::LabelMarker] 136//! * [`LabelText`][Name::LabelText] 137//! * [`LineEnding`][Name::LineEnding] 138//! * [`Link`][Name::Link] 139//! * [`Reference`][Name::Reference] 140//! * [`ReferenceMarker`][Name::ReferenceMarker] 141//! * [`ReferenceString`][Name::ReferenceString] 142//! * [`Resource`][Name::Resource] 143//! * [`ResourceDestination`][Name::ResourceDestination] 144//! * [`ResourceDestinationLiteral`][Name::ResourceDestinationLiteral] 145//! * [`ResourceDestinationLiteralMarker`][Name::ResourceDestinationLiteralMarker] 146//! * [`ResourceDestinationRaw`][Name::ResourceDestinationRaw] 147//! * [`ResourceDestinationString`][Name::ResourceDestinationString] 148//! * [`ResourceMarker`][Name::ResourceMarker] 149//! * [`ResourceTitle`][Name::ResourceTitle] 150//! * [`ResourceTitleMarker`][Name::ResourceTitleMarker] 151//! * [`ResourceTitleString`][Name::ResourceTitleString] 152//! * [`SpaceOrTab`][Name::SpaceOrTab] 153//! 154//! ## References 155//! 156//! * [`label-end.js` in `micromark`](https://github.com/micromark/micromark/blob/main/packages/micromark-core-commonmark/dev/lib/label-end.js) 157//! * [`micromark-extension-gfm-task-list-item`](https://github.com/micromark/micromark-extension-gfm-footnote) 158//! * [*§ 4.7 Link reference definitions* in `CommonMark`](https://spec.commonmark.org/0.31/#link-reference-definitions) 159//! * [*§ 6.3 Links* in `CommonMark`](https://spec.commonmark.org/0.31/#links) 160//! * [*§ 6.4 Images* in `CommonMark`](https://spec.commonmark.org/0.31/#images) 161//! 162//! > 👉 **Note**: Footnotes are not specified in GFM yet. 163//! > See [`github/cmark-gfm#270`](https://github.com/github/cmark-gfm/issues/270) 164//! > for the related issue. 165//! 166//! [string]: crate::construct::string 167//! [text]: crate::construct::text 168//! [destination]: crate::construct::partial_destination 169//! [title]: crate::construct::partial_title 170//! [label]: crate::construct::partial_label 171//! [label_start_image]: crate::construct::label_start_image 172//! [label_start_link]: crate::construct::label_start_link 173//! [gfm_label_start_footnote]: crate::construct::gfm_label_start_footnote 174//! [definition]: crate::construct::definition 175//! [autolink]: crate::construct::autolink 176//! [sanitize_uri]: crate::util::sanitize_uri::sanitize 177//! [normalize_identifier]: crate::util::normalize_identifier::normalize_identifier 178//! [html_a]: https://html.spec.whatwg.org/multipage/text-level-semantics.html#the-a-element 179//! [html_img]: https://html.spec.whatwg.org/multipage/embedded-content.html#the-img-element 180//! [html_sup]: https://html.spec.whatwg.org/multipage/text-level-semantics.html#the-sub-and-sup-elements 181 182use crate::construct::partial_space_or_tab_eol::space_or_tab_eol; 183use crate::event::{Event, Kind, Name}; 184use crate::resolve::Name as ResolveName; 185use crate::state::{Name as StateName, State}; 186use crate::subtokenize::Subresult; 187use crate::tokenizer::{Label, LabelKind, LabelStart, Tokenizer}; 188use crate::util::{ 189 constant::RESOURCE_DESTINATION_BALANCE_MAX, 190 normalize_identifier::normalize_identifier, 191 skip, 192 slice::{Position, Slice}, 193}; 194use alloc::{string::String, vec}; 195 196/// Start of label end. 197/// 198/// ```markdown 199/// > | [a](b) c 200/// ^ 201/// > | [a][b] c 202/// ^ 203/// > | [a][] b 204/// ^ 205/// > | [a] b 206/// ``` 207pub fn start(tokenizer: &mut Tokenizer) -> State { 208 if Some(b']') == tokenizer.current && tokenizer.parse_state.options.constructs.label_end { 209 // If there is an okay opening: 210 if !tokenizer.tokenize_state.label_starts.is_empty() { 211 let label_start = tokenizer.tokenize_state.label_starts.last().unwrap(); 212 213 tokenizer.tokenize_state.end = tokenizer.events.len(); 214 215 // If the corresponding label (link) start is marked as inactive, 216 // it means we’d be wrapping a link, like this: 217 // 218 // ```markdown 219 // > | a [b [c](d) e](f) g. 220 // ^ 221 // ``` 222 // 223 // We can’t have that, so it’s just balanced brackets. 224 if label_start.inactive { 225 return State::Retry(StateName::LabelEndNok); 226 } 227 228 tokenizer.enter(Name::LabelEnd); 229 tokenizer.enter(Name::LabelMarker); 230 tokenizer.consume(); 231 tokenizer.exit(Name::LabelMarker); 232 tokenizer.exit(Name::LabelEnd); 233 return State::Next(StateName::LabelEndAfter); 234 } 235 } 236 237 State::Nok 238} 239 240/// After `]`. 241/// 242/// ```markdown 243/// > | [a](b) c 244/// ^ 245/// > | [a][b] c 246/// ^ 247/// > | [a][] b 248/// ^ 249/// > | [a] b 250/// ^ 251/// ``` 252pub fn after(tokenizer: &mut Tokenizer) -> State { 253 let start_index = tokenizer.tokenize_state.label_starts.len() - 1; 254 let start = &tokenizer.tokenize_state.label_starts[start_index]; 255 256 let indices = ( 257 tokenizer.events[start.start.1].point.index, 258 tokenizer.events[tokenizer.tokenize_state.end].point.index, 259 ); 260 261 // We don’t care about virtual spaces, so `indices` and `as_str` are fine. 262 let mut id = normalize_identifier( 263 Slice::from_indices(tokenizer.parse_state.bytes, indices.0, indices.1).as_str(), 264 ); 265 266 // See if this matches a footnote definition. 267 if start.kind == LabelKind::GfmFootnote { 268 if tokenizer.parse_state.gfm_footnote_definitions.contains(&id) { 269 return State::Retry(StateName::LabelEndOk); 270 } 271 272 // Nope, this might be a normal link? 273 tokenizer.tokenize_state.label_starts[start_index].kind = LabelKind::GfmUndefinedFootnote; 274 let mut new_id = String::new(); 275 new_id.push('^'); 276 new_id.push_str(&id); 277 id = new_id; 278 } 279 280 let defined = tokenizer.parse_state.definitions.contains(&id); 281 282 match tokenizer.current { 283 // Resource (`[asd](fgh)`)? 284 Some(b'(') => { 285 tokenizer.attempt( 286 State::Next(StateName::LabelEndOk), 287 State::Next(if defined { 288 StateName::LabelEndOk 289 } else { 290 StateName::LabelEndNok 291 }), 292 ); 293 State::Retry(StateName::LabelEndResourceStart) 294 } 295 // Full (`[asd][fgh]`) or collapsed (`[asd][]`) reference? 296 Some(b'[') => { 297 tokenizer.attempt( 298 State::Next(StateName::LabelEndOk), 299 State::Next(if defined { 300 StateName::LabelEndReferenceNotFull 301 } else { 302 StateName::LabelEndNok 303 }), 304 ); 305 State::Retry(StateName::LabelEndReferenceFull) 306 } 307 // Shortcut (`[asd]`) reference? 308 _ => State::Retry(if defined { 309 StateName::LabelEndOk 310 } else { 311 StateName::LabelEndNok 312 }), 313 } 314} 315 316/// After `]`, at `[`, but not at a full reference. 317/// 318/// > 👉 **Note**: we only get here if the label is defined. 319/// 320/// ```markdown 321/// > | [a][] b 322/// ^ 323/// > | [a] b 324/// ^ 325/// ``` 326pub fn reference_not_full(tokenizer: &mut Tokenizer) -> State { 327 tokenizer.attempt( 328 State::Next(StateName::LabelEndOk), 329 State::Next(StateName::LabelEndNok), 330 ); 331 State::Retry(StateName::LabelEndReferenceCollapsed) 332} 333 334/// Done, we found something. 335/// 336/// ```markdown 337/// > | [a](b) c 338/// ^ 339/// > | [a][b] c 340/// ^ 341/// > | [a][] b 342/// ^ 343/// > | [a] b 344/// ^ 345/// ``` 346pub fn ok(tokenizer: &mut Tokenizer) -> State { 347 // Remove the start. 348 let label_start = tokenizer.tokenize_state.label_starts.pop().unwrap(); 349 350 // If this is a link or footnote, we need to mark earlier link starts as no 351 // longer viable for use (as they would otherwise contain a link). 352 // These link starts are still looking for balanced closing brackets, so 353 // we can’t remove them, but we can mark them. 354 if label_start.kind != LabelKind::Image { 355 let mut index = 0; 356 while index < tokenizer.tokenize_state.label_starts.len() { 357 let label_start = &mut tokenizer.tokenize_state.label_starts[index]; 358 if label_start.kind != LabelKind::Image { 359 label_start.inactive = true; 360 } 361 index += 1; 362 } 363 } 364 365 tokenizer.tokenize_state.labels.push(Label { 366 kind: label_start.kind, 367 start: label_start.start, 368 end: (tokenizer.tokenize_state.end, tokenizer.events.len() - 1), 369 }); 370 tokenizer.tokenize_state.end = 0; 371 tokenizer.register_resolver_before(ResolveName::Label); 372 State::Ok 373} 374 375/// Done, it’s nothing. 376/// 377/// There was an okay opening, but we didn’t match anything. 378/// 379/// ```markdown 380/// > | [a](b c 381/// ^ 382/// > | [a][b c 383/// ^ 384/// > | [a] b 385/// ^ 386/// ``` 387pub fn nok(tokenizer: &mut Tokenizer) -> State { 388 let start = tokenizer.tokenize_state.label_starts.pop().unwrap(); 389 tokenizer.tokenize_state.label_starts_loose.push(start); 390 tokenizer.tokenize_state.end = 0; 391 State::Nok 392} 393 394/// At a resource. 395/// 396/// ```markdown 397/// > | [a](b) c 398/// ^ 399/// ``` 400pub fn resource_start(tokenizer: &mut Tokenizer) -> State { 401 match tokenizer.current { 402 Some(b'(') => { 403 tokenizer.enter(Name::Resource); 404 tokenizer.enter(Name::ResourceMarker); 405 tokenizer.consume(); 406 tokenizer.exit(Name::ResourceMarker); 407 State::Next(StateName::LabelEndResourceBefore) 408 } 409 _ => unreachable!("expected `(`"), 410 } 411} 412 413/// In resource, after `(`, at optional whitespace. 414/// 415/// ```markdown 416/// > | [a](b) c 417/// ^ 418/// ``` 419pub fn resource_before(tokenizer: &mut Tokenizer) -> State { 420 if matches!(tokenizer.current, Some(b'\t' | b'\n' | b' ')) { 421 tokenizer.attempt( 422 State::Next(StateName::LabelEndResourceOpen), 423 State::Next(StateName::LabelEndResourceOpen), 424 ); 425 State::Retry(space_or_tab_eol(tokenizer)) 426 } else { 427 State::Retry(StateName::LabelEndResourceOpen) 428 } 429} 430 431/// In resource, after optional whitespace, at `)` or a destination. 432/// 433/// ```markdown 434/// > | [a](b) c 435/// ^ 436/// ``` 437pub fn resource_open(tokenizer: &mut Tokenizer) -> State { 438 if let Some(b')') = tokenizer.current { 439 State::Retry(StateName::LabelEndResourceEnd) 440 } else { 441 tokenizer.tokenize_state.token_1 = Name::ResourceDestination; 442 tokenizer.tokenize_state.token_2 = Name::ResourceDestinationLiteral; 443 tokenizer.tokenize_state.token_3 = Name::ResourceDestinationLiteralMarker; 444 tokenizer.tokenize_state.token_4 = Name::ResourceDestinationRaw; 445 tokenizer.tokenize_state.token_5 = Name::ResourceDestinationString; 446 tokenizer.tokenize_state.size_b = RESOURCE_DESTINATION_BALANCE_MAX; 447 448 tokenizer.attempt( 449 State::Next(StateName::LabelEndResourceDestinationAfter), 450 State::Next(StateName::LabelEndResourceDestinationMissing), 451 ); 452 State::Retry(StateName::DestinationStart) 453 } 454} 455 456/// In resource, after destination, at optional whitespace. 457/// 458/// ```markdown 459/// > | [a](b) c 460/// ^ 461/// ``` 462pub fn resource_destination_after(tokenizer: &mut Tokenizer) -> State { 463 tokenizer.tokenize_state.token_1 = Name::Data; 464 tokenizer.tokenize_state.token_2 = Name::Data; 465 tokenizer.tokenize_state.token_3 = Name::Data; 466 tokenizer.tokenize_state.token_4 = Name::Data; 467 tokenizer.tokenize_state.token_5 = Name::Data; 468 tokenizer.tokenize_state.size_b = 0; 469 470 if matches!(tokenizer.current, Some(b'\t' | b'\n' | b' ')) { 471 tokenizer.attempt( 472 State::Next(StateName::LabelEndResourceBetween), 473 State::Next(StateName::LabelEndResourceEnd), 474 ); 475 State::Retry(space_or_tab_eol(tokenizer)) 476 } else { 477 State::Retry(StateName::LabelEndResourceEnd) 478 } 479} 480 481/// At invalid destination. 482/// 483/// ```markdown 484/// > | [a](<<) b 485/// ^ 486/// ``` 487pub fn resource_destination_missing(tokenizer: &mut Tokenizer) -> State { 488 tokenizer.tokenize_state.token_1 = Name::Data; 489 tokenizer.tokenize_state.token_2 = Name::Data; 490 tokenizer.tokenize_state.token_3 = Name::Data; 491 tokenizer.tokenize_state.token_4 = Name::Data; 492 tokenizer.tokenize_state.token_5 = Name::Data; 493 tokenizer.tokenize_state.size_b = 0; 494 State::Nok 495} 496 497/// In resource, after destination and whitespace, at `(` or title. 498/// 499/// ```markdown 500/// > | [a](b ) c 501/// ^ 502/// ``` 503pub fn resource_between(tokenizer: &mut Tokenizer) -> State { 504 match tokenizer.current { 505 Some(b'"' | b'\'' | b'(') => { 506 tokenizer.tokenize_state.token_1 = Name::ResourceTitle; 507 tokenizer.tokenize_state.token_2 = Name::ResourceTitleMarker; 508 tokenizer.tokenize_state.token_3 = Name::ResourceTitleString; 509 tokenizer.attempt( 510 State::Next(StateName::LabelEndResourceTitleAfter), 511 State::Nok, 512 ); 513 State::Retry(StateName::TitleStart) 514 } 515 _ => State::Retry(StateName::LabelEndResourceEnd), 516 } 517} 518 519/// In resource, after title, at optional whitespace. 520/// 521/// ```markdown 522/// > | [a](b "c") d 523/// ^ 524/// ``` 525pub fn resource_title_after(tokenizer: &mut Tokenizer) -> State { 526 tokenizer.tokenize_state.token_1 = Name::Data; 527 tokenizer.tokenize_state.token_2 = Name::Data; 528 tokenizer.tokenize_state.token_3 = Name::Data; 529 530 if matches!(tokenizer.current, Some(b'\t' | b'\n' | b' ')) { 531 tokenizer.attempt( 532 State::Next(StateName::LabelEndResourceEnd), 533 State::Next(StateName::LabelEndResourceEnd), 534 ); 535 State::Retry(space_or_tab_eol(tokenizer)) 536 } else { 537 State::Retry(StateName::LabelEndResourceEnd) 538 } 539} 540 541/// In resource, at `)`. 542/// 543/// ```markdown 544/// > | [a](b) d 545/// ^ 546/// ``` 547pub fn resource_end(tokenizer: &mut Tokenizer) -> State { 548 match tokenizer.current { 549 Some(b')') => { 550 tokenizer.enter(Name::ResourceMarker); 551 tokenizer.consume(); 552 tokenizer.exit(Name::ResourceMarker); 553 tokenizer.exit(Name::Resource); 554 State::Ok 555 } 556 _ => State::Nok, 557 } 558} 559 560/// In reference (full), at `[`. 561/// 562/// ```markdown 563/// > | [a][b] d 564/// ^ 565/// ``` 566pub fn reference_full(tokenizer: &mut Tokenizer) -> State { 567 match tokenizer.current { 568 Some(b'[') => { 569 tokenizer.tokenize_state.token_1 = Name::Reference; 570 tokenizer.tokenize_state.token_2 = Name::ReferenceMarker; 571 tokenizer.tokenize_state.token_3 = Name::ReferenceString; 572 tokenizer.attempt( 573 State::Next(StateName::LabelEndReferenceFullAfter), 574 State::Next(StateName::LabelEndReferenceFullMissing), 575 ); 576 State::Retry(StateName::LabelStart) 577 } 578 _ => unreachable!("expected `[`"), 579 } 580} 581 582/// In reference (full), after `]`. 583/// 584/// ```markdown 585/// > | [a][b] d 586/// ^ 587/// ``` 588pub fn reference_full_after(tokenizer: &mut Tokenizer) -> State { 589 tokenizer.tokenize_state.token_1 = Name::Data; 590 tokenizer.tokenize_state.token_2 = Name::Data; 591 tokenizer.tokenize_state.token_3 = Name::Data; 592 593 if tokenizer 594 .parse_state 595 .definitions 596 // We don’t care about virtual spaces, so `as_str` is fine. 597 .contains(&normalize_identifier( 598 Slice::from_position( 599 tokenizer.parse_state.bytes, 600 &Position::from_exit_event( 601 &tokenizer.events, 602 skip::to_back( 603 &tokenizer.events, 604 tokenizer.events.len() - 1, 605 &[Name::ReferenceString], 606 ), 607 ), 608 ) 609 .as_str(), 610 )) 611 { 612 State::Ok 613 } else { 614 State::Nok 615 } 616} 617 618/// In reference (full) that was missing. 619/// 620/// ```markdown 621/// > | [a][b d 622/// ^ 623/// ``` 624pub fn reference_full_missing(tokenizer: &mut Tokenizer) -> State { 625 tokenizer.tokenize_state.token_1 = Name::Data; 626 tokenizer.tokenize_state.token_2 = Name::Data; 627 tokenizer.tokenize_state.token_3 = Name::Data; 628 State::Nok 629} 630 631/// In reference (collapsed), at `[`. 632/// 633/// > 👉 **Note**: we only get here if the label is defined. 634/// 635/// ```markdown 636/// > | [a][] d 637/// ^ 638/// ``` 639pub fn reference_collapsed(tokenizer: &mut Tokenizer) -> State { 640 // We only attempt a collapsed label if there’s a `[`. 641 debug_assert_eq!(tokenizer.current, Some(b'['), "expected opening bracket"); 642 tokenizer.enter(Name::Reference); 643 tokenizer.enter(Name::ReferenceMarker); 644 tokenizer.consume(); 645 tokenizer.exit(Name::ReferenceMarker); 646 State::Next(StateName::LabelEndReferenceCollapsedOpen) 647} 648 649/// In reference (collapsed), at `]`. 650/// 651/// > 👉 **Note**: we only get here if the label is defined. 652/// 653/// ```markdown 654/// > | [a][] d 655/// ^ 656/// ``` 657pub fn reference_collapsed_open(tokenizer: &mut Tokenizer) -> State { 658 match tokenizer.current { 659 Some(b']') => { 660 tokenizer.enter(Name::ReferenceMarker); 661 tokenizer.consume(); 662 tokenizer.exit(Name::ReferenceMarker); 663 tokenizer.exit(Name::Reference); 664 State::Ok 665 } 666 _ => State::Nok, 667 } 668} 669 670/// Resolve images, links, and footnotes. 671/// 672/// This turns matching label starts and label ends into links, images, and 673/// footnotes, and turns unmatched label starts back into data. 674pub fn resolve(tokenizer: &mut Tokenizer) -> Option<Subresult> { 675 // Inject labels. 676 let labels = tokenizer.tokenize_state.labels.split_off(0); 677 inject_labels(tokenizer, &labels); 678 // Handle loose starts. 679 let starts = tokenizer.tokenize_state.label_starts.split_off(0); 680 mark_as_data(tokenizer, &starts); 681 let starts = tokenizer.tokenize_state.label_starts_loose.split_off(0); 682 mark_as_data(tokenizer, &starts); 683 684 tokenizer.map.consume(&mut tokenizer.events); 685 None 686} 687 688/// Inject links/images/footnotes. 689fn inject_labels(tokenizer: &mut Tokenizer, labels: &[Label]) { 690 // Add grouping events. 691 let mut index = 0; 692 while index < labels.len() { 693 let label = &labels[index]; 694 let group_name = if label.kind == LabelKind::GfmFootnote { 695 Name::GfmFootnoteCall 696 } else if label.kind == LabelKind::Image { 697 Name::Image 698 } else { 699 Name::Link 700 }; 701 702 // If this is a fine link, which starts with a footnote start that did 703 // not match, we need to inject the caret as data. 704 let mut caret = vec![]; 705 706 if label.kind == LabelKind::GfmUndefinedFootnote { 707 // Add caret. 708 caret.push(Event { 709 kind: Kind::Enter, 710 name: Name::Data, 711 // Enter:GfmFootnoteCallMarker. 712 point: tokenizer.events[label.start.1 - 2].point.clone().clone(), 713 link: None, 714 }); 715 caret.push(Event { 716 kind: Kind::Exit, 717 name: Name::Data, 718 // Exit:GfmFootnoteCallMarker. 719 point: tokenizer.events[label.start.1 - 1].point.clone(), 720 link: None, 721 }); 722 // Change and move label end. 723 tokenizer.events[label.start.0].name = Name::LabelLink; 724 tokenizer.events[label.start.1].name = Name::LabelLink; 725 tokenizer.events[label.start.1].point = caret[0].point.clone(); 726 // Remove the caret. 727 // Enter:GfmFootnoteCallMarker, Exit:GfmFootnoteCallMarker. 728 tokenizer.map.add(label.start.1 - 2, 2, vec![]); 729 } 730 731 // Insert a group enter and label enter. 732 tokenizer.map.add( 733 label.start.0, 734 0, 735 vec![ 736 Event { 737 kind: Kind::Enter, 738 name: group_name.clone(), 739 point: tokenizer.events[label.start.0].point.clone(), 740 link: None, 741 }, 742 Event { 743 kind: Kind::Enter, 744 name: Name::Label, 745 point: tokenizer.events[label.start.0].point.clone(), 746 link: None, 747 }, 748 ], 749 ); 750 751 // Empty events not allowed. 752 // Though: if this was what looked like a footnote, but didn’t match, 753 // it’s a link instead, and we need to inject the `^`. 754 if label.start.1 != label.end.0 || !caret.is_empty() { 755 tokenizer.map.add_before( 756 label.start.1 + 1, 757 0, 758 vec![Event { 759 kind: Kind::Enter, 760 name: Name::LabelText, 761 point: tokenizer.events[label.start.1].point.clone(), 762 link: None, 763 }], 764 ); 765 tokenizer.map.add( 766 label.end.0, 767 0, 768 vec![Event { 769 kind: Kind::Exit, 770 name: Name::LabelText, 771 point: tokenizer.events[label.end.0].point.clone(), 772 link: None, 773 }], 774 ); 775 } 776 777 if !caret.is_empty() { 778 tokenizer.map.add(label.start.1 + 1, 0, caret); 779 } 780 781 // Insert a label exit. 782 tokenizer.map.add( 783 label.end.0 + 4, 784 0, 785 vec![Event { 786 kind: Kind::Exit, 787 name: Name::Label, 788 point: tokenizer.events[label.end.0 + 3].point.clone(), 789 link: None, 790 }], 791 ); 792 793 // Insert a group exit. 794 tokenizer.map.add( 795 label.end.1 + 1, 796 0, 797 vec![Event { 798 kind: Kind::Exit, 799 name: group_name, 800 point: tokenizer.events[label.end.1].point.clone(), 801 link: None, 802 }], 803 ); 804 805 index += 1; 806 } 807} 808 809/// Remove loose label starts. 810fn mark_as_data(tokenizer: &mut Tokenizer, events: &[LabelStart]) { 811 let mut index = 0; 812 813 while index < events.len() { 814 let data_enter_index = events[index].start.0; 815 let data_exit_index = events[index].start.1; 816 817 tokenizer.map.add( 818 data_enter_index, 819 data_exit_index - data_enter_index + 1, 820 vec![ 821 Event { 822 kind: Kind::Enter, 823 name: Name::Data, 824 point: tokenizer.events[data_enter_index].point.clone(), 825 link: None, 826 }, 827 Event { 828 kind: Kind::Exit, 829 name: Name::Data, 830 point: tokenizer.events[data_exit_index].point.clone(), 831 link: None, 832 }, 833 ], 834 ); 835 836 index += 1; 837 } 838}