NAME
Text::KDL::XS::Parser - Streaming, event-based KDL parser
SYNOPSIS
use Text::KDL::XS::Parser;
open my $fh, '<:raw', 'big.kdl' or die $!;
my $parser = Text::KDL::XS::Parser->new($fh, version => 'detect');
while (my $ev = $parser->next_event) {
if ($ev->{event} eq 'start_node') {
print "node ", $ev->{name}, "\n";
}
elsif ($ev->{event} eq 'argument') {
print " arg ", $ev->{value}->as_string // '#null', "\n";
}
elsif ($ev->{event} eq 'property') {
print " $ev->{name} = ", $ev->{value}->as_string // '#null', "\n";
}
}
# next_event returned undef: end of input
DESCRIPTION
Text::KDL::XS::Parser exposes ckdl's event stream directly. Instead of building a tree, it hands you one event at a time: a node starts, an argument or property was read, a node ends. With a filehandle or code reference source the input is consumed in chunks, so memory use does not grow with the document (a string source is copied once in full), and you can stop reading at any point.
"parse_kdl" in Text::KDL::XS is built on this class; use it unless you need streaming, early termination, or access to comments and slashdashed elements.
CONSTRUCTOR
new
my $parser = Text::KDL::XS::Parser->new($source);
my $parser = Text::KDL::XS::Parser->new($source, version => '2', emit_comments => 1);
Creates a parser over $source, which must be one of the following.
- String
-
The whole document as a Perl character string (decode UTF-8 bytes first, see "ENCODING" in Text::KDL::XS). The parser keeps a copy, so the caller may discard or modify the original afterwards.
- Filehandle or IO object
-
A glob (
*STDIN), a glob reference (\*STDIN,$fh), an IO object (*STDIN{IO}, IO::Handle, IO::File), a tied handle, or an object with areadorsysreadmethod. Open handles are read with Perl'sreadin chunks whose size ckdl chooses, so every PerlIO layer, data buffered by earlier reads and in-memory handles work; other objects are read through theirreadmethod (preferred) orsysreadmethod. See "FILEHANDLE SOURCES" in Text::KDL::XS. - Code reference
-
Called as
$code->($wanted_bytes)whenever the parser needs more input. Return the next chunk of UTF-8 bytes (a character string is encoded to UTF-8), or an empty string orundefat the end of the input; the code reference is not called again after that.$wanted_bytesis a hint: longer chunks are kept and handed over in pieces, and chunk boundaries need not align with lines, tokens or characters. An exception thrown inside the code reference propagates unchanged out of thenewornext_eventcall that needed the input, and the parser is failed from then on (see "next_event").
The source is read once before new returns: ckdl reads ahead to look for a byte order mark, so a code reference is called for the first time, and a read error or exception can occur, inside new.
undef dies with Text::KDL::XS::Parser: source is required; any other reference type dies with Text::KDL::XS::Parser: unsupported source ref type '...', and a closed filehandle with Text::KDL::XS::Parser: filehandle is not open.
Options:
- version => 'detect' | '1' | '2'
-
Which KDL version to accept; also
'v1'and'v2', in any letter case; default'detect'. See "KDL VERSIONS" in Text::KDL::XS. - emit_comments => 0 | 1
-
When true, comments produce
commentevents, and nodes, arguments and properties that were commented out with a slashdash (/-) are reported withcommented => 1instead of being dropped. Default false. - max_depth => $levels
-
The deepest nesting of nodes allowed (a top-level node is at depth 1); default 512,
0for unlimited. The event that would go deeper dies withKDL parse error: nesting depth exceeds max_depth (512).
Unknown options, an odd number of option arguments and invalid values die. An option whose value is undef is treated as not given.
new may be called on a subclass, and the parser is then an object of that class. A parser cannot be used in a thread other than the one that created it (see "THREADS" in Text::KDL::XS).
METHODS
next_event
my $ev = $parser->next_event; # hashref, or undef at end of input
Returns the next event as a hash reference, or undef once the document has been consumed. Calling it again after undef keeps returning undef without reading from the source.
On malformed input it dies with KDL parse error: REASON, where REASON is ckdl's explanation (for example Unexpected end of data (unclosed lists of children)); an exception thrown by a source code reference is passed through unchanged. After an error the parser is finished: every further call dies with the same error. Errors are reported at the line of your call.
next_event must not be called from inside the parser's own source code reference; that dies with Text::KDL::XS::Parser: next_event called from inside the parser's own source callback.
EVENT HASH
Each event is a hash reference with these keys:
key present for value
--------- --------------------------- ----------------------------------------
event always 'start_node' | 'end_node' | 'argument'
| 'property' | 'comment'
commented always 1 if the element was slashdashed (/-),
otherwise 0 (see below)
name start_node, property node name / property key (character string)
type start_node, when annotated the node's type annotation
value argument, property a Text::KDL::XS::Value
text comment the comment, delimiters included
Notes:
commentedis only ever 1 when the parser was created withemit_comments => 1; without that option slashdashed elements are not reported at all. A slashdashed node reports all of its arguments, properties, children and itsend_nodewithcommented => 1.commentevents (only withemit_comments) carry the comment as written intext, including its delimiters:// notefor a single-line comment (without the line end),/* note */for a multi-line one. They havecommented => 1.Type annotations on argument and property values are on the Text::KDL::XS::Value object (
$ev->{value}->type_annotation), not in the event hash.Every
start_nodeis eventually matched by exactly oneend_node, unless a parse error intervenes. Arguments and properties of a node arrive between itsstart_nodeand thestart_nodeof its first child (or its ownend_node).The event hashes and the value objects in them are fresh Perl data and may be kept or modified.
EXAMPLES
Event sequence for a small document
node 1 key=2 {
child 3
}
produces, in order:
start_node name=node
argument value=1
property name=key value=2
start_node name=child
argument value=3
end_node
end_node
Seeing slashdashed elements and comments
my $p = Text::KDL::XS::Parser->new("// note\nnode 1 /-2 {\n /-gone\n}\n", emit_comments => 1);
while (my $ev = $p->next_event) {
printf "%-10s commented=%d %s\n", $ev->{event}, $ev->{commented}, $ev->{name} // $ev->{text} // '';
}
comment commented=1 // note
start_node commented=0 node
argument commented=0
argument commented=1
start_node commented=1 gone
end_node commented=1
end_node commented=0
Stopping early
Because events are pulled on demand, you can stop as soon as you have what you need. Here we find the first top-level version node and stop:
my $p = Text::KDL::XS::Parser->new($big_document);
my ($depth, $version) = (0);
while (my $ev = $p->next_event) {
if ($ev->{event} eq 'start_node') {
if ($depth == 0 && $ev->{name} eq 'version') {
my $next = $p->next_event; # the next event: usually its first argument
$version = $next->{value}->as_string if $next && $next->{event} eq 'argument';
last;
}
$depth++;
}
elsif ($ev->{event} eq 'end_node') { $depth-- }
}
Building your own structure
The tree builder in Text::KDL::XS::Document is a short loop over these events and is a good template for custom builders: keep a stack of open nodes, push on start_node, pop on end_node, attach values to the top of the stack, and skip events with commented set.
SEE ALSO
Text::KDL::XS, Text::KDL::XS::Value, "Stream a large file without building a tree" in Text::KDL::XS::Cookbook.
AUTHOR
Davenonymous <perl@davenonymous.com>
LICENSE
Copyright (C) 2026 Davenonymous.
This Perl distribution is licensed under the same terms as Perl itself.