minify_html_onepass/unit/attr/
mod.rs

1use crate::err::ProcessingResult;
2use crate::proc::checkpoint::WriteCheckpoint;
3use crate::proc::range::ProcessorRange;
4use crate::proc::MatchAction::*;
5use crate::proc::MatchMode::*;
6use crate::proc::Processor;
7use crate::unit::attr::value::process_attr_value;
8use crate::unit::attr::value::skip_attr_value;
9use crate::unit::attr::value::DelimiterType;
10use crate::unit::attr::value::ProcessedAttrValue;
11use minify_html_common::gen::attrs::ATTRS;
12use minify_html_common::gen::codepoints::WHATWG_ATTR_NAME_CHAR;
13use minify_html_common::gen::codepoints::WHITESPACE;
14use minify_html_common::spec::tag::ns::Namespace;
15
16mod value;
17
18#[derive(Clone, Copy, Eq, PartialEq)]
19pub enum AttrType {
20  Quoted,
21  Unquoted,
22  NoValue,
23}
24
25pub struct ProcessedAttr {
26  pub name: ProcessorRange,
27  pub typ: AttrType,
28  pub value: Option<ProcessorRange>,
29}
30
31pub fn process_attr(
32  proc: &mut Processor,
33  ns: Namespace,
34  element: ProcessorRange,
35) -> ProcessingResult<ProcessedAttr> {
36  // It's possible to expect attribute name but not be called at an attribute, e.g. due to whitespace between name and
37  // value, which causes name to be considered boolean attribute and `=` to be start of new (invalid) attribute name.
38  let name = proc
39    .m(WhileInLookup(WHATWG_ATTR_NAME_CHAR), Keep)
40    .require("attribute name")?;
41  proc.make_lowercase(name);
42  let attr_cfg = ATTRS.get(ns, &proc[element], &proc[name]);
43  let is_boolean = attr_cfg.filter(|attr| attr.boolean).is_some();
44  let after_name = WriteCheckpoint::new(proc);
45
46  // TODO Use attr cfg: collapse, trim, case_sensitive.
47  let should_collapse_and_trim_value_ws =
48    attr_cfg.filter(|attr| attr.collapse && attr.trim).is_some();
49  proc.m(WhileInLookup(WHITESPACE), Discard);
50  let has_value = proc.m(IsChar(b'='), Keep).nonempty();
51
52  let (typ, value) = if !has_value {
53    (AttrType::NoValue, None)
54  } else {
55    proc.m(WhileInLookup(WHITESPACE), Discard);
56    if is_boolean {
57      skip_attr_value(proc)?;
58      // Discard `=`.
59      debug_assert_eq!(after_name.written_count(proc), 1);
60      after_name.erase_written(proc);
61      (AttrType::NoValue, None)
62    } else {
63      match process_attr_value(proc, should_collapse_and_trim_value_ws)? {
64        ProcessedAttrValue { value: None, .. } => {
65          // Value is empty, which is equivalent to no value, so discard `=`.
66          debug_assert_eq!(after_name.written_count(proc), 1);
67          after_name.erase_written(proc);
68          (AttrType::NoValue, None)
69        }
70        ProcessedAttrValue {
71          delimiter: DelimiterType::Unquoted,
72          value,
73        } => (AttrType::Unquoted, value),
74        ProcessedAttrValue {
75          delimiter: DelimiterType::Double,
76          value,
77        }
78        | ProcessedAttrValue {
79          delimiter: DelimiterType::Single,
80          value,
81        } => (AttrType::Quoted, value),
82      }
83    }
84  };
85
86  Ok(ProcessedAttr { name, typ, value })
87}