3. Writing XML
3.1. Memory and file writers
XMLBufferWriter retains a document for conversion to a string or immediate
reuse by XMLReader. XMLFileWriter associates the same writer interface
with an output filename:
xmlio::XMLBufferWriter memory("settings");
xmlio::XMLFileWriter file("settings.xml", "settings");
xmlio::write(file, "count", 4);
file.close();
An explicit close is recommended for files because it reports output
errors. The destructor also closes the file but cannot safely propagate an
exception.
3.2. Creating the document tree
A root name may be supplied to the constructor, or the first push or
openTag may create it. push makes a new child current and pop
returns to its parent:
xmlio::XMLBufferWriter output("project");
xmlio::push(output, "limits");
xmlio::write(output, "lower", 0.0);
xmlio::write(output, "upper", 1.0);
xmlio::pop(output);
The writer rejects a second document root and attempts to close an element when
the stack is empty. depth exposes the number of open elements when a
diagnostic or test needs to verify stack balance.
3.3. Attributes and empty elements
set_attr attaches an attribute to the current element. Attribute lists are
useful when creating a tag and its attributes together:
xmlio::XMLBufferWriter output;
output.openTag(
"project",
xmlio::AttributeList{
xmlio::Attribute("name", "example"),
xmlio::Attribute("version", 2)});
output.emptyTag("marker", {xmlio::Attribute("kind", "checkpoint")});
output.closeTag();
String and arithmetic attributes use the same locale-independent conversion as element values.
3.4. Namespaces
Namespace declarations are ordinary xmlns attributes. Qualified elements
and attributes require a declared prefix:
xmlio::XMLBufferWriter output;
output.openTag(
"document",
{xmlio::Attribute("xmlns:m", "urn:example:metrics")});
output.openTag("m", "score");
output.write(8.5);
output.closeTag();
output.closeTag();
The reader must independently register the prefix before using it in XPath. The prefix chosen for a query need not be the same spelling used in the file; the namespace URI is what identifies the namespace.
3.5. Text, escaping, and raw XML
All normal text and attribute values are escaped by libxml2. A string such as
A < B & B < C therefore remains data and cannot accidentally create tags.
writeXML is different: it parses its argument as a well-formed XML fragment
and appends the resulting nodes. Use it only when the caller intentionally has
XML rather than plain text. Malformed fragments raise WriterError.
3.6. Embedding documents
A reader or memory writer can be embedded by name:
xmlio::XMLBufferWriter result("result");
xmlio::write(result, "input", original_reader);
This produces <input> containing the original document root. Streaming a
reader with operator<< inserts its root directly under the current element.
Both forms omit the embedded document’s XML declaration.
3.7. Serialization and output
str returns the complete document and accepts a flag controlling the XML
declaration. printRoot returns only the root element. print writes to a
stream, and save replaces a file with the current document.
XMLFileWriter is DOM buffered. flush serializes the complete current
document and replaces the associated file; it is not an incremental streaming
operation. This is appropriate for configuration and ordinary result files.
Applications producing very large XML graphs or event streams should evaluate
their memory requirements before choosing this writer.
3.8. Complete composition example
This example preserves an input document inside a result and also extracts deferred operation definitions:
1/**
2 * @file composition.cpp
3 * @brief Extract deferred XML groups and preserve an input document in output.
4 */
5
6#include <xmlio/xmlio.hpp>
7
8#include <iostream>
9#include <string>
10
11int main() {
12 auto input = xmlio::XMLReader::from_string(R"xml(
13<control>
14 <operations>
15 <elem><type>constant</type><value>2.0</value></elem>
16 <elem><type>linear</type><slope>0.5</slope></elem>
17 </operations>
18</control>
19)xml");
20
21 const auto groups =
22 xmlio::read_xml_vector_group(input, "/control/operations", "type");
23 for (const auto& group : groups) {
24 std::cout << "deferred operation: " << group.id << '\n';
25 }
26
27 xmlio::XMLBufferWriter output("result");
28 xmlio::write(output, "input", input);
29 xmlio::write(output, "operation_count", groups.size());
30
31 const auto result = xmlio::XMLReader::from_string(output.str());
32 std::cout << result.xpath_str("/result/input/control/operations") << '\n';
33}