Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 47 additions & 9 deletions minify-html/src/parse/element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use minify_html_common::spec::tag::ns::Namespace;
use minify_html_common::spec::tag::void::VOID_TAGS;
use std::fmt::Debug;
use std::fmt::Formatter;
use std::io::Write;
use std::str::from_utf8;

fn parse_tag_name(code: &mut Code) -> Vec<u8> {
Expand Down Expand Up @@ -80,15 +81,52 @@ pub fn parse_tag(code: &mut Code) -> ParsedTag {
break;
};
let mut attr_name = Vec::new();
// An attribute name can start with `=`, but ends at the next whitespace, `=`, `/`, or `>`.
if let Some(c) = code.shift_if_next_not_in_lookup(WHITESPACE_OR_SLASH) {
attr_name.push(c);
};
attr_name.extend_from_slice(
code.slice_and_shift_while_not_in_lookup(WHITESPACE_OR_SLASH_OR_EQUALS_OR_RIGHT_CHEVRON),
);
debug_assert!(!attr_name.is_empty());
attr_name.make_ascii_lowercase();

// preserve template passthrough behavior in tags
let mut template_enclosures = Vec::new();

if code.opts.treat_brace_as_opaque {
template_enclosures.extend_from_slice(&[
("{{".as_bytes(), "}}".as_bytes()),
("{%".as_bytes(), "%}".as_bytes()),
("{#".as_bytes(), "#}".as_bytes()),
]);
}

if code.opts.treat_chevron_percent_as_opaque {
template_enclosures.push(("<%".as_bytes(), "%>".as_bytes()));
}

let mut template_attr_name = false;
for (opening, closing) in template_enclosures {
if code.shift_if_next_seq_case_insensitive(opening) {
let _ = attr_name.write(opening);
loop {
attr_name.extend_from_slice(code.slice_and_shift_while_not_seq_case_insensitive(closing));

match code.shift_if_next_not_in_lookup(WHITESPACE_OR_SLASH_OR_EQUALS_OR_RIGHT_CHEVRON) {
Some(c) => attr_name.push(c),
None => break,
}
}
template_attr_name = true;
break;
}
}

if !template_attr_name {
// An attribute name can start with `=`, but ends at the next whitespace, `=`, `/`, or `>`.
if let Some(c) = code.shift_if_next_not_in_lookup(WHITESPACE_OR_SLASH) {
attr_name.push(c);
}

attr_name.extend_from_slice(
code.slice_and_shift_while_not_in_lookup(WHITESPACE_OR_SLASH_OR_EQUALS_OR_RIGHT_CHEVRON),
);
debug_assert!(!attr_name.is_empty());
attr_name.make_ascii_lowercase();
}

// See comment for WHITESPACE_OR_SLASH in codepoints.ts for details of complex attr parsing.
code.shift_while_in_lookup(WHITESPACE);
let has_value = code.shift_if_next(b'=');
Expand Down
22 changes: 22 additions & 0 deletions minify-html/src/parse/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,28 @@ impl<'c> Code<'c> {
last
}

pub fn slice_and_shift_while_not_seq_case_insensitive(&mut self, seq: &[u8]) -> &[u8] {
let mut len = 0;
let mut internal_next = self.next;

loop {
match self.code.get(internal_next..internal_next + seq.len()) {
Some(n) if !n.eq_ignore_ascii_case(seq) => {
len += 1;
internal_next += 1;
}
None => {
// prevent out-of-range panic in slice_and_shift()
len = len.min(self.rem() - seq.len());

break;
}
_ => break,
}
}
self.slice_and_shift(len + seq.len())
}

pub fn rem(&self) -> usize {
self.code.len() - self.next
}
Expand Down