///|
/// A markup syntax tree: HTML, SVG and MathML.
///
/// The shape follows biome for tolerance -- every enum a parse can fail inside
/// of has a `Bogus` member, so a parse always yields a tree rather than an
/// error, and the tree always covers the whole file -- and html5ever for
/// vocabulary, in that an element's name is a namespace and a local name rather
/// than a string.
///
/// It is a MARKUP tree, not a document tree, and that is the load-bearing
/// decision in the whole design. This is what was WRITTEN: `a
b` is two
/// start tags and no end tags. A conforming WHATWG parse produces something
/// else -- two sibling paragraphs inside an invented `
`, with
/// misnesting repaired and text foster-parented out of tables -- and that tree
/// is not reversible. A formatter built on it would hand back a file that
/// renders the same and reads differently, which is the one thing a formatter
/// may never do.
///
/// It is also SEMANTIC rather than lossless: parse then write gives back markup
/// that means the same thing, not the same bytes. Four things are preserved
/// anyway, because losing them is a semantic loss rather than a formatting one:
///
/// * a character reference's spelling, in `Text::raw` and `AttrValue`, so
/// ` ` does not become an invisible byte;
/// * attribute order, so reindenting a file does not rewrite every line;
/// * `disabled` against `disabled=""`, which are the same to a browser and
/// different to a reader;
/// * how an element was closed, in `Closing`, which is what distinguishes
/// `
` from `
` and a written `` from one the table supplied.
///
/// Nothing here depends on `error-report`: a `Bogus` carries an
/// `@kind.ErrorKind`, which is a plain closed enum, so building a tree and
/// printing it links neither the diagnostic library nor the parser.
///|
/// What could not be parsed, kept so that the tree covers the whole source.
///
/// `text` is the exact source the node replaced. Keeping it is what lets the
/// printer echo an unparsable region verbatim instead of deleting it -- a page
/// with one templating directive the parser has never seen should come back
/// with that directive still in it.
pub(all) struct Bogus {
kind : @kind.ErrorKind
span : @span.Span
text : String
} derive(Eq, Debug)
///|
pub fn Bogus::new(
kind : @kind.ErrorKind,
span : @span.Span,
text? : String = "",
) -> Bogus {
{ kind, span, text, }
}
// ---------------------------------------------------------------- documents
///|
/// A document, or a fragment: the same type.
///
/// One type rather than two, because a fragment is a document whose children
/// happen not to include a doctype, and every operation either would support is
/// the same operation. html5ever needs the distinction because its tree builder
/// runs different insertion modes for the two; a markup parser has no insertion
/// modes to run.
pub(all) struct Document {
children : Array[Node]
span : @span.Span
} derive(Eq, Debug)
///|
pub(all) enum Node {
Element(Element)
Text(Text)
/// The content of a `script` or `style`: never escaped, never markup.
RawText(RawText)
Comment(Comment)
Doctype(Doctype)
/// A ``, which is legal in foreign content and a bogus
/// comment in HTML. Kept as itself so the printer can put back whichever it
/// was looking at.
Cdata(String, @span.Span)
Bogus(Bogus)
} derive(Eq, Debug)
// ---------------------------------------------------------------- elements
///|
pub(all) struct Element {
name : TagName
attrs : Array[Attribute]
children : Array[Node]
closing : Closing
span : @span.Span
/// Where the name sits inside the start tag, for a diagnostic that wants to
/// point at `div` rather than at ``.
name_span : @span.Span
} derive(Eq, Debug)
///|
/// How an element ended.
///
/// You cannot print an element back without knowing this, and no other field
/// can be made to say it. It is the same argument `LayerRule::body` makes on
/// the CSS side: the alternatives are different constructs, not one construct
/// with an optional part.
pub(all) enum Closing {
/// `...`.
Explicit
/// ` `. Load-bearing in foreign content, ignored in HTML, and a
/// house style there that a formatter has no business changing.
SelfClosing
/// `
`: the element cannot have an end tag at all.
Void
/// `a b`: closed by the table in `names`, not by the source.
Implied
/// Ran to the end of the input. A diagnostic, not a failure.
Unclosed
} derive(Eq, Debug)
///|
/// An element's name: a namespace and a local name.
///
/// The name is NORMALISED -- ASCII-lowercased in HTML, case-adjusted by table
/// in SVG and MathML -- so that `` and `` are one node, and
/// `` and `` are the one that exists. Case is
/// not a semantic difference in either vocabulary, which is why this normalises
/// where `Text::raw` preserves.
pub(all) struct TagName {
ns : Namespace
name : String
} derive(Eq, Debug)
///|
pub fn TagName::html(name : String) -> TagName {
{ ns: Html, name, }
}
///|
/// Which vocabulary a name is drawn from.
///
/// Not decoration: it decides four things that are otherwise ambiguous in the
/// same bytes -- whether `/>` closes an element or is ignored, whether an
/// attribute name is lowercased or table-adjusted, whether `` holds text or elements.
pub(all) enum Namespace {
Html
Svg
MathMl
/// Reached only through an `xmlns` this library does not interpret. Kept so
/// that a name can say it is neither of the three rather than pretending.
Other(String)
} derive(Eq, Debug)
// -------------------------------------------------------------- attributes
///|
pub(all) struct Attribute {
name : AttrName
value : AttrValue
span : @span.Span
} derive(Eq, Debug)
///|
/// `xlink:href` is `prefix = Some("xlink")`. The prefix is kept unresolved:
/// resolving it needs the `xmlns` declarations in scope, which is a document
/// question and not a markup one.
pub(all) struct AttrName {
prefix : String?
name : String
} derive(Eq, Debug)
///|
pub fn AttrName::plain(name : String) -> AttrName {
{ prefix: None, name, }
}
///|
/// The written form of an attribute name, prefix and all.
pub fn AttrName::text(self : AttrName) -> String {
match self.prefix {
Some(p) => p + ":" + self.name
None => self.name
}
}
///|
/// `Empty` is `disabled`; `Value("", ..)` is `disabled=""`.
///
/// The two are identical to a browser and different to a person reading a
/// diff, so the tree keeps the distinction and the printer decides what to do
/// with it. Quoting style is deliberately NOT kept: `Pretty` always
/// double-quotes and `Minified` drops the quotes where that is legal, the same
/// way the CSS printer normalises `'x'` to `"x"`.
pub(all) enum AttrValue {
Empty
/// The decoded value, and the source spelling when decoding was not the
/// identity -- `&` in a query string, say. `None` when it was.
Value(String, String?)
} derive(Eq, Debug)
///|
/// The decoded text of a value, with `Empty` reading as `""`.
pub fn AttrValue::text(self : AttrValue) -> String {
match self {
Empty => ""
Value(v, _) => v
}
}
// ------------------------------------------------------------------- leaves
///|
/// A run of character data, decoded.
///
/// `raw` is the source spelling, kept when decoding was not the identity. It
/// exists for the reason `Number::repr` exists in the CSS tree: a formatter
/// asked to reindent a file must not also rewrite every entity in it, and
/// turning ` ` into an invisible byte is a change a reviewer cannot see
/// and a reader cannot undo.
pub(all) struct Text {
text : String
raw : String?
span : @span.Span
} derive(Eq, Debug)
///|
pub fn Text::new(
text : String,
raw? : String? = None,
span? : @span.Span = @span.nowhere,
) -> Text {
{ text, raw, span, }
}
///|
/// The body of a `script` or a `style`.
///
/// A separate node from `Text` because it is not text: no reference resolves in
/// it, nothing in it is escaped on the way out, and the printer must refuse to
/// emit a body containing the element's own end tag rather than escaping it,
/// because there is no escape.
pub(all) struct RawText {
text : String
span : @span.Span
} derive(Eq, Debug)
///|
/// A ``. `text` is the body, without the delimiters.
///
/// Comments are first-class nodes rather than trivia attached to a neighbour.
/// Conditional comments, `` and every framework that hides a
/// directive in one depend on it, and the CSS module's decision to drop them
/// through the notation is the one lossy spot this module does not repeat.
pub(all) struct Comment {
text : String
span : @span.Span
} derive(Eq, Debug)
///|
/// A ``, or a legacy one with its identifiers.
pub(all) struct Doctype {
name : String
public_id : String?
system_id : String?
span : @span.Span
} derive(Eq, Debug)
// ---------------------------------------------------------------- accessors
///|
/// The span of any node, for a caller assembling a parent's.
pub fn Node::span(self : Node) -> @span.Span {
match self {
Element(e) => e.span
Text(t) => t.span
RawText(r) => r.span
Comment(c) => c.span
Doctype(d) => d.span
Cdata(_, s) => s
Bogus(b) => b.span
}
}
///|
/// The children of a node, empty for everything that cannot have any.
///
/// A total function rather than an option, because every caller that walks a
/// tree wants to recurse without asking first, and "a text node has no
/// children" is not a case worth making them handle.
pub fn Node::children(self : Node) -> Array[Node] {
match self {
Element(e) => e.children
_ => []
}
}