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 float, at the width of the perl's floats; 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
The ckdl sources are bundled in the ckdl directory and compiled into
the module, so no system package and no Alien module is needed. A C11
compiler and a perl 5.30 or newer with 64-bit integers are.
ckdl/UPSTREAM.md names the bundled upstream commit.
Status
Version 0.003 bundles the ckdl sources and drops the Alien::ckdl
dependency; the API is unchanged. Version 0.002 fixed the defects found
in 0.001 (see Changes; what remains are the upstream limitations
above). The API (parse_kdl, emit_kdl, Parser, Document, Node,
Value) is stable.
See also
- KDL specification and kdl.dev
- ckdl, the underlying C library
License
Copyright (C) 2026 Davenonymous.
This Perl distribution is released under the same terms as Perl itself.
The bundled ckdl library is MIT-licensed; see ckdl/COPYING.