use crate::cfg::Cfg;
use crate::err::ProcessingResult;
use crate::proc::checkpoint::ReadCheckpoint;
use crate::proc::entity::maybe_normalise_entity;
use crate::proc::range::ProcessorRange;
use crate::proc::MatchAction::*;
use crate::proc::MatchMode::*;
use crate::proc::Processor;
use crate::unit::bang::process_bang;
use crate::unit::comment::process_comment;
use crate::unit::instruction::process_instruction;
use crate::unit::tag::process_tag;
use crate::unit::tag::MaybeClosingTag;
use minify_html_common::gen::codepoints::TAG_NAME_CHAR;
use minify_html_common::gen::codepoints::WHITESPACE;
use minify_html_common::spec::tag::ns::Namespace;
use minify_html_common::spec::tag::omission::can_omit_as_before;
use minify_html_common::spec::tag::omission::can_omit_as_last_node;
use minify_html_common::spec::tag::whitespace::get_whitespace_minification_for_tag;
use minify_html_common::spec::tag::whitespace::WhitespaceMinification;
#[derive(Copy, Clone, PartialEq, Eq)]
enum ContentType {
Comment,
Bang,
Instruction,
Tag,
Start,
End,
Text,
}
impl ContentType {
fn peek(proc: &mut Processor) -> ContentType {
match proc.peek(0) {
None => ContentType::End,
Some(b'<') => match proc.peek(1) {
Some(b'/') => ContentType::End,
Some(b'?') => ContentType::Instruction,
Some(b'!') => match proc.peek_many(2, 2) {
Some(b"--") => ContentType::Comment,
_ => ContentType::Bang,
},
Some(c) if TAG_NAME_CHAR[c] => ContentType::Tag,
_ => ContentType::Text,
},
Some(_) => ContentType::Text,
}
}
}
pub struct ProcessedContent {
pub closing_tag_omitted: bool,
}
pub fn process_content(
proc: &mut Processor,
cfg: &Cfg,
ns: Namespace,
parent: Option<ProcessorRange>,
descendant_of_pre: bool,
) -> ProcessingResult<ProcessedContent> {
let &WhitespaceMinification {
collapse,
destroy_whole,
trim,
} = get_whitespace_minification_for_tag(ns, proc.get_or_empty(parent), descendant_of_pre);
let handle_ws = collapse || destroy_whole || trim;
let mut last_written = ContentType::Start;
let mut ws_skipped = false;
let mut prev_sibling_closing_tag = MaybeClosingTag::none();
loop {
let next_content_type = ContentType::peek(proc);
match next_content_type {
ContentType::Comment => {
process_comment(proc)?;
continue;
}
ContentType::Bang => {
process_bang(proc)?;
continue;
}
ContentType::Instruction => {
process_instruction(proc)?;
continue;
}
_ => {}
};
maybe_normalise_entity(proc, false);
if handle_ws {
if next_content_type == ContentType::Text
&& proc.m(IsInLookup(WHITESPACE), Discard).nonempty()
{
ws_skipped = true;
continue;
};
#[allow(clippy::if_same_then_else)] if ws_skipped {
if destroy_whole
&& last_written == ContentType::Tag
&& next_content_type == ContentType::Tag
{
} else if trim
&& (last_written == ContentType::Start || next_content_type == ContentType::End)
{
} else if collapse {
prev_sibling_closing_tag.write_if_exists(proc);
proc.write(b' ');
last_written = ContentType::Text;
} else {
unreachable!();
};
ws_skipped = false;
};
};
match next_content_type {
ContentType::Tag => {
let tag_checkpoint = ReadCheckpoint::new(proc);
proc.skip_expect();
let tag_name = proc
.m(WhileInLookup(TAG_NAME_CHAR), Discard)
.require("tag name")?;
proc.make_lowercase(tag_name);
if can_omit_as_before(proc.get_or_empty(parent), &proc[tag_name]) {
prev_sibling_closing_tag.write_if_exists(proc);
tag_checkpoint.restore(proc);
return Ok(ProcessedContent {
closing_tag_omitted: true,
});
};
let new_closing_tag = process_tag(
proc,
cfg,
ns,
parent,
descendant_of_pre
|| ns == Namespace::Html && parent.filter(|p| &proc[*p] == b"pre").is_some(),
prev_sibling_closing_tag,
tag_name,
)?;
prev_sibling_closing_tag.replace(new_closing_tag);
}
ContentType::End => {
if prev_sibling_closing_tag
.exists_and(|prev_tag| !can_omit_as_last_node(proc.get_or_empty(parent), &proc[prev_tag]))
{
prev_sibling_closing_tag.write(proc);
};
break;
}
ContentType::Text => {
if prev_sibling_closing_tag.exists() {
prev_sibling_closing_tag.write(proc);
};
let c = proc.peek(0).unwrap();
if proc.last_is(b'<') && (TAG_NAME_CHAR[c] || c == b'?' || c == b'!' || c == b'/') {
proc.undo_write(1);
proc.write_slice(b"<");
};
proc.accept_expect();
}
_ => unreachable!(),
};
last_written = next_content_type;
}
Ok(ProcessedContent {
closing_tag_omitted: false,
})
}