2. Reading XML

2.1. Input sources

An XMLReader can load a file, XML text, an input stream, or the current contents of an XMLBufferWriter:

auto file = xmlio::XMLReader::from_file("settings.xml");
auto text = xmlio::XMLReader::from_string("<settings><count>4</count></settings>");

std::istringstream stream("<settings><count>4</count></settings>");
xmlio::XMLReader streamed(stream);

xmlio::XMLBufferWriter buffer("settings");
xmlio::write(buffer, "count", 4);
xmlio::XMLReader buffered(buffer);

A default-constructed reader may be populated later with load_from_file, load_from_string, or the corresponding open overload. Loading a new source replaces the previous document.

2.2. Paths and scalar reads

Paths beginning with / are evaluated from the document root. Other paths are evaluated from the reader’s current context:

const int count = xmlio::read<int>(xml, "/settings/count");

int another_count{};
xmlio::read(xml, "/settings/count", another_count);

A scalar read must select exactly one element, text node, or attribute. Element text is only scalar when the selected element has no child elements. Numeric conversion consumes the complete value, so 12 extra is not accepted as the integer 12.

Strings preserve meaningful leading and trailing whitespace. Numeric parsing uses the classic locale and therefore expects a period as the decimal point.

2.3. Subreaders

A subreader shares its parent’s parsed document but changes the context used by relative paths:

xmlio::XMLReader settings(xml, "/document/settings");
const bool enabled = xmlio::read<bool>(settings, "enabled");
const int count = xmlio::read<int>(settings, "count");

Subreaders are movable, inexpensive, and side-effect free. An absolute path on a subreader still addresses the original document root. The XPath expression . selects the current context itself, which is occasionally useful inside generic read functions.

2.4. Attributes

Attributes can be selected directly with XPath or read through the convenience functions:

const auto name = xmlio::read<std::string>(xml, "/project/@name");
const auto version = xmlio::read<int>(xml, "/project/@version");

std::string same_name;
xml.getAttribute("/project", "name", same_name);

2.5. Queries and optional values

exists reports whether an XPath expression has a result. count returns the size of a selected node set:

if (xml.exists("/project/description")) {
  // The field is present.
}
const int task_count = xml.count("/project/tasks/elem");

Use read_optional when absence is part of the schema:

const auto label =
    xmlio::read_optional<std::string>(xml, "/project/label");

int retries = 3;
xmlio::read_optional(xml, "/project/retries", retries);

The second form leaves retries unchanged when the path is absent and returns false. Conversion and query errors still propagate when a present field is invalid.

2.6. Repeated siblings

read_many reads repeated scalar siblings selected by one XPath expression:

<tags>
  <tag>small</tag>
  <tag>portable</tag>
</tags>
std::vector<std::string> tags;
xmlio::read_many(xml, "/tags/tag", tags);

Containers represented by a parent with <elem> children use ordinary read instead. Their exact representations are listed in Types and XML Representations.

2.7. Reader diagnostics and mutation

str, root_str, context_str, and xpath_str return serialized views of the document. Matching print functions write those views to a stream. These operations are useful for diagnostics and extracting subtrees.

set and set_text replace the scalar content of exactly one selected node. They are convenient for small transformations, but applications should normally deserialize, validate, and rewrite structured data rather than use the XML document as their primary mutable model.

2.8. Complete XPath example

The following example combines a predicate, an attribute, a subreader, a namespace, and an absent optional field:

 1/**
 2 * @file xpath_queries.cpp
 3 * @brief Query attributes, predicates, namespaces, and optional values.
 4 */
 5
 6#include <xmlio/xmlio.hpp>
 7
 8#include <iostream>
 9#include <string>
10
11int main() {
12  const std::string document = R"xml(
13<inventory xmlns:m="urn:example:metrics">
14  <item id="A"><name>paper</name><stock>12</stock><m:score>8.5</m:score></item>
15  <item id="B"><name>pencil</name><m:score>9.0</m:score></item>
16</inventory>
17)xml";
18
19  auto inventory = xmlio::XMLReader::from_string(document);
20  inventory.registerNamespace("m", "urn:example:metrics");
21
22  xmlio::XMLReader pencil(inventory, "/inventory/item[@id='B']");
23  const auto name = xmlio::read<std::string>(pencil, "name");
24  const auto score = xmlio::read<double>(pencil, "m:score");
25  const auto stock = xmlio::read_optional<int>(pencil, "stock");
26
27  std::cout << inventory.count("/inventory/item") << " items; " << name
28            << " scores " << score << " and has stock data: "
29            << std::boolalpha << stock.has_value() << '\n';
30}