Skip to content

Repository files navigation

libtaurus: High-Performance XML Parser & XPath Engine in C

Language Runtime Dependencies License Tests ASAN Fuzzing

libtaurus is a C99 library providing fast XML 1.0 parsing, complete XPath 1.0 evaluation, event-based SAX processing, and document canonicalization (C14N). It is designed for low-latency, memory-efficient workloads where predictable performance and safe memory handling matter.

  • XML 1.0 parsing with UTF-8 validation and optional encoding conversion (UTF-16, ISO-8859, Shift-JIS, EBCDIC, and others via iconv).

  • XPath 1.0 engine implementing all 13 axes, 27 functions, and 15 operators.

  • SAX API for event-driven streaming on documents that don’t fit in a DOM.

  • Canonical XML (C14N) for digital signatures and cryptographic hashing.

  • Pool-based memory model — every allocation reachable from a document is released in a single taurus_document_free call. Zero leaks across the test suite.

  • Recursion depth guard — deeply nested input is rejected with a parse error rather than crashing.

  • Per-document strict mode — strict and lenient parsing can coexist in the same thread.

  • Vtable-based dispatch — adding a new node type is purely additive; no switches to edit.

  • CLI tooltaurus parse, taurus xpath, taurus format, taurus version for command-line XML processing.

  • Zero required runtime dependencies — utf8proc and iconv are optional features, not prerequisites.

  • Stable C ABI with a documented FFI contract; bindings planned for Ruby, Python, and Rust. See FFI Design.

Use libtaurus when Consider alternatives when

You need a C library with a small footprint and no required runtime dependencies.

You need full XML Schema 1.1 validation.

You parse documents that may not fit in memory (SAX streaming).

You need XSLT 1.0 / 2.0 transformation.

You need deterministic memory usage and zero leaks in normal operation.

You need XQuery 1.0 (XPath only here).

You want to embed XML processing in another language via FFI.

You’re already on a platform with libxml2 + bindings you trust.

==

#include <taurus.h>
#include <stdio.h>
#include <string.h>

int main(void) {
    const char* xml = "<root><item>hello</item></root>";

    TaurusStatus status = TAURUS_OK;
    TaurusDocument doc = taurus_parse_string(xml, strlen(xml), &status);
    if (!doc) {
        fprintf(stderr, "parse failed: %d\n", status);
        return 1;
    }

    TaurusElement root = taurus_document_root(doc);
    printf("root element: %s\n", taurus_element_name(root));

    TaurusXPathResult items = taurus_xpath_eval(doc, NULL, "//item");
    printf("item count: %zu\n", taurus_xpath_result_count(items));
    taurus_xpath_result_free(items);

    taurus_document_free(doc);  /* releases the entire pool */
    return 0;
}

Compile and run:

cmake -B build -S . -DCMAKE_BUILD_TYPE=Release
cmake --build build
./build/cli/taurus parse 'fixtures/basic.xml'
./build/cli/taurus xpath 'fixtures/basic.xml' 'count(//item)'
cmake -B build -S . -DCMAKE_BUILD_TYPE=Release
cmake --build build
sudo cmake --install build --prefix /usr/local

After install, the library is discoverable via find_package(taurus):

cmake_minimum_required(VERSION 3.20)
project(myapp LANGUAGES C CXX)

find_package(taurus CONFIG REQUIRED)
target_link_libraries(myapp PRIVATE taurus::taurus)

Or via pkg-config:

gcc myapp.c $(pkg-config --cflags --libs taurus)
git clone https://github.com/microsoft/vcpkg
./vcpkg/vcpkg install taurus

See vcpkg integration for the portfile template.

Distro Install command

Alpine

apk add libtaurus-dev (pending)

Debian/Ubuntu

apt install libtaurus-dev (pending)

Homebrew

brew install taurus (pending)

MSYS2 (Windows)

pacman -S mingw-w64-taurus (pending)

Option Default Description

BUILD_TESTING

ON

Build the Google Test suite under test/.

TAURUS_BUILD_CLI

ON

Build the taurus command-line tool.

TAURUS_BUILD_BENCHMARKS

OFF

Build performance comparison targets (libxml2 / pugixml).

TAURUS_BUILD_MAN_PAGES

OFF

Generate man pages from the AsciiDoc sources.

TAURUS_ENABLE_UTF8PROC

ON

UTF-8 validation via utf8proc.

TAURUS_ENABLE_ICONV

ON

Encoding conversion via iconv (ISO-8859-1, Shift-JIS, etc.).

TAURUS_ENABLE_ASAN

OFF

Build with AddressSanitizer.

TAURUS_ENABLE_FUZZING

OFF

Build the libFuzzer harness.

TAURUS_BUILD_DOCS

OFF

Generate Doxygen API docs.

cmake -B build-asan -S . -DTAURUS_ENABLE_ASAN=ON -DBUILD_TESTING=ON
cmake --build build-asan
ASAN_OPTIONS=detect_leaks=1 ctest --test-dir build-asan
brew install llvm   # macOS
export CC=/opt/homebrew/opt/llvm/bin/clang
cmake -B build-fuzz -S . -DTAURUS_ENABLE_FUZZING=ON
cmake --build build-fuzz --target fuzz_parse
./build-fuzz/fuzz_parse -max_total_time=600 corpus/
brew install doxygen
cmake -B build -S . -DTAURUS_BUILD_DOCS=ON
cmake --build build --target docs
open build/docs/api-generated/html/index.html

The library exposes a single import target:

target_link_libraries(your_app PRIVATE taurus::taurus)
taurus_dep = dependency('taurus')
executable('your_app', 'main.c', dependencies: taurus_dep)

taurus_parse_string is the entry point. It accepts a UTF-8 buffer and a status output parameter:

TaurusStatus status;
TaurusDocument doc = taurus_parse_string(xml, strlen(xml), &status);
if (!doc) {
    /* status is one of TAURUS_ERROR_PARSE, TAURUS_ERROR_MEMORY, ... */
}

/* Document is now a pool of nodes; no need to track them individually. */

/* Always release the document — the pool is destroyed too. */
taurus_document_free(doc);
TaurusXPathResult r = taurus_xpath_eval(doc, NULL, "//item[@price > 10]");
if (r) {
    size_t n = taurus_xpath_result_count(r);
    for (size_t i = 0; i < n; i++) {
        TaurusNodeRef node = taurus_xpath_result_node(r, i);
        printf("  %s\n", taurus_node_name(node));
    }
    taurus_xpath_result_free(r);
}

Supported: all 13 axes, all 27 functions, all 15 operators, full predicate syntax. See xpath-coverage for details.

static void on_start(void* ud, const char* name, const char** attrs) {
    fprintf(stderr, "<%s>\n", name);
}

TaurusSAXHandler handler = {0};
handler.start_element = on_start;

taurus_sax_parse(xml, len, &handler, NULL);
TaurusSerializeOptions opts = { .indent = 2, .xml_declaration = 1 };
char* out = taurus_document_serialize(doc, &opts);
puts(out);
taurus_free_string(out);
char* canonical = taurus_c14n_canonicalize(doc, TAURUS_C14N_1_0, 0);
fputs(canonical, stdout);
putchar('\n');  /* canonical output may not end with newline */
taurus_free_string(canonical);
taurus_free_string(canonical);

Every byte the parser allocates that ends up referenced by a document lives in the document’s pool. taurus_document_free destroys the pool and releases everything in one call.

Allocation Where it lives

Node structs (element, text, comment, CDATA, PI, doctype)

Pool, allocated contiguously with content where possible.

Node content strings

Pool, contiguous with the struct (cache locality).

Attribute names

Pool hash table (interned; dedup across elements).

Attribute values

Pool, bypassing interning (attrs.xml regression fixed).

DTD container + hash tables

Pool, with DTD subsystem owned by the document.

XPath intermediates

Pool, freed at result destruction.

For bindings: the C API has opaque handles. All freeing is explicit. See the Memory: comment on each public function.

Opaque handles are pointer-sized — enforced at compile time:

_Static_assert(sizeof(TaurusDocument) == sizeof(void*), "...");

To pin enum values (bindings hard-code these):

ctest --test-dir build -R HeaderHygiene

libtaurus exposes a stable C ABI. Bindings planned:

  • Ruby — via ruby-ffi (no compilation)

  • Python — via cffi (header-aware)

  • Rust — via bindgen + idiomatic wrapper

To parse the headers from a binding tool:

cc -DTAURUS_FOR_BINDGEN -E src/include/taurus.h   # strips TAURUS_API

See docs/FFI.md for the full design document.

# Parse a document
taurus parse document.xml

# Round-trip via XPath count
taurus xpath --count document.xml 'count(//item)'

# Pretty-print
taurus format --indent 4 document.xml < ugly.xml > pretty.xml

# Validate / version
taurus version

Exit codes: 0 on success, 1 on parse error or invalid usage.

Taurus is benchmarked against libxml2 and pugixml on every push via CI (GitHub Actions, Linux + macOS). Numbers below are from Apple Silicon, clang -O3 -flto=thin (LTO is default for Release builds since TODO 110).

Benchmark Taurus libxml2 Advantage

SAX small (~1 KB)

2.8 µs (377 MB/s)

7.1 µs (124 MB/s)

2.5× faster

SAX medium (~5 KB)

7.5 µs (624 MB/s)

26.9 µs (175 MB/s)

3.6× faster

DOM parse (~5 KB)

33 µs

47 µs

1.4× faster

Benchmark Taurus libxml2 Advantage

Attribute lookup by name

1.6 µs

3.0 µs

1.9× faster

Text content extraction

1.4 µs

3.5 µs

2.6× faster

Indexed child access (1000 × 50)

2.3 µs (O(1) cached)

2.5 µs

9% faster

Benchmark Taurus pugixml libxml2

Append 1000 children

15.0 µs

11.9 µs

56.2 µs

Set 100 attributes

43.3 µs

10.4 µs

33.4 µs

Set text

0.9 µs

0.7 µs

0.8 µs

Parse + 10 writes (medium)

30.7 µs

5.1 µs

41.6 µs

Taurus beats libxml2 on every benchmark except set-text (1.15× slower). Against pugixml, append is within 1.25× and set-text within 1.3×. The remaining gap (set-attributes) is due to node layout — see TODO 90 for the compact-storage migration plan.

LTO is enabled by default for Release and RelWithDebInfo builds. Disable with -DTAURUS_ENABLE_LTO=OFF:

cmake -B build -S . -DCMAKE_BUILD_TYPE=Release
cmake --build build          # LTO is on automatically

# or explicitly:
cmake -B build -S . -DCMAKE_BUILD_TYPE=Release -DTAURUS_ENABLE_LTO=ON
cmake -B build -S . \
  -DCMAKE_BUILD_TYPE=Release \
  -DTAURUS_BUILD_BENCHMARKS=ON
cmake --build build

# Run individual benchmarks:
./build/benchmarks/bench_dom_taurus
./build/benchmarks/bench_sax_taurus
./build/benchmarks/benchmark_write     # vs pugixml + libxml2
./build/benchmarks/bench_xpath_pugixml  # XPath vs pugixml

CI uploads a benchmark-results-<os> artifact per push with JSON
Markdown output from every benchmark binary.

A vcpkg port pattern follows the jemalloc convention (see tamatebako/jemalloc/ports/jemalloc/). The library ships with:

  • A vcpkg.json (manifest) for vcpkg consumption.

  • A portfile.cmake template for vcpkg port submission.

  • A usage file documenting the linkage pattern.

# portfile.cmake (excerpt — see repo for full version)
vcpkg_cmake_configure(
    SOURCE_PATH "${SOURCE_PATH}"
    OPTIONS
        -DTAURUS_BUILD_CLI=OFF
        -DTAURUS_ENABLE_UTF8PROC=ON
        -DTAURUS_ENABLE_ICONV=ON
)
vcpkg_cmake_install()
vcpkg_cmake_config_fixup(CONFIG_PATH lib/cmake/taurus)
  • CMake ≥ 3.20

  • C99 compiler (GCC, Clang, MSVC, MinGW)

  • Optional: utf8proc (Unicode), iconv (encoding conversion), Doxygen (API docs)

No runtime dependencies when built without utf8proc/iconv.

Workflow Triggers What it does

.github/workflows/test.yml

Every push/PR

Build, run all 103 specs across 13 modules.

.github/workflows/asan.yml

Every push/PR

Build with AddressSanitizer; verify zero leaks / errors.

.github/workflows/fuzz-nightly.yml

Nightly cron

libFuzzer for 5 minutes; report any crashes.

taurus/
  src/                  # library + CLI + tests source
    include/            # public C API headers
    taurus/              # internal C source
      dom/              # DOM node types + pool
      parse/            # parser
      xpath/            # XPath evaluator
      sax/              # SAX parser
      encode/           # UTF-16 + iconv
      serialize/        # output writer
      memory/           # pool allocator
      dtd/              # DTD subsystem
    cli/                # command-line tool
  test/                 # 103 specs across 13 modules
  benchmark/            # libxml2 / pugixml comparisons
  TODO.fix/             # local scratchpad (gitignored)
  archive/              # historical / disabled code
  .github/workflows/    # CI
  docs/                 # README, building guide, FFI design
==

== Roadmap

See link:archive/README.md[archive/README.md] for historical
context, and link:docs/FFI.md[docs/FFI.md] for the FFI roadmap.

In-flight:

* Ruby / Python / Rust bindings.
* Doxygen API reference polish.
* Split of `taurus.c` (2900 lines → focused modules).
* Unified string ownership model.

== Contributing

Issues and pull requests at
https://github.com/lutaml/taurus[github.com/lutaml/taurus].

For C contribution, see link:docs/guide/building.md[docs/guide/building.md].
For the testing policy, see `test/README.md`.

== Acknowledgments

This project draws structural inspiration from several long-running
C projects in the wider ecosystem:

* *jemalloc* (Tebako fork) — memory model and CI patterns.
* *libxml2* — public API ergonomics.
* *pugixml* — pool-based XML DOM, performance targets.

== License

MIT.  See link:LICENSE.md[LICENSE.md].

About

Ultra-fast XML parser with full XPath support in Ruby

Resources

Code of conduct

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages