AxlXml — XML

Streaming XML writer + pull-token reader. See AxlData — Data Structures for an overview of all data modules including the XML writer/reader pair.

Header: <axl/axl-xml.h>

API Reference

Defines

AXL_XML_WRITER_MAX_DEPTH

Maximum open-tag nesting the writer’s balance stack tracks. Exceeding this sets the sticky error flag.

Typedefs

typedef struct AxlXmlReader AxlXmlReader

Forward decl — opaque type.

Enums

enum AxlXmlWriterFlags

axl-xml.h:

Streaming XML writer + pull-token reader. Caller-managed namespaces: the writer treats qnames like D:multistatus as opaque strings; namespace declarations are normal attributes. The reader returns one token at a time — START_ELEMENT, END_ELEMENT, TEXT, END_DOCUMENT — and attribute lookup is via axl_xml_reader_attr while positioned at a START_ELEMENT.

Out of scope (intentional): DTD validation, schema validation (XSD / RelaxNG), XPath, XSLT, XML signatures. UTF-8 only.

Two independent APIs:

  • AxlXmlWriter — build XML into an AxlString

  • AxlXmlReader — pull tokens from an XML buffer AxlXmlWriterFlags:

Flags passed to axl_xml_writer_init.

Values:

enumerator AXL_XML_WRITER_DEFAULT

compact output (no indent / no newlines)

enumerator AXL_XML_WRITER_PRETTY

2-space indent + newlines between elements

enum AxlXmlTokenType

AxlXmlTokenType:

Discriminator for AxlXmlToken. END_DOCUMENT is delivered once after the last element closes; subsequent axl_xml_reader_next calls return false.

Values:

enumerator AXL_XML_TOKEN_START_ELEMENT

<qname ...> (or self-closing <qname/>)

enumerator AXL_XML_TOKEN_END_ELEMENT

</qname> (or self-closing pair)

enumerator AXL_XML_TOKEN_TEXT

body text or CDATA section content

enumerator AXL_XML_TOKEN_END_DOCUMENT

no more tokens

Functions

void axl_xml_writer_init(AxlXmlWriter *w, AxlString *out, uint32_t flags)

Initialize a writer.

The writer appends to out — it does not clear it. To reuse a string between writes, the caller calls axl_string_clear before init.

Parameters:
  • w – writer to initialize

  • out – destination string (caller-owned)

  • flags – AxlXmlWriterFlags bitmask

size_t axl_xml_writer_finish(AxlXmlWriter *w)

Finalize the writer.

Validates that all opened elements were closed; sets the sticky error flag if not. Frees any heap state the writer still owns (tag stack entries). NULL-safe.

Parameters:
  • w – writer (NULL-safe)

Returns:

the length of out after this writer’s calls. (Equal to the bytes written iff the AxlString was empty at axl_xml_writer_init; otherwise includes any pre-existing content.)

bool axl_xml_writer_error(const AxlXmlWriter *w)

Query the sticky error flag.

Set on AxlString OOM, structural misuse (close-without-start, close-of-wrong-tag, stack overflow), or write-after-finish. Once set, all subsequent writer calls become no-ops.

Parameters:
  • w – writer

Returns:

true if any error occurred since init.

void axl_xml_writer_prologue(AxlXmlWriter *w)

Emit the XML prologue: <?xml version="1.0" encoding="UTF-8"?>.

Must be the first emit call after init (writer is otherwise unconstrained — prologue is optional). Calling it after any element-emitting call sets the sticky error flag.

Parameters:
  • w – writer

void axl_xml_writer_doctype(AxlXmlWriter *w, const char *root, const char *dtd_uri)

Emit a DOCTYPE declaration: <!DOCTYPE root SYSTEM "dtd_uri">.

dtd_uri may be NULL to emit a bare <!DOCTYPE root>. Must appear before the first element start; later calls set the sticky error flag. Neither root nor dtd_uri is escaped — caller is responsible for providing values legal in those positions (no < > " &).

Parameters:
  • w – writer

  • root – root element name

  • dtd_uri – DTD system URI (NULL → bare DOCTYPE)

void axl_xml_writer_start_element(AxlXmlWriter *w, const char *qname)

Open an element: <qname.

The element stays “open” — axl_xml_writer_attribute calls add attributes, then the start tag is auto-closed (<qname ...>) on the first child / text / end. Nesting limit: AXL_XML_WRITER_MAX_DEPTH.

qname is treated as opaque (writer does not split namespace prefix from local name) and is NOT escaped — caller is responsible for the name being legal in tag position. Caller also manages namespace declarations via axl_xml_writer_attribute (e.g. attr xmlns:D = "DAV:").

Parameters:
  • w – writer

  • qname – element qname (NUL-terminated)

void axl_xml_writer_attribute(AxlXmlWriter *w, const char *name, const char *value)

Add an attribute to the currently-open start tag.

Only valid between axl_xml_writer_start_element and the next text/child/end call. Calling outside that window — or with an empty name — sets the sticky error flag. value is auto-escaped for & < ". name is NOT escaped; caller provides a legal attribute name.

Parameters:
  • w – writer

  • name – attribute name

  • value – attribute value (escaped)

void axl_xml_writer_text(AxlXmlWriter *w, const char *text)

Emit body text content for the current element.

Auto-escapes & < >. Multiple text calls between a start and end concatenate. NUL-terminated input variant. An empty text is a silent no-op (the element may still self-close as <foo/> at axl_xml_writer_end_element); to force <foo></foo>, omit the empty text call entirely — the difference is purely stylistic since <foo/> and <foo></foo> are XML-equivalent.

Parameters:
  • w – writer

  • text – NUL-terminated text

void axl_xml_writer_textn(AxlXmlWriter *w, const char *text, size_t n)

Length-counted variant of axl_xml_writer_text. Allows emitting non-NUL-terminated slices of a larger buffer.

Parameters:
  • w – writer

  • text – text bytes

  • n – byte count

void axl_xml_writer_end_element(AxlXmlWriter *w)

Close the most recently opened element.

Emits </qname> (or /> if the element has no body content). Order is enforced: closing an element when the stack top doesn’t match implies a caller-side balance bug — the writer can’t detect “wrong tag” because the closer doesn’t name it, but close-without-start does set the sticky error flag.

Parameters:
  • w – writer

AxlXmlReader *axl_xml_reader_new(const char *buf, size_t len)

Create a reader over buf.

The reader references buf — caller must keep the buffer alive until axl_xml_reader_free. NUL terminator not required. Returns NULL on OOM.

Parameters:
  • buf – XML bytes

  • len – byte count

bool axl_xml_reader_next(AxlXmlReader *r, AxlXmlToken *out)

Pull the next token.

On success, fills out with the next token’s data and returns true. On parse error or after END_DOCUMENT was delivered, returns false; call axl_xml_reader_error to distinguish.

The reader yields START_ELEMENT, END_ELEMENT, TEXT, and a single END_DOCUMENT after the last close. XML declaration (<?xml ?>), processing instructions, comments, and DOCTYPE declarations are skipped silently. CDATA section content arrives as a TEXT token with is_cdata set.

Parameters:
  • r – reader

  • out – [out] filled on success

const char *axl_xml_reader_attr(AxlXmlReader *r, const char *name)

Look up an attribute on the current START_ELEMENT.

Valid between a successful axl_xml_reader_next that returned a START_ELEMENT and the next axl_xml_reader_next call. Returns NULL if the attribute isn’t present, the reader is not positioned at a START_ELEMENT, or name is NULL. The returned pointer references reader-owned storage and is valid until the next axl_xml_reader_next.

Parameters:
  • r – reader

  • name – attribute name (NUL-terminated)

bool axl_xml_reader_error(const AxlXmlReader *r, uint32_t *line, uint32_t *col, const char **msg)

Retrieve error details after a false return from axl_xml_reader_next.

Each out-param is optional (NULL skips). After clean EOF (AXL_XML_TOKEN_END_DOCUMENT was the last delivered token), returns false with no error message. After a parse error, returns true and fills line / column / message.

Parameters:
  • line – [out, optional] 1-based line of the error

  • col – [out, optional] 1-based column of the error

  • msg – [out, optional] static error message

Returns:

true iff a parse error has occurred.

void axl_xml_reader_free(AxlXmlReader *r)

Free the reader (NULL-safe).

Releases heap state. Does NOT free the input buffer.

Parameters:
  • r – reader (NULL-safe)

const char *axl_xml_token_local_name(const AxlXmlToken *tok, size_t *out_len)

Return the local name (post-colon) of tok's qname.

For a prefixed name like D:response, returns a pointer to response and writes 8 into out_len. For an unprefixed name like response, returns the full name. The returned pointer aliases into tok's name slice and is valid for the same lifetime (until the next axl_xml_reader_next).

Defined only for START_ELEMENT and END_ELEMENT tokens. On TEXT / END_DOCUMENT (or NULL inputs) returns NULL with *out_len = 0.

Parameters:
  • out_len – [out] local-name byte length (NULL OK)

bool axl_xml_token_local_name_eq(const AxlXmlToken *tok, const char *want)

Compare tok's local name to a NUL-terminated literal.

Convenience predicate folding the local-name extract + memcmp. Returns false for TEXT / END_DOCUMENT tokens, NULL inputs, or length / byte mismatches.

struct AxlXmlWriter
#include <axl-xml.h>

AxlXmlWriter:

Streaming XML writer that builds into a caller-owned AxlString. Element start / attribute / text / end are independent calls; the state machine tracks open-tag depth so closes can’t outrun starts. Auto-escapes & < > in text, plus " in attribute values. Fields are private — use accessors.

Errors are sticky: once any call detects a structural misuse (close-without-start, stack overflow, attribute-after-content, prologue / doctype after first element) or the backing AxlString fails to grow, every subsequent call is a no-op. Check via axl_xml_writer_error at finish.

Public Members

AxlString *out

backing store (caller-owned)

uint32_t flags

AxlXmlWriterFlags.

uint32_t depth

current open-tag depth (0..MAX)

bool in_start_tag

inside <foo, attrs/text/close pending

bool prologue_emitted

prologue may only be emitted once

bool doctype_emitted

doctype may only be emitted once

bool any_element_emitted

root element has been started

bool error

sticky error flag

uint64_t had_text_bits

bit i: depth-i element has body text

uint64_t had_child_bits

bit i: depth-i element has a child element

char *stack[64]

Tag-name stack: stack[i] is the qname of the element opened at depth i. strdup’d at start, freed at end. Used in pretty mode (where we need the name on the closing tag emit path) and the close-without-start guard.

struct AxlXmlToken
#include <axl-xml.h>

AxlXmlToken:

One pulled token. String pointers reference into reader-owned storage and are valid only until the next axl_xml_reader_next call. Caller must copy out anything it needs to retain past that.

Public Members

AxlXmlTokenType type
const char *name

element qname (START / END only); NULL otherwise

size_t name_len
const char *ns_uri

Resolved namespace URI for this element (START / END only). NULL when no xmlns binding is in scope. The reader maintains an internal xmlns binding stack: a prefixed qname (e.g. D:foo) resolves against the nearest enclosing xmlns:D= declaration; an unprefixed name resolves against the nearest enclosing xmlns= (default-namespace) declaration. Use axl_xml_token_local_name to extract the post-colon part.

Known limitations (lenient v1; tighten when a consumer asks):

  • The reserved xml: prefix is NOT implicitly bound to . Elements like <xml:lang> without an explicit xmlns:xml= declaration resolve to NULL ns_uri.

  • xmlns="" (the empty-string undeclare-default-ns form per Namespaces 1.0 errata) is honored: subsequent unprefixed children resolve to NULL ns_uri, not “”.

size_t ns_uri_len
const char *text

text bytes (TEXT only); NULL otherwise

size_t text_len
bool is_cdata

TEXT only: true iff token came from <![CDATA[ ... ]]>