///|
/// 编码前端 + 版本键控 Char 门。
/// 职责:把 bytes 变成规范 UTF-8 源文本,喂给 [parse] 前的源域处理:
/// ① BOM 嗅探(UTF-8 / UTF-16LE / UTF-16BE,BOM 是 UTF-16 的唯一合法引导);
/// ② 声明面编码决策——支持集 = utf-8(严格)/ utf-16(须 BOM 键控)/
/// iso-8859-1 / ascii 同族;其余编码名 = 不支持编码,拒
/// (引擎实际能力如实报错;套件 error 判词档若按门要求必解析,则挂 deferred,
/// 见 ctx.md §5);
/// 施工账:ctx.md §5(役A1)。
/// ③ BOM/声明冲突检测(两个方向都是错);
/// ④ 严格 UTF-8:坏字节 / CESU 代理 / 过长形一律拒;
/// ⑤ 全文 Char 门,版本取自声明:1.0 全表 vs 1.1 restricted-literal
/// (1.1 的 [#x1-#x8]…[#x86-#x9F] 区须走字符引用,字面 = 拒;NEL 字面合法)。
/// 错误面 = [XmlError](新词 [XmlErrorKind::Encoding] / [XmlErrorKind::IllegalChar]);
/// offset 精确,行列尽力(byte 面按 0x0A 计,text 面按字符计)。
pub fn decode_source(bytes : Bytes) -> Result[String, XmlError] {
Ok(decode_from(bytes[:])) catch {
error => Err(error)
}
}
///|
/// 编码分流:UTF-16 BOM 两臂(分流后声明只许 utf-16 族)、
/// UTF-8 BOM 一臂(剥后按 8 位面走)、无 BOM 8 位面。
fn decode_from(view : BytesView) -> String raise XmlError {
match view {
[0xFF, 0xFE, .. rest] => decode_utf16_document(rest, false)
[0xFE, 0xFF, .. rest] => decode_utf16_document(rest, true)
[0xEF, 0xBB, 0xBF, .. rest] => decode_8bit_document(rest, true)
_ => decode_8bit_document(view, false)
}
}
///|
/// UTF-16 文档面:严格解码(代理配对)→ 声明冲突检 → Char 门。
/// BOM 是本面的编码真源;声明若给出非 utf-16 族编码名即冲突。
fn decode_utf16_document(
view : BytesView,
big_endian : Bool,
) -> String raise XmlError {
let text = decode_utf16(view, big_endian)
let declaration = scan_decl(text)
match declaration.encoding {
Some(name) =>
if !name.to_lower().has_prefix("utf-16") {
raise encoding_error(
view,
0,
"UTF-16 byte order mark with '\{name}' declaration",
)
}
None => ()
}
gate_chars(text, declaration.version is Some("1.1"))
text
}
///|
/// 8 位文档面(UTF-8 BOM 已剥):声明嗅探在 latin-1 预览上做
/// (声明段必为 ASCII;非 ASCII 字节只可能出现在值里,嗅探容之)。
fn decode_8bit_document(
view : BytesView,
has_utf8_bom : Bool,
) -> String raise XmlError {
let declaration = scan_decl(latin1_preview(view))
let declared : String? = match declaration.encoding {
Some(name) => Some(name.to_lower())
None => None
}
if declared is Some(name) && name.has_prefix("utf-16") {
raise encoding_error(view, 0, "UTF-16 declaration without byte order mark")
}
if has_utf8_bom && declared is Some("iso-8859-1") {
raise encoding_error(
view, 0, "UTF-8 byte order mark with 'iso-8859-1' declaration",
)
}
match declared {
None | Some("utf-8") | Some("ascii") | Some("us-ascii") => {
let text = decode_strict_utf8(view)
gate_chars(text, declaration.version is Some("1.1"))
text
}
Some("iso-8859-1") => {
let text = decode_latin1(view)
gate_chars(text, declaration.version is Some("1.1"))
text
}
Some(name) =>
match jis_encoding_of_label(name) {
Some(encoding) => {
let text = decode_jis(encoding, view)
gate_chars(text, declaration.version is Some("1.1"))
text
}
None => raise encoding_error(view, 0, "unsupported encoding '\{name}'")
}
}
}
///|
/// 声明嗅探结果:只取 version / encoding 两属性。
priv struct DeclInfo {
mut version : String?
mut encoding : String?
}
///|
/// 声明嗅探:文本头恰为 ` DeclInfo {
let info : DeclInfo = { version: None, encoding: None, }
let marker = "`;游标为字符位。
fn scan_decl_attrs(head : String, start : Int32, info : DeclInfo) -> DeclInfo {
let limit = head.char_length()
let mut cursor = start
while cursor < limit {
while cursor < limit && is_decl_space(head.get_char(cursor)) {
cursor = cursor + 1
}
if cursor >= limit {
return info
}
if head.get_char(cursor) == Some('?') {
return info
}
let name : Array[Char] = []
while cursor < limit {
match head.get_char(cursor) {
Some('=') | Some('?') => break
Some(ch) =>
if is_decl_space(Some(ch)) {
break
} else {
name.push(ch)
cursor = cursor + 1
}
None => return info
}
}
while cursor < limit && is_decl_space(head.get_char(cursor)) {
cursor = cursor + 1
}
if head.get_char(cursor) != Some('=') {
return info
}
cursor = cursor + 1
while cursor < limit && is_decl_space(head.get_char(cursor)) {
cursor = cursor + 1
}
let quote = head.get_char(cursor)
if !(quote is Some('\'') || quote is Some('"')) {
return info
}
cursor = cursor + 1
let value : Array[Char] = []
while cursor < limit && head.get_char(cursor) != quote {
match head.get_char(cursor) {
Some(ch) => {
value.push(ch)
cursor = cursor + 1
}
None => return info
}
}
if cursor >= limit {
return info
}
cursor = cursor + 1
let attribute = String::from_array(name[:]).to_lower()
if attribute == "version" {
info.version = Some(String::from_array(value[:]))
} else if attribute == "encoding" {
info.encoding = Some(String::from_array(value[:]))
}
}
info
}
///|
/// 声明段空白(S 生产 = 空格/制表/CR/LF;NEL 在 1.1 是行尾归空白——
/// 嗅探两版统一容之,1.0 面的声明合法性仍归底座裁判)。
fn is_decl_space(ch : Char?) -> Bool {
ch is Some(' ') ||
ch is Some('\t') ||
ch is Some('\r') ||
ch is Some('\n') ||
ch is Some('\u{85}')
}
///|
/// 声明嗅探用 latin-1 预览:前 256 字节逐字节映字符。
fn latin1_preview(view : BytesView) -> String {
let bound = if view.length() < 256 { view.length() } else { 256 }
decode_latin1(view[0:bound])
}
///|
/// 严格 UTF-8:坏字节即拒(@utf8.decode raise Malformed,
/// 载荷为坏点起的余段 → 反解 offset)。
fn decode_strict_utf8(view : BytesView) -> String raise XmlError {
@utf8.decode(view, ignore_bom=true) catch {
malformed =>
match malformed {
@utf8.Malformed(remaining) => {
let offset = view.length() - remaining.length()
raise encoding_error(view, offset, "malformed UTF-8 sequence")
}
}
}
}
///|
/// UTF-16 严格解码:双字节成码位,代理高低配对接成增面字符,
/// 落单代理 / 奇长度即拒。奇长度先整检(错误定位到末字节)。
fn decode_utf16(view : BytesView, big_endian : Bool) -> String raise XmlError {
if view.length() % 2 != 0 {
raise encoding_error(
view,
view.length() - 1,
"UTF-16 sequence with odd byte length",
)
}
let chars : Array[Char] = []
for cursor = 0, scan = view {
match scan {
[] => break
[_] | [_, _, _] =>
raise encoding_error(
view,
view.length() - 1,
"UTF-16 sequence with odd byte length",
)
[first, second, .. rest] => {
let unit = utf16_unit(first, second, big_endian)
if is_high_surrogate(unit) {
match rest {
[third, fourth, .. tail] => {
let paired = utf16_unit(third, fourth, big_endian)
if is_low_surrogate(paired) {
let codepoint = 0x10000 +
(unit - 0xD800) * 0x400 +
(paired - 0xDC00)
chars.push(codepoint.unsafe_to_char())
continue cursor + 4, tail
}
raise encoding_error(view, cursor, "unpaired surrogate in UTF-16")
}
_ =>
raise encoding_error(view, cursor, "unpaired surrogate in UTF-16")
}
}
if is_low_surrogate(unit) {
raise encoding_error(view, cursor, "unpaired surrogate in UTF-16")
}
chars.push(unit.unsafe_to_char())
continue cursor + 2, rest
}
}
}
String::from_array(chars[:])
}
///|
/// 双字节 → UTF-16 码位(字节序由 BOM 定)。
fn utf16_unit(first : Byte, second : Byte, big_endian : Bool) -> Int32 {
if big_endian {
first.to_int() * 256 + second.to_int()
} else {
second.to_int() * 256 + first.to_int()
}
}
///|
fn is_high_surrogate(unit : Int32) -> Bool {
unit >= 0xD800 && unit <= 0xDBFF
}
///|
fn is_low_surrogate(unit : Int32) -> Bool {
unit >= 0xDC00 && unit <= 0xDFFF
}
///|
/// ISO-8859-1:字节直映码位(#x80-#x9F 控制位照映,合法性归 Char 门)。
fn decode_latin1(view : BytesView) -> String {
let chars : Array[Char] = []
for scan = view {
match scan {
[] => break
[byte, .. rest] => {
chars.push(byte.to_int().unsafe_to_char())
continue rest
}
}
}
String::from_array(chars[:])
}
///|
/// 全文 Char 门:对整个解码后文本逐字符查版本键控表。
/// Char 生产约束文档一切产生式(内容/属性/注释/PI/声明/DTD),
/// 前置整扫是正判据非碰绿——底座不做此面,本层补位。
fn gate_chars(text : String, version_is_1_1 : Bool) -> Unit raise XmlError {
for (offset, ch) in text.iter2() {
let codepoint = ch.to_int()
let illegal = if version_is_1_1 {
illegal_char_1_1(codepoint)
} else {
illegal_char_1_0(codepoint)
}
if illegal {
raise XmlError::At(
error=XmlErrorKind::IllegalChar(codepoint),
span=char_span(text, offset),
)
}
}
}
///|
/// XML 1.0 Char 生产(4e 全表):
/// [#x9|#xA|#xD|[#x20-#xD7FF]|[#xE000-#xFFFD]|[#x10000-#x10FFFF]]
/// 之外皆非法(含 #x0、C0 受禁区、代理、#xFFFE/#xFFFF)。
fn illegal_char_1_0(codepoint : Int32) -> Bool {
if codepoint == 0x9 || codepoint == 0xA || codepoint == 0xD {
return false
}
if codepoint >= 0x20 && codepoint <= 0xD7FF {
return false
}
if codepoint >= 0xE000 && codepoint <= 0xFFFD {
return false
}
if codepoint >= 0x10000 && codepoint <= 0x10FFFF {
return false
}
true
}
///|
/// XML 1.1 字面受限区:[#x1-#x8]|[#xB-#xC]|[#xE-#x1F]|[#x7F-#x84]|[#x86-#x9F]
/// 须走字符引用,字面出现即拒;#x85(NEL)字面合法(1.1 行尾)。
/// 区外沿用 1.0 全表(1.1 合法集 ⊇ 1.0 合法集)。
fn illegal_char_1_1(codepoint : Int32) -> Bool {
if codepoint == 0x85 {
return false
}
if codepoint >= 0x1 && codepoint <= 0x8 {
return true
}
if codepoint >= 0xB && codepoint <= 0xC {
return true
}
if codepoint >= 0xE && codepoint <= 0x1F {
return true
}
if codepoint >= 0x7F && codepoint <= 0x84 {
return true
}
if codepoint >= 0x86 && codepoint <= 0x9F {
return true
}
illegal_char_1_0(codepoint)
}
///|
/// 编码面错误(byte 域):message + offset(0 起),行列按 0x0A 计尽力。
fn encoding_error(
view : BytesView,
offset : Int32,
message : String,
) -> XmlError {
XmlError::At(
error=XmlErrorKind::Encoding(message),
span=byte_span(view, offset),
)
}
///|
/// byte 面位置:0x0A 不出现在 UTF-8/UTF-16 多字节序内部,
/// 直接数字节得 line;column = 距上一 0x0A 的字节数。
fn byte_span(view : BytesView, offset : Int32) -> SourceSpan {
let mut line = 1
let mut last_newline = -1
for index = 0, scan = view {
match scan {
[] => break
[byte, .. rest] => {
if index >= offset {
break
}
if byte.to_int() == 0x0A {
line = line + 1
last_newline = index
}
continue index + 1, rest
}
}
}
let column = if last_newline < 0 { offset + 1 } else { offset - last_newline }
{
start: { offset, line, column, },
end: { offset: offset + 1, line, column: column + 1, },
}
}
///|
/// text 面位置(Char 门用):按字符计行列,offset 为字节位(iter2 给)。
fn char_span(text : String, offset : Int32) -> SourceSpan {
let mut line = 1
let mut column = 1
for (seen, ch) in text.iter2() {
if seen >= offset {
break
}
if ch == '\n' {
line = line + 1
column = 1
} else {
column = column + 1
}
}
{
start: { offset, line, column, },
end: { offset: offset + 1, line, column: column + 1, },
}
}