#[inline(always)]
#[must_use]
pub const fn is_unicode_scalar_char(c: char) -> bool {
let u = c as u32;
u <= 0xD7FF || (u >= 0xE000 && u <= 0x10FFFF)
}
#[inline(always)]
#[must_use]
pub const fn is_xml_char(c: char) -> bool {
let u = c as u32;
(u == 0x09)
|| (u == 0x0A)
|| (u == 0x0D)
|| (u >= 0x20 && u <= 0xD7FF)
|| (u >= 0xE000 && u <= 0xFFFD)
|| (u >= 0x10000 && u <= 0x10FFFF)
}
#[inline(always)]
#[must_use]
pub const fn is_unicode_assignable_char(c: char) -> bool {
let u = c as u32;
(u == 0x09)
|| (u == 0x0A)
|| (u == 0x0D)
|| (u >= 0x20 && u <= 0x7E)
|| (u >= 0xA0 && u <= 0xD7FF)
|| (u >= 0xE000 && u <= 0xFDCF)
|| (u >= 0xFDF0 && u <= 0xFFFD)
|| (u >= 0x10000 && u <= 0x1FFFD)
|| (u >= 0x20000 && u <= 0x2FFFD)
|| (u >= 0x30000 && u <= 0x3FFFD)
|| (u >= 0x40000 && u <= 0x4FFFD)
|| (u >= 0x50000 && u <= 0x5FFFD)
|| (u >= 0x60000 && u <= 0x6FFFD)
|| (u >= 0x70000 && u <= 0x7FFFD)
|| (u >= 0x80000 && u <= 0x8FFFD)
|| (u >= 0x90000 && u <= 0x9FFFD)
|| (u >= 0xA0000 && u <= 0xAFFFD)
|| (u >= 0xB0000 && u <= 0xBFFFD)
|| (u >= 0xC0000 && u <= 0xCFFFD)
|| (u >= 0xD0000 && u <= 0xDFFFD)
|| (u >= 0xE0000 && u <= 0xEFFFD)
|| (u >= 0xF0000 && u <= 0xFFFFD)
|| (u >= 0x100000 && u <= 0x10FFFD)
}
#[inline]
#[must_use]
pub fn is_unicode_scalar(s: &str) -> bool {
let bytes = s.as_bytes();
let mut i = 0;
while i < bytes.len() {
let b = bytes[i];
if b < 0x80 {
i += 1;
} else {
return s[i..].chars().all(is_unicode_scalar_char);
}
}
true
}
#[inline]
#[must_use]
pub fn is_xml_chars(s: &str) -> bool {
let bytes = s.as_bytes();
let mut i = 0;
while i < bytes.len() {
let b = bytes[i];
if b < 0x80 {
if !ascii_ok(b) {
return false;
}
i += 1;
} else {
return s[i..].chars().all(is_xml_char);
}
}
true
}
#[inline]
#[must_use]
pub fn is_unicode_assignable(s: &str) -> bool {
let bytes = s.as_bytes();
let mut i = 0;
while i < bytes.len() {
let b = bytes[i];
if b < 0x80 {
if !ascii_ok(b) {
return false;
}
i += 1;
} else {
return s[i..].chars().all(is_unicode_assignable_char);
}
}
true
}
#[inline]
#[must_use]
pub fn is_unicode_scalar_bytes(bytes: &[u8]) -> bool {
let mut i = 0;
while i < bytes.len() {
let b = bytes[i];
if b < 0x80 {
i += 1;
} else {
return if let Ok(s) = std::str::from_utf8(&bytes[i..]) {
s.chars().all(is_unicode_scalar_char)
} else {
false
};
}
}
true
}
#[inline]
#[must_use]
pub fn is_xml_chars_bytes(bytes: &[u8]) -> bool {
let mut i = 0;
while i < bytes.len() {
let b = bytes[i];
if b < 0x80 {
if !ascii_ok(b) {
return false;
}
i += 1;
} else {
return if let Ok(s) = std::str::from_utf8(&bytes[i..]) {
s.chars().all(is_xml_char)
} else {
false
};
}
}
true
}
#[inline]
#[must_use]
pub fn is_unicode_assignable_bytes(bytes: &[u8]) -> bool {
let mut i = 0;
while i < bytes.len() {
let b = bytes[i];
if b < 0x80 {
if !ascii_ok(b) {
return false;
}
i += 1;
} else {
return if let Ok(s) = std::str::from_utf8(&bytes[i..]) {
s.chars().all(is_unicode_assignable_char)
} else {
false
};
}
}
true
}
#[inline(always)]
fn ascii_ok(b: u8) -> bool {
b == b'\t' || b == b'\n' || b == b'\r' || (b >= 0x20 && b <= 0x7E)
}
#[cfg(test)]
mod tests {
use super::*;
fn sample_scalars() -> [char; 8] {
[
'\u{0000}', 'A',
'\u{007E}', '\u{00A0}', '\u{D7FF}', '\u{E000}', '\u{FFFD}', '\u{1F600}', ]
}
fn make_ascii(n: usize) -> String {
"A".repeat(n)
}
const BMP_FFFE: char = '\u{FFFE}';
const BMP_FFFF: char = '\u{FFFF}';
#[test]
fn scalar_char_accepts_all_valid_char() {
for c in sample_scalars() {
assert!(is_unicode_scalar_char(c), "char U+{:04X}", c as u32);
}
}
#[test]
fn scalar_char_rejects_surrogates_by_construction() {
assert!(is_unicode_scalar_char('\u{D7FF}'));
assert!(is_unicode_scalar_char('\u{E000}'));
}
#[test]
fn xml_char_allows_tab_lf_cr_and_printables() {
for &c in &['\t', '\n', '\r', ' ', 'A', '~'] {
assert!(is_xml_char(c), "expected XML char: {:?}", c);
}
}
#[test]
fn xml_char_disallows_other_c0_controls_and_nonchars() {
assert!(!is_xml_char('\u{0000}'));
assert!(!is_xml_char('\u{001F}')); assert!(!is_xml_char(BMP_FFFE));
assert!(!is_xml_char(BMP_FFFF));
}
#[test]
fn xml_char_allows_valid_planes() {
assert!(is_xml_char('\u{D7FF}')); assert!(is_xml_char('\u{E000}')); assert!(is_xml_char('\u{FFFD}')); assert!(is_xml_char('\u{10000}')); assert!(is_xml_char('\u{10FFFF}')); }
#[test]
fn assignable_char_allows_useful_controls_and_printable_ascii() {
for &c in &['\t', '\n', '\r', ' ', 'A', '~'] {
assert!(
is_unicode_assignable_char(c),
"expected assignable: {:?}",
c
);
}
}
#[test]
fn assignable_char_rejects_del_and_c1_controls() {
assert!(!is_unicode_assignable_char('\u{007F}')); for cp in 0x80u32..=0x9Fu32 {
let c = char::from_u32(cp).unwrap();
assert!(!is_unicode_assignable_char(c), "C1 control U+{:04X}", cp);
}
}
#[test]
fn assignable_char_rejects_standardized_noncharacters() {
for cp in 0xFDD0u32..=0xFDEFu32 {
let c = char::from_u32(cp).unwrap();
assert!(
!is_unicode_assignable_char(c),
"U+{:04X} should be excluded",
cp
);
}
let planes = [0x00000u32, 0x10000, 0x20000, 0xE0000, 0xF0000, 0x100000];
for base in planes {
let fffe = char::from_u32(base + 0xFFFE).unwrap();
let ffff = char::from_u32(base + 0xFFFF).unwrap();
assert!(
!is_unicode_assignable_char(fffe),
"U+{:06X} excluded",
base + 0xFFFE
);
assert!(
!is_unicode_assignable_char(ffff),
"U+{:06X} excluded",
base + 0xFFFF
);
}
}
#[test]
fn assignable_char_allows_up_to_fffd_each_plane() {
let ok_points = [0x00FFFD, 0x01FFFD, 0x02FFFD, 0x0EFFFD, 0x0FFFFD, 0x10FFFD];
for cp in ok_points {
let c = char::from_u32(cp).unwrap();
assert!(
is_unicode_assignable_char(c),
"U+{:06X} should be allowed",
cp
);
}
}
#[test]
fn unicode_scalar_str_is_true_for_valid_utf8() {
assert!(is_unicode_scalar("hello"));
assert!(is_unicode_scalar("héllo"));
assert!(is_unicode_scalar("emoji 👍"));
assert!(is_unicode_scalar(&make_ascii(1024)));
}
#[test]
fn xml_chars_str_basic_cases() {
assert!(is_xml_chars("ok\tline\n\rmore"));
assert!(!is_xml_chars("\u{0000}"));
assert!(!is_xml_chars(&format!("x{}", BMP_FFFE)));
assert!(is_xml_chars("valid \u{FFFD} char"));
}
#[test]
fn assignable_str_basic_cases() {
assert!(is_unicode_assignable("Hello 👍"));
assert!(!is_unicode_assignable("\u{007F}")); assert!(!is_unicode_assignable("\u{0085}")); assert!(!is_unicode_assignable("\u{FDD0}")); assert!(!is_unicode_assignable("\u{1FFFE}")); }
#[test]
fn ascii_fast_path_large_strings() {
let s = make_ascii(4096);
assert!(is_xml_chars(&s));
assert!(is_unicode_assignable(&s));
}
#[test]
fn mixed_ascii_then_utf8_tail() {
let s = format!("{}{}", make_ascii(64), "👍👍👍");
assert!(is_xml_chars(&s));
assert!(is_unicode_assignable(&s));
}
#[test]
fn unicode_scalar_bytes_valid_and_invalid_utf8() {
assert!(is_unicode_scalar_bytes(b"ASCII only"));
assert!(is_unicode_scalar_bytes("héllo".as_bytes()));
let invalid = [0xF0, 0x28, 0x8C, 0x28];
assert!(!is_unicode_scalar_bytes(&invalid));
}
#[test]
fn xml_chars_bytes_respects_ascii_rules_and_utf8_decode() {
assert!(is_xml_chars_bytes(b"ok\t\n\r ASCII"));
assert!(!is_xml_chars_bytes(&[0x00]));
let with_nonchar = "x\u{FFFF}".as_bytes().to_vec();
assert!(!is_xml_chars_bytes(&with_nonchar));
let invalid_utf8 = [0xE2, 0x28, 0xA1]; assert!(!is_xml_chars_bytes(&invalid_utf8));
}
#[test]
fn assignable_bytes_respects_exclusions() {
assert!(is_unicode_assignable_bytes(b"Hello World"));
assert!(is_unicode_assignable_bytes("👍".as_bytes()));
assert!(!is_unicode_assignable_bytes(&[0x7F])); let with_plane_nonchar = "x\u{1FFFE}".as_bytes().to_vec();
assert!(!is_unicode_assignable_bytes(&with_plane_nonchar));
}
#[test]
fn ascii_ok_rules() {
for &b in &[b'\t', b'\n', b'\r', b' ', b'~'] {
assert!(super::ascii_ok(b), "byte 0x{:02X} should be ok", b);
}
assert!(!super::ascii_ok(0x1F));
assert!(!super::ascii_ok(0x7F));
}
#[test]
fn xml_boundaries() {
assert!(!is_xml_char('\u{001F}'));
assert!(is_xml_char('\u{0020}'));
assert!(is_xml_char('\u{D7FF}'));
assert!(is_xml_char('\u{E000}'));
assert!(is_xml_char('\u{FFFD}'));
assert!(!is_xml_char('\u{FFFE}'));
assert!(!is_xml_char('\u{FFFF}'));
}
#[test]
fn assignable_boundaries() {
assert!(is_unicode_assignable_char('\u{007E}'));
assert!(!is_unicode_assignable_char('\u{007F}'));
assert!(is_unicode_assignable_char('\u{00A0}'));
assert!(is_unicode_assignable_char('\u{D7FF}'));
assert!(is_unicode_assignable_char('\u{E000}'));
assert!(is_unicode_assignable_char('\u{FDCF}'));
assert!(!is_unicode_assignable_char('\u{FDD0}'));
assert!(!is_unicode_assignable_char('\u{FDEF}'));
assert!(is_unicode_assignable_char('\u{FDF0}'));
assert!(is_unicode_assignable_char('\u{FFFD}'));
assert!(!is_unicode_assignable_char('\u{FFFE}'));
assert!(!is_unicode_assignable_char('\u{FFFF}'));
}
#[test]
fn supplementary_plane_boundaries_assignable() {
for plane in 0x1u32..=0x10 {
let start = (plane << 16) + 0x0000;
let end_ok = (plane << 16) + 0xFFFD;
let end_bad1 = (plane << 16) + 0xFFFE;
let end_bad2 = (plane << 16) + 0xFFFF;
let start_char = char::from_u32(start).unwrap();
let ok_char = char::from_u32(end_ok).unwrap();
let bad1_char = char::from_u32(end_bad1).unwrap();
let bad2_char = char::from_u32(end_bad2).unwrap();
assert!(
is_unicode_assignable_char(start_char),
"U+{:06X} should be allowed",
start
);
assert!(
is_unicode_assignable_char(ok_char),
"U+{:06X} should be allowed",
end_ok
);
assert!(
!is_unicode_assignable_char(bad1_char),
"U+{:06X} should be excluded",
end_bad1
);
assert!(
!is_unicode_assignable_char(bad2_char),
"U+{:06X} should be excluded",
end_bad2
);
}
}
#[test]
fn xml_and_assignable_mixed_strings() {
let xml_ok = format!("start\t\n\r mid {} end", '\u{FFFD}');
assert!(is_xml_chars(&xml_ok));
let xml_bad = format!("x{}", '\u{FFFF}');
assert!(!is_xml_chars(&xml_bad));
let asg_ok = format!("hello {} world {}", '\u{00A0}', '\u{1F600}');
assert!(is_unicode_assignable(&asg_ok));
let asg_bad = format!("bad{}", '\u{1FFFE}');
assert!(!is_unicode_assignable(&asg_bad));
}
}