NAME

Text::KDL::XS - KDL Document Language parser and emitter built on libckdl

SYNOPSIS

use utf8;                             # this source file is UTF-8
use Text::KDL::XS qw(parse_kdl emit_kdl);
binmode STDOUT, ':encoding(UTF-8)';   # parsed strings are Perl characters

# Parse a character string (or a filehandle, or a code reference returning chunks).
my $doc = parse_kdl(<<'KDL');
package "kdl-rs" {
    version "0.4.0"
    author "Kat Marchán" email="kat@example.com"
    keywords "config" "data"
}
KDL

# Walk the tree.
for my $node (@{ $doc->nodes }) {
    print $node->name, "\n";                              # package
    print $node->args->[0]->as_string, "\n";              # kdl-rs
    for my $child (@{ $node->children }) {
        printf "  %s", $child->name;
        printf " %s", $_->as_string for @{ $child->args };
        printf " %s=%s", $_->[0], $_->[1]->as_string for @{ $child->props };
        print "\n";
    }
}

# Look up a property, with type information intact.
my $email = $doc->nodes->[0]->children->[1]->prop('email');
print $email->type;        # string
print $email->as_string;   # kat@example.com

# Write it back out (tree mode: preserves order, kinds and annotations).
print emit_kdl($doc);

# Or serialise plain Perl data (data mode).
print emit_kdl({ server => { host => 'localhost', port => 8080 } });
# server {
#     host localhost
#     port 8080
# }

DESCRIPTION

Text::KDL::XS reads and writes documents in the KDL Document Language, a small configuration and data language that looks like this:

node "argument" key="value" {
    child 1 2 3
    (type)annotated #true
}

It is a thin XS binding to ckdl, a C11 implementation that passes the official KDL test suites. Both KDL 2.0.0 (current) and KDL 1.0.0 (legacy) are supported, and the version is detected automatically unless you pin it.

The distribution provides:

A tour of every KDL feature with runnable examples is in Text::KDL::XS::Cookbook. This page is the API reference.

QUICK REFERENCE

parse a string, a file, a stream

"parse_kdl", "Sources"

choose KDL v1 or v2, detect the version

"parse_kdl options", "KDL VERSIONS"

write KDL from Document and Node objects

"emit_kdl", "Tree mode"

write KDL from hashes and arrays

"Data mode", "Scalar coercion"

control indentation, escaping, quoting

"emit_kdl options"

read comments and slashdashed elements

Text::KDL::XS::Parser

access nodes, arguments, properties, children

Text::KDL::XS::Node

strings, numbers, booleans, null, type annotations

Text::KDL::XS::Value

big integers, 1e400, #inf, #nan, number kinds

"VALUE MODEL" in Text::KDL::XS::Value, "ARBITRARY PRECISION NUMBERS" in Text::KDL::XS::Value

UTF-8 and character strings

"ENCODING"

filehandles, STDIN, pipes, in-memory handles

"FILEHANDLE SOURCES"

untrusted input, nesting limits

"parse_kdl options", "Parse untrusted input" in Text::KDL::XS::Cookbook

what dies and when

"ERRORS"

threads and fork

"THREADS"

known problems and workarounds

"KNOWN ISSUES AND LIMITATIONS"

feature-by-feature examples and recipes

Text::KDL::XS::Cookbook

EXPORTS

Nothing is exported by default. Request the functions you need:

use Text::KDL::XS qw(parse_kdl emit_kdl);

Loading Text::KDL::XS also loads Text::KDL::XS::Parser, Text::KDL::XS::Document, Text::KDL::XS::Node, Text::KDL::XS::Value and Text::KDL::XS::Emitter. Each of these modules can also be loaded on its own; it loads Text::KDL::XS itself.

FUNCTIONS

parse_kdl

my $doc = parse_kdl($source);
my $doc = parse_kdl($source, version => '2');
my $doc = parse_kdl($source, %options);

Parses a complete KDL document and returns a Text::KDL::XS::Document. Dies on malformed input (see "ERRORS").

Sources

$source is one of:

A string

The document text as a Perl character string: a string literal in a source file with use utf8, text read through an :encoding(UTF-8) layer, the result of Encode::decode, or the output of "emit_kdl". This is the fastest source; the string is copied once and ckdl reads from the copy.

A string of UTF-8 bytes (read through a :raw handle, or a literal without use utf8) must be decoded first, with utf8::decode or Encode::decode('UTF-8', ...), or passed as a filehandle instead; otherwise every byte is taken as one character. See "ENCODING".

A filehandle or IO object

A glob (*STDIN), a glob reference (\*STDIN, $fh from open), an IO object (*STDIN{IO}, IO::Handle, IO::File, IO::Socket), a tied handle, or any other object with a read or sysread method. Open handles are read with Perl's read, so every PerlIO layer, data buffered by earlier reads and in-memory handles work. See "FILEHANDLE SOURCES".

A code reference

Called as $code->($wanted_bytes) whenever the parser needs more input. It returns the next chunk of the document, or an empty string or undef at the end of the input; after that it is not called again.

Chunks are UTF-8 bytes. A character string (a string with Perl's UTF-8 flag on) is used in its UTF-8 encoding, so returning characters works as well. $wanted_bytes is only a hint: a longer chunk is kept and handed to ckdl in pieces, and chunk boundaries need not align with lines, tokens or even characters.

The first call happens while the parser is created, before parse_kdl or "new" in Text::KDL::XS::Parser returns: ckdl reads ahead to look for a byte order mark. An exception thrown by the code reference propagates unchanged (an exception object stays the same object) out of the call that needed the input; a reference returned as a chunk dies.

parse_kdl options

version => 'detect' | '1' | '2'

Which KDL syntax to accept. The default 'detect' accepts both and settles on one at the first version-specific construct (#true versus true, #"raw"# versus r"raw", a bare identifier used as a value). '1' and '2' accept exactly one version and reject the other's syntax. 'v1' and 'v2' are accepted as well, in any letter case. Any other value dies with Text::KDL::XS::Parser: unknown version '...' (expected 'detect', '1' or '2').

See "KDL VERSIONS" for how the versions differ.

max_depth => $levels

The deepest nesting of nodes allowed; a top-level node is at depth 1. Default 512; 0 means unlimited. A document nested deeper dies with KDL parse error: nesting depth exceeds max_depth (512) as soon as the parser reaches the extra level, before the tree gets any deeper. This bounds the recursion of "emit_kdl", "as_data" in Text::KDL::XS::Node and your own tree walks for documents from untrusted sources.

emit_comments => 0 | 1

Accepted for symmetry with Text::KDL::XS::Parser, where it makes the parser report comments and slashdashed elements. It does not change the result of parse_kdl: comments and elements commented out with /- never become part of the tree.

Unknown options, an odd number of option arguments and invalid values die. An option whose value is undef is treated as not given.

Return value

A Text::KDL::XS::Document. Its nodes method returns the top-level nodes as Text::KDL::XS::Node objects; every argument and property value is a Text::KDL::XS::Value. Node names, property keys, string values and type annotations are returned as Perl character strings. An empty or comment-only document gives a document with no nodes.

emit_kdl

my $text = emit_kdl($document);
my $text = emit_kdl($node);
my $text = emit_kdl(\@nodes);
my $text = emit_kdl(\%data);
my $text = emit_kdl(\@data);
my $text = emit_kdl($anything, %options);

Serialises a tree or a plain data structure to KDL and returns the text as a Perl character string (encode it as UTF-8 before writing it to a file; see "ENCODING"). The output ends with a newline; an empty document is a single newline. Everything is validated before it is written, so emit_kdl either returns a document that parses back to the same data or dies.

The mode is chosen from the type of the first argument.

Tree mode

Selected when the argument is a Text::KDL::XS::Document, a Text::KDL::XS::Node, or a non-empty array reference whose elements are all Text::KDL::XS::Node objects; subclasses of these classes are accepted everywhere. An array that mixes nodes with anything else is data mode, where the nodes make it die.

Tree mode is faithful: it writes arguments and properties in their stored order, keeps type annotations on nodes and values, keeps the integer/float/string distinction of numbers (1.0 stays 1.0), and writes repeated properties as often as they occur. It is the mode to use for round-tripping a parsed document. What it does not preserve (comments, layout, number spelling) is listed in "What a round trip loses" in Text::KDL::XS::Cookbook.

Argument and property values inside the tree are normally Text::KDL::XS::Value objects, but plain scalars and objects are accepted too and are converted as described under "Scalar coercion". A node that is its own descendant dies with emit_kdl: cyclic data structure; a node that appears in several places is written in each.

Data mode

Selected for any other unblessed hash or array reference. Data mode is a convenience for writing configuration from ordinary Perl data; it is deterministic but lossy. Every hash key becomes a node; data mode never writes properties.

Perl value                       Emitted as
-------------------------------  ----------------------------------------------
{ key => $scalar }               key <value>
{ key => undef }                 key #null
{ key => [ $s1, $s2, ... ] }     key <s1> <s2> ...      (all elements scalars)
{ key => [] }                    key                    (bare node)
{ key => {} }                    key                    (bare node)
{ key => { ... } }               key { <children> }
{ key => [ {...}, {...} ] }      key { ... }  key { ... }   (one sibling per element)
{ key => [ $s, {...} ] }         key <s>  key { ... }   (mixed: one sibling per element)
{ key => [ [1,2], [3] ] }        key 1 2  key 3         (inner arrays: one sibling each)
[ $a, $b, ... ]   (top level)    - <a>  - <b>  ...      (nodes named "-")
{} or []          (top level)    (a single newline)
boolean object                   #true / #false
Text::KDL::XS::Value object      as the object says (type, kind, annotation)

Hash keys are emitted in sorted order. Single values are classified by "Scalar coercion". A hash or array that contains itself dies with emit_kdl: cyclic data structure; one that is referenced from several places is written in each.

What data mode cannot express: properties; arguments and children on the same node; a specific node order (keys are sorted); type annotations on nodes; and the difference between { key => 'a' } and { key => ['a'] } (both give key a). Build a Text::KDL::XS::Node tree when you need any of these.

Scalar coercion

Single values that are not Text::KDL::XS::Value objects, in either mode, are mapped like this:

Perl value                                      KDL value
----------------------------------------------  ----------------------------
undef                                           #null
JSON::PP::Boolean, Types::Serialiser::Boolean,
  JSON::Boolean, boolean, Mojo::JSON::_Bool
  (or a subclass)                               #true / #false (by truthiness)
Text::KDL::XS::Value (or a subclass)            as specified by the object
a number: a scalar with an integer or floating
  point value and no string value, or one whose
  string value is exactly Perl's rendering of
  that number                                   number (integer or float)
any other plain scalar                          string
Math::BigInt, Math::BigFloat                    number, written with its exact digits
another object with string overloading          string ("$object")
any other object or reference                   dies

A number with an integer value is written as an integer over the whole native range (-2**63 to 2**64-1, unsigned values included); any other number as a float, with the shortest text that reads back as the same double (0.30000000000000004, 1e+21, -0.0, 123456789.0; a float always has a decimal point or an exponent). A scalar that has both an integer and a floating point value, such as 3.0 after it has been compared with ==, is written as an integer.

The decision uses the scalar's value, not how it looks. '42' from a string literal is a string and 42 is a number. A string that has been used as a number is a number only when its text is exactly the number Perl would print: '42' after $x + 0 becomes the number 42, but '007', '1.50', '1e3', ' 42 ' and dualvars stay strings, so no text is ever changed. Perl's false value !!0 is the empty string, as in JSON::PP. Force one or the other with "$x" or 0 + $x. The strings 'true' and 'false' are never promoted to booleans.

Math::BigInt and Math::BigFloat objects become numbers with their exact digits (NaN and infinity die with emit_kdl: cannot write the Math::BigInt NaN as a KDL number). Any other object with a string conversion (URI, Path::Tiny, ...) is written as a string. Objects without one, and references other than the hashes and arrays of data mode (code, scalar and glob references), die with emit_kdl: cannot serialize Foo object or emit_kdl: cannot serialize CODE ref.

emit_kdl options

version => 'detect' | '1' | '2'

Output syntax. '2' (and 'detect', the default) writes KDL 2.0.0: #true, #null, bare identifier strings where possible. '1' writes KDL 1.0.0: true, null, every string value quoted. 'v1' and 'v2' are accepted as well, in any letter case. KDL v1 has no spelling for infinity and NaN, so a non-finite float dies in v1 output with emit_kdl: KDL v1 has no representation for inf/nan; v2 writes #inf, #-inf and #nan.

indent => $columns

Number of spaces per nesting level, an integer from 0 to 64. Default 4.

escape_mode => $bitmask

Which characters inside quoted strings are written as escape sequences. Values are combinations of the ckdl kdl_escape_mode flags:

0       minimal: " and \, plus (in v2 output) the characters KDL never
        allows literally: U+0000 to U+0008, U+000E to U+001F, U+007F,
        the bidi controls and U+FEFF, which are always written as \u{...}
0x10    also escape backspace (\b) and vertical tab
0x20    also escape newline characters: LF, CR, FF, NEL, LS, PS
0x40    also escape tabs
0x70    default (control characters, newlines and tabs)
0x170   ASCII only: every non-ASCII character becomes \u{...}

0x10, 0x20 and 0x40 combine with bitwise or; 0x170 is a preset (0x100 has an effect only together with all of 0x70). Any other bit dies. KDL v2 does not allow a newline inside a quoted string, so for v2 output ('2' and 'detect') 0x20 is always added. In v1 output 0 really is minimal and writes newlines and control characters literally.

identifier_mode => 0 | 1 | 2

How node names, property keys, type annotations and (in v2) string values are written:

0    bare whenever the characters allow it
1    always quoted
2    bare only when pure ASCII

Without this option, emit_kdl writes identifiers bare where possible (mode 0) and switches the whole document to mode 1 when a name, key, annotation or v2 string value would otherwise be read back as something else: a keyword (true, false, null; in v2 also inf, -inf, nan) or a number (-1, +1, .5). So the output always parses back to the same data. When you pass identifier_mode, it is used as given: with mode 0 or 2 such strings are written bare and do not round-trip.

Unknown options, an odd number of option arguments and invalid values die. An option whose value is undef is treated as not given.

ENCODING

KDL documents are UTF-8 by definition. The rules for this module are:

  • A string passed to "parse_kdl" or "new" in Text::KDL::XS::Parser is a Perl character string.

  • Filehandles and code references deliver the document as UTF-8 bytes. A handle with an :encoding(UTF-8) or :utf8 layer and a code reference returning character strings work too: characters are encoded to UTF-8 before they reach the parser.

  • Everything the parser returns (names, keys, strings, annotations, comment text) is a Perl character string.

  • "emit_kdl" takes Perl character strings and returns a character string. Encode it when writing it out, either explicitly (encode('UTF-8', $text)) or through an :encoding(UTF-8) output layer. Printing it to a handle without a layer writes a string whose non-ASCII characters are all below U+0100 as Latin-1 bytes (wrong for a UTF-8 consumer), and a string containing a character above U+00FF as UTF-8 with a "Wide character" warning.

So parse_kdl(emit_kdl($data)) always works, and so does parsing text read through an :encoding(UTF-8) layer. A string of UTF-8 bytes, such as a heredoc in a source file without use utf8 or data slurped through a :raw handle, must be decoded first:

my $doc = parse_kdl(Encode::decode('UTF-8', $bytes));
utf8::decode($bytes) or die "not UTF-8";   # in place, no module needed
my $doc = parse_kdl($bytes);

Text::KDL::XS 0.001 read string sources as UTF-8 bytes; code that relied on that has to decode as shown above (or pass the filehandle).

Only Unicode text is accepted, in both directions. Input that is not well-formed UTF-8, or that encodes a surrogate (U+D800 to U+DFFF) or a code point above U+10FFFF, dies with KDL parse error: input is not valid UTF-8; a \u{...} escape that produces such a code point dies with KDL parse error: string contains a surrogate or a code point above U+10FFFF. emit_kdl dies when a name, key, annotation or string value contains one (emit_kdl: string value contains a surrogate or a code point above U+10FFFF) instead of writing something else.

FILEHANDLE SOURCES

An open filehandle is read with Perl's read, in chunks of the size the underlying library asks for (a few kilobytes). Consequences:

  • Any PerlIO layer works. A :raw handle delivers UTF-8 bytes, an :encoding(UTF-8) or :utf8 handle delivers characters, which are encoded again; :crlf is harmless because KDL accepts CRLF line ends.

  • Reading continues where the handle stands: lines read with <$fh> before are not seen by the parser, and nothing that PerlIO has buffered is lost.

  • In-memory handles (open my $fh, '<', \$string), tied handles and bare globs such as *STDIN work.

  • read waits until it has the requested number of bytes or the input ends. On a pipe or socket the first events can therefore arrive later than the data they describe. When latency matters, pass a code reference that uses sysread (see "Read from STDIN, a socket or a pipe" in Text::KDL::XS::Cookbook).

  • A read error dies with Text::KDL::XS::Parser: read failed: ... (the text of $!); a closed filehandle dies with Text::KDL::XS::Parser: filehandle is not open.

An object that is not a filehandle but has a read method (or, failing that, sysread) is read through it. The method is called like Perl's read, as $object->read($buffer, $length), and must return the number of bytes or characters read, 0 at the end of the input, or undef on error.

KDL VERSIONS

KDL 1.0.0 (2021) and KDL 2.0.0 (2024) share most of their syntax, and any document that parses under both versions has the same meaning under both. The differences that matter most when reading or writing with this module are the # prefix on #true, #false and #null; bare identifier strings as values (v2 only); raw strings #"..."# (v2) versus r"..." (v1); multi-line strings """ (v2) versus literal newlines inside quotes (v1); and the keyword numbers #inf, #-inf and #nan (v2 only). The complete table is in "Differences between KDL v1 and v2" in Text::KDL::XS::Cookbook.

With the default version => 'detect', the parser accepts either until the first construct that only exists in one version, and then requires that version for the rest of the document. To reject the other version outright, pin version. The parser does not report which version it detected; "Version detection" in Text::KDL::XS::Cookbook shows how to find out.

emit_kdl defaults to v2 output. Pass version => '1' to write legacy documents; the parsed data is identical either way, so converting a document between versions is a parse followed by an emit ("Converting between versions" in Text::KDL::XS::Cookbook). A document containing #inf, #-inf or #nan cannot be converted to v1.

ERRORS

All errors are exceptions (die). Messages end with the location of the call in your code (at script.pl line 12.), not a line inside this distribution. The exception is an exception thrown by a source code reference, which is passed through exactly as thrown.

Parsing

KDL parse error: REASON

The input is not valid KDL for the selected version. REASON is the explanation of the underlying library, for example Unexpected end of data (unclosed lists of children), Dangling slashdash (/-), Whitespace required before argument or property or Bare identifier not allowed here. There is no line or column: the library does not track positions. Raised by parse_kdl and by "next_event" in Text::KDL::XS::Parser. After an error the parser is finished: every further next_event dies with the same error.

KDL parse error: input is not valid UTF-8
KDL parse error: string contains a surrogate or a code point above U+10FFFF

See "ENCODING". The second message names the field: node name, property key, type annotation, string or comment.

KDL parse error: nesting depth exceeds max_depth (512)

The document is nested deeper than the max_depth option allows.

Text::KDL::XS::Parser: source is required

parse_kdl(undef).

Text::KDL::XS::Parser: unsupported source ref type 'X'

The source was a reference that is neither a code reference nor a filehandle nor an object with a read or sysread method (for example an array or hash reference).

Text::KDL::XS::Parser: filehandle is not open
Text::KDL::XS::Parser: read failed: ...

See "FILEHANDLE SOURCES".

Text::KDL::XS::Parser: the source callback must return a string or undef, not a reference

A code reference source returned a reference.

Text::KDL::XS::Parser: next_event called from inside the parser's own source callback

A source code reference called next_event on the parser it feeds.

Text::KDL::XS::Parser: unknown version 'X' (expected 'detect', '1' or '2')
Text::KDL::XS::Parser: unknown option 'X'
Text::KDL::XS::Parser: expected name => value pairs, got an odd number of arguments
Text::KDL::XS::Parser: max_depth must be a non-negative integer, got 'X'

Bad options to parse_kdl or "new" in Text::KDL::XS::Parser.

Emitting

emit_kdl: expected Document, Node, ARRAY ref, or HASH ref

emit_kdl was given a plain scalar, a code reference or an object that is not a document or node.

emit_kdl: cannot serialize X object, emit_kdl: cannot serialize X ref

A value that "Scalar coercion" does not accept: an object without a string conversion, or a reference other than the hashes and arrays of data mode.

emit_kdl: cyclic data structure

A hash, array or node contains itself.

emit_kdl: tree mode expects Text::KDL::XS::Node, got X

An element of $node->children or $doc->nodes is not a node.

emit_kdl: a property must be a [ key => value ] pair

An element of $node->props is not an array reference.

emit_kdl: node name must be defined, emit_kdl: property key must be defined

A hand-built node without a name, or a property with an undefined key.

emit_kdl: node name contains a surrogate or a code point above U+10FFFF

See "ENCODING"; the message names the field.

emit_kdl: KDL v1 has no representation for inf/nan

A non-finite float in version => '1' output.

emit_kdl: cannot write the Math::BigInt NaN as a KDL number

A Math::BigInt or Math::BigFloat that is NaN or infinite.

emit_kdl: unknown version 'X' (expected 'detect', '1' or '2')
emit_kdl: indent must be an integer from 0 to 64, got 'X'
emit_kdl: escape_mode must be a combination of 0x10, 0x20, 0x40 and 0x170, got 'X'
emit_kdl: identifier_mode must be an integer from 0 to 2, got 'X'
emit_kdl: unknown option 'X'

Bad options to emit_kdl.

emit_kdl: 'X' is not a KDL number, emit_kdl: unknown number kind 'X' ..., emit_kdl: unknown value type 'X' ...

A Text::KDL::XS::Value whose hash was changed by hand to something that "new" in Text::KDL::XS::Value would have refused. The emitter checks every value again, so that nothing but valid KDL is ever written.

Constructors

"new" in Text::KDL::XS::Value, "new" in Text::KDL::XS::Node and "new" in Text::KDL::XS::Document die with messages starting with their class name, for example Text::KDL::XS::Value->new: unknown type 'Number' (expected null, bool, number or string); see the class documentation. Calling a method of Text::KDL::XS::Parser on something that is not a parser object made by its constructor dies with not a valid Text::KDL::XS::Parser object.

Warnings

"as_number" in Text::KDL::XS::Value on a string value that is not numeric warns Argument "..." isn't numeric, like any numeric use of such a string.

KNOWN ISSUES AND LIMITATIONS

The remaining limitations come from the underlying ckdl library or from KDL itself.

Parse errors carry no position

ckdl does not track lines or columns, so errors have a reason but no location in the document.

Keyword-like strings switch the whole document to quoted identifiers

ckdl would write a string such as true or -1 without quotes, which reads back as a keyword or a number. emit_kdl detects this and writes the document with every identifier quoted (see "emit_kdl options"), which is correct but more verbose than needed.

A children block needs whitespace before it

KDL 2.0.0 requires it, so node{} is correctly rejected in v2, but ckdl rejects it in v1 mode as well although KDL 1.0.0 allows it. Write node {}.

Detect mode is not a complete KDL v1 parser

ckdl documents the hybrid mode as exact for v2 and for almost all v1 documents. Pin version => '1' for strict v1.

Detect mode accepts .5 as an identifier

.5, -.5 and +.5 are rejected by both version => '1' and version => '2' but accepted as strings by the default detection.

Unicode escapes are parsed leniently

\u{} with no digits is accepted and yields U+0000, and more than six hex digits are accepted and wrap around (\u{1000000041} is A). Escapes that produce a surrogate or a code point above U+10FFFF are rejected (see "ENCODING").

Parsing leaks a few bytes per property

When ckdl reports a property it replaces an internal string (the node name or the previous key) without freeing it, so every property of a parsed document leaks one small allocation (about 32 bytes). Documents without properties are not affected. This only matters for long-running processes that parse many documents (about 30 MB per million properties); the fix belongs in ckdl.

Vertical tab is whitespace, not a newline

KDL 2.0.0 lists U+000B as a newline; ckdl treats it as whitespace.

Special numbers cannot be written as KDL v1

KDL 1.0.0 has no spelling for infinity and NaN; emit_kdl dies instead of writing invalid v1.

Number spelling is not preserved

Numbers are written in a canonical form: 0xFF comes back as 255, 1_000 as 1000, 1e3 as 1000.0. Only numbers kept as text (kind string, see "VALUE MODEL" in Text::KDL::XS::Value) keep their digits. ckdl's own float parser is up to one unit in the last place off; the values this module returns are rounded correctly.

Duplicate properties are all written

node a=1 a=2 is written back as node a=1 a=2, the way it was read; other implementations write node a=2. Both parse to the same data.

THREADS

Parser and emitter objects hold C state and are never copied into a new thread: in a thread created while such an object exists, the copy is an unblessed, unusable reference. Create parsers inside the thread that uses them; parse_kdl and emit_kdl can be called from any thread. Document, node and value objects are plain Perl data and are cloned like any other. After fork, each process has its own copy of everything and may use it.

PERFORMANCE NOTES

Parsing from a string is the fastest path: the document is handed to ckdl as a single buffer and each event is converted to Perl objects once. Filehandle and code reference sources cost one Perl callback per chunk. The streaming parser avoids building the tree and is the right tool for very large documents or for extracting a few values from a big file.

Values are created as small blessed hashes; a document with a million values needs about 500 MB as a tree. Use Text::KDL::XS::Parser for anything of that size.

The module requires Perl 5.12 or newer built with 64-bit integers (ivsize 8, the default on 64-bit platforms).

SEE ALSO

Text::KDL::XS::Cookbook

Every KDL feature with KDL and Perl examples, plus recipes.

Text::KDL::XS::Parser, Text::KDL::XS::Document, Text::KDL::XS::Node, Text::KDL::XS::Value

The classes that make up the API.

Text::KDL::XS::Emitter

Internal; documented for completeness.

Alien::ckdl

Builds and provides the ckdl library this module links against; its alienfile pins the ckdl commit that is compiled.

https://kdl.dev, https://github.com/kdl-org/kdl

The KDL specification and reference test suite.

https://github.com/tjol/ckdl

The C library doing the actual parsing and emitting.

AUTHOR

Davenonymous <perl@davenonymous.com>

LICENSE

Copyright (C) 2026 Davenonymous.

This Perl distribution is licensed under the same terms as Perl itself. The bundled ckdl library (linked statically via Alien::ckdl) is MIT-licensed.