jotup/lib.rs
1//! A pull parser for [Djot](https://djot.net).
2//!
3//! The main entry is through [`Parser`] which constructs an [`Iterator`] of [`Event`]s. The events
4//! can then be processed before rendering them via the [`Render`] trait. This crate provides an
5//! [`html`] module that implements an HTML renderer.
6//!
7//! # Feature flags
8//!
9//! - `html` (default): build the html module and a binary that converts djot to HTML.
10//!
11//! # Examples
12//!
13//! Generate HTML from Djot input:
14//!
15//! ```
16//! # #[cfg(feature = "html")]
17//! # {
18//! let djot_input = "hello *world*!";
19//! let events = jotup::Parser::new(djot_input);
20//! let html = jotup::html::Renderer::default().render_events_to_string(events);
21//! assert_eq!(html, "<p>hello <strong>world</strong>!</p>\n");
22//! # }
23//! ```
24//!
25//! Apply some filter to a specific type of element:
26//!
27//! ```
28//! # #[cfg(feature = "html")]
29//! # {
30//! # use jotup::Event;
31//! # use jotup::Container::Link;
32//! let events =
33//! jotup::Parser::new("a [link](https://example.com)").map(|e| match e {
34//! Event::Start(Link(dst, ty), attrs) => {
35//! Event::Start(Link(dst.replace(".com", ".net").into(), ty), attrs)
36//! }
37//! e => e,
38//! });
39//! let html = jotup::html::Renderer::default().render_events_to_string(events);
40//! assert_eq!(html, "<p>a <a href=\"https://example.net\">link</a></p>\n");
41//! # }
42//! ```
43
44#[cfg(feature = "html")]
45pub mod html;
46
47#[cfg(feature = "async")]
48pub mod r#async;
49
50mod attr;
51mod block;
52mod inline;
53mod lex;
54
55pub use attr::AttributeKind;
56pub use attr::AttributeValue;
57pub use attr::AttributeValueParts;
58pub use attr::Attributes;
59pub use attr::ParseAttributesError;
60
61type CowStr<'s> = std::borrow::Cow<'s, str>;
62
63/// An implementation of rendering a djot document
64///
65/// This interface will be called by the djot parser with elements
66/// parsed by it.
67pub trait Render<'s> {
68 type Error;
69
70 /// Called iteratively with every single event emitted by parsing djot document
71 fn emit(&mut self, event: Event<'s>) -> Result<(), Self::Error>;
72}
73
74/// Utility extensions method for [`Render`] trait
75pub trait RenderExt<'s>: Render<'s> {
76 /// Parse and render the whole document with `renderer`
77 fn render_document(&mut self, src: &'s str) -> Result<(), Self::Error> {
78 self.render_events(Parser::new(src))
79 }
80
81 /// Render document as a list of events already parsed by the [`Parser`]
82 fn render_events<I>(&mut self, events: I) -> Result<(), Self::Error>
83 where
84 I: Iterator<Item = Event<'s>>,
85 {
86 self.emit(Event::Start(Container::Document, Attributes::new()))?;
87
88 for event in events {
89 self.emit(event)?;
90 }
91
92 self.emit(Event::End)
93 }
94}
95
96impl<'s, R> RenderExt<'s> for R where R: Render<'s> {}
97
98/// A `Render` that produces an output
99pub trait RenderOutput<'s>: Render<'s> {
100 type Output;
101 fn into_output(self) -> Self::Output;
102}
103
104/// Utility extension method for [`RenderOutput`] trait
105pub trait RenderOutputExt<'s>: RenderOutput<'s> {
106 fn render_into_document(
107 mut self,
108 input: &'s str,
109 ) -> Result<Self::Output, <Self as Render<'s>>::Error>
110 where
111 Self: Sized,
112 {
113 RenderExt::<'s>::render_document(&mut self, input)?;
114 Ok(self.into_output())
115 }
116}
117
118impl<'s, R> RenderOutputExt<'s> for R where R: RenderOutput<'s> {}
119
120/// A Djot event.
121///
122/// A Djot document is represented by a sequence of events. An element may consist of one or
123/// multiple events. [`Container`] elements are represented by a [`Event::Start`] followed by
124/// events representing its content, and finally a [`Event::End`]. Atomic elements without any
125/// inside elements are represented by a single event.
126#[derive(Debug, Clone, PartialEq, Eq)]
127pub enum Event<'s> {
128 /// Start of a container.
129 ///
130 /// Always paired with a matching [`Event::End`].
131 ///
132 /// # Examples
133 ///
134 /// ```
135 /// # use jotup::*;
136 /// let src = concat!(
137 /// "{#a}\n",
138 /// "[word]{#b}\n",
139 /// );
140 /// let events: Vec<_> = Parser::new(src).collect();
141 /// assert_eq!(
142 /// &events,
143 /// &[
144 /// Event::Start(
145 /// Container::Paragraph,
146 /// [(AttributeKind::Id, "a".into())].into_iter().collect(),
147 /// ),
148 /// Event::Start(
149 /// Container::Span,
150 /// [(AttributeKind::Id, "b".into())].into_iter().collect(),
151 /// ),
152 /// Event::Str("word".into()),
153 /// Event::End,
154 /// Event::End,
155 /// ],
156 /// );
157 /// let html = "<p id=\"a\"><span id=\"b\">word</span></p>\n";
158 /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
159 /// ```
160 Start(Container<'s>, Attributes<'s>),
161 /// End of a container.
162 ///
163 /// Always paired with a matching [`Event::Start`].
164 End,
165 /// A string object, text only.
166 ///
167 /// The strings from the parser will always be borrowed, but users may replace them with owned
168 /// variants before rendering.
169 ///
170 /// # Examples
171 ///
172 /// ```
173 /// # use jotup::*;
174 /// let src = "str";
175 /// let events: Vec<_> = Parser::new(src).collect();
176 /// assert_eq!(
177 /// &events,
178 /// &[
179 /// Event::Start(Container::Paragraph, Attributes::new()),
180 /// Event::Str("str".into()),
181 /// Event::End,
182 /// ],
183 /// );
184 /// let html = "<p>str</p>\n";
185 /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
186 /// ```
187 Str(CowStr<'s>),
188 /// A footnote reference.
189 ///
190 /// # Examples
191 ///
192 /// ```
193 /// # use jotup::*;
194 /// let src = "txt[^nb].";
195 /// let events: Vec<_> = Parser::new(src).collect();
196 /// assert_eq!(
197 /// &events,
198 /// &[
199 /// Event::Start(Container::Paragraph, Attributes::new()),
200 /// Event::Str("txt".into()),
201 /// Event::FootnoteReference("nb".into()),
202 /// Event::Str(".".into()),
203 /// Event::End,
204 /// ],
205 /// );
206 /// let html = concat!(
207 /// "<p>txt<a id=\"fnref1\" href=\"#fn1\" role=\"doc-noteref\"><sup>1</sup></a>.</p>\n",
208 /// "<section role=\"doc-endnotes\">\n",
209 /// "<hr>\n",
210 /// "<ol>\n",
211 /// "<li id=\"fn1\">\n",
212 /// "<p><a href=\"#fnref1\" role=\"doc-backlink\">↩\u{fe0e}</a></p>\n",
213 /// "</li>\n",
214 /// "</ol>\n",
215 /// "</section>\n",
216 /// );
217 /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
218 /// ```
219 FootnoteReference(CowStr<'s>),
220 /// A symbol, by default rendered literally but may be treated specially.
221 ///
222 /// # Examples
223 ///
224 /// ```
225 /// # use jotup::*;
226 /// let src = "a :sym:";
227 /// let events: Vec<_> = Parser::new(src).collect();
228 /// assert_eq!(
229 /// &events,
230 /// &[
231 /// Event::Start(Container::Paragraph, Attributes::new()),
232 /// Event::Str("a ".into()),
233 /// Event::Symbol("sym".into()),
234 /// Event::End,
235 /// ],
236 /// );
237 /// let html = "<p>a :sym:</p>\n";
238 /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
239 /// ```
240 Symbol(CowStr<'s>),
241 /// Left single quotation mark.
242 ///
243 /// # Examples
244 ///
245 /// ```
246 /// # use jotup::*;
247 /// let src = r#"'quote'"#;
248 /// let events: Vec<_> = Parser::new(src).collect();
249 /// assert_eq!(
250 /// &events,
251 /// &[
252 /// Event::Start(Container::Paragraph, Attributes::new()),
253 /// Event::LeftSingleQuote,
254 /// Event::Str("quote".into()),
255 /// Event::RightSingleQuote,
256 /// Event::End,
257 /// ],
258 /// );
259 /// let html = "<p>‘quote’</p>\n";
260 /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
261 /// ```
262 LeftSingleQuote,
263 /// Right single quotation mark.
264 ///
265 /// # Examples
266 ///
267 /// ```
268 /// # use jotup::*;
269 /// let src = r#"'}Tis Socrates'"#;
270 /// let events: Vec<_> = Parser::new(src).collect();
271 /// assert_eq!(
272 /// &events,
273 /// &[
274 /// Event::Start(Container::Paragraph, Attributes::new()),
275 /// Event::RightSingleQuote,
276 /// Event::Str("Tis Socrates".into()),
277 /// Event::RightSingleQuote,
278 /// Event::End,
279 /// ],
280 /// );
281 /// let html = "<p>’Tis Socrates’</p>\n";
282 /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
283 /// ```
284 RightSingleQuote,
285 /// Left single quotation mark.
286 ///
287 /// # Examples
288 ///
289 /// ```
290 /// # use jotup::*;
291 /// let src = r#""Hello," he said"#;
292 /// let events: Vec<_> = Parser::new(src).collect();
293 /// assert_eq!(
294 /// &events,
295 /// &[
296 /// Event::Start(Container::Paragraph, Attributes::new()),
297 /// Event::LeftDoubleQuote,
298 /// Event::Str("Hello,".into()),
299 /// Event::RightDoubleQuote,
300 /// Event::Str(" he said".into()),
301 /// Event::End,
302 /// ],
303 /// );
304 /// let html = "<p>“Hello,” he said</p>\n";
305 /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
306 /// ```
307 LeftDoubleQuote,
308 /// Right double quotation mark.
309 RightDoubleQuote,
310 /// A horizontal ellipsis, i.e. a set of three periods.
311 ///
312 /// # Examples
313 ///
314 /// ```
315 /// # use jotup::*;
316 /// let src = "yes...";
317 /// let events: Vec<_> = Parser::new(src).collect();
318 /// assert_eq!(
319 /// &events,
320 /// &[
321 /// Event::Start(Container::Paragraph, Attributes::new()),
322 /// Event::Str("yes".into()),
323 /// Event::Ellipsis,
324 /// Event::End,
325 /// ],
326 /// );
327 /// let html = "<p>yes…</p>\n";
328 /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
329 /// ```
330 Ellipsis,
331 /// An en dash.
332 ///
333 /// # Examples
334 ///
335 /// ```
336 /// # use jotup::*;
337 /// let src = "57--33";
338 /// let events: Vec<_> = Parser::new(src).collect();
339 /// assert_eq!(
340 /// &events,
341 /// &[
342 /// Event::Start(Container::Paragraph, Attributes::new()),
343 /// Event::Str("57".into()),
344 /// Event::EnDash,
345 /// Event::Str("33".into()),
346 /// Event::End,
347 /// ],
348 /// );
349 /// let html = "<p>57–33</p>\n";
350 /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
351 /// ```
352 EnDash,
353 /// An em dash.
354 ///
355 /// # Examples
356 ///
357 /// ```
358 /// # use jotup::*;
359 /// let src = "oxen---and";
360 /// let events: Vec<_> = Parser::new(src).collect();
361 /// assert_eq!(
362 /// &events,
363 /// &[
364 /// Event::Start(Container::Paragraph, Attributes::new()),
365 /// Event::Str("oxen".into()),
366 /// Event::EmDash,
367 /// Event::Str("and".into()),
368 /// Event::End,
369 /// ],
370 /// );
371 /// let html = "<p>oxen—and</p>\n";
372 /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
373 /// ```
374 EmDash,
375 /// A space that must not break a line.
376 ///
377 /// # Examples
378 ///
379 /// ```
380 /// # use jotup::*;
381 /// let src = "no\\ break";
382 /// let events: Vec<_> = Parser::new(src).collect();
383 /// assert_eq!(
384 /// &events,
385 /// &[
386 /// Event::Start(Container::Paragraph, Attributes::new()),
387 /// Event::Str("no".into()),
388 /// Event::Escape,
389 /// Event::NonBreakingSpace,
390 /// Event::Str("break".into()),
391 /// Event::End,
392 /// ],
393 /// );
394 /// let html = "<p>no break</p>\n";
395 /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
396 /// ```
397 NonBreakingSpace,
398 /// A newline that may or may not break a line in the output.
399 ///
400 /// # Examples
401 ///
402 /// ```
403 /// # use jotup::*;
404 /// let src = concat!(
405 /// "soft\n",
406 /// "break\n",
407 /// );
408 /// let events: Vec<_> = Parser::new(src).collect();
409 /// assert_eq!(
410 /// &events,
411 /// &[
412 /// Event::Start(Container::Paragraph, Attributes::new()),
413 /// Event::Str("soft".into()),
414 /// Event::Softbreak,
415 /// Event::Str("break".into()),
416 /// Event::End,
417 /// ],
418 /// );
419 /// let html = concat!(
420 /// "<p>soft\n",
421 /// "break</p>\n",
422 /// );
423 /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
424 /// ```
425 Softbreak,
426 /// A newline that must break a line in the output.
427 ///
428 /// # Examples
429 ///
430 /// ```
431 /// # use jotup::*;
432 /// let src = concat!(
433 /// "hard\\\n",
434 /// "break\n",
435 /// );
436 /// let events: Vec<_> = Parser::new(src).collect();
437 /// assert_eq!(
438 /// &events,
439 /// &[
440 /// Event::Start(Container::Paragraph, Attributes::new()),
441 /// Event::Str("hard".into()),
442 /// Event::Escape,
443 /// Event::Hardbreak,
444 /// Event::Str("break".into()),
445 /// Event::End,
446 /// ],
447 /// );
448 /// let html = concat!(
449 /// "<p>hard<br>\n",
450 /// "break</p>\n",
451 /// );
452 /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
453 /// ```
454 Hardbreak,
455 /// An escape character, not visible in output.
456 ///
457 /// # Examples
458 ///
459 /// ```
460 /// # use jotup::*;
461 /// let src = "\\*a\\*";
462 /// let events: Vec<_> = Parser::new(src).collect();
463 /// assert_eq!(
464 /// &events,
465 /// &[
466 /// Event::Start(Container::Paragraph, Attributes::new()),
467 /// Event::Escape,
468 /// Event::Str("*a".into()),
469 /// Event::Escape,
470 /// Event::Str("*".into()),
471 /// Event::End,
472 /// ],
473 /// );
474 /// let html = "<p>*a*</p>\n";
475 /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
476 /// ```
477 Escape,
478 /// A blank line, not visible in output.
479 ///
480 /// # Examples
481 ///
482 /// ```
483 /// # use jotup::*;
484 /// let src = concat!(
485 /// "para0\n",
486 /// "\n",
487 /// "para1\n",
488 /// );
489 /// let events: Vec<_> = Parser::new(src).collect();
490 /// assert_eq!(
491 /// &events,
492 /// &[
493 /// Event::Start(Container::Paragraph, Attributes::new()),
494 /// Event::Str("para0".into()),
495 /// Event::End,
496 /// Event::Blankline,
497 /// Event::Start(Container::Paragraph, Attributes::new()),
498 /// Event::Str("para1".into()),
499 /// Event::End,
500 /// ],
501 /// );
502 /// let html = concat!(
503 /// "<p>para0</p>\n",
504 /// "<p>para1</p>\n",
505 /// );
506 /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
507 /// ```
508 Blankline,
509 /// A thematic break, typically a horizontal rule.
510 ///
511 /// # Examples
512 ///
513 /// ```
514 /// # use jotup::*;
515 /// let src = concat!(
516 /// "para0\n",
517 /// "\n",
518 /// " * * * *\n",
519 /// "para1\n",
520 /// "\n",
521 /// "{.c}\n",
522 /// "----\n",
523 /// );
524 /// let events: Vec<_> = Parser::new(src).collect();
525 /// assert_eq!(
526 /// &events,
527 /// &[
528 /// Event::Start(Container::Paragraph, Attributes::new()),
529 /// Event::Str("para0".into()),
530 /// Event::End,
531 /// Event::Blankline,
532 /// Event::ThematicBreak(Attributes::new()),
533 /// Event::Start(Container::Paragraph, Attributes::new()),
534 /// Event::Str("para1".into()),
535 /// Event::End,
536 /// Event::Blankline,
537 /// Event::ThematicBreak(
538 /// [(AttributeKind::Class, "c".into())]
539 /// .into_iter()
540 /// .collect(),
541 /// ),
542 /// ],
543 /// );
544 /// let html = concat!(
545 /// "<p>para0</p>\n",
546 /// "<hr>\n",
547 /// "<p>para1</p>\n",
548 /// "<hr class=\"c\">\n",
549 /// );
550 /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
551 /// ```
552 ThematicBreak(Attributes<'s>),
553 /// Dangling attributes not attached to anything.
554 ///
555 /// # Examples
556 ///
557 /// ```
558 /// # use jotup::*;
559 /// let src = concat!(
560 /// "{#a}\n",
561 /// "\n",
562 /// "inline {#b}\n",
563 /// "\n",
564 /// "{#c}\n",
565 /// );
566 /// let events: Vec<_> = Parser::new(src).collect();
567 /// assert_eq!(
568 /// &events,
569 /// &[
570 /// Event::Attributes(
571 /// [(AttributeKind::Id, "a".into())]
572 /// .into_iter()
573 /// .collect(),
574 /// ),
575 /// Event::Blankline,
576 /// Event::Start(Container::Paragraph, Attributes::new()),
577 /// Event::Str("inline ".into()),
578 /// Event::Attributes(
579 /// [(AttributeKind::Id, "b".into())]
580 /// .into_iter()
581 /// .collect(),
582 /// ),
583 /// Event::End,
584 /// Event::Blankline,
585 /// Event::Attributes(
586 /// [(AttributeKind::Id, "c".into())]
587 /// .into_iter()
588 /// .collect(),
589 /// ),
590 /// ],
591 /// );
592 /// let html = concat!(
593 /// "\n",
594 /// "<p>inline </p>\n",
595 /// );
596 /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
597 /// ```
598 Attributes(Attributes<'s>),
599}
600
601/// A container that may contain other elements.
602///
603/// There are three types of containers:
604///
605/// - inline, may only contain inline elements,
606/// - block leaf, may only contain inline elements,
607/// - block container, may contain any block-level elements.
608#[derive(Debug, Clone, PartialEq, Eq)]
609pub enum Container<'s> {
610 /// The top-level document container.
611 Document,
612 /// A blockquote element.
613 ///
614 /// # Examples
615 ///
616 /// ```
617 /// # use jotup::*;
618 /// let src = concat!(
619 /// "> a\n",
620 /// "> b\n",
621 /// );
622 /// let events: Vec<_> = Parser::new(src).collect();
623 /// assert_eq!(
624 /// &events,
625 /// &[
626 /// Event::Start(Container::Blockquote, Attributes::new()),
627 /// Event::Start(Container::Paragraph, Attributes::new()),
628 /// Event::Str("a".into()),
629 /// Event::Softbreak,
630 /// Event::Str("b".into()),
631 /// Event::End,
632 /// Event::End,
633 /// ],
634 /// );
635 /// let html = concat!(
636 /// "<blockquote>\n",
637 /// "<p>a\n",
638 /// "b</p>\n",
639 /// "</blockquote>\n",
640 /// );
641 /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
642 /// ```
643 Blockquote,
644 /// A list.
645 ///
646 /// # Examples
647 ///
648 /// ```
649 /// # use jotup::*;
650 /// let src = concat!(
651 /// "- a\n",
652 /// "\n",
653 /// "- b\n",
654 /// );
655 /// let events: Vec<_> = Parser::new(src).collect();
656 /// assert_eq!(
657 /// &events,
658 /// &[
659 /// Event::Start(
660 /// Container::List {
661 /// kind: ListKind::Unordered(ListBulletType::Dash),
662 /// tight: false,
663 /// },
664 /// Attributes::new(),
665 /// ),
666 /// Event::Start(Container::ListItem, Attributes::new()),
667 /// Event::Start(Container::Paragraph, Attributes::new()),
668 /// Event::Str("a".into()),
669 /// Event::End,
670 /// Event::Blankline,
671 /// Event::End,
672 /// Event::Start(Container::ListItem, Attributes::new()),
673 /// Event::Start(Container::Paragraph, Attributes::new()),
674 /// Event::Str("b".into()),
675 /// Event::End,
676 /// Event::End,
677 /// Event::End,
678 /// ],
679 /// );
680 /// let html = concat!(
681 /// "<ul>\n",
682 /// "<li>\n",
683 /// "<p>a</p>\n",
684 /// "</li>\n",
685 /// "<li>\n",
686 /// "<p>b</p>\n",
687 /// "</li>\n",
688 /// "</ul>\n",
689 /// );
690 /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
691 /// ```
692 List { kind: ListKind, tight: bool },
693 /// An item of a list
694 ///
695 /// # Examples
696 ///
697 /// ```
698 /// # use jotup::*;
699 /// let src = "- a";
700 /// let events: Vec<_> = Parser::new(src).collect();
701 /// assert_eq!(
702 /// &events,
703 /// &[
704 /// Event::Start(
705 /// Container::List {
706 /// kind: ListKind::Unordered(ListBulletType::Dash),
707 /// tight: true,
708 /// },
709 /// Attributes::new(),
710 /// ),
711 /// Event::Start(Container::ListItem, Attributes::new()),
712 /// Event::Start(Container::Paragraph, Attributes::new()),
713 /// Event::Str("a".into()),
714 /// Event::End,
715 /// Event::End,
716 /// Event::End,
717 /// ],
718 /// );
719 /// let html = concat!(
720 /// "<ul>\n",
721 /// "<li>\n",
722 /// "a\n",
723 /// "</li>\n",
724 /// "</ul>\n",
725 /// );
726 /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
727 /// ```
728 ListItem,
729 /// An item of a task list, either checked or unchecked.
730 ///
731 /// # Examples
732 ///
733 /// ```
734 /// # use jotup::*;
735 /// let src = "- [x] a";
736 /// let events: Vec<_> = Parser::new(src).collect();
737 /// assert_eq!(
738 /// &events,
739 /// &[
740 /// Event::Start(
741 /// Container::List {
742 /// kind: ListKind::Task(ListBulletType::Dash),
743 /// tight: true
744 /// },
745 /// Attributes::new(),
746 /// ),
747 /// Event::Start(
748 /// Container::TaskListItem { checked: true },
749 /// Attributes::new(),
750 /// ),
751 /// Event::Start(Container::Paragraph, Attributes::new()),
752 /// Event::Str("a".into()),
753 /// Event::End,
754 /// Event::End,
755 /// Event::End,
756 /// ],
757 /// );
758 /// let html = concat!(
759 /// "<ul class=\"task-list\">\n",
760 /// "<li>\n",
761 /// "<input disabled=\"\" type=\"checkbox\" checked=\"\"/>\n",
762 /// "a\n",
763 /// "</li>\n",
764 /// "</ul>\n",
765 /// );
766 /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
767 /// ```
768 TaskListItem { checked: bool },
769 /// A description list.
770 ///
771 /// # Examples
772 ///
773 /// ```
774 /// # use jotup::*;
775 /// let src = concat!(
776 /// ": orange\n",
777 /// "\n",
778 /// " citrus fruit\n",
779 /// ": apple\n",
780 /// "\n",
781 /// " malus fruit\n",
782 /// );
783 /// let events: Vec<_> = Parser::new(src).collect();
784 /// assert_eq!(
785 /// &events,
786 /// &[
787 /// Event::Start(Container::DescriptionList, Attributes::new()),
788 /// Event::Start(Container::DescriptionTerm, Attributes::new()),
789 /// Event::Str("orange".into()),
790 /// Event::End,
791 /// Event::Blankline,
792 /// Event::Start(Container::DescriptionDetails, Attributes::new()),
793 /// Event::Start(Container::Paragraph, Attributes::new()),
794 /// Event::Str("citrus fruit".into()),
795 /// Event::End,
796 /// Event::End,
797 /// Event::Start(Container::DescriptionTerm, Attributes::new()),
798 /// Event::Str("apple".into()),
799 /// Event::End,
800 /// Event::Blankline,
801 /// Event::Start(Container::DescriptionDetails, Attributes::new()),
802 /// Event::Start(Container::Paragraph, Attributes::new()),
803 /// Event::Str("malus fruit".into()),
804 /// Event::End,
805 /// Event::End,
806 /// Event::End,
807 /// ],
808 /// );
809 /// let html = concat!(
810 /// "<dl>\n",
811 /// "<dt>orange</dt>\n",
812 /// "<dd>\n",
813 /// "<p>citrus fruit</p>\n",
814 /// "</dd>\n",
815 /// "<dt>apple</dt>\n",
816 /// "<dd>\n",
817 /// "<p>malus fruit</p>\n",
818 /// "</dd>\n",
819 /// "</dl>\n",
820 /// );
821 /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
822 /// ```
823 DescriptionList,
824 /// Details describing a term within a description list.
825 DescriptionDetails,
826 /// A footnote definition.
827 ///
828 /// # Examples
829 ///
830 /// ```
831 /// # use jotup::*;
832 /// let src = concat!(
833 /// "txt[^nb]\n",
834 /// "\n",
835 /// "[^nb]: actually..\n",
836 /// );
837 /// let events: Vec<_> = Parser::new(src).collect();
838 /// assert_eq!(
839 /// &events,
840 /// &[
841 /// Event::Start(Container::Paragraph, Attributes::new()),
842 /// Event::Str("txt".into()),
843 /// Event::FootnoteReference("nb".into()),
844 /// Event::End,
845 /// Event::Blankline,
846 /// Event::Start(
847 /// Container::Footnote { label: "nb".into() },
848 /// Attributes::new(),
849 /// ),
850 /// Event::Start(Container::Paragraph, Attributes::new()),
851 /// Event::Str("actually..".into()),
852 /// Event::End,
853 /// Event::End,
854 /// ],
855 /// );
856 /// let html = concat!(
857 /// "<p>txt<a id=\"fnref1\" href=\"#fn1\" role=\"doc-noteref\"><sup>1</sup></a></p>\n",
858 /// "<section role=\"doc-endnotes\">\n",
859 /// "<hr>\n",
860 /// "<ol>\n",
861 /// "<li id=\"fn1\">\n",
862 /// "<p>actually..<a href=\"#fnref1\" role=\"doc-backlink\">↩\u{fe0e}</a></p>\n",
863 /// "</li>\n",
864 /// "</ol>\n",
865 /// "</section>\n",
866 /// );
867 /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
868 /// ```
869 Footnote { label: CowStr<'s> },
870 /// A table element.
871 ///
872 /// # Examples
873 ///
874 /// ```
875 /// # use jotup::*;
876 /// let src = concat!(
877 /// "| a | b |\n",
878 /// "|---|--:|\n",
879 /// "| 1 | 2 |\n",
880 /// );
881 /// let events: Vec<_> = Parser::new(src).collect();
882 /// assert_eq!(
883 /// &events,
884 /// &[
885 /// Event::Start(Container::Table, Attributes::new()),
886 /// Event::Start(
887 /// Container::TableRow { head: true },
888 /// Attributes::new(),
889 /// ),
890 /// Event::Start(
891 /// Container::TableCell {
892 /// alignment: Alignment::Unspecified,
893 /// head: true
894 /// },
895 /// Attributes::new(),
896 /// ),
897 /// Event::Str("a".into()),
898 /// Event::End,
899 /// Event::Start(
900 /// Container::TableCell {
901 /// alignment: Alignment::Right,
902 /// head: true,
903 /// },
904 /// Attributes::new(),
905 /// ),
906 /// Event::Str("b".into()),
907 /// Event::End,
908 /// Event::End,
909 /// Event::Start(
910 /// Container::TableRow { head: false },
911 /// Attributes::new(),
912 /// ),
913 /// Event::Start(
914 /// Container::TableCell {
915 /// alignment: Alignment::Unspecified,
916 /// head: false
917 /// },
918 /// Attributes::new(),
919 /// ),
920 /// Event::Str("1".into()),
921 /// Event::End,
922 /// Event::Start(
923 /// Container::TableCell {
924 /// alignment: Alignment::Right,
925 /// head: false,
926 /// },
927 /// Attributes::new(),
928 /// ),
929 /// Event::Str("2".into()),
930 /// Event::End,
931 /// Event::End,
932 /// Event::End,
933 /// ],
934 /// );
935 /// let html = concat!(
936 /// "<table>\n",
937 /// "<tr>\n",
938 /// "<th>a</th>\n",
939 /// "<th style=\"text-align: right;\">b</th>\n",
940 /// "</tr>\n",
941 /// "<tr>\n",
942 /// "<td>1</td>\n",
943 /// "<td style=\"text-align: right;\">2</td>\n",
944 /// "</tr>\n",
945 /// "</table>\n",
946 /// );
947 /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
948 /// ```
949 Table,
950 /// A row element of a table.
951 TableRow { head: bool },
952 /// A section belonging to a top level heading.
953 ///
954 /// # Examples
955 ///
956 /// ```
957 /// # use jotup::*;
958 /// let src = concat!(
959 /// "# outer\n",
960 /// "\n",
961 /// "## inner\n",
962 /// );
963 /// let events: Vec<_> = Parser::new(src).collect();
964 /// assert_eq!(
965 /// &events,
966 /// &[
967 /// Event::Start(
968 /// Container::Section { id: "outer".into() },
969 /// Attributes::new(),
970 /// ),
971 /// Event::Start(
972 /// Container::Heading {
973 /// level: 1,
974 /// has_section: true,
975 /// id: "outer".into(),
976 /// },
977 /// Attributes::new(),
978 /// ),
979 /// Event::Str("outer".into()),
980 /// Event::End,
981 /// Event::Blankline,
982 /// Event::Start(
983 /// Container::Section { id: "inner".into() },
984 /// Attributes::new(),
985 /// ),
986 /// Event::Start(
987 /// Container::Heading {
988 /// level: 2,
989 /// has_section: true,
990 /// id: "inner".into(),
991 /// },
992 /// Attributes::new(),
993 /// ),
994 /// Event::Str("inner".into()),
995 /// Event::End,
996 /// Event::End,
997 /// Event::End,
998 /// ],
999 /// );
1000 /// let html = concat!(
1001 /// "<section id=\"outer\">\n",
1002 /// "<h1>outer</h1>\n",
1003 /// "<section id=\"inner\">\n",
1004 /// "<h2>inner</h2>\n",
1005 /// "</section>\n",
1006 /// "</section>\n",
1007 /// );
1008 /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
1009 /// ```
1010 Section { id: CowStr<'s> },
1011 /// A block-level divider element.
1012 ///
1013 /// # Examples
1014 ///
1015 /// ```
1016 /// # use jotup::*;
1017 /// let src = concat!(
1018 /// "::: note\n",
1019 /// "this is a note\n",
1020 /// ":::\n",
1021 /// );
1022 /// let events: Vec<_> = Parser::new(src).collect();
1023 /// assert_eq!(
1024 /// &events,
1025 /// &[
1026 /// Event::Start(
1027 /// Container::Div { class: "note".into() },
1028 /// Attributes::new(),
1029 /// ),
1030 /// Event::Start(Container::Paragraph, Attributes::new()),
1031 /// Event::Str("this is a note".into()),
1032 /// Event::End,
1033 /// Event::End,
1034 /// ],
1035 /// );
1036 /// let html = concat!(
1037 /// "<div class=\"note\">\n",
1038 /// "<p>this is a note</p>\n",
1039 /// "</div>\n",
1040 /// );
1041 /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
1042 /// ```
1043 Div { class: CowStr<'s> },
1044 /// A paragraph.
1045 Paragraph,
1046 /// A heading.
1047 ///
1048 /// # Examples
1049 ///
1050 /// ```
1051 /// # use jotup::*;
1052 /// let src = "# heading";
1053 /// let events: Vec<_> = Parser::new(src).collect();
1054 /// assert_eq!(
1055 /// &events,
1056 /// &[
1057 /// Event::Start(
1058 /// Container::Section { id: "heading".into() },
1059 /// Attributes::new(),
1060 /// ),
1061 /// Event::Start(
1062 /// Container::Heading {
1063 /// level: 1,
1064 /// has_section: true,
1065 /// id: "heading".into(),
1066 /// },
1067 /// Attributes::new(),
1068 /// ),
1069 /// Event::Str("heading".into()),
1070 /// Event::End,
1071 /// Event::End,
1072 /// ],
1073 /// );
1074 /// let html = concat!(
1075 /// "<section id=\"heading\">\n",
1076 /// "<h1>heading</h1>\n",
1077 /// "</section>\n",
1078 /// );
1079 /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
1080 /// ```
1081 Heading {
1082 level: u16,
1083 has_section: bool,
1084 id: CowStr<'s>,
1085 },
1086 /// A cell element of row within a table.
1087 TableCell { alignment: Alignment, head: bool },
1088 /// A caption within a table.
1089 ///
1090 /// # Examples
1091 ///
1092 /// ```
1093 /// # use jotup::*;
1094 /// let src = concat!(
1095 /// "|a|\n",
1096 /// "^ caption\n",
1097 /// );
1098 /// let events: Vec<_> = Parser::new(src).collect();
1099 /// assert_eq!(
1100 /// &events,
1101 /// &[
1102 /// Event::Start(Container::Table, Attributes::new()),
1103 /// Event::Start(Container::Caption, Attributes::new()),
1104 /// Event::Str("caption".into()),
1105 /// Event::End,
1106 /// Event::Start(
1107 /// Container::TableRow { head: false },
1108 /// Attributes::new(),
1109 /// ),
1110 /// Event::Start(
1111 /// Container::TableCell {
1112 /// alignment: Alignment::Unspecified,
1113 /// head: false
1114 /// },
1115 /// Attributes::new(),
1116 /// ),
1117 /// Event::Str("a".into()),
1118 /// Event::End,
1119 /// Event::End,
1120 /// Event::End,
1121 /// ],
1122 /// );
1123 /// let html = concat!(
1124 /// "<table>\n",
1125 /// "<caption>caption</caption>\n",
1126 /// "<tr>\n",
1127 /// "<td>a</td>\n",
1128 /// "</tr>\n",
1129 /// "</table>\n",
1130 /// );
1131 /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
1132 /// ```
1133 Caption,
1134 /// A term within a description list.
1135 DescriptionTerm,
1136 /// A link definition.
1137 ///
1138 /// # Examples
1139 ///
1140 /// ```
1141 /// # use jotup::*;
1142 /// let src = "[label]: url";
1143 /// let events: Vec<_> = Parser::new(src).collect();
1144 /// assert_eq!(
1145 /// &events,
1146 /// &[
1147 /// Event::Start(
1148 /// Container::LinkDefinition { label: "label".into() },
1149 /// Attributes::new(),
1150 /// ),
1151 /// Event::Str("url".into()),
1152 /// Event::End,
1153 /// ],
1154 /// );
1155 /// let html = "\n";
1156 /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
1157 /// ```
1158 LinkDefinition { label: CowStr<'s> },
1159 /// A block with raw markup for a specific output format.
1160 ///
1161 /// # Examples
1162 ///
1163 /// ```
1164 /// # use jotup::*;
1165 /// let src = concat!(
1166 /// "```=html\n",
1167 /// "<tag>x</tag>\n",
1168 /// "```\n",
1169 /// );
1170 /// let events: Vec<_> = Parser::new(src).collect();
1171 /// assert_eq!(
1172 /// &events,
1173 /// &[
1174 /// Event::Start(
1175 /// Container::RawBlock { format: "html".into() },
1176 /// Attributes::new(),
1177 /// ),
1178 /// Event::Str("<tag>x</tag>".into()),
1179 /// Event::End,
1180 /// ],
1181 /// );
1182 /// let html = "<tag>x</tag>\n";
1183 /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
1184 /// ```
1185 RawBlock { format: CowStr<'s> },
1186 /// A block with code in a specific language.
1187 ///
1188 /// # Examples
1189 ///
1190 /// ```
1191 /// # use jotup::*;
1192 /// let src = concat!(
1193 /// "```html\n",
1194 /// "<tag>x</tag>\n",
1195 /// "```\n",
1196 /// );
1197 /// let events: Vec<_> = Parser::new(src).collect();
1198 /// assert_eq!(
1199 /// &events,
1200 /// &[
1201 /// Event::Start(
1202 /// Container::CodeBlock { language: "html".into() },
1203 /// Attributes::new(),
1204 /// ),
1205 /// Event::Str("<tag>x</tag>\n".into()),
1206 /// Event::End,
1207 /// ],
1208 /// );
1209 /// let html = concat!(
1210 /// "<pre><code class=\"language-html\"><tag>x</tag>\n",
1211 /// "</code></pre>\n",
1212 /// );
1213 /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
1214 /// ```
1215 CodeBlock { language: CowStr<'s> },
1216 /// An inline divider element.
1217 ///
1218 /// # Examples
1219 ///
1220 /// Can be used to add attributes:
1221 ///
1222 /// ```
1223 /// # use jotup::*;
1224 /// let src = concat!(
1225 /// "word{#a}\n",
1226 /// "[two words]{#b}\n",
1227 /// );
1228 /// let events: Vec<_> = Parser::new(src).collect();
1229 /// assert_eq!(
1230 /// &events,
1231 /// &[
1232 /// Event::Start(Container::Paragraph, Attributes::new()),
1233 /// Event::Start(
1234 /// Container::Span,
1235 /// [(AttributeKind::Id, "a".into())].into_iter().collect(),
1236 /// ),
1237 /// Event::Str("word".into()),
1238 /// Event::End,
1239 /// Event::Softbreak,
1240 /// Event::Start(
1241 /// Container::Span,
1242 /// [(AttributeKind::Id, "b".into())].into_iter().collect(),
1243 /// ),
1244 /// Event::Str("two words".into()),
1245 /// Event::End,
1246 /// Event::End,
1247 /// ],
1248 /// );
1249 /// let html = concat!(
1250 /// "<p><span id=\"a\">word</span>\n",
1251 /// "<span id=\"b\">two words</span></p>\n",
1252 /// );
1253 /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
1254 /// ```
1255 Span,
1256 /// An inline link, the first field is either a destination URL or an unresolved tag.
1257 ///
1258 /// # Examples
1259 ///
1260 /// URLs or email addresses can be enclosed with angled brackets to create a hyperlink:
1261 ///
1262 /// ```
1263 /// # use jotup::*;
1264 /// let src = concat!(
1265 /// "<https://example.com>\n",
1266 /// "<me@example.com>\n",
1267 /// );
1268 /// let events: Vec<_> = Parser::new(src).collect();
1269 /// assert_eq!(
1270 /// &events,
1271 /// &[
1272 /// Event::Start(Container::Paragraph, Attributes::new()),
1273 /// Event::Start(
1274 /// Container::Link(
1275 /// "https://example.com".into(),
1276 /// LinkType::AutoLink,
1277 /// ),
1278 /// Attributes::new(),
1279 /// ),
1280 /// Event::Str("https://example.com".into()),
1281 /// Event::End,
1282 /// Event::Softbreak,
1283 /// Event::Start(
1284 /// Container::Link(
1285 /// "me@example.com".into(),
1286 /// LinkType::Email,
1287 /// ),
1288 /// Attributes::new(),
1289 /// ),
1290 /// Event::Str("me@example.com".into()),
1291 /// Event::End,
1292 /// Event::End,
1293 /// ],
1294 /// );
1295 /// let html = concat!(
1296 /// "<p><a href=\"https://example.com\">https://example.com</a>\n",
1297 /// "<a href=\"mailto:me@example.com\">me@example.com</a></p>\n",
1298 /// );
1299 /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
1300 /// ```
1301 ///
1302 /// Anchor text and the URL can be specified inline:
1303 ///
1304 /// ```
1305 /// # use jotup::*;
1306 /// let src = "[anchor](url)\n";
1307 /// let events: Vec<_> = Parser::new(src).collect();
1308 /// assert_eq!(
1309 /// &events,
1310 /// &[
1311 /// Event::Start(Container::Paragraph, Attributes::new()),
1312 /// Event::Start(
1313 /// Container::Link(
1314 /// "url".into(),
1315 /// LinkType::Span(SpanLinkType::Inline),
1316 /// ),
1317 /// Attributes::new(),
1318 /// ),
1319 /// Event::Str("anchor".into()),
1320 /// Event::End,
1321 /// Event::End,
1322 /// ],
1323 /// );
1324 /// let html = "<p><a href=\"url\">anchor</a></p>\n";
1325 /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
1326 /// ```
1327 ///
1328 /// Alternatively, the URL can be retrieved from a link definition using hard brackets, if it
1329 /// exists:
1330 ///
1331 /// ```
1332 /// # use jotup::*;
1333 /// let src = concat!(
1334 /// "[a][label]\n",
1335 /// "[b][non-existent]\n",
1336 /// "\n",
1337 /// "[label]: url\n",
1338 /// );
1339 /// let events: Vec<_> = Parser::new(src).collect();
1340 /// assert_eq!(
1341 /// &events,
1342 /// &[
1343 /// Event::Start(Container::Paragraph, Attributes::new()),
1344 /// Event::Start(
1345 /// Container::Link(
1346 /// "url".into(),
1347 /// LinkType::Span(SpanLinkType::Reference),
1348 /// ),
1349 /// Attributes::new(),
1350 /// ),
1351 /// Event::Str("a".into()),
1352 /// Event::End,
1353 /// Event::Softbreak,
1354 /// Event::Start(
1355 /// Container::Link(
1356 /// "non-existent".into(),
1357 /// LinkType::Span(SpanLinkType::Unresolved),
1358 /// ),
1359 /// Attributes::new(),
1360 /// ),
1361 /// Event::Str("b".into()),
1362 /// Event::End,
1363 /// Event::End,
1364 /// Event::Blankline,
1365 /// Event::Start(
1366 /// Container::LinkDefinition { label: "label".into() },
1367 /// Attributes::new(),
1368 /// ),
1369 /// Event::Str("url".into()),
1370 /// Event::End,
1371 /// ],
1372 /// );
1373 /// let html = concat!(
1374 /// "<p><a href=\"url\">a</a>\n",
1375 /// "<a>b</a></p>\n",
1376 /// );
1377 /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
1378 /// ```
1379 Link(CowStr<'s>, LinkType),
1380 /// An inline image, the first field is either a destination URL or an unresolved tag.
1381 ///
1382 /// # Examples
1383 ///
1384 /// Inner Str objects compose the alternative text:
1385 ///
1386 /// ```
1387 /// # use jotup::*;
1388 /// let src = "";
1389 /// let events: Vec<_> = Parser::new(src).collect();
1390 /// assert_eq!(
1391 /// &events,
1392 /// &[
1393 /// Event::Start(Container::Paragraph, Attributes::new()),
1394 /// Event::Start(
1395 /// Container::Image("img.png".into(), SpanLinkType::Inline),
1396 /// Attributes::new(),
1397 /// ),
1398 /// Event::Str("alt text".into()),
1399 /// Event::End,
1400 /// Event::End,
1401 /// ],
1402 /// );
1403 /// let html = "<p><img alt=\"alt text\" src=\"img.png\"></p>\n";
1404 /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
1405 /// ```
1406 Image(CowStr<'s>, SpanLinkType),
1407 /// An inline verbatim string.
1408 ///
1409 /// # Examples
1410 ///
1411 /// ```
1412 /// # use jotup::*;
1413 /// let src = "inline `verbatim`";
1414 /// let events: Vec<_> = Parser::new(src).collect();
1415 /// assert_eq!(
1416 /// &events,
1417 /// &[
1418 /// Event::Start(Container::Paragraph, Attributes::new()),
1419 /// Event::Str("inline ".into()),
1420 /// Event::Start(Container::Verbatim, Attributes::new()),
1421 /// Event::Str("verbatim".into()),
1422 /// Event::End,
1423 /// Event::End,
1424 /// ],
1425 /// );
1426 /// let html = "<p>inline <code>verbatim</code></p>\n";
1427 /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
1428 /// ```
1429 Verbatim,
1430 /// An inline or display math element.
1431 ///
1432 /// # Examples
1433 ///
1434 /// ```
1435 /// # use jotup::*;
1436 /// let src = concat!(
1437 /// "inline $`a\\cdot{}b` or\n",
1438 /// "display $$`\\frac{a}{b}`\n",
1439 /// );
1440 /// let events: Vec<_> = Parser::new(src).collect();
1441 /// assert_eq!(
1442 /// &events,
1443 /// &[
1444 /// Event::Start(Container::Paragraph, Attributes::new()),
1445 /// Event::Str("inline ".into()),
1446 /// Event::Start(
1447 /// Container::Math { display: false },
1448 /// Attributes::new(),
1449 /// ),
1450 /// Event::Str(r"a\cdot{}b".into()),
1451 /// Event::End,
1452 /// Event::Str(" or".into()),
1453 /// Event::Softbreak,
1454 /// Event::Str("display ".into()),
1455 /// Event::Start(
1456 /// Container::Math { display: true },
1457 /// Attributes::new(),
1458 /// ),
1459 /// Event::Str(r"\frac{a}{b}".into()),
1460 /// Event::End,
1461 /// Event::End,
1462 /// ],
1463 /// );
1464 /// let html = concat!(
1465 /// "<p>inline <span class=\"math inline\">\\(a\\cdot{}b\\)</span> or\n",
1466 /// "display <span class=\"math display\">\\[\\frac{a}{b}\\]</span></p>\n",
1467 /// );
1468 /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
1469 /// ```
1470 Math { display: bool },
1471 /// Inline raw markup for a specific output format.
1472 ///
1473 /// # Examples
1474 ///
1475 /// ```
1476 /// # use jotup::*;
1477 /// let src = "`<tag>a</tag>`{=html}";
1478 /// let events: Vec<_> = Parser::new(src).collect();
1479 /// assert_eq!(
1480 /// &events,
1481 /// &[
1482 /// Event::Start(Container::Paragraph, Attributes::new()),
1483 /// Event::Start(
1484 /// Container::RawInline { format: "html".into() }, Attributes::new(),
1485 /// ),
1486 /// Event::Str("<tag>a</tag>".into()),
1487 /// Event::End,
1488 /// Event::End,
1489 /// ],
1490 /// );
1491 /// let html = "<p><tag>a</tag></p>\n";
1492 /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
1493 /// ```
1494 RawInline { format: CowStr<'s> },
1495 /// A subscripted element.
1496 ///
1497 /// # Examples
1498 ///
1499 /// ```
1500 /// # use jotup::*;
1501 /// let src = "~SUB~";
1502 /// let events: Vec<_> = Parser::new(src).collect();
1503 /// assert_eq!(
1504 /// &events,
1505 /// &[
1506 /// Event::Start(Container::Paragraph, Attributes::new()),
1507 /// Event::Start(Container::Subscript, Attributes::new()),
1508 /// Event::Str("SUB".into()),
1509 /// Event::End,
1510 /// Event::End,
1511 /// ],
1512 /// );
1513 /// let html = "<p><sub>SUB</sub></p>\n";
1514 /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
1515 /// ```
1516 Subscript,
1517 /// A superscripted element.
1518 ///
1519 /// # Examples
1520 ///
1521 /// ```
1522 /// # use jotup::*;
1523 /// let src = "^SUP^";
1524 /// let events: Vec<_> = Parser::new(src).collect();
1525 /// assert_eq!(
1526 /// &events,
1527 /// &[
1528 /// Event::Start(Container::Paragraph, Attributes::new()),
1529 /// Event::Start(Container::Superscript, Attributes::new()),
1530 /// Event::Str("SUP".into()),
1531 /// Event::End,
1532 /// Event::End,
1533 /// ],
1534 /// );
1535 /// let html = "<p><sup>SUP</sup></p>\n";
1536 /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
1537 /// ```
1538 Superscript,
1539 /// An inserted inline element.
1540 ///
1541 /// # Examples
1542 ///
1543 /// ```
1544 /// # use jotup::*;
1545 /// let src = "{+INS+}";
1546 /// let events: Vec<_> = Parser::new(src).collect();
1547 /// assert_eq!(
1548 /// &events,
1549 /// &[
1550 /// Event::Start(Container::Paragraph, Attributes::new()),
1551 /// Event::Start(Container::Insert, Attributes::new()),
1552 /// Event::Str("INS".into()),
1553 /// Event::End,
1554 /// Event::End,
1555 /// ],
1556 /// );
1557 /// let html = "<p><ins>INS</ins></p>\n";
1558 /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
1559 /// ```
1560 Insert,
1561 /// A deleted inline element.
1562 ///
1563 /// # Examples
1564 ///
1565 /// ```
1566 /// # use jotup::*;
1567 /// let src = "{-DEL-}";
1568 /// let events: Vec<_> = Parser::new(src).collect();
1569 /// assert_eq!(
1570 /// &events,
1571 /// &[
1572 /// Event::Start(Container::Paragraph, Attributes::new()),
1573 /// Event::Start(Container::Delete, Attributes::new()),
1574 /// Event::Str("DEL".into()),
1575 /// Event::End,
1576 /// Event::End,
1577 /// ],
1578 /// );
1579 /// let html = "<p><del>DEL</del></p>\n";
1580 /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
1581 /// ```
1582 Delete,
1583 /// An inline element emphasized with a bold typeface.
1584 ///
1585 /// # Examples
1586 ///
1587 /// ```
1588 /// # use jotup::*;
1589 /// let src = "*STRONG*";
1590 /// let events: Vec<_> = Parser::new(src).collect();
1591 /// assert_eq!(
1592 /// &events,
1593 /// &[
1594 /// Event::Start(Container::Paragraph, Attributes::new()),
1595 /// Event::Start(Container::Strong, Attributes::new()),
1596 /// Event::Str("STRONG".into()),
1597 /// Event::End,
1598 /// Event::End,
1599 /// ],
1600 /// );
1601 /// let html = "<p><strong>STRONG</strong></p>\n";
1602 /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
1603 /// ```
1604 Strong,
1605 /// An emphasized inline element.
1606 ///
1607 /// # Examples
1608 ///
1609 /// ```
1610 /// # use jotup::*;
1611 /// let src = "_EM_";
1612 /// let events: Vec<_> = Parser::new(src).collect();
1613 /// assert_eq!(
1614 /// &events,
1615 /// &[
1616 /// Event::Start(Container::Paragraph, Attributes::new()),
1617 /// Event::Start(Container::Emphasis, Attributes::new()),
1618 /// Event::Str("EM".into()),
1619 /// Event::End,
1620 /// Event::End,
1621 /// ],
1622 /// );
1623 /// let html = "<p><em>EM</em></p>\n";
1624 /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
1625 /// ```
1626 Emphasis,
1627 /// A highlighted inline element.
1628 ///
1629 /// # Examples
1630 ///
1631 /// ```
1632 /// # use jotup::*;
1633 /// let src = "{=MARK=}";
1634 /// let events: Vec<_> = Parser::new(src).collect();
1635 /// assert_eq!(
1636 /// &events,
1637 /// &[
1638 /// Event::Start(Container::Paragraph, Attributes::new()),
1639 /// Event::Start(Container::Mark, Attributes::new()),
1640 /// Event::Str("MARK".into()),
1641 /// Event::End,
1642 /// Event::End,
1643 /// ],
1644 /// );
1645 /// let html = "<p><mark>MARK</mark></p>\n";
1646 /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
1647 /// ```
1648 Mark,
1649}
1650
1651impl Container<'_> {
1652 /// Is a block element.
1653 #[must_use]
1654 pub fn is_block(&self) -> bool {
1655 match self {
1656 Self::Document
1657 | Self::Blockquote
1658 | Self::List { .. }
1659 | Self::ListItem
1660 | Self::TaskListItem { .. }
1661 | Self::DescriptionList
1662 | Self::DescriptionDetails
1663 | Self::Footnote { .. }
1664 | Self::Table
1665 | Self::TableRow { .. }
1666 | Self::Section { .. }
1667 | Self::Div { .. }
1668 | Self::Paragraph
1669 | Self::Heading { .. }
1670 | Self::TableCell { .. }
1671 | Self::Caption
1672 | Self::DescriptionTerm
1673 | Self::LinkDefinition { .. }
1674 | Self::RawBlock { .. }
1675 | Self::CodeBlock { .. } => true,
1676 Self::Span
1677 | Self::Link(..)
1678 | Self::Image(..)
1679 | Self::Verbatim
1680 | Self::Math { .. }
1681 | Self::RawInline { .. }
1682 | Self::Subscript
1683 | Self::Superscript
1684 | Self::Insert
1685 | Self::Delete
1686 | Self::Strong
1687 | Self::Emphasis
1688 | Self::Mark => false,
1689 }
1690 }
1691
1692 /// Is a block element that may contain children blocks.
1693 #[must_use]
1694 pub fn is_block_container(&self) -> bool {
1695 match self {
1696 Self::Document
1697 | Self::Blockquote
1698 | Self::List { .. }
1699 | Self::ListItem
1700 | Self::TaskListItem { .. }
1701 | Self::DescriptionList
1702 | Self::DescriptionDetails
1703 | Self::Footnote { .. }
1704 | Self::Table
1705 | Self::TableRow { .. }
1706 | Self::Section { .. }
1707 | Self::Div { .. } => true,
1708 Self::Paragraph
1709 | Self::Heading { .. }
1710 | Self::TableCell { .. }
1711 | Self::Caption
1712 | Self::DescriptionTerm
1713 | Self::LinkDefinition { .. }
1714 | Self::RawBlock { .. }
1715 | Self::CodeBlock { .. }
1716 | Self::Span
1717 | Self::Link(..)
1718 | Self::Image(..)
1719 | Self::Verbatim
1720 | Self::Math { .. }
1721 | Self::RawInline { .. }
1722 | Self::Subscript
1723 | Self::Superscript
1724 | Self::Insert
1725 | Self::Delete
1726 | Self::Strong
1727 | Self::Emphasis
1728 | Self::Mark => false,
1729 }
1730 }
1731}
1732
1733/// Alignment of a table column.
1734#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1735pub enum Alignment {
1736 Unspecified,
1737 Left,
1738 Center,
1739 Right,
1740}
1741
1742/// The type of an inline span link.
1743#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1744pub enum SpanLinkType {
1745 /// E.g. `[text](url)`
1746 Inline,
1747 /// In the form `[text][tag]` or `[tag][]`.
1748 Reference,
1749 /// Like reference, but the tag is unresolved.
1750 Unresolved,
1751}
1752
1753/// The type of an inline link.
1754#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1755pub enum LinkType {
1756 /// E.g. `[text](url)`.
1757 Span(SpanLinkType),
1758 /// In the form `<url>`.
1759 AutoLink,
1760 /// In the form `<address>`.
1761 Email,
1762}
1763
1764/// Character used to create an unordered list item.
1765#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1766pub enum ListBulletType {
1767 /// `-`
1768 Dash,
1769 /// `*`
1770 Star,
1771 /// `+`
1772 Plus,
1773}
1774
1775impl TryFrom<u8> for ListBulletType {
1776 type Error = ();
1777
1778 fn try_from(c: u8) -> Result<Self, Self::Error> {
1779 match c {
1780 b'-' => Ok(Self::Dash),
1781 b'*' => Ok(Self::Star),
1782 b'+' => Ok(Self::Plus),
1783 _ => Err(()),
1784 }
1785 }
1786}
1787
1788impl TryFrom<char> for ListBulletType {
1789 type Error = ();
1790
1791 fn try_from(c: char) -> Result<Self, Self::Error> {
1792 u8::try_from(u32::from(c))
1793 .map_err(|_| ())
1794 .and_then(Self::try_from)
1795 }
1796}
1797
1798impl From<ListBulletType> for u8 {
1799 fn from(t: ListBulletType) -> Self {
1800 match t {
1801 ListBulletType::Dash => b'-',
1802 ListBulletType::Star => b'*',
1803 ListBulletType::Plus => b'+',
1804 }
1805 }
1806}
1807
1808impl From<ListBulletType> for char {
1809 fn from(t: ListBulletType) -> Self {
1810 u8::from(t).into()
1811 }
1812}
1813
1814/// The type of a list.
1815#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1816pub enum ListKind {
1817 /// A bullet list.
1818 Unordered(ListBulletType),
1819 /// An enumerated list.
1820 Ordered {
1821 numbering: OrderedListNumbering,
1822 style: OrderedListStyle,
1823 start: u64,
1824 },
1825 /// A task list.
1826 Task(ListBulletType),
1827}
1828
1829/// Numbering type of an ordered list.
1830#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1831pub enum OrderedListNumbering {
1832 /// Decimal numbering, e.g. `1)`.
1833 Decimal,
1834 /// Lowercase alphabetic numbering, e.g. `a)`.
1835 AlphaLower,
1836 /// Uppercase alphabetic numbering, e.g. `A)`.
1837 AlphaUpper,
1838 /// Lowercase roman or alphabetic numbering, e.g. `iv)`.
1839 RomanLower,
1840 /// Uppercase roman or alphabetic numbering, e.g. `IV)`.
1841 RomanUpper,
1842}
1843
1844/// Style of an ordered list.
1845#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1846pub enum OrderedListStyle {
1847 /// Number is followed by a period, e.g. `1.`.
1848 Period,
1849 /// Number is followed by a closing parenthesis, e.g. `1)`.
1850 Paren,
1851 /// Number is enclosed by parentheses, e.g. `(1)`.
1852 ParenParen,
1853}
1854
1855impl OrderedListNumbering {
1856 fn parse_number(self, n: &str) -> u64 {
1857 match self {
1858 Self::Decimal => n.parse().unwrap(),
1859 Self::AlphaLower | Self::AlphaUpper => {
1860 let d0 = u64::from(if matches!(self, Self::AlphaLower) {
1861 b'a'
1862 } else {
1863 b'A'
1864 });
1865 let weights = (1..=n.len()).scan(1, |a, _| {
1866 let prev = *a;
1867 *a *= 26;
1868 Some(prev)
1869 });
1870 n.as_bytes()
1871 .iter()
1872 .rev()
1873 .copied()
1874 .map(u64::from)
1875 .zip(weights)
1876 .map(|(d, w)| w * (d - d0 + 1))
1877 .sum()
1878 }
1879 Self::RomanLower | Self::RomanUpper => {
1880 fn value(d: char) -> u64 {
1881 match d {
1882 'i' | 'I' => 1,
1883 'v' | 'V' => 5,
1884 'x' | 'X' => 10,
1885 'l' | 'L' => 50,
1886 'c' | 'C' => 100,
1887 'd' | 'D' => 500,
1888 'm' | 'M' => 1000,
1889 _ => panic!(),
1890 }
1891 }
1892 let mut prev = 0;
1893 let mut sum = 0;
1894 for d in n.chars().rev() {
1895 let v = value(d);
1896 if v < prev {
1897 sum -= v;
1898 } else {
1899 sum += v;
1900 }
1901 prev = v;
1902 }
1903 sum
1904 }
1905 }
1906 }
1907}
1908
1909impl OrderedListStyle {
1910 fn number(self, marker: &str) -> &str {
1911 &marker[usize::from(matches!(self, Self::ParenParen))..marker.len() - 1]
1912 }
1913}
1914
1915#[cfg(not(feature = "deterministic"))]
1916type Map<K, V> = std::collections::HashMap<K, V>;
1917#[cfg(feature = "deterministic")]
1918type Map<K, V> = std::collections::BTreeMap<K, V>;
1919
1920#[cfg(not(feature = "deterministic"))]
1921type Set<T> = std::collections::HashSet<T>;
1922#[cfg(feature = "deterministic")]
1923type Set<T> = std::collections::BTreeSet<T>;
1924
1925/// A parser that generates [`Event`]s from a Djot document.
1926///
1927/// When created, it will perform an initial pass and build up a tree of the document's block
1928/// structure that will be kept for the duration of the parser's lifetime. Then, when the iterator
1929/// is advanced, the parser will start from the beginning of the document and parse inline elements
1930/// and emit [`Event`]s.
1931///
1932/// It is possible to clone the parser to e.g. avoid performing the block parsing multiple times.
1933#[derive(Clone)]
1934pub struct Parser<'s> {
1935 src: &'s str,
1936
1937 /// Block tree parsed at first.
1938 blocks: std::iter::Peekable<std::vec::IntoIter<block::Event<'s>>>,
1939
1940 /// Contents obtained by the prepass.
1941 pre_pass: PrePass<'s>,
1942
1943 /// Last parsed block attributes, and its span.
1944 block_attributes: Option<(Attributes<'s>, std::ops::Range<usize>)>,
1945
1946 /// Current table row is a head row.
1947 table_head_row: bool,
1948
1949 /// Currently within a verbatim code block.
1950 verbatim: bool,
1951
1952 /// Inline parser.
1953 inline_parser: inline::Parser<'s>,
1954}
1955
1956#[derive(Clone)]
1957struct Heading {
1958 /// Location of heading in src.
1959 location: u32,
1960 /// Automatically generated id from heading text.
1961 id_auto: String,
1962 /// Text of heading, formatting stripped.
1963 text: String,
1964 /// Overriding id from an explicit attribute on the heading.
1965 id_override: Option<String>,
1966}
1967
1968/// Because of potential future references, an initial pass is required to obtain all definitions.
1969#[derive(Clone)]
1970struct PrePass<'s> {
1971 /// Link definitions and their attributes.
1972 link_definitions: Map<&'s str, (CowStr<'s>, attr::Attributes<'s>)>,
1973 /// Cache of all heading ids.
1974 headings: Vec<Heading>,
1975 /// Indices to headings sorted lexicographically.
1976 headings_lex: Vec<usize>,
1977}
1978
1979impl<'s> PrePass<'s> {
1980 #[must_use]
1981 fn new(
1982 src: &'s str,
1983 mut blocks: std::slice::Iter<block::Event<'s>>,
1984 inline_parser: &mut inline::Parser<'s>,
1985 ) -> Self {
1986 use std::fmt::Write;
1987 let mut link_definitions = Map::new();
1988 let mut headings: Vec<Heading> = Vec::new();
1989 let mut used_ids: Set<String> = Set::new();
1990
1991 let mut attr_prev: Vec<std::ops::Range<usize>> = Vec::new();
1992 while let Some(e) = blocks.next() {
1993 match e.kind {
1994 block::EventKind::Enter(block::Node::Leaf(block::Leaf::LinkDefinition {
1995 label,
1996 })) => {
1997 // All link definition tags have to be obtained initially, as references can
1998 // appear before the definition.
1999 let attrs = Attributes::from_iter(attr_prev.iter().flat_map(|sp| {
2000 Attributes::try_from(&src[sp.clone()]).expect("should be valid")
2001 }));
2002 let url = if let Some(block::Event {
2003 kind: block::EventKind::Inline,
2004 span,
2005 }) = blocks.next()
2006 {
2007 let start =
2008 src[span.clone()].trim_matches(|c: char| c.is_ascii_whitespace());
2009 if let Some(block::Event {
2010 kind: block::EventKind::Inline,
2011 span,
2012 }) = blocks.next()
2013 {
2014 let mut url = start.to_string();
2015 url.push_str(
2016 src[span.clone()].trim_matches(|c: char| c.is_ascii_whitespace()),
2017 );
2018 while let Some(block::Event {
2019 kind: block::EventKind::Inline,
2020 span,
2021 }) = blocks.next()
2022 {
2023 url.push_str(
2024 src[span.clone()]
2025 .trim_matches(|c: char| c.is_ascii_whitespace()),
2026 );
2027 }
2028 url.into() // owned
2029 } else {
2030 start.into() // borrowed
2031 }
2032 } else {
2033 "".into() // static
2034 };
2035 link_definitions.insert(label, (url, attrs));
2036 }
2037 block::EventKind::Enter(block::Node::Leaf(block::Leaf::Heading { .. })) => {
2038 // All headings ids have to be obtained initially, as references can appear
2039 // before the heading. Additionally, determining the id requires inline parsing
2040 // as formatting must be removed.
2041 //
2042 // We choose to parse all headers twice instead of caching them.
2043 let attrs = Attributes::from_iter(attr_prev.iter().flat_map(|sp| {
2044 Attributes::try_from(&src[sp.clone()]).expect("should be valid")
2045 }));
2046 let id_override = attrs.get_value("id").map(|s| s.to_string());
2047
2048 let mut id_auto = String::new();
2049 let mut text = String::new();
2050 let mut last_whitespace = true;
2051 inline_parser.reset();
2052 let mut last_end = 0;
2053 loop {
2054 let span_inline = blocks.next().and_then(|e| {
2055 if matches!(e.kind, block::EventKind::Inline) {
2056 last_end = e.span.end;
2057 Some(e.span.clone())
2058 } else {
2059 None
2060 }
2061 });
2062 inline_parser.feed_line(
2063 span_inline.as_ref().cloned().unwrap_or(last_end..last_end),
2064 span_inline.is_none(),
2065 );
2066 inline_parser.for_each(|ev| match ev.kind {
2067 inline::EventKind::Str => {
2068 text.push_str(&src[ev.span.clone()]);
2069 let mut chars = src[ev.span].chars().peekable();
2070 while let Some(c) = chars.next() {
2071 if c.is_ascii_whitespace() {
2072 while chars.peek().is_some_and(|c| c.is_ascii_whitespace())
2073 {
2074 chars.next();
2075 }
2076 if !last_whitespace {
2077 last_whitespace = true;
2078 id_auto.push('-');
2079 }
2080 } else if !c.is_ascii_punctuation() || matches!(c, '-' | '_') {
2081 id_auto.push(c);
2082 last_whitespace = false;
2083 }
2084 }
2085 }
2086 inline::EventKind::Atom(inline::Atom::Softbreak) => {
2087 text.push(' ');
2088 id_auto.push('-');
2089 }
2090 _ => {}
2091 });
2092 if span_inline.is_none() {
2093 break;
2094 }
2095 }
2096 id_auto.drain(id_auto.trim_end_matches('-').len()..);
2097
2098 // ensure id unique
2099 if used_ids.contains::<str>(&id_auto) || id_auto.is_empty() {
2100 if id_auto.is_empty() {
2101 id_auto.push('s');
2102 }
2103 let mut num = 1;
2104 id_auto.push('-');
2105 let i_num = id_auto.len();
2106 write!(id_auto, "{}", num).unwrap();
2107 while used_ids.contains::<str>(&id_auto) {
2108 num += 1;
2109 id_auto.drain(i_num..);
2110 write!(id_auto, "{}", num).unwrap();
2111 }
2112 }
2113
2114 used_ids.insert(id_auto.clone());
2115 headings.push(Heading {
2116 location: e.span.start as u32,
2117 id_auto,
2118 text,
2119 id_override,
2120 });
2121 }
2122 block::EventKind::Atom(block::Atom::Attributes) => {
2123 attr_prev.push(e.span.clone());
2124 }
2125 block::EventKind::Enter(..)
2126 | block::EventKind::Exit(block::Node::Container(block::Container::Section {
2127 ..
2128 })) => {}
2129 _ => {
2130 attr_prev = Vec::new();
2131 }
2132 }
2133 }
2134
2135 let mut headings_lex = (0..headings.len()).collect::<Vec<_>>();
2136 headings_lex.sort_by_key(|i| &headings[*i].text);
2137
2138 Self {
2139 link_definitions,
2140 headings,
2141 headings_lex,
2142 }
2143 }
2144
2145 fn heading_id(&self, i: usize) -> &str {
2146 let h = &self.headings[i];
2147 h.id_override.as_ref().unwrap_or(&h.id_auto)
2148 }
2149
2150 fn heading_id_by_location(&self, location: u32) -> Option<&str> {
2151 self.headings
2152 .binary_search_by_key(&location, |h| h.location)
2153 .ok()
2154 .map(|i| self.heading_id(i))
2155 }
2156
2157 fn heading_id_by_tag(&self, tag: &str) -> Option<&str> {
2158 self.headings_lex
2159 .binary_search_by_key(&tag, |i| &self.headings[*i].text)
2160 .ok()
2161 .map(|i| self.heading_id(self.headings_lex[i]))
2162 }
2163}
2164
2165impl<'s> Parser<'s> {
2166 #[must_use]
2167 pub fn new(src: &'s str) -> Self {
2168 let blocks = block::parse(src);
2169 let mut inline_parser = inline::Parser::new(src);
2170 let pre_pass = PrePass::new(src, blocks.iter(), &mut inline_parser);
2171
2172 Self {
2173 src,
2174 blocks: blocks.into_iter().peekable(),
2175 pre_pass,
2176 block_attributes: None,
2177 table_head_row: false,
2178 verbatim: false,
2179 inline_parser,
2180 }
2181 }
2182
2183 /// Turn the [`Parser`] into an iterator of tuples, each with an [`Event`] and a start/end byte
2184 /// offset for its corresponding input (as a [`std::ops::Range<usize>`]).
2185 ///
2186 /// Generally, the range of each event does not overlap with any other event and the ranges are
2187 /// in same order as the events are emitted, i.e. the start offset of an event must be greater
2188 /// or equal to the (exclusive) end offset of all events that were emitted before that event.
2189 /// However, there is an exception to this rule:
2190 ///
2191 /// - Caption events are emitted before the table rows while the input for the caption content
2192 /// is located after the table rows, causing the ranges to be out of order.
2193 ///
2194 /// Characters between events, that are not part of any event range, are typically whitespace
2195 /// but may also consist of unattached attributes or `>` characters from blockquotes.
2196 ///
2197 /// # Examples
2198 ///
2199 /// Start and end events of containers correspond only to the start and end markers for that
2200 /// container, not its inner content:
2201 ///
2202 /// ```
2203 /// # use jotup::*;
2204 /// # use jotup::Event::*;
2205 /// # use jotup::Container::*;
2206 /// let input = "> _hello_ [text](url)\n";
2207 /// assert!(matches!(
2208 /// Parser::new(input)
2209 /// .into_offset_iter()
2210 /// .map(|(e, r)| (&input[r], e))
2211 /// .collect::<Vec<_>>()
2212 /// .as_slice(),
2213 /// &[
2214 /// (">", Start(Blockquote, ..)),
2215 /// ("", Start(Paragraph, ..)),
2216 /// ("_", Start(Emphasis, ..)),
2217 /// ("hello", Str(..)),
2218 /// ("_", End),
2219 /// (" ", Str(..)),
2220 /// ("[", Start(Link { .. }, ..)),
2221 /// ("text", Str(..)),
2222 /// ("](url)", End),
2223 /// ("", End),
2224 /// ("", End),
2225 /// ],
2226 /// ));
2227 /// ```
2228 ///
2229 /// _Block_ attributes that belong to a container are included in the _start_ event. _Inline_
2230 /// attributes that belong to a container are included in the _end_ event:
2231 ///
2232 /// ```
2233 /// # use jotup::*;
2234 /// # use jotup::Event::*;
2235 /// # use jotup::Container::*;
2236 /// let input = "
2237 /// {.quote}
2238 /// > [Hello]{lang=en} world!";
2239 /// assert!(matches!(
2240 /// Parser::new(input)
2241 /// .into_offset_iter()
2242 /// .map(|(e, r)| (&input[r], e))
2243 /// .collect::<Vec<_>>()
2244 /// .as_slice(),
2245 /// &[
2246 /// ("\n", Blankline),
2247 /// ("{.quote}\n>", Start(Blockquote, ..)),
2248 /// ("", Start(Paragraph, ..)),
2249 /// ("[", Start(Span, ..)),
2250 /// ("Hello", Str(..)),
2251 /// ("]{lang=en}", End),
2252 /// (" world!", Str(..)),
2253 /// ("", End),
2254 /// ("", End),
2255 /// ],
2256 /// ));
2257 /// ```
2258 ///
2259 /// Inline events that span multiple lines may contain characters from outer block containers
2260 /// (e.g. `>` characters from blockquotes or whitespace from list items):
2261 ///
2262 /// ```
2263 /// # use jotup::*;
2264 /// # use jotup::Event::*;
2265 /// # use jotup::Container::*;
2266 /// let input = "
2267 /// > [txt](multi
2268 /// > line)";
2269 /// assert!(matches!(
2270 /// Parser::new(input)
2271 /// .into_offset_iter()
2272 /// .map(|(e, r)| (&input[r], e))
2273 /// .collect::<Vec<_>>()
2274 /// .as_slice(),
2275 /// &[
2276 /// ("\n", Blankline),
2277 /// (">", Start(Blockquote, ..)),
2278 /// ("", Start(Paragraph, ..)),
2279 /// ("[", Start(Link { .. }, ..)),
2280 /// ("txt", Str(..)),
2281 /// ("](multi\n> line)", End),
2282 /// ("", End),
2283 /// ("", End),
2284 /// ],
2285 /// ));
2286 /// ```
2287 pub fn into_offset_iter(self) -> OffsetIter<'s> {
2288 OffsetIter { parser: self }
2289 }
2290
2291 fn inline(&mut self) -> Option<(Event<'s>, std::ops::Range<usize>)> {
2292 let next = self.inline_parser.next()?;
2293
2294 let (inline, mut attributes) = match next {
2295 inline::Event {
2296 kind: inline::EventKind::Attributes { attrs, .. },
2297 ..
2298 } => (
2299 self.inline_parser.next(),
2300 self.inline_parser.store_attributes[attrs as usize].clone(),
2301 ),
2302 inline => (Some(inline), Attributes::new()),
2303 };
2304
2305 let event = inline.map(|inline| {
2306 let enter = matches!(inline.kind, inline::EventKind::Enter(_));
2307 let event = match inline.kind {
2308 inline::EventKind::Enter(c) | inline::EventKind::Exit(c) => {
2309 let t = match c {
2310 inline::Container::Span => Container::Span,
2311 inline::Container::Verbatim => Container::Verbatim,
2312 inline::Container::InlineMath => Container::Math { display: false },
2313 inline::Container::DisplayMath => Container::Math { display: true },
2314 inline::Container::RawFormat { format } => Container::RawInline {
2315 format: format.into(),
2316 },
2317 inline::Container::Subscript => Container::Subscript,
2318 inline::Container::Superscript => Container::Superscript,
2319 inline::Container::Insert => Container::Insert,
2320 inline::Container::Delete => Container::Delete,
2321 inline::Container::Emphasis => Container::Emphasis,
2322 inline::Container::Strong => Container::Strong,
2323 inline::Container::Mark => Container::Mark,
2324 inline::Container::InlineLink(url) => Container::Link(
2325 self.inline_parser.store_cowstrs[url as usize].clone(),
2326 LinkType::Span(SpanLinkType::Inline),
2327 ),
2328 inline::Container::InlineImage(url) => Container::Image(
2329 self.inline_parser.store_cowstrs[url as usize].clone(),
2330 SpanLinkType::Inline,
2331 ),
2332 inline::Container::ReferenceLink(tag)
2333 | inline::Container::ReferenceImage(tag) => {
2334 let tag = &self.inline_parser.store_cowstrs[tag as usize];
2335 let link_def =
2336 self.pre_pass.link_definitions.get(tag.as_ref()).cloned();
2337
2338 let (url_or_tag, ty) = if let Some((url, mut attrs_def)) = link_def {
2339 if enter {
2340 attrs_def.append(&mut attributes);
2341 attributes = attrs_def;
2342 }
2343 (url, SpanLinkType::Reference)
2344 } else {
2345 self.pre_pass.heading_id_by_tag(tag.as_ref()).map_or_else(
2346 || (tag.clone(), SpanLinkType::Unresolved),
2347 |id| (format!("#{}", id).into(), SpanLinkType::Reference),
2348 )
2349 };
2350
2351 if matches!(c, inline::Container::ReferenceLink(..)) {
2352 Container::Link(url_or_tag, LinkType::Span(ty))
2353 } else {
2354 Container::Image(url_or_tag, ty)
2355 }
2356 }
2357 inline::Container::Autolink(url) => {
2358 let ty = if url.contains('@') {
2359 LinkType::Email
2360 } else {
2361 LinkType::AutoLink
2362 };
2363 Container::Link(url.into(), ty)
2364 }
2365 };
2366 if enter {
2367 Event::Start(t, attributes.take())
2368 } else {
2369 Event::End
2370 }
2371 }
2372 inline::EventKind::Atom(a) => match a {
2373 inline::Atom::FootnoteReference { label } => {
2374 Event::FootnoteReference(label.into())
2375 }
2376 inline::Atom::Symbol(sym) => Event::Symbol(sym.into()),
2377 inline::Atom::Quote { ty, left } => match (ty, left) {
2378 (inline::QuoteType::Single, true) => Event::LeftSingleQuote,
2379 (inline::QuoteType::Single, false) => Event::RightSingleQuote,
2380 (inline::QuoteType::Double, true) => Event::LeftDoubleQuote,
2381 (inline::QuoteType::Double, false) => Event::RightDoubleQuote,
2382 },
2383 inline::Atom::Ellipsis => Event::Ellipsis,
2384 inline::Atom::EnDash => Event::EnDash,
2385 inline::Atom::EmDash => Event::EmDash,
2386 inline::Atom::Nbsp => Event::NonBreakingSpace,
2387 inline::Atom::Softbreak => Event::Softbreak,
2388 inline::Atom::Hardbreak => Event::Hardbreak,
2389 inline::Atom::Escape => Event::Escape,
2390 },
2391 inline::EventKind::Empty => {
2392 debug_assert!(!attributes.is_empty());
2393 Event::Attributes(attributes.take())
2394 }
2395 inline::EventKind::Str => Event::Str(self.src[inline.span.clone()].into()),
2396 inline::EventKind::Attributes { .. } | inline::EventKind::Placeholder => {
2397 panic!("{:?}", inline)
2398 }
2399 };
2400 (event, inline.span)
2401 });
2402
2403 debug_assert!(
2404 attributes.is_empty(),
2405 "unhandled attributes: {:?}",
2406 attributes
2407 );
2408
2409 event
2410 }
2411
2412 fn block(&mut self) -> Option<(Event<'s>, std::ops::Range<usize>)> {
2413 while let Some(ev) = self.blocks.peek() {
2414 let mut ev_span = ev.span.clone();
2415 let mut pop = true;
2416 let event = match ev.kind {
2417 block::EventKind::Atom(a) => match a {
2418 block::Atom::Blankline => {
2419 debug_assert_eq!(self.block_attributes, None);
2420 Event::Blankline
2421 }
2422 block::Atom::ThematicBreak => {
2423 let attrs = if let Some((attrs, span)) = self.block_attributes.take() {
2424 ev_span.start = span.start;
2425 attrs
2426 } else {
2427 Attributes::new()
2428 };
2429 Event::ThematicBreak(attrs)
2430 }
2431 block::Atom::Attributes => {
2432 let (mut attrs, mut span) = self
2433 .block_attributes
2434 .take()
2435 .unwrap_or_else(|| (Attributes::new(), ev_span.clone()));
2436 attrs
2437 .parse(&self.src[ev_span.clone()])
2438 .expect("should be valid");
2439 span.end = ev_span.end;
2440 self.blocks.next().unwrap();
2441 if matches!(
2442 self.blocks.peek().map(|e| &e.kind),
2443 Some(block::EventKind::Atom(block::Atom::Blankline))
2444 ) {
2445 return Some((Event::Attributes(attrs), span));
2446 }
2447 self.block_attributes = Some((attrs, span));
2448 continue;
2449 }
2450 },
2451 block::EventKind::Enter(c) | block::EventKind::Exit(c) => {
2452 let enter = matches!(ev.kind, block::EventKind::Enter(..));
2453 let cont = match c {
2454 block::Node::Leaf(l) => {
2455 self.inline_parser.reset();
2456 match l {
2457 block::Leaf::Paragraph => Container::Paragraph,
2458 block::Leaf::Heading {
2459 level,
2460 has_section,
2461 pos,
2462 } => Container::Heading {
2463 level,
2464 has_section,
2465 id: self
2466 .pre_pass
2467 .heading_id_by_location(pos)
2468 .unwrap_or_default()
2469 .to_string()
2470 .into(),
2471 },
2472 block::Leaf::DescriptionTerm => Container::DescriptionTerm,
2473 block::Leaf::CodeBlock { language } => {
2474 self.verbatim = enter;
2475 if let Some(format) = language.strip_prefix('=') {
2476 Container::RawBlock {
2477 format: format.into(),
2478 }
2479 } else {
2480 Container::CodeBlock {
2481 language: language.into(),
2482 }
2483 }
2484 }
2485 block::Leaf::TableCell(alignment) => Container::TableCell {
2486 alignment,
2487 head: self.table_head_row,
2488 },
2489 block::Leaf::Caption => Container::Caption,
2490 block::Leaf::LinkDefinition { label } => {
2491 self.verbatim = enter;
2492 Container::LinkDefinition {
2493 label: label.into(),
2494 }
2495 }
2496 }
2497 }
2498 block::Node::Container(c) => match c {
2499 block::Container::Blockquote => Container::Blockquote,
2500 block::Container::Div { class } => Container::Div {
2501 class: class.into(),
2502 },
2503 block::Container::Footnote { label } => Container::Footnote {
2504 label: label.into(),
2505 },
2506 block::Container::List { ty, tight } => {
2507 if matches!(ty, block::ListType::Description) {
2508 Container::DescriptionList
2509 } else {
2510 let kind = match ty {
2511 block::ListType::Unordered(c) => ListKind::Unordered(
2512 c.try_into().expect("should be bullet character"),
2513 ),
2514 block::ListType::Task(c) => ListKind::Task(
2515 c.try_into().expect("should be bullet character"),
2516 ),
2517 block::ListType::Ordered(
2518 block::ListNumber { numbering, value },
2519 style,
2520 ) => ListKind::Ordered {
2521 numbering,
2522 style,
2523 start: value,
2524 },
2525 block::ListType::Description => unreachable!(),
2526 };
2527 Container::List { kind, tight }
2528 }
2529 }
2530 block::Container::ListItem(kind) => match kind {
2531 block::ListItemKind::Task { checked } => {
2532 Container::TaskListItem { checked }
2533 }
2534 block::ListItemKind::Description => Container::DescriptionDetails,
2535 block::ListItemKind::List => Container::ListItem,
2536 },
2537 block::Container::Table => Container::Table,
2538 block::Container::TableRow { head } => {
2539 if enter {
2540 self.table_head_row = head;
2541 }
2542 Container::TableRow { head }
2543 }
2544 block::Container::Section { pos } => Container::Section {
2545 id: self
2546 .pre_pass
2547 .heading_id_by_location(pos)
2548 .unwrap_or_default()
2549 .to_string()
2550 .into(),
2551 },
2552 },
2553 };
2554 if enter {
2555 let attrs = if let Some((attrs, span)) = self.block_attributes.take() {
2556 ev_span.start = span.start;
2557 attrs
2558 } else {
2559 Attributes::new()
2560 };
2561 Event::Start(cont, attrs)
2562 } else if let Some((attrs, sp)) = self.block_attributes.take() {
2563 pop = false;
2564 ev_span = sp;
2565 Event::Attributes(attrs)
2566 } else {
2567 Event::End
2568 }
2569 }
2570 block::EventKind::Inline => {
2571 if self.verbatim {
2572 Event::Str(self.src[ev_span.clone()].into())
2573 } else {
2574 self.blocks.next().unwrap();
2575 self.inline_parser.feed_line(
2576 ev_span.clone(),
2577 !matches!(
2578 self.blocks.peek().map(|e| &e.kind),
2579 Some(block::EventKind::Inline),
2580 ),
2581 );
2582 return self.next_span();
2583 }
2584 }
2585 block::EventKind::Stale => {
2586 self.blocks.next().unwrap();
2587 continue;
2588 }
2589 };
2590 if pop {
2591 self.blocks.next().unwrap();
2592 }
2593 return Some((event, ev_span));
2594 }
2595 None
2596 }
2597
2598 fn next_span(&mut self) -> Option<(Event<'s>, std::ops::Range<usize>)> {
2599 self.inline().or_else(|| self.block()).or_else(|| {
2600 self.block_attributes
2601 .take()
2602 .map(|(attrs, span)| (Event::Attributes(attrs), span))
2603 })
2604 }
2605}
2606
2607impl<'s> Iterator for Parser<'s> {
2608 type Item = Event<'s>;
2609
2610 fn next(&mut self) -> Option<Self::Item> {
2611 self.next_span().map(|(e, _)| e)
2612 }
2613}
2614
2615/// An iterator that is identical to a [`Parser`], except that it also emits the location of each
2616/// event within the input.
2617///
2618/// See the documentation of [`Parser::into_offset_iter`] for more information.
2619pub struct OffsetIter<'s> {
2620 parser: Parser<'s>,
2621}
2622
2623impl<'s> Iterator for OffsetIter<'s> {
2624 type Item = (Event<'s>, std::ops::Range<usize>);
2625
2626 fn next(&mut self) -> Option<Self::Item> {
2627 self.parser.next_span()
2628 }
2629}
2630
2631#[cfg(test)]
2632mod test {
2633 use super::OrderedListNumbering::*;
2634
2635 #[test]
2636 fn numbering_alpha() {
2637 assert_eq!(AlphaLower.parse_number("a"), 1);
2638 assert_eq!(AlphaUpper.parse_number("B"), 2);
2639 assert_eq!(AlphaUpper.parse_number("Z"), 26);
2640 assert_eq!(AlphaLower.parse_number("aa"), 27);
2641 }
2642}