Sign in

    moonxml

    MoonBit XML base: spec-shaped document model, namespace-aware pull parsing with BOM/encoding detection, canonical serialization, and W3C XML Conformance coverage.

    xml
    parser
    dom
    Download zip
    Author
    Version
    0.1.0
    License
    Apache-2.0
    Last updated
    4 hours ago
    Downloads
    3

    #MoonXML

    thy1016/moonxml 是一个用 MoonBit 编写的 XML 通用库:提供命名空间感知的 拉取式解析、强类型文档模型(DOM 树)、规范化序列化与编码前端,另含 XML 1.0 版次面 / 1.1 键控、外部实体解析端口与 billion-laughs 防护。

    本仓与 thy1016/moonttl(Turtle / TriG / N-Triples / N-Quads 解析)、 thy1016/jsonld(JSON-LD 1.1 通用库)同属 RDF 解析库项目族—— XML 底座为族内 RDF/XML 映射等消费方向而建,工程纪律与体例一脉相承。

    #特性

    • 规范型文档模型:Document / Element / Node 强类型树,to_xml 规范化序列化,解析–序列化往返稳定。
    • 解析入口分层:parse 一行入口(命名空间已解析、实体已展开); parse_edition 全参入口(版次面 / NS 面 / 实体解析器可选)。
    • 命名空间双面:NsMode::On(NS-aware,缺省)与 NsMode::Off (非 NS 面,名字按原文直通,冒号名与组合字符衔接段合法)。
    • 版本面键控:XML 1.0 四版次(Editions1to4 全表 / Edition5 紧凑) 与 1.1 的名字产生式差异按版次键控。
    • 编码前端 decode_source:BOM 嗅探(UTF-8 / UTF-16 LE/BE)+ 声明面 编码决策(utf-8、utf-16、iso-8859-1,及 euc-jp / shift_jis / iso-2022-jp 三码十三标签),BOM 与声明冲突检测。
    • 外部实体可控:EntityResolver 端口缺省不取外件(XXE 防御缺省关); 实体展开带 document 级输出预算双闸。
    • 强类型错误面:XmlError 携带精确 SourceSpan(行 / 列 / 字节偏移)。
    • W3C 一致性:XML Conformance Suite(xmlts20130923)自测 2572 passed / 13 deferred / 0 failed。

    #安装

    确保环境中已安装 MoonBit 工具链,然后在模块根目录:

    moon add thy1016/moonxml

    再在用到它的包的 moon.pkg import 块中添加 "thy1016/moonxml", 代码内以 @moonxml 前缀使用。

    #快速上手

    #解析与序列化

    match @moonxml.parse("<r><a>1</a><b x=\"2\"/></r>") {
    Ok(doc) => println(doc.to_xml())
    Err(_) => println("parse failed")
    }

    Document 含 decl / doctype / prologue / root / epilogue; 元素与节点按 Element / Node(Text / CData / Comment / PI) 强类型遍历,命名空间信息在 XmlName(prefix / local_name / namespace_uri)。

    #版次面与 NS 面

    // XML 1.0 四版次全表名字面(Edition5 为现行第五版紧凑面)
    let opts_edition = @moonxml.parse_edition(
    source,
    edition: @moonxml.XmlEdition::Editions1to4,
    )
    // 非 NS 面:名字原文直通,xmlns 即普通属性
    let ns_off = @moonxml.parse_edition(
    source,
    edition: @moonxml.XmlEdition::Edition5,
    ns: @moonxml.NsMode::Off,
    )

    #字节输入与编码

    // BOM 嗅探 + 声明面编码决策;不支持面返回 Encoding 错
    match @moonxml.decode_source(bytes) {
    Ok(text) => println("decoded \{text.length()} chars")
    Err(_) => println("unsupported encoding")
    }

    #外部实体解析

    库内不做网络 I/O;外部实体经 EntityResolver 回调由调用方供给, 缺省 None = 拒取一切外件(XXE 防御缺省关):

    // resolver : &EntityResolver——实现 resolve(public_id, system_id, base)
    // 返回 None 表示拒取;构造时经 parse_edition 的 resolver 参数注入。

    #API 总览

    入口作用
    parse字符串 → Document(NS-aware,实体已展开)
    parse_edition全参入口:版次面 + NS 面 + 实体解析器
    decode_sourceBytes → 文本(BOM 嗅探 + 声明面编码决策)
    Document::to_xml规范化序列化(往返稳定形)
    is_name_start_char_11 等名字产生式区间表谓词(1.0 四版次 / 1.1 双面)

    #错误面

    XmlError = At(error, span):XmlErrorKind 十变体 —— UnexpectedEof / InvalidSyntax / UnmatchedTag / InvalidAttribute / InvalidEntity / Encoding / IllegalChar / IllegalName / Structure / External,位置信息在 SourceSpan(行 / 列 / 字节偏移)。

    #设计边界

    • 流式(SAX / 增量管线):不提供,暂不排期;出现官方流式 oracle 套件 或消费端真实流式需求时重估。
    • 全后端可移植:moon.mod 不钉 preferred_target。
    • 底座隔离:解析引擎型不跨入公开 API 面。

    定位与边界的完整说明见 spec.md。

    #测试与验证

    moon test --target native

    运行时输出各测试面的计数行(下表实测于 2026-10-24):

    面读数
    W3C XML Conformance(xmlts20130923,2585 案)2572 passed / 13 deferred / 0 failed
    单元与契约门120 / 120

    测试语料锁版随仓:.xmlconf/(W3C XML Conformance Suite,由 SHA256SUMS 锁定)、.xmlnames/(W3C XML 1.0 4e / 1.1 2e 推荐标准 快照)、.encodings/(WHATWG Encoding Standard 索引表)。

    版本沿革见 CHANGELOG.md。

    #许可证

    • 本项目代码采用 Apache License 2.0(见 LICENSE)。
    • .xmlconf/ 内的 W3C 测试文件、.xmlnames/ 内的 W3C 推荐标准快照按 W3C 文档许可分发,.encodings/ 内的 WHATWG 索引表按 CC BY 4.0 分发; 原始文件逐字节未修改,来源与许可细目见 THIRD_PARTY_NOTICES.md。
    • 无背书声明:上文套件通过计数是本项目自测结果,只表示本实现能通过相应 用例,不代表 W3C、WHATWG 认证、合规认定或任何形式的背书。

    EntityResolver

    pub(open) trait EntityResolver {
    fn resolve(Self, base : String?, system_id : String, public_id : String?) -> Bytes?
    }

    外部资源解析端口:外部 DTD 子集与外取实体的唯一取件缝。 缺省拒绝取件(parse_edition 的 resolver 缺省 None,不取外件,语义零漂移) ——XXE 防线。策略对齐同门 D-18 消费端政策参数先例:取件失败走独立错误词 [XmlErrorKind::External],不污染编解码错误语义。网络访问由消费方实现, 本仓不内置网络 resolver。 施工账:ctx.md §11(役B2,R-B1a)。

    XmlError

    pub(all) suberror XmlError {
    At(error~ : XmlErrorKind, span~ : SourceSpan)
    } derive(Eq)

    错误 = kind + 源位置(ctx §1 契约三:位置信息直接透传)。

    XmlError::equal

    fn XmlError::equal(XmlError, XmlError) -> Bool

    XmlError::not_equal

    fn XmlError::not_equal(x : XmlError, y : XmlError) -> Bool

    XmlErrorKind

    pub(all) suberror XmlErrorKind {
    UnexpectedEof
    InvalidSyntax(String)
    UnmatchedTag(expected~ : String, found~ : String)
    InvalidAttribute(String)
    InvalidEntity(String)
    Encoding(String)
    IllegalChar(Int)
    IllegalName(String)
    Structure(String)
    External(String)
    } derive(Eq)

    解析错误面:词表对位底座 kind(规范拼写保留),结构自有。 [Structure] 为适配层新增——事件序/树结构错(底座已保 WF,此臂为防御纵深)。 [IllegalName]:名字段门用(树不带 span,零位报)。 [External]:外取面错误(取件被拒 / 取件失败 / 声明解析拒),与编解码 错误语义分立——它是编/解码之外的第三面:外取政策面。 施工账:ctx.md §6(役E1)· §11(役B2,R-B1a/R-G2)。

    XmlErrorKind::equal

    XmlErrorKind::not_equal

    fn XmlErrorKind::not_equal(x : XmlErrorKind, y : XmlErrorKind) -> Bool

    Attribute

    pub(all) struct Attribute {
    name : XmlName
    value : String
    } derive(Eq)

    属性(命名空间已解析;xmlns 声明在底座侧与普通属性分离, 适配层并回普通属性面——序列化即统一按属性发,再解析等价)。

    Attribute::equal

    fn Attribute::equal(Attribute, Attribute) -> Bool

    Attribute::not_equal

    fn Attribute::not_equal(x : Attribute, y : Attribute) -> Bool

    Document

    pub(all) struct Document {
    decl : XmlDecl?
    doctype : String?
    prologue : Array[Node]
    root : Element
    epilogue : Array[Node]
    } derive(Eq)

    文档 = 声明 + DOCTYPE + 根前杂项 + 根元素(恰一,适配层查)+ 根后杂项。DOCTYPE 仅名字(底座事件面只暴露名;内部子集在解析期 展开,不进树——往返在展开文本上等价)。

    Document::equal

    fn Document::equal(Document, Document) -> Bool

    Document::not_equal

    fn Document::not_equal(x : Document, y : Document) -> Bool

    Document::to_xml

    fn Document::to_xml(self : Document) -> String

    文档 → 规范 XML 串:声明/DOCTYPE 在前,根元素无子时自闭合, 文本与属性值转义,CDATA/注释/PI 原形发。 合同:对 parse 产物往返等价(字符串定点);手搭树的非法值不在此校验。

    Element

    pub(all) struct Element {
    name : XmlName
    attributes : Array[Attribute]
    children : Array[Node]
    } derive(Eq)

    元素:名字 + 属性 + 混合内容子节点。

    Element::equal

    fn Element::equal(Element, Element) -> Bool

    Element::not_equal

    fn Element::not_equal(x : Element, y : Element) -> Bool

    Node

    pub(all) enum Node {
    Element(Element)
    Text(String)
    CData(String)
    Comment(String)
    PI(target~ : String, data~ : String)
    } derive(Eq)

    树节点:元素 / 文本(实体已展开、相邻段已拼接,ctx §1 契约一)/ CDATA 分型保留 / 注释 / 处理指令。

    Node::equal

    fn Node::equal(Node, Node) -> Bool

    Node::not_equal

    fn Node::not_equal(x : Node, y : Node) -> Bool

    NsMode

    pub(all) enum NsMode {
    On
    Off
    } derive(Eq)

    命名空间模式:XML 1.0 基础规范与 Namespaces 是两个规范面——非 NS 消费 (RDF/XML 旧系、嵌入式)合法地接受首/尾冒号名、组合字符衔接段等 NCName 规则外的名字。 · On(缺省):命名空间感知——前缀解析 / xmlns 收集 / NCName 规则,由底座 NamespaceReader 承担; · Off:非 NS 面——名字按 Name 原文直通,xmlns 即普通属性,不解析前缀, 由底座 raw Reader 承担。 施工账:ctx.md §8(役N1,R-N1a)。

    NsMode::equal

    fn NsMode::equal(NsMode, NsMode) -> Bool

    NsMode::not_equal

    fn NsMode::not_equal(x : NsMode, y : NsMode) -> Bool

    SourcePosition

    pub(all) struct SourcePosition {
    offset : Int
    line : Int
    column : Int
    } derive(Eq)

    源位置(错误报告用;字段对位底座,翻译在适配层)。

    SourcePosition::equal

    SourcePosition::not_equal

    fn SourcePosition::not_equal(x : SourcePosition, y : SourcePosition) -> Bool

    SourceSpan

    pub(all) struct SourceSpan {
    start : SourcePosition
    end : SourcePosition
    } derive(Eq)

    SourceSpan::equal

    fn SourceSpan::equal(SourceSpan, SourceSpan) -> Bool

    SourceSpan::not_equal

    fn SourceSpan::not_equal(x : SourceSpan, y : SourceSpan) -> Bool

    XmlDecl

    pub(all) struct XmlDecl {
    version : String
    encoding : String?
    standalone : String?
    } derive(Eq)

    XML 声明 <?xml ... ?> 的载荷。

    XmlDecl::equal

    fn XmlDecl::equal(XmlDecl, XmlDecl) -> Bool

    XmlDecl::not_equal

    fn XmlDecl::not_equal(x : XmlDecl, y : XmlDecl) -> Bool

    XmlEdition

    pub(all) enum XmlEdition {
    Editions1to4
    Edition5
    } derive(Eq)

    XML 1.0 版本面(套件 EDITION 语义:测试按适用版次标注,处理器按面选子集)。

    XmlEdition::equal

    fn XmlEdition::equal(XmlEdition, XmlEdition) -> Bool

    XmlEdition::not_equal

    fn XmlEdition::not_equal(x : XmlEdition, y : XmlEdition) -> Bool

    XmlName

    pub(all) struct XmlName {
    qualified_name : String
    prefix : String?
    local_name : String
    namespace_uri : String?
    } derive(Eq)

    XML 名字(命名空间解析后的 infoset 形):qualified_name 为原文全名, prefix/local_name/namespace_uri 为拆解结果(无命名空间时后两者为 None)。

    XmlName::equal

    fn XmlName::equal(XmlName, XmlName) -> Bool

    XmlName::not_equal

    fn XmlName::not_equal(x : XmlName, y : XmlName) -> Bool

    decode_source

    fn decode_source(bytes : Bytes) -> Result[String, XmlError]

    编码前端 + 版本键控 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 面按字符计)。

    is_base_char_10

    fn is_base_char_10(code_point : Int) -> Bool

    is_combining_char_10

    fn is_combining_char_10(code_point : Int) -> Bool

    is_digit_10

    fn is_digit_10(code_point : Int) -> Bool

    is_extender_10

    fn is_extender_10(code_point : Int) -> Bool

    is_ideographic_10

    fn is_ideographic_10(code_point : Int) -> Bool

    is_name_char_11

    fn is_name_char_11(code_point : Int) -> Bool

    is_name_start_char_11

    fn is_name_start_char_11(code_point : Int) -> Bool

    parse

    fn parse(source : String) -> Result[Document, XmlError]

    解析入口:源文本 → 文档树(命名空间已解析,实体已展开; 错误面 = [XmlError],位置透传)。缺省 = XML 1.0 五版面(Edition5, 现行底座行为);版次敏感消费方用 [parse_edition]。

    parse_edition

    fn parse_edition(source : String, edition~ : XmlEdition, ns? : NsMode, resolver? : &EntityResolver?) -> Result[Document, XmlError]

    按版本面解析:树建后走名字段门。version="1.1" 文档恒走 1.1 紧凑表, 不受版本面选择影响;版本面只区分 1.0 文档。 施工账:ctx.md §6(役E1,R-E1c)。