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
    Download zip
    Version
    0.2.6
    License
    Apache-2.0
    Last updated
    3 days ago
    Downloads
    143

    Dependencies

    #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 {
    fn accept(Self, &XMLVisitor) -> Unit
    }

    XMLVisitor

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

    AttDef

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

    impl Show for AttDef

    AttDef::to_string

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

    AttType

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

    impl Show for AttType

    AttType::to_string

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

    AttlistDecl

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

    impl Show for AttlistDecl

    AttlistDecl::to_string

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

    ChildrenContentSpecOp

    pub(all) enum ChildrenContentSpecOp {
    Optional
    ZeroOrMore
    OneOrMore
    }

    ChildrenContentSpecOp::to_string

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

    ContentParticle

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

    ContentParticle::to_string

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

    ContentSpec

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

    impl Show for ContentSpec

    ContentSpec::to_string

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

    DTDStatement

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

    DTDStatement::to_string

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

    DefaultDecl

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

    impl Show for DefaultDecl

    DefaultDecl::to_string

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

    DocTypeDecl

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

    impl Show for DocTypeDecl

    DocTypeDecl::to_string

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

    ElementDecl

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

    impl Show for ElementDecl

    ElementDecl::to_string

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

    EntityDecl

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

    impl Show for EntityDecl

    EntityDecl::to_string

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

    EntityDef

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

    impl Show for EntityDef

    EntityDef::to_string

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

    ExternalID

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

    impl Show for ExternalID

    ExternalID::to_string

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

    GetElementVisitor

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

    Location

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

    impl Show for Location

    Location::to_string

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

    MarkUpDecl

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

    impl Show for MarkUpDecl

    MarkUpDecl::to_string

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

    Misc

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

    NotationDecl

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

    NotationDecl::to_string

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

    NotationDeclID

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

    NotationDeclID::to_string

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

    SingleContentParticle

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

    SingleContentParticle::to_string

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

    XMLChildren

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

    impl Show for XMLChildren

    XMLChildren::to_string

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

    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]

    XMLDocument::to_string

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

    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

    XMLElement::to_string

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

    XMLErrorKind

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

    XMLErrorKind::to_string

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

    XMLParseError

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

    XMLParseError::is_fatal

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

    XMLParseError::to_string

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

    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

    XMLParserContext::to_string

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

    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 = @XMLParser.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 = @XMLParser.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 = @XMLParser.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 = @XMLParser.pcdata().run(@combinator.Seq::from_string(input))
    debug_inspect(
    result,
    content=(
    #|Some(("Some <raw> XML & content"))
    ),
    )
    let input = "<![CDATA[]]>"
    let result = @XMLParser.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 = @XMLParser.pcharRef().run(@combinator.Seq::from_string("&#123;"))
    let hex = @XMLParser.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 = @XMLParser.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 = @XMLParser.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 = @XMLParser.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 = @XMLParser.pname().run(@combinator.Seq::from_string("element1"))
    debug_inspect(
    result1,
    content=(
    #|Some(("element1"))
    ),
    )

    // Invalid XML names (starting with digit)
    let result2 = @XMLParser.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 = @XMLParser.ppi().run(@combinator.Seq::from_string("<?php echo 'Hello' ?>"))
    debug_inspect(
    result,
    content=(
    #|Some(("php echo 'Hello' "))
    ),
    )
    let result = @XMLParser.ppi().run(@combinator.Seq::from_string("<??>"))
    debug_inspect(
    result,
    content=(
    #|Some((""))
    ),
    )
    let result = @XMLParser.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 = @XMLParser.preference().run(@combinator.Seq::from_string("&amp;"))
    debug_inspect(
    entity,
    content=(
    #|Some(("&amp;"))
    ),
    )

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

    // Character reference (hexadecimal)
    let hex = @XMLParser.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 = @XMLParser.ptext().run(@combinator.Seq::from_string("Hello World"))
    debug_inspect(
    result,
    content=(
    #|Some(("Hello World"))
    ),
    )
    let result = @XMLParser.ptext().run(@combinator.Seq::from_string("Text with & and <"))
    debug_inspect(
    result,
    content=(
    #|Some(("Text with ", & and <))
    ),
    ) // stops parsing at '&'
    let result = @XMLParser.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 = @XMLParser.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 = @XMLParser.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 = @XMLParser.xml_from_string(xml).unwrap()
    debug_inspect(
    doc.root.name,
    content=(
    #|"root"
    ),
    )
    }