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 vector or range field (int2..``float4``, ranges) maps to the same-named attribute holding the space-separated components, e.g. float3(1, 2, 3) <-> "1 2 3";

  • 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 fixed-array field (int[3], float3[2], nested int[3][3]) maps to a same-named child element holding one <item> per element; the size is fixed, so a source with too few items leaves the trailing cells at their declared defaults (subject to the struct-ctor caveat under from_xml) and extra items are ignored;

  • 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

  • Vector and range types (int2..``float4``, ranges): space-separated components in one attribute, e.g. "1 2 3"

  • 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, a float or double is printed in a form that reads back to the same value. Tables and tuples are not supported.

Round-trip limitations

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

  • 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, primitive types and vector/range types (int2..``float4``, ranges), 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); see Round-trip limitations on the XML page for the exceptions (variant arm selection, string whitespace/newline normalization).

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 and fixed-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 - and the trailing cells of a fixed array the XML does not fill - 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 and fixed-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/vector/struct/array data round-trips (from_xml(to_xml(x)) == x); see Round-trip limitations on the XML page for the exceptions (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). A float or double is printed in a form that reads back to the same value.

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 and reads back via from_xml into a flat var.

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