Filename | /usr/share/perl5/XML/Simple.pm |
Statements | Executed 10456 statements in 16.3ms |
Calls | P | F | Exclusive Time |
Inclusive Time |
Subroutine |
---|---|---|---|---|---|
142 | 2 | 1 | 3.08ms | 3.82ms | collapse (recurses: max depth 3, inclusive time 4.49ms) | XML::Simple::
2 | 1 | 1 | 2.24ms | 55.6ms | build_tree | XML::Simple::
1 | 1 | 1 | 1.45ms | 1.45ms | BEGIN@1712 | XML::Simple::
478 | 2 | 1 | 1.05ms | 1.05ms | characters | XML::Simple::
142 | 2 | 1 | 620µs | 620µs | start_element | XML::Simple::
14 | 1 | 1 | 200µs | 241µs | array_to_hash | XML::Simple::
278 | 4 | 1 | 200µs | 200µs | CORE:match (opcode) | XML::Simple::
2 | 1 | 1 | 112µs | 114µs | handle_options | XML::Simple::
142 | 2 | 1 | 112µs | 112µs | end_element | XML::Simple::
2 | 1 | 1 | 80µs | 108µs | new | XML::Simple::
2 | 1 | 1 | 47µs | 59.4ms | build_simple_tree | XML::Simple::
2 | 1 | 1 | 35µs | 59.6ms | parse_file | XML::Simple::
2 | 1 | 1 | 33µs | 59.8ms | XMLin | XML::Simple::
26 | 2 | 1 | 32µs | 32µs | new_hashref | XML::Simple::
4 | 2 | 1 | 28µs | 145µs | _get_object | XML::Simple::
2 | 1 | 1 | 25µs | 25µs | _strict_mode_for_caller | XML::Simple::
2 | 1 | 1 | 23µs | 61µs | find_xml_file | XML::Simple::
1 | 1 | 1 | 18µs | 20µs | import | XML::Simple::
2 | 1 | 1 | 11µs | 11µs | start_document | XML::Simple::
1 | 1 | 1 | 10µs | 60µs | BEGIN@41 | XML::Simple::
1 | 1 | 1 | 9µs | 9µs | BEGIN@2 | XML::Simple::
2 | 1 | 1 | 9µs | 9µs | CORE:ftis (opcode) | XML::Simple::
1 | 1 | 1 | 8µs | 74µs | BEGIN@49 | XML::Simple::
1 | 1 | 1 | 8µs | 24µs | BEGIN@40 | XML::Simple::
2 | 1 | 1 | 8µs | 8µs | end_document | XML::Simple::
8 | 2 | 1 | 5µs | 5µs | CORE:subst (opcode) | XML::Simple::
0 | 0 | 0 | 0s | 0s | XMLout | XML::Simple::
0 | 0 | 0 | 0s | 0s | build_tree_xml_parser | XML::Simple::
0 | 0 | 0 | 0s | 0s | cache_read_memcopy | XML::Simple::
0 | 0 | 0 | 0s | 0s | cache_read_memshare | XML::Simple::
0 | 0 | 0 | 0s | 0s | cache_read_storable | XML::Simple::
0 | 0 | 0 | 0s | 0s | cache_write_memcopy | XML::Simple::
0 | 0 | 0 | 0s | 0s | cache_write_memshare | XML::Simple::
0 | 0 | 0 | 0s | 0s | cache_write_storable | XML::Simple::
0 | 0 | 0 | 0s | 0s | collapse_content | XML::Simple::
0 | 0 | 0 | 0s | 0s | copy_hash | XML::Simple::
0 | 0 | 0 | 0s | 0s | default_config_file | XML::Simple::
0 | 0 | 0 | 0s | 0s | die_or_warn | XML::Simple::
0 | 0 | 0 | 0s | 0s | escape_value | XML::Simple::
0 | 0 | 0 | 0s | 0s | get_var | XML::Simple::
0 | 0 | 0 | 0s | 0s | hash_to_array | XML::Simple::
0 | 0 | 0 | 0s | 0s | normalise_space | XML::Simple::
0 | 0 | 0 | 0s | 0s | numeric_escape | XML::Simple::
0 | 0 | 0 | 0s | 0s | parse_fh | XML::Simple::
0 | 0 | 0 | 0s | 0s | parse_string | XML::Simple::
0 | 0 | 0 | 0s | 0s | set_var | XML::Simple::
0 | 0 | 0 | 0s | 0s | sorted_keys | XML::Simple::
0 | 0 | 0 | 0s | 0s | storable_filename | XML::Simple::
0 | 0 | 0 | 0s | 0s | value_to_xml | XML::Simple::
Line | State ments |
Time on line |
Calls | Time in subs |
Code |
---|---|---|---|---|---|
1 | package XML::Simple; | ||||
2 | # spent 9µs within XML::Simple::BEGIN@2 which was called:
# once (9µs+0s) by C4::Context::BEGIN@102 at line 4 | ||||
3 | 1 | 6µs | $XML::Simple::VERSION = '2.20'; | ||
4 | 1 | 40µs | 1 | 9µs | } # spent 9µs making 1 call to XML::Simple::BEGIN@2 |
5 | |||||
6 | =head1 NAME | ||||
7 | |||||
8 | XML::Simple - Easily read/write XML (esp config files) | ||||
9 | |||||
10 | =head1 SYNOPSIS | ||||
11 | |||||
12 | use XML::Simple qw(:strict); | ||||
13 | |||||
14 | my $ref = XMLin([<xml file or string>] [, <options>]); | ||||
15 | |||||
16 | my $xml = XMLout($hashref [, <options>]); | ||||
17 | |||||
18 | Or the object oriented way: | ||||
19 | |||||
20 | require XML::Simple qw(:strict); | ||||
21 | |||||
22 | my $xs = XML::Simple->new([<options>]); | ||||
23 | |||||
24 | my $ref = $xs->XMLin([<xml file or string>] [, <options>]); | ||||
25 | |||||
26 | my $xml = $xs->XMLout($hashref [, <options>]); | ||||
27 | |||||
28 | (or see L<"SAX SUPPORT"> for 'the SAX way'). | ||||
29 | |||||
30 | Note, in these examples, the square brackets are used to denote optional items | ||||
31 | not to imply items should be supplied in arrayrefs. | ||||
32 | |||||
33 | =cut | ||||
34 | |||||
35 | # See after __END__ for more POD documentation | ||||
36 | |||||
37 | |||||
38 | # Load essentials here, other modules loaded on demand later | ||||
39 | |||||
40 | 2 | 27µs | 2 | 41µs | # spent 24µs (8+17) within XML::Simple::BEGIN@40 which was called:
# once (8µs+17µs) by C4::Context::BEGIN@102 at line 40 # spent 24µs making 1 call to XML::Simple::BEGIN@40
# spent 17µs making 1 call to strict::import |
41 | 2 | 41µs | 2 | 111µs | # spent 60µs (10+51) within XML::Simple::BEGIN@41 which was called:
# once (10µs+51µs) by C4::Context::BEGIN@102 at line 41 # spent 60µs making 1 call to XML::Simple::BEGIN@41
# spent 51µs making 1 call to Exporter::import |
42 | 1 | 1µs | require Exporter; | ||
43 | |||||
44 | |||||
45 | ############################################################################## | ||||
46 | # Define some constants | ||||
47 | # | ||||
48 | |||||
49 | 2 | 5.76ms | 2 | 139µs | # spent 74µs (8+66) within XML::Simple::BEGIN@49 which was called:
# once (8µs+66µs) by C4::Context::BEGIN@102 at line 49 # spent 74µs making 1 call to XML::Simple::BEGIN@49
# spent 66µs making 1 call to vars::import |
50 | |||||
51 | 1 | 7µs | @ISA = qw(Exporter); | ||
52 | 1 | 700ns | @EXPORT = qw(XMLin XMLout); | ||
53 | 1 | 300ns | @EXPORT_OK = qw(xml_in xml_out); | ||
54 | 1 | 300ns | $PREFERRED_PARSER = undef; | ||
55 | |||||
56 | 1 | 600ns | my %StrictMode = (); | ||
57 | |||||
58 | 1 | 2µs | my @KnownOptIn = qw(keyattr keeproot forcecontent contentkey noattr | ||
59 | searchpath forcearray cache suppressempty parseropts | ||||
60 | grouptags nsexpand datahandler varattr variables | ||||
61 | normalisespace normalizespace valueattr strictmode); | ||||
62 | |||||
63 | 1 | 2µs | my @KnownOptOut = qw(keyattr keeproot contentkey noattr | ||
64 | rootname xmldecl outputfile noescape suppressempty | ||||
65 | grouptags nsexpand handler noindent attrindent nosort | ||||
66 | valueattr numericescape strictmode); | ||||
67 | |||||
68 | 1 | 500ns | my @DefKeyAttr = qw(name key id); | ||
69 | 1 | 300ns | my $DefRootName = qq(opt); | ||
70 | 1 | 100ns | my $DefContentKey = qq(content); | ||
71 | 1 | 100ns | my $DefXmlDecl = qq(<?xml version='1.0' standalone='yes'?>); | ||
72 | |||||
73 | 1 | 100ns | my $xmlns_ns = 'http://www.w3.org/2000/xmlns/'; | ||
74 | 1 | 1µs | my $bad_def_ns_jcn = '{' . $xmlns_ns . '}'; # LibXML::SAX workaround | ||
75 | |||||
76 | |||||
77 | ############################################################################## | ||||
78 | # Globals for use by caching routines | ||||
79 | # | ||||
80 | |||||
81 | 1 | 200ns | my %MemShareCache = (); | ||
82 | 1 | 100ns | my %MemCopyCache = (); | ||
83 | |||||
84 | |||||
85 | ############################################################################## | ||||
86 | # Wrapper for Exporter - handles ':strict' | ||||
87 | # | ||||
88 | |||||
89 | # spent 20µs (18+2) within XML::Simple::import which was called:
# once (18µs+2µs) by C4::Context::BEGIN@102 at line 102 of C4/Context.pm | ||||
90 | # Handle the :strict tag | ||||
91 | |||||
92 | 1 | 2µs | my($calling_package) = caller(); | ||
93 | 1 | 10µs | 1 | 2µs | _strict_mode_for_caller(1) if grep(/^:strict$/, @_); # spent 2µs making 1 call to XML::Simple::CORE:match |
94 | |||||
95 | # Pass everything else to Exporter.pm | ||||
96 | |||||
97 | 1 | 4µs | 1 | 300ns | @_ = grep(!/^:strict$/, @_); # spent 300ns making 1 call to XML::Simple::CORE:match |
98 | 1 | 6µs | 1 | 36µs | goto &Exporter::import; # spent 36µs making 1 call to Exporter::import |
99 | } | ||||
100 | |||||
101 | |||||
102 | ############################################################################## | ||||
103 | # Constructor for optional object interface. | ||||
104 | # | ||||
105 | |||||
106 | # spent 108µs (80+28) within XML::Simple::new which was called 2 times, avg 54µs/call:
# 2 times (80µs+28µs) by XML::Simple::_get_object at line 166, avg 54µs/call | ||||
107 | 2 | 1µs | my $class = shift; | ||
108 | |||||
109 | 2 | 2µs | if(@_ % 2) { | ||
110 | croak "Default options must be name=>value pairs (odd number supplied)"; | ||||
111 | } | ||||
112 | |||||
113 | 2 | 500ns | my %known_opt; | ||
114 | 2 | 38µs | @known_opt{@KnownOptIn, @KnownOptOut} = (); | ||
115 | |||||
116 | 2 | 2µs | my %raw_opt = @_; | ||
117 | 2 | 6µs | 2 | 25µs | $raw_opt{strictmode} = _strict_mode_for_caller() # spent 25µs making 2 calls to XML::Simple::_strict_mode_for_caller, avg 13µs/call |
118 | unless exists $raw_opt{strictmode}; | ||||
119 | 2 | 500ns | my %def_opt; | ||
120 | 2 | 4µs | while(my($key, $val) = each %raw_opt) { | ||
121 | 2 | 2µs | my $lkey = lc($key); | ||
122 | 2 | 10µs | 2 | 3µs | $lkey =~ s/_//g; # spent 3µs making 2 calls to XML::Simple::CORE:subst, avg 1µs/call |
123 | 2 | 1µs | croak "Unrecognised option: $key" unless(exists($known_opt{$lkey})); | ||
124 | 2 | 2µs | $def_opt{$lkey} = $val; | ||
125 | } | ||||
126 | 2 | 3µs | my $self = { def_opt => \%def_opt }; | ||
127 | |||||
128 | 2 | 11µs | return(bless($self, $class)); | ||
129 | } | ||||
130 | |||||
131 | |||||
132 | ############################################################################## | ||||
133 | # Sub: _strict_mode_for_caller() | ||||
134 | # | ||||
135 | # Gets or sets the XML::Simple :strict mode flag for the calling namespace. | ||||
136 | # Walks back through call stack to find the calling namespace and sets the | ||||
137 | # :strict mode flag for that namespace if an argument was supplied and returns | ||||
138 | # the flag value if not. | ||||
139 | # | ||||
140 | |||||
141 | # spent 25µs within XML::Simple::_strict_mode_for_caller which was called 2 times, avg 13µs/call:
# 2 times (25µs+0s) by XML::Simple::new at line 117, avg 13µs/call | ||||
142 | 2 | 1µs | my $set_mode = @_; | ||
143 | 2 | 600ns | my $frame = 1; | ||
144 | 2 | 14µs | while(my($package) = caller($frame++)) { | ||
145 | 6 | 4µs | next if $package eq 'XML::Simple'; | ||
146 | 2 | 400ns | $StrictMode{$package} = 1 if $set_mode; | ||
147 | 2 | 8µs | return $StrictMode{$package}; | ||
148 | } | ||||
149 | return(0); | ||||
150 | } | ||||
151 | |||||
152 | |||||
153 | ############################################################################## | ||||
154 | # Sub: _get_object() | ||||
155 | # | ||||
156 | # Helper routine called from XMLin() and XMLout() to create an object if none | ||||
157 | # was provided. Note, this routine does mess with the caller's @_ array. | ||||
158 | # | ||||
159 | |||||
160 | sub _get_object { | ||||
161 | 4 | 800ns | my $self; | ||
162 | 4 | 23µs | 4 | 9µs | if($_[0] and UNIVERSAL::isa($_[0], 'XML::Simple')) { # spent 9µs making 4 calls to UNIVERSAL::isa, avg 2µs/call |
163 | $self = shift; | ||||
164 | } | ||||
165 | else { | ||||
166 | 2 | 6µs | 2 | 108µs | $self = XML::Simple->new(); # spent 108µs making 2 calls to XML::Simple::new, avg 54µs/call |
167 | } | ||||
168 | |||||
169 | 4 | 8µs | return $self; | ||
170 | } | ||||
171 | |||||
172 | |||||
173 | ############################################################################## | ||||
174 | # Sub/Method: XMLin() | ||||
175 | # | ||||
176 | # Exported routine for slurping XML into a hashref - see pod for info. | ||||
177 | # | ||||
178 | # May be called as object method or as a plain function. | ||||
179 | # | ||||
180 | # Expects one arg for the source XML, optionally followed by a number of | ||||
181 | # name => value option pairs. | ||||
182 | # | ||||
183 | |||||
184 | # spent 59.8ms (33µs+59.8) within XML::Simple::XMLin which was called 2 times, avg 29.9ms/call:
# 2 times (33µs+59.8ms) by C4::Context::read_config_file at line 262 of C4/Context.pm, avg 29.9ms/call | ||||
185 | 2 | 3µs | 2 | 134µs | my $self = &_get_object; # note, @_ is passed implicitly # spent 134µs making 2 calls to XML::Simple::_get_object, avg 67µs/call |
186 | |||||
187 | 2 | 900ns | my $target = shift; | ||
188 | |||||
189 | |||||
190 | # Work out whether to parse a string, a file or a filehandle | ||||
191 | |||||
192 | 2 | 10µs | 2 | 2µs | if(not defined $target) { # spent 2µs making 2 calls to XML::Simple::CORE:match, avg 800ns/call |
193 | return $self->parse_file(undef, @_); | ||||
194 | } | ||||
195 | |||||
196 | elsif($target eq '-') { | ||||
197 | local($/) = undef; | ||||
198 | $target = <STDIN>; | ||||
199 | return $self->parse_string(\$target, @_); | ||||
200 | } | ||||
201 | |||||
202 | elsif(my $type = ref($target)) { | ||||
203 | if($type eq 'SCALAR') { | ||||
204 | return $self->parse_string($target, @_); | ||||
205 | } | ||||
206 | else { | ||||
207 | return $self->parse_fh($target, @_); | ||||
208 | } | ||||
209 | } | ||||
210 | |||||
211 | elsif($target =~ m{<.*?>}s) { | ||||
212 | return $self->parse_string(\$target, @_); | ||||
213 | } | ||||
214 | |||||
215 | else { | ||||
216 | 2 | 18µs | 2 | 59.6ms | return $self->parse_file($target, @_); # spent 59.6ms making 2 calls to XML::Simple::parse_file, avg 29.8ms/call |
217 | } | ||||
218 | } | ||||
219 | |||||
220 | |||||
221 | ############################################################################## | ||||
222 | # Sub/Method: parse_file() | ||||
223 | # | ||||
224 | # Same as XMLin, but only parses from a named file. | ||||
225 | # | ||||
226 | |||||
227 | # spent 59.6ms (35µs+59.6) within XML::Simple::parse_file which was called 2 times, avg 29.8ms/call:
# 2 times (35µs+59.6ms) by XML::Simple::XMLin at line 216, avg 29.8ms/call | ||||
228 | 2 | 2µs | 2 | 11µs | my $self = &_get_object; # note, @_ is passed implicitly # spent 11µs making 2 calls to XML::Simple::_get_object, avg 6µs/call |
229 | |||||
230 | 2 | 600ns | my $filename = shift; | ||
231 | |||||
232 | 2 | 6µs | 2 | 114µs | $self->handle_options('in', @_); # spent 114µs making 2 calls to XML::Simple::handle_options, avg 57µs/call |
233 | |||||
234 | 2 | 600ns | $filename = $self->default_config_file if not defined $filename; | ||
235 | |||||
236 | 2 | 5µs | 2 | 61µs | $filename = $self->find_xml_file($filename, @{$self->{opt}->{searchpath}}); # spent 61µs making 2 calls to XML::Simple::find_xml_file, avg 30µs/call |
237 | |||||
238 | # Check cache for previous parse | ||||
239 | |||||
240 | 2 | 2µs | if($self->{opt}->{cache}) { | ||
241 | foreach my $scheme (@{$self->{opt}->{cache}}) { | ||||
242 | my $method = 'cache_read_' . $scheme; | ||||
243 | my $opt = $self->$method($filename); | ||||
244 | return($opt) if($opt); | ||||
245 | } | ||||
246 | } | ||||
247 | |||||
248 | 2 | 7µs | 2 | 59.4ms | my $ref = $self->build_simple_tree($filename, undef); # spent 59.4ms making 2 calls to XML::Simple::build_simple_tree, avg 29.7ms/call |
249 | |||||
250 | 2 | 1µs | if($self->{opt}->{cache}) { | ||
251 | my $method = 'cache_write_' . $self->{opt}->{cache}->[0]; | ||||
252 | $self->$method($ref, $filename); | ||||
253 | } | ||||
254 | |||||
255 | 2 | 5µs | return $ref; | ||
256 | } | ||||
257 | |||||
258 | |||||
259 | ############################################################################## | ||||
260 | # Sub/Method: parse_fh() | ||||
261 | # | ||||
262 | # Same as XMLin, but only parses from a filehandle. | ||||
263 | # | ||||
264 | |||||
265 | sub parse_fh { | ||||
266 | my $self = &_get_object; # note, @_ is passed implicitly | ||||
267 | |||||
268 | my $fh = shift; | ||||
269 | croak "Can't use " . (defined $fh ? qq{string ("$fh")} : 'undef') . | ||||
270 | " as a filehandle" unless ref $fh; | ||||
271 | |||||
272 | $self->handle_options('in', @_); | ||||
273 | |||||
274 | return $self->build_simple_tree(undef, $fh); | ||||
275 | } | ||||
276 | |||||
277 | |||||
278 | ############################################################################## | ||||
279 | # Sub/Method: parse_string() | ||||
280 | # | ||||
281 | # Same as XMLin, but only parses from a string or a reference to a string. | ||||
282 | # | ||||
283 | |||||
284 | sub parse_string { | ||||
285 | my $self = &_get_object; # note, @_ is passed implicitly | ||||
286 | |||||
287 | my $string = shift; | ||||
288 | |||||
289 | $self->handle_options('in', @_); | ||||
290 | |||||
291 | return $self->build_simple_tree(undef, ref $string ? $string : \$string); | ||||
292 | } | ||||
293 | |||||
294 | |||||
295 | ############################################################################## | ||||
296 | # Method: default_config_file() | ||||
297 | # | ||||
298 | # Returns the name of the XML file to parse if no filename (or XML string) | ||||
299 | # was provided. | ||||
300 | # | ||||
301 | |||||
302 | sub default_config_file { | ||||
303 | my $self = shift; | ||||
304 | |||||
305 | require File::Basename; | ||||
306 | |||||
307 | my($basename, $script_dir, $ext) = File::Basename::fileparse($0, '\.[^\.]+'); | ||||
308 | |||||
309 | # Add script directory to searchpath | ||||
310 | |||||
311 | if($script_dir) { | ||||
312 | unshift(@{$self->{opt}->{searchpath}}, $script_dir); | ||||
313 | } | ||||
314 | |||||
315 | return $basename . '.xml'; | ||||
316 | } | ||||
317 | |||||
318 | |||||
319 | ############################################################################## | ||||
320 | # Method: build_simple_tree() | ||||
321 | # | ||||
322 | # Builds a 'tree' data structure as provided by XML::Parser and then | ||||
323 | # 'simplifies' it as specified by the various options in effect. | ||||
324 | # | ||||
325 | |||||
326 | # spent 59.4ms (47µs+59.4) within XML::Simple::build_simple_tree which was called 2 times, avg 29.7ms/call:
# 2 times (47µs+59.4ms) by XML::Simple::parse_file at line 248, avg 29.7ms/call | ||||
327 | 2 | 900ns | my $self = shift; | ||
328 | |||||
329 | 2 | 5µs | 2 | 55.6ms | my $tree = $self->build_tree(@_); # spent 55.6ms making 2 calls to XML::Simple::build_tree, avg 27.8ms/call |
330 | |||||
331 | return $self->{opt}->{keeproot} | ||||
332 | ? $self->collapse({}, @$tree) | ||||
333 | 2 | 41µs | 2 | 3.82ms | : $self->collapse(@{$tree->[1]}); # spent 3.82ms making 2 calls to XML::Simple::collapse, avg 1.91ms/call |
334 | } | ||||
335 | |||||
336 | |||||
337 | ############################################################################## | ||||
338 | # Method: build_tree() | ||||
339 | # | ||||
340 | # This routine will be called if there is no suitable pre-parsed tree in a | ||||
341 | # cache. It parses the XML and returns an XML::Parser 'Tree' style data | ||||
342 | # structure (summarised in the comments for the collapse() routine below). | ||||
343 | # | ||||
344 | # XML::Simple requires the services of another module that knows how to parse | ||||
345 | # XML. If XML::SAX is installed, the default SAX parser will be used, | ||||
346 | # otherwise XML::Parser will be used. | ||||
347 | # | ||||
348 | # This routine expects to be passed a filename as argument 1 or a 'string' as | ||||
349 | # argument 2. The 'string' might be a string of XML (passed by reference to | ||||
350 | # save memory) or it might be a reference to an IO::Handle. (This | ||||
351 | # non-intuitive mess results in part from the way XML::Parser works but that's | ||||
352 | # really no excuse). | ||||
353 | # | ||||
354 | |||||
355 | # spent 55.6ms (2.24+53.3) within XML::Simple::build_tree which was called 2 times, avg 27.8ms/call:
# 2 times (2.24ms+53.3ms) by XML::Simple::build_simple_tree at line 329, avg 27.8ms/call | ||||
356 | 2 | 700ns | my $self = shift; | ||
357 | 2 | 500ns | my $filename = shift; | ||
358 | 2 | 400ns | my $string = shift; | ||
359 | |||||
360 | |||||
361 | 2 | 900ns | my $preferred_parser = $PREFERRED_PARSER; | ||
362 | 2 | 2µs | unless(defined($preferred_parser)) { | ||
363 | $preferred_parser = $ENV{XML_SIMPLE_PREFERRED_PARSER} || ''; | ||||
364 | } | ||||
365 | 2 | 900ns | if($preferred_parser eq 'XML::Parser') { | ||
366 | return($self->build_tree_xml_parser($filename, $string)); | ||||
367 | } | ||||
368 | |||||
369 | 4 | 1.30ms | eval { require XML::SAX; }; # We didn't need it until now | ||
370 | 2 | 700ns | if($@) { # No XML::SAX - fall back to XML::Parser | ||
371 | if($preferred_parser) { # unless a SAX parser was expressly requested | ||||
372 | croak "XMLin() could not load XML::SAX"; | ||||
373 | } | ||||
374 | return($self->build_tree_xml_parser($filename, $string)); | ||||
375 | } | ||||
376 | |||||
377 | 2 | 600ns | $XML::SAX::ParserPackage = $preferred_parser if($preferred_parser); | ||
378 | |||||
379 | 2 | 10µs | 2 | 34.0ms | my $sp = XML::SAX::ParserFactory->parser(Handler => $self); # spent 34.0ms making 2 calls to XML::SAX::ParserFactory::parser, avg 17.0ms/call |
380 | |||||
381 | 2 | 2µs | $self->{nocollapse} = 1; | ||
382 | 2 | 500ns | my($tree); | ||
383 | 2 | 6µs | 2 | 16.6ms | if($filename) { # spent 16.6ms making 2 calls to XML::SAX::Base::parse_uri, avg 8.30ms/call |
384 | $tree = $sp->parse_uri($filename); | ||||
385 | } | ||||
386 | else { | ||||
387 | if(ref($string) && ref($string) ne 'SCALAR') { | ||||
388 | $tree = $sp->parse_file($string); | ||||
389 | } | ||||
390 | else { | ||||
391 | $tree = $sp->parse_string($$string); | ||||
392 | } | ||||
393 | } | ||||
394 | |||||
395 | 2 | 26µs | return($tree); | ||
396 | } | ||||
397 | |||||
398 | |||||
399 | ############################################################################## | ||||
400 | # Method: build_tree_xml_parser() | ||||
401 | # | ||||
402 | # This routine will be called if XML::SAX is not installed, or if XML::Parser | ||||
403 | # was specifically requested. It takes the same arguments as build_tree() and | ||||
404 | # returns the same data structure (XML::Parser 'Tree' style). | ||||
405 | # | ||||
406 | |||||
407 | sub build_tree_xml_parser { | ||||
408 | my $self = shift; | ||||
409 | my $filename = shift; | ||||
410 | my $string = shift; | ||||
411 | |||||
412 | |||||
413 | eval { | ||||
414 | local($^W) = 0; # Suppress warning from Expat.pm re File::Spec::load() | ||||
415 | require XML::Parser; # We didn't need it until now | ||||
416 | }; | ||||
417 | if($@) { | ||||
418 | croak "XMLin() requires either XML::SAX or XML::Parser"; | ||||
419 | } | ||||
420 | |||||
421 | if($self->{opt}->{nsexpand}) { | ||||
422 | carp "'nsexpand' option requires XML::SAX"; | ||||
423 | } | ||||
424 | |||||
425 | my $xp = XML::Parser->new(Style => 'Tree', @{$self->{opt}->{parseropts}}); | ||||
426 | my($tree); | ||||
427 | if($filename) { | ||||
428 | # $tree = $xp->parsefile($filename); # Changed due to prob w/mod_perl | ||||
429 | open(my $xfh, '<', $filename) || croak qq($filename - $!); | ||||
430 | $tree = $xp->parse($xfh); | ||||
431 | } | ||||
432 | else { | ||||
433 | $tree = $xp->parse($$string); | ||||
434 | } | ||||
435 | |||||
436 | return($tree); | ||||
437 | } | ||||
438 | |||||
439 | |||||
440 | ############################################################################## | ||||
441 | # Method: cache_write_storable() | ||||
442 | # | ||||
443 | # Wrapper routine for invoking Storable::nstore() to cache a parsed data | ||||
444 | # structure. | ||||
445 | # | ||||
446 | |||||
447 | sub cache_write_storable { | ||||
448 | my($self, $data, $filename) = @_; | ||||
449 | |||||
450 | my $cachefile = $self->storable_filename($filename); | ||||
451 | |||||
452 | require Storable; # We didn't need it until now | ||||
453 | |||||
454 | if ('VMS' eq $^O) { | ||||
455 | Storable::nstore($data, $cachefile); | ||||
456 | } | ||||
457 | else { | ||||
458 | # If the following line fails for you, your Storable.pm is old - upgrade | ||||
459 | Storable::lock_nstore($data, $cachefile); | ||||
460 | } | ||||
461 | |||||
462 | } | ||||
463 | |||||
464 | |||||
465 | ############################################################################## | ||||
466 | # Method: cache_read_storable() | ||||
467 | # | ||||
468 | # Wrapper routine for invoking Storable::retrieve() to read a cached parsed | ||||
469 | # data structure. Only returns cached data if the cache file exists and is | ||||
470 | # newer than the source XML file. | ||||
471 | # | ||||
472 | |||||
473 | sub cache_read_storable { | ||||
474 | my($self, $filename) = @_; | ||||
475 | |||||
476 | my $cachefile = $self->storable_filename($filename); | ||||
477 | |||||
478 | return unless(-r $cachefile); | ||||
479 | return unless((stat($cachefile))[9] > (stat($filename))[9]); | ||||
480 | |||||
481 | require Storable; # We didn't need it until now | ||||
482 | |||||
483 | if ('VMS' eq $^O) { | ||||
484 | return(Storable::retrieve($cachefile)); | ||||
485 | } | ||||
486 | else { | ||||
487 | return(Storable::lock_retrieve($cachefile)); | ||||
488 | } | ||||
489 | |||||
490 | } | ||||
491 | |||||
492 | |||||
493 | ############################################################################## | ||||
494 | # Method: storable_filename() | ||||
495 | # | ||||
496 | # Translates the supplied source XML filename into a filename for the storable | ||||
497 | # cached data. A '.stor' suffix is added after stripping an optional '.xml' | ||||
498 | # suffix. | ||||
499 | # | ||||
500 | |||||
501 | sub storable_filename { | ||||
502 | my($self, $cachefile) = @_; | ||||
503 | |||||
504 | $cachefile =~ s{(\.xml)?$}{.stor}; | ||||
505 | return $cachefile; | ||||
506 | } | ||||
507 | |||||
508 | |||||
509 | ############################################################################## | ||||
510 | # Method: cache_write_memshare() | ||||
511 | # | ||||
512 | # Takes the supplied data structure reference and stores it away in a global | ||||
513 | # hash structure. | ||||
514 | # | ||||
515 | |||||
516 | sub cache_write_memshare { | ||||
517 | my($self, $data, $filename) = @_; | ||||
518 | |||||
519 | $MemShareCache{$filename} = [time(), $data]; | ||||
520 | } | ||||
521 | |||||
522 | |||||
523 | ############################################################################## | ||||
524 | # Method: cache_read_memshare() | ||||
525 | # | ||||
526 | # Takes a filename and looks in a global hash for a cached parsed version. | ||||
527 | # | ||||
528 | |||||
529 | sub cache_read_memshare { | ||||
530 | my($self, $filename) = @_; | ||||
531 | |||||
532 | return unless($MemShareCache{$filename}); | ||||
533 | return unless($MemShareCache{$filename}->[0] > (stat($filename))[9]); | ||||
534 | |||||
535 | return($MemShareCache{$filename}->[1]); | ||||
536 | |||||
537 | } | ||||
538 | |||||
539 | |||||
540 | ############################################################################## | ||||
541 | # Method: cache_write_memcopy() | ||||
542 | # | ||||
543 | # Takes the supplied data structure and stores a copy of it in a global hash | ||||
544 | # structure. | ||||
545 | # | ||||
546 | |||||
547 | sub cache_write_memcopy { | ||||
548 | my($self, $data, $filename) = @_; | ||||
549 | |||||
550 | require Storable; # We didn't need it until now | ||||
551 | |||||
552 | $MemCopyCache{$filename} = [time(), Storable::dclone($data)]; | ||||
553 | } | ||||
554 | |||||
555 | |||||
556 | ############################################################################## | ||||
557 | # Method: cache_read_memcopy() | ||||
558 | # | ||||
559 | # Takes a filename and looks in a global hash for a cached parsed version. | ||||
560 | # Returns a reference to a copy of that data structure. | ||||
561 | # | ||||
562 | |||||
563 | sub cache_read_memcopy { | ||||
564 | my($self, $filename) = @_; | ||||
565 | |||||
566 | return unless($MemCopyCache{$filename}); | ||||
567 | return unless($MemCopyCache{$filename}->[0] > (stat($filename))[9]); | ||||
568 | |||||
569 | return(Storable::dclone($MemCopyCache{$filename}->[1])); | ||||
570 | |||||
571 | } | ||||
572 | |||||
573 | |||||
574 | ############################################################################## | ||||
575 | # Sub/Method: XMLout() | ||||
576 | # | ||||
577 | # Exported routine for 'unslurping' a data structure out to XML. | ||||
578 | # | ||||
579 | # Expects a reference to a data structure and an optional list of option | ||||
580 | # name => value pairs. | ||||
581 | # | ||||
582 | |||||
583 | sub XMLout { | ||||
584 | my $self = &_get_object; # note, @_ is passed implicitly | ||||
585 | |||||
586 | croak "XMLout() requires at least one argument" unless(@_); | ||||
587 | my $ref = shift; | ||||
588 | |||||
589 | $self->handle_options('out', @_); | ||||
590 | |||||
591 | |||||
592 | # If namespace expansion is set, XML::NamespaceSupport is required | ||||
593 | |||||
594 | if($self->{opt}->{nsexpand}) { | ||||
595 | require XML::NamespaceSupport; | ||||
596 | $self->{nsup} = XML::NamespaceSupport->new(); | ||||
597 | $self->{ns_prefix} = 'aaa'; | ||||
598 | } | ||||
599 | |||||
600 | |||||
601 | # Wrap top level arrayref in a hash | ||||
602 | |||||
603 | if(UNIVERSAL::isa($ref, 'ARRAY')) { | ||||
604 | $ref = { anon => $ref }; | ||||
605 | } | ||||
606 | |||||
607 | |||||
608 | # Extract rootname from top level hash if keeproot enabled | ||||
609 | |||||
610 | if($self->{opt}->{keeproot}) { | ||||
611 | my(@keys) = keys(%$ref); | ||||
612 | if(@keys == 1) { | ||||
613 | $ref = $ref->{$keys[0]}; | ||||
614 | $self->{opt}->{rootname} = $keys[0]; | ||||
615 | } | ||||
616 | } | ||||
617 | |||||
618 | # Ensure there are no top level attributes if we're not adding root elements | ||||
619 | |||||
620 | elsif($self->{opt}->{rootname} eq '') { | ||||
621 | if(UNIVERSAL::isa($ref, 'HASH')) { | ||||
622 | my $refsave = $ref; | ||||
623 | $ref = {}; | ||||
624 | foreach (keys(%$refsave)) { | ||||
625 | if(ref($refsave->{$_})) { | ||||
626 | $ref->{$_} = $refsave->{$_}; | ||||
627 | } | ||||
628 | else { | ||||
629 | $ref->{$_} = [ $refsave->{$_} ]; | ||||
630 | } | ||||
631 | } | ||||
632 | } | ||||
633 | } | ||||
634 | |||||
635 | |||||
636 | # Encode the hashref and write to file if necessary | ||||
637 | |||||
638 | $self->{_ancestors} = []; | ||||
639 | my $xml = $self->value_to_xml($ref, $self->{opt}->{rootname}, ''); | ||||
640 | delete $self->{_ancestors}; | ||||
641 | |||||
642 | if($self->{opt}->{xmldecl}) { | ||||
643 | $xml = $self->{opt}->{xmldecl} . "\n" . $xml; | ||||
644 | } | ||||
645 | |||||
646 | if($self->{opt}->{outputfile}) { | ||||
647 | if(ref($self->{opt}->{outputfile})) { | ||||
648 | my $fh = $self->{opt}->{outputfile}; | ||||
649 | if(UNIVERSAL::isa($fh, 'GLOB') and !UNIVERSAL::can($fh, 'print')) { | ||||
650 | eval { require IO::Handle; }; | ||||
651 | croak $@ if $@; | ||||
652 | } | ||||
653 | return($fh->print($xml)); | ||||
654 | } | ||||
655 | else { | ||||
656 | open(my $out, '>', "$self->{opt}->{outputfile}") || | ||||
657 | croak "open($self->{opt}->{outputfile}): $!"; | ||||
658 | binmode($out, ':utf8') if($] >= 5.008); | ||||
659 | print $out $xml or croak "print: $!"; | ||||
660 | close $out or croak "close: $!"; | ||||
661 | } | ||||
662 | } | ||||
663 | elsif($self->{opt}->{handler}) { | ||||
664 | require XML::SAX; | ||||
665 | my $sp = XML::SAX::ParserFactory->parser( | ||||
666 | Handler => $self->{opt}->{handler} | ||||
667 | ); | ||||
668 | return($sp->parse_string($xml)); | ||||
669 | } | ||||
670 | else { | ||||
671 | return($xml); | ||||
672 | } | ||||
673 | } | ||||
674 | |||||
675 | |||||
676 | ############################################################################## | ||||
677 | # Method: handle_options() | ||||
678 | # | ||||
679 | # Helper routine for both XMLin() and XMLout(). Both routines handle their | ||||
680 | # first argument and assume all other args are options handled by this routine. | ||||
681 | # Saves a hash of options in $self->{opt}. | ||||
682 | # | ||||
683 | # If default options were passed to the constructor, they will be retrieved | ||||
684 | # here and merged with options supplied to the method call. | ||||
685 | # | ||||
686 | # First argument should be the string 'in' or the string 'out'. | ||||
687 | # | ||||
688 | # Remaining arguments should be name=>value pairs. Sets up default values | ||||
689 | # for options not supplied. Unrecognised options are a fatal error. | ||||
690 | # | ||||
691 | |||||
692 | # spent 114µs (112+2) within XML::Simple::handle_options which was called 2 times, avg 57µs/call:
# 2 times (112µs+2µs) by XML::Simple::parse_file at line 232, avg 57µs/call | ||||
693 | 2 | 600ns | my $self = shift; | ||
694 | 2 | 600ns | my $dirn = shift; | ||
695 | |||||
696 | |||||
697 | # Determine valid options based on context | ||||
698 | |||||
699 | 2 | 300ns | my %known_opt; | ||
700 | 2 | 12µs | if($dirn eq 'in') { | ||
701 | @known_opt{@KnownOptIn} = @KnownOptIn; | ||||
702 | } | ||||
703 | else { | ||||
704 | @known_opt{@KnownOptOut} = @KnownOptOut; | ||||
705 | } | ||||
706 | |||||
707 | |||||
708 | # Store supplied options in hashref and weed out invalid ones | ||||
709 | |||||
710 | 2 | 1µs | if(@_ % 2) { | ||
711 | croak "Options must be name=>value pairs (odd number supplied)"; | ||||
712 | } | ||||
713 | 2 | 3µs | my %raw_opt = @_; | ||
714 | 2 | 900ns | my $opt = {}; | ||
715 | 2 | 2µs | $self->{opt} = $opt; | ||
716 | |||||
717 | 2 | 6µs | while(my($key, $val) = each %raw_opt) { | ||
718 | 6 | 3µs | my $lkey = lc($key); | ||
719 | 6 | 10µs | 6 | 2µs | $lkey =~ s/_//g; # spent 2µs making 6 calls to XML::Simple::CORE:subst, avg 333ns/call |
720 | 6 | 2µs | croak "Unrecognised option: $key" unless($known_opt{$lkey}); | ||
721 | 6 | 3µs | $opt->{$lkey} = $val; | ||
722 | } | ||||
723 | |||||
724 | |||||
725 | # Merge in options passed to constructor | ||||
726 | |||||
727 | 2 | 6µs | foreach (keys(%known_opt)) { | ||
728 | 38 | 13µs | unless(exists($opt->{$_})) { | ||
729 | if(exists($self->{def_opt}->{$_})) { | ||||
730 | $opt->{$_} = $self->{def_opt}->{$_}; | ||||
731 | } | ||||
732 | } | ||||
733 | } | ||||
734 | |||||
735 | |||||
736 | # Set sensible defaults if not supplied | ||||
737 | |||||
738 | 2 | 1µs | if(exists($opt->{rootname})) { | ||
739 | unless(defined($opt->{rootname})) { | ||||
740 | $opt->{rootname} = ''; | ||||
741 | } | ||||
742 | } | ||||
743 | else { | ||||
744 | 2 | 2µs | $opt->{rootname} = $DefRootName; | ||
745 | } | ||||
746 | |||||
747 | 2 | 800ns | if($opt->{xmldecl} and $opt->{xmldecl} eq '1') { | ||
748 | $opt->{xmldecl} = $DefXmlDecl; | ||||
749 | } | ||||
750 | |||||
751 | 2 | 1µs | if(exists($opt->{contentkey})) { | ||
752 | if($opt->{contentkey} =~ m{^-(.*)$}) { | ||||
753 | $opt->{contentkey} = $1; | ||||
754 | $opt->{collapseagain} = 1; | ||||
755 | } | ||||
756 | } | ||||
757 | else { | ||||
758 | 2 | 1µs | $opt->{contentkey} = $DefContentKey; | ||
759 | } | ||||
760 | |||||
761 | 2 | 2µs | unless(exists($opt->{normalisespace})) { | ||
762 | $opt->{normalisespace} = $opt->{normalizespace}; | ||||
763 | } | ||||
764 | 2 | 1µs | $opt->{normalisespace} = 0 unless(defined($opt->{normalisespace})); | ||
765 | |||||
766 | # Cleanups for values assumed to be arrays later | ||||
767 | |||||
768 | 2 | 1µs | if($opt->{searchpath}) { | ||
769 | unless(ref($opt->{searchpath})) { | ||||
770 | $opt->{searchpath} = [ $opt->{searchpath} ]; | ||||
771 | } | ||||
772 | } | ||||
773 | else { | ||||
774 | 2 | 2µs | $opt->{searchpath} = [ ]; | ||
775 | } | ||||
776 | |||||
777 | 2 | 700ns | if($opt->{cache} and !ref($opt->{cache})) { | ||
778 | $opt->{cache} = [ $opt->{cache} ]; | ||||
779 | } | ||||
780 | 2 | 900ns | if($opt->{cache}) { | ||
781 | $_ = lc($_) foreach (@{$opt->{cache}}); | ||||
782 | foreach my $scheme (@{$opt->{cache}}) { | ||||
783 | my $method = 'cache_read_' . $scheme; | ||||
784 | croak "Unsupported caching scheme: $scheme" | ||||
785 | unless($self->can($method)); | ||||
786 | } | ||||
787 | } | ||||
788 | |||||
789 | 2 | 2µs | if(exists($opt->{parseropts})) { | ||
790 | if($^W) { | ||||
791 | carp "Warning: " . | ||||
792 | "'ParserOpts' is deprecated, contact the author if you need it"; | ||||
793 | } | ||||
794 | } | ||||
795 | else { | ||||
796 | 2 | 1µs | $opt->{parseropts} = [ ]; | ||
797 | } | ||||
798 | |||||
799 | |||||
800 | # Special cleanup for {forcearray} which could be regex, arrayref or boolean | ||||
801 | # or left to default to 0 | ||||
802 | |||||
803 | 2 | 2µs | if(exists($opt->{forcearray})) { | ||
804 | 2 | 2µs | if(ref($opt->{forcearray}) eq 'Regexp') { | ||
805 | $opt->{forcearray} = [ $opt->{forcearray} ]; | ||||
806 | } | ||||
807 | |||||
808 | 2 | 2µs | if(ref($opt->{forcearray}) eq 'ARRAY') { | ||
809 | 2 | 2µs | my @force_list = @{$opt->{forcearray}}; | ||
810 | 2 | 1µs | if(@force_list) { | ||
811 | 2 | 1µs | $opt->{forcearray} = {}; | ||
812 | 2 | 1µs | foreach my $tag (@force_list) { | ||
813 | 6 | 2µs | if(ref($tag) eq 'Regexp') { | ||
814 | push @{$opt->{forcearray}->{_regex}}, $tag; | ||||
815 | } | ||||
816 | else { | ||||
817 | 6 | 3µs | $opt->{forcearray}->{$tag} = 1; | ||
818 | } | ||||
819 | } | ||||
820 | } | ||||
821 | else { | ||||
822 | $opt->{forcearray} = 0; | ||||
823 | } | ||||
824 | } | ||||
825 | else { | ||||
826 | $opt->{forcearray} = ( $opt->{forcearray} ? 1 : 0 ); | ||||
827 | } | ||||
828 | } | ||||
829 | else { | ||||
830 | if($opt->{strictmode} and $dirn eq 'in') { | ||||
831 | croak "No value specified for 'ForceArray' option in call to XML$dirn()"; | ||||
832 | } | ||||
833 | $opt->{forcearray} = 0; | ||||
834 | } | ||||
835 | |||||
836 | |||||
837 | # Special cleanup for {keyattr} which could be arrayref or hashref or left | ||||
838 | # to default to arrayref | ||||
839 | |||||
840 | 2 | 2µs | if(exists($opt->{keyattr})) { | ||
841 | 2 | 2µs | if(ref($opt->{keyattr})) { | ||
842 | 2 | 1µs | if(ref($opt->{keyattr}) eq 'HASH') { | ||
843 | |||||
844 | # Make a copy so we can mess with it | ||||
845 | |||||
846 | $opt->{keyattr} = { %{$opt->{keyattr}} }; | ||||
847 | |||||
848 | |||||
849 | # Convert keyattr => { elem => '+attr' } | ||||
850 | # to keyattr => { elem => [ 'attr', '+' ] } | ||||
851 | |||||
852 | foreach my $el (keys(%{$opt->{keyattr}})) { | ||||
853 | if($opt->{keyattr}->{$el} =~ /^(\+|-)?(.*)$/) { | ||||
854 | $opt->{keyattr}->{$el} = [ $2, ($1 ? $1 : '') ]; | ||||
855 | if($opt->{strictmode} and $dirn eq 'in') { | ||||
856 | next if($opt->{forcearray} == 1); | ||||
857 | next if(ref($opt->{forcearray}) eq 'HASH' | ||||
858 | and $opt->{forcearray}->{$el}); | ||||
859 | croak "<$el> set in KeyAttr but not in ForceArray"; | ||||
860 | } | ||||
861 | } | ||||
862 | else { | ||||
863 | delete($opt->{keyattr}->{$el}); # Never reached (famous last words?) | ||||
864 | } | ||||
865 | } | ||||
866 | } | ||||
867 | else { | ||||
868 | 2 | 1µs | if(@{$opt->{keyattr}} == 0) { | ||
869 | delete($opt->{keyattr}); | ||||
870 | } | ||||
871 | } | ||||
872 | } | ||||
873 | else { | ||||
874 | $opt->{keyattr} = [ $opt->{keyattr} ]; | ||||
875 | } | ||||
876 | } | ||||
877 | else { | ||||
878 | if($opt->{strictmode}) { | ||||
879 | croak "No value specified for 'KeyAttr' option in call to XML$dirn()"; | ||||
880 | } | ||||
881 | $opt->{keyattr} = [ @DefKeyAttr ]; | ||||
882 | } | ||||
883 | |||||
884 | |||||
885 | # Special cleanup for {valueattr} which could be arrayref or hashref | ||||
886 | |||||
887 | 2 | 2µs | if(exists($opt->{valueattr})) { | ||
888 | if(ref($opt->{valueattr}) eq 'ARRAY') { | ||||
889 | $opt->{valueattrlist} = {}; | ||||
890 | $opt->{valueattrlist}->{$_} = 1 foreach(@{ delete $opt->{valueattr} }); | ||||
891 | } | ||||
892 | } | ||||
893 | |||||
894 | # make sure there's nothing weird in {grouptags} | ||||
895 | |||||
896 | 2 | 900ns | if($opt->{grouptags}) { | ||
897 | croak "Illegal value for 'GroupTags' option - expected a hashref" | ||||
898 | unless UNIVERSAL::isa($opt->{grouptags}, 'HASH'); | ||||
899 | |||||
900 | while(my($key, $val) = each %{$opt->{grouptags}}) { | ||||
901 | next if $key ne $val; | ||||
902 | croak "Bad value in GroupTags: '$key' => '$val'"; | ||||
903 | } | ||||
904 | } | ||||
905 | |||||
906 | |||||
907 | # Check the {variables} option is valid and initialise variables hash | ||||
908 | |||||
909 | 2 | 1µs | if($opt->{variables} and !UNIVERSAL::isa($opt->{variables}, 'HASH')) { | ||
910 | croak "Illegal value for 'Variables' option - expected a hashref"; | ||||
911 | } | ||||
912 | |||||
913 | 2 | 10µs | if($opt->{variables}) { | ||
914 | $self->{_var_values} = { %{$opt->{variables}} }; | ||||
915 | } | ||||
916 | elsif($opt->{varattr}) { | ||||
917 | $self->{_var_values} = {}; | ||||
918 | } | ||||
919 | |||||
920 | } | ||||
921 | |||||
922 | |||||
923 | ############################################################################## | ||||
924 | # Method: find_xml_file() | ||||
925 | # | ||||
926 | # Helper routine for XMLin(). | ||||
927 | # Takes a filename, and a list of directories, attempts to locate the file in | ||||
928 | # the directories listed. | ||||
929 | # Returns a full pathname on success; croaks on failure. | ||||
930 | # | ||||
931 | |||||
932 | # spent 61µs (23+38) within XML::Simple::find_xml_file which was called 2 times, avg 30µs/call:
# 2 times (23µs+38µs) by XML::Simple::parse_file at line 236, avg 30µs/call | ||||
933 | 2 | 800ns | my $self = shift; | ||
934 | 2 | 600ns | my $file = shift; | ||
935 | 2 | 900ns | my @search_path = @_; | ||
936 | |||||
937 | |||||
938 | 2 | 2µs | require File::Basename; | ||
939 | 2 | 1µs | require File::Spec; | ||
940 | |||||
941 | 2 | 4µs | 2 | 30µs | my($filename, $filedir) = File::Basename::fileparse($file); # spent 30µs making 2 calls to File::Basename::fileparse, avg 15µs/call |
942 | |||||
943 | 2 | 20µs | 2 | 9µs | if($filename ne $file) { # Ignore searchpath if dir component # spent 9µs making 2 calls to XML::Simple::CORE:ftis, avg 4µs/call |
944 | return($file) if(-e $file); | ||||
945 | } | ||||
946 | else { | ||||
947 | my($path); | ||||
948 | foreach $path (@search_path) { | ||||
949 | my $fullpath = File::Spec->catfile($path, $file); | ||||
950 | return($fullpath) if(-e $fullpath); | ||||
951 | } | ||||
952 | } | ||||
953 | |||||
954 | # If user did not supply a search path, default to current directory | ||||
955 | |||||
956 | if(!@search_path) { | ||||
957 | return($file) if(-e $file); | ||||
958 | croak "File does not exist: $file"; | ||||
959 | } | ||||
960 | |||||
961 | croak "Could not find $file in ", join(':', @search_path); | ||||
962 | } | ||||
963 | |||||
964 | |||||
965 | ############################################################################## | ||||
966 | # Method: collapse() | ||||
967 | # | ||||
968 | # Helper routine for XMLin(). This routine really comprises the 'smarts' (or | ||||
969 | # value add) of this module. | ||||
970 | # | ||||
971 | # Takes the parse tree that XML::Parser produced from the supplied XML and | ||||
972 | # recurses through it 'collapsing' unnecessary levels of indirection (nested | ||||
973 | # arrays etc) to produce a data structure that is easier to work with. | ||||
974 | # | ||||
975 | # Elements in the original parser tree are represented as an element name | ||||
976 | # followed by an arrayref. The first element of the array is a hashref | ||||
977 | # containing the attributes. The rest of the array contains a list of any | ||||
978 | # nested elements as name+arrayref pairs: | ||||
979 | # | ||||
980 | # <element name>, [ { <attribute hashref> }, <element name>, [ ... ], ... ] | ||||
981 | # | ||||
982 | # The special element name '0' (zero) flags text content. | ||||
983 | # | ||||
984 | # This routine cuts down the noise by discarding any text content consisting of | ||||
985 | # only whitespace and then moves the nested elements into the attribute hash | ||||
986 | # using the name of the nested element as the hash key and the collapsed | ||||
987 | # version of the nested element as the value. Multiple nested elements with | ||||
988 | # the same name will initially be represented as an arrayref, but this may be | ||||
989 | # 'folded' into a hashref depending on the value of the keyattr option. | ||||
990 | # | ||||
991 | |||||
992 | sub collapse { | ||||
993 | 142 | 17µs | my $self = shift; | ||
994 | |||||
995 | |||||
996 | # Start with the hash of attributes | ||||
997 | |||||
998 | 142 | 12µs | my $attr = shift; | ||
999 | 142 | 66µs | if($self->{opt}->{noattr}) { # Discard if 'noattr' set | ||
1000 | $attr = $self->new_hashref; | ||||
1001 | } | ||||
1002 | elsif($self->{opt}->{normalisespace} == 2) { | ||||
1003 | while(my($key, $value) = each %$attr) { | ||||
1004 | $attr->{$key} = $self->normalise_space($value) | ||||
1005 | } | ||||
1006 | } | ||||
1007 | |||||
1008 | |||||
1009 | # Do variable substitutions | ||||
1010 | |||||
1011 | 142 | 22µs | if(my $var = $self->{_var_values}) { | ||
1012 | while(my($key, $val) = each(%$attr)) { | ||||
1013 | $val =~ s{\$\{([\w.]+)\}}{ $self->get_var($1) }ge; | ||||
1014 | $attr->{$key} = $val; | ||||
1015 | } | ||||
1016 | } | ||||
1017 | |||||
1018 | |||||
1019 | # Roll up 'value' attributes (but only if no nested elements) | ||||
1020 | |||||
1021 | 142 | 23µs | if(!@_ and keys %$attr == 1) { | ||
1022 | my($k) = keys %$attr; | ||||
1023 | if($self->{opt}->{valueattrlist} and $self->{opt}->{valueattrlist}->{$k}) { | ||||
1024 | return $attr->{$k}; | ||||
1025 | } | ||||
1026 | } | ||||
1027 | |||||
1028 | |||||
1029 | # Add any nested elements | ||||
1030 | |||||
1031 | 142 | 11µs | my($key, $val); | ||
1032 | 142 | 50µs | while(@_) { | ||
1033 | 414 | 68µs | $key = shift; | ||
1034 | 414 | 56µs | $val = shift; | ||
1035 | 414 | 36µs | $val = '' if not defined $val; | ||
1036 | |||||
1037 | 414 | 90µs | if(ref($val)) { | ||
1038 | 140 | 167µs | 140 | 0s | $val = $self->collapse(@$val); # spent 4.49ms making 140 calls to XML::Simple::collapse, avg 32µs/call, recursion: max depth 3, sum of overlapping time 4.49ms |
1039 | 140 | 32µs | next if(!defined($val) and $self->{opt}->{suppressempty}); | ||
1040 | } | ||||
1041 | elsif($key eq '0') { | ||||
1042 | 274 | 1.21ms | 274 | 196µs | next if($val =~ m{^\s*$}s); # Skip all whitespace content # spent 196µs making 274 calls to XML::Simple::CORE:match, avg 717ns/call |
1043 | |||||
1044 | 118 | 28µs | $val = $self->normalise_space($val) | ||
1045 | if($self->{opt}->{normalisespace} == 2); | ||||
1046 | |||||
1047 | # do variable substitutions | ||||
1048 | |||||
1049 | 118 | 20µs | if(my $var = $self->{_var_values}) { | ||
1050 | $val =~ s{\$\{(\w+)\}}{ $self->get_var($1) }ge; | ||||
1051 | } | ||||
1052 | |||||
1053 | |||||
1054 | # look for variable definitions | ||||
1055 | |||||
1056 | 118 | 18µs | if(my $var = $self->{opt}->{varattr}) { | ||
1057 | if(exists $attr->{$var}) { | ||||
1058 | $self->set_var($attr->{$var}, $val); | ||||
1059 | } | ||||
1060 | } | ||||
1061 | |||||
1062 | |||||
1063 | # Collapse text content in element with no attributes to a string | ||||
1064 | |||||
1065 | 118 | 237µs | if(!%$attr and !@_) { | ||
1066 | return($self->{opt}->{forcecontent} ? | ||||
1067 | { $self->{opt}->{contentkey} => $val } : $val | ||||
1068 | ); | ||||
1069 | } | ||||
1070 | 28 | 12µs | $key = $self->{opt}->{contentkey}; | ||
1071 | } | ||||
1072 | |||||
1073 | |||||
1074 | # Combine duplicate attributes into arrayref if required | ||||
1075 | |||||
1076 | 168 | 471µs | 168 | 162µs | if(exists($attr->{$key})) { # spent 162µs making 168 calls to UNIVERSAL::isa, avg 963ns/call |
1077 | if(UNIVERSAL::isa($attr->{$key}, 'ARRAY')) { | ||||
1078 | push(@{$attr->{$key}}, $val); | ||||
1079 | } | ||||
1080 | else { | ||||
1081 | 6 | 5µs | $attr->{$key} = [ $attr->{$key}, $val ]; | ||
1082 | } | ||||
1083 | } | ||||
1084 | elsif(defined($val) and UNIVERSAL::isa($val, 'ARRAY')) { | ||||
1085 | $attr->{$key} = [ $val ]; | ||||
1086 | } | ||||
1087 | else { | ||||
1088 | 136 | 185µs | if( $key ne $self->{opt}->{contentkey} | ||
1089 | and ( | ||||
1090 | ($self->{opt}->{forcearray} == 1) | ||||
1091 | or ( | ||||
1092 | (ref($self->{opt}->{forcearray}) eq 'HASH') | ||||
1093 | and ( | ||||
1094 | $self->{opt}->{forcearray}->{$key} | ||||
1095 | or (grep $key =~ $_, @{$self->{opt}->{forcearray}->{_regex}}) | ||||
1096 | ) | ||||
1097 | ) | ||||
1098 | ) | ||||
1099 | ) { | ||||
1100 | $attr->{$key} = [ $val ]; | ||||
1101 | } | ||||
1102 | else { | ||||
1103 | 128 | 91µs | $attr->{$key} = $val; | ||
1104 | } | ||||
1105 | } | ||||
1106 | |||||
1107 | } | ||||
1108 | |||||
1109 | |||||
1110 | # Turn arrayrefs into hashrefs if key fields present | ||||
1111 | |||||
1112 | 52 | 79µs | if($self->{opt}->{keyattr}) { | ||
1113 | while(($key,$val) = each %$attr) { | ||||
1114 | 192 | 463µs | 206 | 382µs | if(defined($val) and UNIVERSAL::isa($val, 'ARRAY')) { # spent 241µs making 14 calls to XML::Simple::array_to_hash, avg 17µs/call
# spent 141µs making 192 calls to UNIVERSAL::isa, avg 734ns/call |
1115 | $attr->{$key} = $self->array_to_hash($key, $val); | ||||
1116 | } | ||||
1117 | } | ||||
1118 | } | ||||
1119 | |||||
1120 | |||||
1121 | # disintermediate grouped tags | ||||
1122 | |||||
1123 | 52 | 11µs | if($self->{opt}->{grouptags}) { | ||
1124 | while(my($key, $val) = each(%$attr)) { | ||||
1125 | next unless(UNIVERSAL::isa($val, 'HASH') and (keys %$val == 1)); | ||||
1126 | next unless(exists($self->{opt}->{grouptags}->{$key})); | ||||
1127 | |||||
1128 | my($child_key, $child_val) = %$val; | ||||
1129 | |||||
1130 | if($self->{opt}->{grouptags}->{$key} eq $child_key) { | ||||
1131 | $attr->{$key}= $child_val; | ||||
1132 | } | ||||
1133 | } | ||||
1134 | } | ||||
1135 | |||||
1136 | |||||
1137 | # Fold hashes containing a single anonymous array up into just the array | ||||
1138 | |||||
1139 | 52 | 12µs | my $count = scalar keys %$attr; | ||
1140 | 52 | 7µs | if($count == 1 | ||
1141 | and exists $attr->{anon} | ||||
1142 | and UNIVERSAL::isa($attr->{anon}, 'ARRAY') | ||||
1143 | ) { | ||||
1144 | return($attr->{anon}); | ||||
1145 | } | ||||
1146 | |||||
1147 | |||||
1148 | # Do the right thing if hash is empty, otherwise just return it | ||||
1149 | |||||
1150 | 52 | 8µs | if(!%$attr and exists($self->{opt}->{suppressempty})) { | ||
1151 | if(defined($self->{opt}->{suppressempty}) and | ||||
1152 | $self->{opt}->{suppressempty} eq '') { | ||||
1153 | return(''); | ||||
1154 | } | ||||
1155 | return(undef); | ||||
1156 | } | ||||
1157 | |||||
1158 | |||||
1159 | # Roll up named elements with named nested 'value' attributes | ||||
1160 | |||||
1161 | 52 | 9µs | if($self->{opt}->{valueattr}) { | ||
1162 | while(my($key, $val) = each(%$attr)) { | ||||
1163 | next unless($self->{opt}->{valueattr}->{$key}); | ||||
1164 | next unless(UNIVERSAL::isa($val, 'HASH') and (keys %$val == 1)); | ||||
1165 | my($k) = keys %$val; | ||||
1166 | next unless($k eq $self->{opt}->{valueattr}->{$key}); | ||||
1167 | $attr->{$key} = $val->{$k}; | ||||
1168 | } | ||||
1169 | } | ||||
1170 | |||||
1171 | 52 | 61µs | return($attr) | ||
1172 | |||||
1173 | } | ||||
1174 | |||||
1175 | |||||
1176 | ############################################################################## | ||||
1177 | # Method: set_var() | ||||
1178 | # | ||||
1179 | # Called when a variable definition is encountered in the XML. (A variable | ||||
1180 | # definition looks like <element attrname="name">value</element> where attrname | ||||
1181 | # matches the varattr setting). | ||||
1182 | # | ||||
1183 | |||||
1184 | sub set_var { | ||||
1185 | my($self, $name, $value) = @_; | ||||
1186 | |||||
1187 | $self->{_var_values}->{$name} = $value; | ||||
1188 | } | ||||
1189 | |||||
1190 | |||||
1191 | ############################################################################## | ||||
1192 | # Method: get_var() | ||||
1193 | # | ||||
1194 | # Called during variable substitution to get the value for the named variable. | ||||
1195 | # | ||||
1196 | |||||
1197 | sub get_var { | ||||
1198 | my($self, $name) = @_; | ||||
1199 | |||||
1200 | my $value = $self->{_var_values}->{$name}; | ||||
1201 | return $value if(defined($value)); | ||||
1202 | |||||
1203 | return '${' . $name . '}'; | ||||
1204 | } | ||||
1205 | |||||
1206 | |||||
1207 | ############################################################################## | ||||
1208 | # Method: normalise_space() | ||||
1209 | # | ||||
1210 | # Strips leading and trailing whitespace and collapses sequences of whitespace | ||||
1211 | # characters to a single space. | ||||
1212 | # | ||||
1213 | |||||
1214 | sub normalise_space { | ||||
1215 | my($self, $text) = @_; | ||||
1216 | |||||
1217 | $text =~ s/^\s+//s; | ||||
1218 | $text =~ s/\s+$//s; | ||||
1219 | $text =~ s/\s\s+/ /sg; | ||||
1220 | |||||
1221 | return $text; | ||||
1222 | } | ||||
1223 | |||||
1224 | |||||
1225 | ############################################################################## | ||||
1226 | # Method: array_to_hash() | ||||
1227 | # | ||||
1228 | # Helper routine for collapse(). | ||||
1229 | # Attempts to 'fold' an array of hashes into an hash of hashes. Returns a | ||||
1230 | # reference to the hash on success or the original array if folding is | ||||
1231 | # not possible. Behaviour is controlled by 'keyattr' option. | ||||
1232 | # | ||||
1233 | |||||
1234 | # spent 241µs (200+41) within XML::Simple::array_to_hash which was called 14 times, avg 17µs/call:
# 14 times (200µs+41µs) by XML::Simple::collapse at line 1114, avg 17µs/call | ||||
1235 | 14 | 3µs | my $self = shift; | ||
1236 | 14 | 3µs | my $name = shift; | ||
1237 | 14 | 1µs | my $arrayref = shift; | ||
1238 | |||||
1239 | 14 | 10µs | 14 | 12µs | my $hashref = $self->new_hashref; # spent 12µs making 14 calls to XML::Simple::new_hashref, avg 850ns/call |
1240 | |||||
1241 | 14 | 1µs | my($i, $key, $val, $flag); | ||
1242 | |||||
1243 | |||||
1244 | # Handle keyattr => { .... } | ||||
1245 | |||||
1246 | 14 | 9µs | if(ref($self->{opt}->{keyattr}) eq 'HASH') { | ||
1247 | return($arrayref) unless(exists($self->{opt}->{keyattr}->{$name})); | ||||
1248 | ($key, $flag) = @{$self->{opt}->{keyattr}->{$name}}; | ||||
1249 | for($i = 0; $i < @$arrayref; $i++) { | ||||
1250 | if(UNIVERSAL::isa($arrayref->[$i], 'HASH') and | ||||
1251 | exists($arrayref->[$i]->{$key}) | ||||
1252 | ) { | ||||
1253 | $val = $arrayref->[$i]->{$key}; | ||||
1254 | if(ref($val)) { | ||||
1255 | $self->die_or_warn("<$name> element has non-scalar '$key' key attribute"); | ||||
1256 | return($arrayref); | ||||
1257 | } | ||||
1258 | $val = $self->normalise_space($val) | ||||
1259 | if($self->{opt}->{normalisespace} == 1); | ||||
1260 | $self->die_or_warn("<$name> element has non-unique value in '$key' key attribute: $val") | ||||
1261 | if(exists($hashref->{$val})); | ||||
1262 | $hashref->{$val} = $self->new_hashref( %{$arrayref->[$i]} ); | ||||
1263 | $hashref->{$val}->{"-$key"} = $hashref->{$val}->{$key} if($flag eq '-'); | ||||
1264 | delete $hashref->{$val}->{$key} unless($flag eq '+'); | ||||
1265 | } | ||||
1266 | else { | ||||
1267 | $self->die_or_warn("<$name> element has no '$key' key attribute"); | ||||
1268 | return($arrayref); | ||||
1269 | } | ||||
1270 | } | ||||
1271 | } | ||||
1272 | |||||
1273 | |||||
1274 | # Or assume keyattr => [ .... ] | ||||
1275 | |||||
1276 | else { | ||||
1277 | my $default_keys = | ||||
1278 | 14 | 23µs | join(',', @DefKeyAttr) eq join(',', @{$self->{opt}->{keyattr}}); | ||
1279 | |||||
1280 | 14 | 9µs | ELEMENT: for($i = 0; $i < @$arrayref; $i++) { | ||
1281 | 20 | 55µs | 20 | 8µs | return($arrayref) unless(UNIVERSAL::isa($arrayref->[$i], 'HASH')); # spent 8µs making 20 calls to UNIVERSAL::isa, avg 420ns/call |
1282 | |||||
1283 | 18 | 9µs | foreach $key (@{$self->{opt}->{keyattr}}) { | ||
1284 | 18 | 9µs | if(defined($arrayref->[$i]->{$key})) { | ||
1285 | 12 | 3µs | $val = $arrayref->[$i]->{$key}; | ||
1286 | 12 | 1µs | if(ref($val)) { | ||
1287 | $self->die_or_warn("<$name> element has non-scalar '$key' key attribute") | ||||
1288 | if not $default_keys; | ||||
1289 | return($arrayref); | ||||
1290 | } | ||||
1291 | 12 | 3µs | $val = $self->normalise_space($val) | ||
1292 | if($self->{opt}->{normalisespace} == 1); | ||||
1293 | 12 | 2µs | $self->die_or_warn("<$name> element has non-unique value in '$key' key attribute: $val") | ||
1294 | if(exists($hashref->{$val})); | ||||
1295 | 12 | 23µs | 12 | 20µs | $hashref->{$val} = $self->new_hashref( %{$arrayref->[$i]} ); # spent 20µs making 12 calls to XML::Simple::new_hashref, avg 2µs/call |
1296 | 12 | 5µs | delete $hashref->{$val}->{$key}; | ||
1297 | 12 | 4µs | next ELEMENT; | ||
1298 | } | ||||
1299 | } | ||||
1300 | |||||
1301 | 6 | 12µs | return($arrayref); # No keyfield matched | ||
1302 | } | ||||
1303 | } | ||||
1304 | |||||
1305 | # collapse any hashes which now only have a 'content' key | ||||
1306 | |||||
1307 | 6 | 2µs | if($self->{opt}->{collapseagain}) { | ||
1308 | $hashref = $self->collapse_content($hashref); | ||||
1309 | } | ||||
1310 | |||||
1311 | 6 | 7µs | return($hashref); | ||
1312 | } | ||||
1313 | |||||
1314 | |||||
1315 | ############################################################################## | ||||
1316 | # Method: die_or_warn() | ||||
1317 | # | ||||
1318 | # Takes a diagnostic message and does one of three things: | ||||
1319 | # 1. dies if strict mode is enabled | ||||
1320 | # 2. warns if warnings are enabled but strict mode is not | ||||
1321 | # 3. ignores message and returns silently if neither strict mode nor warnings | ||||
1322 | # are enabled | ||||
1323 | # | ||||
1324 | # Option 2 looks at the global warnings variable $^W - which is not really | ||||
1325 | # appropriate in the modern world of lexical warnings - TODO: Fix | ||||
1326 | |||||
1327 | sub die_or_warn { | ||||
1328 | my $self = shift; | ||||
1329 | my $msg = shift; | ||||
1330 | |||||
1331 | croak $msg if($self->{opt}->{strictmode}); | ||||
1332 | carp "Warning: $msg" if($^W); | ||||
1333 | } | ||||
1334 | |||||
1335 | |||||
1336 | ############################################################################## | ||||
1337 | # Method: new_hashref() | ||||
1338 | # | ||||
1339 | # This is a hook routine for overriding in a sub-class. Some people believe | ||||
1340 | # that using Tie::IxHash here will solve order-loss problems. | ||||
1341 | # | ||||
1342 | |||||
1343 | sub new_hashref { | ||||
1344 | 26 | 3µs | my $self = shift; | ||
1345 | |||||
1346 | 26 | 48µs | return { @_ }; | ||
1347 | } | ||||
1348 | |||||
1349 | |||||
1350 | ############################################################################## | ||||
1351 | # Method: collapse_content() | ||||
1352 | # | ||||
1353 | # Helper routine for array_to_hash | ||||
1354 | # | ||||
1355 | # Arguments expected are: | ||||
1356 | # - an XML::Simple object | ||||
1357 | # - a hasref | ||||
1358 | # the hashref is a former array, turned into a hash by array_to_hash because | ||||
1359 | # of the presence of key attributes | ||||
1360 | # at this point collapse_content avoids over-complicated structures like | ||||
1361 | # dir => { libexecdir => { content => '$exec_prefix/libexec' }, | ||||
1362 | # localstatedir => { content => '$prefix' }, | ||||
1363 | # } | ||||
1364 | # into | ||||
1365 | # dir => { libexecdir => '$exec_prefix/libexec', | ||||
1366 | # localstatedir => '$prefix', | ||||
1367 | # } | ||||
1368 | |||||
1369 | sub collapse_content { | ||||
1370 | my $self = shift; | ||||
1371 | my $hashref = shift; | ||||
1372 | |||||
1373 | my $contentkey = $self->{opt}->{contentkey}; | ||||
1374 | |||||
1375 | # first go through the values,checking that they are fit to collapse | ||||
1376 | foreach my $val (values %$hashref) { | ||||
1377 | return $hashref unless ( (ref($val) eq 'HASH') | ||||
1378 | and (keys %$val == 1) | ||||
1379 | and (exists $val->{$contentkey}) | ||||
1380 | ); | ||||
1381 | } | ||||
1382 | |||||
1383 | # now collapse them | ||||
1384 | foreach my $key (keys %$hashref) { | ||||
1385 | $hashref->{$key}= $hashref->{$key}->{$contentkey}; | ||||
1386 | } | ||||
1387 | |||||
1388 | return $hashref; | ||||
1389 | } | ||||
1390 | |||||
1391 | |||||
1392 | ############################################################################## | ||||
1393 | # Method: value_to_xml() | ||||
1394 | # | ||||
1395 | # Helper routine for XMLout() - recurses through a data structure building up | ||||
1396 | # and returning an XML representation of that structure as a string. | ||||
1397 | # | ||||
1398 | # Arguments expected are: | ||||
1399 | # - the data structure to be encoded (usually a reference) | ||||
1400 | # - the XML tag name to use for this item | ||||
1401 | # - a string of spaces for use as the current indent level | ||||
1402 | # | ||||
1403 | |||||
1404 | sub value_to_xml { | ||||
1405 | my $self = shift;; | ||||
1406 | |||||
1407 | |||||
1408 | # Grab the other arguments | ||||
1409 | |||||
1410 | my($ref, $name, $indent) = @_; | ||||
1411 | |||||
1412 | my $named = (defined($name) and $name ne '' ? 1 : 0); | ||||
1413 | |||||
1414 | my $nl = "\n"; | ||||
1415 | |||||
1416 | my $is_root = $indent eq '' ? 1 : 0; # Warning, dirty hack! | ||||
1417 | if($self->{opt}->{noindent}) { | ||||
1418 | $indent = ''; | ||||
1419 | $nl = ''; | ||||
1420 | } | ||||
1421 | |||||
1422 | |||||
1423 | # Convert to XML | ||||
1424 | |||||
1425 | if(ref($ref)) { | ||||
1426 | croak "circular data structures not supported" | ||||
1427 | if(grep($_ == $ref, @{$self->{_ancestors}})); | ||||
1428 | push @{$self->{_ancestors}}, $ref; | ||||
1429 | } | ||||
1430 | else { | ||||
1431 | if($named) { | ||||
1432 | return(join('', | ||||
1433 | $indent, '<', $name, '>', | ||||
1434 | ($self->{opt}->{noescape} ? $ref : $self->escape_value($ref)), | ||||
1435 | '</', $name, ">", $nl | ||||
1436 | )); | ||||
1437 | } | ||||
1438 | else { | ||||
1439 | return("$ref$nl"); | ||||
1440 | } | ||||
1441 | } | ||||
1442 | |||||
1443 | |||||
1444 | # Unfold hash to array if possible | ||||
1445 | |||||
1446 | if(UNIVERSAL::isa($ref, 'HASH') # It is a hash | ||||
1447 | and keys %$ref # and it's not empty | ||||
1448 | and $self->{opt}->{keyattr} # and folding is enabled | ||||
1449 | and !$is_root # and its not the root element | ||||
1450 | ) { | ||||
1451 | $ref = $self->hash_to_array($name, $ref); | ||||
1452 | } | ||||
1453 | |||||
1454 | |||||
1455 | my @result = (); | ||||
1456 | my($key, $value); | ||||
1457 | |||||
1458 | |||||
1459 | # Handle hashrefs | ||||
1460 | |||||
1461 | if(UNIVERSAL::isa($ref, 'HASH')) { | ||||
1462 | |||||
1463 | # Reintermediate grouped values if applicable | ||||
1464 | |||||
1465 | if($self->{opt}->{grouptags}) { | ||||
1466 | $ref = $self->copy_hash($ref); | ||||
1467 | while(my($key, $val) = each %$ref) { | ||||
1468 | if($self->{opt}->{grouptags}->{$key}) { | ||||
1469 | $ref->{$key} = $self->new_hashref( | ||||
1470 | $self->{opt}->{grouptags}->{$key} => $val | ||||
1471 | ); | ||||
1472 | } | ||||
1473 | } | ||||
1474 | } | ||||
1475 | |||||
1476 | |||||
1477 | # Scan for namespace declaration attributes | ||||
1478 | |||||
1479 | my $nsdecls = ''; | ||||
1480 | my $default_ns_uri; | ||||
1481 | if($self->{nsup}) { | ||||
1482 | $ref = $self->copy_hash($ref); | ||||
1483 | $self->{nsup}->push_context(); | ||||
1484 | |||||
1485 | # Look for default namespace declaration first | ||||
1486 | |||||
1487 | if(exists($ref->{xmlns})) { | ||||
1488 | $self->{nsup}->declare_prefix('', $ref->{xmlns}); | ||||
1489 | $nsdecls .= qq( xmlns="$ref->{xmlns}"); | ||||
1490 | delete($ref->{xmlns}); | ||||
1491 | } | ||||
1492 | $default_ns_uri = $self->{nsup}->get_uri(''); | ||||
1493 | |||||
1494 | |||||
1495 | # Then check all the other keys | ||||
1496 | |||||
1497 | foreach my $qname (keys(%$ref)) { | ||||
1498 | my($uri, $lname) = $self->{nsup}->parse_jclark_notation($qname); | ||||
1499 | if($uri) { | ||||
1500 | if($uri eq $xmlns_ns) { | ||||
1501 | $self->{nsup}->declare_prefix($lname, $ref->{$qname}); | ||||
1502 | $nsdecls .= qq( xmlns:$lname="$ref->{$qname}"); | ||||
1503 | delete($ref->{$qname}); | ||||
1504 | } | ||||
1505 | } | ||||
1506 | } | ||||
1507 | |||||
1508 | # Translate any remaining Clarkian names | ||||
1509 | |||||
1510 | foreach my $qname (keys(%$ref)) { | ||||
1511 | my($uri, $lname) = $self->{nsup}->parse_jclark_notation($qname); | ||||
1512 | if($uri) { | ||||
1513 | if($default_ns_uri and $uri eq $default_ns_uri) { | ||||
1514 | $ref->{$lname} = $ref->{$qname}; | ||||
1515 | delete($ref->{$qname}); | ||||
1516 | } | ||||
1517 | else { | ||||
1518 | my $prefix = $self->{nsup}->get_prefix($uri); | ||||
1519 | unless($prefix) { | ||||
1520 | # $self->{nsup}->declare_prefix(undef, $uri); | ||||
1521 | # $prefix = $self->{nsup}->get_prefix($uri); | ||||
1522 | $prefix = $self->{ns_prefix}++; | ||||
1523 | $self->{nsup}->declare_prefix($prefix, $uri); | ||||
1524 | $nsdecls .= qq( xmlns:$prefix="$uri"); | ||||
1525 | } | ||||
1526 | $ref->{"$prefix:$lname"} = $ref->{$qname}; | ||||
1527 | delete($ref->{$qname}); | ||||
1528 | } | ||||
1529 | } | ||||
1530 | } | ||||
1531 | } | ||||
1532 | |||||
1533 | |||||
1534 | my @nested = (); | ||||
1535 | my $text_content = undef; | ||||
1536 | if($named) { | ||||
1537 | push @result, $indent, '<', $name, $nsdecls; | ||||
1538 | } | ||||
1539 | |||||
1540 | if(keys %$ref) { | ||||
1541 | my $first_arg = 1; | ||||
1542 | foreach my $key ($self->sorted_keys($name, $ref)) { | ||||
1543 | my $value = $ref->{$key}; | ||||
1544 | next if(substr($key, 0, 1) eq '-'); | ||||
1545 | if(!defined($value)) { | ||||
1546 | next if $self->{opt}->{suppressempty}; | ||||
1547 | unless(exists($self->{opt}->{suppressempty}) | ||||
1548 | and !defined($self->{opt}->{suppressempty}) | ||||
1549 | ) { | ||||
1550 | carp 'Use of uninitialized value' if($^W); | ||||
1551 | } | ||||
1552 | if($key eq $self->{opt}->{contentkey}) { | ||||
1553 | $text_content = ''; | ||||
1554 | } | ||||
1555 | else { | ||||
1556 | $value = exists($self->{opt}->{suppressempty}) ? {} : ''; | ||||
1557 | } | ||||
1558 | } | ||||
1559 | |||||
1560 | if(!ref($value) | ||||
1561 | and $self->{opt}->{valueattr} | ||||
1562 | and $self->{opt}->{valueattr}->{$key} | ||||
1563 | ) { | ||||
1564 | $value = $self->new_hashref( | ||||
1565 | $self->{opt}->{valueattr}->{$key} => $value | ||||
1566 | ); | ||||
1567 | } | ||||
1568 | |||||
1569 | if(ref($value) or $self->{opt}->{noattr}) { | ||||
1570 | push @nested, | ||||
1571 | $self->value_to_xml($value, $key, "$indent "); | ||||
1572 | } | ||||
1573 | else { | ||||
1574 | $value = $self->escape_value($value) unless($self->{opt}->{noescape}); | ||||
1575 | if($key eq $self->{opt}->{contentkey}) { | ||||
1576 | $text_content = $value; | ||||
1577 | } | ||||
1578 | else { | ||||
1579 | push @result, "\n$indent " . ' ' x length($name) | ||||
1580 | if($self->{opt}->{attrindent} and !$first_arg); | ||||
1581 | push @result, ' ', $key, '="', $value , '"'; | ||||
1582 | $first_arg = 0; | ||||
1583 | } | ||||
1584 | } | ||||
1585 | } | ||||
1586 | } | ||||
1587 | else { | ||||
1588 | $text_content = ''; | ||||
1589 | } | ||||
1590 | |||||
1591 | if(@nested or defined($text_content)) { | ||||
1592 | if($named) { | ||||
1593 | push @result, ">"; | ||||
1594 | if(defined($text_content)) { | ||||
1595 | push @result, $text_content; | ||||
1596 | $nested[0] =~ s/^\s+// if(@nested); | ||||
1597 | } | ||||
1598 | else { | ||||
1599 | push @result, $nl; | ||||
1600 | } | ||||
1601 | if(@nested) { | ||||
1602 | push @result, @nested, $indent; | ||||
1603 | } | ||||
1604 | push @result, '</', $name, ">", $nl; | ||||
1605 | } | ||||
1606 | else { | ||||
1607 | push @result, @nested; # Special case if no root elements | ||||
1608 | } | ||||
1609 | } | ||||
1610 | else { | ||||
1611 | push @result, " />", $nl; | ||||
1612 | } | ||||
1613 | $self->{nsup}->pop_context() if($self->{nsup}); | ||||
1614 | } | ||||
1615 | |||||
1616 | |||||
1617 | # Handle arrayrefs | ||||
1618 | |||||
1619 | elsif(UNIVERSAL::isa($ref, 'ARRAY')) { | ||||
1620 | foreach $value (@$ref) { | ||||
1621 | next if !defined($value) and $self->{opt}->{suppressempty}; | ||||
1622 | if(!ref($value)) { | ||||
1623 | push @result, | ||||
1624 | $indent, '<', $name, '>', | ||||
1625 | ($self->{opt}->{noescape} ? $value : $self->escape_value($value)), | ||||
1626 | '</', $name, ">$nl"; | ||||
1627 | } | ||||
1628 | elsif(UNIVERSAL::isa($value, 'HASH')) { | ||||
1629 | push @result, $self->value_to_xml($value, $name, $indent); | ||||
1630 | } | ||||
1631 | else { | ||||
1632 | push @result, | ||||
1633 | $indent, '<', $name, ">$nl", | ||||
1634 | $self->value_to_xml($value, 'anon', "$indent "), | ||||
1635 | $indent, '</', $name, ">$nl"; | ||||
1636 | } | ||||
1637 | } | ||||
1638 | } | ||||
1639 | |||||
1640 | else { | ||||
1641 | croak "Can't encode a value of type: " . ref($ref); | ||||
1642 | } | ||||
1643 | |||||
1644 | |||||
1645 | pop @{$self->{_ancestors}} if(ref($ref)); | ||||
1646 | |||||
1647 | return(join('', @result)); | ||||
1648 | } | ||||
1649 | |||||
1650 | |||||
1651 | ############################################################################## | ||||
1652 | # Method: sorted_keys() | ||||
1653 | # | ||||
1654 | # Returns the keys of the referenced hash sorted into alphabetical order, but | ||||
1655 | # with the 'key' key (as in KeyAttr) first, if there is one. | ||||
1656 | # | ||||
1657 | |||||
1658 | sub sorted_keys { | ||||
1659 | my($self, $name, $ref) = @_; | ||||
1660 | |||||
1661 | return keys %$ref if $self->{opt}->{nosort}; | ||||
1662 | |||||
1663 | my %hash = %$ref; | ||||
1664 | my $keyattr = $self->{opt}->{keyattr}; | ||||
1665 | |||||
1666 | my @key; | ||||
1667 | |||||
1668 | if(ref $keyattr eq 'HASH') { | ||||
1669 | if(exists $keyattr->{$name} and exists $hash{$keyattr->{$name}->[0]}) { | ||||
1670 | push @key, $keyattr->{$name}->[0]; | ||||
1671 | delete $hash{$keyattr->{$name}->[0]}; | ||||
1672 | } | ||||
1673 | } | ||||
1674 | elsif(ref $keyattr eq 'ARRAY') { | ||||
1675 | foreach (@{$keyattr}) { | ||||
1676 | if(exists $hash{$_}) { | ||||
1677 | push @key, $_; | ||||
1678 | delete $hash{$_}; | ||||
1679 | last; | ||||
1680 | } | ||||
1681 | } | ||||
1682 | } | ||||
1683 | |||||
1684 | return(@key, sort keys %hash); | ||||
1685 | } | ||||
1686 | |||||
1687 | ############################################################################## | ||||
1688 | # Method: escape_value() | ||||
1689 | # | ||||
1690 | # Helper routine for automatically escaping values for XMLout(). | ||||
1691 | # Expects a scalar data value. Returns escaped version. | ||||
1692 | # | ||||
1693 | |||||
1694 | sub escape_value { | ||||
1695 | my($self, $data) = @_; | ||||
1696 | |||||
1697 | return '' unless(defined($data)); | ||||
1698 | |||||
1699 | $data =~ s/&/&/sg; | ||||
1700 | $data =~ s/</</sg; | ||||
1701 | $data =~ s/>/>/sg; | ||||
1702 | $data =~ s/"/"/sg; | ||||
1703 | |||||
1704 | my $level = $self->{opt}->{numericescape} or return $data; | ||||
1705 | |||||
1706 | return $self->numeric_escape($data, $level); | ||||
1707 | } | ||||
1708 | |||||
1709 | sub numeric_escape { | ||||
1710 | my($self, $data, $level) = @_; | ||||
1711 | |||||
1712 | 2 | 2.19ms | 2 | 1.45ms | # spent 1.45ms (1.45+3µs) within XML::Simple::BEGIN@1712 which was called:
# once (1.45ms+3µs) by C4::Context::BEGIN@102 at line 1712 # spent 1.45ms making 1 call to XML::Simple::BEGIN@1712
# spent 3µs making 1 call to utf8::import |
1713 | |||||
1714 | if($self->{opt}->{numericescape} eq '2') { | ||||
1715 | $data =~ s/([^\x00-\x7F])/'&#' . ord($1) . ';'/gse; | ||||
1716 | } | ||||
1717 | else { | ||||
1718 | $data =~ s/([^\x00-\xFF])/'&#' . ord($1) . ';'/gse; | ||||
1719 | } | ||||
1720 | |||||
1721 | return $data; | ||||
1722 | } | ||||
1723 | |||||
1724 | |||||
1725 | ############################################################################## | ||||
1726 | # Method: hash_to_array() | ||||
1727 | # | ||||
1728 | # Helper routine for value_to_xml(). | ||||
1729 | # Attempts to 'unfold' a hash of hashes into an array of hashes. Returns a | ||||
1730 | # reference to the array on success or the original hash if unfolding is | ||||
1731 | # not possible. | ||||
1732 | # | ||||
1733 | |||||
1734 | sub hash_to_array { | ||||
1735 | my $self = shift; | ||||
1736 | my $parent = shift; | ||||
1737 | my $hashref = shift; | ||||
1738 | |||||
1739 | my $arrayref = []; | ||||
1740 | |||||
1741 | my($key, $value); | ||||
1742 | |||||
1743 | my @keys = $self->{opt}->{nosort} ? keys %$hashref : sort keys %$hashref; | ||||
1744 | foreach $key (@keys) { | ||||
1745 | $value = $hashref->{$key}; | ||||
1746 | return($hashref) unless(UNIVERSAL::isa($value, 'HASH')); | ||||
1747 | |||||
1748 | if(ref($self->{opt}->{keyattr}) eq 'HASH') { | ||||
1749 | return($hashref) unless(defined($self->{opt}->{keyattr}->{$parent})); | ||||
1750 | push @$arrayref, $self->copy_hash( | ||||
1751 | $value, $self->{opt}->{keyattr}->{$parent}->[0] => $key | ||||
1752 | ); | ||||
1753 | } | ||||
1754 | else { | ||||
1755 | push(@$arrayref, { $self->{opt}->{keyattr}->[0] => $key, %$value }); | ||||
1756 | } | ||||
1757 | } | ||||
1758 | |||||
1759 | return($arrayref); | ||||
1760 | } | ||||
1761 | |||||
1762 | |||||
1763 | ############################################################################## | ||||
1764 | # Method: copy_hash() | ||||
1765 | # | ||||
1766 | # Helper routine for hash_to_array(). When unfolding a hash of hashes into | ||||
1767 | # an array of hashes, we need to copy the key from the outer hash into the | ||||
1768 | # inner hash. This routine makes a copy of the original hash so we don't | ||||
1769 | # destroy the original data structure. You might wish to override this | ||||
1770 | # method if you're using tied hashes and don't want them to get untied. | ||||
1771 | # | ||||
1772 | |||||
1773 | sub copy_hash { | ||||
1774 | my($self, $orig, @extra) = @_; | ||||
1775 | |||||
1776 | return { @extra, %$orig }; | ||||
1777 | } | ||||
1778 | |||||
1779 | ############################################################################## | ||||
1780 | # Methods required for building trees from SAX events | ||||
1781 | ############################################################################## | ||||
1782 | |||||
1783 | # spent 11µs within XML::Simple::start_document which was called 2 times, avg 6µs/call:
# 2 times (11µs+0s) by XML::SAX::Base::start_document at line 1265 of XML/SAX/Base.pm, avg 6µs/call | ||||
1784 | 2 | 700ns | my $self = shift; | ||
1785 | |||||
1786 | 2 | 1µs | $self->handle_options('in') unless($self->{opt}); | ||
1787 | |||||
1788 | 2 | 1µs | $self->{lists} = []; | ||
1789 | 2 | 10µs | $self->{curlist} = $self->{tree} = []; | ||
1790 | } | ||||
1791 | |||||
1792 | |||||
1793 | # spent 620µs within XML::Simple::start_element which was called 142 times, avg 4µs/call:
# 140 times (603µs+0s) by XML::SAX::Base::__ANON__[/usr/share/perl5/XML/SAX/Base.pm:298] at line 298 of XML/SAX/Base.pm, avg 4µs/call
# 2 times (17µs+0s) by XML::SAX::Base::start_element at line 299 of XML/SAX/Base.pm, avg 8µs/call | ||||
1794 | 142 | 16µs | my $self = shift; | ||
1795 | 142 | 11µs | my $element = shift; | ||
1796 | |||||
1797 | 142 | 26µs | my $name = $element->{Name}; | ||
1798 | 142 | 32µs | if($self->{opt}->{nsexpand}) { | ||
1799 | $name = $element->{LocalName} || ''; | ||||
1800 | if($element->{NamespaceURI}) { | ||||
1801 | $name = '{' . $element->{NamespaceURI} . '}' . $name; | ||||
1802 | } | ||||
1803 | } | ||||
1804 | 142 | 31µs | my $attributes = {}; | ||
1805 | 142 | 44µs | if($element->{Attributes}) { # Might be undef | ||
1806 | 142 | 129µs | foreach my $attr (values %{$element->{Attributes}}) { | ||
1807 | 56 | 30µs | if($self->{opt}->{nsexpand}) { | ||
1808 | my $name = $attr->{LocalName} || ''; | ||||
1809 | if($attr->{NamespaceURI}) { | ||||
1810 | $name = '{' . $attr->{NamespaceURI} . '}' . $name | ||||
1811 | } | ||||
1812 | $name = 'xmlns' if($name eq $bad_def_ns_jcn); | ||||
1813 | $attributes->{$name} = $attr->{Value}; | ||||
1814 | } | ||||
1815 | else { | ||||
1816 | 56 | 35µs | $attributes->{$attr->{Name}} = $attr->{Value}; | ||
1817 | } | ||||
1818 | } | ||||
1819 | } | ||||
1820 | 142 | 48µs | my $newlist = [ $attributes ]; | ||
1821 | 142 | 43µs | push @{ $self->{lists} }, $self->{curlist}; | ||
1822 | 142 | 70µs | push @{ $self->{curlist} }, $name => $newlist; | ||
1823 | 142 | 247µs | $self->{curlist} = $newlist; | ||
1824 | } | ||||
1825 | |||||
1826 | |||||
1827 | # spent 1.05ms within XML::Simple::characters which was called 478 times, avg 2µs/call:
# 476 times (1.03ms+0s) by XML::SAX::Base::__ANON__[/usr/share/perl5/XML/SAX/Base.pm:206] at line 206 of XML/SAX/Base.pm, avg 2µs/call
# 2 times (11µs+0s) by XML::SAX::Base::characters at line 207 of XML/SAX/Base.pm, avg 5µs/call | ||||
1828 | 478 | 56µs | my $self = shift; | ||
1829 | 478 | 36µs | my $chars = shift; | ||
1830 | |||||
1831 | 478 | 102µs | my $text = $chars->{Data}; | ||
1832 | 478 | 66µs | my $clist = $self->{curlist}; | ||
1833 | 478 | 94µs | my $pos = $#$clist; | ||
1834 | |||||
1835 | 478 | 1.05ms | if ($pos > 0 and $clist->[$pos - 1] eq '0') { | ||
1836 | $clist->[$pos] .= $text; | ||||
1837 | } | ||||
1838 | else { | ||||
1839 | 274 | 148µs | push @$clist, 0 => $text; | ||
1840 | } | ||||
1841 | } | ||||
1842 | |||||
1843 | |||||
1844 | # spent 112µs within XML::Simple::end_element which was called 142 times, avg 788ns/call:
# 140 times (108µs+0s) by XML::SAX::Base::__ANON__[/usr/share/perl5/XML/SAX/Base.pm:2207] at line 2207 of XML/SAX/Base.pm, avg 768ns/call
# 2 times (4µs+0s) by XML::SAX::Base::end_element at line 2208 of XML/SAX/Base.pm, avg 2µs/call | ||||
1845 | 142 | 16µs | my $self = shift; | ||
1846 | |||||
1847 | 142 | 251µs | $self->{curlist} = pop @{ $self->{lists} }; | ||
1848 | } | ||||
1849 | |||||
1850 | |||||
1851 | # spent 8µs within XML::Simple::end_document which was called 2 times, avg 4µs/call:
# 2 times (8µs+0s) by XML::SAX::Base::end_document at line 1449 of XML/SAX/Base.pm, avg 4µs/call | ||||
1852 | 2 | 900ns | my $self = shift; | ||
1853 | |||||
1854 | 2 | 2µs | delete($self->{curlist}); | ||
1855 | 2 | 700ns | delete($self->{lists}); | ||
1856 | |||||
1857 | 2 | 600ns | my $tree = $self->{tree}; | ||
1858 | 2 | 400ns | delete($self->{tree}); | ||
1859 | |||||
1860 | |||||
1861 | # Return tree as-is to XMLin() | ||||
1862 | |||||
1863 | 2 | 6µs | return($tree) if($self->{nocollapse}); | ||
1864 | |||||
1865 | |||||
1866 | # Or collapse it before returning it to SAX parser class | ||||
1867 | |||||
1868 | if($self->{opt}->{keeproot}) { | ||||
1869 | $tree = $self->collapse({}, @$tree); | ||||
1870 | } | ||||
1871 | else { | ||||
1872 | $tree = $self->collapse(@{$tree->[1]}); | ||||
1873 | } | ||||
1874 | |||||
1875 | if($self->{opt}->{datahandler}) { | ||||
1876 | return($self->{opt}->{datahandler}->($self, $tree)); | ||||
1877 | } | ||||
1878 | |||||
1879 | return($tree); | ||||
1880 | } | ||||
1881 | |||||
1882 | 1 | 1µs | *xml_in = \&XMLin; | ||
1883 | 1 | 200ns | *xml_out = \&XMLout; | ||
1884 | |||||
1885 | 1 | 10µs | 1; | ||
1886 | |||||
1887 | __END__ | ||||
# spent 9µs within XML::Simple::CORE:ftis which was called 2 times, avg 4µs/call:
# 2 times (9µs+0s) by XML::Simple::find_xml_file at line 943, avg 4µs/call | |||||
# spent 200µs within XML::Simple::CORE:match which was called 278 times, avg 719ns/call:
# 274 times (196µs+0s) by XML::Simple::collapse at line 1042, avg 717ns/call
# 2 times (2µs+0s) by XML::Simple::XMLin at line 192, avg 800ns/call
# once (2µs+0s) by XML::Simple::import at line 93
# once (300ns+0s) by XML::Simple::import at line 97 | |||||
sub XML::Simple::CORE:subst; # opcode |