XMLParser

An XML parsing library written in MoonBit that converts XML strings into structured data, supporting elements, attributes, comments, CDATA, and processing instructions.

XML
parser
moon add moonbit-community/XMLParser@0.2.5
Download zip
Version
0.2.5
License
Apache-2.0
Last updated
3 months ago
Downloads
105

Dependencies

README

#XMLParser

XML Parser is an XML parsing library written in MoonBit. It can parse XML strings into structured data, supporting basic elements, attributes, comments, CDATA, processing instructions, etc., of XML documents. The library is implemented using parser combinators.

#Examples

let str =
#|<?xml version="1.0"?>
#|<root>
#| Text before
#| <child empty="true"/>
#| Text between
#| <?php echo "processing instruction" ?>
#| <![CDATA[Preserving <tags> in CDATA]]>
#| <!-- A comment here -->
#| <child>
#| Nested text
#| <grandchild />
#| More nested text
#| </child>
#| Text after
#|</root>
let xml = @parser.xml_from_string(str)
let xml = @parser.xml_from_iter(str.iter())
let (xml, ctx) = @parser.xml_from_string_with_ctx(str)
let xml = xml.unwrap()
println(xml)

#XML Conformance Test Suite

Some tests (e.g. xmlconf valid sa) require the W3C XML conformance test suite to be downloaded into ./resource/.

  • Unix/macOS: bash scripts/init.sh
  • Windows (PowerShell): ./scripts/init.ps1

Then run moon test.

#
XMLNode

pub(open) trait XMLNode {
accept(Self, &XMLVisitor) -> Unit
}

#
XMLVisitor

pub(open) trait XMLVisitor {
visit(Self, XMLElement) -> Unit
}

#
AttDef

pub(all) struct AttDef {
name : String
att_type : AttType
default_decl : DefaultDecl
}

impl Show for AttDef

#
AttType

pub(all) enum AttType {
StringType(String)
TokenizedType(String)
NotationType(Array[String])
Enumeration(Array[String])
}

impl Show for AttType

#
AttlistDecl

pub(all) struct AttlistDecl {
name : String
att_defs : Array[AttDef]
}

impl Show for AttlistDecl

#
ChildrenContentSpecOp

pub(all) enum ChildrenContentSpecOp {
Optional
ZeroOrMore
OneOrMore
}

#
ContentParticle

pub(all) struct ContentParticle(SingleContentParticle, ChildrenContentSpecOp?)

#
ContentSpec

pub(all) enum ContentSpec {
EMPTY
ANY
Mixed(Array[String])
Children(ContentParticle)
}

impl Show for ContentSpec

#
DTDStatement

pub(all) enum DTDStatement {
Decl(MarkUpDecl)
Sep(String)
}

#
DefaultDecl

pub(all) enum DefaultDecl {
Required
Implied
Fixed(String)
Value(String)
}

impl Show for DefaultDecl

#
DocTypeDecl

pub(all) struct DocTypeDecl {
name : String
externalID : ExternalID?
intSubset : Array[DTDStatement]?
}

impl Show for DocTypeDecl

#
ElementDecl

pub(all) struct ElementDecl {
name : String
content_spec : ContentSpec
}

impl Show for ElementDecl

#
EntityDecl

pub(all) enum EntityDecl {
GEDecl(String, EntityDef)
PEDecl(String, EntityDef)
}

impl Show for EntityDecl

#
EntityDef

pub(all) enum EntityDef {
EntityValue(String)
GExternalID(ExternalID, String?)
PExternalID(ExternalID)
}

impl Show for EntityDef

#
ExternalID

pub(all) enum ExternalID {
System(String)
Public(String, String)
}

impl Show for ExternalID

#
GetElementVisitor

pub(all) struct GetElementVisitor {
name : String
elements : Array[XMLElement]
}

#
Location

pub(all) struct Location {
index : Int
line : Int
column : Int
length : Int
}

impl Show for Location

#
MarkUpDecl

pub(all) enum MarkUpDecl {
ElementDecl(ElementDecl)
AttListDecl(AttlistDecl)
EntityDecl(EntityDecl)
NotationDecl(NotationDecl)
PI(String)
Comment(String)
}

impl Show for MarkUpDecl

#
Misc

pub(all) enum Misc {
Comment(String)
PI(String)
WhiteSpace(String)
}

#
NotationDecl

pub(all) struct NotationDecl {
name : String
id : NotationDeclID
}

#
NotationDeclID

pub(all) enum NotationDeclID {
PublicID(String)
ExternalID(ExternalID)
}

#
SingleContentParticle

pub(all) enum SingleContentParticle {
Name(String)
Seq(Array[ContentParticle])
Choice(Array[ContentParticle])
}

#
XMLChildren

pub(all) enum XMLChildren {
Element(XMLElement)
Reference(String)
CDATA(String)
PI(String)
Comment(String)
Text(String)
WhiteSpace(String)
}

impl Show for XMLChildren

#
XMLDocument

pub(all) struct XMLDocument {
version : String
encoding : String
standalone : Bool
dtd : DocTypeDecl?
root : XMLElement
}

impl Show for XMLDocument

#
XMLDocument::get_element_by_name

fn XMLDocument::get_element_by_name(self : XMLDocument, name : String) -> Array[XMLElement]

#
XMLElement

pub(all) struct XMLElement {
name : String
empty_element : Bool
attributes : Map[String, String]
children : Array[XMLChildren]
}

impl Show for XMLElement

#
XMLElement::get_attribute

fn XMLElement::get_attribute(self : XMLElement, attr : String) -> String?

#
XMLElement::get_children

fn XMLElement::get_children(self : XMLElement) -> Array[XMLChildren]

#
XMLElement::get_text

fn XMLElement::get_text(self : XMLElement) -> String

#
XMLErrorKind

pub(all) enum XMLErrorKind {
SyntaxError
ValidationError
EncodingError
MalformedReference
MismatchedTags(String, String)
InternalParserError
}

#
XMLParseError

pub(all) struct XMLParseError {
kind : XMLErrorKind
message : String
location : Location
}

#
XMLParseError::is_fatal

fn XMLParseError::is_fatal(self : XMLParseError) -> Bool

#
XMLParser

接收Seq[Token]和XMLParserContext,返回((Value, Seq[Token])?, XMLParseError, XMLParserContext) 这里我们考虑单个Parser最多产生一个Error,同时Parser可能在遇到错误后继续解析(例如dtd检查不通过时)。 XMLParser需要在遇到格式错误时立刻停止解析。

#
XMLParser::and_then

fn[Token, A, B] XMLParser::and_then(self : XMLParser[Token, A], other : XMLParser[Token, B]) -> XMLParser[Token, (A, B)]

#
XMLParser::from_ref

fn[Token, A] XMLParser::from_ref(self :
Ref
[XMLParser[Token, A]]) -> XMLParser[Token, A]

#
XMLParser::map

fn[Token, A, B] XMLParser::map(self : XMLParser[Token, A], f : (A) -> B) -> XMLParser[Token, B]

#
XMLParser::omit_first

fn[Token, A, B] XMLParser::omit_first(self : XMLParser[Token, (A, B)]) -> XMLParser[Token, B]

#
XMLParser::omit_second

fn[Token, A, B] XMLParser::omit_second(self : XMLParser[Token, (A, B)]) -> XMLParser[Token, A]

#
XMLParser::optional

fn[Token, A] XMLParser::optional(self : XMLParser[Token, A]) -> XMLParser[Token, A?]

#
XMLParser::or_else

fn[Token, A] XMLParser::or_else(self : XMLParser[Token, A], other : XMLParser[Token, A]) -> XMLParser[Token, A]

#
XMLParser::repeat_with_sep

fn[Token, A, B] XMLParser::repeat_with_sep(self : XMLParser[Token, A], sep : XMLParser[Token, B]) -> XMLParser[Token, Array[A]]

#
XMLParser::run

#
XMLParserContext

pub struct XMLParserContext {
input_array : Array[Char]
index : Int
line_number : Int
column_number : Int
errors : Array[XMLParseError]
warnings : Array[String]
}

在解析时只维护index, line_number和column_number暂时只在最后更新

#
XMLParserContext::merge

fn XMLParserContext::merge(self : XMLParserContext, child : XMLParserContext) -> Unit

#
XMLParserContext::raise_error

fn XMLParserContext::raise_error(self : XMLParserContext, error : XMLParseError) -> Unit

#
pEntityRef

fn pEntityRef() ->
Parser
[Char, String]

Parses an XML entity reference of the form &name;, where name is a valid XML name.

Returns a Parser that accepts a sequence of characters and produces a String containing the complete entity reference. For example, parsing &lt; will return "&lt;".

Example:

test {
let result = pEntityRef().run(@combinator.Seq::from_string("&lt;"))
debug_inspect(
result,
content=(
#|Some(("&lt;"))
),
)
}

#
pEntityRef_with_ctx

fn pEntityRef_with_ctx() ->
Parser
[Char, (String?, XMLParserContext)]

#
pattribute

fn pattribute() ->
Parser
[Char, (String, String)]

Parses an XML attribute, which consists of a name followed by an equals sign and a quoted string value.

Parameters:

  • input : A sequence of characters representing the XML attribute to be parsed. The sequence should start with a valid XML name followed by "=" and a double-quoted string value.

Returns a Parser[Char, (String, String)] that, when applied to an input sequence:

  • Succeeds with a tuple containing the attribute name and value if parsing is successful
  • Fails if the input does not match the expected XML attribute format

Example:

test {
let result = pattribute().run(@combinator.Seq::from_string("name=\"value\""))
debug_inspect(
result,
content=(
#|Some((("name", "value")))
),
)
}

#
pattribute_with_ctx

fn pattribute_with_ctx() ->
Parser
[Char, ((String, String)?, XMLParserContext)]

#
pattributes

fn pattributes() ->
Parser
[Char, Map[String, String]]

Parses a sequence of XML attributes, each followed by optional whitespace. Combines all parsed attributes into a map where keys are attribute names and values are corresponding attribute values.

Returns a parser that produces a map from attribute names to their values. The parser succeeds even if no attributes are present, returning an empty map.

Example:

test {
let result = pattributes().run(
@combinator.Seq::from_string(" name=\"value\" type=\"text\""),
)
let attrs = result.unwrap().0
debug_inspect(attrs.get("name"), content="Some(\"value\")")
debug_inspect(attrs.get("type"), content="Some(\"text\")")
}

#
pattributes_with_ctx

fn pattributes_with_ctx() ->
Parser
[Char, (Map[String, String]?, XMLParserContext)]

#
pcdata

Parses a CDATA section in XML, which allows text containing characters that would otherwise need to be escaped. A CDATA section starts with "". The content between these markers is treated as plain text, not XML markup.

Parameters:

  • seq : The input sequence of characters to be parsed. Expected to start with a CDATA section.

Returns a parser that produces the content of the CDATA section as a string, excluding the CDATA markers.

Example:

test {
let input = "<![CDATA[Some <raw> XML & content]]>"
let result = pcdata().run(@combinator.Seq::from_string(input))
debug_inspect(
result,
content=(
#|Some(("Some <raw> XML & content"))
),
)
let input = "<![CDATA[]]>"
let result = pcdata().run(@combinator.Seq::from_string(input))
debug_inspect(
result,
content=(
#|Some((""))
),
)
}

#
pcdata_with_ctx

#
pcharRef

Parses a character reference in XML, which can be either a decimal reference (&#d;) or a hexadecimal reference (&#xh;) where 'd' is a sequence of decimal digits and 'h' is a sequence of hexadecimal digits.

Returns a Parser[Char, String] that, when applied to an input sequence:

  • For decimal references, matches patterns like "{"
  • For hexadecimal references, matches patterns like "¥"
  • Returns the matched reference as a string, including the delimiters

Example:

test {
let decimal = pcharRef().run(@combinator.Seq::from_string("&#123;"))
let hex = pcharRef().run(@combinator.Seq::from_string("&#xA5;"))
debug_inspect(
decimal,
content=(
#|Some(("#123;"))
),
)
debug_inspect(
hex,
content=(
#|Some(("#A5;"))
),
)
}

#
pcharRef_with_ctx

fn pcharRef_with_ctx() ->
Parser
[Char, (String?, XMLParserContext)]

#
pcomment

Parses an XML comment. A comment in XML starts with "". The parser captures all characters between these delimiters, excluding the delimiters themselves.

Parameters:

  • seq : A sequence of characters representing the XML content to be parsed. The sequence should start with an XML comment.

Returns a parser that produces a string containing the comment content when successful, or fails if the input is not a valid XML comment.

Example:

test {
let comment = "<!-- This is a comment -->"
let result = pcomment().run(@combinator.Seq::from_string(comment))
debug_inspect(
result,
content=(
#|Some((" This is a comment "))
),
)
}

#
pcomment_with_ctx

fn pcomment_with_ctx() ->
Parser
[Char, (String?, XMLParserContext)]

#
pconst

fn[Token, A] pconst(a : A) -> XMLParser[Token, A]

#
pdtd

Parses a Document Type Definition (DTD) declaration in an XML document. Starts with "<!" and ends with ">", capturing all characters in between except for the closing angle bracket.

Returns a parser that, when applied to an input sequence:

  • Succeeds with the contents of the DTD declaration as a string, excluding the opening "<!" and closing ">" delimiters
  • Fails if the input doesn't match the expected DTD format

Example:

test {
let result = pdtd()
.run(@combinator.Seq::from_string("<!DOCTYPE html>"))
.unwrap().0
debug_inspect(result, content="<!DOCTYPE html>")
}

#
pdtd_with_ctx

#
pelement

Parses an XML element, which can be either an empty element (e.g., <br/>) or an element with content (e.g., <div>...</div>). The parser handles nested elements, text content, CDATA sections, processing instructions, comments, and entity references.

Parameters:

  • input : A sequence of characters representing an XML element. The sequence should start with an opening tag (e.g., <tag>) and end with either a self-closing tag (e.g., />) or a matching closing tag (e.g., </tag>).

Returns a parser that produces an XMLElement structure containing:

  • The element's name
  • A map of attribute names to their values
  • A queue of child nodes (XMLChildren)

Example:

test {
// Parse a simple XML element with attributes and text content
let input = "<user id=\"1\" name=\"John\">Hello, World!</user>"
let result = pelement().run(@combinator.Seq::from_string(input))
let element = result.unwrap().0
debug_inspect(
element.name,
content=(
#|"user"
),
)
debug_inspect(element.attributes.get("id"), content="Some(\"1\")")
debug_inspect(element.attributes.get("name"), content="Some(\"John\")")
}

#
pelement_with_ctx

#
pname

Parses an XML name according to the XML 1.0 specification. An XML name must start with a name start character (letter, underscore, or colon) followed by zero or more name characters (letters, digits, dots, hyphens, underscores, or colons).

Returns a parser that, when successful, produces a string containing the parsed XML name.

Example:

test {
// Valid XML names
let result1 = pname().run(@combinator.Seq::from_string("element1"))
debug_inspect(
result1,
content=(
#|Some(("element1"))
),
)

// Invalid XML names (starting with digit)
let result2 = pname().run(@combinator.Seq::from_string("1element"))
debug_inspect(result2, content="None")
}

#
pname_with_ctx

#
ppi

Parses an XML processing instruction (PI) from a sequence of characters. A processing instruction begins with "", and ends with "?>".

Returns a parser that, when applied to an input sequence:

  • Succeeds with the content of the processing instruction (excluding the "" delimiters) if a valid PI is found
  • Fails if the input does not start with a valid processing instruction

Example:

test {
let result = ppi().run(@combinator.Seq::from_string("<?php echo 'Hello' ?>"))
debug_inspect(
result,
content=(
#|Some(("php echo 'Hello' "))
),
)
let result = ppi().run(@combinator.Seq::from_string("<??>"))
debug_inspect(
result,
content=(
#|Some((""))
),
)
let result = ppi().run(@combinator.Seq::from_string("<?incomplete"))
debug_inspect(result, content="None")
}
TODO PI ::= '' Char*)))? '?>' PITarget ::= Name - (('X' | 'x') ('M' | 'm') ('L' | 'l'))

#
ppiTarget

#
ppi_with_ctx

#
pprolog

fn pprolog() ->
Parser
[Char, Map[String, String]]

Parses an XML prolog declaration, which appears at the beginning of an XML document. The prolog typically contains information about XML version, encoding, and standalone status.

Returns a parser that, when applied to an input sequence:

  • Succeeds with a map containing prolog attributes if the input starts with a valid XML prolog
  • Fails if the input does not match the XML prolog syntax: <?xmlattr1="value1" attr2="value2"?>

Example:Have let input = "" let result = pprolog().run(Seq::from_string(input)) let attrs = result.unwrap().0 debug_inspect(attrs.get("version"), content="Some("1.0")") debug_inspect(attrs.get("encoding"), content="Some("UTF-8")")

#
pprolog_with_ctx

fn pprolog_with_ctx() ->
Parser
[Char, (Map[String, String]?, XMLParserContext)]

#
preference

fn preference() ->
Parser
[Char, String]

Parses an XML reference, which can be either an entity reference (like &amp;) or a character reference (like &#x20; or &#32;).

Returns a parser that succeeds with the reference string if the input matches either an entity reference or a character reference pattern, and fails otherwise.

Example:

test {
// Entity reference
let entity = preference().run(@combinator.Seq::from_string("&amp;"))
debug_inspect(
entity,
content=(
#|Some(("&amp;"))
),
)

// Character reference (decimal)
let decimal = preference().run(@combinator.Seq::from_string("&#32;"))
debug_inspect(
decimal,
content=(
#|Some(("#32;"))
),
)

// Character reference (hexadecimal)
let hex = preference().run(@combinator.Seq::from_string("&#x20;"))
debug_inspect(
hex,
content=(
#|Some(("#20;"))
),
)
}

#
preference_with_ctx

fn preference_with_ctx() ->
Parser
[Char, (String?, XMLParserContext)]

#
ptext

Parses XML text content excluding special characters ('<' and '&').

Parameters:

  • input : A sequence of characters to be parsed. Should not contain any instances of '<' or '&'.

Returns a Parser[Char, String] that, when applied to an input sequence:

  • Succeeds and produces a String representing the concatenated characters that are not '<' or '&'.
  • Fails if any character in the sequence is '<' or '&'.

Example:

test {
let result = ptext().run(@combinator.Seq::from_string("Hello World"))
debug_inspect(
result,
content=(
#|Some(("Hello World"))
),
)
let result = ptext().run(@combinator.Seq::from_string("Text with & and <"))
debug_inspect(
result,
content=(
#|Some(("Text with ", & and <))
),
) // stops parsing at '&'
let result = ptext().run(@combinator.Seq::from_string(""))
debug_inspect(
result,
content=(
#|Some((""))
),
)
}

#
ptext_with_ctx

#
pwhite_space

fn pwhite_space() ->
Parser
[Char, String]

Parses a sequence of whitespace characters (space, tab, carriage return, line feed) into a string.

Returns a parser that, when applied to an input sequence:

  • Consumes zero or more whitespace characters.
  • Concatenates all consumed whitespace characters into a single string.
  • Returns the concatenated string.

Example:

test {
let result = pwhite_space().run(@combinator.Seq::from_string(" \t\n\rtext"))
debug_inspect(
result,
content=(
#|Some((" \t\n\r", text))
),
)
}

#
pwhite_space_with_ctx

fn pwhite_space_with_ctx() ->
Parser
[Char, (String?, XMLParserContext)]

#
pxml

Parses a complete XML document string into an XMLDocument structure. Handles XML prolog, DOCTYPE declaration (DTD), and the root element with its contents. Supports whitespace between major components.

Parameters:

  • seq : A sequence of characters representing the XML document to be parsed. Should contain optional XML prolog, optional DOCTYPE declaration, and exactly one root element.

Returns a Parser[Char, XMLDocument] that, when applied to an input sequence:

  • Succeeds with an XMLDocument containing version (defaults to "1.0"), encoding (defaults to "UTF-8"), standalone flag, and the parsed root element.
  • Fails if the input is not a well-formed XML document.

Example:

test {
let xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><root><child>Text</child></root>"
let result = pxml().run(@combinator.Seq::from_string(xml))
let doc = result.unwrap().0
debug_inspect(
doc.version,
content=(
#|"1.0"
),
)
debug_inspect(
doc.root.name,
content=(
#|"root"
),
)
}

#
xml_from_iter

fn xml_from_iter(iter : Iter[Char]) -> XMLDocument?

This function is slower than xml_from_string. Have O(n^2) complexity.

#
xml_from_string

fn xml_from_string(s : String) -> XMLDocument?

Parses an XML string and converts it into an XMLDocument structure. The input string should contain a well-formed XML document with a single root element.

Parameters:

  • xml_string : A string containing the XML document to be parsed.

Returns an Option type containing the parsed XMLDocument if successful, or None if the parsing fails due to invalid XML syntax.

Example:

test {
let xml = "<?xml version=\"1.0\"?><root><child>Content</child></root>"
let doc = xml_from_string(xml).unwrap()
debug_inspect(
doc.root.name,
content=(
#|"root"
),
)
}