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
16 changes: 14 additions & 2 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ on:
branches:
- main
paths:
- 'CMakeLists.txt'
- 'CMakePresets.json'
- 'cmake/**'
- '.devcontainer/**'
- 'README.md'
- 'include/**'
- '.github/workflows/**'
- 'tests/**'
Expand All @@ -15,6 +20,11 @@ on:
branches:
- main
paths:
- 'CMakeLists.txt'
- 'CMakePresets.json'
- 'cmake/**'
- '.devcontainer/**'
- 'README.md'
- 'include/**'
- '.github/workflows/**'
- 'tests/**'
Expand All @@ -34,16 +44,18 @@ jobs:
with:
push: never
runCmd: |
set -eu
cmake --preset release-test-gcc
cmake --build --preset build-release-test
cd build && ctest --output-on-failure --output-junit test-results.xml
ctest --test-dir build/release --output-on-failure --no-tests=error --output-junit test-results.xml
cmake -DSERIAL_XML_SOURCE_DIR="$PWD" -DSERIAL_XML_BINARY_DIR="$PWD/build/release" -P tests/consumer/run.cmake

- name: Test Report
if: ${{ !cancelled() }}
uses: dorny/test-reporter@v3
with:
name: C++ Tests
path: build/test-results.xml
path: build/release/test-results.xml
reporter: java-junit
fail-on-error: true
list-tests: failed
Expand Down
27 changes: 19 additions & 8 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
cmake_minimum_required(VERSION 4.3)
cmake_minimum_required(VERSION 4.3.3)

set(CMAKE_CXX_STANDARD 26)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(CMAKE_VERSION VERSION_LESS_EQUAL 4.3)
if(CMAKE_VERSION VERSION_LESS 4.4)
set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD "451f2fe2-a8a2-47c3-bc32-94786d8fc91b")
elseif(CMAKE_VERSION VERSION_GREATER_EQUAL 4.4)
set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD "f35a9ac6-8463-4d38-8eec-5d6008153e7d")
Expand All @@ -11,7 +11,7 @@ set(CMAKE_CXX_MODULE_STD 1)

project(
SerialXML
VERSION 0.1.0.0
VERSION 1.0.0
LANGUAGES CXX)

include(FetchContent)
Expand All @@ -22,14 +22,14 @@ if(NOT StructuralTuple_FOUND)
FetchContent_Declare(
structural_tuple
GIT_REPOSITORY https://github.com/EJainDev/StructuralTuple.git
GIT_TAG main)
GIT_TAG 7ad88be28ceece1d6d41da72754c6cf2c969fd6d)

FetchContent_MakeAvailable(structural_tuple)
endif()

option(BUILD_TESTS "Enable building of tests" OFF)
option(BUILD_EXAMPLES "Enable building of examples" ON)
option(BUILD_BENCHMARKS "Enable building of benchmarks" ON)
option(BUILD_EXAMPLES "Enable building of examples" ${PROJECT_IS_TOP_LEVEL})
option(BUILD_BENCHMARKS "Enable building of benchmarks" ${PROJECT_IS_TOP_LEVEL})

include(GNUInstallDirs)
include(CMakePackageConfigHelpers)
Expand All @@ -41,9 +41,16 @@ endif()

# Library

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -freflection")
# StructuralTuple's current release sets reflection flags only in its directory.
# Export them on the fetched target too, including its standard-module
# dependency.
if(TARGET structural_tuple AND NOT StructuralTuple_FOUND)
target_compile_options(structural_tuple PUBLIC -freflection)
endif()

add_library(serial_xml STATIC)
add_library(serial_xml::serial_xml ALIAS serial_xml)
target_compile_options(serial_xml PUBLIC -freflection)
target_sources(
serial_xml
PUBLIC FILE_SET
Expand All @@ -57,7 +64,11 @@ target_sources(
target_include_directories(
serial_xml PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>)
target_link_libraries(serial_xml PUBLIC structural_tuple)
if(TARGET structural_tuple::structural_tuple)
target_link_libraries(serial_xml PUBLIC structural_tuple::structural_tuple)
else()
target_link_libraries(serial_xml PUBLIC structural_tuple)
endif()
target_compile_features(serial_xml PUBLIC cxx_std_26)

# Installation configuration
Expand Down
13 changes: 7 additions & 6 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ Before submitting a bug report, ensure the issue hasn't been reported already. P

To report compile errors, specify:
1. The exact error message or symptom.
2. The compiler and version (for example, GCC 16.1) and the CMake version used (for example, 4.3.0).
2. The compiler and version (for example, GCC 16.1) and the CMake version used (for example, 4.4.3).
3. The full compiler output / compilation log.
4. A minimal, self-contained reproducible example (smallest code that reproduces the problem).
5. The exact build commands or CMake preset used to reproduce the issue (for example: `cmake --preset debug-gcc-16` and `cmake --build --preset build-debug`).
5. The exact build commands or CMake preset used to reproduce the issue (for example: `cmake --preset debug-test-gcc` and `cmake --build --preset build-debug-test`).

### Runtime Bugs

Expand All @@ -57,14 +57,15 @@ When possible, prefer **Implement and test (PR)** so the change can be reviewed
## Local Setup Instructions

1. Clone the repository.
2. Install GCC 16.1 and CMake 4.3 or higher.
3. Configure the project using the `debug-gcc-16` preset: `cmake --preset debug-gcc-16`
4. Build the project using the `build-debug` preset: `cmake --build --preset build-debug`
2. Install GCC 16.1 and CMake 4.3.3 or higher.
3. Configure the project using the `debug-test-gcc` preset: `cmake --preset debug-test-gcc`
4. Build the project using the `build-debug-test` preset: `cmake --build --preset build-debug-test`

## Testing Guidelines

- Use Google Test as the testing framework.
- Run the full test suite before submitting changes: `ctest --preset build-debug`
- Run the full test suite before submitting changes: `ctest --test-dir build/debug --output-on-failure --no-tests=error`
- Check both consumer paths after a build: `cmake -DSERIAL_XML_SOURCE_DIR="$PWD" -DSERIAL_XML_BINARY_DIR="$PWD/build/debug" -P tests/consumer/run.cmake`.
- All tests must pass. Do not break existing functionality.
- Add new tests for new features.
- Add regression tests for bug fixes.
Expand Down
78 changes: 59 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# SerialXML
# SerialXML 1.0

![Banner](./assets/SerialXML.png)

![Build and Tests](https://github.com/EJainDev/SerialXML/actions/workflows/build-and-test.yml/badge.svg)
![C++26](https://img.shields.io/badge/C%2B%2B-26-blue)
![CMake 4.3+](https://img.shields.io/badge/CMake-4.3%2B-orange)
![CMake 4.3.3+](https://img.shields.io/badge/CMake-4.3.3%2B-orange)
![License](https://img.shields.io/badge/license-MIT-lightgray)

> Reflection based XML serialization and deserialization for C++26
Expand Down Expand Up @@ -41,10 +41,10 @@ import serial_xml;
struct Person {
int age;
std::string favorite_food;
}
};

int main() {
std::print(to_xml(Person{3, "pizza"}));
std::print("{}", serial_xml::to_xml(Person{3, "pizza"}));
}
```

Expand Down Expand Up @@ -171,11 +171,21 @@ attribute whitespace follow XML normalization rules.
cannot be separated and causes an error. `raw` ranges read matching item tags
directly from the parent. Unpacked objects and automatically handled optionals
keep their tagged representation even with `raw`, following `to_xml` precedence. CDATA fields read the parent's CDATA sections in member
order, matching `to_xml`'s unwrapped CDATA output. Optional or externally split
CDATA fields can be ambiguous; use tagged string fields when boundaries must be
order, matching `to_xml`'s unwrapped CDATA output. The writer splits `]]>` across CDATA sections and emits carriage returns as
character references; the reader rejoins those canonical continuations. Adjacent
fields whose boundaries look exactly like a canonical split (a field ending in
`]]` followed by one starting in `>`) and optional or externally split CDATA fields
can be ambiguous; use tagged string fields when boundaries must be
unambiguous. Standard tagged string fields can contain mixed ordinary text and
CDATA sections, which are concatenated.

Serialization rejects forbidden XML 1.0 characters and malformed UTF-8 with
`std::invalid_argument`, including custom formatter output. Carriage returns in
text and tabs/newlines/carriage returns in attributes use character references to
preserve their values. Annotation names must be valid XML 1.0 names at compile
time; invalid runtime root overrides throw `std::invalid_argument`. Unicode,
underscores, and literal namespace prefixes are accepted.

See [the deserialization example](examples/deserialization.cpp) for round trips,
custom string conversion, optional fields, and independent setters.

Expand All @@ -184,15 +194,27 @@ custom string conversion, optional fields, and independent setters.
### CMake FetchContent (Recommended)

```cmake
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_tests test.cpp)
target_link_libraries(my_tests PRIVATE serial_xml::serial_xml)
add_executable(my_app main.cpp)
target_link_libraries(my_app PRIVATE serial_xml::serial_xml)
```

### Install from source
Expand All @@ -201,26 +223,36 @@ target_link_libraries(my_tests PRIVATE serial_xml::serial_xml)
git clone https://github.com/EJainDev/SerialXML.git
cd SerialXML

cmake --preset "release-gcc-16"
cmake --build build

cmake --install build
cmake --preset release-gcc -DBUILD_BENCHMARKS=OFF
cmake --build --preset build-release
cmake --install build/release --prefix "$HOME/.local"
```

Then, in your `CMakeLists.txt`, put:
Use the same compiler and standard-module setup shown above, then replace the
FetchContent block with:
```cmake
find_package(SerialXML REQUIRED)
find_package(SerialXML 1.0 CONFIG REQUIRED)
add_executable(my_app main.cpp)
target_link_libraries(my_app PRIVATE serial_xml::serial_xml)
```

Configure your application with `-DCMAKE_PREFIX_PATH="$HOME/.local"` for this
install prefix. A source install also installs the fetched StructuralTuple dependency;
if using an existing StructuralTuple package, make its prefix available to consumers too.
Reflection compiler options and the C++26 requirement propagate from the library target.
FetchContent builds default to disabling examples and benchmarks when embedded.

### Requirements

| Component | Min Version | Notes |
| --------- | ----------- | ----- |
Compiler | GCC 16.1 | C++26 SIMD, Reflection, and more |
CMake | 4.3 | Change `std` experiment key for lower versions |
CMake | 4.3.3 | Ninja; experimental `import std` enabled before `project()` |
C++ Standard | 26 | SIMD, Reflection, Annotations |

The dev container uses GCC 16.2.0 and CMake 4.4.3. Other compilers are not currently
supported. StructuralTuple is pinned to a tested commit.

## Benchmarks

The same executable compares serialization and deserialization with SerialXML,
Expand Down Expand Up @@ -375,7 +407,15 @@ See [the mocking example](examples/mocking.cpp).

### The `prettify` function

A simple function to prettify (add indentation and newlines) the generated XML output. The only parameter is the output and it returns a new string with the output.
`serial_xml::prettify(xml)` validates the input and returns XML with two-space
indentation for element-only content. Subtrees containing direct text (including
whitespace), CDATA, or an `xml:space` attribute retain their original bytes. This
preserves string leaf values and mixed content, including quoted `>` characters,
comments, and processing instructions. Malformed input throws `deserialization_error`.

Indentation adds whitespace between elements. Use compact `to_xml` output for
round trips involving raw scalar fields alongside child elements, or when every
whitespace node matters.

## Examples

Expand All @@ -384,9 +424,9 @@ major SerialXML feature. Examples are built by default; configure the project an
run an executable from the build directory, for example:

```bash
cmake --preset "release-gcc-16"
cmake --build build
./build/examples/hello_world
cmake --preset release-gcc -DBUILD_BENCHMARKS=OFF
cmake --build --preset build-release
./build/release/examples/hello_world
```

Set `-D BUILD_EXAMPLES=OFF` when configuring CMake to omit them from your build.
Expand Down
5 changes: 5 additions & 0 deletions cmake/SerialXMLConfig.cmake.in
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
@PACKAGE_INIT@

include(CMakeFindDependencyMacro)
find_dependency(StructuralTuple CONFIG)

include("${CMAKE_CURRENT_LIST_DIR}/serial_xml_targets.cmake")

if(NOT TARGET serial_xml)
add_library(serial_xml ALIAS serial_xml::serial_xml)
endif()

check_required_components(SerialXML)
Loading
Loading