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 a read or sysread method. Open handles are read with Perl's read in chunks whose size ckdl chooses, so every PerlIO layer, data buffered by earlier reads and in-memory handles work; other objects are read through their read method (preferred) or sysread method. 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 or undef at the end of the input; the code reference is not called again after that. $wanted_bytes is 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 the new or next_event call 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 comment events, and nodes, arguments and properties that were commented out with a slashdash (/-) are reported with commented => 1 instead of being dropped. Default false.

max_depth => $levels

The deepest nesting of nodes allowed (a top-level node is at depth 1); default 512, 0 for unlimited. The event that would go deeper dies with KDL 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:

  • commented is only ever 1 when the parser was created with emit_comments => 1; without that option slashdashed elements are not reported at all. A slashdashed node reports all of its arguments, properties, children and its end_node with commented => 1.

  • comment events (only with emit_comments) carry the comment as written in text, including its delimiters: // note for a single-line comment (without the line end), /* note */ for a multi-line one. They have commented => 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_node is eventually matched by exactly one end_node, unless a parse error intervenes. Arguments and properties of a node arrive between its start_node and the start_node of its first child (or its own end_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.