rexml

package module
v0.0.0-...-283af9b Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 17, 2026 License: BSD-3-Clause Imports: 4 Imported by: 0

README

go-ruby-rexml/rexml

rexml — go-ruby-rexml

Docs License Go Coverage

A pure-Go (no cgo) reimplementation of the core of Ruby's REXML XML library — the tree model, the parser, the serialisers (the compact default formatter and Formatters::Pretty), and a widely-used subset of XPath — matching MRI 4.0.5's rexml 3.4.4 behaviour without any Ruby runtime.

It is built from scratch rather than wrapping encoding/xml: REXML's API, whitespace handling and round-trip semantics differ. REXML keeps text and attribute values in their raw, still-escaped form, defaults to single-quoted attributes, sorts attributes by local name in the compact formatter (but keeps source order in Pretty), self-closes empty elements, and preserves comments / CDATA / processing instructions verbatim — so ParseDocument(xml).ToString() reproduces what REXML::Document.new(xml).to_s emits, byte-for-byte.

It is the REXML backend for go-embedded-ruby, but is a standalone, reusable module with no dependency on the Ruby runtime — a sibling of go-ruby-yaml (Psych), go-ruby-regexp (Onigmo) and go-ruby-erb (ERB).

Features

Faithful port of REXML's DOM + parse + serialise + XPath, validated against the ruby binary on every supported platform:

  • ParseParseDocument builds a tree of Document / Element / Attribute / Text / Comment / CData / Instruction (PI) / DocType / XMLDecl, preserving raw text, entities, the XML declaration and the DOCTYPE (including a bracketed internal subset). Malformed input returns a *ParseError with a byte offset, mirroring REXML::ParseException.
  • DOMElement{Prefix, Name, Attributes, Children, …} with Add / AddElement / AddAttribute / AddText / SetText, Elements(path), EachElement, ChildElements, Text / GetText / Texts, ElementAt(n) (1-based), FirstElement, QName / ExpandedName / Prefix / NamespaceURI, Root / RootNode. Attributes is an ordered map.
  • Serialise(*Document).ToString (compact, REXML's to_s), Write(WriteOptions), and the Pretty formatter Pretty(indent) / PrettyString — matching REXML's exact output: single-quoted attributes, local-name attribute ordering (compact) vs source order (Pretty), entity escaping (& < > in text, & < > " ' for programmatic values, literal ' escaped to &apos; inside the single-quoted form), numeric-reference and CDATA / comment / PI round-trip, and self-closing empty elements.
  • XPath subsetXPathFirst / XPathEach / XPathMatch over the common subset (see the boundary below).

CGO-free, dependency-free, 100% test coverage, gofmt + go vet clean, and green across the six 64-bit Go targets (amd64, arm64, riscv64, loong64, ppc64le, s390x).

Install

go get github.com/go-ruby-rexml/rexml

Usage

package main

import (
	"fmt"

	"github.com/go-ruby-rexml/rexml"
)

func main() {
	d, _ := rexml.ParseDocument(`<config><server name="web" port="8080"/></config>`)

	fmt.Println(d.ToString())
	// <config><server name='web' port='8080'/></config>   (attrs sorted, single-quoted)

	fmt.Println(d.Pretty(2))
	// <config>
	//   <server name='web' port='8080'/>
	// </config>

	srv := rexml.XPathFirst(d, "//server").(*rexml.Element)
	name, _ := srv.Attr("name")
	fmt.Println(name) // web

	// Build a tree programmatically.
	doc := rexml.NewDocument()
	root := doc.AddElement("root")
	root.AddElement("item").AddText("hello & <world>")
	fmt.Println(doc.ToString())
	// <root><item>hello &amp; &lt;world&gt;</item></root>
}

Node model

ParseDocument returns a *Document whose Children hold the top-level nodes (optional *XMLDecl, *DocType, comments / PIs, and the single root *Element). Every node implements the unexported Node interface; the concrete types a host binds are:

REXML class Go type Notes
REXML::Document *Document Root, RootNode, XMLDecl, DocType
REXML::Element *Element Prefix, Name, Attributes, Children
REXML::Attribute *Attribute Prefix, Name, Value, UnescapedValue
REXML::Attributes *Attributes ordered; Get, Set, Each, EachSorted
REXML::Text *Text raw round-trip; Val decodes, String raw
REXML::Comment *Comment verbatim content
REXML::CData *CData verbatim content
REXML::Instruction (PI) *Instruction Target, Content
REXML::DocType *DocType raw body, round-trips
REXML::XMLDecl *XMLDecl Version, Encoding, Standalone

XPath subset boundary

XPathMatch / XPathFirst / XPathEach implement the widely-used subset REXML's XPath covers:

Supported — absolute (/a/b/c) and descendant (//tag, a//b) paths; the wildcard *; the child and descendant axes; attribute steps @attr and @*; the text() node test; and predicates [n] (1-based per-parent position, matching REXML's //a[1] selecting the first a within each parent), [@attr] (existence) and [@attr='v'] / [@attr="v"] (value, compared against the decoded attribute value). A relative path applied to a *Document resolves from the root element, like REXML.

Out of scope (documented boundary) — axes other than child / descendant (no ancestor, following-sibling, parent, ..); functions beyond text() (no count(), position(), last(), name(), contains(), …); arithmetic; and compound boolean predicates (and / or). An unrecognised predicate leaves the node set unchanged.

Tests & coverage

The suite pairs deterministic, ruby-free tests (which alone hold coverage at 100%, so the qemu cross-arch and Windows lanes pass the gate) with a differential MRI oracle: a wide XML corpus is parsed and re-serialised here and by the system ruby (REXML::Document.new(xml).to_s, Formatters::Pretty, REXML::XPath.match), and the bytes are compared exactly. The oracle feeds XML through stdin and $stdout.binmode / $stdin.binmode so Windows text-mode never pollutes the bytes; it is gated to Ruby ≥ 4.0 and skips itself where ruby is absent.

COVERPKG=$(go list ./... | paste -sd, -)
go test -race -coverpkg="$COVERPKG" -coverprofile=cover.out ./...
go tool cover -func=cover.out | tail -1   # 100.0%

License

BSD-3-Clause — see LICENSE. Copyright the go-ruby-rexml/rexml authors.

WebAssembly

Being pure Go (CGO=0), this library also compiles to WebAssembly — both GOOS=js GOARCH=wasm (browser / Node.js) and GOOS=wasip1 GOARCH=wasm (WASI). CI builds both targets on every push, alongside the six 64-bit native/qemu arches.

GOOS=js     GOARCH=wasm go build ./...   # browser / Node
GOOS=wasip1 GOARCH=wasm go build ./...   # WASI (wasmtime, wasmer, wasmedge, …)

Documentation

Overview

Package rexml is a pure-Go (no cgo) reimplementation of the core of Ruby's REXML XML library — the tree model, the parser, the serialisers (the default compact formatter and Formatters::Pretty), and a widely-used subset of XPath.

It mirrors REXML's observable behaviour rather than wrapping encoding/xml: REXML stores text in its raw, still-escaped form, defaults to single-quoted attributes, self-closes empty elements, and preserves comments / CDATA / processing instructions verbatim on a parse→serialise round trip. The Go tokenizer is used internally where convenient, but the output and value model match MRI's REXML, not Go's encoding/xml.

Index

Constants

View Source
const VERSION = "3.4.4"

VERSION reports the REXML release whose observable behaviour this package targets (MRI 4.0.5 ships rexml 3.4.4).

Variables

This section is empty.

Functions

func AttrValue

func AttrValue(n Node) string

AttrValue returns the decoded value of a matched @attr node, the empty string for any other node — convenience for XPath @attr matches.

func PrettyString

func PrettyString(d *Document, indent int) string

PrettyString returns the document rendered by the Pretty formatter with the given indent width (REXML's Formatters::Pretty.new(indent)).

func XPathEach

func XPathEach(ctx Node, path string, fn func(Node))

XPathEach calls fn for every node matching path — REXML::XPath.each.

Types

type Attribute

type Attribute struct {
	Prefix string
	Name   string // local name
	Value  string
	Raw    bool // Value is already in normalized form (parsed); emit near-verbatim
}

Attribute is a single element attribute. Prefix is the namespace prefix (the part before ':' in the qualified name), empty for an unprefixed attribute. Value holds the normalized (still-escaped) form when Raw is set — the byte form a parser captured — and the plain unescaped form otherwise.

func (*Attribute) QName

func (a *Attribute) QName() string

QName is the qualified attribute name (prefix:name, or just name).

func (*Attribute) UnescapedValue

func (a *Attribute) UnescapedValue() string

UnescapedValue returns the attribute's decoded value — REXML's Attribute#value.

type Attributes

type Attributes struct {
	// contains filtered or unexported fields
}

Attributes is an ordered map of attributes keyed by qualified name, matching REXML's source order on serialisation.

func (*Attributes) Delete

func (a *Attributes) Delete(qname string)

Delete removes an attribute by qualified name; it is a no-op if absent.

func (*Attributes) Each

func (a *Attributes) Each(fn func(*Attribute))

Each calls fn for every attribute in source / insertion order — REXML's each_attribute, the order the Pretty formatter uses.

func (*Attributes) EachSorted

func (a *Attributes) EachSorted(fn func(*Attribute))

EachSorted calls fn for every attribute ordered by local name with a stable sort — the order REXML's default (compact) formatter emits.

func (*Attributes) Get

func (a *Attributes) Get(qname string) (string, bool)

Get returns the attribute's decoded value for a qualified name and whether it was set — REXML's Attributes#[] (which returns the unnormalized value).

func (*Attributes) GetAttr

func (a *Attributes) GetAttr(qname string) *Attribute

GetAttr returns the *Attribute for a qualified name, or nil.

func (*Attributes) Len

func (a *Attributes) Len() int

Len is the number of attributes.

func (*Attributes) Set

func (a *Attributes) Set(qname, value string)

Set inserts or replaces an attribute with an unescaped value (programmatic add_attribute), preserving first-seen order.

type CData

type CData struct {
	Value string
	// contains filtered or unexported fields
}

CData is a CDATA section; its content is emitted verbatim between <![CDATA[ and ]]>.

type Child

type Child = Node

Child is a node that can live inside an Element's child list and also at the Document's top level. Every Node type implements it.

type Comment

type Comment struct {
	Value string
	// contains filtered or unexported fields
}

Comment is an XML comment; its content is emitted verbatim between <!-- -->.

type DocType

type DocType struct {

	// Body is the text between "<!DOCTYPE " and the closing ">".
	Body string
	// contains filtered or unexported fields
}

DocType is a document type declaration. The whole declaration is kept as its raw bytes (without the surrounding <!DOCTYPE ... >) so it round-trips.

type Document

type Document struct {
	Children []Node
	// contains filtered or unexported fields
}

Document is the root of a REXML tree. Its Children hold the top-level nodes: an optional XMLDecl, DocType, comments / PIs, and exactly one root element.

func NewDocument

func NewDocument() *Document

NewDocument returns an empty document.

func Parse

func Parse(xml string) (*Document, error)

Parse is an alias for ParseDocument, matching the common REXML idiom REXML::Document.new(xml).

func ParseDocument

func ParseDocument(xml string) (*Document, error)

ParseDocument parses an XML string into a Document tree, mirroring REXML::Document.new. Text, comments, CDATA, PIs, the XML declaration and the DOCTYPE are preserved so a parse→serialise round trip matches MRI's REXML.

func (*Document) Add

func (d *Document) Add(n Node) Node

Add appends a top-level node. Element children get the document recorded as a nil parent (REXML root elements have no parent element).

func (*Document) AddElement

func (d *Document) AddElement(qname string) *Element

AddElement creates the root element, appends it, and returns it.

func (*Document) DocType

func (d *Document) DocType() *DocType

DocType returns the document's DOCTYPE declaration, or nil.

func (*Document) Pretty

func (d *Document) Pretty(indent int) string

Pretty renders the document with the Pretty formatter at the given indent — a convenience for REXML::Formatters::Pretty.new(indent).write(doc, out).

func (*Document) Root

func (d *Document) Root() *Element

Root returns the document's root element, or nil — REXML's Document#root.

func (*Document) RootNode

func (d *Document) RootNode() *Document

RootNode returns the document itself — REXML's root_node returns the containing Document for a Document.

func (*Document) String

func (d *Document) String() string

String makes Document satisfy fmt.Stringer, aliasing ToString.

func (*Document) ToString

func (d *Document) ToString() string

ToString returns the document's default (compact) serialisation — REXML's Document#to_s.

func (*Document) Write

func (d *Document) Write(opts WriteOptions) string

Write serialises the document with the chosen formatter and returns the text.

func (*Document) XMLDecl

func (d *Document) XMLDecl() *XMLDecl

XMLDecl returns the document's XML declaration, or nil.

type Element

type Element struct {
	Prefix     string
	Name       string
	Attributes *Attributes
	Children   []Node
	// contains filtered or unexported fields
}

Element is an XML element node. Name is the local name; Prefix is the namespace prefix (empty when unprefixed). Attributes preserves source order; Children holds the ordered child nodes.

func NewElement

func NewElement(qname string) *Element

NewElement builds a detached element from a qualified name (prefix:name).

func (*Element) Add

func (e *Element) Add(n Node) Node

Add appends any node as a child, setting its parent.

func (*Element) AddAttribute

func (e *Element) AddAttribute(qname, value string)

AddAttribute sets an attribute by qualified name (REXML's add_attribute).

func (*Element) AddElement

func (e *Element) AddElement(qname string) *Element

AddElement creates a child element with the given qualified name, appends it, and returns it (REXML's add_element).

func (*Element) AddText

func (e *Element) AddText(s string)

AddText appends an unescaped text node (REXML's add_text). Consecutive add_text calls in REXML coalesce; this mirrors that for raw==raw text nodes.

func (*Element) Attr

func (e *Element) Attr(qname string) (string, bool)

Attr returns an attribute value by qualified name and whether it is present.

func (*Element) ChildElements

func (e *Element) ChildElements() []*Element

ChildElements returns the direct child elements in order.

func (*Element) EachElement

func (e *Element) EachElement(fn func(*Element))

EachElement calls fn for every direct child element (REXML's each_element).

func (*Element) ElementAt

func (e *Element) ElementAt(n int) *Element

ElementAt returns the element at the 1-based child-element index, or nil — REXML's elements[n] with an Integer.

func (*Element) Elements

func (e *Element) Elements(path string) []*Element

Elements resolves an XPath-style path against this element and returns the matching elements — REXML's Element#elements[path] / each. A bare positive integer selects the 1-based nth child element.

func (*Element) ExpandedName

func (e *Element) ExpandedName() string

ExpandedName is REXML's expanded_name: prefix:name when prefixed, else name.

func (*Element) FirstElement

func (e *Element) FirstElement(path string) *Element

FirstElement returns the first element matching the path, or nil.

func (*Element) GetText

func (e *Element) GetText() *Text

GetText returns the first child Text node, or nil — REXML's get_text.

func (*Element) HasText

func (e *Element) HasText() bool

HasText reports whether the element has at least one text child.

func (*Element) NamespaceURI

func (e *Element) NamespaceURI() string

NamespaceURI resolves the element's own prefix to a namespace URI by walking up the xmlns declarations, mirroring REXML's Element#namespace.

func (*Element) Parent

func (e *Element) Parent() *Element

Parent returns the parent element, or nil at the root.

func (*Element) QName

func (e *Element) QName() string

QName is the element's qualified name (prefix:name or name).

func (*Element) SetText

func (e *Element) SetText(s string)

SetText replaces the element's first text node (or inserts one) with s, matching REXML's Element#text=.

func (*Element) Text

func (e *Element) Text() string

Text returns the unescaped value of the first text node, or "" if none — REXML's Element#text (which returns nil; "" is the empty-text proxy here).

func (*Element) Texts

func (e *Element) Texts() []*Text

Texts returns every direct Text child — REXML's texts.

type Instruction

type Instruction struct {
	Target  string
	Content string
	// contains filtered or unexported fields
}

Instruction is a processing instruction (PI): <?target content?>.

type Node

type Node interface {
	// contains filtered or unexported methods
}

Node is any node in a REXML tree: Element, Text, Comment, CData, Instruction, DocType, XMLDecl, or the Document itself.

func XPathFirst

func XPathFirst(ctx Node, path string) Node

XPathFirst returns the first node matching path relative to ctx, or nil — REXML::XPath.first.

func XPathMatch

func XPathMatch(ctx Node, path string) []Node

XPathMatch returns every node matching path — REXML::XPath.match.

type ParseError

type ParseError struct {
	Offset int
	Msg    string
}

ParseError reports a parse failure with a byte offset, mirroring the fact that REXML::Document.new raises ParseException on malformed input.

func (*ParseError) Error

func (e *ParseError) Error() string

type PrettyFormatter

type PrettyFormatter struct {
	Indentation int
	Width       int
	Compact     bool
}

PrettyFormatter is REXML::Formatters::Pretty. It re-indents the tree, drops whitespace-only text nodes between elements, collapses runs of whitespace in text, and (when Compact) keeps short text-only elements on one line.

type Text

type Text struct {

	// Value is the raw stored string (escaped form when Raw, plain otherwise).
	Value string
	// Raw reports that Value already holds the serialised (escaped) bytes, so
	// writeTo emits it verbatim. Parsed text sets this; NewText leaves it false.
	Raw bool
	// contains filtered or unexported fields
}

Text is character data inside an element. REXML keeps the text in its raw, still-escaped form: a parsed "&#65;&amp;" round-trips byte-for-byte, while text added programmatically (already-decoded) is escaped on output. The Raw flag distinguishes the two — parsed text is Raw, NewText escapes on write.

func NewText

func NewText(s string) *Text

NewText builds a text node from an unescaped string (REXML's add_text). The string is escaped on serialisation.

func (*Text) String

func (t *Text) String() string

String returns the raw stored bytes — REXML's Text#to_s.

func (*Text) Val

func (t *Text) Val() string

Val returns the unescaped character value — REXML's Text#value.

type WriteOptions

type WriteOptions struct {
	// Indent selects the Pretty formatter with this indent width when > 0.
	// REXML uses -1 to mean "no indentation" (the compact default).
	Indent int
	// Pretty forces the Pretty formatter even when Indent is 0 (REXML default
	// indent of 2). Ignored when Indent > 0.
	Pretty bool
}

WriteOptions configures Document.Write. The zero value selects the default compact formatter (REXML's Document#write with no indent).

type XMLDecl

type XMLDecl struct {
	Version    string
	Encoding   string
	Standalone string
	// contains filtered or unexported fields
}

XMLDecl is the XML declaration: <?xml version='1.0' ...?>. REXML emits the version, and the encoding / standalone only when present.

type XPath

type XPath struct{}

XPath implements the widely-used subset of XPath 1.0 that REXML's XPath.first / each / match cover. The supported grammar is documented on Match.

Boundary (out of scope): axes other than child/descendant (no ancestor, following-sibling, …), functions beyond text() (no count(), position(), last(), name(), contains(), …), arithmetic, and nested boolean predicates (and/or). Predicates support [n] (1-based position), [@attr], [@attr='value'] / [@attr="value"], and the boolean attribute existence test.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL