home *** CD-ROM | disk | FTP | other *** search
- package YAML;
- $VERSION = '0.35';
-
- # This module implements a Loader and Dumper for the YAML serialization
- # language, VERSION 1.0 TRIAL2. (http://www.yaml.org/spec/)
-
- require Exporter;
- @ISA = qw(Exporter);
- # Basic interface is Load & Dump
- # Phasing out Store in favor of Dump XXX
- # Leave it in for now. XXX
- @EXPORT = qw(Load Dump Store);
- # Provide a bunch of aliases for TMTOWTDI's sake
- @EXPORT_OK = qw(LoadFile DumpFile
- Dumper Eval
- freeze thaw
- VALUE COMMENT
- Bless Blessed
- );
- # Export groups
- %EXPORT_TAGS = (all => [qw(Load Dump Store LoadFile DumpFile Bless Blessed)],
- constants => [qw(VALUE COMMENT)],
- Storable => [qw(freeze thaw)],
- POE => [qw(freeze thaw)],
- );
-
- use strict;
- use YAML::Node;
- use YAML::Transfer;
- use Carp;
-
- sub PRINT { print STDERR @_, "\n" } # XXX
- sub DUMP { use Data::Dumper(); print STDERR Data::Dumper::Dumper(@_) } # XXX
-
- # Context constants
- use constant LEAF => 1;
- use constant COLLECTION => 2;
- use constant KEY => 3;
- use constant BLESSED => 4;
- use constant FROMARRAY => 5;
- use constant VALUE => "\x07YAML\x07VALUE\x07";
- use constant COMMENT => "\x07YAML\x07COMMENT\x07";
-
- # These are the user changable options
- {
- no strict 'vars';
- $Indent = 2 unless defined $Indent;
- $UseHeader = 1 unless defined $UseHeader;
- $UseVersion = 1 unless defined $UseVersion;
- $SortKeys = 1 unless defined $SortKeys;
- $AnchorPrefix = '' unless defined $AnchorPrefix;
- $UseCode = 0 unless defined $UseCode;
- $DumpCode = '' unless defined $DumpCode;
- $LoadCode = '' unless defined $LoadCode;
- $ForceBlock = 0 unless defined $ForceBlock;
- $UseBlock = 0 unless defined $UseBlock;
- $UseFold = 0 unless defined $UseFold;
- $CompressSeries = 1 unless defined $CompressSeries;
- $InlineSeries = 0 unless defined $InlineSeries;
- $UseAliases = 1 unless defined $UseAliases;
- $Purity = 0 unless defined $Purity;
- $DateClass = '' unless defined $DateClass;
- }
-
- # Common YAML character sets
- my $WORD_CHAR = '[A-Za-z-]';
- my $ESCAPE_CHAR = '[\\x00-\\x08\\x0b-\\x0d\\x0e-\\x1f]';
- my $INDICATOR_CHAR = '[#-:?*&!|\\\\^@%]';
- my $FOLD_CHAR = '>';
- my $BLOCK_CHAR = '|';
- my $BLOCK_CHAR_RX = "\\$BLOCK_CHAR";
-
- # $o is the YAML object. It contains the complete state of the YAML.pm
- # process. This is set at the file scope level so that I can avoid using
- # OO syntax or passing the object around in function calls.
- #
- # When callback are added to YAML.pm the calling code will have to save
- # the object so that it won't get clobbered. Also YAML.pm can't be subclassed.
- #
- # The purpose of this is for efficiency and also for much simpler code.
- my $o;
-
- # YAML OO constructor function
- sub new {
- my $class = shift;
- my $o = {
- stream => '',
- level => 0,
- anchor => 1,
- Indent => $YAML::Indent,
- UseHeader => $YAML::UseHeader,
- UseVersion => $YAML::UseVersion,
- SortKeys => $YAML::SortKeys,
- AnchorPrefix => $YAML::AnchorPrefix,
- DumpCode => $YAML::DumpCode,
- LoadCode => $YAML::LoadCode,
- ForceBlock => $YAML::ForceBlock,
- UseBlock => $YAML::UseBlock,
- UseFold => $YAML::UseFold,
- CompressSeries => $YAML::CompressSeries,
- InlineSeries => $YAML::InlineSeries,
- UseAliases => $YAML::UseAliases,
- Purity => $YAML::Purity,
- DateClass => $YAML::DateClass,
- };
- bless $o, $class;
- set_default($o, 'DumpCode', $YAML::UseCode);
- set_default($o, 'LoadCode', $YAML::UseCode);
- return $o if is_valid($o);
- }
-
- my $global = {}; # A global lookup
- sub Bless { YAML::bless($global, @_) }
- sub Blessed { YAML::blessed($global, @_) }
-
- sub blessed {
- my ($o, $ref) = @_;
- $ref = \$_[0] unless ref $ref;
- my (undef, undef, $node_id) = YAML::Node::info($ref);
- $o->{blessed}{$node_id};
- }
-
- sub bless {
- my ($o, $ref, $blessing) = @_;
- my $ynode;
- $ref = \$_[0] unless ref $ref;
- my (undef, undef, $node_id) = YAML::Node::info($ref);
- if (not defined $blessing) {
- $ynode = YAML::Node->new($ref);
- }
- elsif (ref $blessing) {
- croak unless ynode($blessing);
- $ynode = $blessing;
- }
- else {
- no strict 'refs';
- my $transfer = $blessing . "::yaml_dump";
- croak unless defined &{$transfer};
- $ynode = &{$transfer}($ref);
- croak unless ynode($ynode);
- }
- $o->{blessed}{$node_id} = $ynode;
- my $object = ynode($ynode) or croak;
- return $object;
- }
-
- sub stream {
- my ($o, $stream) = @_;
- if (not defined $stream) {
- return $o->{$stream};
- }
- elsif (ref($stream) eq 'CODE') {
- $o->{stream_fetch} = $stream;
- $o->{stream_eos} = 0;
- }
- elsif ($stream eq '') {
- $o->{stream} = '';
- }
- else {
- $o->{stream} .= $stream;
- }
- }
-
- sub set_default {
- my ($o, $option, $default) = (@_);
- return if length $o->{$option};
- if (length $default) {
- $o->{$option} = $default;
- }
- else {
- $o->{$option} = -1;
- }
- }
-
- sub is_valid {
- my ($o) = (@_);
- croak YAML_DUMP_ERR_INVALID_INDENT($o->{Indent})
- unless ($o->{Indent} =~ /^(\d+)$/) and $1 > 0;
- # NOTE: Add more tests...
- return 1;
- }
-
- #==============================================================================
- # Save the contents of a Dump operation to a file. If the file exists
- # and has data, and a concatenation was requested, then verify the
- # existing header.
- sub DumpFile {
- my $filename = shift;
- local $/ = "\n"; # reset special to "sane"
- my $mode = '>';
- if ($filename =~ /^\s*(>{1,2})\s*(.*)$/) {
- ($mode, $filename) = ($1, $2);
- }
- if ($mode eq '>>' && -f $filename && -s $filename) {
- open MYYAML, "< $filename"
- or croak YAML_LOAD_ERR_FILE_INPUT($filename, $!);
- my $line = <MYYAML>;
- close MYYAML;
- croak YAML_DUMP_ERR_FILE_CONCATENATE($filename)
- unless $line =~ /^---(\s|$)/;
- }
- open MYYAML, "$mode $filename"
- or croak YAML_DUMP_ERR_FILE_OUTPUT($filename, $!);
- print MYYAML YAML::Dump(@_);
- close MYYAML;
- }
-
- # Serialize a list of elements
- sub Dump {
- $o = YAML->new();
- $o->dump(@_);
- }
-
- sub Store {
- warn YAML_DUMP_WARN_STORE() if $^W;
- goto &Dump;
- }
-
- # Aliases for Dump
- *freeze = *freeze = \&Dump; # alias for Storable or POE users
-
- # OO version of Dump. YAML->new->dump($foo);
- sub dump {
- $o = shift;
- # local $| = 1; # set buffering to "hot" (for testing) XXX
- local $/ = "\n"; # reset special to "sane" XXX (danger) fix for callbacks
- $o->{stream} = '';
- $o->{document} = 0;
- for my $document (@_) {
- $o->{document}++;
- $o->{transferred} = {};
- $o->{id_refcnt} = {};
- $o->{id_anchor} = {};
- $o->{anchor} = 1;
- $o->{level} = 0;
- $o->{offset}[0] = 0 - $o->{Indent};
- _prewalk($document);
- _emit_header($document);
- _emit_node($document);
- }
- return $o->{stream};
- }
-
- # Every YAML document in the stream must begin with a YAML header, unless
- # there is only a single document and the user requests "no header".
- sub _emit_header {
- my ($node) = @_;
- if (not $o->{UseHeader} and
- $o->{document} == 1
- ) {
- croak YAML_DUMP_ERR_NO_HEADER() unless ref($node) =~ /^(HASH|ARRAY)$/;
- croak YAML_DUMP_ERR_NO_HEADER() if ref($node) eq 'HASH' and
- keys(%$node) == 0;
- croak YAML_DUMP_ERR_NO_HEADER() if ref($node) eq 'ARRAY' and
- @$node == 0;
- # XXX Also croak if aliased, blessed, or ynode
- $o->{headless} = 1;
- return;
- }
- $o->{stream} .= '---';
- if ($o->{UseVersion}) {
- $o->{stream} .= " #YAML:1.0";
- }
- }
-
- # Walk the tree to be dumped and keep track of its reference counts.
- # This function is where the Dumper does all its work. All transfers
- # happen here.
- sub _prewalk {
- my $value;
- my ($class, $type, $node_id) = YAML::Node::info(\$_[0]);
- # Handle typeglobs
- if ($type eq 'GLOB') {
- $value = $o->{transferred}{$node_id} =
- YAML::Transfer::glob::yaml_dump($_[0]);
- return _prewalk($value);
- }
- # Handle regexps
- if (ref($_[0]) eq 'Regexp') {
- $o->{transferred}{$node_id} = YAML::Transfer::regexp::yaml_dump($_[0]);
- return;
- }
- # Handle Purity for scalars. XXX can't find a use case yet. Might be YAGNI.
- if (not ref $_[0]) {
- $o->{id_refcnt}{$node_id}++ if $o->{Purity};
- return;
- }
- # Make a copy of original
- $value = $_[0];
- ($class, $type, $node_id) = YAML::Node::info($value);
- # Look for things already transferred.
- if ($o->{transferred}{$node_id}) {
- (undef, undef, $node_id) = (ref $o->{transferred}{$node_id})
- ? YAML::Node::info($o->{transferred}{$node_id})
- : YAML::Node::info(\ $o->{transferred}{$node_id});
- $o->{id_refcnt}{$node_id}++;
- return;
- }
- # Handle code refs
- if ($type eq 'CODE') {
- $o->{transferred}{$node_id} = 'crufty tracking reference placeholder';
- YAML::Transfer::code::yaml_dump($o->{DumpCode},
- $_[0],
- $o->{transferred}{$node_id});
- ($class, $type, $node_id) =
- YAML::Node::info(\ $o->{transferred}{$node_id});
- $o->{id_refcnt}{$node_id}++;
- return;
- }
- # Handle blessed things
- elsif (defined $class) {
- no strict 'refs';
- if ($class eq $o->{DateClass}) {
- $value = eval "&${class}::yaml_dump(\$value)";
- }
- elsif (defined &{$class . "::yaml_dump"}) {
- $value = eval "&${class}::yaml_dump(\$value)";
- }
- elsif ($type eq 'SCALAR') {
- $o->{transferred}{$node_id} = 'tracking reference placeholder';
- YAML::Transfer::blessed::yaml_dump
- ($_[0], $o->{transferred}{$node_id});
- ($class, $type, $node_id) =
- YAML::Node::info(\ $o->{transferred}{$node_id});
- $o->{id_refcnt}{$node_id}++;
- return;
- }
- else {
- $value = YAML::Transfer::blessed::yaml_dump($value);
- }
- $o->{transferred}{$node_id} = $value;
- (undef, $type, $node_id) = YAML::Node::info($value);
- }
- # Handle YAML Blessed things
- if (defined $global->{blessed}{$node_id}) {
- $value = $global->{blessed}{$node_id};
- $o->{transferred}{$node_id} = $value;
- ($class, $type, $node_id) = YAML::Node::info($value);
- return _prewalk($value);
- }
- # Handle hard refs
- if ($type eq 'REF' or $type eq 'SCALAR') {
- $value = YAML::Transfer::ref::yaml_dump($value);
- $o->{transferred}{$node_id} = $value;
- (undef, $type, $node_id) = YAML::Node::info($value);
- }
- # Handle ref-to-glob's
- elsif ($type eq 'GLOB') {
- my $ref_ynode = $o->{transferred}{$node_id} =
- YAML::Transfer::ref::yaml_dump($value);
-
- my $glob_ynode = $ref_ynode->{&VALUE} =
- YAML::Transfer::glob::yaml_dump($$value);
-
- (undef, undef, $node_id) = YAML::Node::info($glob_ynode);
- $o->{transferred}{$node_id} = $glob_ynode;
- return _prewalk($glob_ynode);
- }
-
- # Increment ref count for node
- return if ++($o->{id_refcnt}{$node_id}) > 1;
-
- # Continue walking
- if ($type eq 'HASH') {
- _prewalk($value->{$_}) for keys %{$value};
- }
- elsif ($type eq 'ARRAY') {
- _prewalk($_) for @{$value};
- }
- }
-
- # Every data element and sub data element is a node. Everything emitted
- # goes through this function.
- sub _emit_node {
- my ($type, $node_id);
- my $ref = ref($_[0]);
- if ($ref and $ref ne 'Regexp') {
- (undef, $type, $node_id) = YAML::Node::info($_[0]);
- }
- else {
- $type = $ref || 'SCALAR';
- (undef, undef, $node_id) = YAML::Node::info(\$_[0]);
- }
-
- my ($ynode, $family) = ('') x 2;
- my ($value, $context) = (@_, 0); # XXX don't copy scalars
- if (defined $o->{transferred}{$node_id}) {
- $value = $o->{transferred}{$node_id};
- $ynode = ynode($value);
- if (ref $value) {
- $family = defined $ynode ? $ynode->family->short : '';
- (undef, $type, $node_id) = YAML::Node::info($value);
- }
- else {
- $family = ynode($o->{transferred}{$node_id})->family->short;
- $type = 'SCALAR';
- (undef, undef, $node_id) =
- YAML::Node::info(\ $o->{transferred}{$node_id});
- }
- }
- elsif ($ynode = ynode($value)) {
- $family = $ynode->family->short;
- }
-
- if ($o->{UseAliases}) {
- $o->{id_refcnt}{$node_id} ||= 0;
- if ($o->{id_refcnt}{$node_id} > 1) {
- if (defined $o->{id_anchor}{$node_id}) {
- $o->{stream} .= ' *' . $o->{id_anchor}{$node_id} . "\n";
- return;
- }
- my $anchor = $o->{AnchorPrefix} . $o->{anchor}++;
- $o->{stream} .= ' &' . $anchor;
- $o->{id_anchor}{$node_id} = $anchor;
- }
- }
-
- return _emit_scalar($value, $family) if $type eq 'SCALAR' and $family;
- return _emit_str($value) if $type eq 'SCALAR';
- return _emit_mapping($value, $family, $node_id, $context) if $type eq 'HASH';
- return _emit_sequence($value, $family) if $type eq 'ARRAY';
- warn YAML_DUMP_WARN_BAD_NODE_TYPE($type) if $^W;
- return _emit_str("$value");
- }
-
- # A YAML mapping is akin to a Perl hash.
- sub _emit_mapping {
- my ($value, $family, $node_id, $context) = @_;
- $o->{stream} .= " !$family" if $family;
-
- # Sometimes 'keys' fails. Like on a bad tie implementation.
- my $empty_hash = not(eval {keys %$value});
- warn YAML_EMIT_WARN_KEYS($@) if $^W and $@;
- return ($o->{stream} .= " {}\n") if $empty_hash;
-
- # If CompressSeries is on (default) and legal is this context, then
- # use it and make the indent level be 2 for this node.
- if ($context == FROMARRAY and $o->{CompressSeries} and
- not (defined $o->{id_anchor}{$node_id} or $family or $empty_hash)
- ) {
- $o->{stream} .= ' ';
- $o->{offset}[$o->{level}+1] = $o->{offset}[$o->{level}] + 2;
- }
- else {
- $context = 0;
- $o->{stream} .= "\n" unless $o->{headless} && not($o->{headless} = 0);
- $o->{offset}[$o->{level}+1] = $o->{offset}[$o->{level}] + $o->{Indent};
- }
-
- $o->{level}++;
- my @keys;
- if ($o->{SortKeys} == 1) {
- if (ynode($value)) {
- @keys = keys %$value;
- }
- else {
- @keys = sort keys %$value;
- }
- }
- elsif ($o->{SortKeys} == 2) {
- @keys = sort keys %$value;
- }
- # XXX This is hackish but sometimes handy. Not sure whether to leave it in.
- elsif (ref($o->{SortKeys}) eq 'ARRAY') {
- my $i = 1;
- my %order = map { ($_, $i++) } @{$o->{SortKeys}};
- @keys = sort {
- (defined $order{$a} and defined $order{$b})
- ? ($order{$a} <=> $order{$b})
- : ($a cmp $b);
- } keys %$value;
- }
- else {
- @keys = keys %$value;
- }
- # Force the YAML::VALUE ('=') key to sort last.
- if (exists $value->{&VALUE}) {
- for (my $i = 0; $i < @keys; $i++) {
- if ($keys[$i] eq &VALUE) {
- splice(@keys, $i, 1);
- push @keys, &VALUE;
- last;
- }
- }
- }
-
- for my $key (@keys) {
- _emit_key($key, $context);
- $context = 0;
- $o->{stream} .= ':';
- _emit_node($value->{$key});
- }
- $o->{level}--;
- }
-
- # A YAML series is akin to a Perl array.
- sub _emit_sequence {
- my ($value, $family) = @_;
- $o->{stream} .= " !$family" if $family;
-
- return ($o->{stream} .= " []\n") if @$value == 0;
-
- $o->{stream} .= "\n" unless $o->{headless} && not($o->{headless} = 0);
-
- # XXX Really crufty feature. Better implemented by ynodes.
- if ($o->{InlineSeries} and
- @$value <= $o->{InlineSeries} and
- not (scalar grep {ref or /\n/} @$value)
- ) {
- $o->{stream} =~ s/\n\Z/ /;
- $o->{stream} .= '[';
- for (my $i = 0; $i < @$value; $i++) {
- _emit_str($value->[$i], KEY);
- last if $i == $#{$value};
- $o->{stream} .= ', ';
- }
- $o->{stream} .= "]\n";
- return;
- }
-
- $o->{offset}[$o->{level} + 1] = $o->{offset}[$o->{level}] + $o->{Indent};
- $o->{level}++;
- for my $val (@$value) {
- $o->{stream} .= ' ' x $o->{offset}[$o->{level}];
- $o->{stream} .= '-';
- _emit_node($val, FROMARRAY);
- }
- $o->{level}--;
- }
-
- # Emit a mapping key
- sub _emit_key {
- my ($value, $context) = @_;
- $o->{stream} .= ' ' x $o->{offset}[$o->{level}]
- unless $context == FROMARRAY;
- _emit_str($value, KEY);
- }
-
- # Emit a blessed SCALAR
- sub _emit_scalar {
- my ($value, $family) = @_;
- $o->{stream} .= " !$family";
- _emit_str($value, BLESSED);
- }
-
- # Emit a string value. YAML has many scalar styles. This routine attempts to
- # guess the best style for the text.
- sub _emit_str {
- my $type = $_[1] || 0;
-
- # Use heuristics to find the best scalar emission style.
- $o->{offset}[$o->{level} + 1] = $o->{offset}[$o->{level}] + $o->{Indent};
- $o->{level}++;
-
- if (defined $_[0] and
- $_[0] !~ /$ESCAPE_CHAR/ and
- (length($_[0]) > 50 or $_[0] =~ /\n\s/ or
- ($o->{ForceBlock} and $type != KEY)
- )
- ) {
- $o->{stream} .= ($type == KEY) ? '? ' : ' ';
- if (($o->{UseFold} and not $o->{ForceBlock}) or
- ($_[0] =~ /^\S[^\n]{76}/m)
- ) {
- if (is_valid_implicit($_[0]) && # XXX Check implicit check
- $type != BLESSED
- ) {
- $o->{stream} .= '! ';
- }
- _emit_nested($FOLD_CHAR, $_[0]);
- }
- else {
- _emit_nested($BLOCK_CHAR, $_[0]);
- }
- $o->{stream} .= "\n";
- }
- else {
- $o->{stream} .= ' ' if $type != KEY;
- if (defined $_[0] && $_[0] eq VALUE) {
- $o->{stream} .= '=';
- }
- elsif (is_valid_implicit($_[0])) {
- _emit_simple($_[0]);
- }
- elsif ($_[0] =~ /$ESCAPE_CHAR|\n|\'/) {
- _emit_double($_[0]);
- }
- else {
- _emit_single($_[0]);
- }
- $o->{stream} .= "\n" if $type != KEY;
- }
-
- $o->{level}--;
-
- return;
- }
-
- # Check whether or not a scalar should be emitted as an simple scalar.
- sub is_valid_implicit {
- return 1 if not defined $_[0];
- return 1 if $_[0] =~ /^(-?\d+)$/; # !int
- return 1 if $_[0] =~ /^-?\d+\.\d+$/; # !float
- return 1 if $_[0] =~ /^-?\d+e[+-]\d+$/; # !float
- # XXX - Detect date objects someday (or not)
- return 0 if $_[0] =~ /$ESCAPE_CHAR/;
- return 0 if $_[0] =~ /(^\s|\:( |$)|\#( |$)|\s$)/;
- return 1 if $_[0] =~ /^\w/; # !str
- return 0;
- }
-
- # A nested scalar is either block or folded
- sub _emit_nested {
- my ($indicator, $value) = @_;
- $o->{stream} .= $indicator;
- $value =~ /(\n*)\Z/;
- my $chomp = length $1 ? (length $1 > 1) ? '+' : '' : '-';
- $value = '~' if not defined $value;
- $o->{stream} .= $chomp;
- $o->{stream} .= $o->{Indent} if $value =~ /^\s/;
- if ($indicator eq $FOLD_CHAR) {
- $value = fold($value);
- chop $value unless $chomp eq '+';
- }
- $o->{stream} .= indent($value);
- }
-
- # Simple means that the scalar is unquoted. It is analyzed for its type
- # implicitly using regexes.
- sub _emit_simple {
- $o->{stream} .= defined $_[0] ? $_[0] : '~';
- }
-
- # Double quoting is for single lined escaped strings.
- sub _emit_double {
- (my $escaped = escape($_[0])) =~ s/"/\\"/g;
- $o->{stream} .= qq{"$escaped"};
- }
-
- # Single quoting is for single lined unescaped strings.
- sub _emit_single {
- $o->{stream} .= "'$_[0]'";
- }
-
- #==============================================================================
- # Read a YAML stream from a file and call Load on it.
- sub LoadFile {
- my $filename = shift;
- local $/ = "\n"; # reset special to "sane"
- open MYYAML, $filename or croak YAML_LOAD_ERR_FILE_INPUT($filename, $!);
- my $yaml = join '', <MYYAML>;
- close MYYAML;
- return Load($yaml);
- }
-
- # Deserialize a YAML stream into a list of data elements
- sub Load {
- croak YAML_LOAD_USAGE() unless @_ == 1;
- $o = YAML->new;
- $o->{stream} = defined $_[0] ? $_[0] : '';
- return load();
- }
-
- # Aliases for Load
- *Undent = *Undent = \&Load;
- *Eval = *Eval = \&Load;
- *thaw = *thaw = \&Load;
-
- # OO version of Load
- sub load {
- # local $| = 1; # set buffering to "hot" (for testing)
- local $/ = "\n"; # reset special to "sane"
- return _parse();
- }
-
- # Top level function for parsing. Parse each document in order and
- # handle processing for YAML headers.
- sub _parse {
- my (%directives, $preface);
- $o->{stream} =~ s|\015\012|\012|g;
- $o->{stream} =~ s|\015|\012|g;
- $o->{line} = 0;
- croak YAML_PARSE_ERR_BAD_CHARS()
- if $o->{stream} =~ /$ESCAPE_CHAR/;
- croak YAML_PARSE_ERR_NO_FINAL_NEWLINE()
- if length($o->{stream}) and
- $o->{stream} !~ s/(.)\n\Z/$1/s;
- @{$o->{lines}} = split /\x0a/, $o->{stream}, -1;
- $o->{line} = 1;
- # Throw away any comments or blanks before the header (or start of
- # content for headerless streams)
- _parse_throwaway_comments();
- $o->{document} = 0;
- $o->{documents} = [];
- # Add an "assumed" header if there is no header and the stream is
- # not empty (after initial throwaways).
- if (not $o->{eos}) {
- if ($o->{lines}[0] !~ /^---(\s|$)/) {
- unshift @{$o->{lines}}, '--- #YAML:1.0';
- $o->{line}--;
- }
- }
-
- # Main Loop. Parse out all the top level nodes and return them.
- while (not $o->{eos}) {
- $o->{anchor2node} = {};
- $o->{document}++;
- $o->{done} = 0;
- $o->{level} = 0;
- $o->{offset}[0] = -1;
-
- if ($o->{lines}[0] =~ /^---\s*(.*)$/) {
- my @words = split /\s+/, $1;
- %directives = ();
- while (@words && $words[0] =~ /^#(\w+):(\S.*)$/) {
- my ($key, $value) = ($1, $2);
- shift(@words);
- if (defined $directives{$key}) {
- warn YAML_PARSE_WARN_MULTIPLE_DIRECTIVES
- ($key, $o->{document}) if $^W;
- next;
- }
- $directives{$key} = $value;
- }
- $o->{preface} = join ' ', @words;
- }
- else {
- croak YAML_PARSE_ERR_NO_SEPARATOR();
- }
-
- if (not $o->{done}) {
- _parse_next_line(COLLECTION);
- }
- if ($o->{done}) {
- $o->{indent} = -1;
- $o->{content} = '';
- }
-
- $directives{YAML} ||= '1.0';
- $directives{TAB} ||= 'NONE';
- ($o->{major_version}, $o->{minor_version}) =
- split /\./, $directives{YAML}, 2;
- croak YAML_PARSE_ERR_BAD_MAJOR_VERSION($directives{YAML})
- if ($o->{major_version} ne '1');
- warn YAML_PARSE_WARN_BAD_MINOR_VERSION($directives{YAML})
- if ($^W and $o->{minor_version} ne '0');
- croak "Unrecognized TAB policy" # XXX add to ::Error
- unless $directives{TAB} =~ /^(NONE|\d+)(:HARD)?$/;
-
-
- push @{$o->{documents}}, _parse_node();
- }
- return wantarray ? @{$o->{documents}} : $o->{documents}[-1];
- }
-
- # This function is the dispatcher for parsing each node. Every node
- # recurses back through here. (Inlines are an exception as they have
- # their own sub-parser.)
- sub _parse_node {
- # ??????????????????????????????????????
- # $|=1;
- # print <<END;
- # _parse_node ${\++$YAML::x}
- # indent - $o->{indent}
- # preface - $o->{preface}
- # content - $o->{content}
- # level - $o->{level}
- # offsets - @{$o->{offset}}
- # END
- # ??????????????????????????????????????
- my $preface = $o->{preface};
- $o->{preface} = '';
- my ($node, $type, $indicator, $escape, $chomp) = ('') x 5;
- my ($anchor, $alias, $explicit, $implicit, $class) = ('') x 5;
- ($anchor, $alias, $explicit, $implicit, $class, $preface) =
- _parse_qualifiers($preface);
- if ($anchor) {
- $o->{anchor2node}{$anchor} = CORE::bless [], 'YAML-anchor2node';
- }
- $o->{inline} = '';
- while (length $preface) {
- my $line = $o->{line} - 1;
- # XXX rking suggests refactoring the following regex and its evil twin
- if ($preface =~ s/^($FOLD_CHAR|$BLOCK_CHAR_RX)(-|\+)?\d*\s*//) {
- $indicator = $1;
- $chomp = $2 if defined($2);
- }
- else {
- croak YAML_PARSE_ERR_TEXT_AFTER_INDICATOR() if $indicator;
- $o->{inline} = $preface;
- $preface = '';
- }
- }
- if ($alias) {
- croak YAML_PARSE_ERR_NO_ANCHOR($alias)
- unless defined $o->{anchor2node}{$alias};
- if (ref($o->{anchor2node}{$alias}) ne 'YAML-anchor2node') {
- $node = $o->{anchor2node}{$alias};
- }
- else {
- $node = do {my $sv = "*$alias"};
- push @{$o->{anchor2node}{$alias}}, [\$node, $o->{line}];
- }
- }
- elsif (length $o->{inline}) {
- $node = _parse_inline(1, $implicit, $explicit, $class);
- if (length $o->{inline}) {
- croak YAML_PARSE_ERR_SINGLE_LINE();
- }
- }
- elsif ($indicator eq $BLOCK_CHAR) {
- $o->{level}++;
- $node = _parse_block($chomp);
- $node = _parse_implicit($node) if $implicit;
- $o->{level}--;
- }
- elsif ($indicator eq $FOLD_CHAR) {
- $o->{level}++;
- $node = _parse_unfold($chomp);
- $node = _parse_implicit($node) if $implicit;
- $o->{level}--;
- }
- else {
- $o->{level}++;
- $o->{offset}[$o->{level}] ||= 0;
- if ($o->{indent} == $o->{offset}[$o->{level}]) {
- if ($o->{content} =~ /^-( |$)/) {
- $node = _parse_seq($anchor);
- }
- elsif ($o->{content} =~ /(^\?|\:( |$))/) {
- $node = _parse_mapping($anchor);
- }
- elsif ($preface =~ /^\s*$/) {
- $node = _parse_implicit('');
- }
- else {
- croak YAML_PARSE_ERR_BAD_NODE();
- }
- }
- else {
- $node = '';
- }
- $o->{level}--;
- }
- $#{$o->{offset}} = $o->{level};
-
- if ($explicit) {
- if ($class) {
- if (not ref $node) {
- my $copy = $node;
- undef $node;
- $node = \$copy;
- }
- CORE::bless $node, $class;
- }
- else {
- $node = _parse_explicit($node, $explicit);
- }
- }
- if ($anchor) {
- if (ref($o->{anchor2node}{$anchor}) eq 'YAML-anchor2node') {
- # XXX Can't remember what this code actually does
- for my $ref (@{$o->{anchor2node}{$anchor}}) {
- ${$ref->[0]} = $node;
- warn YAML_LOAD_WARN_UNRESOLVED_ALIAS($anchor, $ref->[1]) if $^W;
- }
- }
- $o->{anchor2node}{$anchor} = $node;
- }
- return $node;
- }
-
- # Preprocess the qualifiers that may be attached to any node.
- sub _parse_qualifiers {
- my ($preface) = @_;
- my ($anchor, $alias, $explicit, $implicit, $class, $token) = ('') x 6;
- $o->{inline} = '';
- while ($preface =~ /^[&*!]/) {
- my $line = $o->{line} - 1;
- if ($preface =~ s/^\!(\S+)\s*//) {
- croak YAML_PARSE_ERR_MANY_EXPLICIT() if $explicit;
- $explicit = $1;
- }
- elsif ($preface =~ s/^\!\s*//) {
- croak YAML_PARSE_ERR_MANY_IMPLICIT() if $implicit;
- $implicit = 1;
- }
- elsif ($preface =~ s/^\&([^ ,:]+)\s*//) {
- $token = $1;
- croak YAML_PARSE_ERR_BAD_ANCHOR()
- unless $token =~ /^[a-zA-Z0-9]+$/;
- croak YAML_PARSE_ERR_MANY_ANCHOR() if $anchor;
- croak YAML_PARSE_ERR_ANCHOR_ALIAS() if $alias;
- $anchor = $token;
- }
- elsif ($preface =~ s/^\*([^ ,:]+)\s*//) {
- $token = $1;
- croak YAML_PARSE_ERR_BAD_ALIAS() unless $token =~ /^[a-zA-Z0-9]+$/;
- croak YAML_PARSE_ERR_MANY_ALIAS() if $alias;
- croak YAML_PARSE_ERR_ANCHOR_ALIAS() if $anchor;
- $alias = $token;
- }
- }
- return ($anchor, $alias, $explicit, $implicit, $class, $preface);
- }
-
- # Morph a node to it's explicit type
- sub _parse_explicit {
- my ($node, $explicit) = @_;
- if ($explicit =~ m{^(int|float|bool|date|time|datetime|binary)$}) {
- my $handler = "YAML::_load_$1";
- no strict 'refs';
- return &$handler($node);
- }
- elsif ($explicit =~ m{^perl/(glob|regexp|code|ref)\:(\w(\w|\:\:)*)?$}) {
- my ($type, $class) = (($1 || ''), ($2 || ''));
- my $handler = "YAML::_load_perl_$type";
- no strict 'refs';
- if (defined &$handler) {
- return &$handler($node, $class);
- }
- else {
- croak YAML_LOAD_ERR_NO_CONVERT('XXX', $explicit);
- }
- }
- elsif ($explicit =~ m{^perl/(\@|\$)?([a-zA-Z](\w|::)+)$}) {
- my ($package) = ($2);
- my $handler = "${package}::yaml_load";
- no strict 'refs';
- if (defined &$handler) {
- return &$handler(YAML::Node->new($node, $explicit));
- }
- else {
- return CORE::bless $node, $package;
- }
- }
- elsif ($explicit !~ m|/|) {
- croak YAML_LOAD_ERR_NO_CONVERT('XXX', $explicit);
- }
- else {
- return YAML::Node->new($node, $explicit);
- }
- }
-
- # Morph to a perl reference
- sub _load_perl_ref {
- my ($node) = @_;
- croak YAML_LOAD_ERR_NO_DEFAULT_VALUE('ptr') unless exists $node->{&VALUE};
- return \$node->{&VALUE};
- }
-
- # Morph to a perl regexp
- sub _load_perl_regexp {
- my ($node) = @_;
- my ($regexp, $modifiers);
- if (defined $node->{REGEXP}) {
- $regexp = $node->{REGEXP};
- delete $node->{REGEXP};
- }
- else {
- warn YAML_LOAD_WARN_NO_REGEXP_IN_REGEXP() if $^W;
- return undef;
- }
- if (defined $node->{MODIFIERS}) {
- $modifiers = $node->{MODIFIERS};
- delete $node->{MODIFIERS};
- } else {
- $modifiers = '';
- }
- for my $elem (sort keys %$node) {
- warn YAML_LOAD_WARN_BAD_REGEXP_ELEM($elem) if $^W;
- }
- my $value = eval "qr($regexp)$modifiers";
- if ($@) {
- warn YAML_LOAD_WARN_REGEXP_CREATE($regexp, $modifiers, $@) if $^W;
- return undef;
- }
- return $value;
- }
-
- # Morph to a perl glob
- sub _load_perl_glob {
- my ($node) = @_;
- my ($name, $package);
- if (defined $node->{NAME}) {
- $name = $node->{NAME};
- delete $node->{NAME};
- }
- else {
- warn YAML_LOAD_WARN_GLOB_NAME() if $^W;
- return undef;
- }
- if (defined $node->{PACKAGE}) {
- $package = $node->{PACKAGE};
- delete $node->{PACKAGE};
- } else {
- $package = 'main';
- }
- no strict 'refs';
- if (exists $node->{SCALAR}) {
- *{"${package}::$name"} = \$node->{SCALAR};
- delete $node->{SCALAR};
- }
- for my $elem (qw(ARRAY HASH CODE IO)) {
- if (exists $node->{$elem}) {
- if ($elem eq 'IO') {
- warn YAML_LOAD_WARN_GLOB_IO() if $^W;
- delete $node->{IO};
- next;
- }
- *{"${package}::$name"} = $node->{$elem};
- delete $node->{$elem};
- }
- }
- for my $elem (sort keys %$node) {
- warn YAML_LOAD_WARN_BAD_GLOB_ELEM($elem) if $^W;
- }
- return *{"${package}::$name"};
- }
-
- # Special support for an empty mapping
- #sub _parse_str_to_map {
- # my ($node) = @_;
- # croak YAML_LOAD_ERR_NON_EMPTY_STRING('mapping') unless $node eq '';
- # return {};
- #}
-
- # Special support for an empty sequence
- #sub _parse_str_to_seq {
- # my ($node) = @_;
- # croak YAML_LOAD_ERR_NON_EMPTY_STRING('sequence') unless $node eq '';
- # return [];
- #}
-
- # Support for sparse sequences
- #sub _parse_map_to_seq {
- # my ($node) = @_;
- # my $seq = [];
- # for my $index (keys %$node) {
- # croak YAML_LOAD_ERR_BAD_MAP_TO_SEQ($index) unless $index =~ /^\d+/;
- # $seq->[$index] = $node->{$index};
- # }
- # return $seq;
- #}
-
- # Support for !int
- sub _load_int {
- my ($node) = @_;
- croak YAML_LOAD_ERR_BAD_STR_TO_INT() unless $node =~ /^-?\d+$/;
- return $node;
- }
-
- # Support for !date
- sub _load_date {
- my ($node) = @_;
- croak YAML_LOAD_ERR_BAD_STR_TO_DATE() unless $node =~ /^\d\d\d\d-\d\d-\d\d$/;
- return $node;
- }
-
- # Support for !time
- sub _load_time {
- my ($node) = @_;
- croak YAML_LOAD_ERR_BAD_STR_TO_TIME() unless $node =~ /^\d\d:\d\d:\d\d$/;
- return $node;
- }
-
- # Support for !perl/code;deparse
- sub _load_perl_code {
- my ($node, $class) = @_;
- if ($o->{LoadCode}) {
- my $code = eval "package main; sub $node";
- if ($@) {
- warn YAML_LOAD_WARN_PARSE_CODE($@) if $^W;
- return sub {};
- }
- else {
- CORE::bless $code, $class if $class;
- return $code;
- }
- }
- else {
- return sub {};
- }
- }
-
- # Parse a YAML mapping into a Perl hash
- sub _parse_mapping {
- my ($anchor) = @_;
- my $mapping = {};
- $o->{anchor2node}{$anchor} = $mapping;
- my $key;
- while (not $o->{done} and $o->{indent} == $o->{offset}[$o->{level}]) {
- # If structured key:
- if ($o->{content} =~ s/^\?\s*//) {
- $o->{preface} = $o->{content};
- _parse_next_line(COLLECTION);
- $key = _parse_node();
- $key = "$key";
- }
- # If "default" key (equals sign)
- elsif ($o->{content} =~ s/^\=\s*//) {
- $key = VALUE;
- }
- # If "comment" key (slash slash)
- elsif ($o->{content} =~ s/^\=\s*//) {
- $key = COMMENT;
- }
- # Regular scalar key:
- else {
- $o->{inline} = $o->{content};
- $key = _parse_inline();
- $key = "$key";
- $o->{content} = $o->{inline};
- $o->{inline} = '';
- }
-
- unless ($o->{content} =~ s/^:\s*//) {
- croak YAML_LOAD_ERR_BAD_MAP_ELEMENT();
- }
- $o->{preface} = $o->{content};
- my $line = $o->{line};
- _parse_next_line(COLLECTION);
- my $value = _parse_node();
- if (exists $mapping->{$key}) {
- warn YAML_LOAD_WARN_DUPLICATE_KEY() if $^W;
- }
- else {
- $mapping->{$key} = $value;
- }
- }
- return $mapping;
- }
-
- # Parse a YAML sequence into a Perl array
- sub _parse_seq {
- my ($anchor) = @_;
- my $seq = [];
- $o->{anchor2node}{$anchor} = $seq;
- while (not $o->{done} and $o->{indent} == $o->{offset}[$o->{level}]) {
- if ($o->{content} =~ /^-(?: (.*))?$/) {
- $o->{preface} = defined($1) ? $1 : '';
- }
- else {
- croak YAML_LOAD_ERR_BAD_SEQ_ELEMENT();
- }
- if ($o->{preface} =~ /^(\s*)(\w.*\:(?: |$).*)$/) {
- $o->{indent} = $o->{offset}[$o->{level}] + 2 + length($1);
- $o->{content} = $2;
- $o->{offset}[++$o->{level}] = $o->{indent};
- $o->{preface} = '';
- push @$seq, _parse_mapping('');
- $o->{level}--;
- $#{$o->{offset}} = $o->{level};
- }
- else {
- _parse_next_line(COLLECTION);
- push @$seq, _parse_node();
- }
- }
- return $seq;
- }
-
- # Parse an inline value. Since YAML supports inline collections, this is
- # the top level of a sub parsing.
- sub _parse_inline {
- my ($top, $top_implicit, $top_explicit, $top_class) = (@_, '', '', '', '');
- $o->{inline} =~ s/^\s*(.*)\s*$/$1/;
- my ($node, $anchor, $alias, $explicit, $implicit, $class) = ('') x 6;
- ($anchor, $alias, $explicit, $implicit, $class, $o->{inline}) =
- _parse_qualifiers($o->{inline});
- if ($anchor) {
- $o->{anchor2node}{$anchor} = CORE::bless [], 'YAML-anchor2node';
- }
- $implicit ||= $top_implicit;
- $explicit ||= $top_explicit;
- $class ||= $top_class;
- ($top_implicit, $top_explicit, $top_class) = ('', '', '');
- if ($alias) {
- croak YAML_PARSE_ERR_NO_ANCHOR($alias)
- unless defined $o->{anchor2node}{$alias};
- if (ref($o->{anchor2node}{$alias}) ne 'YAML-anchor2node') {
- $node = $o->{anchor2node}{$alias};
- }
- else {
- $node = do {my $sv = "*$alias"};
- push @{$o->{anchor2node}{$alias}}, [\$node, $o->{line}];
- }
- }
- elsif ($o->{inline} =~ /^\{/) {
- $node = _parse_inline_mapping($anchor);
- }
- elsif ($o->{inline} =~ /^\[/) {
- $node = _parse_inline_seq($anchor);
- }
- elsif ($o->{inline} =~ /^"/) {
- $node = _parse_inline_double_quoted();
- $node = _unescape($node);
- $node = _parse_implicit($node) if $implicit;
- }
- elsif ($o->{inline} =~ /^'/) {
- $node = _parse_inline_single_quoted();
- $node = _parse_implicit($node) if $implicit;
- }
- else {
- if ($top) {
- $node = $o->{inline};
- $o->{inline} = '';
- }
- else {
- $node = _parse_inline_simple();
- }
- $node = _parse_implicit($node) unless $explicit;
- }
- if ($explicit) {
- if ($class) {
- if (not ref $node) {
- my $copy = $node;
- undef $node;
- $node = \$copy;
- }
- CORE::bless $node, $class;
- }
- else {
- $node = _parse_explicit($node, $explicit);
- }
- }
- if ($anchor) {
- if (ref($o->{anchor2node}{$anchor}) eq 'YAML-anchor2node') {
- for my $ref (@{$o->{anchor2node}{$anchor}}) {
- ${$ref->[0]} = $node;
- warn YAML_LOAD_WARN_UNRESOLVED_ALIAS($anchor, $ref->[1]) if $^W;
- }
- }
- $o->{anchor2node}{$anchor} = $node;
- }
- return $node;
- }
-
- # Parse the inline YAML mapping into a Perl hash
- sub _parse_inline_mapping {
- my ($anchor) = @_;
- my $node = {};
- $o->{anchor2node}{$anchor} = $node;
-
- croak YAML_PARSE_ERR_INLINE_MAP() unless $o->{inline} =~ s/^\{\s*//;
- while (not $o->{inline} =~ s/^\}//) {
- my $key = _parse_inline();
- croak YAML_PARSE_ERR_INLINE_MAP() unless $o->{inline} =~ s/^\: \s*//;
- my $value = _parse_inline();
- if (exists $node->{$key}) {
- warn YAML_LOAD_WARN_DUPLICATE_KEY() if $^W;
- }
- else {
- $node->{$key} = $value;
- }
- next if $o->{inline} =~ /^\}/;
- croak YAML_PARSE_ERR_INLINE_MAP() unless $o->{inline} =~ s/^\,\s*//;
- }
- return $node;
- }
-
- # Parse the inline YAML sequence into a Perl array
- sub _parse_inline_seq {
- my ($anchor) = @_;
- my $node = [];
- $o->{anchor2node}{$anchor} = $node;
-
- croak YAML_PARSE_ERR_INLINE_SEQUENCE() unless $o->{inline} =~ s/^\[\s*//;
- while (not $o->{inline} =~ s/^\]//) {
- my $value = _parse_inline();
- push @$node, $value;
- next if $o->{inline} =~ /^\]/;
- croak YAML_PARSE_ERR_INLINE_SEQUENCE()
- unless $o->{inline} =~ s/^\,\s*//;
- }
- return $node;
- }
-
- # Parse the inline double quoted string.
- sub _parse_inline_double_quoted {
- my $node;
- if ($o->{inline} =~ /^"((?:\\"|[^"])*)"\s*(.*)$/) {
- $node = $1;
- $o->{inline} = $2;
- $node =~ s/\\"/"/g;
- } else {
- croak YAML_PARSE_ERR_BAD_DOUBLE();
- }
- return $node;
- }
-
-
- # Parse the inline single quoted string.
- sub _parse_inline_single_quoted {
- my $node;
- if ($o->{inline} =~ /^'((?:''|[^'])*)'\s*(.*)$/) {
- $node = $1;
- $o->{inline} = $2;
- $node =~ s/''/'/g;
- } else {
- croak YAML_PARSE_ERR_BAD_SINGLE();
- }
- return $node;
- }
-
- # Parse the inline unquoted string and do implicit typing.
- sub _parse_inline_simple {
- my $value;
- if ($o->{inline} =~ /^(|[^!@#%^&*].*?)(?=[,[\]{}]|: |- |:\s*$|$)/) {
- $value = $1;
- substr($o->{inline}, 0, length($1)) = '';
- }
- else {
- croak YAML_PARSE_ERR_BAD_INLINE_IMPLICIT($value);
- }
- return $value;
- }
-
- # Apply regex matching for YAML's implicit types. !str, !int, !real,
- # !null, !date and !time
- sub _parse_implicit {
- my ($value) = @_;
- $value =~ s/\s*$//;
- return $value if $value eq '';
- return $value + 0 if $value =~ /^-?\d+$/;
- return $value * 1.0
- if ($value =~ /^[+-]?(\d*)(?:\.(\d*))?([Ee][+-]?\d+)?$/) and
- (defined($3) ? defined($1) : defined($1) || defined($2));
- return "$value" if $value =~ # XXX Change this to a Time::Object
- /^\d{4}\-\d\d\-\d\d(T\d\d:\d\d:\d\d(\.\d*[1-9])?(Z|[-+]\d\d(:\d\d)?))?$/;
- return "$value" if $value =~ /^\w/;
- return undef if $value =~ /^~$/;
- return 1 if $value =~ /^\+$/;
- return 0 if $value =~ /^-$/;
- croak YAML_PARSE_ERR_BAD_IMPLICIT($value);
- }
-
- # Unfold a YAML multiline scalar into a single string.
- sub _parse_unfold {
- my ($chomp) = @_;
- my $node = '';
- my $space = 0;
- while (not $o->{done} and $o->{indent} == $o->{offset}[$o->{level}]) {
- $node .= "$o->{content}\n";
- _parse_next_line(LEAF);
- }
- $node =~ s/^(\S.*)\n(?=\S)/$1 /gm;
- $node =~ s/^(\S.*)\n(\n+\S)/$1$2/gm;
- $node =~ s/\n*\Z// unless $chomp eq '+';
- $node .= "\n" unless $chomp;
- return $node;
- }
-
- # Parse a YAML block style scalar. This is like a Perl here-document.
- sub _parse_block {
- my ($chomp) = @_;
- my $node = '';
- while (not $o->{done} and $o->{indent} == $o->{offset}[$o->{level}]) {
- $node .= $o->{content} . "\n";
- _parse_next_line(LEAF);
- }
- return $node if '+' eq $chomp;
- $node =~ s/\n*\Z/\n/;
- $node =~ s/\n\Z// if $chomp eq '-';
- return $node;
- }
-
- # Handle Perl style '#' comments. Comments must be at the same indentation
- # level as the collection line following them.
- sub _parse_throwaway_comments {
- while (@{$o->{lines}} and
- $o->{lines}[0] =~ m{^\s*(\#|$)}
- ) {
- shift @{$o->{lines}};
- $o->{line}++;
- }
- $o->{eos} = $o->{done} = not @{$o->{lines}};
- }
-
- # This is the routine that controls what line is being parsed. It gets called
- # once for each line in the YAML stream.
- #
- # This routine must:
- # 1) Skip past the current line
- # 2) Determine the indentation offset for a new level
- # 3) Find the next _content_ line
- # A) Skip over any throwaways (Comments/blanks)
- # B) Set $o->{indent}, $o->{content}, $o->{line}
- # 4) Expand tabs appropriately
- sub _parse_next_line {
- my ($type) = @_;
- my $level = $o->{level};
- my $offset = $o->{offset}[$level];
- croak YAML_EMIT_ERR_BAD_LEVEL() unless defined $offset;
- shift @{$o->{lines}};
- $o->{eos} = $o->{done} = not @{$o->{lines}};
- return if $o->{eos};
- $o->{line}++;
-
- # Determine the offset for a new leaf node
- if ($o->{preface} =~ qr/(?:$FOLD_CHAR|$BLOCK_CHAR_RX)(?:-|\+)?(\d*)\s*$/) {
- croak YAML_PARSE_ERR_ZERO_INDENT() if length($1) and $1 == 0;
- $type = LEAF;
- if (length($1)) {
- $o->{offset}[$level + 1] = $offset + $1;
- }
- else {
- # First get rid of any comments.
- while (@{$o->{lines}} && ($o->{lines}[0] =~ /^\s*#/)) {
- $o->{lines}[0] =~ /^( *)/ or die;
- last unless length($1) <= $offset;
- shift @{$o->{lines}};
- $o->{line}++;
- }
- $o->{eos} = $o->{done} = not @{$o->{lines}};
- return if $o->{eos};
- if ($o->{lines}[0] =~ /^( *)\S/ and length($1) > $offset) {
- $o->{offset}[$level+1] = length($1);
- }
- else {
- $o->{offset}[$level+1] = $offset + 1;
- }
- }
- $offset = $o->{offset}[++$level];
- }
- # Determine the offset for a new collection level
- elsif ($type == COLLECTION and
- $o->{preface} =~ /^(\s*(\!\S*|\&\S+))*\s*$/) {
- _parse_throwaway_comments();
- if ($o->{eos}) {
- $o->{offset}[$level+1] = $offset + 1;
- return;
- }
- else {
- $o->{lines}[0] =~ /^( *)\S/ or die;
- if (length($1) > $offset) {
- $o->{offset}[$level+1] = length($1);
- }
- else {
- $o->{offset}[$level+1] = $offset + 1;
- }
- }
- $offset = $o->{offset}[++$level];
- }
-
- if ($type == LEAF) {
- while (@{$o->{lines}} and
- $o->{lines}[0] =~ m{^( *)(\#)} and
- length($1) < $offset
- ) {
- shift @{$o->{lines}};
- $o->{line}++;
- }
- $o->{eos} = $o->{done} = not @{$o->{lines}};
- }
- else {
- _parse_throwaway_comments();
- }
- return if $o->{eos};
-
- if ($o->{lines}[0] =~ /^---(\s|$)/) {
- $o->{done} = 1;
- return;
- }
- if ($type == LEAF and
- $o->{lines}[0] =~ /^ {$offset}(.*)$/
- ) {
- $o->{indent} = $offset;
- $o->{content} = $1;
- }
- elsif ($o->{lines}[0] =~ /^\s*$/) {
- $o->{indent} = $offset;
- $o->{content} = '';
- }
- else {
- $o->{lines}[0] =~ /^( *)(\S.*)$/;
- # print " indent(${\length($1)}) offsets(@{$o->{offset}}) \n";
- while ($o->{offset}[$level] > length($1)) {
- $level--;
- }
- croak YAML_PARSE_ERR_INCONSISTENT_INDENTATION()
- if $o->{offset}[$level] != length($1);
- $o->{indent} = length($1);
- $o->{content} = $2;
- }
- croak YAML_PARSE_ERR_INDENTATION() if $o->{indent} - $offset > 1;
- }
-
- #==============================================================================
- # Utility subroutines.
- #==============================================================================
-
- # Indent a scalar to the current indentation level.
- sub indent {
- my ($text) = @_;
- return $text unless length $text;
- $text =~ s/\n\Z//;
- my $indent = ' ' x $o->{offset}[$o->{level}];
- $text =~ s/^/$indent/gm;
- $text = "\n$text";
- return $text;
- }
-
- # Fold a paragraph to fit within a certain columnar restraint.
- sub fold {
- my ($text) = @_;
- my $folded = '';
- $text =~ s/^(\S.*)\n(?=\S)/$1\n\n/gm;
- while (length $text > 0) {
- if ($text =~ s/^([^\n]{0,76})(\n|\Z)//) {
- $folded .= $1;
- }
- elsif ($text =~ s/^(.{0,76})\s//) {
- $folded .= $1;
- }
- else {
- croak "bad news" unless $text =~ s/(.*?)(\s|\Z)//;
- $folded .= $1;
- }
- $folded .= "\n";
- }
- return $folded;
- }
-
- # Escapes for unprintable characters
- my @escapes = qw(\z \x01 \x02 \x03 \x04 \x05 \x06 \a
- \x08 \t \n \v \f \r \x0e \x0f
- \x10 \x11 \x12 \x13 \x14 \x15 \x16 \x17
- \x18 \x19 \x1a \e \x1c \x1d \x1e \x1f
- );
-
- # Escape the unprintable characters
- sub escape {
- my ($text) = @_;
- $text =~ s/\\/\\\\/g;
- $text =~ s/([\x00-\x1f])/$escapes[ord($1)]/ge;
- return $text;
- }
-
- # Printable characters for escapes
- my %unescapes =
- (
- z => "\x00", a => "\x07", t => "\x09",
- n => "\x0a", v => "\x0b", f => "\x0c",
- r => "\x0d", e => "\x1b", '\\' => '\\',
- );
-
- # Transform all the backslash style escape characters to their literal meaning
- sub _unescape {
- my ($node) = @_;
- $node =~ s/\\([never\\fartz]|x([0-9a-fA-F]{2}))/
- (length($1)>1)?pack("H2",$2):$unescapes{$1}/gex;
- return $node;
- }
-
- sub DESTROY () {}
-
- sub AUTOLOAD {
- (my $autoload = $YAML::AUTOLOAD) =~ s/^YAML:://;
- if ($autoload =~ /^[A-Z]/ and
- ref($_[0]) eq 'YAML' and
- defined $_[0]->{$autoload}
- ) {
- defined($_[1]) ? (($_[0]->{$autoload} = $_[1]), return $_[0])
- : return $_[0]->{$autoload};
- return;
- }
-
- croak "Can't autoload '$YAML::AUTOLOAD'\n"
- unless $autoload =~ /^YAML_(PARSE|LOAD|DUMP|EMIT)_(ERR|WARN|USAGE)/;
- require YAML::Error;
- $o->{error} = YAML::Error->new($autoload, $o->{line}, $o->{document}, @_);
- my $o_save = $o;
- my $dump = $o->{error}->dump;
- $o = $o_save;
- return "$dump...\n";
- }
-
- 1;
-