use crate::syntax::html::tag_end;
use super::is_blank;
const HTML_BLOCK_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",
"tfoot",
"th",
"thead",
"title",
"tr",
"track",
"ul",
];
const HTML_VERBATIM_TAGS: &[&str] = &["pre", "script", "style", "textarea"];
pub fn html_block_start(tail: &str, paragraph_open: bool) -> Option<u8> {
let bytes = tail.as_bytes();
if *bytes.first()? != b'<' {
return None;
}
let rest = &tail[1..];
if rest.starts_with("!--") {
return Some(2);
}
if rest.starts_with('?') {
return Some(3);
}
if rest.starts_with("![CDATA[") {
return Some(5);
}
if rest.starts_with('!') && rest[1..].starts_with(|c: char| c.is_ascii_alphabetic()) {
return Some(4);
}
let closing = rest.starts_with('/');
let name_start = usize::from(closing);
let name_len =
rest[name_start..].bytes().take_while(|b| b.is_ascii_alphanumeric() || *b == b'-').count();
if name_len == 0 {
return None;
}
let name = &rest[name_start..name_start + name_len];
let after = &rest[name_start + name_len..];
let is_tag_in = |tags: &[&str]| tag_in(tags, name);
if !closing
&& is_tag_in(HTML_VERBATIM_TAGS)
&& (after.is_empty() || after.starts_with([' ', '\t', '>']))
{
return Some(1);
}
if is_tag_in(HTML_BLOCK_TAGS)
&& (after.is_empty()
|| after.starts_with([' ', '\t', '>'])
|| (!closing && after.starts_with("/>")))
{
return Some(6);
}
if !paragraph_open
&& tag_end(after.as_bytes(), 0, closing, false).is_some_and(|end| is_blank(&after[end..]))
{
return Some(7);
}
None
}
fn tag_in(tags: &[&str], name: &str) -> bool {
tags.iter().any(|tag| tag.eq_ignore_ascii_case(name))
}
pub fn html_block_end(tail: &str, kind: u8) -> bool {
match kind {
1 => {
let bytes = tail.as_bytes();
(0..bytes.len()).any(|i| {
if bytes[i] != b'<' || bytes.get(i + 1) != Some(&b'/') {
return false;
}
let name =
bytes[i + 2..].iter().take(9).take_while(|b| b.is_ascii_alphabetic()).count();
bytes.get(i + 2 + name) == Some(&b'>')
&& tag_in(HTML_VERBATIM_TAGS, &tail[i + 2..i + 2 + name])
})
}
2 => tail.contains("-->"),
3 => tail.contains("?>"),
4 => tail.contains('>'),
5 => tail.contains("]]>"),
_ => false,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn block_starts_by_type() {
assert_eq!(html_block_start("<script>", false), Some(1));
assert_eq!(html_block_start("<PRE lang=x>", false), Some(1));
assert_eq!(html_block_start("<textarea", false), Some(1));
assert_eq!(html_block_start("<!-- c", false), Some(2));
assert_eq!(html_block_start("<?php", false), Some(3));
assert_eq!(html_block_start("<!DOCTYPE html>", false), Some(4));
assert_eq!(html_block_start("<![CDATA[x", false), Some(5));
assert_eq!(html_block_start("<div", false), Some(6));
assert_eq!(html_block_start("</DIV>", false), Some(6));
assert_eq!(html_block_start("<div/>", false), Some(6));
assert_eq!(html_block_start("<div>text", false), Some(6));
assert_eq!(html_block_start("<x-tag a=\"1\"> ", false), Some(7));
assert_eq!(html_block_start("<x-tag a=\"1\">", true), None, "type 7 never interrupts");
assert_eq!(html_block_start("<x>y", false), None, "type 7 needs only whitespace after");
assert_eq!(html_block_start("<div/ >", false), None);
assert_eq!(html_block_start("<!1", false), None);
assert_eq!(html_block_start("<>", false), None);
assert_eq!(html_block_start("text", false), None);
}
#[test]
fn verbatim_tags_closing_or_self_closing_are_type_7() {
assert_eq!(html_block_start("</pre>", false), Some(7));
assert_eq!(html_block_start("<pre/>", false), Some(7));
}
#[test]
fn block_ends() {
assert!(html_block_end("x</PRE>", 1));
assert!(html_block_end("</textarea>y", 1));
assert!(!html_block_end("</pre", 1));
assert!(!html_block_end("</prefoo>", 1));
assert!(!html_block_end("</pre >", 1));
assert!(!html_block_end("</div>", 1));
assert!(html_block_end("a --> b", 2));
assert!(html_block_end("?>", 3));
assert!(html_block_end("x>", 4));
assert!(html_block_end("]]>", 5));
assert!(!html_block_end("</div>", 6), "types 6/7 end on blank lines only");
}
}