A document-buffered pull XML parser for MoonBit
let xml = "<root><item id=\"1\">Hello</item></root>"
let reader = @xml.Reader::from_string(xml)
while true {
let event = reader.read_event()
match event.kind {
Start(elem) => println("Start: \{elem.name}")
End(name) => println("End: \{name}")
Text(content) => println("Text: \{content}")
Eof => break
_ => continue
}
}let reader = @xml.NamespaceReader::from_string(
"<p:root xmlns:p=\"urn:example\" p:id=\"1\"/>",
)
match reader.read_event().kind {
Empty(element) => {
println(element.name.local_name) // root
println(element.name.namespace_uri) // Some("urn:example")
}
_ => ()
}let input = "<root id='a&b'/>"
let reader = @xml.Reader::from_string(input)
let parsed = reader.read_event()
let authored = input[parsed.span.start.offset:parsed.span.end.offset]
assert_eq(authored, input)
guard parsed.kind is Empty(element) else { abort("expected empty element") }
assert_eq(element.attributes[0].value, "a&b")| Event | Description |
|---|---|
| Start(XmlElement) | Opening tag <name> |
| End(String) | Closing tag </name> |
| Empty(XmlElement) | Self-closing tag <name/> |
| Text(String) | Text content (entities decoded) |
| CData(String) | CDATA section <![CDATA[...]]> |
| Comment(String) | Comment <!-- ... --> |
| PI(target, data) | Processing instruction <?target data?> |
| Decl(version, encoding, standalone) | XML declaration |
| DocType(String) | DOCTYPE declaration |
| Eof | End of document |
| Category | Tests | Assertion |
|---|---|---|
| W3C accepted documents | 771 | Includes valid and DTD invalid documents; 246 also compare official canonical output |
| W3C malformed documents | 899 | Reject through the reader selected by namespace metadata |
| Unit and property tests | 152 | Raw events, reader, writer, escape, namespaces, source spans, resources, properties and mutation fuzzing |
moon test --target native --include-skipped --filter 'w3c/invalid/rmt-e3e-13'
# Currently fails: undefined entity reference &ent2;moon test --target all --deny-warnpython3 -m pip install -r scripts/requirements.txt
python3 scripts/generate_conformance_tests.py --download
python3 -m unittest discover -s scripts -p 'test_*.py'
# Check that committed tests and coverage match the pinned source:
python3 scripts/generate_conformance_tests.py --download --check| Reason | Entries |
|---|---|
| XML 1.1 / Namespaces 1.1 | 274 |
| Tests restricted to older XML editions | 310 |
| Requires external entity contents | 265 |
| Byte decoding / encoding-declaration tests outside the decoded String API | 57 |
| Optional errors with no required accept/reject result | 9 |
moon bench --target native --release --filter 'bench *'| Case | Baseline | After optimization |
|---|---|---|
| 1,024 character references | 801 µs | 375 µs |
| 4,096 character references | 11.55 ms | 1.57 ms |
| 16,384 character references | 228.67 ms | 6.30 ms |
| Namespace depth 256 | 7.02 ms | 916 µs |
| Namespace depth 1,024 | 26.68 ms | 1.50 ms |
| 4,096 children from nested entities | 345.00 ms | 8.79 ms |
| 2 MiB of text | 11.14 ms | 10.96 ms |
pub(all) enum EventKind {
Start(XmlElement)
End(String)
Empty(XmlElement)
Text(String)
CData(String)
Comment(String)
PI(target~ : String, data~ : String)
Decl(version~ : String, encoding~ : String?, standalone~ : String?)
DocType(String)
Eof
} derive(Eq, Debug)pub struct NamespaceAttribute {
name : XmlName
value : String
span : SourceSpan
name_span : SourceSpan
value_span : SourceSpan
} derive(Eq, Debug)pub struct NamespaceDeclaration {
prefix : String?
namespace_uri : String
span : SourceSpan
name_span : SourceSpan
value_span : SourceSpan
} derive(Eq, Debug)pub struct NamespaceElement {
name : XmlName
attributes : Array[NamespaceAttribute]
namespace_declarations : Array[NamespaceDeclaration]
} derive(Eq, Debug)pub(all) enum NamespaceEventKind {
Start(NamespaceElement)
End(XmlName)
Empty(NamespaceElement)
Text(String)
CData(String)
Comment(String)
PI(target~ : String, data~ : String)
Decl(version~ : String, encoding~ : String?, standalone~ : String?)
DocType(String)
Eof
} derive(Eq, Debug)fn NamespaceReader::read_events_until_eof(self : NamespaceReader) -> Array[NamespaceEvent] raise XmlErrorpub struct Reader {
input : Array[Char]
pos : Int
offset : Int
line : Int
column : Int
entities : Map[String, String]
entity_expansion_remaining : Ref[Int]
entity_stack : Array[String]
namespace_aware : Bool
attr_types : Map[String, String]
default_attributes : Map[String, Array[XmlAttribute]]
tag_stack : Array[(String, SourceSpan)]
seen_root : Bool
seen_doctype : Bool
root_closed : Bool
seen_content : Bool
just_saw_decl : Bool
pending_events : Array[Event]
pending_index : Int
internal_subset_events : Array[Event]
just_saw_doctype : Bool
in_entity_content : Bool
in_reference : Bool
}pub struct Writer {
buffer : StringBuilder
element_stack : Array[String]
seen_root : Bool
wrote_anything : Bool
doctype_root : String?
}fn Writer::empty_element(self : Writer, name : String, attributes : Array[(String, String)]) -> Unit raise WriterErrorfn Writer::start_element(self : Writer, name : String, attributes : Array[(String, String)]) -> Unit raise WriterErrorpub struct XmlAttribute {
name : String
value : String
span : SourceSpan
name_span : SourceSpan
value_span : SourceSpan
} derive(Eq, Debug)impl Show for XmlElementimpl Debug for XmlElementfn escape(text : String) -> StringInstall
Download zipA document-buffered pull XML parser for MoonBit