Text::KDL::XS
A fast Perl XS binding to ckdl for parsing and emitting KDL documents. Supports KDL v1 and v2 with automatic version detection.
NAME
Text::KDL::XS - parse and emit KDL Document Language with libckdl
What is KDL?
KDL ("cuddle" - the KDL Document Language) is a small,
human-friendly configuration language. It looks a bit like a cleaned-up mix
of JSON and an XML-ish s-expression: every line is a node with a name,
optional arguments, optional properties (key=value pairs), and an
optional block of child nodes.
package "kdl-rs" {
version "0.4.0"
author "Kat Marchán" email="kat@example.com"
keywords "config" "data" "structured"
license "MIT" {
url "https://opensource.org/licenses/MIT"
}
}
In that snippet:
packageis a node with one argument ("kdl-rs") and a child block.versionis a child node with one argument.authorhas one argument ("Kat Marchán") and one property (email="kat@example.com").keywordshas three arguments - KDL nodes can carry as many as you want.licensehas both an argument and its own children.
KDL also supports typed values ((u32)42, (date)"2026-04-29"), booleans
(#true/#false in v2, true/false in v1), #null, and several number
formats including arbitrary-precision integers. See
the KDL spec for the full grammar.
SYNOPSIS
Parsing
use Text::KDL::XS qw(parse_kdl);
my $doc = parse_kdl(<<'KDL');
package "thing" {
version "1.0"
author "Kat" email="kat@example.com"
}
KDL
for my $node (@{ $doc->nodes }) {
say $node->name; # "package"
for my $child (@{ $node->children }) {
say " ", $child->name; # "version", "author"
say " arg: ", $child->args->[0]->as_string;
if (my $email = $child->prop('email')) {
say " email: ", $email->as_string;
}
}
}
parse_kdl accepts a string, a filehandle/IO object, or a code reference
that returns chunks of bytes. The version is auto-detected by default; pass
version => '1' or version => '2' to force one.
my $doc = parse_kdl($string);
my $doc = parse_kdl(\*STDIN);
my $doc = parse_kdl($io_object);
my $doc = parse_kdl(sub { read_some_bytes() });
my $doc = parse_kdl($string, version => '2');
Emitting
The emitter has two modes, chosen automatically from the input.
Tree mode (full fidelity) takes a Document, a Node, or an arrayref
of Node objects:
use Text::KDL::XS qw(emit_kdl);
my $kdl = emit_kdl($doc); # Document round-trip
my $kdl = emit_kdl($doc, indent => 4);
Data mode (convenience) takes any other hashref or arrayref of plain Perl data:
my $kdl = emit_kdl({
package => 'kdl-rs',
version => '1.0',
authors => [ 'Kat', 'Sam' ], # multiple args on one node
meta => { # nested hash -> child block
license => 'MIT',
year => 2026,
},
});
Produces something like:
authors "Kat" "Sam"
meta {
license "MIT"
year 2026
}
package "kdl-rs"
version "1.0"
Hash keys are emitted in sorted order for deterministic output. Arrays of hashrefs become repeated sibling nodes:
emit_kdl({
author => [
{ name => 'Kat', email => 'kat@example.com' },
{ name => 'Sam' },
],
});
author {
email "kat@example.com"
name "Kat"
}
author {
name "Sam"
}
For booleans, pass an explicit boolean object - strings like "true" are
not auto-promoted:
use JSON::PP ();
emit_kdl({ enabled => JSON::PP::true(), debug => JSON::PP::false() });
Streaming
For SAX-like access without building a tree, use the parser directly:
use Text::KDL::XS::Parser;
my $p = Text::KDL::XS::Parser->new(\*STDIN);
while (my $ev = $p->next_event) {
if ($ev->{event} eq 'start_node') {
say "node: ", $ev->{name};
}
elsif ($ev->{event} eq 'argument') {
say " arg: ", $ev->{value}->as_string;
}
elsif ($ev->{event} eq 'property') {
say " prop: $ev->{name} = ", $ev->{value}->as_string;
}
}
INSTALLATION
From a checkout:
perl Makefile.PL
make
make test
Text::KDL::XS links statically against the ckdl C library through the
sibling Alien::ckdl distribution, so no system package is required.
STATUS
Pre 1.000. The full test suite passes and the API has stabilized around the
parse_kdl / emit_kdl pair plus the Parser, Document, Node, and
Value classes - but minor adjustments are still possible before a 1.000
release. Builds against the pinned ckdl commit shipped by Alien::ckdl.
API OVERVIEW
| Module | Role |
|----------------------------------------------------------|----------------------------------------------|
| Text::KDL::XS | Top-level functions: parse_kdl, emit_kdl |
| Text::KDL::XS::Parser | Streaming event iterator |
| Text::KDL::XS::Document | Top-level nodes container |
| Text::KDL::XS::Node | A node: name, args, props, children |
| Text::KDL::XS::Value | A typed scalar value |
Read the embedded POD (perldoc Text::KDL::XS, etc.) for the per-module
reference.
FEATURES
- High-level tree API (
parse_kdl/emit_kdl) with full round-trip. - Streaming event API for memory-bounded ingestion of large documents.
- Sources: strings, filehandles, blessed IO objects, code references.
- Faithful KDL value model: per-value type annotations, distinct bool/null, integer/float/bigint preservation.
- KDL v1 and v2, auto-detected (
version => '1' | '2' | 'detect'). - Plain-Perl data emission for the common "I just want config out" case.
SEE ALSO
ckdl- the underlying C library- KDL spec
Alien::ckdl- sibling Alien distribution
LICENSE
This Perl distribution is released under the same terms as Perl itself.
The bundled ckdl library (linked statically through Alien::ckdl) is
MIT-licensed.