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