File Coverage

blib/lib/HTML/Inject.pm
Criterion Covered Total %
statement 21 23 91.3
branch n/a
condition n/a
subroutine 8 8 100.0
pod n/a
total 29 31 93.5


line stmt bran cond sub pod time code
1             package HTML::Inject;
2              
3 2     2   86079 use 5.010;
  2         8  
  2         86  
4 2     2   13 use strict;
  2         3  
  2         70  
5 2     2   12 use warnings;
  2         9  
  2         64  
6 2     2   2306 use utf8;
  2         20  
  2         11  
7              
8             BEGIN {
9 2     2   126 $HTML::Inject::AUTHORITY = 'cpan:TOBYINK';
10 2         75 $HTML::Inject::VERSION = '0.003';
11             }
12              
13             use constant {
14 2         213 true => !!1,
15             false => !!0,
16             read_only => 'ro',
17             read_write => 'rw',
18 2     2   12 };
  2         3  
19              
20 2     2   11 use Carp;
  2         4  
  2         203  
21 2     2   2385 use HTML::HTML5::Parser;
  0            
  0            
22             use IO::Detect qw( is_filehandle is_filename );
23             use Scalar::Does 0.002 qw( blessed reftype -constants );
24             use XML::LibXML 1.94;
25              
26             sub must_do {
27             my $role = shift;
28             return sub { does($_[0], $role) or confess "Does not do role $_[0]" };
29             }
30              
31             use Moo;
32             use namespace::sweep -also => [qw(
33             must_do
34             true false read_only read_write
35             )];
36              
37             has target => (
38             is => read_only,
39             isa => must_do( 'XML::LibXML::Document' ),
40             coerce => \&_coerce_dom,
41             required => true,
42             );
43              
44             has missing_nodes => (
45             is => read_write,
46             isa => must_do( ARRAY ),
47             default => sub { [] },
48             );
49              
50             has head_element_test => (
51             is => read_only,
52             isa => must_do( CODE ),
53             default => sub { sub { no warnings; $_[0]->nodeName ~~ [qw(title link meta style)] } },
54             );
55              
56             has body_element_test => (
57             is => read_only,
58             isa => must_do( CODE ),
59             default => sub { sub { no warnings; $_[0]->nodeName ~~ [qw(script map)] } },
60             );
61              
62             sub inject
63             {
64             my ($self, $content) = @_;
65             my %content = $self->_find_content($content);
66             my $dom = $self->target->cloneNode(true);
67            
68             @{ $self->missing_nodes } = ();
69             while (my ($id, $el) = each %content)
70             {
71             if ($id =~ /^-inject/)
72             {
73             if ($self->head_element_test->($el))
74             { $dom->getElementsByTagName('head')->get_node(1)->appendChild($el) }
75             elsif ($self->body_element_test->($el))
76             { $dom->getElementsByTagName('body')->get_node(1)->appendChild($el) }
77             else
78             { push @{ $self->missing_nodes }, $el }
79             }
80             else
81             {
82             my $target = $dom->findnodes(qq{//*[\@id="$id"]});
83             if ($target->size)
84             {
85             $target->foreach(sub {
86             my $t = $_;
87             $t->{$_} = $el->{$_} for keys %$el;
88             $t->appendChild($_->cloneNode(1)) for $el->childNodes;
89             });
90             }
91             else
92             {
93             push @{ $self->missing_nodes }, $el;
94             }
95             }
96             }
97            
98             return $dom;
99             }
100              
101             sub inject_and_new
102             {
103             my ($self, $content) = @_;
104             my $class = ref $self;
105             $class->new( target => $self->inject($content) );
106             }
107              
108             sub _find_content
109             {
110             my ($self, $content) = @_;
111             my %rv;
112            
113             if (does($content, ARRAY))
114             {
115             for my $c (@$content)
116             {
117             my %tmp = $self->_find_content($c);
118             $rv{$_} //= $tmp{$_} for keys %tmp;
119             }
120             return %rv;
121             }
122            
123             my $i;
124             _coerce_dom($content)
125             -> findnodes('/*/*/*')
126             -> foreach(sub {
127             $rv{ $_->{id} ? $_->{id} : sprintf('-inject_%d', ++$i) } = $_;
128             });
129             return %rv;
130             }
131              
132             sub _coerce_dom
133             {
134             my ($it) = @_;
135            
136             return $it
137             if does($it, 'XML::LibXML::Document');
138            
139             return HTML::HTML5::Parser::->load_html(IO => $it)
140             if is_filehandle $it;
141            
142             return HTML::HTML5::Parser::->load_html(location => $it)
143             if is_filename $it;
144            
145             return HTML::HTML5::Parser::->load_html(location => "$it")
146             if blessed $it && $it->isa('URI');
147            
148             return HTML::HTML5::Parser::->load_html(location => "$it")
149             if !blessed $it && $it =~ /^(https?|file):\S+$/i;
150            
151             return HTML::HTML5::Parser::->load_html(string => $it)
152             }
153              
154             1;
155              
156             __END__