domtree/error.rs
1//! Non-fatal parse diagnostics.
2//!
3//! Parsing never fails: [`crate::parse`] always returns a best-effort tree.
4//! Anything the parser had to recover from (a stray end tag, an unterminated
5//! comment, an unknown entity, …) is recorded as a [`ParseError`] and exposed
6//! via [`crate::Dom::errors`]. Linters and validators can inspect them; the
7//! common "just give me the tree" path can ignore them entirely.
8
9use core::fmt;
10
11/// A single recoverable problem encountered while parsing, with the byte
12/// offset into the source where it occurred.
13#[derive(Clone, Debug, PartialEq, Eq)]
14pub struct ParseError {
15 /// Byte offset into the original source string.
16 pub position: usize,
17 /// What kind of problem this is.
18 pub kind: ErrorKind,
19}
20
21impl ParseError {
22 pub(crate) fn new(position: usize, kind: ErrorKind) -> Self {
23 ParseError { position, kind }
24 }
25
26 /// A short, human-readable description of the problem.
27 pub fn message(&self) -> &'static str {
28 self.kind.message()
29 }
30}
31
32/// The category of a [`ParseError`].
33///
34/// Marked `#[non_exhaustive]`: new recovery cases may be added without it being
35/// a breaking change, so always include a `_ =>` arm when matching.
36#[non_exhaustive]
37#[derive(Clone, Debug, PartialEq, Eq)]
38pub enum ErrorKind {
39 /// Input ended in the middle of a construct (tag, attribute, …).
40 UnexpectedEof,
41 /// A `<!-- comment` was never closed with `-->`.
42 UnterminatedComment,
43 /// A `<![CDATA[` section was never closed with `]]>`.
44 UnterminatedCdata,
45 /// A tag was opened with `<` but never closed with `>`.
46 UnterminatedTag,
47 /// An end tag (`</x>`) had no matching open element and was ignored.
48 StrayEndTag,
49 /// An end tag closed elements other than the current one (mis-nesting).
50 MismatchedEndTag,
51 /// An element was closed implicitly by another tag or end-of-input.
52 ImplicitlyClosed,
53 /// A `&entity;` reference was not recognised and was left verbatim.
54 UnknownEntity,
55 /// A `<! ... >` or `<? ... >` construct was treated as a bogus comment.
56 BogusComment,
57}
58
59impl ErrorKind {
60 pub(crate) fn message(&self) -> &'static str {
61 match self {
62 ErrorKind::UnexpectedEof => "unexpected end of input",
63 ErrorKind::UnterminatedComment => "unterminated comment",
64 ErrorKind::UnterminatedCdata => "unterminated CDATA section",
65 ErrorKind::UnterminatedTag => "unterminated tag",
66 ErrorKind::StrayEndTag => "stray end tag with no matching open element",
67 ErrorKind::MismatchedEndTag => "end tag closed mis-nested elements",
68 ErrorKind::ImplicitlyClosed => "element implicitly closed",
69 ErrorKind::UnknownEntity => "unknown character reference left verbatim",
70 ErrorKind::BogusComment => "bogus comment",
71 }
72 }
73}
74
75impl fmt::Display for ParseError {
76 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77 write!(f, "{} at byte {}", self.kind.message(), self.position)
78 }
79}
80
81#[cfg(feature = "std")]
82impl std::error::Error for ParseError {}