use super::*;
pub(super) static HTML_BLOCK_TYPE6_TAGS: &[&str] = &[
"address",
"article",
"aside",
"base",
"basefont",
"blockquote",
"body",
"caption",
"center",
"col",
"colgroup",
"dd",
"details",
"dialog",
"dir",
"div",
"dl",
"dt",
"fieldset",
"figcaption",
"figure",
"footer",
"form",
"frame",
"frameset",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"head",
"header",
"hr",
"html",
"iframe",
"legend",
"li",
"link",
"main",
"menu",
"menuitem",
"nav",
"noframes",
"ol",
"optgroup",
"option",
"p",
"param",
"search",
"section",
"summary",
"table",
"tbody",
"td",
"template",
"tfoot",
"th",
"thead",
"title",
"tr",
"track",
"ul",
];
pub(super) fn starts_with_tag_ci(bytes: &[u8], tag: &[u8]) -> bool {
if bytes.len() < 1 + tag.len() {
return false;
}
if bytes[0] != b'<' {
return false;
}
for i in 0..tag.len() {
if bytes[1 + i].to_ascii_lowercase() != tag[i] {
return false;
}
}
let after = bytes.get(1 + tag.len()).copied();
matches!(
after,
None | Some(b' ') | Some(b'\t') | Some(b'>') | Some(b'\n')
)
}
pub(super) fn parse_html_block_start(line: &str, in_paragraph: bool) -> Option<HtmlBlockEnd> {
let bytes = line.as_bytes();
if bytes.is_empty() || bytes[0] != b'<' {
return None;
}
for &(tag, end) in &[
(b"pre" as &[u8], "</pre>"),
(b"script", "</script>"),
(b"style", "</style>"),
(b"textarea", "</textarea>"),
] {
if starts_with_tag_ci(bytes, tag) {
return Some(HtmlBlockEnd::EndTag(end));
}
}
if bytes.len() >= 4 && &bytes[..4] == b"<!--" {
return Some(HtmlBlockEnd::Comment);
}
if bytes.len() >= 2 && &bytes[..2] == b"<?" {
return Some(HtmlBlockEnd::ProcessingInstruction);
}
if bytes.len() >= 2
&& bytes[0] == b'<'
&& bytes[1] == b'!'
&& bytes.len() > 2
&& bytes[2].is_ascii_alphabetic()
{
return Some(HtmlBlockEnd::Declaration);
}
if bytes.len() >= 9 && &bytes[..9] == b"<![CDATA[" {
return Some(HtmlBlockEnd::Cdata);
}
if check_html_block_type6(line) {
return Some(HtmlBlockEnd::BlankLine);
}
if !in_paragraph && is_html_block_type7(line) {
return Some(HtmlBlockEnd::BlankLine);
}
None
}
#[inline]
pub(super) fn check_html_block_type6(line: &str) -> bool {
let bytes = line.as_bytes();
if bytes.len() < 2 || bytes[0] != b'<' {
return false;
}
let start = if bytes[1] == b'/' { 2 } else { 1 };
let mut end = start;
while end < bytes.len() && bytes[end].is_ascii_alphanumeric() {
end += 1;
}
if end == start {
return false;
}
if end < bytes.len() {
let next = bytes[end];
if !matches!(next, b' ' | b'\t' | b'>' | b'/' | b'\n') {
return false;
}
}
let tag_len = end - start;
if tag_len > 10 {
return false;
}
let mut buf = [0u8; 10];
for i in 0..tag_len {
buf[i] = bytes[start + i].to_ascii_lowercase();
}
HTML_BLOCK_TYPE6_TAGS
.binary_search_by(|t| t.as_bytes().cmp(&buf[..tag_len]))
.is_ok()
}
pub(super) fn is_html_block_type7(line: &str) -> bool {
let bytes = line.as_bytes();
if bytes.len() < 3 || bytes[0] != b'<' {
return false;
}
let is_close = bytes[1] == b'/';
let start = if is_close { 2 } else { 1 };
let mut i = start;
if i >= bytes.len() || !bytes[i].is_ascii_alphabetic() {
return false;
}
while i < bytes.len() && (bytes[i].is_ascii_alphanumeric() || bytes[i] == b'-') {
i += 1;
}
if is_close {
while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
i += 1;
}
if i >= bytes.len() || bytes[i] != b'>' {
return false;
}
i += 1;
} else {
loop {
let had_space = {
let before = i;
while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
i += 1;
}
i > before
};
if i >= bytes.len() {
return false;
}
if bytes[i] == b'>' {
i += 1;
break;
}
if bytes[i] == b'/' {
i += 1;
if i >= bytes.len() || bytes[i] != b'>' {
return false;
}
i += 1;
break;
}
if !had_space {
return false;
}
if !bytes[i].is_ascii_alphabetic() && bytes[i] != b'_' && bytes[i] != b':' {
return false;
}
while i < bytes.len()
&& (bytes[i].is_ascii_alphanumeric()
|| matches!(bytes[i], b'_' | b':' | b'.' | b'-'))
{
i += 1;
}
while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
i += 1;
}
if i < bytes.len() && bytes[i] == b'=' {
i += 1;
while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
i += 1;
}
if i >= bytes.len() {
return false;
}
if bytes[i] == b'\'' || bytes[i] == b'"' {
let quote = bytes[i];
i += 1;
while i < bytes.len() && bytes[i] != quote {
i += 1;
}
if i >= bytes.len() {
return false;
}
i += 1;
} else {
while i < bytes.len()
&& !matches!(
bytes[i],
b' ' | b'\t' | b'"' | b'\'' | b'=' | b'<' | b'>' | b'`'
)
{
i += 1;
}
}
}
}
}
while i < bytes.len() {
if bytes[i] != b' ' && bytes[i] != b'\t' {
return false;
}
i += 1;
}
true
}
pub(super) fn contains_ci(haystack: &[u8], needle: &[u8]) -> bool {
if needle.len() > haystack.len() {
return false;
}
let first_lower = needle[0];
let first_upper = first_lower.to_ascii_uppercase();
let mut pos = 0;
let end = haystack.len() - needle.len() + 1;
while pos < end {
let next = if first_lower == first_upper {
memchr::memchr(first_lower, &haystack[pos..end])
} else {
memchr::memchr2(first_lower, first_upper, &haystack[pos..end])
};
let Some(off) = next else { return false };
let i = pos + off;
let mut matched = true;
for j in 1..needle.len() {
if haystack[i + j].to_ascii_lowercase() != needle[j] {
matched = false;
break;
}
}
if matched {
return true;
}
pos = i + 1;
}
false
}
pub(super) fn html_block_ends(condition: &HtmlBlockEnd, line: &str) -> bool {
let bytes = line.as_bytes();
match condition {
HtmlBlockEnd::EndTag(tag) => contains_ci(bytes, tag.as_bytes()),
HtmlBlockEnd::Comment => {
let mut pos = 0;
while let Some(off) = memchr::memchr(b'-', &bytes[pos..]) {
let i = pos + off;
if i + 2 < bytes.len() && bytes[i + 1] == b'-' && bytes[i + 2] == b'>' {
return true;
}
pos = i + 1;
}
false
}
HtmlBlockEnd::ProcessingInstruction => {
let mut pos = 0;
while let Some(off) = memchr::memchr(b'?', &bytes[pos..]) {
let i = pos + off;
if i + 1 < bytes.len() && bytes[i + 1] == b'>' {
return true;
}
pos = i + 1;
}
false
}
HtmlBlockEnd::Declaration => memchr::memchr(b'>', bytes).is_some(),
HtmlBlockEnd::Cdata => {
let mut pos = 0;
while let Some(off) = memchr::memchr(b']', &bytes[pos..]) {
let i = pos + off;
if i + 2 < bytes.len() && bytes[i + 1] == b']' && bytes[i + 2] == b'>' {
return true;
}
pos = i + 1;
}
false
}
HtmlBlockEnd::BlankLine => false,
}
}