File Coverage

lib/XML/Schema/Base.pm
Criterion Covered Total %
statement 138 225 61.3
branch 51 108 47.2
condition 24 50 48.0
subroutine 20 28 71.4
pod 4 10 40.0
total 237 421 56.2


line stmt bran cond sub pod time code
1             #============================================================= -*-perl-*-
2             #
3             # XML::Schema::Base
4             #
5             # DESCRIPTION
6             # Base class for various XML::Schema::* module implementing common
7             # functionality such as error reporting, etc.
8             #
9             # AUTHOR
10             # Andy Wardley
11             #
12             # COPYRIGHT
13             # Copyright (C) 2001 Canon Research Centre Europe Ltd.
14             # All Rights Reserved.
15             #
16             # This module is free software; you can redistribute it and/or
17             # modify it under the same terms as Perl itself.
18             #
19             # REVISION
20             # $Id: Base.pm,v 1.2 2001/12/20 13:26:27 abw Exp $
21             #
22             #========================================================================
23              
24             package XML::Schema::Base;
25              
26 28     28   348 use strict;
  28         53  
  28         1119  
27 28         3673 use vars qw( $VERSION $DEBUG $ERROR $ECLASS $ETYPE $SNIPPET
28 28     28   317 $TRACE_LEVEL $INSPECT_LEVEL );
  28         65  
29 28     28   11812 use XML::Schema::Exception;
  28         69  
  28         10528  
30              
31             $VERSION = sprintf("%d.%02d", q$Revision: 1.2 $ =~ /(\d+)\.(\d+)/);
32             $DEBUG = 0 unless defined $DEBUG;
33             $ERROR = '';
34             $ECLASS = 'XML::Schema::Exception';
35             $ETYPE = 'undef';
36             $SNIPPET = 16;
37             $TRACE_LEVEL = 4 unless defined $TRACE_LEVEL;
38             $INSPECT_LEVEL = 3 unless defined $INSPECT_LEVEL;
39              
40              
41              
42             #------------------------------------------------------------------------
43             # new(@config)
44             # new(\%config)
45             #
46             # General purpose constructor method for instantiating derived class
47             # objects. Looks for the @BASEARGS package variable in the derived
48             # class package which may define mandatory positional parameters
49             # expected by the constructor. Values for these @BASEARGS are shifted
50             # off the argument list leaving any remaining configuration items as a
51             # hash reference or as a list of key => value pairs which are folded
52             # into a hash reference. Creates a new blessed hash seeded with these
53             # various values and then calls the init() method to perform any
54             # object initialisation. On success the new blessed object is
55             # returned. On error undef is returned and the $ERROR package
56             # variable is set in the _derived_ class' package.
57             #------------------------------------------------------------------------
58              
59             sub new {
60 847     847 1 2262 my $class = shift;
61 847 100       4171 my $config = UNIVERSAL::isa($_[0], 'HASH') ? shift : { @_ };
62              
63 847         3535 $class->error('');
64              
65             my $self = bless {
66             _ERROR => '',
67             _FACTORY => $config->{ factory } || $config->{ FACTORY },
68 847   100     5784 }, $class;
69              
70 847   66     4173 return $self->init($config)
71             || $class->error($self->error());
72             }
73              
74              
75             #------------------------------------------------------------------------
76             # init(\%config)
77             #
78             # Initialisation method called by the new() constructor and passing a
79             # reference to a hash array containing any configuration items specified
80             # as constructor arguments. Should return $self on success or undef on
81             # error, via a call to the error() method to set the error message.
82             #------------------------------------------------------------------------
83              
84             sub init {
85 6     6 1 13 my ($self, $config) = @_;
86 6         45 return $self;
87             }
88              
89              
90             #------------------------------------------------------------------------
91             # init_mandopt(\%config)
92             #
93             # Optional initialisation method which probes the caller's package
94             # for @MANDATORY and @OPTIONAL arguments and does the right thing
95             # to extract them from $config into $self.
96             #------------------------------------------------------------------------
97              
98             sub init_mandopt {
99 117     117 0 180 my ($self, $config) = @_;
100              
101 117         454 my ($mand, $option)
102 117         143 = @{ $self->_baseargs( qw( @MANDATORY %OPTIONAL ) ) };
103              
104 117 100 100     536 $self->_mandatory($mand, $config)
105             || return if @$mand;
106              
107 115 50       462 $self->_optional($option, $config)
108             || return;
109              
110 115         513 return $self;
111             }
112              
113              
114             #------------------------------------------------------------------------
115             # error()
116             # error($msg, ...)
117             #
118             # General purpose method error for getting/setting object or class
119             # error value. When called as a class method it operates on the
120             # package variable $ERROR. When called as an object method it
121             # operates on the internal error item $self->{ _ERROR }. When called
122             # without any arguments it returns the current value for the variable.
123             # When called with one or more arguments (multiple arguments are
124             # concatenated) it updates the error variable and then returns undef.
125             #------------------------------------------------------------------------
126              
127             sub error {
128 1829     1829 1 3466 my $self = shift;
129 1829         1796 my $errvar;
130              
131             {
132 28     28   201 no strict qw( refs );
  28         43  
  28         9555  
  1829         2065  
133 1829 100       3825 $errvar = ref $self ? \$self->{ _ERROR } : \${"$self\::ERROR"};
  1116         5595  
134             }
135 1829 100       3841 if (@_) {
136             # don't join if first arg is an object (may force stringification)
137 1236 50       3318 $$errvar = ref($_[0]) ? shift : join('', @_);
138 1236         5054 return undef;
139             }
140             else {
141 593         2952 return $$errvar;
142             }
143             }
144              
145              
146             #------------------------------------------------------------------------
147             # error_value($item, $got, @values)
148             #
149             # Error reporting method which generates an error message of the form
150             # "item must be 'this', 'that' or 'the_other' (not: 'value_got')"
151             # and passes it to the error() method.
152             #------------------------------------------------------------------------
153              
154             sub error_value {
155 4     4 0 28 my ($self, $item, $got, @values) = @_;
156 4         10 my $last = pop @values;
157 4         19 my $vals = join(', ', map { "'$_'" } @values);
  6         20  
158 4         31 $self->error("$item must be $vals or '$last' (not '$got')");
159             }
160              
161              
162             #------------------------------------------------------------------------
163             # throw($info)
164             # throw($type, $info)
165             #
166             # Throws an error as an XML::Schema::Exception object of type $type
167             # with an informational message as supplied by $info. If the type
168             # is unspecified (i.e. 1 argument, not 2) then the value defined in
169             # the object's $ETYPE package variable is used or if undefined, the
170             # value of the $ETYPE in this base class package.
171             #------------------------------------------------------------------------
172              
173             sub throw {
174 4     4 1 14 my ($self, $error, $info) = @_;
175 4         12 my $factory = $self->factory();
176 4         16 local $" = ', ';
177              
178             # die! die! die!
179 4 100       15 if ($factory->isa( exception => $error )) {
    100          
180             # $self->DEBUG("throwing existing exception $error\n");
181 1         8 die $error;
182             }
183             elsif (defined $info) {
184 1         5 $error = $factory->create( exception => $error, $info );
185             # $self->DEBUG("throwing created exception $error\n");
186 1         5 die $error;
187             }
188             else {
189 28     28   158 no strict 'refs';
  28         43  
  28         19832  
190 2   50     7 $error ||= '';
191             # look for $ETYPE in derived class
192 2         193 my $class = ref $self;
193 2   66     6 my $ename = ${"$class\::ETYPE"} || $ETYPE;
194 2         8 $error = $factory->create( exception => $ename, $error );
195             # $self->DEBUG("throwing created '$ename' exception $error\n");
196 2         10 die $error;
197             }
198              
199             # not reached
200             }
201              
202              
203             #------------------------------------------------------------------------
204             # factory()
205             # factory($new_factory)
206             #
207             # When called without arguments, returns the factory referenced by the
208             # internal _FACTORY item or the value specified in the
209             # $XML::Schema::FACTORY package variable ('XML::Schema::Factory' by
210             # default). When called as an object method with an argument, the
211             # internal _FACTORY item is updated to store the reference to the new
212             # factory passed as the argument.
213             #------------------------------------------------------------------------
214              
215             sub factory {
216 99     99 0 168 my $self = shift;
217 99         115 my $factory;
218              
219             return ($self->{ _FACTORY } = shift)
220 99 100 66     327 if @_ && ref $self;
221              
222 98 50       303 $factory = $self->{ _FACTORY } if ref $self;
223 98   66     248 $factory ||= do {
224 7         43 require XML::Schema;
225 7         25 $XML::Schema::FACTORY;
226             };
227 98   33     433 return $factory || $self->error('no factory defined');
228             }
229              
230              
231              
232             #========================================================================
233             # private/protected methods
234             #========================================================================
235              
236             #------------------------------------------------------------------------
237             # _baseargs(@names)
238             # _baseargs(\%options, @names)
239             #
240             # This method walks up the inheritance tree collecting various package
241             # variables along the way and collating them for the derived object
242             # class. Variable names are passed as arguments, e.g. qw( @MANDATORY,
243             # %OPTIONAL ). A list reference is returned containing references to
244             # lists of all the items found.
245             #------------------------------------------------------------------------
246              
247             sub _baseargs {
248 1600     1600   3187 my ($class, @names) = @_;
249 1600         1623 my ($cache, @pending, %examined, $isa, $base);
250 0         0 my ($name, $type, $arg);
251 1600         2191 my $args = { };
252 1600 100       4326 my $options = ref $names[0] eq 'HASH' ? shift(@names) : { };
253              
254 1600 50       3161 if ($DEBUG) {
255 0         0 $class->DEBUG("_baseargs options: ",
256             $class->_inspect($options), "\n");
257             }
258              
259 1600   66     3579 $class = ref $class || $class;
260 1600         2447 push(@pending, $class);
261              
262 28     28   163 no strict 'refs';
  28         53  
  28         26131  
263 1600         2065 local $" = ', ';
264              
265             # looked for cached version
266             # $cache = \@{"$class\::BASEARGS"};
267             # return $cache if @$cache;
268              
269 1600         3054 while (@pending) {
270 8718         12245 $base = shift @pending;
271 8718 100       19327 next if $examined{ $base };
272 7375         11922 $examined{ $base } = 1;
273              
274 7375         10856 foreach (@names) {
275 11687         14254 $name = $_; # copy to avoid aliasing problems
276 11687         45801 ($type, $name) = ($name =~ /([@%])(\w+)/);
277 11687 100       33416 next if $options->{ skip }->{ $name };
278 10671         12643 $arg = \@{"$base\::$name"};
  10671         43417  
279              
280 10671 100       24380 if ($type eq '@') {
    50          
281 7502         7533 unshift(@{ $args->{ $name } }, @$arg);
  7502         17032  
282 7502 50       14745 $class->DEBUG("$type$base\::$name : [ @$arg ]\n")
283             if $DEBUG;
284             # stop if we only wanted (and got) first match
285 7502 100 100     24116 if ($options->{ first } && @$arg) {
286 332 50       834 $class->DEBUG("found first item for $name: [ @$arg ]\n")
287             if $DEBUG;
288 332         1042 $options->{ skip }->{ $name } = 1;
289             }
290             }
291             elsif ($type eq '%') {
292 3169 100       5057 if (@$arg) { $arg = { map { ($_ => '') } @$arg } }
  1435         2222  
  2727         7307  
293 1734         1733 else { $arg = \%{"$base\::$name"}; }
  1734         5181  
294 3169 100       5722 $args->{ $name } = { %$arg, %{ $args->{ $name } || { } } };
  3169         16258  
295 3169 50       8615 $class->DEBUG("$type$class\::$name : { %$arg }\n")
296             if $DEBUG;
297 3169 50 33     10311 if ($options->{ first } && %{ $args->{ $name } }) {
  0         0  
298 0 0       0 $class->DEBUG("found first item for $name: [ @$arg ]\n")
299             if $DEBUG;
300 0         0 $options->{ skip }->{ $name } = 1;
301             }
302             }
303             }
304              
305             # note need to reverse @isa to ensure multiple inheritance (gasp!)
306             # of the form @ISA = qw( foo bar ) checks packages in the right
307             # order (bar, then foo) for unshifting onto the front of the
308             # @baseargs list
309 7375         9029 $isa = \@{"$base\::ISA"};
  7375         34154  
310 7375 50       11906 push(@pending, map { $examined{ $_ } ? () : $_ } reverse @$isa);
  7118         20196  
311              
312 7375 50 33     23827 $class->DEBUG("$base isa @$isa\n") if $DEBUG && @$isa;
313             }
314              
315             # cache arguments for future invocations
316             # @$cache = ( @$args{ map { (/(\w+)/) } @names } );
317 1600         2273 $cache = [ @$args{ map { (/(\w+)/) } @names } ];
  2547         9504  
318 1600         9081 return $cache;
319             }
320              
321              
322             #------------------------------------------------------------------------
323             # _arguments(\@names, \@args)
324             #
325             # Copies mandatory positional arguments, specified in $names list, from
326             # $args list and sets them as internal data items. Returns reference to
327             # $self on success or undef on error.
328             #------------------------------------------------------------------------
329              
330             sub _arguments {
331 9     9   95 my ($self, $names, $args) = @_;
332            
333             # shift off all mandatory positional arguments
334 9         19 foreach my $name (@$names) {
335 29 100 66     132 return $self->error(ref($self) . ": $name not specified")
336             unless @$args && defined $args->[0];
337 22         52 $self->{ $name } = shift @$args;
338             }
339 2         7 return $self;
340             }
341              
342              
343             #------------------------------------------------------------------------
344             # _mandatory(\@names, \%config)
345             #
346             # Copies mandatory fields, specified in $names list, from $config
347             # hash ref to $self. Returns reference to $self on success or raises
348             # error and returns undef if any field is undefined.
349             #------------------------------------------------------------------------
350              
351             sub _mandatory {
352 497     497   854 my ($self, $names, $config) = @_;
353              
354 497         903 foreach my $name (@$names) {
355 629 100       1624 return $self->error(ref($self) . ": $name not specified")
356             unless defined $config->{ $name };
357 608         2343 $self->{ $name } = $config->{ $name };
358             }
359 476         1571 return $self;
360             }
361              
362              
363             #------------------------------------------------------------------------
364             # _optional(\@names, \%config)
365             # _optional(\%names, \%config)
366             #
367             # Copies optional fields, specified in $names, from $config hash ref
368             # to $self. If $names is a hash reference then the corresponding value
369             # for any field will be used as a default if otherwise undefined in
370             # $config. Returns reference to $self.
371             #------------------------------------------------------------------------
372              
373             sub _optional {
374 730     730   1108 my ($self, $names, $config) = @_;
375 730         756 my ($key, $val);
376              
377 730 100       1725 $names = { map { ($_ => '') } @$names }
  8         30  
378             if ref $names eq 'ARRAY';
379              
380 730         2586 while (($key, $val) = each %$names) {
381 2689 100       13132 $self->{ $key } = defined $config->{ $key }
    100          
382             ? $config->{ $key }
383             : (ref $val eq 'CODE' ? &$val() : $val);
384             }
385              
386 730         2998 return $self;
387             }
388              
389              
390              
391              
392             #========================================================================
393             # debugging methods
394             #========================================================================
395              
396             #------------------------------------------------------------------------
397             # DEBUG(@args)
398             #
399             # Prints all arguments to STDERR.
400             #------------------------------------------------------------------------
401              
402             sub DEBUG {
403 0     0 0 0 my $self = shift;
404 0         0 print STDERR @_;
405             }
406              
407              
408             #------------------------------------------------------------------------
409             # ID
410             #
411             # Returns a string to identify an object. May be redefined in subclasses
412             # to return more meaningful identifier.
413             #------------------------------------------------------------------------
414              
415             sub ID {
416 0     0 0 0 my $self = shift;
417 0   0     0 my $class = ref $self || $self;
418 28     28   178 no strict 'refs';
  28         52  
  28         3796  
419 0   0     0 my $etype = ${"$class\::ETYPE"} || $self;
420 0         0 return $etype;
421             }
422              
423              
424             #------------------------------------------------------------------------
425             # TRACE(@args)
426             #
427             # Generates a trace message showing the object and method from where the
428             # TRACE method was called, along with any additional arguments passed.
429             # Non-reference arguments are added to the trace message intact, references
430             # are first stringified via the _dump_ref() method. The generated message
431             # is then sent to the DEBUG method.
432             #
433             # The internal _DEBUG item (or package variable $DEBUG if not
434             # defined) is used to determine the verbosity of the generated message.
435             #
436             # 1 print argument only
437             # 2 prefix arguments with object ID
438             # 3 as 2 but also with calling method name
439             # 4 as 3 but also with package, file and line info
440             #------------------------------------------------------------------------
441              
442             sub TRACE {
443 2     2 0 5 my $self = shift;
444 2         19 my ($pkg, $file, $line, $sub, $args,
445             $wantarray, $evaltext, $isreq, $hints, $bitmask) = caller(1);
446              
447 2         5 my $level;
448 2 50       7 if (exists $self->{ _DEBUG }) {
449 0         0 $level = $self->{ _DEBUG };
450             }
451             else
452             {
453 28     28   133 no strict 'refs';
  28         55  
  28         30541  
454 2         3 my $class = ref $self;
455 2         3 $level = ${"$class\::DEBUG"};
  2         9  
456             }
457 2 50       9 return unless $level;
458              
459 0           my $output = '';
460              
461 0 0         if ($level > 1) {
462 0           $output .= $self->ID;
463              
464 0 0         if ($level > 2) {
465 0           $sub =~ s/.*::(\w+)$/$1/;
466 0           $output .= "->$sub()";
467              
468 0 0         if ($level > 3) {
469 0           my ($ownpkg, $ownfile, $ownline) = caller();
470 0           $output .= "\n# at $ownfile line $ownline";
471 0           $output .= "\n# called from $file line $line\n";
472             }
473             else {
474 0           $output .= ' : ';
475             }
476             }
477             else {
478 0           $output .= ' : ';
479             }
480             }
481              
482 0           foreach my $arg (@_) {
483 0 0         $arg = '' unless defined $arg;
484 0           $output .= $self->_inspect($arg);
485             }
486 0           chomp($output);
487 0           $output .= "\n";
488              
489 0           $output =~ s/\n(.)/\n | $1/gs;
490              
491 0           $self->DEBUG('T| ' . $output);
492             }
493              
494              
495             #------------------------------------------------------------------------
496             # _inspect($something, $level)
497             #
498             # Attempts to Do The Right Thing to print a meaningful representation of
499             # the $something passes as an argument. Will recurse into $something's
500             # structure while $level is less that $INSPECT_LEVEL.
501             #------------------------------------------------------------------------
502              
503             sub _inspect {
504 0     0     my ($self, $item, $level) = @_;
505 0   0       $level ||= 0;
506 0           my $output = '';
507 0           my $pad = ' ';
508 0 0         my $pad1 = $level ? ($pad x $level) : '';
509 0           my $pad2 = $pad x ++$level;
510              
511 0 0         $item = '' unless defined $item;
512 0 0         return $item if $level > $INSPECT_LEVEL;
513 0 0         return "''" unless length $item;
514 0 0         return $item unless ref $item;
515              
516 0 0         if (UNIVERSAL::isa($item, 'HASH')) {
    0          
    0          
517 0 0         if (%$item) {
518 0           $output .= "$item : {\n";
519 0           while (my ($key, $value) = each %$item) {
520 0           $output .= sprintf("$pad2%-8s => %s,\n", $key, $self->_inspect($value, $level));
521             }
522 0           $output .= "$pad1}";
523             }
524             else {
525 0           $output .= "$item : { }";
526             }
527             }
528             elsif (UNIVERSAL::isa($item, 'ARRAY')) {
529 0 0         if (@$item) {
530 0           $output .= "$item : [\n";
531 0           foreach my $i (@$item) {
532 0           $output .= $pad2 . $self->_inspect($i, $level) . ",\n";
533             }
534 0           $output .= "$pad1]";
535             }
536             else {
537 0           $output .= "$item : [ ]";
538             }
539             }
540             elsif (UNIVERSAL::isa($item, 'SCALAR')) {
541 0           $output .= $item . ' : \\"' . $self->_inspect($$item) . '"';
542             }
543             else {
544 0           $output .= $item;
545             }
546              
547             # $output =~ s/^/$pad/mg;
548 0           return $output;
549             }
550              
551              
552             #------------------------------------------------------------------------
553             # _text_snippet($text, $length)
554             #
555             # Return $text truncated to at most $length characters or $SNIPPET if
556             # $length is undefined.
557             #------------------------------------------------------------------------
558              
559             sub _text_snippet {
560 0     0     my ($self, $text, $length) = @_;
561 0   0       $length ||= $SNIPPET;
562 0           my $snippet = substr($text, 0, $length);
563 0 0         $snippet .= '...' if length $text > $length;
564 0           $snippet =~ s/\n/\\n/g;
565 0           return $snippet;
566             }
567              
568              
569             #------------------------------------------------------------------------
570             # old stuff, no longer used (I think)
571             #------------------------------------------------------------------------
572              
573             sub _old_dump {
574 0     0     my $self = shift;
575 0           my $output = "$self:\n";
576 0           while (my ($key, $value) = each %$self) {
577 0 0         $value = '' unless defined $value;
578 0           $output .= sprintf(" %-12s => %s\n", $key, $value);
579             }
580 0           return $output;
581             }
582              
583             sub _old_dump_ref {
584 0     0     my ($self, $ref) = @_;
585 0 0         if (UNIVERSAL::isa($ref, 'HASH')) {
    0          
586 0           return $self->_old_dump_hash($ref);
587             }
588             elsif (UNIVERSAL::isa($ref, 'LIST')) {
589 0           return $self->_old_dump_list($ref);
590             }
591             else {
592 0           return $ref;
593             }
594             }
595              
596             sub _old_dump_hash {
597 0     0     my $self = shift;
598 0           my $hash = shift;
599 0   0       my $shallow = shift || 0;
600 0           return '{ ' . join(', ', map {
601 0           my $val = $hash->{ $_};
602 0 0         $val = '' unless defined $val;
603             # $val = $self->_old_dump_ref($val) if ref $val && ! $shallow;
604 0           "$_ => $val"
605             } keys %$hash) . ' }';
606             }
607              
608             sub _old_dump_list {
609 0     0     my ($self, $list) = @_;
610 0           return '[ ' . join(', ', map {
611 0           my $val = $_;
612 0 0         $val = '' unless defined $val;
613 0 0         $val = $self->_old_dump_ref($val) if ref $val;
614 0           $val;
615             } @$list) . ' ]';
616             }
617              
618              
619             1;
620              
621             __END__