Modern C++20 Guide¶
PolyXML delivers high-throughput native XML processing for modern C++20 applications. The C++ bindings are header-only (bindings/cpp/include/polyxml.hpp), wrapping the pure Rust engine (polyxml-c) with idiomatic C++20 types, std::optional, std::string_view, RAII memory management, and move semantics.
📦 Build System Integration¶
Option A: CMake FetchContent¶
include(FetchContent)
FetchContent_Declare(
polyxml
GIT_REPOSITORY https://github.com/nth-bailey/PolyXML.git
GIT_TAG main
)
FetchContent_MakeAvailable(polyxml)
target_link_libraries(my_app PRIVATE polyxml_cpp)
Option B: CMake add_subdirectory¶
Option C: Conan & vcpkg¶
- Conan: Add
polyxml/0.1.0to yourconanfile.txt. - vcpkg: Run
vcpkg install polyxml.
1. Schema Construction & Basic Deserialization¶
Define an XML schema using the fluent SchemaBuilder API and parse XML into a type-safe polyxml::Value:
#include "polyxml.hpp"
#include <iostream>
int main() {
// 1. Build the schema
auto schema = polyxml::SchemaBuilder("Telemetry")
.add_attribute("device_id", "id", POLYXML_SCALAR_INT)
.add_element("altitude", "altitude", POLYXML_SCALAR_FLOAT)
.add_element("armed", "armed", POLYXML_SCALAR_BOOL)
.add_element("status", "status", POLYXML_SCALAR_STRING)
.build();
std::string_view xml = R"(
<Telemetry id="1001">
<altitude>24500.5</altitude>
<armed>true</armed>
<status>STABLE</status>
</Telemetry>
)";
// 2. Deserialize XML string into polyxml::Value
polyxml::Value val = polyxml::deserialize(xml, schema);
// 3. Extract values using type-safe std::optional accessors
int64_t id = val.get("device_id")->as_int().value_or(0);
double alt = val.get("altitude")->as_float().value_or(0.0);
bool armed = val.get("armed")->as_bool().value_or(false);
std::string_view status = val.get("status")->as_string().value_or("UNKNOWN");
std::cout << "Device " << id << " (" << status << "): " << alt << " ft, Armed: " << std::boolalpha << armed << "\n";
return 0;
}
2. Formatting & Serialization¶
Convert native values back into formatted XML with customizable indentation:
#include "polyxml.hpp"
#include <iostream>
int main() {
auto schema = polyxml::SchemaBuilder("SystemConfig")
.add_attribute("env", "env", POLYXML_SCALAR_STRING)
.add_element("max_threads", "max_threads", POLYXML_SCALAR_INT)
.add_element("debug", "debug", POLYXML_SCALAR_BOOL)
.build();
std::string xml = R"(<SystemConfig env="production"><max_threads>32</max_threads><debug>false</debug></SystemConfig>)";
auto val = polyxml::deserialize(xml, schema);
// Serialize with 2-space indentation
std::string pretty_xml = polyxml::serialize("SystemConfig", val, schema, 2);
std::cout << "Formatted XML:\n" << pretty_xml << "\n";
// Compact serialization (indent = 0)
std::string compact_xml = polyxml::serialize("SystemConfig", val, schema, 0);
std::cout << "Compact:\n" << compact_xml << "\n";
return 0;
}
3. RAII, Move Semantics & Exception Safety¶
polyxml::Value and polyxml::Schema are strict RAII objects:
- Destruction automatically releases underlying Rust memory (polyxml_value_free, polyxml_schema_free).
- Copy construction is explicitly disabled to prevent accidental double-free bugs.
- Move construction (std::move) transfers ownership with zero overhead.
- Parsing or serialization errors throw polyxml::Exception.
#include "polyxml.hpp"
#include <iostream>
#include <vector>
void process_batch(const std::vector<std::string>& payloads) {
auto schema = polyxml::SchemaBuilder("Sensor")
.add_attribute("id", "id", POLYXML_SCALAR_INT)
.add_element("val", "val", POLYXML_SCALAR_FLOAT)
.build();
for (const auto& payload : payloads) {
try {
// Deserializes and manages lifetime via RAII
polyxml::Value record = polyxml::deserialize(payload, schema);
// Move ownership into another container or function
polyxml::Value moved_record = std::move(record);
std::cout << "Processed sensor: " << moved_record.get("id")->as_int().value_or(-1) << "\n";
} catch (const polyxml::Exception& ex) {
std::cerr << "Malformed XML payload: " << ex.what() << "\n";
}
}
}
4. Struct Mapping Pattern¶
For production codebases, wrap polyxml::Value extraction into a typed C++ struct:
#include "polyxml.hpp"
#include <iostream>
#include <string>
struct DroneTelemetry {
int64_t drone_id;
double battery_pct;
double velocity_mps;
std::string flight_mode;
static DroneTelemetry from_xml(std::string_view xml, const polyxml::Schema& schema) {
auto val = polyxml::deserialize(xml, schema);
return DroneTelemetry{
.drone_id = val.get("id")->as_int().value_or(0),
.battery_pct = val.get("battery")->as_float().value_or(0.0),
.velocity_mps = val.get("velocity")->as_float().value_or(0.0),
.flight_mode = std::string(val.get("mode")->as_string().value_or("MANUAL")),
};
}
};
int main() {
auto schema = polyxml::SchemaBuilder("Drone")
.add_attribute("id", "id", POLYXML_SCALAR_INT)
.add_element("battery", "battery", POLYXML_SCALAR_FLOAT)
.add_element("velocity", "velocity", POLYXML_SCALAR_FLOAT)
.add_element("mode", "mode", POLYXML_SCALAR_STRING)
.build();
std::string xml = R"(<Drone id="707"><battery>94.5</battery><velocity>18.2</velocity><mode>AUTONOMOUS</mode></Drone>)";
DroneTelemetry telem = DroneTelemetry::from_xml(xml, schema);
std::cout << "Drone " << telem.drone_id << " in " << telem.flight_mode
<< " mode at " << telem.velocity_mps << " m/s (" << telem.battery_pct << "% battery)\n";
return 0;
}
5. Performance Guidelines for C++20¶
- Keep
polyxml::SchemaInstances Long-Lived: Creating a schema involves heap allocation and string parsing. Create schemas once (e.g. asstatic constor class members) and reuse them across all requests. - Use
std::string_view: Thepolyxml::deserializefunction acceptsstd::string_view, allowing zero-copy parsing from network buffers, memory-mapped files (mmap), or string literals. - Avoid Copying String Values:
val.get("field")->as_string()returnsstd::optional<std::string_view>, pointing directly into the parsed token memory without heap string allocations.