use crate::XmlVersion;
#[inline]
pub(crate) const fn is_xml10_char(ch: char) -> bool {
matches!(ch,
'\u{09}' | '\u{0A}' | '\u{0D}'
| '\u{20}'..='\u{D7FF}'
| '\u{E000}'..='\u{FFFD}'
| '\u{10000}'..='\u{10FFFF}')
}
#[inline]
pub(crate) const fn is_xml11_char(ch: char) -> bool {
matches!(ch,
'\u{01}'..='\u{D7FF}'
| '\u{E000}'..='\u{FFFD}'
| '\u{10000}'..='\u{10FFFF}')
}
#[inline]
pub(crate) const fn is_xml11_restricted_char(ch: char) -> bool {
matches!(ch,
'\u{01}'..='\u{08}'
| '\u{0B}'..='\u{0C}'
| '\u{0E}'..='\u{1F}'
| '\u{7F}'..='\u{84}'
| '\u{86}'..='\u{9F}')
}
#[inline]
pub(crate) const fn is_char(ch: char, version: XmlVersion) -> bool {
match version {
XmlVersion::Explicit1_1 => is_xml11_char(ch),
_ => is_xml10_char(ch),
}
}
#[inline]
pub(crate) const fn is_literal_char(ch: char, version: XmlVersion) -> bool {
match version {
XmlVersion::Explicit1_1 => is_xml11_char(ch) && !is_xml11_restricted_char(ch),
_ => is_xml10_char(ch),
}
}
#[inline]
pub(crate) const fn is_space(ch: char) -> bool {
matches!(ch, ' ' | '\t' | '\r' | '\n')
}
#[inline]
pub(crate) const fn is_name_start_char(ch: char) -> bool {
matches!(ch,
':' | 'A'..='Z' | '_' | 'a'..='z'
| '\u{C0}'..='\u{D6}'
| '\u{D8}'..='\u{F6}'
| '\u{F8}'..='\u{2FF}'
| '\u{370}'..='\u{37D}'
| '\u{37F}'..='\u{1FFF}'
| '\u{200C}'..='\u{200D}'
| '\u{2070}'..='\u{218F}'
| '\u{2C00}'..='\u{2FEF}'
| '\u{3001}'..='\u{D7FF}'
| '\u{F900}'..='\u{FDCF}'
| '\u{FDF0}'..='\u{FFFD}'
| '\u{10000}'..='\u{EFFFF}')
}
#[inline]
pub(crate) const fn is_name_char(ch: char) -> bool {
is_name_start_char(ch)
|| matches!(ch,
'-' | '.' | '0'..='9' | '\u{B7}'
| '\u{0300}'..='\u{036F}'
| '\u{203F}'..='\u{2040}')
}
pub(crate) fn is_name(name: &str) -> bool {
let mut chars = name.chars();
match chars.next() {
Some(first) if is_name_start_char(first) => chars.all(is_name_char),
_ => false,
}
}
pub(crate) fn is_ncname(name: &str) -> bool {
is_name(name) && !name.contains(':')
}
pub(crate) fn is_qname(name: &str) -> bool {
match name.split_once(':') {
Some((prefix, local)) => is_ncname(prefix) && is_ncname(local),
None => is_ncname(name),
}
}
pub(crate) fn is_nmtoken(token: &str) -> bool {
!token.is_empty() && token.chars().all(is_name_char)
}
#[inline]
pub(crate) const fn is_pubid_char(ch: char) -> bool {
matches!(ch,
' ' | '\r' | '\n'
| 'a'..='z' | 'A'..='Z' | '0'..='9'
| '-' | '\'' | '(' | ')' | '+' | ',' | '.' | '/' | ':'
| '=' | '?' | ';' | '!' | '*' | '#' | '@' | '$' | '_' | '%')
}
pub(crate) fn is_enc_name(name: &str) -> bool {
let mut chars = name.chars();
match chars.next() {
Some(first) if first.is_ascii_alphabetic() => {
chars.all(|ch| ch.is_ascii_alphanumeric() || matches!(ch, '.' | '_' | '-'))
}
_ => false,
}
}
pub(crate) fn is_version_num(version: &str) -> bool {
match version.strip_prefix("1.") {
Some(minor) => !minor.is_empty() && minor.bytes().all(|b| b.is_ascii_digit()),
None => false,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn char_production() {
assert!(!is_xml10_char('\u{0}'));
assert!(!is_xml10_char('\u{C}'));
assert!(is_xml10_char('\t'));
assert!(is_xml10_char('\u{10FFFF}'));
assert!(!is_xml11_char('\u{0}'));
assert!(is_xml11_char('\u{C}'));
}
#[test]
fn restricted_char_production() {
for ch in [
'\u{1}', '\u{8}', '\u{B}', '\u{C}', '\u{E}', '\u{1F}', '\u{7F}',
]
.into_iter()
.chain(['\u{84}', '\u{86}', '\u{9F}'])
{
assert!(is_xml11_restricted_char(ch), "U+{:04X}", ch as u32);
assert!(is_xml11_char(ch), "U+{:04X}", ch as u32);
assert!(
!is_literal_char(ch, XmlVersion::Explicit1_1),
"U+{:04X}",
ch as u32
);
}
for ch in ['\u{9}', '\u{A}', '\u{D}', '\u{20}', '\u{85}', '\u{A0}'] {
assert!(!is_xml11_restricted_char(ch), "U+{:04X}", ch as u32);
assert!(
is_literal_char(ch, XmlVersion::Explicit1_1),
"U+{:04X}",
ch as u32
);
}
assert!(is_literal_char('\u{85}', XmlVersion::Explicit1_0));
assert!(!is_literal_char('\u{C}', XmlVersion::Explicit1_0));
}
#[test]
fn name_production() {
assert!(is_name("doc"));
assert!(is_name(":doc"));
assert!(is_name("a-b.c_d"));
assert!(!is_name(""));
assert!(!is_name(".doc"));
assert!(!is_name("0A"));
assert!(!is_name("A@"));
assert!(!is_name("a b"));
}
#[test]
fn namespace_productions() {
assert!(is_ncname("foo"));
assert!(!is_ncname("a:b"));
assert!(is_qname("a:b"));
assert!(!is_qname("a:b:c"));
assert!(!is_qname("foo:"));
assert!(!is_qname(":foo"));
assert!(!is_qname("xmlns:"));
}
#[test]
fn nmtoken_production() {
assert!(is_nmtoken("0A"));
assert!(!is_nmtoken(""));
assert!(!is_nmtoken("a@"));
}
#[test]
fn literals() {
assert!(is_pubid_char('\''));
assert!(!is_pubid_char('"'));
assert!(!is_pubid_char('['));
assert!(is_enc_name("UTF-8"));
assert!(!is_enc_name(" UTF-8"));
assert!(!is_enc_name("-8"));
assert!(is_version_num("1.0"));
assert!(is_version_num("1.10"));
assert!(!is_version_num("1."));
assert!(!is_version_num("1.0 "));
assert!(!is_version_num("2.0"));
}
}