File Coverage

lib/XML/XPath/XMLParser.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package XML::XPath::XMLParser;
2              
3             $VERSION = '1.42';
4              
5 1     1   6 use strict; use warnings;
  1     1   2  
  1         32  
  1         5  
  1         2  
  1         32  
6              
7 1     1   143 use XML::Parser;
  0            
  0            
8             use XML::XPath::Node;
9             use XML::XPath::Node::Element;
10             use XML::XPath::Node::Text;
11             use XML::XPath::Node::Comment;
12             use XML::XPath::Node::PI;
13             use XML::XPath::Node::Attribute;
14             use XML::XPath::Node::Namespace;
15              
16             my @options = qw(
17             filename
18             xml
19             parser
20             ioref
21             );
22              
23             my ($_current, $_namespaces_on);
24             my %IdNames;
25              
26             use vars qw/$xmlns_ns $xml_ns/;
27              
28             $xmlns_ns = "http://www.w3.org/2000/xmlns/";
29             $xml_ns = "http://www.w3.org/XML/1998/namespace";
30              
31             sub new {
32             my $proto = shift;
33             my $class = ref($proto) || $proto;
34             my %args = @_;
35             my %hash = map(( "_$_" => $args{$_} ), @options);
36             bless \%hash, $class;
37             }
38              
39             sub parse {
40             my $self = shift;
41              
42             $self->{IdNames} = {};
43             $self->{InScopeNamespaceStack} = [ {
44             '_Default' => undef,
45             'xmlns' => $xmlns_ns,
46             'xml' => $xml_ns,
47             } ];
48              
49             $self->{NodeStack} = [ ];
50              
51             $self->set_xml($_[0]) if $_[0];
52              
53             my $parser = $self->get_parser || XML::Parser->new(
54             ErrorContext => 2,
55             ParseParamEnt => $XML::XPath::ParseParamEnt,
56             );
57              
58             $parser->setHandlers(
59             Init => sub { $self->parse_init(@_) },
60             Char => sub { $self->parse_char(@_) },
61             Start => sub { $self->parse_start(@_) },
62             End => sub { $self->parse_end(@_) },
63             Final => sub { $self->parse_final(@_) },
64             Proc => sub { $self->parse_pi(@_) },
65             Comment => sub { $self->parse_comment(@_) },
66             Attlist => sub { $self->parse_attlist(@_) },
67             );
68              
69             my $toparse;
70             if ($toparse = $self->get_filename) {
71             return $parser->parsefile($toparse);
72             }
73             else {
74             return $parser->parse($self->get_xml || $self->get_ioref);
75             }
76             }
77              
78             sub parsefile {
79             my $self = shift;
80             my ($filename) = @_;
81             $self->set_filename($filename);
82             $self->parse;
83             }
84              
85             sub parse_init {
86             my $self = shift;
87             my $e = shift;
88             my $document = XML::XPath::Node::Element->new();
89             my $newns = XML::XPath::Node::Namespace->new('xml', $xml_ns);
90             $document->appendNamespace($newns);
91             $self->{current} = $self->{DOC_Node} = $document;
92             }
93              
94             sub parse_final {
95             my $self = shift;
96             return $self->{DOC_Node};
97             }
98              
99             sub parse_char {
100             my $self = shift;
101             my $e = shift;
102             my $text = shift;
103              
104             my $parent = $self->{current};
105              
106             my $last = $parent->getLastChild;
107             if ($last && $last->isTextNode) {
108             # append to previous text node
109             $last->appendText($text);
110             return;
111             }
112              
113             my $node = XML::XPath::Node::Text->new($text);
114             $parent->appendChild($node, 1);
115             }
116              
117             sub parse_start {
118             my $self = shift;
119             my $e = shift;
120             my $tag = shift;
121              
122             push @{ $self->{InScopeNamespaceStack} },
123             { %{ $self->{InScopeNamespaceStack}[-1] } };
124             $self->_scan_namespaces(@_);
125              
126             my ($prefix, $namespace) = $self->_namespace($tag);
127              
128             my $node = XML::XPath::Node::Element->new($tag, $prefix);
129              
130             my @attributes;
131             for (my $ii = 0; $ii < $#_; $ii += 2) {
132             my ($name, $value) = ($_[$ii], $_[$ii+1]);
133             if ($name =~ /^xmlns(:(.*))?$/) {
134             # namespace node
135             my $prefix = $2 || '#default';
136             # warn "Creating NS node: $prefix = $value\n";
137             my $newns = XML::XPath::Node::Namespace->new($prefix, $value);
138             $node->appendNamespace($newns);
139             }
140             else {
141             my ($prefix, $namespace) = $self->_namespace($name);
142             undef $namespace unless $prefix;
143              
144             my $newattr = XML::XPath::Node::Attribute->new($name, $value, $prefix);
145             $node->appendAttribute($newattr, 1);
146             if (exists($self->{IdNames}{$tag}) && ($self->{IdNames}{$tag} eq $name)) {
147             # warn "appending Id Element: $val for ", $node->getName, "\n";
148             $self->{DOC_Node}->appendIdElement($value, $node);
149             }
150             }
151             }
152              
153             $self->{current}->appendChild($node, 1);
154             $self->{current} = $node;
155             }
156              
157             sub parse_end {
158             my $self = shift;
159             my $e = shift;
160             $self->{current} = $self->{current}->getParentNode;
161             }
162              
163             sub parse_pi {
164             my $self = shift;
165             my $e = shift;
166             my ($target, $data) = @_;
167             my $node = XML::XPath::Node::PI->new($target, $data);
168             $self->{current}->appendChild($node, 1);
169             }
170              
171             sub parse_comment {
172             my $self = shift;
173             my $e = shift;
174             my ($data) = @_;
175             my $node = XML::XPath::Node::Comment->new($data);
176             $self->{current}->appendChild($node, 1);
177             }
178              
179             sub parse_attlist {
180             my $self = shift;
181             my $e = shift;
182             my ($elname, $attname, $type, $default, $fixed) = @_;
183             if ($type eq 'ID') {
184             $self->{IdNames}{$elname} = $attname;
185             }
186             }
187              
188             sub _scan_namespaces {
189             my ($self, %attributes) = @_;
190              
191             while (my ($attr_name, $value) = each %attributes) {
192             if ($attr_name eq 'xmlns') {
193             $self->{InScopeNamespaceStack}[-1]{'_Default'} = $value;
194             } elsif ($attr_name =~ /^xmlns:(.*)$/) {
195             my $prefix = $1;
196             $self->{InScopeNamespaceStack}[-1]{$prefix} = $value;
197             }
198             }
199             }
200              
201             sub _namespace {
202             my ($self, $name) = @_;
203              
204             my ($prefix, $localname) = split(/:/, $name);
205             if (!defined($localname)) {
206             if ($prefix eq 'xmlns') {
207             return '', undef;
208             } else {
209             return '', $self->{InScopeNamespaceStack}[-1]{'_Default'};
210             }
211             } else {
212             return $prefix, $self->{InScopeNamespaceStack}[-1]{$prefix};
213             }
214             }
215              
216             sub as_string {
217             my $node = shift;
218             $node->toString;
219             }
220              
221             sub get_parser { shift->{_parser}; }
222             sub get_filename { shift->{_filename}; }
223             sub get_xml { shift->{_xml}; }
224             sub get_ioref { shift->{_ioref}; }
225              
226             sub set_parser { $_[0]->{_parser} = $_[1]; }
227             sub set_filename { $_[0]->{_filename} = $_[1]; }
228             sub set_xml { $_[0]->{_xml} = $_[1]; }
229             sub set_ioref { $_[0]->{_ioref} = $_[1]; }
230              
231             1;
232              
233             __END__