///|
/// xmlconf_flatten —— W3C XML Conformance Suite(xmlts20130923)清单扁平化:
/// 官方聚合清单 xmlconf.xml 用 DTD 外部参数实体聚合 21 本分册(外挂实体
/// 面超出 @moonxml 解析层),本命令按「组→分册」映射逐册用 @moonxml 自解析
/// (dogfood),产出带校验和的 TSV:TYPE \t base+URI \t ID \t EDITION \t NS(保持
/// 册序,再生逐字节稳定;EDITION/NS 缺省 = 空串 = 全版本适用/非 NS 面)。
/// 运行:moon run src/cmd/xmlconf_flatten(cwd = moonxml 仓根)。
///|
/// 分册→基准径映射(摘自 xmlconf.xml 聚合体;套件 2013 版固定后不再变动)。
let manifest_table : Array[(String, String)] = [
("xmltest/xmltest.xml", "xmltest/"),
("japanese/japanese.xml", "japanese/"),
("sun/sun-valid.xml", "sun/"),
("sun/sun-invalid.xml", "sun/"),
("sun/sun-not-wf.xml", "sun/"),
("sun/sun-error.xml", "sun/"),
("oasis/oasis.xml", "oasis/"),
("ibm/ibm_oasis_invalid.xml", "ibm/"),
("ibm/ibm_oasis_not-wf.xml", "ibm/"),
("ibm/ibm_oasis_valid.xml", "ibm/"),
("ibm/xml-1.1/ibm_invalid.xml", "ibm/xml-1.1/"),
("ibm/xml-1.1/ibm_not-wf.xml", "ibm/xml-1.1/"),
("ibm/xml-1.1/ibm_valid.xml", "ibm/xml-1.1/"),
("eduni/errata-2e/errata2e.xml", "eduni/errata-2e/"),
("eduni/xml-1.1/xml11.xml", "eduni/xml-1.1/"),
("eduni/namespaces/1.0/rmt-ns10.xml", "eduni/namespaces/1.0/"),
("eduni/namespaces/1.1/rmt-ns11.xml", "eduni/namespaces/1.1/"),
("eduni/errata-3e/errata3e.xml", "eduni/errata-3e/"),
("eduni/errata-4e/errata4e.xml", "eduni/errata-4e/"),
("eduni/namespaces/errata-1e/errata1e.xml", "eduni/namespaces/errata-1e/"),
("eduni/misc/ht-bh.xml", "eduni/misc/"),
]
///|
/// 属性按限定名原文取(清单无命名空间)。
fn attribute_of(element : @base.Element, wanted : String) -> String {
for attribute in element.attributes {
if attribute.name.qualified_name == wanted {
return attribute.value
}
}
""
}
///|
/// 递归收 TEST 素(分册顶层或 TESTCASES 包裹两形)。
fn collect_tests(
element : @base.Element,
base : String,
rows : Array[String],
) -> Unit {
for node in element.children {
match node {
@base.Node::Element(sub) => {
if sub.name.local_name == "TEST" {
let uri = attribute_of(sub, "URI")
let ty = attribute_of(sub, "TYPE")
let id = attribute_of(sub, "ID")
// EDITION 属性:测试适用的 XML 1.0 版次面("1 2 3 4" / "5";
// 缺省 = 全版本适用)。套件的裁决模型是"处理器按面选子集"。
let edition = attribute_of(sub, "EDITION")
// NS 列:Namespaces in XML 测试只对命名空间感知处理器适用
// (官方套件把 NS 测试独立成节)——按分册径标 "ns",余者空 = 非 NS 面。
let ns = if base.has_prefix("eduni/namespaces/") { "ns" } else { "" }
rows.push("\{ty}\t\{base}\{uri}\t\{id}\t\{edition}\t\{ns}")
}
collect_tests(sub, base, rows)
}
_ => ()
}
}
}
///|
/// 剥前置 XML 声明(包裹根前移会挤掉「声明必须在文档首」的位置)。
fn strip_declaration(text : String) -> String {
if text.has_prefix("") {
Some(index) => {
let body_start = index + 2
text[body_start:].to_owned()
}
None => text
}
} else {
text
}
}
///|
/// XmlError → 可读面(套件工具无 Show/Debug,手格式化;行/列 = 起点值)。
fn xmlconf_error_hint(err : @base.XmlError) -> String {
match err {
At(error~, span~) => {
let kind_text = match error {
InvalidSyntax(message) => "InvalidSyntax(\{message})"
UnmatchedTag(expected~, found~) =>
"UnmatchedTag(expected=\{expected}, found=\{found})"
InvalidAttribute(message) => "InvalidAttribute(\{message})"
InvalidEntity(message) => "InvalidEntity(\{message})"
Encoding(message) => "Encoding(\{message})"
IllegalName(message) => "IllegalName(\{message})"
IllegalChar(codepoint) => "IllegalChar(\{codepoint})"
Structure(message) => "Structure(\{message})"
External(message) => "External(\{message})"
UnexpectedEof => "UnexpectedEof"
}
"At(\{kind_text} @ line \{span.start.line} col \{span.start.column})"
}
}
}
///|
async fn main {
let rows : Array[String] = []
for pair in manifest_table {
let (manifest, base) = pair
let text = @fs.read_file("./.xmlconf/xmlconf/\{manifest}").text()
// sun/ibm 分册是裸 TEST/TESTCASES 片段(多顶层素)——剥声明+包合成根再解析
let body = strip_declaration(text)
let document = @base.parse("\{body}") catch {
err => abort("flatten: \{manifest}: \{xmlconf_error_hint(err)}")
}
collect_tests(document.root, base, rows)
}
let mut joined = ""
for row in rows {
joined = "\{joined}\{row}\n"
}
@fs.write_file("./.xmlconf/xmlconf_manifest.tsv", joined)
// 分型计数读数(固定后由 harness 断言)
let mut valid_count = 0
let mut invalid_count = 0
let mut not_wf_count = 0
let mut error_count = 0
for row in rows {
if row.has_prefix("valid\t") {
valid_count += 1
} else if row.has_prefix("invalid\t") {
invalid_count += 1
} else if row.has_prefix("not-wf\t") {
not_wf_count += 1
} else if row.has_prefix("error\t") {
error_count += 1
} else {
abort("flatten: unknown TYPE in row: \{row}")
}
}
debug(
"flatten: \{rows.length()} tests (valid \{valid_count} / invalid \{invalid_count} / not-wf \{not_wf_count} / error \{error_count})",
)
}