Help us shape the future of Go! Take the 2026 Go Developer Survey

dom

package module
v0.0.0-...-a342f18 Latest Latest
Warning

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

Go to latest
Published: Feb 28, 2022 License: MIT Imports: 8 Imported by: 0

README

DOM Level 3 for Go

Experimental attempt for a complete implementation of the DOM, level 3, using the specs from W3.org, in the Go language.

There are several other implementations floating about. This is currently just an experiment to see how far I can get, and perhaps it can grow into a usable implementation.

Interfaces

Since the spec for DOM3 regards everything as interfaces, I tried to do the same in this package. Whether that will work out well or not is the question. Right now, the Document type is (per spec) the main entry point for creating any kind of Node. The following interfaces have (partial) implementations:

  • Document: the entry point for creating Nodes.
  • Element: for example: <pfx:element/>
  • Attr: attributes of elements, for example: <pfx:element pfx:attribute="hi"/>
  • ProcessingInstruction: for example: <?spacing true?>
  • Comment: for example: <!-- comment node -->
  • Text: basic text as a child of an Element

The following are omitted:

  • NodeList: is just too convoluted to implement this as well IMO. A slice is sufficient.
  • CDATASection: it's just Text.
  • CharacterData: which is just a String type with methods defined on it.

Example code

Reading XML documents is pretty much working, and the DOM can be traversed using the supplied methods. An example program using this DOM implementation:

package main

import (
	"fmt"
	"net/http"
	"os"

	"github.com/krpors/dom"
)

func main() {
	resp, err := http.Get("https://www.reddit.com/r/golang/.xml")
	if err != nil {
		fmt.Println("failed:", err)
		os.Exit(1)
	}

	parser := dom.NewParser(resp.Body)
	doc, err := parser.Parse()
	if err != nil {
		os.Exit(2)
	}

	entries := doc.GetElementsByTagName("entry")
	for _, entry := range entries {
		username := entry.GetElementsByTagName("name")[0]
		title := entry.GetElementsByTagName("title")[0]

		fmt.Printf("'%s' posted '%s'\n", username.GetFirstChild().GetNodeValue(), title.GetFirstChild().GetNodeValue())
	}
}

This will get an XML feed from the r/golang subreddit, and prints out the name and title nodes from each entry node. It's obviously a bit verbose and probably not really 'idiomatic' Go, but the goal is mostly to be consistent with the DOM specification.

Challenges

  1. No inheritance, meaning double implementations of the same functionality (Element, Comment, ProcessingInstruction, ...). More code, but better separation.
  2. Spec mentions things like 'if x is null, then ...', but Go doesn't have null pointers for Strings, for example.

Documentation

Overview

Package dom is an implementation of the DOM specification, level 3. Since elements, text nodes, comments, processing instructions, etc. cannot exist outside the context of a Document, the Document interface also contains the factory methods needed to create these objects. The Node objects created have an ownerDocument attribute which associates them with the Document within whose context they were created.

Example:

package dom

import (
	"bytes"
	"fmt"
	"github.com/krpors/dom"
)

func main() {
	doc := dom.NewDocument()
	root, _ := doc.CreateElement("rootNode")
	text := doc.CreateText("some arbitrary text")
	root.AppendChild(text)
	doc.AppendChild(root)

	var b bytes.Buffer
	dom.ToXML(doc, false, &b)
	fmt.Println(b.String())
}

Running this code will yield:

<?xml version="1.0" encoding="UTF-8"?><rootNode>some arbitrary text</rootNode>

References:

https://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/Overview.html#contents
https://www.w3.org/TR/xml/

Index

Constants

This section is empty.

Variables

View Source
var ErrorAttrInUse = errors.New("INUSE_ATTRIBUTE_ERR: the attribute is already an attribute of another Element")

ErrorAttrInUse is returned when an attribute is already an attribute of another Element object. The DOM user must explicitly create/clone Attr nodes to re-use them in other elements.

View Source
var ErrorHierarchyRequest = errors.New("HIERARCHY_REQUEST_ERR: an attempt was made to insert a node where it is not permitted")

ErrorHierarchyRequest is the error which can be returned when the node is of a type that does not allow children, if the node to append to is one of this node's ancestors or this node itself, or if this node is of type Document and the DOM application attempts to append a second DocumentType or Element node.

View Source
var ErrorInvalidCharacter = errors.New("INVALID_CHARACTER_ERR: an invalid or illegal XML character is specified")

ErrorInvalidCharacter is returned when an invalid character is used for for example an element or attribute name.

View Source
var ErrorNotFound = errors.New("NOT_FOUND_ERR: the given child is not found in the current context")

ErrorNotFound is returned when a specified Node is not found, for instance during an attempt to delete a child Node from another Node.

View Source
var ErrorNotSupported = errors.New("NOT_SUPPORTED_ERR: this implementation does not support the requested type of object or operation")

ErrorNotSupported is returned when this implementation does not support the requested operation or object.

View Source
var ErrorWrongDocument = errors.New("WRONG_DOCUMENT_ERR: the child was created from a different Document instance")

ErrorWrongDocument is returned when an insertion is attempted of a Node which was created from a different document instance.

View Source
var XMLDeclaration = `<?xml version="1.0" encoding="UTF-8"?>`

XMLDeclaration is the usually default XML processing instruction at the start of XML documents. This is merely added as a convenience. It's the same declaration which the encoding/xml package has, except it does not have a trailing newline.

Functions

func MoveNamespacesToRoot

func MoveNamespacesToRoot(d Document)

MoveNamespacesToRoot literally moves all found namespace declarations to the "base" Node.

func PrintTree

func PrintTree(start Node, w io.Writer)

PrintTree prints the whole tree starting from the given Node 'node' to the writer w.

Types

type Attr

type Attr interface {
	Node

	GetName() string
	IsSpecified() bool
	GetValue() string
	SetValue(string)
	GetOwnerElement() Element
	// contains filtered or unexported methods
}

Attr represents an attribute in an Element. It implements the Node interface.

type Comment

type Comment interface {
	Node

	GetComment() string        // Returns the comment text of this node.
	SetComment(comment string) // Sets the comment text of this node.
}

Comment represents a comment node in an XML tree (e.g. <!-- ... -->). It implements the Node interface.

type Configuration

type Configuration struct {
	CDataSections            bool   // Keep CDataSection Nodes in the Document.
	Comments                 bool   // Keep Comment nodes in the Document.
	ElementContentWhitespace bool   // Keep all whitespaces in the Document.
	Namespaces               bool   // Perform namespace processing as defined in https://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/namespaces-algorithms.html#normalizeDocumentAlgo
	NamespaceDeclarations    bool   // Include (true) or discard (false) namespace declaration attributes.
	NormalizeCharacters      bool   // Perform or do not perform character normalization.
	OmitXMLDeclaration       bool   // Omits XML declaration during serialization. Default: false.
	PrettyPrint              bool   // Pretty print during serialization. Default: false.
	IndentCharacter          string // Indent character, if pretty printing. Default is four spaces.
}

Configuration contains fields which can control the output of the Parser and Serializer. Note that not (all configuration are specified or used (yet).

func NewConfiguration

func NewConfiguration() Configuration

NewConfiguration creates a Configuration object with the defaults as per the DOM spec.

type Document

type Document interface {
	Node

	// Creates an element with the given tagname and returns it. Will return
	// an ErrorInvalidCharacter if the specified name is not an XML name according
	// to the XML version in use, specified in the XMLVersion attribute.
	CreateElement(tagName string) (Element, error)
	// Creates an element of the givens qualified name and namespace URI, and
	// returns it. Use an empty string if no namespace is necessary. See
	// CreateElement(string).
	CreateElementNS(namespaceURI, tagName string) (Element, error)
	// Creates a Text node given the specified string and returns it.
	CreateText(string) Text
	// Creates an Attr of the given name and returns it.
	CreateAttribute(name string) (Attr, error)
	// Creates an Attr using the given namespace URI and name.
	CreateAttributeNS(namespaceURI, name string) (Attr, error)
	// CreateComment creates a Comment node with the given comment content. If
	// the comment contains a double hyphen (--), this should generate an error.
	CreateComment(comment string) (Comment, error)
	// CreateProcessingInstruction creates a processing instruction and returns it.
	CreateProcessingInstruction(target, data string) (ProcessingInstruction, error)
	// Gets the document element, which should be the first (and only) child Node
	// of the Document. Can be nil if none is set yet.
	GetDocumentElement() Element
	// GetElementsByTagName finds all descendant elements of the current element,
	// with the given tag name, in document order.
	GetElementsByTagName(string) []Element
	// GetElementsByTagNameNS finds all descendant elements of the current element,
	// with the given tag name and namespace URI, in document order.
	GetElementsByTagNameNS(namespaceURI, tagname string) []Element

	NormalizeDocument() // Puts the Document in 'normal form'.
}

Document is the root of the Document Object Model. It implements the Node interface. As per the spec, all child nodes must be created through an instance of a Document object.

func NewDocument

func NewDocument() Document

NewDocument creates a new Document which can be used to create custom documents using the methods supplied.

type DocumentType

type DocumentType interface {
	Node

	GetName() string     // Gets the name of the DTD; i.e. the name immediately following the DOCTYPE keyword.
	GetPublicID() string // Returns the public identifier of the external subset.
	GetSystemID() string // Returns the system identifier of the external subset. This may be an absolute URI or not.
}

DocumentType belongs to a Document, but can also be nil. The DocumentType interface in the DOM Core provides an interface to the list of entities that are defined for the document, and little else because the effect of namespaces and the various XML schema efforts on DTD representation are not clearly understood as of this writing. (Direct copy of the spec).

type Element

type Element interface {
	Node

	GetTagName() string                                            // Gets the tag name of this element.
	SetAttribute(name, value string) error                         // Convenience function to add an attribute.
	SetAttributeNode(a Attr) error                                 // Sets an attribute based on the Attr type.
	GetAttribute(name string) string                               // Convenience function to get an attribute value.
	GetElementsByTagName(string) []Element                         // Find all descendant elements of the current element.
	GetElementsByTagNameNS(namespaceURI, tagname string) []Element // Like GetElementsByTagName, except with a namespace URI.
	// contains filtered or unexported methods
}

Element represents an element in an HTML or XML document. It implements the Node interface.

type NamedNodeMap

type NamedNodeMap interface {
	GetNamedItem(string) Node  // Gets a named item identified by the given string. Returns nil if nothing is found.
	SetNamedItem(Node) error   // Adds a new item. The node's NodeName is used as a key.
	RemoveNamedItem(string)    // Removes the item identified by the given string.
	GetItems() map[string]Node // Gets the items as a Go map.
	Length() int               // Gets the amount of items in the named node map.
}

NamedNodeMap represents collections of nodes that can be accessed by name.

type Node

type Node interface {
	GetNodeName() string   // Returns the node name.
	GetNodeType() NodeType // Returns the node type (e.g. ElementNode, CommentNode, ...)
	GetNodeValue() string  // Returns the node value.
	GetLocalName() string  // Returns the local part of the node name.

	GetParentNode() Node // Gets the parent Node, or nil of the Node has no parent.

	GetAttributes() NamedNodeMap // Returns the NamedNodeMap containing attributes, if any.
	HasAttributes() bool         // Returns true if the Node has any attributes, false if othwerwise.

	GetOwnerDocument() Document // Gets the owner document (the Document instance which was used to create the Node).

	GetChildNodes() []Node                              // Gets the list of child nodes this Node has, if any
	GetFirstChild() Node                                // Gets the first child Node of this Node. May return nil if no child nodes exist.
	GetLastChild() Node                                 // Gets the last child Node of this Node. May return nil if there is no such Node.
	AppendChild(Node) error                             // Appends a child to this Node. Returns an error when the Node does not allow the child Node.
	RemoveChild(oldChild Node) (Node, error)            // Removes oldChild and returns it.
	ReplaceChild(newChild, oldChild Node) (Node, error) // Replaces oldChild with newChild, and returns newChild.
	InsertBefore(newChild, refChild Node) (Node, error) // Inserts newChild before the refChild.
	HasChildNodes() bool                                // Returns true when the Node has one or more children.

	CloneNode(deep bool) Node          // Creates a duplicate of the current node.
	ImportNode(n Node, deep bool) Node // Imports a node from another document to this document, without altering or removing the source node from the original document.

	GetPreviousSibling() Node // Gets the Node immediately preceding this Node. Returns nil if no previous sibling exists.
	GetNextSibling() Node     // Gets the Node immediately following this Node. Returns nil if no following sibling exists.

	GetNamespaceURI() string    // Returns the namespace URI of this node.
	GetNamespacePrefix() string // Returns the namespace prefix of this node.

	LookupPrefix(namespace string) (string, bool) // Look up the prefix associated to the given namespace URI, starting from this node.
	LookupNamespaceURI(pfx string) (string, bool) // LookupNamespaceURI looks up the namespace URI associated to the given prefix.
	IsDefaultNamespace(namespace string) bool     // IsDefaultNamespace checks if the node resides in the default namespace.

	GetTextContent() string // Gets the text content of the current Node.
	SetTextContent(string)  // Sets the text content of the current Node. Any possible children are removed.
	// contains filtered or unexported methods
}

Node is the primary interface for the entire Document Object Model. It represents a single node in the document tree. While all objects implementing the Node interface expose methods for dealing with children, not all objects implementing the Node interface may have children.

type NodeType

type NodeType uint8

NodeType defines the types of nodes which exist in the DOM.

const (
	ElementNode NodeType = iota
	AttributeNode
	TextNode
	CDATASectionNode
	EntityReferenceNode
	EntityNode
	ProcessingInstructionNode
	CommentNode
	DocumentNode
	DocumentTypeNode
	DocumentFragmentNode
)

Enumeration of all types of Nodes in the DOM.

func (NodeType) String

func (n NodeType) String() string

String returns the string representation of the NodeType, using the default representation by the W3 specification.

type Parser

type Parser struct {
	Configuration Configuration
	// contains filtered or unexported fields
}

Parser is the entrypoint of the dom package to parse an XML tree from the given reader, into the doc attribute, using the decoder from encoding/xml.

func NewParser

func NewParser(reader io.Reader) *Parser

NewParser constructs a new Parser using the given reader. The reader is expected to contain the (...valid) XML tree. Namespace awareness will be set to true per default. Parser configuration will be set to a default one.

func (*Parser) Parse

func (b *Parser) Parse() (Document, error)

Parse parses an XML Document contained within the reader attribute of the current Parser. A Document will be returned and a nil error if the parsing succeeded.

type ProcessingInstruction

type ProcessingInstruction interface {
	Node

	GetTarget() string   // Gets the target of the processing instruction.
	GetData() string     // Gets the data of the processing instruction.
	SetData(data string) // Sets the data.
}

ProcessingInstruction interface represents a "processing instruction", used in XML as a way to keep processor-specific information in the text of the document. A processing instruction has the following form in an XML document:

<?target data?>

The target of a processing instruction can be anything except the string [XxMmLl]. The data can be anything, except the string ?> since that denotes the end of the processing instruction. If that happens, a fatal error should occur.

type Serializer

type Serializer struct {
	Configuration Configuration // Serializer's configuration.
}

Serializer defines the type that can be used to serialize a Node + its children. The struct configuration can be used to control the output of the serialization to a certain degree.

func NewSerializer

func NewSerializer() *Serializer

NewSerializer creates a new Serializer using the default configuration.

func (*Serializer) Serialize

func (s *Serializer) Serialize(node Node, w io.Writer)

Serialize writes the node plus its children to the writer w. The Serializer does not do any specific mutations on the given Node to serialize, i.e. it will write it as-is. No normalizations, alterations etc are done.

type Text

type Text interface {
	Node

	GetText() string  // Gets the character data of this Text node.
	SetText(s string) // Sets the character data of this Text node.

	IsElementContentWhitespace() bool // Return true if the Text node contains "ignorable whitespace".
}

Text represents character data within an element. It implements the Node interface. Note that the name of the methods defined on this interface are not aligned with the specifications, due to the fact the Go's interfaces will not see a correct difference between this Text interface, or the ProcessingInstruction interface when the methods have the same signatures.

type XMLName

type XMLName string

XMLName represents an XML Name according to the specification at https://www.w3.org/TR/xml/#NT-Name. The name can be validated using the IsValid() function, which will return true if the name is valid, or false if the name is invalid.

func (XMLName) GetLocalPart

func (n XMLName) GetLocalPart() string

GetLocalPart gets the local part of the XMLName. For example, the following:

name := XMLName("pfx:element")
fmt.Println(name)

will output:

element

func (XMLName) GetPrefix

func (n XMLName) GetPrefix() string

GetPrefix gets the prefix of the XMLName. For example, the following:

name := XMLName("pfx:element")
fmt.Println(name.GetPrefix())

will output:

pfx

XML names without a prefix will return an empty string.

func (XMLName) IsValid

func (n XMLName) IsValid() bool

IsValid returns true if the given XMLName is valid according to the XML specification.

Jump to

Keyboard shortcuts

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