XML

To use this module, include the following line in your project file:

require engine.format.xml_core // or require engine.core

Serialize typed Daslang values to XML strings and back.

Use from_xml to parse an XML string into a typed Daslang value, and to_xml to print one back out - the mirror of from_json / to_json.

Usage example:

struct Player {
    name : string
    score : int
}

// <player name="Alice" score="42"/>
var loaded : Player
from_xml("<player name=\"Alice\" score=\"42\"/>", loaded)
// loaded.name == "Alice", loaded.score == 42

let xml = to_xml(loaded)   // "<root name=\"Alice\" score=\"42\"/>" (pretty-printed)

from_xml walks from the document’s single root element, so the destination struct maps to that root (the way a JSON object root maps to a struct for from_json). to_xml wraps the value in a <root> element; the root’s own name is irrelevant to from_xml, so the two round-trip.

Mapping convention

One convention decides where each field maps - no per-field configuration is needed beyond the two annotations below - and it is symmetric: to_xml writes what from_xml reads. Ordinary struct/array data round-trips (from_xml(to_xml(x)) == x); the exceptions are listed under Round-trip limitations below.

  • a flat field (bool, int, uint, int64, uint64, float, double, string, enum, bitfield) maps to the same-named attribute of the struct’s element;

  • a struct field maps to a single same-named child element;

  • an array<T> field maps to the repeated same-named child elements, in document order;

  • a field marked @xml_inner_text maps to the element’s text content.

On read, unknown attributes and child elements are ignored, exactly as extra JSON keys are ignored by from_json. Fields whose attribute/element is absent keep the value the destination already has (its field initializer default on a freshly constructed value).

Field annotations

@rename = "key"

Use key as the attribute/element name instead of the field name. The same annotation from_json uses, so one struct reads from both formats. Also needed when the XML name is a Daslang reserved word (e.g. type, template):

struct Object {
    @rename = "type"
    type_ : string
}

// reads the "type" attribute: <object type="hero"/>
@xml_inner_text

Read the field from the element’s text content (PCDATA and CDATA) rather than an attribute or child. Element text has no name, so it cannot follow the same-named rule and needs this marker:

struct Data {
    @rename = "encoding"
    encoding : string       // <data encoding="csv"> attribute
    @xml_inner_text
    text : string           // the text between <data> and </data>
}

CDATA

Text read via @xml_inner_text includes CDATA sections verbatim, so characters that would otherwise need escaping (<, &) pass through unchanged:

struct Note {
    @xml_inner_text
    text : string
}

var note : Note
from_xml("<note><![CDATA[a<b & c]]></note>", note)
// note.text == "a<b & c"

Supported types

  • Primitives: bool, int, uint, int64, uint64, float, double, string

  • Enums and bitfields (enums matched by member name; bitfields parsed from a numeric string)

  • Structs (non-class, non-lambda)

  • array<T> (element type must be serializable)

On read, numeric/bool/string values are parsed from their attribute or text string; a value that fails to parse leaves the field at its current value (its default), so malformed input never corrupts a field. On write, floats and doubles are printed at full precision, so they survive a round trip bit-for-bit. Tables and tuples are not supported.

Round-trip limitations

Ordinary flat/struct/array data round-trips exactly. These shapes do not, because XML has no lossless place for them under the single convention:

  • Vector types (int2..``float4``) have no serializer: a vector field writes nothing and reads back as its default; a vector root writes an empty <root/> (and cannot be read back).

  • Variant arm selection on read is a best-effort guess from the text (there is no type tag), so an arm can change on the round trip when two arms share a text shape: a float arm holding an integral value (2.0 prints as 2) reads back as an int arm; a string arm holding a numeric string ("42") reads as a number arm. A struct arm is picked when the element has a child element, or as the fallback when no flat/string arm matches - so an all-attribute struct arm round-trips as long as no flat/string arm wins classification first.

  • String normalization: leading/trailing whitespace in an attribute value or inner text is kept, but a whitespace-only inner text reads back as "" (the XML parser drops it); a carriage return (\r) is folded to \n; a null string reads back as "".

File loading

The engine imports .xml files as text assets (alongside .txt and .json); read a file’s contents with request_text and pass the string to from_xml.

Functions

from_xml(xml_str: string; value: any)

Deserializes an XML string into a typed Daslang value.

Supports structs, arrays and primitive types, parsing from the document’s root element. The inverse is to_xml, which writes exactly what from_xml reads (from_xml(to_xml(x)) == x).

One mapping convention decides where each field’s value comes from (no per-field DSL):

  • a flat field (int/float/bool/string/enum/bitfield) reads the same-named attribute of the struct’s element;

  • a struct field reads a single same-named child element;

  • an array<T> field reads the repeated same-named child elements, in document order;

  • a field annotated @xml_inner_text reads the element’s text content (PCDATA and CDATA).

@rename = "key" overrides the attribute/element name (the same annotation from_json honors), so a struct annotated for JSON reads from XML unchanged. Unknown attributes and child elements are ignored, exactly as from_json ignores extra JSON keys.

Fields missing in the XML keep the value the destination variable already has. Array elements are created by the deserializer and initialized with the struct’s field initializers (by running its generated zero-argument constructor), so attributes absent on an element read as the declared defaults.

Note

The generated constructor of a struct that is never constructed explicitly in script code can be removed by dead code elimination - array elements of such a struct then stay zero-initialized and the field initializers are lost. Add options always_export_initializer to the module declaring the struct to keep its constructor alive.

Arguments:
  • xml_str : string implicit - XML string to parse

  • value : any - variable to deserialize into (must be var, non-const)

Usage example:

options always_export_initializer // keep struct ctors for from_xml defaults

struct Tile {
    id : int
    @rename = "type"
    type_ : string
    visible : bool = true      // absent attribute -> keeps this default
}

struct Layer {
    name : string
    @rename = "tile"
    tile : array<Tile>         // repeated <tile> children
}

var layer : Layer
from_xml("<layer name=\"ground\"><tile id=\"1\" type=\"grass\"/><tile id=\"2\"/></layer>", layer)
// layer.name == "ground"
// layer.tile[0].id == 1, layer.tile[0].type_ == "grass", layer.tile[0].visible == true
// layer.tile[1].id == 2, layer.tile[1].visible == true (default kept)

// @xml_inner_text reads the element body (CDATA passes '<' and '&' through verbatim)
struct Note {
    @xml_inner_text
    text : string
}
var note : Note
from_xml("<note><![CDATA[a<b & c]]></note>", note)
// note.text == "a<b & c"
to_xml(value: any): string

Serializes a typed Daslang value to an XML string. The inverse of from_xml.

The value is wrapped in a <root> element and pretty-printed. The field mapping is the same one from_xml reads: ordinary flat/struct/array data round-trips (from_xml(to_xml(x)) == x); see Round-trip limitations on the XML page for the exceptions (vectors, variant arm selection, string whitespace/newline normalization). The mapping:

  • a flat field (int/float/bool/string/enum/bitfield) is written as the same-named attribute;

  • a struct field is written as a single same-named child element;

  • an array<T> field is written as the repeated same-named child elements;

  • a field annotated @xml_inner_text is written as the element’s text content.

@rename = "key" overrides the attribute/element name (the same annotation from_json and to_json honor). Floats and doubles are printed at full precision, so a value survives a round trip bit-for-bit.

Arguments:
  • value : any - any serializable value - a struct/array root, or a flat root (int/float/string) which

is written as the root element’s text. Reading back requires a struct/array destination, so a bare flat root does not round-trip.

Returns:
  • string - pretty-printed XML string with a <root> element

Usage example:

struct Player {
    name : string
    @rename = "type"
    type_ : string
    score : int
}

let p = Player(name = "Alice", type_ = "hero", score = 42)
let xml = to_xml(p)
// <root name="Alice" type="hero" score="42" />

var loaded : Player
from_xml(xml, loaded)      // round-trips: loaded == p