1use std::borrow::Cow;
19use std::sync::Arc;
20
21#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
27pub struct Span {
28 pub start: u64,
30 pub end: u64,
31 pub line: Option<u32>,
33 pub page: Option<u32>,
35}
36
37impl Span {
38 pub const fn empty() -> Self {
39 Span {
40 start: 0,
41 end: 0,
42 line: None,
43 page: None,
44 }
45 }
46
47 pub const fn bytes(start: u64, end: u64) -> Self {
48 Span {
49 start,
50 end,
51 line: None,
52 page: None,
53 }
54 }
55
56 pub const fn at_line(start: u64, end: u64, line: u32) -> Self {
57 Span {
58 start,
59 end,
60 line: Some(line),
61 page: None,
62 }
63 }
64}
65
66#[derive(Clone, Debug, PartialEq)]
68pub struct Spanned<T> {
69 pub node: T,
70 pub span: Span,
71}
72
73impl<T> Spanned<T> {
74 pub const fn new(node: T, span: Span) -> Self {
75 Spanned { node, span }
76 }
77
78 pub const fn bare(node: T) -> Self {
80 Spanned {
81 node,
82 span: Span::empty(),
83 }
84 }
85}
86
87pub type Events<'a> = Box<dyn Iterator<Item = crate::Result<Spanned<Event<'a>>>> + 'a>;
91
92#[derive(Clone, Debug, PartialEq)]
93pub enum Event<'a> {
94 Start(Tag<'a>),
96 End(TagKind),
98
99 Text(Cow<'a, str>),
101 Code(Cow<'a, str>),
103 Image {
104 source: ImageSource<'a>,
105 alt: Cow<'a, str>,
106 dims: Option<(u32, u32)>,
107 },
108 Math {
109 inline: bool,
110 tex: Cow<'a, str>,
111 },
112 FootnoteRef(Cow<'a, str>),
113 Break(BreakKind),
114 Rule,
115 PageBreak,
116 TaskMarker(bool),
118
119 Diagnostic(Diagnostic),
122}
123
124#[derive(Clone, Debug, PartialEq)]
125pub enum Tag<'a> {
126 Document(Box<Metadata<'a>>),
129 Section {
130 level: u8,
131 },
132 Heading {
133 level: u8,
134 id: Option<Cow<'a, str>>,
135 },
136 Paragraph,
137 Preformatted,
141 List {
142 ordered: bool,
143 start: u64,
144 tight: bool,
145 },
146 ListItem {
147 marker: Marker,
148 },
149 Table {
150 align: Vec<Align>,
151 },
152 TableHead,
153 TableRow,
154 TableCell {
155 colspan: u16,
156 rowspan: u16,
157 },
158 CodeBlock {
159 lang: Option<Cow<'a, str>>,
160 filename: Option<Cow<'a, str>>,
161 },
162 BlockQuote {
163 attribution: Option<Cow<'a, str>>,
164 },
165 Admonition {
166 kind: AdmonitionKind,
167 },
168 Figure {
169 caption: Option<Cow<'a, str>>,
170 },
171 Footnote {
172 id: Cow<'a, str>,
173 },
174 DefinitionList,
175 DefinitionTerm,
176 DefinitionDetail,
177
178 Emphasis,
180 Strong,
181 Strikethrough,
182 Underline,
183 Highlight,
184 SmallCaps,
185 Sub,
186 Super,
187 Link {
188 href: Cow<'a, str>,
189 title: Option<Cow<'a, str>>,
190 },
191}
192
193#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
195pub enum TagKind {
196 Document,
197 Section,
198 Heading,
199 Paragraph,
200 Preformatted,
201 List,
202 ListItem,
203 Table,
204 TableHead,
205 TableRow,
206 TableCell,
207 CodeBlock,
208 BlockQuote,
209 Admonition,
210 Figure,
211 Footnote,
212 DefinitionList,
213 DefinitionTerm,
214 DefinitionDetail,
215 Emphasis,
216 Strong,
217 Strikethrough,
218 Underline,
219 Highlight,
220 SmallCaps,
221 Sub,
222 Super,
223 Link,
224}
225
226impl Tag<'_> {
227 pub fn kind(&self) -> TagKind {
228 match self {
229 Tag::Document(_) => TagKind::Document,
230 Tag::Section { .. } => TagKind::Section,
231 Tag::Heading { .. } => TagKind::Heading,
232 Tag::Paragraph => TagKind::Paragraph,
233 Tag::Preformatted => TagKind::Preformatted,
234 Tag::List { .. } => TagKind::List,
235 Tag::ListItem { .. } => TagKind::ListItem,
236 Tag::Table { .. } => TagKind::Table,
237 Tag::TableHead => TagKind::TableHead,
238 Tag::TableRow => TagKind::TableRow,
239 Tag::TableCell { .. } => TagKind::TableCell,
240 Tag::CodeBlock { .. } => TagKind::CodeBlock,
241 Tag::BlockQuote { .. } => TagKind::BlockQuote,
242 Tag::Admonition { .. } => TagKind::Admonition,
243 Tag::Figure { .. } => TagKind::Figure,
244 Tag::Footnote { .. } => TagKind::Footnote,
245 Tag::DefinitionList => TagKind::DefinitionList,
246 Tag::DefinitionTerm => TagKind::DefinitionTerm,
247 Tag::DefinitionDetail => TagKind::DefinitionDetail,
248 Tag::Emphasis => TagKind::Emphasis,
249 Tag::Strong => TagKind::Strong,
250 Tag::Strikethrough => TagKind::Strikethrough,
251 Tag::Underline => TagKind::Underline,
252 Tag::Highlight => TagKind::Highlight,
253 Tag::SmallCaps => TagKind::SmallCaps,
254 Tag::Sub => TagKind::Sub,
255 Tag::Super => TagKind::Super,
256 Tag::Link { .. } => TagKind::Link,
257 }
258 }
259}
260
261impl TagKind {
262 pub fn is_block(self) -> bool {
264 !matches!(
265 self,
266 TagKind::Emphasis
267 | TagKind::Strong
268 | TagKind::Strikethrough
269 | TagKind::Underline
270 | TagKind::Highlight
271 | TagKind::SmallCaps
272 | TagKind::Sub
273 | TagKind::Super
274 | TagKind::Link
275 )
276 }
277}
278
279#[derive(Clone, Copy, Debug, PartialEq, Eq)]
280pub enum BreakKind {
281 Soft,
283 Hard,
285}
286
287#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
288pub enum Align {
289 #[default]
290 None,
291 Left,
292 Center,
293 Right,
294}
295
296#[derive(Clone, Copy, Debug, PartialEq, Eq)]
297pub enum AdmonitionKind {
298 Note,
299 Tip,
300 Important,
301 Warning,
302 Caution,
303}
304
305#[derive(Clone, Copy, Debug, PartialEq, Eq)]
307pub enum Marker {
308 Bullet {
309 depth: u8,
311 },
312 Ordered {
313 number: u64,
314 },
315}
316
317#[derive(Clone)]
323pub enum ImageSource<'a> {
324 Path(Cow<'a, str>),
325 Bytes(Arc<[u8]>),
326 Entry {
328 container: Cow<'a, str>,
329 name: Cow<'a, str>,
330 },
331}
332
333impl std::fmt::Debug for ImageSource<'_> {
334 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
335 match self {
336 ImageSource::Path(p) => write!(f, "Path({p:?})"),
337 ImageSource::Bytes(b) => write!(f, "Bytes({} bytes)", b.len()),
338 ImageSource::Entry { container, name } => {
339 write!(f, "Entry({container:?}, {name:?})")
340 }
341 }
342 }
343}
344
345impl PartialEq for ImageSource<'_> {
346 fn eq(&self, other: &Self) -> bool {
347 match (self, other) {
348 (ImageSource::Path(a), ImageSource::Path(b)) => a == b,
349 (ImageSource::Bytes(a), ImageSource::Bytes(b)) => a == b,
350 (
351 ImageSource::Entry {
352 container: c1,
353 name: n1,
354 },
355 ImageSource::Entry {
356 container: c2,
357 name: n2,
358 },
359 ) => c1 == c2 && n1 == n2,
360 _ => false,
361 }
362 }
363}
364
365#[derive(Clone, Debug, Default, PartialEq)]
366pub struct Metadata<'a> {
367 pub title: Option<Cow<'a, str>>,
368 pub authors: Vec<Cow<'a, str>>,
369 pub date: Option<Cow<'a, str>>,
370 pub language: Option<Cow<'a, str>>,
371 pub page_count: Option<u32>,
372 pub word_count: Option<u64>,
373 pub source_format: Option<crate::FormatId>,
374 pub encoding: Option<Cow<'a, str>>,
375 pub custom: Vec<(Cow<'a, str>, Cow<'a, str>)>,
377}
378
379#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
380pub enum Severity {
381 Info,
382 Warning,
383 Error,
384}
385
386#[derive(Clone, Debug, PartialEq)]
390pub struct Diagnostic {
391 pub severity: Severity,
392 pub message: String,
393}
394
395impl Diagnostic {
396 pub fn warning(message: impl Into<String>) -> Self {
397 Diagnostic {
398 severity: Severity::Warning,
399 message: message.into(),
400 }
401 }
402
403 pub fn error(message: impl Into<String>) -> Self {
404 Diagnostic {
405 severity: Severity::Error,
406 message: message.into(),
407 }
408 }
409}
410
411#[cfg(test)]
412mod tests {
413 use super::*;
414
415 #[test]
416 fn kind_round_trips_for_every_tag() {
417 let tags = [
419 Tag::Paragraph,
420 Tag::Preformatted,
421 Tag::Emphasis,
422 Tag::Strong,
423 Tag::Heading { level: 1, id: None },
424 Tag::List {
425 ordered: false,
426 start: 1,
427 tight: true,
428 },
429 Tag::Link {
430 href: "x".into(),
431 title: None,
432 },
433 ];
434 for t in tags {
435 let k = t.kind();
436 assert_eq!(k, t.kind(), "kind() must be stable");
437 }
438 }
439
440 #[test]
441 fn inline_is_not_a_block() {
442 assert!(!TagKind::Emphasis.is_block());
443 assert!(!TagKind::Link.is_block());
444 assert!(TagKind::Paragraph.is_block());
445 assert!(TagKind::Table.is_block());
446 }
447}