use "raw/"
use "debug"
class Xml2Node
"""
Wrapper around a libxml2 `xmlNode` pointer, providing helpers for XPath
evaluation, attribute/content access, and basic tree navigation.
"""
var ptr': NullablePointer[XmlNode]
var ptr: XmlNode
var xml2doc: Xml2Doc tag
new fromPTR(xml2doc': Xml2Doc tag, ptrx: NullablePointer[XmlNode])? =>
"""
Create an `Xml2Node` from a non-null libxml2 `xmlNode*`.
- `ptrx`: Nullable pointer to an `XmlNode`.
Raises an error if `ptrx` is `None`, otherwise stores the pointer in
`ptr'`, assigns the underlying `XmlNode` to `ptr`, and marks the node
as allocated.
"""
xml2doc = xml2doc'
if ptrx.is_none() then
error
else
ptr' = ptrx
ptr = ptr'.apply()?
end
fun xpathEval(
xpath: String val,
namespaces: Array[(String val, String val)] = [])
: Xml2XPathResult
=>
"""
Evaluate an XPath expression relative to this node as the context node.
- `xpath`: The XPath expression to evaluate.
- `namespaces`: Optional list of `(prefix, uri)` pairs to register on a
temporary XPath context before evaluation.
Internally, creates a new `xmlXPathContext` for the owning document,
registers the provided namespaces, sets this node as the context node,
calls `xmlXPathEval`, frees the context, and returns an
`Xml2XPathResult` for the resulting `xmlXPathObject*`.
"""
let tmpctx: NullablePointer[XmlXPathContext] =
LibXML2.xmlXPathNewContext(ptr.doc)
for (n, url) in namespaces.values() do
LibXML2.xmlXPathRegisterNs(tmpctx, n, url)
end
LibXML2.xmlXPathSetContextNode(ptr', tmpctx)
let xptr: NullablePointer[XmlXPathObject] =
LibXML2.xmlXPathEval(xpath, tmpctx)
let xpo: Xml2XPathResult = Xml2XPathObject(xml2doc, xptr)
LibXML2.xmlXPathFreeContext(tmpctx)
xpo
fun xpathEvalNodes(
xpath: String val,
namespaces: Array[(String val, String val)] = [])
: Array[Xml2Node] ?
=>
"""
A convenience method that calls xpathEval and returns an Array[Xml2Node].
"""
(xpathEval(xpath, namespaces) as Array[Xml2Node])
fun xpathEvalString(
xpath: String val,
namespaces: Array[(String val, String val)] = [])
: String val ?
=>
"""
A convenience method that calls xpathEval and returns a String val.
"""
(xpathEval(xpath, namespaces) as String val)
fun xpathEvalF64(
xpath: String val,
namespaces: Array[(String val, String val)] = [])
: F64 ?
=>
"""
A convenience method that calls xpathEval and returns an F64 (XML's
default Number type in libxml2).
"""
(xpathEval(xpath, namespaces) as F64)
fun xpathEvalBool(
xpath: String val,
namespaces: Array[(String val, String val)] = [])
: Bool ?
=>
"""
A convenience method that calls xpathEval and returns a Bool.
"""
(xpathEval(xpath, namespaces) as Bool)
fun ref name(): String val =>
"""
Return this node’s name as a Pony `String`.
The name is obtained from the underlying `xmlNode->name` C string and
cloned into a Pony-managed string.
"""
String.from_cstring(ptr.name).clone()
fun ref getLineNo(): I64 =>
"""
Return the line number in the source document where this node was
parsed, or -1 if the information is unavailable.
This forwards to `xmlGetLineNo` on the underlying node.
"""
LibXML2.xmlGetLineNo(ptr')
fun ref getNodePath(): String =>
"""
Return an XPath-like string representing the absolute path of this node
within the document tree.
This is the result of libxml2’s `xmlGetNodePath` helper.
"""
LibXML2.xmlGetNodePath(ptr')
fun ref xpathCastNodeToString(): String =>
"""
Cast this node to a string using libxml2’s XPath string conversion
rules (equivalent to XPath’s `string()` function applied to the node).
Delegates to `xmlXPathCastNodeToString` on the underlying node.
"""
LibXML2.xmlXPathCastNodeToString(ptr')
fun ref getProps(): Array[(String, String)] =>
var rv: Array[(String, String)] = Array[(String, String)]
var attr: NullablePointer[XmlAttr] = ptr.properties
while not attr.is_none() do
try
var attrname: String val =
String.from_cstring(attr.apply()?.name).clone()
rv.push((attrname, getProp(attrname)))
attr = attr.apply()?.next
else
return rv
end
end
rv
fun ref getProp(pname: String): String =>
"""
Get the value of an attribute on this element node.
- `pname`: Attribute name (without any prefix/namespace handling).
Returns the attribute value string from `xmlGetProp`, or an empty
string if the attribute is not present.
"""
LibXML2.xmlGetProp(ptr', pname)
fun ref getContent(): String =>
"""
Return the textual content of this node, including text from its
descendants as defined by libxml2’s `xmlNodeGetContent`.
Useful for retrieving the logical text value of elements or text nodes.
"""
LibXML2.xmlNodeGetContent(ptr')
fun ref getLang(): String =>
"""
Return the `xml:lang` value in scope for this node.
This consults the node and its ancestors using `xmlNodeGetLang`.
"""
LibXML2.xmlNodeGetLang(ptr')
fun ref getChildren(): Array[Xml2Node] =>
"""
Return all child **element** nodes of this node as an array of
`Xml2Node`.
Uses `xmlChildElementCount`, `xmlFirstElementChild`, and
`xmlNextElementSibling` to iterate only over element children, skipping
text, comments, and other non-element node types. If there are no
element children, returns an empty array.
"""
var rv: Array[Xml2Node] = Array[Xml2Node]
var elementCount: U64 = LibXML2.xmlChildElementCount(ptr')
if elementCount == 0 then
return rv
end
var child: NullablePointer[XmlNode] = LibXML2.xmlFirstElementChild(ptr')
while not child.is_none() do
try
rv.push(Xml2Node.fromPTR(xml2doc, child)?)
end
child = LibXML2.xmlNextElementSibling(child)
end
rv
fun ref setProp(pname: String val, pvalue: String val) =>
"""
Creates or Sets a property value (think XML Attribute) on this node.
"""
LibXML2.xmlSetProp(ptr', pname, pvalue)
fun ref unsetProp(pname: String val) =>
"""
Unsets a property (think XML Attribute) on this node.
"""
LibXML2.xmlUnsetProp(ptr', pname)
fun ref nodeDump(plevel: I32, pformat: I32): String val =>
"""
Serialize this node and its subtree to a string using libxml2's
`xmlNodeDump`.
- `plevel`: Indentation level to start from when pretty-printing.
- `pformat`: Non-zero to enable formatted (indented) output, zero for
unformatted.
Internally, creates a temporary `xmlBuffer`, dumps the node into it, and
returns the buffer's contents as a Pony `String`.
"""
var buf: NullablePointer[XmlBuffer] = LibXML2.xmlBufferCreate()
LibXML2.xmlNodeDump(buf, ptr.doc, ptr', plevel, pformat)
LibXML2.xmlBufferContent(buf)
fun ref appendChild(child: Xml2Node): Xml2Node ? =>
"""
Add a child node to this element.
- `child`: Node to add as child
Returns the added child node. The child is added at the end of the
children list. Raises error if the operation fails.
Example:
```pony
let parent = doc.createElement("parent")?
let child = doc.createElement("child")?
parent.appendChild(child)?
```
"""
let result = LibXML2.xmlAddChild(ptr', child.ptr')
if result.is_none() then error end
Xml2Node.fromPTR(xml2doc, result)?
fun ref setContent(content: String): None =>
"""
Set the text content of this node.
- `content`: Text content to set
Replaces any existing content of the node. For elements with children,
this will replace all children with a single text node.
Example:
```pony
let elem = doc.createElement("item")?
elem.setContent("New content")
```
"""
LibXML2.xmlNodeSetContent(ptr', content)
fun ref addChild(child_name: String, content: String = ""): Xml2Node ? =>
"""
Convenience method to create and add a child element in one step.
- `child_name`: Element name for the new child
- `content`: Optional text content
Creates a new child element, adds it to this node, and returns the
new child wrapped as Xml2Node.
Example:
```pony
let doc = Xml2Doc.createWithRoot("root")?
let root = doc.getRootElement()?
let child1 = root.addChild("item", "Hello")?
let child2 = root.addChild("item", "World")?
child1.setProp("id", "1")
child2.setProp("id", "2")
```
"""
let node_ptr = LibXML2.xmlNewChild(ptr', NullablePointer[XmlNs].none(),
child_name, content)
if node_ptr.is_none() then error end
Xml2Node.fromPTR(xml2doc, node_ptr)?