165 lines
5.3 KiB
Rust
165 lines
5.3 KiB
Rust
use phf::{Map, phf_map, phf_set, Set};
|
|
|
|
// Rules sourced from https://html.spec.whatwg.org/multipage/syntax.html#syntax-tag-omission.
|
|
// TODO html, head, body
|
|
// TODO Opening tags
|
|
|
|
pub enum ClosingTagOmissionRuleIfLast {
|
|
// Closing tag can always be omitted if it's the last node of its parent's children.
|
|
Always,
|
|
// Closing tag can never be omitted if it's the last node of its parent's children.
|
|
Never,
|
|
// Closing tag can be omitted if it's the last node of its parent's children and the parent tag name is not one of these.
|
|
ParentIsNot(Set<&'static [u8]>),
|
|
}
|
|
|
|
pub struct ClosingTagOmissionRule {
|
|
// Closing tag can be omitted if immediately followed by an element node with one of these tag names.
|
|
followed_by: Set<&'static [u8]>,
|
|
// Closing tag can be omitted if it's the last node of its parent's children.
|
|
is_last: ClosingTagOmissionRuleIfLast,
|
|
}
|
|
|
|
impl ClosingTagOmissionRule {
|
|
pub fn can_omit_as_last_node(&self, parent: Option<&[u8]>) -> bool {
|
|
match &self.is_last {
|
|
ClosingTagOmissionRuleIfLast::Always => true,
|
|
ClosingTagOmissionRuleIfLast::Never => false,
|
|
ClosingTagOmissionRuleIfLast::ParentIsNot(parents) => match parent {
|
|
Some(tag) => !parents.contains(tag),
|
|
None => true,
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn can_omit_as_before(&self, after: &[u8]) -> bool {
|
|
self.followed_by.contains(after)
|
|
}
|
|
}
|
|
|
|
static LI_CLOSING_TAG_OMISSION_RULE: &ClosingTagOmissionRule = &ClosingTagOmissionRule {
|
|
followed_by: phf_set!(b"li"),
|
|
is_last: ClosingTagOmissionRuleIfLast::Always,
|
|
};
|
|
|
|
static DT_CLOSING_TAG_OMISSION_RULE: &ClosingTagOmissionRule = &ClosingTagOmissionRule {
|
|
followed_by: phf_set!(b"dt", b"dd"),
|
|
is_last: ClosingTagOmissionRuleIfLast::Never,
|
|
};
|
|
|
|
static DD_CLOSING_TAG_OMISSION_RULE: &ClosingTagOmissionRule = &ClosingTagOmissionRule {
|
|
followed_by: phf_set!(b"dd", b"dt"),
|
|
is_last: ClosingTagOmissionRuleIfLast::Always,
|
|
};
|
|
|
|
static P_CLOSING_TAG_OMISSION_RULE: &ClosingTagOmissionRule = &ClosingTagOmissionRule {
|
|
followed_by: phf_set!(
|
|
b"address",
|
|
b"article",
|
|
b"aside",
|
|
b"blockquote",
|
|
b"details",
|
|
b"div",
|
|
b"dl",
|
|
b"fieldset",
|
|
b"figcaption",
|
|
b"figure",
|
|
b"footer",
|
|
b"form",
|
|
b"h1",
|
|
b"h2",
|
|
b"h3",
|
|
b"h4",
|
|
b"h5",
|
|
b"h6",
|
|
b"header",
|
|
b"hgroup",
|
|
b"hr",
|
|
b"main",
|
|
b"menu",
|
|
b"nav",
|
|
b"ol",
|
|
b"p",
|
|
b"pre",
|
|
b"section",
|
|
b"table",
|
|
b"ul",
|
|
),
|
|
is_last: ClosingTagOmissionRuleIfLast::ParentIsNot(phf_set!(
|
|
b"a",
|
|
b"audio",
|
|
b"del",
|
|
b"ins",
|
|
b"map",
|
|
b"noscript",
|
|
b"video",
|
|
)),
|
|
};
|
|
|
|
static RT_CLOSING_TAG_OMISSION_RULE: &ClosingTagOmissionRule = &ClosingTagOmissionRule {
|
|
followed_by: phf_set!(b"rt", b"rp"),
|
|
is_last: ClosingTagOmissionRuleIfLast::Always,
|
|
};
|
|
|
|
static RP_CLOSING_TAG_OMISSION_RULE: &ClosingTagOmissionRule = &ClosingTagOmissionRule {
|
|
followed_by: phf_set!(b"rt", b"rp"),
|
|
is_last: ClosingTagOmissionRuleIfLast::Always,
|
|
};
|
|
|
|
static OPTGROUP_CLOSING_TAG_OMISSION_RULE: &ClosingTagOmissionRule = &ClosingTagOmissionRule {
|
|
followed_by: phf_set!(b"optgroup"),
|
|
is_last: ClosingTagOmissionRuleIfLast::Always,
|
|
};
|
|
|
|
static OPTION_CLOSING_TAG_OMISSION_RULE: &ClosingTagOmissionRule = &ClosingTagOmissionRule {
|
|
followed_by: phf_set!(b"option", b"optgroup"),
|
|
is_last: ClosingTagOmissionRuleIfLast::Always,
|
|
};
|
|
|
|
static THEAD_CLOSING_TAG_OMISSION_RULE: &ClosingTagOmissionRule = &ClosingTagOmissionRule {
|
|
followed_by: phf_set!(b"tbody", b"tfoot"),
|
|
is_last: ClosingTagOmissionRuleIfLast::Never,
|
|
};
|
|
|
|
static TBODY_CLOSING_TAG_OMISSION_RULE: &ClosingTagOmissionRule = &ClosingTagOmissionRule {
|
|
followed_by: phf_set!(b"tbody", b"tfoot"),
|
|
is_last: ClosingTagOmissionRuleIfLast::Always,
|
|
};
|
|
|
|
static TFOOT_CLOSING_TAG_OMISSION_RULE: &ClosingTagOmissionRule = &ClosingTagOmissionRule {
|
|
followed_by: phf_set!(),
|
|
is_last: ClosingTagOmissionRuleIfLast::Always,
|
|
};
|
|
|
|
static TR_CLOSING_TAG_OMISSION_RULE: &ClosingTagOmissionRule = &ClosingTagOmissionRule {
|
|
followed_by: phf_set!(b"tr"),
|
|
is_last: ClosingTagOmissionRuleIfLast::Always,
|
|
};
|
|
|
|
static TD_CLOSING_TAG_OMISSION_RULE: &ClosingTagOmissionRule = &ClosingTagOmissionRule {
|
|
followed_by: phf_set!(b"td", b"th"),
|
|
is_last: ClosingTagOmissionRuleIfLast::Always,
|
|
};
|
|
|
|
static TH_CLOSING_TAG_OMISSION_RULE: &ClosingTagOmissionRule = &ClosingTagOmissionRule {
|
|
followed_by: phf_set!(b"td", b"th"),
|
|
is_last: ClosingTagOmissionRuleIfLast::Always,
|
|
};
|
|
|
|
pub static CLOSING_TAG_OMISSION_RULES: Map<&'static [u8], &ClosingTagOmissionRule> = phf_map! {
|
|
b"li" => LI_CLOSING_TAG_OMISSION_RULE,
|
|
b"dt" => DT_CLOSING_TAG_OMISSION_RULE,
|
|
b"dd" => DD_CLOSING_TAG_OMISSION_RULE,
|
|
b"p" => P_CLOSING_TAG_OMISSION_RULE,
|
|
b"rt" => RT_CLOSING_TAG_OMISSION_RULE,
|
|
b"rp" => RP_CLOSING_TAG_OMISSION_RULE,
|
|
b"optgroup" => OPTGROUP_CLOSING_TAG_OMISSION_RULE,
|
|
b"option" => OPTION_CLOSING_TAG_OMISSION_RULE,
|
|
b"thead" => THEAD_CLOSING_TAG_OMISSION_RULE,
|
|
b"tbody" => TBODY_CLOSING_TAG_OMISSION_RULE,
|
|
b"tfoot" => TFOOT_CLOSING_TAG_OMISSION_RULE,
|
|
b"tr" => TR_CLOSING_TAG_OMISSION_RULE,
|
|
b"td" => TD_CLOSING_TAG_OMISSION_RULE,
|
|
b"th" => TH_CLOSING_TAG_OMISSION_RULE,
|
|
};
|