| blib/lib/HTTP/WebTest/XMLParser.pm | |||
|---|---|---|---|
| Criterion | Covered | Total | % |
| statement | 19 | 32 | 59.3 |
| branch | n/a | ||
| condition | n/a | ||
| subroutine | 7 | 9 | 77.7 |
| pod | n/a | ||
| total | 26 | 41 | 63.4 |
| line | stmt | bran | cond | sub | pod | time | code |
|---|---|---|---|---|---|---|---|
| 1 | package HTTP::WebTest::XMLParser; | ||||||
| 2 | 3 | 3 | 9913 | use strict; | |||
| 3 | 7 | ||||||
| 3 | 90 | ||||||
| 3 | 3 | 3 | 2463 | use XML::SAX; | |||
| 3 | 28965 | ||||||
| 3 | 147 | ||||||
| 4 | |||||||
| 5 | 3 | 3 | 25 | use vars qw($VERSION); | |||
| 3 | 9 | ||||||
| 3 | 648 | ||||||
| 6 | |||||||
| 7 | $VERSION = '1.00'; | ||||||
| 8 | |||||||
| 9 | my $webtest_definition_version = '1.0'; # NOTE: file lexical scope | ||||||
| 10 | |||||||
| 11 | =head1 NAME | ||||||
| 12 | |||||||
| 13 | HTTP::WebTest::XMLParser - Parse wtscript in XML representation. | ||||||
| 14 | |||||||
| 15 | =head1 SYNOPSIS | ||||||
| 16 | |||||||
| 17 | use HTTP::WebTest::XMLParser; | ||||||
| 18 | my ($tests, $opts) = HTTP::WebTest::XMLParser->parse($xmldata); | ||||||
| 19 | |||||||
| 20 | use HTTP::WebTest; | ||||||
| 21 | my $wt = new HTTP::WebTest; | ||||||
| 22 | $wt->run_tests($tests, $opts); | ||||||
| 23 | |||||||
| 24 | HTTP::WebTest::XMLParser->as_xml($tests, $opts, { nocode => 1 }); | ||||||
| 25 | |||||||
| 26 | =head1 DESCRIPTION | ||||||
| 27 | |||||||
| 28 | Parses a wtscript file in XML format and converts it to a set of test objects. | ||||||
| 29 | |||||||
| 30 | =head2 VERSION | ||||||
| 31 | |||||||
| 32 | $Revision: $ | ||||||
| 33 | |||||||
| 34 | =head1 XML SYNTAX | ||||||
| 35 | |||||||
| 36 | The xml format follows wtscript closely, with the following rules: | ||||||
| 37 | |||||||
| 38 | - the root element is |
||||||
| 39 | - global paramters are in a |
||||||
| 40 | - test definitions are in |
||||||
| 41 | - a list is represented by a |
||||||
| 42 | - a scalar param. is represented by a element | ||||||
| 43 | - a code segment is represented by a element |
||||||
| 44 | - named parameters are named throug a 'name' attribute | ||||||
| 45 | |||||||
| 46 | The DTD is available in 'scripts/webtest.dtd' from the distribition. | ||||||
| 47 | For examples see the test definitions in t/*xml from the distribution. | ||||||
| 48 | |||||||
| 49 | A conversion script from wtscript to XML is available in | ||||||
| 50 | 'scripts/testconversion' from the distribution. This script | ||||||
| 51 | also converts XML definitions from earlier alpha versions of | ||||||
| 52 | this module. | ||||||
| 53 | |||||||
| 54 | =head2 Example | ||||||
| 55 | |||||||
| 56 | This example is the equivalent of the same example for HTTP::WebTest | ||||||
| 57 | |||||||
| 58 | |||||||
| 59 | The definition of tests and params from the original example: | ||||||
| 60 | |||||||
| 61 | my $tests = [ | ||||||
| 62 | { test_name => 'Yahoo home page', | ||||||
| 63 | url => 'http://www.yahoo.com', | ||||||
| 64 | text_require => [ 'Quotations... ' ], |
||||||
| 65 | min_bytes => 13000, | ||||||
| 66 | max_bytes => 99000, | ||||||
| 67 | } | ||||||
| 68 | ]; | ||||||
| 69 | my $params = { mail_server => 'mailhost.mycompany.com', | ||||||
| 70 | mail_addresses => [ 'tester@mycompany.com' ], | ||||||
| 71 | mail => 'all', | ||||||
| 72 | ignore_case => 'yes', | ||||||
| 73 | }; | ||||||
| 74 | |||||||
| 75 | This Perl script tests Yahoo home page and sends full test | ||||||
| 76 | report to "tester@mycompany.com". | ||||||
| 77 | |||||||
| 78 | use HTTP::WebTest; | ||||||
| 79 | use HTTP::WebTest::XMLParser; | ||||||
| 80 | |||||||
| 81 | my $XML = <<"EOXML"; | ||||||
| 82 | |
||||||
| 83 | |
||||||
| 84 | yes | ||||||
| 85 | |
||||||
| 86 | tester@mycompany.com | ||||||
| 87 | |||||||
| 88 | mailhost.mycompany.com | ||||||
| 89 | all | ||||||
| 90 | |||||||
| 91 | |
||||||
| 92 | 13000 | ||||||
| 93 | 99000 | ||||||
| 94 | http://www.yahoo.com | ||||||
| 95 | Yahoo home page | ||||||
| 96 | |
||||||
| 97 | Quotations... ]]> |
||||||
| 98 | |||||||
| 99 | |||||||
| 100 | |||||||
| 101 | EOXML | ||||||
| 102 | |||||||
| 103 | my ($tests, $params) = HTTP::WebTest::XMLParser->parse($XML); | ||||||
| 104 | |||||||
| 105 | my $webtest = new HTTP::WebTest; | ||||||
| 106 | $webtest->run_tests($tests, $params); | ||||||
| 107 | |||||||
| 108 | =head1 CLASS METHODS | ||||||
| 109 | |||||||
| 110 | =head2 parse ($xmldata) | ||||||
| 111 | |||||||
| 112 | Parses wtscript in XML format passed in C<$xmldata> as string. | ||||||
| 113 | |||||||
| 114 | =head3 Returns | ||||||
| 115 | |||||||
| 116 | A list of two elements - a reference to an array that contains test | ||||||
| 117 | objects and a reference to a hash that contains test parameters. | ||||||
| 118 | |||||||
| 119 | =cut | ||||||
| 120 | |||||||
| 121 | sub parse { | ||||||
| 122 | 0 | 0 | my $class = shift; | ||||
| 123 | 0 | my $data = shift; | |||||
| 124 | |||||||
| 125 | 0 | my $filter = new WebTestFilter(); # see below | |||||
| 126 | 0 | my $p = XML::SAX::ParserFactory->parser(Handler => $filter); | |||||
| 127 | 0 | $p->parse_string($data); | |||||
| 128 | #FIXME: add $p->parse_string(" |
||||||
| 129 | 0 | my $cfg = $filter->finalize(); | |||||
| 130 | |||||||
| 131 | 0 | return($cfg->{tests}, $cfg->{params}); | |||||
| 132 | } | ||||||
| 133 | |||||||
| 134 | =head2 as_xml ($tests, $params, $opts) | ||||||
| 135 | |||||||
| 136 | Given a set of test parameters and global parameters, returns the XML | ||||||
| 137 | representation of the test script as a string. | ||||||
| 138 | |||||||
| 139 | The test definitions and parameters can be obtained from plain C |
||||||
| 140 | as parsed by L |
||||||
| 141 | |||||||
| 142 | =head3 Option nocode | ||||||
| 143 | |||||||
| 144 | Forces the replacement of C sections by dummy subroutines. |
||||||
| 145 | Example: | ||||||
| 146 | |||||||
| 147 | $xml = HTTP::WebTest::XMLParser->as_xml( | ||||||
| 148 | $tests, | ||||||
| 149 | $param, | ||||||
| 150 | { nocode => 1 } | ||||||
| 151 | ); | ||||||
| 152 | |||||||
| 153 | =head3 Returns | ||||||
| 154 | |||||||
| 155 | The test defintion in XML format. | ||||||
| 156 | |||||||
| 157 | =head1 BUGS | ||||||
| 158 | |||||||
| 159 | =head3 Method as_xml() | ||||||
| 160 | |||||||
| 161 | Any C references in the test object will be replaced by a |
||||||
| 162 | dummy subroutine if L |
||||||
| 163 | In order to make this more predictable, you can force this | ||||||
| 164 | behaviour by specifying option C |
||||||
| 165 | |||||||
| 166 | Lists of named parameters are internally stored as array with | ||||||
| 167 | an even number of elements, rather than a hash. | ||||||
| 168 | This has the purpose of preserving order of the parameters and | ||||||
| 169 | also allow more than one parameter with the same name. | ||||||
| 170 | When such a list is serialized back into XML, the list element | ||||||
| 171 | contains a list of anonymous parameters, one for each key and | ||||||
| 172 | value. | ||||||
| 173 | |||||||
| 174 | Original test definition: | ||||||
| 175 | |||||||
| 176 | |
||||||
| 177 | text/html,application/xml+html | ||||||
| 178 | deflate,gzip | ||||||
| 179 | |||||||
| 180 | |||||||
| 181 | Output as: | ||||||
| 182 | |||||||
| 183 | |
||||||
| 184 | Accept | ||||||
| 185 | text/html,application/xml+html | ||||||
| 186 | Accept-Encoding | ||||||
| 187 | deflate,gzip | ||||||
| 188 | |||||||
| 189 | |||||||
| 190 | Both versions are functionally equivalent (just like ',' | ||||||
| 191 | and '=>' notation are equivalent for Perl hashes). | ||||||
| 192 | |||||||
| 193 | =cut | ||||||
| 194 | |||||||
| 195 | sub as_xml { | ||||||
| 196 | 0 | 0 | my $class = shift; | ||||
| 197 | 0 | my ($tests, $params, $opt) = @_; | |||||
| 198 | |||||||
| 199 | 0 | my $writer = new WebTestWriter($opt); | |||||
| 200 | 0 | $writer->as_xml($tests, $params); | |||||
| 201 | } | ||||||
| 202 | |||||||
| 203 | =head1 COPYRIGHT | ||||||
| 204 | |||||||
| 205 | Copyright (c) 2002 - 2003 Johannes la Poutre. All rights reserved. | ||||||
| 206 | |||||||
| 207 | This program is free software; you can redistribute it and/or modify | ||||||
| 208 | it under the same terms as Perl itself. | ||||||
| 209 | |||||||
| 210 | =head1 SEE ALSO | ||||||
| 211 | |||||||
| 212 | L |
||||||
| 213 | |||||||
| 214 | L |
||||||
| 215 | |||||||
| 216 | L |
||||||
| 217 | |||||||
| 218 | Examples are in directory 't' from the distribution, the DTD and | ||||||
| 219 | utility scripts are in subdir 'scripts' from the distribution. | ||||||
| 220 | |||||||
| 221 | =cut | ||||||
| 222 | |||||||
| 223 | ################################################## SAX handler class ### | ||||||
| 224 | package WebTestFilter; | ||||||
| 225 | 3 | 3 | 17 | use strict; | |||
| 3 | 4 | ||||||
| 3 | 107 | ||||||
| 226 | 3 | 3 | 13 | use base qw(XML::SAX::Base); | |||
| 3 | 5 | ||||||
| 3 | 5659 | ||||||
| 227 | 3 | 3 | 60188 | use Carp qw(croak); | |||
| 3 | 9 | ||||||
| 3 | 222 | ||||||
| 228 | 3 | 3 | 11474 | use HTTP::WebTest::Utils qw(eval_in_playground make_sub_in_playground); | |||
| 0 | |||||||
| 0 | |||||||
| 229 | |||||||
| 230 | sub new { | ||||||
| 231 | my $class = shift; | ||||||
| 232 | # my %opt = @_; # parser options | ||||||
| 233 | my $self = {}; | ||||||
| 234 | $self->{tests} = [()]; # test definitions | ||||||
| 235 | $self->{params} = {}; # global params | ||||||
| 236 | $self->{stack} = {}; # stack for current test node | ||||||
| 237 | $self->{name} = ''; # current element name | ||||||
| 238 | $self->{context} = [()]; # XML element stack | ||||||
| 239 | return bless $self, $class; | ||||||
| 240 | } | ||||||
| 241 | |||||||
| 242 | sub characters { | ||||||
| 243 | my $self = shift; | ||||||
| 244 | my ($chars) = @_; | ||||||
| 245 | $self->{charbuf} .= $chars->{Data}; | ||||||
| 246 | } | ||||||
| 247 | |||||||
| 248 | sub start_element { | ||||||
| 249 | my $self = shift; | ||||||
| 250 | my ($elt) = @_; | ||||||
| 251 | my $element = $elt->{Name}; | ||||||
| 252 | my $parent = $self->{context}->[-1] || ''; | ||||||
| 253 | if (($parent eq 'param') || ($parent eq 'code')) { | ||||||
| 254 | $self->_croak(sprintf 'No child elements allowed for element "<%s/>"', $parent); | ||||||
| 255 | } | ||||||
| 256 | $self->{charbuf} = ''; # reset character buffer | ||||||
| 257 | # we have 4 relevant events: | ||||||
| 258 | # - param with name attribute | ||||||
| 259 | # - list context: pair of 2 scalars (preserve list order) | ||||||
| 260 | # - scalar context: hash (key, value) pair | ||||||
| 261 | # - param (unnamed) | ||||||
| 262 | # - list context: single value | ||||||
| 263 | # - named list | ||||||
| 264 | # - scalar context: named array (hash key, value = arrayref) | ||||||
| 265 | # - list (unnamed) | ||||||
| 266 | # - list context: (anonymous) arrayref | ||||||
| 267 | # character data is handled in end_element | ||||||
| 268 | my $name = $elt->{Attributes}->{'{}name'}->{Value}; | ||||||
| 269 | #printf "Elt: %s, Name: %s, Context: %s\n", $element, $name || '-', join('/', @{$self->{context}}); | ||||||
| 270 | if (($element eq 'param') || ($element eq 'code')) { | ||||||
| 271 | if (defined $name) { | ||||||
| 272 | if ($parent eq 'list') { # named param, list context | ||||||
| 273 | # push param name as list element | ||||||
| 274 | # character data handled in end_element | ||||||
| 275 | if (ref $self->{stack}->{$self->{name}}->[-1] eq 'ARRAY') { | ||||||
| 276 | # Nested list (LoL): | ||||||
| 277 | push @{ $self->{stack}->{$self->{name}}->[-1] }, $name; | ||||||
| 278 | $self->{sp} = $self->{stack}->{$self->{name}}->[-1]; | ||||||
| 279 | } else { | ||||||
| 280 | # plain (top level) list: | ||||||
| 281 | push @{ $self->{stack}->{$self->{name}} }, $name; | ||||||
| 282 | $self->{sp} = $self->{stack}->{$self->{name}}; | ||||||
| 283 | } | ||||||
| 284 | } else { # named param, scalar context | ||||||
| 285 | # keep track of last name (= hash key) | ||||||
| 286 | $self->{name} = $name; | ||||||
| 287 | $self->{sp} = $self->{stack}->{$self->{name}}; | ||||||
| 288 | # character data will be assigned to | ||||||
| 289 | # $self->{stack}->{$self->{name}} in end_element | ||||||
| 290 | } | ||||||
| 291 | } else { # unnamed param (list context) | ||||||
| 292 | # character data only; handled in end_element | ||||||
| 293 | if (! $parent eq 'list') { | ||||||
| 294 | $self->_croak('Invalid unnamed param in scalar context'); | ||||||
| 295 | } | ||||||
| 296 | $self->{sp} = $self->{stack}->{$self->{name}}; | ||||||
| 297 | } | ||||||
| 298 | } elsif ($element eq 'list') { | ||||||
| 299 | if (defined $name) { # named list | ||||||
| 300 | if ($parent eq 'list') { | ||||||
| 301 | $self->_croak('Invalid named list in list context'); | ||||||
| 302 | } | ||||||
| 303 | # create empty named list, hash key = name | ||||||
| 304 | $self->{sp} = $self->{stack}->{$name} = [()]; | ||||||
| 305 | # keep track of last name (= hash key) | ||||||
| 306 | $self->{name} = $name; | ||||||
| 307 | } else { # unnamed list | ||||||
| 308 | # anonymous list, push ref. to higher level list | ||||||
| 309 | push @{ $self->{stack}->{$self->{name}} }, [()]; | ||||||
| 310 | $self->{sp} = $self->{stack}->{$self->{name}}; | ||||||
| 311 | } | ||||||
| 312 | } elsif ($parent eq 'WebTest') { | ||||||
| 313 | # create a new stack for each second level element (test or params) | ||||||
| 314 | $self->{sp} = $self->{stack} = {}; | ||||||
| 315 | } elsif ($element eq 'WebTest') { | ||||||
| 316 | # root element, validate version attribute | ||||||
| 317 | my $version = $elt->{Attributes}->{'{}version'}->{Value} || '0'; | ||||||
| 318 | if ($version < $webtest_definition_version) { | ||||||
| 319 | $self->_croak("WebTest definition should be version $webtest_definition_version or newer"); | ||||||
| 320 | } | ||||||
| 321 | } else { | ||||||
| 322 | # $self->_croak(sprintf('Unexpected element <%s>', $element)); | ||||||
| 323 | } | ||||||
| 324 | push @{$self->{context}}, $element; | ||||||
| 325 | return; | ||||||
| 326 | } | ||||||
| 327 | |||||||
| 328 | sub end_element { | ||||||
| 329 | my $self = shift; | ||||||
| 330 | my ($elt) = @_; | ||||||
| 331 | my $element = $elt->{Name}; | ||||||
| 332 | if ($element eq 'code') { | ||||||
| 333 | $self->{charbuf} = make_sub_in_playground($self->{charbuf}); | ||||||
| 334 | } | ||||||
| 335 | if ($element eq 'test') { | ||||||
| 336 | push @{ $self->{tests} }, $self->{stack}; | ||||||
| 337 | } elsif ($element eq 'params') { | ||||||
| 338 | $self->{params} = $self->{stack}; | ||||||
| 339 | } elsif (($element eq 'param') || ($element eq 'code')) { | ||||||
| 340 | if (ref $self->{sp} eq 'ARRAY') { | ||||||
| 341 | # list parameter: push character buffer on stack | ||||||
| 342 | push @{ $self->{sp} }, $self->{charbuf}; | ||||||
| 343 | } else { | ||||||
| 344 | # plain scalar parameter: assign character buffer | ||||||
| 345 | $self->{stack}->{$self->{name}} = $self->{charbuf}; | ||||||
| 346 | } | ||||||
| 347 | } elsif ($element eq 'list') { | ||||||
| 348 | $self->_croak('Invalid character data in "list" element') if ($self->{charbuf} =~ /[^\s]/); | ||||||
| 349 | } | ||||||
| 350 | pop @{$self->{context}}; | ||||||
| 351 | $self->{charbuf} = ''; | ||||||
| 352 | } | ||||||
| 353 | |||||||
| 354 | # initialize Locator (for error messages) | ||||||
| 355 | sub set_document_locator { | ||||||
| 356 | my $self = shift; | ||||||
| 357 | $self->{locator} = shift; | ||||||
| 358 | } | ||||||
| 359 | |||||||
| 360 | sub _croak { | ||||||
| 361 | my $self = shift; | ||||||
| 362 | my $msg = shift; | ||||||
| 363 | croak sprintf("%s [Ln: %s, Col: %s]\n", | ||||||
| 364 | $msg, | ||||||
| 365 | $self->{locator}->{LineNumber} || 'N.A.', # Expat: no set_document_locator() | ||||||
| 366 | $self->{locator}->{ColumnNumber} || 'N.A.', | ||||||
| 367 | ); | ||||||
| 368 | } | ||||||
| 369 | |||||||
| 370 | sub finalize { | ||||||
| 371 | my $self = shift; | ||||||
| 372 | return { params => $self->{params}, tests => $self->{tests} }; | ||||||
| 373 | } | ||||||
| 374 | |||||||
| 375 | ################################################## Webtest Writer ### | ||||||
| 376 | package WebTestWriter; | ||||||
| 377 | use strict; | ||||||
| 378 | use XML::Writer; | ||||||
| 379 | use IO::Scalar; | ||||||
| 380 | use Carp qw(croak carp); | ||||||
| 381 | |||||||
| 382 | sub new { | ||||||
| 383 | my $class = shift; | ||||||
| 384 | my $opt = shift; | ||||||
| 385 | my $self = {}; | ||||||
| 386 | $self->{deparse} = 0 if $opt->{nocode}; | ||||||
| 387 | $self->{buffer} = ''; | ||||||
| 388 | my $out = new IO::Scalar(\$self->{buffer}); | ||||||
| 389 | $self->{xh} = new XML::Writer(OUTPUT => $out, | ||||||
| 390 | DATA_MODE => 1, | ||||||
| 391 | DATA_INDENT => 2 | ||||||
| 392 | ); | ||||||
| 393 | return bless $self; | ||||||
| 394 | } | ||||||
| 395 | |||||||
| 396 | # as_xml: writes out test definitions and parameters as XML | ||||||
| 397 | # plain hash {key, val} is output as val | ||||||
| 398 | # list ref: |
||||||
| 399 | # anonymous params/lists lack name attribute | ||||||
| 400 | sub as_xml { | ||||||
| 401 | my $self = shift; | ||||||
| 402 | my ($tests, $params) = @_; | ||||||
| 403 | $self->{xh}->xmlDecl(); | ||||||
| 404 | $self->{xh}->startTag('WebTest', version => $webtest_definition_version); | ||||||
| 405 | $self->_serialize('params', $params); | ||||||
| 406 | foreach my $test (@$tests) { | ||||||
| 407 | $self->_serialize('test', $test); | ||||||
| 408 | } | ||||||
| 409 | $self->{xh}->endTag('WebTest'); | ||||||
| 410 | $self->{xh}->end(); | ||||||
| 411 | return $self->{buffer}; | ||||||
| 412 | } | ||||||
| 413 | |||||||
| 414 | # take a hash ref and serialize to xml in element $elt | ||||||
| 415 | sub _serialize { | ||||||
| 416 | my $self = shift; | ||||||
| 417 | my ($elt, $ref) = @_; | ||||||
| 418 | $self->{xh}->startTag($elt); | ||||||
| 419 | # sort hash to get more predictable output | ||||||
| 420 | foreach my $key (sort keys %$ref) { | ||||||
| 421 | my $val = $ref->{$key}; | ||||||
| 422 | if ((ref $val) && (ref $val eq 'ARRAY')) { # list ref | ||||||
| 423 | $self->_list($key, $val); | ||||||
| 424 | } elsif ((ref $val) && (ref $val eq 'HASH')) { # only from parsed wtscipt | ||||||
| 425 | $self->_hlist($key, $val); | ||||||
| 426 | } else { | ||||||
| 427 | $self->_param($key, $val); | ||||||
| 428 | } | ||||||
| 429 | } | ||||||
| 430 | $self->{xh}->endTag($elt); | ||||||
| 431 | } | ||||||
| 432 | |||||||
| 433 | # lists can be nested | ||||||
| 434 | sub _list { | ||||||
| 435 | my $self = shift; | ||||||
| 436 | my ($key, $val) = @_; | ||||||
| 437 | if (defined $key) { | ||||||
| 438 | $self->{xh}->startTag('list', name => $key); # named list | ||||||
| 439 | } else { | ||||||
| 440 | $self->{xh}->startTag('list'); # anon list | ||||||
| 441 | } | ||||||
| 442 | foreach my $elt (@$val) { | ||||||
| 443 | if ((ref $elt) && (ref $elt eq 'ARRAY')) { | ||||||
| 444 | $self->_list(undef, $elt); # nested anon list; recurse | ||||||
| 445 | } else { | ||||||
| 446 | # At this stage we don't know the difference | ||||||
| 447 | # between a flattened hash or a list of scalar elements. | ||||||
| 448 | # The latter is more safe (odd element count)... | ||||||
| 449 | $self->_param(undef, $elt); # anon param | ||||||
| 450 | } | ||||||
| 451 | } | ||||||
| 452 | $self->{xh}->endTag('list'); | ||||||
| 453 | } | ||||||
| 454 | |||||||
| 455 | # hash list; can contain list | ||||||
| 456 | sub _hlist { | ||||||
| 457 | my $self = shift; | ||||||
| 458 | my ($key, $val) = @_; | ||||||
| 459 | if (defined $key) { | ||||||
| 460 | $self->{xh}->startTag('list', name => $key); # named list | ||||||
| 461 | } else { | ||||||
| 462 | $self->{xh}->startTag('list'); # anon list | ||||||
| 463 | } | ||||||
| 464 | # sort hash to get more predictable output | ||||||
| 465 | foreach my $lkey (sort keys %$val) { | ||||||
| 466 | my $lval = $val->{$lkey}; | ||||||
| 467 | if ((ref $lval) && (ref $lval eq 'ARRAY')) { | ||||||
| 468 | $self->_list($lkey, $lval); | ||||||
| 469 | } else { | ||||||
| 470 | $self->_param($lkey, $lval); | ||||||
| 471 | } | ||||||
| 472 | } | ||||||
| 473 | $self->{xh}->endTag('list'); | ||||||
| 474 | } | ||||||
| 475 | |||||||
| 476 | # params contain scalar data or code ref, no recursion | ||||||
| 477 | sub _param { | ||||||
| 478 | my $self = shift; | ||||||
| 479 | my ($key, $val) = @_; | ||||||
| 480 | my $tag = 'param'; | ||||||
| 481 | if ($val && (ref $val eq 'CODE')) { | ||||||
| 482 | $tag = 'code'; | ||||||
| 483 | if (! defined $self->{deparse}) { | ||||||
| 484 | eval { | ||||||
| 485 | local $SIG{__DIE__}; | ||||||
| 486 | require B::Deparse; # as of Perl 5.6 | ||||||
| 487 | my $vers = $B::Deparse::VERSION || 0; | ||||||
| 488 | die "B::Deparse 0.60 or newer needed, installed version is $vers" if ($vers < 0.60); | ||||||
| 489 | }; | ||||||
| 490 | if ($@) { | ||||||
| 491 | carp($@ . "Couldn't load B::Deparse, CODE blocks will be skipped"); | ||||||
| 492 | $self->{deparse} = 0; | ||||||
| 493 | } else { | ||||||
| 494 | $self->{deparse} = new B::Deparse; # initialize deparser | ||||||
| 495 | } | ||||||
| 496 | } | ||||||
| 497 | $val = ($self->{deparse}) ? $self->{deparse}->coderef2text($val) | ||||||
| 498 | : "sub { 'CODE N.A.' }"; | ||||||
| 499 | } | ||||||
| 500 | if (defined $key) { | ||||||
| 501 | $self->{xh}->startTag($tag, name => $key); # named param | ||||||
| 502 | } else { | ||||||
| 503 | $self->{xh}->startTag($tag); # anon param | ||||||
| 504 | } | ||||||
| 505 | $self->{xh}->characters($val || ''); | ||||||
| 506 | $self->{xh}->endTag($tag); | ||||||
| 507 | } | ||||||
| 508 | |||||||
| 509 | |||||||
| 510 | 1; | ||||||
| 511 | __END__ |