Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Documentation

on:
push:
branches: [main]
paths:
- 'docs/**'
- '.readthedocs.yaml'
- '.github/workflows/docs.yml'
- 'include/**'
- 'examples/**'
- 'assets/**'
- 'LICENSE'
- 'README.md'
pull_request:
paths:
- 'docs/**'
- '.readthedocs.yaml'
- '.github/workflows/docs.yml'
- 'include/**'
- 'examples/**'
- 'assets/**'
- 'LICENSE'
- 'README.md'

permissions:
contents: read

jobs:
build:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.13'
cache: pip
cache-dependency-path: docs/requirements.txt
- run: python -m pip install -r docs/requirements.txt
- run: python -m sphinx -b html -n -W --keep-going docs build/docs/html
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
.*
build/
__pycache__/

!.github
!.devcontainer

!.readthedocs.yaml
!.gitignore
!.gitattributes
!.clang-format
Expand Down
16 changes: 16 additions & 0 deletions .readthedocs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Read the Docs configuration file
# https://docs.readthedocs.io/en/stable/config-file/v2.html
version: 2

build:
os: ubuntu-24.04
tools:
python: "3.13"

sphinx:
configuration: docs/conf.py
fail_on_warning: true

python:
install:
- requirements: docs/requirements.txt
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ XML, missing required members, and invalid values through

**Requires GCC 16.1+, CMake 4.3.3+, and Ninja.** The dev container provides a matching environment.

The full documentation website lives in [`docs/`](docs/), with guides, examples,
and an indexed API reference. See [Building the documentation](docs/building-docs.md)
for local previews and Read the Docs setup.

## Table of Contents

- [Benchmarks](#benchmarks)
Expand Down
26 changes: 26 additions & 0 deletions docs/_ext/cpp26.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""Highlight C++26 annotation expressions, including their whitespace and braces."""

from pygments.lexer import bygroups, include, inherit
from pygments.lexers import CppLexer
from pygments.token import Operator, Punctuation, Whitespace


class Cpp26Lexer(CppLexer):
tokens = {
state: [
(r"(\[\[)(\s*)(=)", bygroups(Punctuation, Whitespace, Operator), "annotation-expression"),
inherit,
]
for state in ("statements", "classname", "enumname")
}
tokens["annotation-expression"] = [
(r"\]\]", Punctuation, "#pop"),
(r"[{}]", Punctuation),
include("whitespace"),
include("statements"),
]


def setup(app):
app.add_lexer("cpp", Cpp26Lexer)
return {"version": "1.0", "parallel_read_safe": True, "parallel_write_safe": True}
21 changes: 21 additions & 0 deletions docs/_snippets/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
cmake_minimum_required(VERSION 4.3.3)
# import std is experimental; its opt-in key depends on the CMake version.
if(CMAKE_VERSION VERSION_LESS 4.4)
set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD "451f2fe2-a8a2-47c3-bc32-94786d8fc91b")
else()
set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD "f35a9ac6-8463-4d38-8eec-5d6008153e7d")
endif()
set(CMAKE_CXX_STANDARD 26)
set(CMAKE_CXX_MODULE_STD ON)
project(my_app LANGUAGES CXX)

include(FetchContent)
FetchContent_Declare(
serial_xml
GIT_REPOSITORY https://github.com/EJainDev/SerialXML.git
GIT_TAG main
)
FetchContent_MakeAvailable(serial_xml)

add_executable(my_app main.cpp)
target_link_libraries(my_app PRIVATE serial_xml::serial_xml)
15 changes: 15 additions & 0 deletions docs/_snippets/how-to/attributes.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import std;
import serial_xml;

struct AttributeAndChild {
[[= serial_xml::attribute]] int x;
int y;
};

int main() {
const AttributeAndChild original{4, 5};
const auto xml = serial_xml::to_xml(original, false);
std::println("{}", xml);
const auto restored = serial_xml::from_xml<AttributeAndChild>(xml);
std::println("x: {}; y: {}", restored.x, restored.y);
}
31 changes: 31 additions & 0 deletions docs/_snippets/how-to/combined-schema.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import std;
import serial_xml;

struct Address {
std::string street;
[[= serial_xml::name{"city_name"}]] std::string city;
int zip_code;
};

struct Employee {
[[ = serial_xml::name{"emp_id"}, = serial_xml::attribute ]] int id;
std::string name;
Address address;
[[= serial_xml::iter{"skill", "skills"}]] std::vector<std::string> skills;
};

struct Department {
[[ = serial_xml::name{"dept_name"}, = serial_xml::attribute ]] std::string name;
[[= serial_xml::name{"team"}]] std::vector<Employee> employees;
};

int main() {
const Employee employee{1, "Alice", {"123 Main St", "Boston", 2128}, {"C++", "Python"}};
const Department original{"Engineering", {employee}};
const auto xml = serial_xml::to_xml(original, false);
std::println("{}", serial_xml::prettify(xml));
const auto restored = serial_xml::from_xml<Department>(xml);
const auto& first = restored.employees.front();
std::println("Department: {}; employee: {}; city: {}; skills: {}", restored.name, first.name,
first.address.city, first.skills.size());
}
30 changes: 30 additions & 0 deletions docs/_snippets/how-to/containers.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import std;
import serial_xml;

struct VectorExample {
std::vector<int> values;
};

struct ExcludeOnEmpty {
[[= serial_xml::exclude_on_empty]] std::vector<int> values;
};

struct NoIter {
[[= serial_xml::no_iter]] std::vector<int> values;
};

int main() {
const auto iterated = serial_xml::to_xml(VectorExample{{1, 2, 3}}, false);
const auto empty = serial_xml::to_xml(VectorExample{{}}, false);
const auto omitted = serial_xml::to_xml(ExcludeOnEmpty{{}}, false);
const auto text = serial_xml::to_xml(NoIter{{1, 2, 3}}, false);
std::println("{}", iterated);
std::println("{}", empty);
std::println("{}", omitted);
std::println("{}", text);
const auto restored = serial_xml::from_xml<VectorExample>(iterated);
const auto restored_text = serial_xml::from_xml<NoIter>(text);
const auto restored_empty = serial_xml::from_xml<ExcludeOnEmpty>(omitted);
std::println("Items: {}; text items: {}; omitted items: {}", restored.values.size(),
restored_text.values.size(), restored_empty.values.size());
}
35 changes: 35 additions & 0 deletions docs/_snippets/how-to/custom-conversion.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import std;
import serial_xml;

struct ProductCode {
int value;
};

std::string format_code(const ProductCode& code) { return "SKU-" + std::to_string(code.value); }

template <>
ProductCode serial_xml::from_string<ProductCode>(std::string_view text) {
if (!text.starts_with("SKU-")) {
throw serial_xml::deserialization_error("Expected SKU- code");
}
return {serial_xml::from_string<int>(text.substr(4))};
}

struct Product {
[[= serial_xml::attribute]] int id;
[[= serial_xml::format{format_code}]] ProductCode code;
};

int main() {
const Product original{7, {42}};
const auto xml = serial_xml::to_xml(original, false);
std::println("{}", xml);
const auto restored = serial_xml::from_xml<Product>(xml);
std::println("Product: {}; code: {}", restored.id, restored.code.value);
try {
const auto invalid = serial_xml::from_string<ProductCode>("OTHER-42");
std::println("{}", invalid.value);
} catch (const serial_xml::deserialization_error& error) {
std::println("Conversion failed: {}", error.what());
}
}
26 changes: 26 additions & 0 deletions docs/_snippets/how-to/escaping.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import std;
import serial_xml;

struct EscapeChild {
std::string text;
};

struct EscapeAttribute {
[[= serial_xml::attribute]] std::string text;
};

struct EscapeRaw {
[[= serial_xml::raw]] std::string text;
};

int main() {
const std::string special = "<>&'\"";
const auto child = serial_xml::to_xml(EscapeChild{special}, false);
const auto attribute = serial_xml::to_xml(EscapeAttribute{special}, false);
const auto raw = serial_xml::to_xml(EscapeRaw{special}, false);
std::println("{}", child);
std::println("{}", attribute);
std::println("{}", raw);
const auto restored = serial_xml::from_xml<EscapeChild>(child);
std::println("Restored text: {}", restored.text);
}
14 changes: 14 additions & 0 deletions docs/_snippets/how-to/external-schema.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import std;
import serial_xml;

class mock_vector {
public:
[[= serial_xml::attribute]] std::size_t size() const;
};

int main() {
const std::vector<int> values{1, 2, 3};
const auto xml = serial_xml::to_xml<mock_vector>(values, false);
std::println("{}", xml);
std::println("Actual vector size: {}", values.size());
}
16 changes: 16 additions & 0 deletions docs/_snippets/how-to/item-names.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import std;
import serial_xml;

struct Numbers {
[[= serial_xml::iter{"number", "numbers"}]] std::vector<int> values;
};

int main() {
const Numbers original{{1, 2, 3}};
const auto xml = serial_xml::to_xml(original, false);
std::println("{}", serial_xml::prettify(xml));
const auto restored = serial_xml::from_xml<Numbers>(xml);
for (const int value : restored.values) {
std::println("Number: {}", value);
}
}
15 changes: 15 additions & 0 deletions docs/_snippets/how-to/naming.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import std;
import serial_xml;

struct[[= serial_xml::name{"person"}]] Person {
[[= serial_xml::attribute]] int age;
[[= serial_xml::name{"food"}]] std::string favorite_food;
};

int main() {
const Person original{3, "pizza"};
const auto xml = serial_xml::to_xml(original, false);
std::println("{}", xml);
const auto restored = serial_xml::from_xml<Person>(xml);
std::println("Age: {}; food: {}", restored.age, restored.favorite_food);
}
21 changes: 21 additions & 0 deletions docs/_snippets/how-to/nested-objects.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import std;
import serial_xml;

struct Address {
std::string street;
std::string city;
int zip;
};

struct Person {
std::string name;
Address address;
};

int main() {
const Person original{"Alice", {"123 Main St", "Boston", 2128}};
const auto xml = serial_xml::to_xml(original, false);
std::println("{}", serial_xml::prettify(xml));
const auto restored = serial_xml::from_xml<Person>(xml);
std::println("{} lives in {} ({})", restored.name, restored.address.city, restored.address.zip);
}
14 changes: 14 additions & 0 deletions docs/_snippets/how-to/numeric-formatting.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import std;
import serial_xml;

struct FormattedAttribute {
[[ = serial_xml::attribute, = serial_xml::format{"03d"} ]] int x;
};

int main() {
const FormattedAttribute original{42};
const auto xml = serial_xml::to_xml(original, false);
std::println("{}", xml);
const auto restored = serial_xml::from_xml<FormattedAttribute>(xml);
std::println("Restored value: {}", restored.x);
}
17 changes: 17 additions & 0 deletions docs/_snippets/how-to/optional-values.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import std;
import serial_xml;

struct OptionalChild {
std::optional<int> value;
};

int main() {
const auto present = serial_xml::to_xml(OptionalChild{{42}}, false);
const auto absent = serial_xml::to_xml(OptionalChild{std::nullopt}, false);
std::println("{}", present);
std::println("{}", absent);
auto restored = serial_xml::from_xml<OptionalChild>(present);
std::println("Present value: {}", restored.value.value());
serial_xml::from_xml(restored, absent);
std::println("After absent input: {}", restored.value.has_value());
}
17 changes: 17 additions & 0 deletions docs/_snippets/how-to/read-errors.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import std;
import serial_xml;

struct Record {
int value;
};

int main() {
const std::string xml = "<Record><value>not-a-number</value></Record>";
try {
const auto record = serial_xml::from_xml<Record>(xml);
std::println("Value: {}", record.value);
} catch (const serial_xml::deserialization_error& error) {
std::println("Read failed");
std::println("{}", error.what());
}
}
Loading
Loading