File Coverage

blib/lib/Mac/PropertyList/SAX.pm
Criterion Covered Total %
statement 124 174 71.2
branch 10 46 21.7
condition 2 16 12.5
subroutine 44 51 86.2
pod 4 4 100.0
total 184 291 63.2


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Mac::PropertyList::SAX - work with Mac plists at a low level, fast
4              
5             =cut
6              
7             package Mac::PropertyList::SAX;
8              
9             =head1 SYNOPSIS
10              
11             See L
12              
13             =head1 DESCRIPTION
14              
15             L is useful, but very slow on large files
16             because it does XML parsing itself, intead of handing it off to a dedicated
17             parser. This module uses L to
18             select a parser capable of doing the heavy lifting, reducing parsing time on
19             large files by a factor of 30 or more.
20              
21             This module does not replace L: it depends
22             on it for some package definitions and plist printing routines. You should,
23             however, be able to replace all C
24             lines with C, without changing anything else, and
25             notice an immediate improvement in performance on large input files.
26              
27             Performance will depend largely on the parser that
28             L selects for you. By default,
29             L is used; to change the parser used, set the
30             environment variable C to a value accepted by
31             $XML::SAX::ParserPackage from
32             L (or set
33             C<$XML::SAX::ParserPackage> directly).
34              
35             =cut
36              
37 19     19   327538 use strict;
  19         44  
  19         672  
38 19     19   93 use warnings;
  19         31  
  19         568  
39              
40 19     19   93 use Carp qw(carp);
  19         33  
  19         1952  
41 19     19   16318 use HTML::Entities qw(encode_entities_numeric);
  19         116945  
  19         1814  
42 19     19   16324 use HTML::Entities::Numbered qw(hex2name name2hex_xml);
  19         72249  
  19         1337  
43             # Passthrough function
44 19     19   16133 use Mac::PropertyList qw(plist_as_string);
  19         688355  
  19         1854  
45 19     19   16593 use XML::SAX::ParserFactory;
  19         116795  
  19         718  
46              
47 19     19   151 use base qw(Exporter);
  19         38  
  19         16625  
48              
49             our @EXPORT_OK = qw(
50             parse_plist
51             parse_plist_fh
52             parse_plist_file
53             parse_plist_string
54             plist_as_string
55             create_from_ref
56             create_from_hash
57             create_from_array
58             );
59              
60             our %EXPORT_TAGS = (
61             all => \@EXPORT_OK,
62             create => [ qw(create_from_ref create_from_hash create_from_array plist_as_string) ],
63             parse => [ qw(parse_plist parse_plist_fh parse_plist_file parse_plist_string) ],
64             );
65              
66             our $VERSION = '0.85';
67              
68              
69              
70             =head1 CLASS VARIABLES
71              
72             Class scoped variables that control the packages settings.
73              
74             =over 4
75              
76             =item ENCODE_ENTITIES
77              
78             Allows the XHTML encoding of the data to be turned off. Default = C<1>
79              
80             =item ENCODE_UNSAFE_CHARS
81              
82             A Perl character class definition containing the only characters to be
83             XHTML encoded. See HTML::Entities::encode_entities for description of
84             the $unsafe_chars parameter. Default = C
85              
86             =cut
87              
88             our $ENCODE_ENTITIES = 1;
89             our $ENCODE_UNSAFE_CHARS = undef;
90              
91             =item OLD_BEHAVIOR
92              
93             Restores the old behavior of double encoding output data. Default = C<0>
94              
95             =cut
96              
97             our $OLD_BEHAVIOR = 0;
98              
99             =item XML::SAX::ParserPackage
100              
101             Parser to use. Can also be set with environment variable
102             C. Default = C<"XML::SAX::Expat">
103              
104             =cut
105              
106             $XML::SAX::ParserPackage = $ENV{MAC_PROPERTYLIST_SAX_PARSER} || "XML::SAX::Expat";
107              
108             =back
109              
110             =head1 EXPORTS
111              
112             By default, no functions are exported. Specify individual functions to export
113             as usual, or use the tags ':all', ':create', and ':parse' for the appropriate
114             sets of functions (':create' includes the create* functions as well as
115             plist_as_string; ':parse' includes the parse* functions).
116              
117             =head1 FUNCTIONS
118              
119             =over 4
120              
121             =item parse_plist_file
122              
123             See L
124              
125             =cut
126              
127             sub parse_plist_file {
128 1     1 1 974 my $file = shift;
129              
130 1 50       4 if (ref $file) {
131 1         4 parse_plist_fh($file);
132             } else {
133 0 0       0 carp("parse_plist_file: file [$file] does not exist!"), return unless -e $file;
134 0         0 _parse("parse_uri", $file);
135             }
136             }
137              
138             =item parse_plist_fh
139              
140             See L
141              
142             =cut
143              
144 2     2 1 1010 sub parse_plist_fh { _parse("parse_file", @_) }
145              
146             =item parse_plist
147              
148             See L
149              
150             =cut
151              
152 16     16 1 13370 sub parse_plist { _parse("parse_string", @_) }
153              
154             =item parse_plist_string
155              
156             An alias to parse_plist, provided for better regularity compared to Perl SAX.
157              
158             =cut
159              
160             *parse_plist_string = \&parse_plist;
161              
162             sub _parse {
163 18     18   47 my ($sub, $data) = @_;
164              
165 18         192 my $handler = Mac::PropertyList::SAX::Handler->new;
166 18         1534 XML::SAX::ParserFactory->parser(Handler => $handler)->$sub($data);
167              
168 0         0 $handler->{struct}
169             }
170              
171             =item create_from_ref( HASH_REF | ARRAY_REF )
172              
173             Create a plist from an array or hash reference.
174              
175             The values of the hash can be simple scalars or references. Hash and array
176             references are handled recursively, and L objects are output
177             correctly. All other scalars are treated as strings (use L
178             objects to represent other types of scalars).
179              
180             Returns a string representing the reference in serialized plist format.
181              
182             =cut
183              
184             sub create_from_ref {
185             sub _handle_value {
186 14     14   154 my ($val) = @_;
187              
188             sub _handle_hash {
189 3     3   3 my ($hash) = @_;
190 38         132 Mac::PropertyList::SAX::dict->write_open,
191 7 50       162 (map { "\t$_" } map {
192 3         24 Mac::PropertyList::SAX::dict->write_key($OLD_BEHAVIOR ? _escape($_) : $_),
193             _handle_value($hash->{$_}) } keys %$hash),
194             Mac::PropertyList::SAX::dict->write_close
195             }
196              
197             sub _handle_array {
198 2     2   5 my ($array) = @_;
199 8         71 Mac::PropertyList::SAX::array->write_open,
200 2         22 (map { "\t$_" } map { _handle_value($_) } @$array),
  5         108  
201             Mac::PropertyList::SAX::array->write_close
202             }
203              
204             # We could hand off serialization of all Mac::PropertyList::Item objects
205             # but there is no 'write' method defined for it (though all its
206             # subclasses have one). Let's just handle Scalars, which are safe.
207 14 100       108 if (UNIVERSAL::can($val, 'write')) { $val->write }
  1 100       5  
    100          
208 3         9 elsif (UNIVERSAL::isa($val, 'HASH')) { _handle_hash ($val) }
209 2         5 elsif (UNIVERSAL::isa($val, 'ARRAY')) { _handle_array($val) }
210 8 50       56 else { Mac::PropertyList::SAX::string->new($OLD_BEHAVIOR ? _escape($val) : $val)->write }
211             }
212              
213 2     2 1 174 $Mac::PropertyList::XML_head .
214             (join "\n", _handle_value(shift)) . "\n" .
215             $Mac::PropertyList::XML_foot;
216             }
217              
218             =item create_from_hash( HASH_REF )
219              
220             Provided for backward compatibility with L: aliases
221             create_from_ref.
222              
223             =cut
224              
225             *create_from_hash = \&create_from_ref;
226              
227             =item create_from_array( ARRAY_REF )
228              
229             Provided for backward compatibility with L: aliases
230             create_from_ref.
231              
232             =cut
233              
234             *create_from_array = \&create_from_ref;
235              
236             =item _escape( STRING )
237              
238             B Escapes illegal characters into XML entities.
239              
240             =cut
241              
242             sub _escape {
243 15     15   543 my $string = join("\n",grep(defined,@_));
244 15 50       56 $ENCODE_ENTITIES &&
245             return name2hex_xml(hex2name(encode_entities_numeric($string,
246             $ENCODE_UNSAFE_CHARS)));
247 0         0 return $string;
248             }
249              
250             package Mac::PropertyList::SAX::Handler;
251              
252 19     19   134 use strict;
  19         46  
  19         685  
253 19     19   273 use warnings;
  19         45  
  19         923  
254             # State definitions
255 19     19   15295 use enum qw(S_EMPTY S_TOP S_FREE S_DICT S_ARRAY S_KEY S_TEXT);
  19         21803  
  19         102  
256              
257 19     19   9156 use Carp qw(carp croak);
  19         34  
  19         880  
258 19     19   15301 use MIME::Base64;
  19         14042  
  19         1379  
259              
260             # Element-name definitions
261 19         1847 use constant +{ qw( ROOT plist
262             KEY key
263             DATA data
264             DICT dict
265 19     19   114 ARRAY array ) };
  19         32  
266              
267 19     19   99 use base qw(XML::SAX::Base);
  19         49  
  19         25680  
268              
269             # From the plist DTD
270             our (%types, %simple_types, %complex_types, %numerical_types);
271             {
272             my @complex_types = (DICT, ARRAY);
273             my @numerical_types = qw(real integer true false);
274             my @simple_types = qw(data date real integer string true false);
275             my @types = (@complex_types, @numerical_types, @simple_types);
276              
277             my $atoh = sub { map { $_ => 1 } @_ };
278              
279             %types = $atoh->(@ types);
280             %simple_types = $atoh->(@ simple_types);
281             %complex_types = $atoh->(@ complex_types);
282             %numerical_types = $atoh->(@numerical_types);
283             }
284              
285             sub new {
286 18     18   125 my %args = (
287             accum => "",
288             context => S_EMPTY,
289             key => undef,
290             stack => [ ],
291             struct => undef,
292             );
293              
294 18         281 shift->SUPER::new(%args, @_)
295             }
296              
297             sub start_element {
298 0     0   0 my $self = shift;
299 0         0 my ($data) = @_;
300 0         0 my $name = $data->{Name};
301              
302             # State transition definitions
303 0 0 0     0 if ($self->{context} == S_EMPTY and $name eq ROOT) {
    0 0        
      0        
304 0         0 $self->{context} = S_TOP;
305             } elsif ($self->{context} == S_TOP or $types{$name} or $name eq KEY) {
306 0         0 push @{ $self->{stack} }, {
  0         0  
307             key => $self->{key},
308             context => $self->{context},
309             struct => $self->{struct},
310             };
311              
312 0 0       0 if ($complex_types{$name}) {
    0          
    0          
313 0         0 $self->{struct} = "Mac::PropertyList::SAX::$name"->new;
314 0         0 $self->{context} = eval "S_" . uc $name;
315 0         0 delete $self->{key};
316             }
317 0         0 elsif ($simple_types{$name}) { $self->{context} = S_TEXT }
318             elsif ($name eq KEY) {
319 0 0       0 croak " in improper context $self->{context}" unless $self->{context} == S_DICT;
320 0         0 $self->{context} = S_KEY;
321             }
322 0         0 else { croak "Top-level element '$name' in plist is not recognized" }
323             } else {
324 0         0 croak "Received invalid start element '$name'";
325             }
326             }
327              
328             sub end_element {
329 0     0   0 my $self = shift;
330 0         0 my ($data) = @_;
331 0         0 my $name = $data->{Name};
332              
333 0 0       0 if ($name ne ROOT) { # Discard plist element
334 0         0 my $elt = pop @{ $self->{stack} };
  0         0  
335              
336 0         0 my $value = $self->{struct};
337 0         0 ($self->{struct}, $self->{key}, $self->{context}) = @{$elt}{qw(struct key context)};
  0         0  
338              
339 0 0       0 if ($simple_types{$name}) {
    0          
340             # Wrap accumulated character data in an object
341 0 0       0 $value = "Mac::PropertyList::SAX::$name"->new(
    0          
342             exists $self->{accum}
343             ? $name eq DATA
344             ? MIME::Base64::decode_base64($self->{accum})
345             : $self->{accum}
346             : ""
347             );
348              
349 0         0 delete $self->{accum};
350             } elsif ($name eq KEY) {
351 0         0 $self->{key} = $self->{accum};
352 0         0 delete $self->{accum};
353 0         0 return;
354             }
355              
356 0 0       0 if ($self->{context} == S_DICT ) { $self->{struct}{$self->{key}} = $value }
  0 0       0  
    0          
357 0         0 elsif ($self->{context} == S_ARRAY) { push @{ $self->{struct} }, $value }
  0         0  
358 0         0 elsif ($self->{context} == S_TOP ) { $self->{struct} = $value }
359 0         0 else { croak "Bad context $self->{context}" }
360             }
361             }
362              
363             sub characters {
364 0     0   0 my $self = shift;
365 0         0 my ($data) = @_;
366 0 0 0     0 $self->{accum} .= $data->{Data} if $self->{context} == S_TEXT or $self->{context} == S_KEY;
367             }
368              
369             # Convenient subclasses
370             package Mac::PropertyList::SAX::array;
371 19     19   386785 use base qw(Mac::PropertyList::array);
  19         48  
  19         11739  
372             package Mac::PropertyList::SAX::dict;
373 19     19   106 use base qw(Mac::PropertyList::dict);
  19         37  
  19         9706  
374 7   50 7   14 sub write_key { "" . (Mac::PropertyList::SAX::_escape($_[1]) || '') . "" }
375             package Mac::PropertyList::SAX::Scalar;
376 19     19   105 use base qw(Mac::PropertyList::Scalar);
  19         49  
  19         10169  
377             sub write {
378 8   50 8   28 $_[0]->write_open .
379             (Mac::PropertyList::SAX::_escape($_[0]->value) || '') .
380             $_[0]->write_close
381             }
382 19     19   115 use overload '""' => sub { $_[0]->as_basic_data };
  19     0   36  
  19         288  
  0         0  
383             package Mac::PropertyList::SAX::date;
384 19     19   1366 use base qw(Mac::PropertyList::date Mac::PropertyList::SAX::Scalar);
  19         135  
  19         17068  
385             package Mac::PropertyList::SAX::real;
386 19     19   102 use base qw(Mac::PropertyList::real Mac::PropertyList::SAX::Scalar);
  19         40  
  19         16368  
387             package Mac::PropertyList::SAX::integer;
388 19     19   112 use base qw(Mac::PropertyList::integer Mac::PropertyList::SAX::Scalar);
  19         34  
  19         17666  
389             package Mac::PropertyList::SAX::string;
390 19     19   161 use base qw(Mac::PropertyList::string Mac::PropertyList::SAX::Scalar);
  19         35  
  19         17510  
391 8     8   90 sub write { $_[0]->Mac::PropertyList::SAX::Scalar::write }
392 19     19   111 use overload '""' => sub { $_[0]->as_basic_data };
  19     0   70  
  19         208  
  0         0  
393             package Mac::PropertyList::SAX::data;
394 19     19   1288 use base qw(Mac::PropertyList::data Mac::PropertyList::SAX::Scalar);
  19         43  
  19         16913  
395             package Mac::PropertyList::SAX::Boolean;
396 19     19   17906 use Object::MultiType;
  19         48722  
  19         1339  
397 19     19   346 use base qw(Mac::PropertyList::Boolean Object::MultiType);
  19         35  
  19         12630  
398 19     19   110 use overload '""' => sub { shift->value };
  19     0   42  
  19         143  
  0         0  
399             sub new {
400 2     2   88 my $class = shift;
401 2         18 my ($type) = $class =~ /::([^:]+)$/;
402 2         6 my $b = lc $type eq "true";
403 2         21 bless Object::MultiType->new(scalar => $type, bool => $b) => $class
404             }
405 0     0     sub value { ${${$_[0]}->scalar} }
  0            
  0            
406             package Mac::PropertyList::SAX::true;
407 19     19   3344 use base qw(Mac::PropertyList::SAX::Boolean Mac::PropertyList::true);
  19         54  
  19         17847  
408             package Mac::PropertyList::SAX::false;
409 19     19   115 use base qw(Mac::PropertyList::SAX::Boolean Mac::PropertyList::true);
  19         35  
  19         17189  
410              
411             1;
412              
413             __END__