xml

Minimal streaming XML pull-parser and writer

moon add marianoguerra/xml@0.2.0
Download zip
Version
0.2.0
License
Apache-2.0
Last updated
11 hours ago
Downloads
8
README

#marianoguerra/xml

A minimal, dependency-free streaming XML pull-parser and writer for MoonBit, in the spirit of Rust's quick-xml.

  • Reader: Event stream (Start, Empty, End, Text, CData, Comment, Eof) with predefined + numeric entity decoding. Malformed markup raises XmlError::Syntax with a code-unit offset.
  • Writer: escaping helpers (escape_text, escape_attr), CDATA section splitting, namespace-free element emission.

///|
test {
let r = @xml.Reader::new("<a k=\"v &amp; co\">text</a>")
let ev1 = try! r.next()
let ev2 = try! r.next()
assert_true(ev1 is Start(name="a", attrs=[{ key: "k", value: "v & co" }]))
assert_true(ev2 is Text("text"))
}

The parser intentionally does not validate DTDs or resolve external entities. Processing instructions and DOCTYPE declarations are skipped.

#
XmlError

pub(all) suberror XmlError {
Syntax(pos~ : Int, msg~ : String)
} derive(Eq,
Debug
)

Errors raised while scanning malformed XML.

#
Attribute

pub(all) struct Attribute {
key : String
value : String
} derive(Eq,
Debug
)

A single attribute of an XML start tag.

#
Document

pub struct Document {
root : Element
} derive(Eq,
Debug
)

A parsed XML document: its single root element.

The declaration, processing instructions, DOCTYPE and comments are not represented because the underlying reader skips them.

#
Document::read_from

fn Document::read_from(input : String) -> Document raise XmlError

Parse a complete XML document into a [Document] tree.

The input must contain exactly one root element; character data is only allowed inside it (top-level whitespace is ignored). A mismatched or unexpected tag raises [XmlError::Syntax].

Example

test {
let doc = @xml.Document::read_from("<a x=\"1\">hi</a>")
inspect(doc.root.name, content="a")
}

#
Document::to_xml

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

Serialize the document with an XML declaration.

#
Element

pub(all) struct Element {
name : String
attrs : Array[Attribute]
children : Array[Node]
} derive(Eq,
Debug
)

An element node: a qualified tag name, its attributes and its children in document order.

#
Element::attr

fn Element::attr(self : Element, key : String) -> String?

The value of the attribute named key, or None when absent.

#
Element::element

fn Element::element(self : Element, name : String) -> Element?

The first direct child element whose raw tag name equals name, or None when absent. Use [Element::elements] and [Element::local_name] for prefix-insensitive lookups.

#
Element::elements

fn Element::elements(self : Element) -> Array[Element]

The direct child elements, in document order.

#
Element::local_name

fn Element::local_name(self : Element) -> String

The local part of the qualified tag name: "itunes:author""author".

#
Element::new

fn Element::new(name~ : String, attrs? : Array[Attribute], children? : Array[Node]) -> Element

Create an element.

#
Element::text_content

fn Element::text_content(self : Element) -> String

The concatenated character data of all descendant Text and CData nodes, like DOM's textContent.

#
Element::to_xml

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

Serialize just this element (no XML declaration).

#
Event

pub(all) enum Event {
Start(name~ : String, attrs~ : Array[Attribute])
Empty(name~ : String, attrs~ : Array[Attribute])
End(String)
Text(String)
CData(String)
Comment(String)
Eof
} derive(Eq,
Debug
)

A streaming XML event.

#
Node

pub(all) enum Node {
Text(String)
CData(String)
Element(Element)
} derive(Eq,
Debug
)

A child node of an element.

#
Reader

pub struct Reader {
// private fields
} derive(
Debug
)

A pull-parser over an in-memory XML document.

The parser is lenient about constructs that do not affect RSS/Atom interpretation (processing instructions and DOCTYPE declarations are skipped) but strict about malformed tags, attributes and CDATA sections.

#
Reader::new

fn Reader::new(input : String) -> Reader

Create a reader over input. A leading BOM is skipped.

#
Reader::next

fn Reader::next(self : Reader) -> Event raise XmlError

Scan and return the next event.

#
Reader::position

fn Reader::position(self : Reader) -> Int

Current code-unit offset of the scanner (for diagnostics).

#
Writer

pub struct Writer {
// private fields
} derive(
Debug
)

A minimal streaming XML writer.

#
Writer::cdata

fn Writer::cdata(self : Writer, content : String) -> Unit

Write a CDATA section, splitting any ]]> occurrences so the section stays well-formed.

#
Writer::close

fn Writer::close(self : Writer, name : String) -> Unit

Write a closing tag </name>.

#
Writer::empty

fn Writer::empty(self : Writer, name : String, attrs? : Array[(String, String)]) -> Unit

Write a self-closing tag; attributes are escaped.

#
Writer::new

fn Writer::new() -> Writer

#
Writer::open

fn Writer::open(self : Writer, name : String) -> Unit

Write an opening tag <name>.

#
Writer::open_with

fn Writer::open_with(self : Writer, name : String, attrs~ : Array[(String, String)]) -> Unit

Write an opening tag with attributes, <name k="v" ...>. Attribute values are escaped.

#
Writer::raw

fn Writer::raw(self : Writer, content : String) -> Unit

Write pre-escaped / raw markup. The caller guarantees well-formedness.

#
Writer::text

fn Writer::text(self : Writer, content : String) -> Unit

Write escaped character data.

#
Writer::to_string

fn Writer::to_string(self : Writer) -> String

Return the accumulated document.

#
Writer::write_decl

fn Writer::write_decl(self : Writer) -> Unit

Write the XML declaration used by RSS/Atom output.

#
decode_entities

fn decode_entities(input : String) -> String

Decode predefined (&amp; &lt; &gt; &quot; &apos;) and numeric (&#NNN;, &#xHHH;) character references. Unknown or malformed references are left verbatim (lenient, mirroring quick-xml defaults).

#
escape_attr

fn escape_attr(s : String) -> String

Escape attribute-value content.

#
escape_text

fn escape_text(s : String) -> String

Escape text-node content.

#
local_name

fn local_name(qname : String) -> String

The local part of a qualified name: "itunes:author""author".

#
prefix_of

fn prefix_of(qname : String) -> String?

The prefix part of a qualified name, or None when unprefixed.

#
split_qname

fn split_qname(qname : String) -> (String?, String)

Split a qualified name into (prefix?, local). "itunes:author" becomes (Some("itunes"), "author"); "title" becomes (None, "title").

#
trim

fn trim(s : String) -> String

Trim ASCII whitespace from both ends.