File Coverage

blib/lib/XML/LibXML/Augment.pm
Criterion Covered Total %
statement 25 27 92.5
branch n/a
condition n/a
subroutine 9 9 100.0
pod n/a
total 34 36 94.4


line stmt bran cond sub pod time code
1             package XML::LibXML::Augment;
2              
3 2     2   24958 use 5.010;
  2         5  
  2         68  
4 2     2   399 use strict;
  2         2  
  2         59  
5 2     2   8 use warnings;
  2         7  
  2         58  
6              
7 2     2   8 use Carp qw//;
  2         3  
  2         38  
8 2     2   1607 use Class::Inspector;
  2         7854  
  2         59  
9 2     2   1594 use match::simple qw/match/;
  2         17382  
  2         18  
10 2     2   2301 use Module::Runtime qw/module_notional_filename/;
  2         3824  
  2         14  
11 2     2   102 use Scalar::Util qw/blessed/;
  2         4  
  2         129  
12 2     2   2299 use XML::LibXML 1.95 qw/:libxml/;
  0            
  0            
13              
14             my %Delegates;
15              
16             BEGIN
17             {
18             $XML::LibXML::Augment::AUTHORITY = 'cpan:TOBYINK';
19             $XML::LibXML::Augment::VERSION = '0.003';
20            
21             no strict 'refs';
22             no warnings 'once';
23            
24             my @_CLASSES = qw/
25             Node Document DocumentFragment Element Attr
26             Text CDATASection Comment Dtd PI NodeList
27             /;
28              
29             # It would be nice to not need to include this block,
30             # but it's currently necessary for Dist::Inkt and the
31             # PAUSE indexer...
32             {
33             package XML::LibXML::Augment::Node;
34             package XML::LibXML::Augment::Document;
35             package XML::LibXML::Augment::DocumentFragment;
36             package XML::LibXML::Augment::Element;
37             package XML::LibXML::Augment::Attr;
38             package XML::LibXML::Augment::Text;
39             package XML::LibXML::Augment::CDATASection;
40             package XML::LibXML::Augment::Comment;
41             package XML::LibXML::Augment::Dtd;
42             package XML::LibXML::Augment::PI;
43             package XML::LibXML::Augment::NodeList;
44             }
45              
46             foreach my $class (@_CLASSES)
47             {
48             if (match $class, [qw/Comment CDATASection/])
49             {
50             # Comment and CDATASection inherit from Text
51             push @{"XML::LibXML::Augment::${class}::ISA"},
52             "XML::LibXML::Augment::Text";
53             }
54             elsif ($class ne 'Node' and $class ne 'NodeList')
55             {
56             # Everything inherits from Node
57             push @{"XML::LibXML::Augment::${class}::ISA"},
58             "XML::LibXML::Augment::Node";
59             }
60            
61             # Inherit from XML::LibXML counterpart
62             push @{"XML::LibXML::Augment::${class}::ISA"},
63             "XML::LibXML::${class}";
64              
65             # $AUTHORITY and $VERSION
66             ${"XML::LibXML::Augment::${class}::AUTHORITY"} =
67             $XML::LibXML::Augment::AUTHORITY;
68             ${"XML::LibXML::Augment::${class}::VERSION"} =
69             $XML::LibXML::Augment::VERSION;
70            
71             # Trick "use".
72             $INC{ module_notional_filename("XML::LibXML::Augment::${class}") }
73             = __FILE__;
74              
75             # Create &rebless.
76             my $our_rebless = sprintf('%s::%s::%s', __PACKAGE__, $class, 'rebless');
77             *$our_rebless = sub
78             {
79             my $self = bless $_[1], $_[0];
80             if (my $onbless = $self->can('BLESS'))
81             {
82             $self->$onbless;
83             }
84             return $self;
85             };
86            
87             # Create stub functions mirroring the superclass.
88             my $our_handler = sprintf('%s::%s', __PACKAGE__, '_handler');
89             my $functions = Class::Inspector->functions('XML::LibXML::'.$class);
90             foreach my $fname (@$functions)
91             {
92             next if $fname =~ /^(_|DESTROY|AUTOLOAD)/;
93             my $our_qname = sprintf('%s::%s::%s', __PACKAGE__, $class, $fname);
94             *$our_qname = sub { unshift @_, $class, $fname; goto \&{$our_handler} };
95             }
96             }
97             }
98              
99             sub import
100             {
101             my ($class, %args) = @_;
102            
103             my $caller = caller;
104             my $type = ucfirst lc(delete($args{'-type'}) || 'Element');
105             my $names = delete($args{'-names'});
106             my $isa = delete($args{'-isa'})
107             || ["XML::LibXML::Augment::$type"];
108            
109             if (keys %args)
110             {
111             my $args = join q{, }, map {"'$_'"} sort keys %args;
112             Carp::croak(__PACKAGE__." does not support args: $args");
113             }
114            
115             Carp::croak("-type argument must be 'Element', 'Attr' or 'Document'")
116             unless match $type, [qw/Attr Document Element/];
117            
118             foreach my $n (@$names)
119             {
120             if (ref $Delegates{$type}{$n} eq 'ARRAY')
121             {
122             push @{ $Delegates{$type}{$n} }, $caller;
123             }
124             elsif (defined $Delegates{$type}{$n})
125             {
126             $Delegates{$type}{$n} = [$Delegates{$type}{$n}, $caller];
127             }
128             else
129             {
130             $Delegates{$type}{$n} = $caller;
131             }
132             }
133              
134             no strict 'refs';
135             push @{"$caller\::ISA"}, @$isa;
136            
137             $class;
138             }
139              
140             sub rebless
141             {
142             my ($class, $object) = @_;
143             my $ideal = $class->ideal_class_for_object($object);
144             $ideal->rebless($object) if $ideal;
145             return $object;
146             }
147              
148             sub ideal_class_for_object
149             {
150             my ($me, $object) = @_;
151             return unless ref $object && blessed $object;
152             my $nodeType = $object->can('nodeType') && $object->nodeType;
153             $nodeType = -1 if $object->isa('XML::LibXML::NodeList');
154             return unless $nodeType;
155            
156             my $ideal = {
157             (-1) => 'NodeList',
158             (XML_ELEMENT_NODE) => 'Element',
159             (XML_ATTRIBUTE_NODE) => 'Attr',
160             (XML_TEXT_NODE) => 'Text',
161             (XML_CDATA_SECTION_NODE) => 'CDATASection',
162             (XML_PI_NODE) => 'PI',
163             (XML_COMMENT_NODE) => 'Comment',
164             (XML_DOCUMENT_NODE) => 'Document',
165             (XML_DOCUMENT_FRAG_NODE) => 'DocumentFragment',
166             (XML_DTD_NODE) => 'Dtd',
167             }->{$nodeType};
168            
169             # This is where we get smart
170             if ($ideal eq 'Element' or $ideal eq 'Attr' or $ideal eq 'Document')
171             {
172             my ($ns, $local);
173             if ($ideal eq 'Document')
174             {
175             $ns = $object->documentElement->namespaceURI // '';
176             $ns = sprintf('{%s}', $ns) if length $ns;
177             $local = $object->documentElement->localname;
178             }
179             else
180             {
181             $ns = $object->namespaceURI // '';
182             $ns = sprintf('{%s}', $ns) if length $ns;
183             $local = $object->localname;
184             }
185            
186             foreach my $clark (map { sprintf('%s%s', $ns, $_) } $local, '*')
187             {
188             if (my $i = $Delegates{$ideal}{$clark})
189             {
190             $Delegates{$ideal}{$clark} = $me->make_class(@{$i}) if ref $i;
191             return $Delegates{$ideal}{$clark};
192             }
193             }
194             }
195            
196             return sprintf('%s::%s', __PACKAGE__, $ideal) if defined $ideal;
197             return;
198             }
199              
200             sub make_class
201             {
202             shift;
203             state $COUNT = 0;
204             state $NS = (__PACKAGE__.'::_ANON_::');
205            
206             if (scalar @_ == 1)
207             {
208             return $_[0];
209             }
210            
211             $COUNT++;
212             no strict 'refs';
213            
214             my $newpkg = sprintf('%sCLS%04d', $NS, $COUNT);
215            
216             my @super;
217             foreach my $x (@_)
218             {
219             if ($x =~ m{ ^ $NS }x)
220             {
221             push @super, @{"$x\::ISA"};
222             }
223             else
224             {
225             push @super, $x;
226             }
227             }
228             @{"$newpkg\::ISA"} = @super;
229            
230             my @blesses = map { my $x = $_->can('BLESS'); $x ? ($x) : () } @super;
231             *{"$newpkg\::BLESS"} = sub
232             {
233             my $self = shift;
234             $self->$_ foreach @blesses;
235             };
236            
237             return $newpkg;
238             }
239              
240             sub _handler
241             {
242             no strict 'refs';
243             my $class = shift;
244             my $sub = shift;
245             my $coderef = "XML::LibXML::$class"->can($sub);
246            
247             if (!defined wantarray)
248             {
249             goto $coderef;
250             }
251            
252             if (wantarray)
253             {
254             @_ = $coderef->(@_);
255             goto \&upgrade;
256             }
257            
258             @_ = (my $r = $coderef->(@_));
259             goto \&upgrade;
260             }
261              
262             sub upgrade
263             {
264             for my $i (0 .. $#_)
265             {
266             if (blessed($_[$i]) and $_[$i]->isa('XML::LibXML::NodeList'))
267             {
268             my $me = __PACKAGE__->can('upgrade');
269             $_[$i] = $_[$i]->foreach($me);
270             next;
271             }
272             my $ideal = __PACKAGE__->ideal_class_for_object($_[$i]);
273             $ideal->rebless($_[$i]) if defined $ideal;
274             }
275            
276             wantarray ? @_ : $_[0]
277             }
278              
279             __PACKAGE__
280             __END__