Text::KDL::XS
A fast Perl XS binding to ckdl for parsing and emitting KDL documents. Supports KDL 2.0.0 and the legacy KDL 1.0.0 with automatic version detection.
The full documentation is in the POD: perldoc Text::KDL::XS for the API
reference and perldoc Text::KDL::XS::Cookbook for a tour of every KDL
feature with Perl examples. This file is the short version.
What is KDL?
KDL ("cuddle", the KDL Document Language) is a small
configuration and data language. A document is a tree of nodes; each
node has a name, optional arguments, optional properties
(key=value) 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"
}
}
Values are strings, numbers, #true/#false, #null or the special
numbers #inf, #-inf and #nan, and any value or node can carry a
(type) annotation. Comments, multi-line and raw strings,
hexadecimal/octal/binary numbers and /- "slashdash" comments round out
the language. The Cookbook shows all of
them.
Synopsis
use Text::KDL::XS qw(parse_kdl emit_kdl);
my $doc = parse_kdl(<<'KDL');
server "web-1" port=8080 {
tls #true
upstream name="app" weight=3
}
KDL
for my $node (@{ $doc->nodes }) {
print $node->name, "\n"; # server
print $node->args->[0]->as_string, "\n"; # web-1
print $node->prop('port')->as_number, "\n"; # 8080
for my $child (@{ $node->children }) {
print " ", $child->name, "\n"; # tls, upstream
}
}
print emit_kdl($doc); # round trip
print emit_kdl({ # plain data
server => { host => 'localhost', port => 8080 },
tags => [ 'a', 'b' ],
});
# server {
# host localhost
# port 8080
# }
# tags a b
parse_kdl accepts a Perl character string, a filehandle (any PerlIO
layer), or a code reference returning chunks of UTF-8. emit_kdl accepts a
parsed document, a node, a list of nodes, or plain hashes and arrays, and
returns a character string, so parse_kdl(emit_kdl($data)) round-trips.
For SAX-style streaming without building a tree:
use Text::KDL::XS::Parser;
open my $fh, '<', 'huge.kdl' or die $!;
my $p = Text::KDL::XS::Parser->new($fh);
while (my $ev = $p->next_event) {
print $ev->{name}, "\n" if $ev->{event} eq 'start_node';
}
Modules
| Module | Role |
|------------------------------------------------------------|--------------------------------------------------------|
| Text::KDL::XS | parse_kdl, emit_kdl, options, encoding, errors |
| Text::KDL::XS::Cookbook | Every KDL feature with KDL and Perl examples; recipes |
| Text::KDL::XS::Parser | Streaming event parser |
| Text::KDL::XS::Document | Container for the top-level nodes |
| Text::KDL::XS::Node | A node: name, type annotation, args, props, children |
| Text::KDL::XS::Value | A typed value: null, bool, number, string |
| Text::KDL::XS::Emitter | Internal emitter helpers |
Features
- Tree API (
parse_kdl/emit_kdl) with faithful round-tripping of argument order, property order, type annotations and number kinds. Floats are written with the shortest text that reads back as the same double; integers exactly over the whole 64-bit signed and unsigned range. - Streaming event API for memory-bounded processing of large documents, with optional reporting of comments (including their text) and slashdashed elements.
- Sources: strings, filehandles with any PerlIO layer, IO objects, code references.
- Complete value model: distinct null and booleans, integers, floats,
arbitrary-precision numbers kept as text (with exact
Math::BigInt/Math::BigFloatconversion),#inf/#-inf/#nan. - KDL 1.0.0 and 2.0.0, detected automatically or pinned with
version => '1' | '2'; emit in either version. - Plain-Perl data emission for the "just write my config" case.
- Safe on untrusted input: strict UTF-8 validation, a nesting limit
(
max_depth, 512 by default), errors with the parser's reason reported at the caller's line, and an emitter that only writes valid KDL. - Passes the upstream KDL test suites for both versions (the only textual differences are the spelling of floats and repeated properties).
Known issues
The remaining limitations come from the underlying ckdl library: parse
errors carry a reason but no line or column, detection mode is not a
complete KDL v1 parser, a few lenient spots in ckdl's parser (such as
\u{} escapes without digits), and a small memory leak in ckdl for every
parsed property (about 32 bytes, relevant only to long-running processes
that parse many documents). Strings that would be written bare but read
back as keywords or numbers (true, -1) make emit_kdl quote the whole
document. All of them are listed in perldoc Text::KDL::XS, section
"KNOWN ISSUES AND LIMITATIONS".
Upgrading from 0.001: a string passed to parse_kdl is now read as Perl
characters. Code that passed UTF-8 byte strings must decode them first
(utf8::decode, Encode::decode) or pass the filehandle instead; see
Changes for the complete list.
Installation
perl Makefile.PL
make
make test
make install
Text::KDL::XS links statically against ckdl through
Alien::ckdl, which
builds the C library from source. No system package is needed; a C11
compiler and a perl with 64-bit integers are.
Status
Version 0.002 fixes the defects found in 0.001 (see Changes; what
remains are the upstream limitations above) and changes one behaviour on
purpose: string sources are character strings. The API
(parse_kdl, emit_kdl, Parser, Document, Node, Value) is
otherwise stable; Value gained as_bignum.
See also
- KDL specification and kdl.dev
- ckdl, the underlying C library
Alien::ckdl
License
Copyright (C) 2026 Davenonymous.
This Perl distribution is released under the same terms as Perl itself.
The bundled ckdl library (linked statically through Alien::ckdl) is
MIT-licensed.