///|
/// 外取件准入面:外取实体/外取子集按"外部件"解码——BOM 容错与 text decl
/// 编码识别复用 [decode_source] 前端;准入三闸:
/// ① text decl 语法闸(仅文首;缺 encoding / 序位倒置 / standalone 越权 /
/// 版本词形 / 分隔符越界皆拒);
/// ② 版次闸(件声明版次晚于文档版次即拒,XML 1.1 §4.3.4);
/// ③ Char 门(外取件按文档规则处理:版本键控字面扫描,字符引用是 ASCII
/// 文本,天然放行)。
/// text decl 不属于实体内容:残留会被底座按 PI 读,而 PI 目标 "xml" 保留,
/// valid 族会被错拒。取件两步 = decode_source(编码面)+ admit(校验面)。
/// 施工账:ctx.md §11–§12(役B2/役B3,R-B3a)。

///|
/// 准入:解码后文本 → text decl 验证剥除 + Char 门 → 实体内容。
/// 无 text decl = 原样过闸(decl 可选,P79)。
fn admit_external_text(
  text : String,
  doc_version : String?,
) -> String raise XmlError {
  let body = validate_strip_text_decl(text, doc_version)
  gate_chars(body, doc_version == Some("1.1"))
  body
}

///|
/// text decl 语法闸(仅文首;P77:,
/// 与 XMLDecl 三不同——version 可省/encoding 必备/standalone 越权即拒)。
/// 非 text decl 的  String raise XmlError {
  if !text.has_prefix(""(后者 = 空声明,缺 encoding 即拒)
  let decl_shaped = limit > 5 &&
    (is_decl_space4(text[5]) || (text[5] == '?' && limit > 6 && text[6] == '>'))
  if !decl_shaped {
    return text
  }
  // 分隔符版次键控:NEL/U+2028 只在 1.1 文档面合法(S 1.1 增两字符;
  // n17/n18:1.0 文档件用 NEL/U+2028 作分隔即拒)
  let allow_1_1_s = doc_version == Some("1.1")
  let mut pos : Int32 = 5
  let mut seen_version : String? = None
  let mut seen_encoding = false
  let mut close : Int32 = -1
  while pos < limit {
    pos = skip_decl_space(text, pos, allow_1_1_s)
    if pos < limit &&
      text[pos] == '?' &&
      pos + 1 < limit &&
      text[pos + 1] == '>' {
      close = pos
      break
    }
    // pseudo-attr 名
    let name_start = pos
    while pos < limit && text[pos] != '=' && !is_decl_space4(text[pos]) {
      pos = pos + 1
    }
    let name = text[name_start:pos].to_owned()
    if name.length() == 0 {
      raise text_decl_error(text, pos, "malformed text declaration")
    }
    // Eq(S* = S*)
    let mut cursor = skip_decl_space(text, pos, allow_1_1_s)
    if cursor >= limit || text[cursor] != '=' {
      raise text_decl_error(text, cursor, "expected '=' in text declaration")
    }
    cursor = skip_decl_space(text, cursor + 1, allow_1_1_s)
    match read_doctype_quoted(text, cursor) {
      Some((value, next)) => {
        if name == "version" {
          if seen_encoding {
            raise text_decl_error(
              text, name_start, "version after encoding in text declaration",
            )
          }
          if seen_version is Some(_) {
            raise text_decl_error(
              text, name_start, "duplicate version in text declaration",
            )
          }
          if value != "1.0" && value != "1.1" {
            raise text_decl_error(
              text,
              name_start,
              "implausible version number '\{value}' in text declaration",
            )
          }
          // 版次闸:件版次晚于文档版次即拒(1.1 件在 1.0 文档 = 违例;
          // 1.0 件在 1.1 文档合法——only-later 规则,eduni/xml-1.1 五案)
          if value == "1.1" && doc_version != Some("1.1") {
            raise text_decl_error(
              text, name_start, "entity version 1.1 later than document version",
            )
          }
          seen_version = Some(value)
        } else if name == "encoding" {
          // EncName(P81):[A-Za-z] 起,续 [A-Za-z0-9._-]
          let value_units = value.length()
          let mut index = 0
          let mut enc_ok = value_units > 0
          while index < value_units {
            let code = value[index].to_int()
            let lead = index == 0
            let ok = if lead {
              (code >= 0x41 && code <= 0x5A) || (code >= 0x61 && code <= 0x7A)
            } else {
              (code >= 0x41 && code <= 0x5A) ||
              (code >= 0x61 && code <= 0x7A) ||
              (code >= 0x30 && code <= 0x39) ||
              code == 0x2E ||
              code == 0x2D ||
              code == 0x5F
            }
            if !ok {
              enc_ok = false
              break
            }
            index = index + 1
          }
          if !enc_ok {
            raise text_decl_error(text, name_start, "malformed encoding name")
          }
          seen_encoding = true
        } else {
          raise text_decl_error(
            text,
            name_start,
            "'\{name}' not allowed in text declaration",
          )
        }
        pos = next
      }
      None =>
        raise text_decl_error(
          text, cursor, "unterminated pseudo-attribute value",
        )
    }
  }
  if close < 0 {
    raise text_decl_error(text, limit - 1, "unterminated text declaration")
  }
  if !seen_encoding {
    raise text_decl_error(
      text, 5, "encoding declaration missing in text declaration",
    )
  }
  text[close + 2:].to_owned()
}

///|
/// decl 面 S 跳扫(版本键控):基础四符处处合法;NEL(#x85)/U+2028 仅
/// 1.1 文档面追加(S 1.1 生产)。其余字符非分隔,即停。
fn skip_decl_space(text : String, start : Int32, allow_1_1 : Bool) -> Int32 {
  let limit = text.length()
  let mut pos = start
  while pos < limit {
    let ch = text[pos]
    if ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' {
      pos = pos + 1
      continue
    }
    if allow_1_1 && (ch == '\u{85}' || ch == '\u{2028}') {
      pos = pos + 1
      continue
    }
    break
  }
  pos
}

///|
fn is_decl_space4(ch : UInt16) -> Bool {
  ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n'
}

///|
/// text decl 违例(件坐标;外取件面 span,错误词 External 语义独立)。
fn text_decl_error(text : String, offset : Int32, message : String) -> XmlError {
  XmlError::At(
    error=XmlErrorKind::External(message),
    span=char_span(text, offset),
  )
}