Overview
Add document creation capabilities to the libxml2 Pony binding, enabling users to build XML documents programmatically from scratch. Currently, the library only supports parsing existing XML via parseFile() and parseDoc().
Current State
- Xml2Doc: Only has parsing constructors (parseFile, parseDoc)
- Xml2Node: Only wraps existing nodes from parsing/XPath, no creation methods
- Memory Management: Automatic via
_final() destructors and tag references
- Raw C API: All required libxml2 functions available (xmlNewDoc, xmlNewNode, xmlAddChild, etc.)
Proposed API
Phase 1: Core Document Creation (MVP)
Add fundamental document creation and tree building capabilities.
Xml2Doc Changes (libxml2/xml2doc.pony)
New Constructor:
new create(version: String = "1.0") ? =>
"""
Create a new empty XML document with the specified version.
- `version`: XML version string (default: "1.0")
Creates an empty document with no root element. Use setRootElement()
or createElement() to build the document tree.
Example:
let doc = Xml2Doc.create()?
let root = doc.createElement("root")?
doc.setRootElement(root)?
"""
let ptrx: NullablePointer[XmlDoc] = LibXML2.xmlNewDoc(version)
if ptrx.is_none() then error end
ptr' = ptrx
New Factory Method:
fun ref createElement(name: String, content: String = ""): Xml2Node ? =>
"""
Create a new element node belonging to this document.
- `name`: Element name (tag name)
- `content`: Optional text content
Returns an Xml2Node wrapper. The node is created but not yet attached
to the document tree. Use setRootElement() or appendChild() to add it.
Example:
let doc = Xml2Doc.create()?
let elem = doc.createElement("item", "Hello")?
elem.setProp("id", "1")
"""
let node_ptr = LibXML2.xmlNewDocNode(ptr', NullablePointer[XmlNs].none(),
name, content)
if node_ptr.is_none() then error end
Xml2Node.fromPTR(recover tag this end, node_ptr)?
New Method:
fun ref setRootElement(root: Xml2Node): Xml2Node ? =>
"""
Set the root element of this document.
- `root`: The node to set as root element
Returns the old root element if one existed, otherwise returns the new root.
Raises error if the operation fails.
Example:
let doc = Xml2Doc.create()?
let root = doc.createElement("root")?
doc.setRootElement(root)?
"""
let old_root = LibXML2.xmlDocSetRootElement(ptr', root.ptr')
if old_root.is_none() then
root // Return the new root if no previous root existed
else
Xml2Node.fromPTR(recover tag this end, old_root)?
end
Xml2Node Changes (libxml2/xml2node.pony)
New Method:
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:
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)?
New Method:
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:
let elem = doc.createElement("item")?
elem.setContent("New content")
"""
LibXML2.xmlNodeSetContent(ptr', content)
Testing (libxml2/_tests/basic_tests.pony)
Add comprehensive tests:
TestCreateEmptyDocument - Create empty document and serialize
TestCreateDocumentWithRoot - Create document, add root, serialize
TestCreateAndAppendChildren - Build tree with multiple children
TestSetContent - Modify node content after creation
TestCreateAndXPath - Verify XPath works on created documents
TestCreateAndSaveFile - Create document and save to file
Phase 2: Convenience Methods (Enhancement)
Add helper methods to improve ergonomics.
Xml2Doc Additions
new createWithRoot(root_name: String, version: String = "1.0") ? =>
"""
Create a new XML document with a root element.
Convenience constructor that creates a document and sets the root
element in one step.
"""
fun ref createTextNode(content: String): Xml2Node ? =>
"""
Create a text node belonging to this document.
Text nodes are typically added as children of element nodes.
"""
fun ref createComment(content: String): Xml2Node ? =>
"""
Create a comment node belonging to this document.
- `content`: Comment text (without <!-- --> delimiters)
"""
Xml2Node Additions
fun ref addChild(name: String, content: String = ""): Xml2Node ? =>
"""
Convenience method to create and add a child element in one step.
Creates a new child element, adds it to this node, and returns the
new child wrapped as Xml2Node.
"""
Testing (libxml2/_tests/coverage_tests.pony)
Add tests:
TestCreateWithRootConvenience - Test convenience constructor
TestMixedContent - Text nodes + element nodes
TestAddChildConvenience - Test addChild helper
TestCreateComment - Comment node creation
Implementation Details
Memory Management
- No changes needed to existing memory management
Xml2Doc._final() already calls xmlFreeDoc() which frees entire tree
Xml2Node already holds tag reference to Xml2Doc preventing premature freeing
- All created nodes attached to document via internal
xmlDoc* pointer
Error Handling
- Follow existing pattern: methods that can fail return
?
- C functions returning NULL pointers trigger Pony errors
- Pattern: Call C function → check for null → error if null
Compatibility
- Created documents work with all existing functionality:
- XPath evaluation (xpathEval, xpathEvalNodes, etc.)
- Serialization (serialize, saveToFile)
- Attribute manipulation (setProp, unsetProp, getProp)
- Tree navigation (getChildren, getRootElement)
C Functions Used
xmlNewDoc(version) - Create document
xmlNewDocNode(doc, ns, name, content) - Create element
xmlDocSetRootElement(doc, root) - Set root
xmlAddChild(parent, child) - Add child
xmlNodeSetContent(node, content) - Set text
xmlNewDocText(doc, content) - Create text node (Phase 2)
xmlNewDocComment(doc, content) - Create comment (Phase 2)
xmlNewChild(parent, ns, name, content) - Create+add in one call (Phase 2)
Critical Files
To Modify:
libxml2/xml2doc.pony (add create constructor and factory methods)
libxml2/xml2node.pony (add tree manipulation methods)
libxml2/_tests/basic_tests.pony (add Phase 1 tests)
libxml2/_tests/coverage_tests.pony (add Phase 2 tests)
To Reference (no changes):
libxml2/raw/functions.pony (C API bindings)
Example Usage After Implementation
Phase 1 Example:
// Create a simple document
let doc = Xml2Doc.create()?
let root = doc.createElement("catalog")?
doc.setRootElement(root)?
let book1 = doc.createElement("book")?
book1.setProp("id", "bk101")
let title = doc.createElement("title", "XML Developer's Guide")?
book1.appendChild(title)?
root.appendChild(book1)?
// Serialize and save
let xml_string = doc.serialize()?
doc.saveToFile(auth, "catalog.xml")?
// XPath works on created documents
let books = doc.xpathEvalNodes("//book")?
env.out.print("Found " + books.size().string() + " books")
Phase 2 Example:
// Convenience constructor
let doc = Xml2Doc.createWithRoot("html")?
let root = doc.getRootElement()?
// Convenience addChild method
let body = root.addChild("body")?
let para = body.addChild("p")?
// Mixed content (text + elements)
para.appendChild(doc.createTextNode("This is "))?
let bold = doc.createElement("b", "bold")?
para.appendChild(bold)?
para.appendChild(doc.createTextNode(" text"))?
// Comments
root.appendChild(doc.createComment("Generated by Pony"))?
let html = doc.serialize()?
Verification Plan
After implementing Phase 1:
- Run
make unit-tests - all existing tests must pass
- New tests should create documents, serialize them, and verify output
- Test that XPath works on created documents
- Test that serialize() and saveToFile() work correctly
- Verify memory cleanup with valgrind if available
After implementing Phase 2:
- Test convenience constructor works
- Test mixed content scenarios (elements + text nodes)
- Test addChild convenience method
- Test comment creation
- Run full test suite with
make test
Success Criteria
- Users can create XML documents from scratch
- Created documents can be serialized to strings and files
- XPath queries work on created documents
- No memory leaks (automatic cleanup via existing destructors)
- API follows existing Pony idiomatic patterns
- All tests pass including new creation tests
- Documentation complete for all new methods
Overview
Add document creation capabilities to the libxml2 Pony binding, enabling users to build XML documents programmatically from scratch. Currently, the library only supports parsing existing XML via
parseFile()andparseDoc().Current State
_final()destructors and tag referencesProposed API
Phase 1: Core Document Creation (MVP)
Add fundamental document creation and tree building capabilities.
Xml2Doc Changes (
libxml2/xml2doc.pony)New Constructor:
New Factory Method:
New Method:
Xml2Node Changes (
libxml2/xml2node.pony)New Method:
New Method:
Testing (
libxml2/_tests/basic_tests.pony)Add comprehensive tests:
TestCreateEmptyDocument- Create empty document and serializeTestCreateDocumentWithRoot- Create document, add root, serializeTestCreateAndAppendChildren- Build tree with multiple childrenTestSetContent- Modify node content after creationTestCreateAndXPath- Verify XPath works on created documentsTestCreateAndSaveFile- Create document and save to filePhase 2: Convenience Methods (Enhancement)
Add helper methods to improve ergonomics.
Xml2Doc Additions
Xml2Node Additions
Testing (
libxml2/_tests/coverage_tests.pony)Add tests:
TestCreateWithRootConvenience- Test convenience constructorTestMixedContent- Text nodes + element nodesTestAddChildConvenience- Test addChild helperTestCreateComment- Comment node creationImplementation Details
Memory Management
Xml2Doc._final()already callsxmlFreeDoc()which frees entire treeXml2Nodealready holds tag reference toXml2Docpreventing premature freeingxmlDoc*pointerError Handling
?Compatibility
C Functions Used
xmlNewDoc(version)- Create documentxmlNewDocNode(doc, ns, name, content)- Create elementxmlDocSetRootElement(doc, root)- Set rootxmlAddChild(parent, child)- Add childxmlNodeSetContent(node, content)- Set textxmlNewDocText(doc, content)- Create text node (Phase 2)xmlNewDocComment(doc, content)- Create comment (Phase 2)xmlNewChild(parent, ns, name, content)- Create+add in one call (Phase 2)Critical Files
To Modify:
libxml2/xml2doc.pony(add create constructor and factory methods)libxml2/xml2node.pony(add tree manipulation methods)libxml2/_tests/basic_tests.pony(add Phase 1 tests)libxml2/_tests/coverage_tests.pony(add Phase 2 tests)To Reference (no changes):
libxml2/raw/functions.pony(C API bindings)Example Usage After Implementation
Phase 1 Example:
Phase 2 Example:
Verification Plan
After implementing Phase 1:
make unit-tests- all existing tests must passAfter implementing Phase 2:
make testSuccess Criteria