line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
### Data::PropertyList - Convert arbitrary objects to/from strings. |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
### Copyright 1996, 1997, 1998 Evolution Online Systems, Inc. |
4
|
|
|
|
|
|
|
# You may use this software for free under the terms of the Artistic License |
5
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
### Change History |
7
|
|
|
|
|
|
|
# 1998-12-17 Minor doc cleanup; added makefile and tests for distribution. |
8
|
|
|
|
|
|
|
# 1998-10-05 Tweaked output spacing for single-line string arrays. |
9
|
|
|
|
|
|
|
# 1998-10-05 Switched from use of String::Escape::add to direct hash access. |
10
|
|
|
|
|
|
|
# 1998-07-23 Conventionalized POD, switched to yyyy.mm_dd version numbering. |
11
|
|
|
|
|
|
|
# 1998-08-23 On further consideration, this really did belong in Data::*. |
12
|
|
|
|
|
|
|
# 1998-06-11 Improved support for <
|
13
|
|
|
|
|
|
|
# 1998-05-07 Fixed problem with reading "0 = ..." lines in hashes. |
14
|
|
|
|
|
|
|
# 1998-03-03 Replaced $r->class with ref($r) -Simon |
15
|
|
|
|
|
|
|
# 1998-02-28 Initialized _parse_multiline $value to '' to run clean under -w. |
16
|
|
|
|
|
|
|
# 1998-02-25 Version 1.00 - String::PropertyList |
17
|
|
|
|
|
|
|
# 1998-02-25 Moved to String:: and @EXPORT_OK for CPAN distribution - jeremy |
18
|
|
|
|
|
|
|
# 1998-01-28 Fixed variable name typo in _parse_array. |
19
|
|
|
|
|
|
|
# 1998-01-11 Added rudimentary support for comments: full-line comments only |
20
|
|
|
|
|
|
|
# 1998-01-02 Renamed package Data::PropertyList to Text::PropertyList -Simon |
21
|
|
|
|
|
|
|
# 1997-12-08 Removed package Data::Types, use UNIVERSAL::isa instead. -Piglet |
22
|
|
|
|
|
|
|
# 1997-11-19 Added loopback handling to astext; now Supress as XREF TO |
23
|
|
|
|
|
|
|
# 1997-10-28 Updated to use new Text::Escape interface. |
24
|
|
|
|
|
|
|
# 1997-10-21 Documentation cleanup. |
25
|
|
|
|
|
|
|
# 1997-08-17 Moved string escape/unescape code into new Text::Escape. -Simon |
26
|
|
|
|
|
|
|
# 1997-01-2? New fromDictionary parser -Eric |
27
|
|
|
|
|
|
|
# 1997-01-14 New asDictionary function provides closer match to NeXT style. |
28
|
|
|
|
|
|
|
# 1997-01-11 Cloned & cleaned for Inetics; moved I/O to file.pm. V3.0 -Simon |
29
|
|
|
|
|
|
|
# 1996-10-29 Added append flag and trailing \n to write. -Piglet |
30
|
|
|
|
|
|
|
# 1996-08-06 Partial fix for blessed data; treat as basic type. V2.05 -Simon |
31
|
|
|
|
|
|
|
# 1996-07-13 Cleaned up flow, fixed headers. |
32
|
|
|
|
|
|
|
# 1996-06-25 Wrote &write. V2.04 -EJ |
33
|
|
|
|
|
|
|
# 1996-06-23 Converted from Perl 4 library to Perl 5 package. V2.03 |
34
|
|
|
|
|
|
|
# 1996-06-18 Iterative line parsing replaces raw recursion. V2.02 |
35
|
|
|
|
|
|
|
# 1996-06-15 Clean start with support for nested data structures. V2.01 |
36
|
|
|
|
|
|
|
# 1996-05-26 Support for =<< multiline values. |
37
|
|
|
|
|
|
|
# 1996-05-08 Parse key-value pairs into a flat hash. Version 1. -Simon |
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
package Data::PropertyList; |
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
require 5.003; |
42
|
3
|
|
|
3
|
|
23164
|
use strict; |
|
3
|
|
|
|
|
8
|
|
|
3
|
|
|
|
|
134
|
|
43
|
|
|
|
|
|
|
|
44
|
3
|
|
|
3
|
|
17
|
use vars qw( $VERSION @ISA @EXPORT_OK ); |
|
3
|
|
|
|
|
5
|
|
|
3
|
|
|
|
|
233
|
|
45
|
|
|
|
|
|
|
$VERSION = 1998.12_17; |
46
|
|
|
|
|
|
|
|
47
|
3
|
|
|
3
|
|
16
|
use Exporter; |
|
3
|
|
|
|
|
15
|
|
|
3
|
|
|
|
|
187
|
|
48
|
|
|
|
|
|
|
push @ISA, qw( Exporter ); |
49
|
|
|
|
|
|
|
push @EXPORT_OK, qw( astext fromtext ); |
50
|
|
|
|
|
|
|
|
51
|
3
|
|
|
3
|
|
20
|
use vars qw( $Separator ); |
|
3
|
|
|
|
|
5
|
|
|
3
|
|
|
|
|
163
|
|
52
|
|
|
|
|
|
|
$Separator = '.'; |
53
|
|
|
|
|
|
|
|
54
|
3
|
|
|
3
|
|
3192
|
use String::Escape qw( qprintable unprintable ); |
|
3
|
|
|
|
|
21697
|
|
|
3
|
|
|
|
|
737
|
|
55
|
|
|
|
|
|
|
$String::Escape::Escapes{'astext'} = \&astext; |
56
|
|
|
|
|
|
|
$String::Escape::Escapes{'fromtext'} = \&fromtext; |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
### Writer |
59
|
|
|
|
|
|
|
|
60
|
3
|
|
|
3
|
|
34
|
use vars qw( %DRefs %Supress $CurrentDRef $CurrentDepth ); |
|
3
|
|
|
|
|
9
|
|
|
3
|
|
|
|
|
244
|
|
61
|
3
|
|
|
3
|
|
14
|
use vars qw( $Indent $ShowClasses $ShowDRefs $Multiline $MaxItems ); |
|
3
|
|
|
|
|
5
|
|
|
3
|
|
|
|
|
3570
|
|
62
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
# $string = astext($value_or_reference); |
64
|
|
|
|
|
|
|
# $string = astext($value_or_reference, %options); |
65
|
|
|
|
|
|
|
# Write out an object graph in NeXT property list format |
66
|
|
|
|
|
|
|
# Numerous variables are localized, then we recurse. |
67
|
|
|
|
|
|
|
sub astext { |
68
|
23
|
|
|
23
|
1
|
2565
|
my $target = shift; |
69
|
23
|
|
|
|
|
52
|
my %options = @_; |
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
# Options |
72
|
23
|
|
|
|
|
38
|
local $CurrentDRef = ''; |
73
|
23
|
|
|
|
|
33
|
local $CurrentDepth = 0; |
74
|
|
|
|
|
|
|
|
75
|
23
|
|
|
|
|
29
|
local $Indent = 2; |
76
|
|
|
|
|
|
|
|
77
|
23
|
50
|
|
|
|
62
|
local $ShowClasses = $options{'-classes'} if (exists $options{'-classes'} ); |
78
|
23
|
100
|
|
|
|
59
|
local $ShowDRefs = $options{'-drefs'} if (exists $options{'-drefs'} ); |
79
|
23
|
50
|
|
|
|
87
|
local $Multiline = $options{'-multiline'} if (exists $options{'-multiline'}); |
80
|
23
|
100
|
|
|
|
54
|
local $MaxItems = $options{'-maxitems'} if (exists $options{'-maxitems'}); |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
# Working scope for this invokation of astext. |
83
|
23
|
|
|
|
|
43
|
local %DRefs = (); |
84
|
23
|
|
|
|
|
33
|
local %Supress = (); |
85
|
|
|
|
|
|
|
|
86
|
23
|
|
|
|
|
50
|
_astext( $target ) |
87
|
|
|
|
|
|
|
} |
88
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
# $string = _astext($referenceorvalue); |
90
|
|
|
|
|
|
|
sub _astext { |
91
|
571
|
|
|
571
|
|
903
|
my $target = shift; |
92
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
# Write out an "UNDEFINED" comment to signal undefined values; |
94
|
571
|
50
|
|
|
|
1262
|
return '/* UNDEFINED */' if (not defined $target); |
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
# Write out escaped version of non-reference (string or number) values. |
97
|
571
|
100
|
|
|
|
1094
|
if ( ! ref($target) ) { |
98
|
557
|
50
|
33
|
|
|
1282
|
if ( $Multiline and $target =~ /\n.*?\n/ ) { |
99
|
0
|
0
|
|
|
|
0
|
return "<
|
100
|
|
|
|
|
|
|
$target . ($target =~ /\n\Z/ ?'':"\n") . |
101
|
|
|
|
|
|
|
" END_OF_TEXT_DELIMITER"; |
102
|
|
|
|
|
|
|
} else { |
103
|
557
|
|
|
|
|
1408
|
return qprintable( $target ); |
104
|
|
|
|
|
|
|
} |
105
|
|
|
|
|
|
|
} |
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
# If this is a reference an item written out elsewhere, write an XREF comment |
108
|
14
|
0
|
66
|
|
|
70
|
return '/* CROSS-REFERENCE TO ' . |
|
|
50
|
|
|
|
|
|
109
|
|
|
|
|
|
|
( length($DRefs{$target}) ? $DRefs{$target} : 'ROOT' ) .' */' |
110
|
|
|
|
|
|
|
if ( exists $DRefs{$target} and $Supress{$target} ); |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
# Store a relative DRef from the root to here, if we haven't already |
113
|
14
|
100
|
|
|
|
50
|
$DRefs{$target} = $CurrentDRef if ( not exists $DRefs{$target}); |
114
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
# We're going to show this item, so don't show it again in the future |
116
|
14
|
|
|
|
|
28
|
$Supress{$target} ++ ; |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
# Variable to hold the stringified form of $target. |
119
|
14
|
|
|
|
|
18
|
my $result = ''; |
120
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
# Write out DRef if $ShowDRefs is set |
122
|
14
|
100
|
100
|
|
|
48
|
$result .= "/* DREF $CurrentDRef */ " |
123
|
|
|
|
|
|
|
if ($ShowDRefs and length $CurrentDRef); |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
# Any DRefs after this point are separated by dots. |
126
|
14
|
100
|
|
|
|
36
|
local $CurrentDRef = $CurrentDRef . $Separator if ( length $CurrentDRef ); |
127
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
# Write out class of item if it's blessed and $ShowClasses is set |
129
|
14
|
0
|
33
|
|
|
34
|
$result .= "/* CLASS " . ref($target) . " */ " if ($ShowClasses and |
|
|
|
33
|
|
|
|
|
130
|
|
|
|
|
|
|
ref($target) and (ref($target) !~ /\A(ARRAY|HASH|SCALAR|REF|CODE)\Z/)); |
131
|
|
|
|
|
|
|
|
132
|
14
|
100
|
0
|
|
|
60
|
if ( UNIVERSAL::isa($target, 'HASH') ) { |
|
|
50
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
133
|
6
|
|
|
|
|
5
|
my $key; |
134
|
6
|
|
|
|
|
7
|
foreach $key (sort keys %{$target}) { |
|
6
|
|
|
|
|
25
|
|
135
|
12
|
|
|
|
|
17
|
my $value = $target->{$key}; |
136
|
12
|
50
|
|
|
|
29
|
next unless (ref $value); |
137
|
0
|
0
|
|
|
|
0
|
$DRefs{$value} = $CurrentDRef . $key unless ( exists $DRefs{$value} ); |
138
|
0
|
|
|
|
|
0
|
$Supress{$value} ++; |
139
|
|
|
|
|
|
|
} |
140
|
6
|
50
|
|
|
|
17
|
$result .= "{" if ($CurrentDepth); |
141
|
6
|
50
|
|
|
|
16
|
$result .= "\n" if ($result); |
142
|
6
|
|
|
|
|
7
|
$CurrentDepth ++; |
143
|
6
|
|
|
|
|
7
|
foreach $key (sort keys %{$target}) { |
|
6
|
|
|
|
|
15
|
|
144
|
12
|
|
|
|
|
75
|
$result .= ' ' x ( ($CurrentDepth - 1) * $Indent); |
145
|
12
|
|
|
|
|
19
|
local $CurrentDRef = $CurrentDRef . $key; |
146
|
12
|
50
|
|
|
|
23
|
$Supress{$target->{$key}} -- if ( ref $target->{$key} ); |
147
|
12
|
|
|
|
|
31
|
$result .= _astext($key) . ' = ' . _astext($target->{$key}) .";\n"; |
148
|
|
|
|
|
|
|
} |
149
|
6
|
|
|
|
|
62
|
$CurrentDepth --; |
150
|
6
|
50
|
|
|
|
20
|
$result .= ' 'x(($CurrentDepth-1) * $Indent) . "}" if ($CurrentDepth); |
151
|
6
|
|
|
|
|
25
|
return $result; |
152
|
|
|
|
|
|
|
} |
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
elsif ( UNIVERSAL::isa($target, 'ARRAY') ) { |
155
|
8
|
|
|
|
|
11
|
my $key; |
156
|
|
|
|
|
|
|
# If $MaxItems is set and there are fewer than that many non-ref items |
157
|
8
|
|
66
|
|
|
23
|
my $one_line = ( $MaxItems and $#{$target} <= $MaxItems ); |
158
|
8
|
|
|
|
|
13
|
foreach $key (0 .. $#{$target}) { |
|
8
|
|
|
|
|
75
|
|
159
|
524
|
|
|
|
|
631
|
my $value = $target->[$key]; |
160
|
524
|
100
|
|
|
|
1273
|
next unless (ref $value); |
161
|
6
|
|
|
|
|
7
|
$one_line = 0; |
162
|
6
|
50
|
|
|
|
22
|
$DRefs{$value} = $CurrentDRef . $key unless ( exists $DRefs{$value} ); |
163
|
6
|
|
|
|
|
13
|
$Supress{$value} ++; |
164
|
|
|
|
|
|
|
} |
165
|
8
|
100
|
|
|
|
24
|
my $joiner = ( $one_line ) ? " " : "\n"; |
166
|
8
|
50
|
|
|
|
21
|
$result .= "(" if ( $CurrentDepth ); |
167
|
8
|
50
|
|
|
|
20
|
$result .= $joiner if ( $result ); |
168
|
8
|
|
|
|
|
10
|
$CurrentDepth ++; |
169
|
8
|
|
|
|
|
11
|
foreach $key (0 .. $#{$target}) { |
|
8
|
|
|
|
|
19
|
|
170
|
524
|
100
|
|
|
|
9784
|
$result .= $one_line ? '' : ' ' x ( ($CurrentDepth - 1) * $Indent); |
171
|
524
|
|
|
|
|
904
|
local $CurrentDRef = $CurrentDRef . $key; |
172
|
524
|
100
|
|
|
|
1053
|
$Supress{$target->[$key]} -- if ( ref $target->[$key] ); |
173
|
524
|
|
|
|
|
956
|
$result .= _astext($target->[$key]) . "," . $joiner; |
174
|
|
|
|
|
|
|
} |
175
|
8
|
|
|
|
|
65
|
$CurrentDepth --; |
176
|
8
|
0
|
|
|
|
28
|
$result .= ( ! $one_line ? ' 'x(($CurrentDepth-1) * $Indent) : '' ) . ")" |
|
|
50
|
|
|
|
|
|
177
|
|
|
|
|
|
|
if ( $CurrentDepth ); |
178
|
8
|
|
|
|
|
404
|
return $result; |
179
|
|
|
|
|
|
|
} |
180
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
elsif (UNIVERSAL::isa($target, 'REF') or UNIVERSAL::isa($target, 'SCALAR')) { |
182
|
0
|
|
|
|
|
0
|
$result .= '/* REFERENCE */ '; |
183
|
0
|
|
|
|
|
0
|
local $CurrentDepth = $CurrentDepth + 1; |
184
|
0
|
|
|
|
|
0
|
local $CurrentDRef = $CurrentDRef . 0; |
185
|
0
|
|
|
|
|
0
|
$result .= _astext($$target); |
186
|
0
|
|
|
|
|
0
|
return $result; |
187
|
|
|
|
|
|
|
} |
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
# Otherwise it's some unsupported kind of reference; just "" stringify it |
190
|
0
|
|
|
|
|
0
|
return "/* REFERENCE TO $target */"; |
191
|
|
|
|
|
|
|
} |
192
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
### Reader |
194
|
|
|
|
|
|
|
|
195
|
3
|
|
|
3
|
|
88
|
use vars qw( @TextLines $LineNumber $Source ); |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
3799
|
|
196
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
# $datastructure = fromtext($string); |
198
|
|
|
|
|
|
|
# $datastructure = fromtext($string, %options); |
199
|
|
|
|
|
|
|
# reconstruct an object graph from a NeXT property list. |
200
|
|
|
|
|
|
|
sub fromtext ($%) { |
201
|
16
|
|
|
16
|
1
|
1107
|
my $dictionary_text = shift; |
202
|
16
|
|
|
|
|
44
|
my %options = @_; |
203
|
|
|
|
|
|
|
|
204
|
16
|
|
|
|
|
60
|
local @TextLines = split("\n", $dictionary_text); |
205
|
16
|
|
|
|
|
23
|
local $LineNumber = 0; |
206
|
16
|
|
100
|
|
|
72
|
local $Source = $options{'-source'} || ''; |
207
|
|
|
|
|
|
|
|
208
|
16
|
100
|
|
|
|
54
|
if ( $options{'-scalar'} ) { |
|
|
50
|
|
|
|
|
|
209
|
15
|
|
|
|
|
25
|
return _parse_value( _get_line() . "\000", "\000" ); |
210
|
|
|
|
|
|
|
} elsif ( $options{'-array'} ) { |
211
|
1
|
|
|
|
|
5
|
return _parse_array(); |
212
|
|
|
|
|
|
|
} else { |
213
|
0
|
|
|
|
|
0
|
return _parse_hash(); |
214
|
|
|
|
|
|
|
} |
215
|
|
|
|
|
|
|
} |
216
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
# _parse_error( $message ); |
218
|
|
|
|
|
|
|
sub _parse_error { |
219
|
0
|
|
|
0
|
|
0
|
my $message = shift; |
220
|
0
|
0
|
|
|
|
0
|
warn 'PropertyList error, ' . $message . |
221
|
|
|
|
|
|
|
' at line ' . $LineNumber . ( $Source ? ' in ' . $Source : '' ) ."\n"; |
222
|
|
|
|
|
|
|
} |
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
# $text = _get_line; |
225
|
|
|
|
|
|
|
sub _get_line { |
226
|
27
|
|
|
27
|
|
29
|
$LineNumber++; |
227
|
27
|
|
|
|
|
70
|
shift(@TextLines); |
228
|
|
|
|
|
|
|
} |
229
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
# $hash_ref = _parse_hash(); |
231
|
|
|
|
|
|
|
sub _parse_hash { |
232
|
3
|
|
|
3
|
|
5
|
my $hash = {}; |
233
|
3
|
|
|
|
|
3
|
my ($key, $value, $current_line); |
234
|
|
|
|
|
|
|
|
235
|
3
|
|
|
|
|
8
|
while (@TextLines) { |
236
|
9
|
|
|
|
|
14
|
$current_line = _get_line(); |
237
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
# Ignore comments |
239
|
9
|
|
|
|
|
13
|
$current_line =~ s#\Q/*\E.*?\Q*/\E##g; |
240
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
# Ignore blank lines |
242
|
9
|
50
|
|
|
|
23
|
next if ( $current_line =~ /^\s*$/ ); |
243
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
# If we hit a closing brace, we're done with this hash |
245
|
9
|
100
|
|
|
|
23
|
last if ( $current_line =~ /^\s*\}[,;]/o ); |
246
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
# Extract key and equals sign. |
248
|
6
|
50
|
|
|
|
36
|
if ( $current_line =~ s/^\s*\"(([^\"\\]|\\.)+)\"//o ) { |
|
|
50
|
|
|
|
|
|
249
|
0
|
|
|
|
|
0
|
$key = unprintable( $1 ); |
250
|
|
|
|
|
|
|
} elsif ( $current_line =~ s/^\s*(\S+)//o ) { |
251
|
6
|
|
|
|
|
14
|
$key = unprintable( $1 ); |
252
|
|
|
|
|
|
|
} else { |
253
|
0
|
|
|
|
|
0
|
_parse_error("Key not found"); |
254
|
0
|
|
|
|
|
0
|
last; |
255
|
|
|
|
|
|
|
} |
256
|
|
|
|
|
|
|
|
257
|
6
|
50
|
|
|
|
54
|
$current_line =~ s/^\s*=\s*//o or _parse_error("= not found"); |
258
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
# Extract value |
260
|
6
|
|
|
|
|
22
|
$value = _parse_value( $current_line, ';' ); |
261
|
|
|
|
|
|
|
|
262
|
6
|
50
|
|
|
|
77
|
next unless (defined $key); |
263
|
|
|
|
|
|
|
|
264
|
6
|
|
|
|
|
18
|
$hash->{$key} = $value; |
265
|
|
|
|
|
|
|
} |
266
|
|
|
|
|
|
|
|
267
|
3
|
|
|
|
|
8
|
return $hash; |
268
|
|
|
|
|
|
|
} |
269
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
# $array_ref = _parse_array(); |
271
|
|
|
|
|
|
|
sub _parse_array { |
272
|
1
|
|
|
1
|
|
18
|
my $array = []; |
273
|
1
|
|
|
|
|
1
|
my ($value, $current_line); |
274
|
|
|
|
|
|
|
|
275
|
1
|
|
|
|
|
4
|
while (@TextLines) { |
276
|
3
|
|
|
|
|
7
|
$current_line = _get_line(); |
277
|
|
|
|
|
|
|
|
278
|
|
|
|
|
|
|
# Ignore comments |
279
|
3
|
|
|
|
|
15
|
$current_line =~ s#\Q/*\E.*?\Q*/\E##g; |
280
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
# Ignore blank lines |
282
|
3
|
50
|
|
|
|
12
|
next if ( $current_line =~ /^\s*$/ ); |
283
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
# If we hit a closing paren, we're done with this hash |
285
|
3
|
50
|
|
|
|
8
|
last if ( $current_line =~ /^\s*\)[,;]/o ); |
286
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
# Extract value |
288
|
3
|
|
|
|
|
7
|
$value = _parse_value( $current_line, ',' ); |
289
|
|
|
|
|
|
|
|
290
|
3
|
|
|
|
|
10
|
push( @$array, $value); |
291
|
|
|
|
|
|
|
|
292
|
3
|
|
|
|
|
7
|
next; |
293
|
|
|
|
|
|
|
} |
294
|
|
|
|
|
|
|
|
295
|
1
|
|
|
|
|
5
|
return $array; |
296
|
|
|
|
|
|
|
} |
297
|
|
|
|
|
|
|
|
298
|
|
|
|
|
|
|
# $string = _parse_multiline($ender); |
299
|
|
|
|
|
|
|
sub _parse_multiline { |
300
|
0
|
|
|
0
|
|
0
|
my $ender = shift; |
301
|
|
|
|
|
|
|
|
302
|
0
|
|
|
|
|
0
|
my $value = ''; |
303
|
0
|
|
|
|
|
0
|
my $current_line; |
304
|
|
|
|
|
|
|
|
305
|
0
|
|
|
|
|
0
|
while (@TextLines) { |
306
|
0
|
|
|
|
|
0
|
$current_line = _get_line(); |
307
|
0
|
0
|
|
|
|
0
|
last if ($current_line =~ /^\s*\Q$ender\E[\;\,]?\s*$/); |
308
|
0
|
|
|
|
|
0
|
$value .= $current_line . "\n"; |
309
|
|
|
|
|
|
|
} |
310
|
0
|
|
|
|
|
0
|
return $value; |
311
|
|
|
|
|
|
|
} |
312
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
# $value = _parse_value( $value, $terminator ); |
314
|
|
|
|
|
|
|
# Extracts a quoted or unquoted string, an array, hash, or a multiline string |
315
|
|
|
|
|
|
|
sub _parse_value { |
316
|
24
|
|
|
24
|
|
32
|
my $current_line = shift; |
317
|
24
|
|
|
|
|
29
|
my $end_value = shift; |
318
|
|
|
|
|
|
|
|
319
|
24
|
100
|
|
|
|
514
|
if ( $current_line =~ /^\s*\"(([^\"\\]|\\.)*)\"\Q$end_value\E\s*/ ) { |
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
320
|
|
|
|
|
|
|
# Extract quoted value |
321
|
10
|
|
|
|
|
29
|
return unprintable( $1 ); |
322
|
|
|
|
|
|
|
} elsif ( $current_line =~ /^\s*(\S+?)\Q$end_value\E\s*/ ) { |
323
|
|
|
|
|
|
|
# Extract unquoted value |
324
|
11
|
|
|
|
|
30
|
return unprintable( $1 ); |
325
|
|
|
|
|
|
|
} elsif ( $current_line =~ /^\s*(\/\*.*?\*\/)\s*\Q$end_value\E\s*/ ) { |
326
|
|
|
|
|
|
|
# Extract comment |
327
|
0
|
|
|
|
|
0
|
return undef; |
328
|
|
|
|
|
|
|
} elsif ( $current_line =~ /^\s*\{/o ) { |
329
|
3
|
|
|
|
|
7
|
return _parse_hash(); |
330
|
|
|
|
|
|
|
} elsif ( $current_line =~ /^\s*\(/o ) { |
331
|
0
|
|
|
|
|
|
return _parse_array(); |
332
|
|
|
|
|
|
|
} elsif ( $current_line =~ /^\s*\<\<(\w+)(?:\Q$end_value\E)?/o ) { |
333
|
0
|
|
|
|
|
|
return _parse_multiline($1); |
334
|
|
|
|
|
|
|
} else { |
335
|
0
|
|
|
|
|
|
_parse_error("value not found in '$current_line' - $end_value"); |
336
|
|
|
|
|
|
|
} |
337
|
|
|
|
|
|
|
} |
338
|
|
|
|
|
|
|
|
339
|
|
|
|
|
|
|
1; |
340
|
|
|
|
|
|
|
|
341
|
|
|
|
|
|
|
__END__ |