use crate::syntax::html::tag_end;
pub fn scan(t: &str, pos: usize) -> Option<usize> {
let bytes = t.as_bytes();
debug_assert_eq!(bytes[pos], b'<');
let rest = &t[pos + 1..];
if let Some(after) = rest.strip_prefix("!--") {
if after.starts_with('>') {
return Some(pos + 1 + 3 + 1);
}
if after.starts_with("->") {
return Some(pos + 1 + 3 + 2);
}
let close = after.find("-->")?;
return Some(pos + 1 + 3 + close + 3);
}
if let Some(after) = rest.strip_prefix("![CDATA[") {
let close = after.find("]]>")?;
return Some(pos + 1 + 8 + close + 3);
}
if let Some(after) = rest.strip_prefix('?') {
let close = after.find("?>")?;
return Some(pos + 1 + 1 + close + 2);
}
if let Some(after) = rest.strip_prefix('!') {
if !after.starts_with(|c: char| c.is_ascii_alphabetic()) {
return None;
}
let close = after.find('>')?;
return Some(pos + 1 + 1 + close + 1);
}
let closing = rest.starts_with('/');
let mut i = pos + 1 + usize::from(closing);
let first = *bytes.get(i)?;
if !first.is_ascii_alphabetic() {
return None;
}
i += 1;
while bytes.get(i).copied().is_some_and(|c| c.is_ascii_alphanumeric() || c == b'-') {
i += 1;
}
tag_end(bytes, i, closing, true)
}
#[cfg(test)]
mod tests {
use super::scan;
#[test]
fn tags() {
assert_eq!(scan("<a>", 0), Some(3));
assert_eq!(scan("</a-b x=1>", 0), None, "closing tags take no attributes");
assert_eq!(scan("<1>", 0), None);
assert_eq!(scan("< a>", 0), None);
assert_eq!(scan("<a", 0), None);
}
#[test]
fn comments_and_friends() {
assert_eq!(scan("<!-->", 0), Some(5));
assert_eq!(scan("<!--->", 0), Some(6));
assert_eq!(scan("<!-- c -->x", 0), Some(10));
assert_eq!(scan("<!-- c", 0), None);
assert_eq!(scan("<![CDATA[x]]>", 0), Some(13));
assert_eq!(scan("<?php ?>", 0), Some(8));
assert_eq!(scan("<!DOCTYPE a>", 0), Some(12));
assert_eq!(scan("<!1>", 0), None);
}
}