moonxml

    HTML, XML and SVG for MoonBit: parse, query and edit.

    html
    xml
    xpath
    Download zip
    Version
    0.1.1
    License
    Apache-2.0
    Last updated
    3 hours ago
    Downloads
    11

    No dependencies

    #<moonxml/>

    HTML, XML and SVG for MoonBit — one mutable tree: parse it, query it by CSS selector or XPath 1.0, edit it in place, write it back.

    CI pages license


    let page = @html.loads("<ul id=list><li>coffee<li>tea</ul>")

    page.css("li").length() // 2 — the document closed neither
    page.xpath("//li[2]").text() // "tea"
    page.css("li").last().add_class("done")
    page.dumps()

    HTML is read the way a browser reads it; XML is read strictly, as XML 1.0 requires; both become the same node kinds, so a query written for a page works on a feed.

    #Why this shape

    One tree, two syntaxesNode is element, text, CDATA, comment, processing instruction or doctype. Nothing about a node says which parser made it.
    One chainEvery query answers a Selection, so css → xpath → selector → custom → items[i] → children()[j] → css again never changes type.
    Two engines, and yoursCSS Selectors 4's statically decidable subset, all of XPath 1.0, and a Selector trait you implement and register by name.
    Editing, not re-parsingA full set of editing verbs on the same selection the query returned, with parent pointers kept true.
    Two ways to readBuild the tree, or stream Events into your own Sink and build nothing. The tree builder is itself a sink: one parser, no drift.
    One error familyRefused: malformed input names its offset, a bad selector names its fault. HTML never refuses — it repairs, and tells you what it repaired.

    #Install

    moon add moonbitstack/moonxml

    import {
    "moonbitstack/moonxml", // tree, chain, engines, writer
    "moonbitstack/moonxml/html", // @html.loads
    "moonbitstack/moonxml/xml", // @xml.loads
    "moonbitstack/moonxml/event", // Event, Sink
    "moonbitstack/moonxml/htmx", // @htmx.Hx — htmx's attributes, read and carried out
    }

    Two dependencies: moonbitlang/core, and nothing else.

    #The chain

    page.xpath("//table") // any engine
    .css("tr:nth-child(2)") // any other
    .selector("mine", "") // one you registered
    .custom(@moonxml.SelectorFn::new(f)) // one you did not
    .items[0] // a Node
    .children()[1] // still going
    .css("span")
    .set_text("done")

    Results arrive in document order, deduplicated by identity — two <li>coffee</li> are different nodes and both are kept — and an empty selection stays empty rather than widening to the document.

    Where XPath answers something that is not a node-set, xpath_value answers it typed: Nodes, Str, Num or Flag.

    Every selector the engine implements, run against one document — the query, the nodes it answered, and the text or attribute taken out of them — is the selector board. It is generated by running src/examples/selectors, so it cannot claim a selector works that the engine does not have.

    #The interface

    Four types, and every method sits on the type it acts on: Selection is a node list, Element is one node, Node is any node, Document is the container the tree hangs from. Query methods exist on all four, so a chain never has to stop and re-wrap.

    #Document

    readroot document_element() flavour() repairs()
    searchcss(q) xpath(q) xpath_value(q) selector(name, q) custom(sel) by_id(v) by_class(v) by_tag(v) by_name(v)
    contenttext() html() children() descendants() dumps() dump() dumps_html() dump_html() events()
    changeappend(node) prepend(node) clear() smooth() register(name, sel) push(event)

    A document's root is a container, not a node of the document, so the verbs that act on one node among siblings are deliberately not forwarded to Document: remove, replace_with, wrap, unwrap, parent, first, last, eq, length, attr, set_attr, remove_attr, add_class, remove_class, filter, closest, set_text. Each would answer for the container itself — remove the document from the document, ask what a document is named — which is not an operation a caller can mean. They stay on Selection and Element, where they name something real.

    #Selection

    searchcss xpath xpath_value selector custom by_id by_class by_tag by_name filter(f) closest(q)
    narrowfirst() last() eq(i) children() parent() descendants() items[i]
    readlength() text() html() attr(name)
    changeappend prepend remove replace_with unwrap wrap clear set_text set_attr remove_attr add_class remove_class smooth

    #Element

    fieldsname attrs children parent closed
    structureappend prepend insert(at, node) insert_before insert_after replace_with remove unwrap wrap clear smooth
    attributesattr has_attr set_attr remove_attr classes has_class add_class remove_class
    contenttext() set_text(s) html() walk(f) document()

    #Node

    name() is_element() is_text() text() children(), plus the query methods above. A node that is not an element answers an empty selection rather than refusing, so .items[0].css("a") never needs a guard.

    Selection-level edits apply to every matched element; the verbs that need a position (insert, insert_before, insert_after) live on Element, where a position means one thing.

    #Parsers

    HTML — WHATWG's tokenizer and tree construction, whole: implied html/head/body/tbody, cells that close themselves, foster parenting in front of tables, formatting reopened after a block closed it, the adoption agency for misnested markup, character references with the Windows-1252 repair. Foreign content is parsed as foreign: <svg> and <math> carry their namespaces, their tags and attributes are spelled the way SVG and MathML spell them, and the integration points (foreignObject, desc, title, mi, mtext, annotation-xml) are where HTML resumes. A <template>'s contents live in a fragment of their own, on element.content(), so what is inside a template is inert until someone puts it in the document. Framesets are documents too. What the parser fixed is countable afterwards on document.repairs().

    XML — XML 1.0 with Namespaces 1.0, strict: a document reads whole or is refused with the offset of the fault. Names become {uri}local, and writing back reproduces attribute order, CDATA, comments, processing instructions, the doctype with its internal subset, and <a/> versus <a></a>.

    The internal subset is read: entity declarations, general and parameter, nested and with recursion refused; attribute defaults and their types; the content models that decide whether a declaration is well-formed. Nothing is fetched — a document whose entities live in a file beside it is read without them, and document.unresolved() names what was skipped, as XML 1.0 asks a non-validating processor to. standalone="yes" turns that gap into a fault, because the document said it needed nothing from outside. Expansion is bounded by entity_limit, a million characters by default, so a document built to expand for ever is refused instead of read.

    Fragments — @html.loads_fragment(text, context="div") reads markup the way innerHTML does, inside the element named as the context; svg desc and math mi name a foreign context. @xml.loads_fragment(text) reads a snippet that has no single root.

    Streaming — @xml.scan(text, sink) emits Events to any Sink; document.events() reads a finished tree back as the same stream.

    #Hypermedia

    moonxml/htmx reads the attributes htmx defines and carries them out: when to ask, what to ask, and where the answer goes.

    let page = @html.loads(
    "<form hx-post='/todos' hx-target='#list' hx-swap='beforeend'>" +
    "<input name='title' value='write it down'><button>Add</button></form>" +
    "<ul id='list'></ul>")

    let request = @htmx.Hx::of(page.css("form").items[0]).request("submit").unwrap()

    request.address() // "/todos"
    request.query() // "title=write+it+down"
    request.headers() // [("HX-Request", "true"), ("HX-Target", "list")]

    request.answer("<li>a second one</li>") // and the list has it

    The verbs (hx-get, hx-post, hx-put, hx-patch, hx-delete), hx-trigger with once, changed, delay:, throttle:, from:, queue: and every; hx-target with this, closest, find, next, previous and bare selectors; hx-swap with its seven positions and its timing; hx-vals, hx-headers, hx-include, hx-params, hx-select, hx-confirm, hx-indicator, hx-push-url, hx-boost and hx-swap-oob. Attributes inherit down the tree, and hx-disinherit and unset stop them, as htmx does.

    What it does not do is the two things a library cannot: send the request, and wait for the answer. It describes the first and applies the second.

    #Examples

    src/examples/tourthe whole surface in one run: moon run src/examples/tour
    src/examples/hypermediaa page that declares its requests: read it, make one, swap the answer in
    src/examples/selectorsevery built-in selector against one document — the run behind the selector board
    src/examples/scrapea page in, a table out: query, filter, reshape, serialize
    src/examples/rewriteread a feed, edit every entry, write it back byte-faithfully

    #What is not here

    document.write, scripting-enabled branches, and the CSS pseudo-classes that need a live browser (:hover, :visited) — none of those can be decided from a document alone, so each is a named answer rather than a silent miss. XSLT, DTD validation and XPath 2.0 are transformations and languages of their own, not features of a tree library. The same goes for htmx's ws: and sse: extensions, hx-on handlers and history cache: they need an event loop and a connection, so moonxml/htmx stops at the request and the swap.

    #Licence

    Apache-2.0. See LICENSE. Test fixtures generated from html5lib-tests (MIT), libxml2's XPath tests (MIT) and the W3C CSS3 Selectors Test Suite in web-platform-tests (BSD-3-Clause).

    Selector

    pub(open) trait Selector {
    fn select(Self, ctx : Selection, query : String) -> Selection raise Refused
    }

    One query language plugged into the chain. Implement this to add your own, register it with Document::register, and run it with selector(name, query) — or pass it straight to custom and skip the registry entirely.

    struct DeepLinks {} impl Selector for DeepLinks with select(_self, ctx, _query) { ctx.css("a[href]").filter(fn(node) { node is ElementNode(e) && e.attr("href").has_prefix("https://") }) }

    Refused

    pub(all) suberror Refused {
    Malformed(at~ : Int, why~ : String)
    BadQuery(at~ : Int, why~ : String)
    UnknownSelector(name~ : String)
    Unsupported(what~ : String)
    Exceeded(at~ : Int, limit~ : Int, got~ : Int)
    } derive(Eq,
    Debug
    )

    Why a document could not be read, or a query could not be run. One family for the whole library, so there is one thing to catch.
    impl Show for Refused

    Refused::equal

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

    Refused::not_equal

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

    Refused::output

    fn Refused::output(self : Refused, logger : &Logger) -> Unit

    Refused::to_repr

    Refused::to_string

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

    Document

    pub struct Document {
    root : Element
    // private fields
    }

    A document: the virtual root (named #document) holding the top-level nodes, the flavour it was read as, the selector registry, and — for HTML — the tally of what the parser repaired.
    impl Sink for Document

    Document::append

    fn Document::append(self : Document, node : Node) -> Unit

    Add a node at the end of the document: where a comment, a processing instruction or a second document element would go. The usual use is a doctype written before the root, which is what prepend is for.

    Document::by_class

    fn Document::by_class(self : Document, name : String) -> Selection

    The elements carrying the class name.

    Document::by_id

    fn Document::by_id(self : Document, id : String) -> Selection

    The elements whose id attribute is id — getElementById, over the whole document.

    Document::by_name

    fn Document::by_name(self : Document, name : String) -> Selection

    The elements whose name attribute is name.

    Document::by_tag

    fn Document::by_tag(self : Document, name : String) -> Selection

    The elements named name; * answers every element.

    Document::children

    fn Document::children(self : Document) -> Selection

    The document's top-level nodes — the doctype, comments and processing instructions beside the document element, in document order.

    Document::clear

    fn Document::clear(self : Document) -> Unit

    Empty the document: every top-level node goes, and the document is then what a freshly created one is.

    Document::css

    fn Document::css(self : Document, query : String) -> Selection raise Refused

    Narrow with a CSS selector over the whole document. Descendant search from the virtual root, so every element is a candidate.

    Document::custom

    fn Document::custom(self : Document, sel : &Selector) -> Selection raise Refused

    Run a selector that was never registered.

    Document::descendants

    fn Document::descendants(self : Document) -> Selection

    Every node in the document, in document order.

    Document::document_element

    fn Document::document_element(self : Document) -> Element?

    The document element — the root's first element child, documentElement in the DOM, getroot() in lxml.

    Document::dump

    fn Document::dump(self : Document) -> Bytes

    A whole document as bytes, in the flavour it was read as.

    Document::dump_html

    fn Document::dump_html(self : Document) -> Bytes

    A whole document as bytes, by HTML's rules.

    Document::dumps

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

    A whole document, in the flavour it was read as.

    Document::dumps_html

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

    A whole document, HTML rules whatever it was read as.

    Document::events

    Read a finished tree back as the event stream it would have produced.

    Document::flavour

    fn Document::flavour(self : Document) -> Flavour

    The flavour this document writes itself back in.

    Document::html

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

    The whole document as markup, in the flavour it was read as.

    Document::new

    fn Document::new(flavour? : Flavour) -> Document

    A fresh document. A tree built by hand has no syntax it was read from, so it writes back with XML's exact rules; ask for flavour=Html when the tree came from HTML.

    Document::prepend

    fn Document::prepend(self : Document, node : Node) -> Unit

    Add a node before everything else in the document.

    Document::push

    fn Document::push(self : Document, event :
    Event
    ) -> Unit

    Document::register

    fn Document::register(self : Document, name : String, sel : &Selector) -> Unit

    Register a selector under a name. Re-registering a name replaces what was there, built-in or not — and a built-in stays reachable through its own method, which calls the engine directly and never consults the registry.

    Document::repairs

    fn Document::repairs(self : Document) -> Repairs

    What the HTML parser repaired; a tree that was not parsed as HTML has an empty tally.

    Document::selector

    fn Document::selector(self : Document, name : String, query : String) -> Selection raise Refused

    Run the selector registered under name, built-in or the caller's own.

    Document::set_repairs

    fn Document::set_repairs(self : Document, repairs : Repairs) -> Unit

    Record what a parser repaired; the parsers call this when they are done.

    Document::set_unresolved

    fn Document::set_unresolved(self : Document, names : Array[String]) -> Unit

    Record the external entities a reader skipped; the XML parser calls this.

    Document::smooth

    fn Document::smooth(self : Document) -> Unit

    Fuse adjacent text nodes everywhere in the document, which is what a writer wants after editing several places.

    Document::text

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

    The text of the whole document, concatenated.

    Document::unresolved

    fn Document::unresolved(self : Document) -> Array[String]

    The external entities the document named but the parser did not read, in the order they were referenced, each name once.

    The library does no I/O, so a document whose entities live in a file beside it is read without them: XML 1.0 says a non-validating processor need not read them, but must tell the application which ones it skipped. That is this list. It is empty for a document that needed nothing from outside.

    Document::xpath

    fn Document::xpath(self : Document, query : String) -> Selection raise Refused

    Narrow to the nodes an XPath 1.0 expression answers, over the whole document.

    Document::xpath_value

    fn Document::xpath_value(self : Document, query : String) -> XPathValue raise Refused

    The typed answer of an XPath 1.0 expression, read from the whole document.

    Element

    pub struct Element {
    name : String
    attrs : Array[(String, String)]
    children : Array[Node]
    parent : Element?
    closed : Bool
    // private fields
    }

    An element: the {uri}local name — the James Clark spelling, chosen over registered prefixes so a node reads the same whichever prefixes the document declared — its attributes in document order, its children, its parent, and how it was closed (<a/> versus <a></a>) for the writer.

    Element::add_class

    fn Element::add_class(self : Element, name : String) -> Unit

    Add a class; an element that already carries it is untouched.

    Element::append

    fn Element::append(self : Element, node : Node) -> Unit

    Append a child node, claiming its parentage.

    Element::attr

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

    The value of an attribute, or None when the element does not carry it.

    Element::by_class

    fn Element::by_class(self : Element, name : String) -> Selection

    The descendants of this element that carry the class name.

    Element::by_id

    fn Element::by_id(self : Element, id : String) -> Selection

    The descendants of this element whose id attribute is id.

    Element::by_name

    fn Element::by_name(self : Element, name : String) -> Selection

    The descendants of this element whose name attribute is name.

    Element::by_tag

    fn Element::by_tag(self : Element, name : String) -> Selection

    The descendants of this element named name; * answers every element.

    Element::classes

    fn Element::classes(self : Element) -> Array[String]

    The class list: the class attribute split on whitespace, empty parts dropped, as HTML says.

    Element::clear

    fn Element::clear(self : Element) -> Unit

    Drop every child.

    Element::closest

    fn Element::closest(self : Element, query : String) -> Selection raise Refused

    The nearest ancestor of this element, itself included, that matches a CSS selector.

    Element::content

    fn Element::content(self : Element) -> Element?

    The contents of a <template>: a fragment of its own, the way the DOM keeps them, and None for every other element.

    What sits in a template is inert — it is not a child of the element, so a query does not reach it and text() does not read it, which is what a browser does too. set_content is how a caller that builds a template by hand gives it contents.

    Element::css

    fn Element::css(self : Element, query : String) -> Selection raise Refused

    Narrow to the descendants of this element that match a CSS selector.

    Element::custom

    fn Element::custom(self : Element, sel : &Selector) -> Selection raise Refused

    Run a selector that was never registered, over this element's descendants.

    Element::document

    fn Element::document(self : Element) -> Document?

    The document an element belongs to, or None when it is detached: the registry a selector(name, ..) call consults lives on the document.

    Element::has_attr

    fn Element::has_attr(self : Element, name : String) -> Bool

    Whether the element carries an attribute of this name.

    Element::has_class

    fn Element::has_class(self : Element, name : String) -> Bool

    Whether the element's class list names this class.

    Element::html

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

    The element's inner markup, written in the flavour its document was read as.

    Element::insert

    fn Element::insert(self : Element, at : Int, node : Node) -> Unit

    Insert node at at; an index past either end clamps to it.

    Element::insert_after

    fn Element::insert_after(self : Element, node : Node) -> Bool

    Insert node after self among its siblings.

    Element::insert_before

    fn Element::insert_before(self : Element, node : Node) -> Bool

    Insert node before self among its siblings.

    Element::new

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

    A fresh element. The constructor claims the parentage of the children it is given, so a tree built this way keeps its parent pointers true.

    Element::prepend

    fn Element::prepend(self : Element, node : Node) -> Unit

    Insert node as the first child.

    Element::remove

    fn Element::remove(self : Element) -> Bool

    Detach self from the parent, answering true when there was one. bs4's extract; the collector owns the rest.

    Element::remove_attr

    fn Element::remove_attr(self : Element, name : String) -> Bool

    Drop an attribute; true when it was there.

    Element::remove_class

    fn Element::remove_class(self : Element, name : String) -> Unit

    Remove one class. The attribute stays, empty if that is what is left — bs4's behaviour, so a caller is never surprised by a missing attribute.

    Element::replace_with

    fn Element::replace_with(self : Element, node : Node) -> Bool

    Take self's place in the parent. bs4's replace_with; false when self has no parent.

    Element::selector

    fn Element::selector(self : Element, name : String, query : String) -> Selection raise Refused

    Run the selector registered under name, over this element's descendants.

    Element::set_attr

    fn Element::set_attr(self : Element, name : String, value : String) -> Unit

    Set or replace an attribute, keeping its place when it is already there.

    Element::set_content

    fn Element::set_content(self : Element, content : Element) -> Unit

    Give a <template> a content fragment. A caller who builds one by hand uses this; the parser does it on its own.

    Element::smooth

    fn Element::smooth(self : Element) -> Unit

    Fuse adjacent text nodes. What a writer wants after edits, and what keeps a tree that was cut and rebuilt from carrying confetti. CDATA is left alone: it is a distinct kind so it can round-trip.

    Element::text

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

    The text of this element and everything under it.

    Element::unwrap

    fn Element::unwrap(self : Element) -> Bool

    Unwrap the element: its children move up to its place, the element itself is dropped. bs4's unwrap; false when the element has no parent.

    Element::walk

    fn Element::walk(self : Element, visit : (Node) -> Unit) -> Unit

    Pre-order walk over the whole subtree. Iterative, so a deep tree cannot outrun the stack.

    Element::wrap

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

    Wrap self in a fresh element with the given name, answering the wrapper. None when self has no parent to be replaced in.

    Element::xpath

    fn Element::xpath(self : Element, query : String) -> Selection raise Refused

    Narrow to the nodes an XPath 1.0 expression answers from this element.

    Element::xpath_value

    fn Element::xpath_value(self : Element, query : String) -> XPathValue raise Refused

    The typed answer of an XPath 1.0 expression, read from this element.

    Flavour

    pub(all) enum Flavour {
    Xml
    Html
    } derive(Eq,
    Debug
    )

    How a document was read, and therefore how it writes itself back.
    impl Show for Flavour

    Flavour::equal

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

    Flavour::not_equal

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

    Flavour::output

    fn Flavour::output(self : Flavour, logger : &Logger) -> Unit

    Flavour::to_repr

    Flavour::to_string

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

    Node

    pub(all) enum Node {
    ElementNode(Element)
    TextNode(String)
    CDataNode(String)
    CommentNode(String)
    ProcessingInstructionNode(ProcessingInstruction)
    DoctypeNode(String)
    }

    One node of the tree. Mixed content survives: text, CDATA, elements, comments, processing instructions and the doctype are all nodes, in document order — none of which fits in a JSON-shaped value, which is why this is not moonjson.

    Node::by_class

    fn Node::by_class(self : Node, name : String) -> Selection

    The descendants of this node that carry the class name.

    Node::by_id

    fn Node::by_id(self : Node, id : String) -> Selection

    The descendants of this node whose id attribute is id.

    Node::by_name

    fn Node::by_name(self : Node, name : String) -> Selection

    The descendants of this node whose name attribute is name.

    Node::by_tag

    fn Node::by_tag(self : Node, name : String) -> Selection

    The descendants of this node named name.

    Node::children

    fn Node::children(self : Node) -> Array[Node]

    The children of a node that is an element; a node with no children answers an empty list.

    Node::closest

    fn Node::closest(self : Node, query : String) -> Selection raise Refused

    The nearest ancestor of this node, itself included, that matches a CSS selector.

    Node::css

    fn Node::css(self : Node, query : String) -> Selection raise Refused

    Narrow to the descendants of this node that match a CSS selector; a node that is not an element answers empty.

    Node::custom

    fn Node::custom(self : Node, sel : &Selector) -> Selection raise Refused

    Run a selector that was never registered, over this node's descendants.

    Node::is_element

    fn Node::is_element(self : Node) -> Bool

    Whether the node is an element.

    Node::is_text

    fn Node::is_text(self : Node) -> Bool

    Text, CDATA included: XPath's text() node test matches both.

    Node::name

    fn Node::name(self : Node) -> String

    An element's name, or "" for a node that is not an element.

    Node::selector

    fn Node::selector(self : Node, name : String, query : String) -> Selection raise Refused

    Run the selector registered under name, over this node's descendants.

    Node::text

    fn Node::text(self : Node) -> String

    A node's text content — the DOM's textContent: an element answers the text of its descendants, a text, CDATA, comment or processing-instruction node answers what it carries.

    Node::xpath

    fn Node::xpath(self : Node, query : String) -> Selection raise Refused

    Narrow to the nodes an XPath 1.0 expression answers from this node.

    Node::xpath_value

    fn Node::xpath_value(self : Node, query : String) -> XPathValue raise Refused

    The typed answer of an XPath 1.0 expression, read from this node.

    ProcessingInstruction

    pub(all) struct ProcessingInstruction {
    target : String
    data : String
    }

    A processing instruction: <?target data?>, with data the raw text between the target and the ?>, so an instruction round-trips exactly.

    Repairs

    pub(all) struct Repairs {
    implied : Int
    dropped : Int
    reparented : Int
    closed_at_eof : Int
    }

    What the HTML parser repaired. WHATWG says repairs happen silently; this tally makes them answerable afterwards, for a caller who wants to ask.

    Repairs::any

    fn Repairs::any(self : Repairs) -> Bool

    Whether the parser repaired anything at all.

    Repairs::new

    fn Repairs::new() -> Repairs

    A tally with nothing in it.

    Repairs::total

    fn Repairs::total(self : Repairs) -> Int

    Every repair the parser made, added up.

    Selection

    pub struct Selection {
    items : Array[Node]
    // private fields
    }

    A selection: the nodes a query returned, and the document they belong to. The empty result is an empty list, never a special value, so a chain never branches — and every query method exists on Selection, on Element and on Document, so a chain never has to stop at a node.

    doc.xpath("//ul").css("li.external").xpath("@href") .selector("tag", "a").custom(DeepLinks::new()) .items[2].children[0].css("span").text()

    Selection::add_class

    fn Selection::add_class(self : Selection, name : String) -> Unit

    Add a class to every selected element.

    Selection::append

    fn Selection::append(self : Selection, node : Node) -> Unit

    Append a node to every selected element.

    Selection::at

    #alias("_[_]")
    fn Selection::at(self : Selection, index : Int) -> Node

    selection[i] in the chain: the node at that position, so a chain can step down into one node and carry on. Out of range answers a detached empty element rather than panicking.

    Selection::attr

    fn Selection::attr(self : Selection, name : String) -> String?

    The named attribute of the first selected element that carries it.

    Selection::by_class

    fn Selection::by_class(self : Selection, name : String) -> Selection

    The elements among the descendants that carry the class name.

    Selection::by_id

    fn Selection::by_id(self : Selection, id : String) -> Selection

    The elements among the descendants whose id attribute is id.

    Selection::by_name

    fn Selection::by_name(self : Selection, name : String) -> Selection

    The elements among the descendants whose name attribute is name.

    Selection::by_tag

    fn Selection::by_tag(self : Selection, name : String) -> Selection

    The elements among the descendants whose name is name; * answers them all.

    Selection::children

    fn Selection::children(self : Selection) -> Selection

    The children of every selected node.

    Selection::clear

    fn Selection::clear(self : Selection) -> Unit

    Drop the children of every selected element.

    Selection::closest

    fn Selection::closest(self : Selection, query : String) -> Selection raise Refused

    The nearest ancestor — self included — matching a CSS selector.

    Selection::css

    fn Selection::css(self : Selection, query : String) -> Selection raise Refused

    Narrow with a CSS selector, Selectors Level 4's statically decidable subset. Searches the descendants of the selected nodes — the context itself is never a candidate, querySelectorAll's rule.

    Selection::custom

    fn Selection::custom(self : Selection, sel : &Selector) -> Selection raise Refused

    Run a selector that was never registered: no name, no state.

    Selection::descendants

    fn Selection::descendants(self : Selection) -> Selection

    Every node below the selected nodes, in document order.

    Selection::eq

    fn Selection::eq(self : Selection, i : Int) -> Selection

    The selected node at i; a negative index counts from the end, the way jQuery's eq does. Out of range answers empty.

    Selection::filter

    fn Selection::filter(self : Selection, keep : (Node) -> Bool) -> Selection

    Keep the selected nodes a closure accepts.

    Selection::first

    fn Selection::first(self : Selection) -> Selection

    The first selected node; empty stays empty.

    Selection::html

    fn Selection::html(self : Selection) -> String

    The inner HTML of the first selected element, written in the flavour its document was read as; "" when the selection holds no element.

    Selection::last

    fn Selection::last(self : Selection) -> Selection

    The last selected node; empty stays empty.

    Selection::length

    fn Selection::length(self : Selection) -> Int

    How many nodes the selection holds.

    Selection::new

    fn Selection::new() -> Selection

    An empty selection, belonging to no document.

    Selection::of

    fn Selection::of(nodes : Array[Node]) -> Selection

    A selection of these nodes, in the order given, carrying the document the first of them belongs to.

    Selection::parent

    fn Selection::parent(self : Selection) -> Selection

    The parent of every selected node that has one.

    Selection::prepend

    fn Selection::prepend(self : Selection, node : Node) -> Unit

    Insert a node as the first child of every selected element.

    Selection::remove

    fn Selection::remove(self : Selection) -> Unit

    Detach every selected element from its parent.

    Selection::remove_attr

    fn Selection::remove_attr(self : Selection, name : String) -> Unit

    Remove an attribute from every selected element.

    Selection::remove_class

    fn Selection::remove_class(self : Selection, name : String) -> Unit

    Remove a class from every selected element.

    Selection::replace_with

    fn Selection::replace_with(self : Selection, node : Node) -> Unit

    Put a node in the place of every selected element.

    Selection::selector

    fn Selection::selector(self : Selection, name : String, query : String) -> Selection raise Refused

    Run a selector by the name it was registered under, built-in or the caller's own. An unregistered name raises.

    Selection::set_attr

    fn Selection::set_attr(self : Selection, name : String, value : String) -> Unit

    Set an attribute on every selected element.

    Selection::set_text

    fn Selection::set_text(self : Selection, text : String) -> Unit

    Replace the children of every selected element with one text node.

    Selection::smooth

    fn Selection::smooth(self : Selection) -> Unit

    Fuse adjacent text nodes under every selected element.

    Selection::text

    fn Selection::text(self : Selection) -> String

    The text content of every selected node, concatenated.

    Selection::unwrap

    fn Selection::unwrap(self : Selection) -> Unit

    Replace every selected element with its children.

    Selection::wrap

    fn Selection::wrap(self : Selection, name : String) -> Unit

    Wrap every selected element in a fresh element of this name.

    Selection::xpath

    fn Selection::xpath(self : Selection, query : String) -> Selection raise Refused

    Narrow the selection with an XPath 1.0 expression, evaluated with each selected node as the context node.

    The answer is a node-set; an expression that evaluates to a string, number or boolean answers its string value as one text node, so the chain never breaks — doc.xpath("string(//h1)").text() reads the way it should. xpath_value is the typed answer.

    Selection::xpath_value

    fn Selection::xpath_value(self : Selection, query : String) -> XPathValue raise Refused

    The typed XPath 1.0 answer: nodes, a string, a number or a boolean.

    SelectorFn

    pub struct SelectorFn {
    f : (Selection, String) -> Selection raise Refused
    }

    A selector that is a closure — the shape a selector with no state of its own takes. A selector that carries configuration is a struct implementing Selector directly, with a constructor of its own.

    SelectorFn::new

    fn SelectorFn::new(f : (Selection, String) -> Selection raise Refused) -> SelectorFn

    A selector made of a closure: the shape one with no state of its own takes.

    SelectorFn::select

    fn SelectorFn::select(self : SelectorFn, ctx : Selection, query : String) -> Selection raise Refused

    XPathValue

    pub(all) enum XPathValue {
    Nodes(Selection)
    Str(String)
    Num(Double)
    Flag(Bool)
    }

    An XPath 1.0 result: what the expression actually answered.
    impl Show for XPathValue

    XPathValue::output

    fn XPathValue::output(self : XPathValue, logger : &Logger) -> Unit

    XPathValue::to_string

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

    dump

    fn dump(node : Node) -> Bytes

    The XML serialization of a node, as bytes.

    dump_html

    fn dump_html(node : Node) -> Bytes

    The HTML serialization of a node, as bytes.

    dumps

    fn dumps(node : Node) -> String

    The XML serialization of a node.

    dumps_html

    fn dumps_html(node : Node) -> String

    The HTML serialization of a node, WHATWG's rules.

    local_of

    fn local_of(name : String) -> String

    The local part of a {uri}local name.

    namespace_of

    fn namespace_of(name : String) -> String?

    The URI of a {uri}local name, or None when the name carries none.

    Powered by MoonBit

    Site sourceReport issueModulesSkillsBuild queueStatistics

    © 2026 mooncakes.io