1 | # $Id: Element.pm 7909 2004-08-06 05:11:55Z mdewsnip $
|
---|
2 |
|
---|
3 | package XML::XPath::Node::Element;
|
---|
4 |
|
---|
5 | use strict;
|
---|
6 | use vars qw/@ISA/;
|
---|
7 |
|
---|
8 | @ISA = ('XML::XPath::Node');
|
---|
9 |
|
---|
10 | package XML::XPath::Node::ElementImpl;
|
---|
11 |
|
---|
12 | use vars qw/@ISA/;
|
---|
13 | @ISA = ('XML::XPath::NodeImpl', 'XML::XPath::Node::Element');
|
---|
14 | use XML::XPath::Node ':node_keys';
|
---|
15 |
|
---|
16 | sub new {
|
---|
17 | my $class = shift;
|
---|
18 | my ($tag, $prefix) = @_;
|
---|
19 |
|
---|
20 | my $pos = XML::XPath::Node->nextPos;
|
---|
21 |
|
---|
22 | my @vals;
|
---|
23 | @vals[node_global_pos, node_prefix, node_children, node_name, node_attribs] =
|
---|
24 | ($pos, $prefix, [], $tag, []);
|
---|
25 |
|
---|
26 | my $self = \@vals;
|
---|
27 | bless $self, $class;
|
---|
28 | }
|
---|
29 |
|
---|
30 | sub getNodeType { ELEMENT_NODE }
|
---|
31 |
|
---|
32 | sub isElementNode { 1; }
|
---|
33 |
|
---|
34 | sub appendChild {
|
---|
35 | my $self = shift;
|
---|
36 | my $newnode = shift;
|
---|
37 | if (shift) { # called from internal to XML::XPath
|
---|
38 | # warn "AppendChild $newnode to $self\n";
|
---|
39 | push @{$self->[node_children]}, $newnode;
|
---|
40 | $newnode->setParentNode($self);
|
---|
41 | $newnode->set_pos($#{$self->[node_children]});
|
---|
42 | }
|
---|
43 | else {
|
---|
44 | if (@{$self->[node_children]}) {
|
---|
45 | $self->insertAfter($newnode, $self->[node_children][-1]);
|
---|
46 | }
|
---|
47 | else {
|
---|
48 | my $pos_number = $self->get_global_pos() + 1;
|
---|
49 |
|
---|
50 | if (my $brother = $self->getNextSibling()) { # optimisation
|
---|
51 | if ($pos_number == $brother->get_global_pos()) {
|
---|
52 | $self->renumber('following::node()', +5);
|
---|
53 | }
|
---|
54 | }
|
---|
55 | else {
|
---|
56 | eval {
|
---|
57 | if ($pos_number ==
|
---|
58 | $self->findnodes(
|
---|
59 | 'following::node()'
|
---|
60 | )->get_node(1)->get_global_pos()) {
|
---|
61 | $self->renumber('following::node()', +5);
|
---|
62 | }
|
---|
63 | };
|
---|
64 | }
|
---|
65 |
|
---|
66 | push @{$self->[node_children]}, $newnode;
|
---|
67 | $newnode->setParentNode($self);
|
---|
68 | $newnode->set_pos($#{$self->[node_children]});
|
---|
69 | $newnode->set_global_pos($pos_number);
|
---|
70 | }
|
---|
71 | }
|
---|
72 | }
|
---|
73 |
|
---|
74 | sub removeChild {
|
---|
75 | my $self = shift;
|
---|
76 | my $delnode = shift;
|
---|
77 |
|
---|
78 | my $pos = $delnode->get_pos;
|
---|
79 |
|
---|
80 | # warn "removeChild: $pos\n";
|
---|
81 |
|
---|
82 | # warn "children: ", scalar @{$self->[node_children]}, "\n";
|
---|
83 |
|
---|
84 | # my $node = $self->[node_children][$pos];
|
---|
85 | # warn "child at $pos is: $node\n";
|
---|
86 |
|
---|
87 | splice @{$self->[node_children]}, $pos, 1;
|
---|
88 |
|
---|
89 | # warn "children now: ", scalar @{$self->[node_children]}, "\n";
|
---|
90 |
|
---|
91 | for (my $i = $pos; $i < @{$self->[node_children]}; $i++) {
|
---|
92 | # warn "Changing pos of child: $i\n";
|
---|
93 | $self->[node_children][$i]->set_pos($i);
|
---|
94 | }
|
---|
95 |
|
---|
96 | $delnode->del_parent_link;
|
---|
97 |
|
---|
98 | }
|
---|
99 |
|
---|
100 | sub appendIdElement {
|
---|
101 | my $self = shift;
|
---|
102 | my ($val, $element) = @_;
|
---|
103 | # warn "Adding '$val' to ID hash\n";
|
---|
104 | $self->[node_ids]{$val} = $element;
|
---|
105 | }
|
---|
106 |
|
---|
107 | sub DESTROY {
|
---|
108 | my $self = shift;
|
---|
109 | # warn "DESTROY ELEMENT: ", $self->[node_name], "\n";
|
---|
110 | # warn "DESTROY ROOT\n" unless $self->[node_name];
|
---|
111 |
|
---|
112 | foreach my $kid ($self->getChildNodes) {
|
---|
113 | $kid && $kid->del_parent_link;
|
---|
114 | }
|
---|
115 | foreach my $attr ($self->getAttributeNodes) {
|
---|
116 | $attr && $attr->del_parent_link;
|
---|
117 | }
|
---|
118 | foreach my $ns ($self->getNamespaceNodes) {
|
---|
119 | $ns && $ns->del_parent_link;
|
---|
120 | }
|
---|
121 | # $self->[node_children] = undef;
|
---|
122 | # $self->[node_attribs] = undef;
|
---|
123 | # $self->[node_namespaces] = undef;
|
---|
124 | }
|
---|
125 |
|
---|
126 | sub getName {
|
---|
127 | my $self = shift;
|
---|
128 | $self->[node_name];
|
---|
129 | }
|
---|
130 |
|
---|
131 | sub getTagName {
|
---|
132 | shift->getName(@_);
|
---|
133 | }
|
---|
134 |
|
---|
135 | sub getLocalName {
|
---|
136 | my $self = shift;
|
---|
137 | my $local = $self->[node_name];
|
---|
138 | $local =~ s/.*://;
|
---|
139 | return $local;
|
---|
140 | }
|
---|
141 |
|
---|
142 | sub getChildNodes {
|
---|
143 | my $self = shift;
|
---|
144 | return wantarray ? @{$self->[node_children]} : $self->[node_children];
|
---|
145 | }
|
---|
146 |
|
---|
147 | sub getChildNode {
|
---|
148 | my $self = shift;
|
---|
149 | my ($pos) = @_;
|
---|
150 | if ($pos < 1 || $pos > @{$self->[node_children]}) {
|
---|
151 | return;
|
---|
152 | }
|
---|
153 | return $self->[node_children][$pos - 1];
|
---|
154 | }
|
---|
155 |
|
---|
156 | sub getFirstChild {
|
---|
157 | my $self = shift;
|
---|
158 | return unless @{$self->[node_children]};
|
---|
159 | return $self->[node_children][0];
|
---|
160 | }
|
---|
161 |
|
---|
162 | sub getLastChild {
|
---|
163 | my $self = shift;
|
---|
164 | return unless @{$self->[node_children]};
|
---|
165 | return $self->[node_children][-1];
|
---|
166 | }
|
---|
167 |
|
---|
168 | sub getAttributeNode {
|
---|
169 | my $self = shift;
|
---|
170 | my ($name) = @_;
|
---|
171 | my $attribs = $self->[node_attribs];
|
---|
172 | foreach my $attr (@$attribs) {
|
---|
173 | return $attr if $attr->getName eq $name;
|
---|
174 | }
|
---|
175 | }
|
---|
176 |
|
---|
177 | sub getAttribute {
|
---|
178 | my $self = shift;
|
---|
179 | my $attr = $self->getAttributeNode(@_);
|
---|
180 | if ($attr) {
|
---|
181 | return $attr->getValue;
|
---|
182 | }
|
---|
183 | }
|
---|
184 |
|
---|
185 | sub getAttributes {
|
---|
186 | my $self = shift;
|
---|
187 | if ($self->[node_attribs]) {
|
---|
188 | return wantarray ? @{$self->[node_attribs]} : $self->[node_attribs];
|
---|
189 | }
|
---|
190 | return wantarray ? () : [];
|
---|
191 | }
|
---|
192 |
|
---|
193 | sub appendAttribute {
|
---|
194 | my $self = shift;
|
---|
195 | my $attribute = shift;
|
---|
196 |
|
---|
197 | if (shift) { # internal call
|
---|
198 | push @{$self->[node_attribs]}, $attribute;
|
---|
199 | $attribute->setParentNode($self);
|
---|
200 | $attribute->set_pos($#{$self->[node_attribs]});
|
---|
201 | }
|
---|
202 | else {
|
---|
203 | my $node_num;
|
---|
204 | if (@{$self->[node_attribs]}) {
|
---|
205 | $node_num = $self->[node_attribs][-1]->get_global_pos() + 1;
|
---|
206 | }
|
---|
207 | else {
|
---|
208 | $node_num = $self->get_global_pos() + 1;
|
---|
209 | }
|
---|
210 |
|
---|
211 | eval {
|
---|
212 | if (@{$self->[node_children]}) {
|
---|
213 | if ($node_num == $self->[node_children][-1]->get_global_pos()) {
|
---|
214 | $self->renumber('descendant::node() | following::node()', +5);
|
---|
215 | }
|
---|
216 | }
|
---|
217 | elsif ($node_num ==
|
---|
218 | $self->findnodes('following::node()')->get_node(1)->get_global_pos()) {
|
---|
219 | $self->renumber('following::node()', +5);
|
---|
220 | }
|
---|
221 | };
|
---|
222 |
|
---|
223 | push @{$self->[node_attribs]}, $attribute;
|
---|
224 | $attribute->setParentNode($self);
|
---|
225 | $attribute->set_pos($#{$self->[node_attribs]});
|
---|
226 | $attribute->set_global_pos($node_num);
|
---|
227 |
|
---|
228 | }
|
---|
229 | }
|
---|
230 |
|
---|
231 | sub removeAttribute {
|
---|
232 | my $self = shift;
|
---|
233 | my $attrib = shift;
|
---|
234 |
|
---|
235 | if (!ref($attrib)) {
|
---|
236 | $attrib = $self->getAttributeNode($attrib);
|
---|
237 | }
|
---|
238 |
|
---|
239 | my $pos = $attrib->get_pos;
|
---|
240 |
|
---|
241 | splice @{$self->[node_attribs]}, $pos, 1;
|
---|
242 |
|
---|
243 | for (my $i = $pos; $i < @{$self->[node_attribs]}; $i++) {
|
---|
244 | $self->[node_attribs][$i]->set_pos($i);
|
---|
245 | }
|
---|
246 |
|
---|
247 | $attrib->del_parent_link;
|
---|
248 | }
|
---|
249 |
|
---|
250 | sub setAttribute {
|
---|
251 | my $self = shift;
|
---|
252 | my ($name, $value) = @_;
|
---|
253 |
|
---|
254 | if (my $attrib = $self->getAttributeNode($name)) {
|
---|
255 | $attrib->setNodeValue($value);
|
---|
256 | return $attrib;
|
---|
257 | }
|
---|
258 |
|
---|
259 | my ($nsprefix) = ($name =~ /^($XML::XPath::Parser::NCName):($XML::XPath::Parser::NCName)$/o);
|
---|
260 |
|
---|
261 | if ($nsprefix && !$self->getNamespace($nsprefix)) {
|
---|
262 | die "No namespace matches prefix: $nsprefix";
|
---|
263 | }
|
---|
264 |
|
---|
265 | my $newnode = XML::XPath::Node::Attribute->new($name, $value, $nsprefix);
|
---|
266 | $self->appendAttribute($newnode);
|
---|
267 | }
|
---|
268 |
|
---|
269 | sub setAttributeNode {
|
---|
270 | my $self = shift;
|
---|
271 | my ($node) = @_;
|
---|
272 |
|
---|
273 | if (my $attrib = $self->getAttributeNode($node->getName)) {
|
---|
274 | $attrib->setNodeValue($node->getValue);
|
---|
275 | return $attrib;
|
---|
276 | }
|
---|
277 |
|
---|
278 | my ($nsprefix) = ($node->getName() =~ /^($XML::XPath::Parser::NCName):($XML::XPath::Parser::NCName)$/o);
|
---|
279 |
|
---|
280 | if ($nsprefix && !$self->getNamespace($nsprefix)) {
|
---|
281 | die "No namespace matches prefix: $nsprefix";
|
---|
282 | }
|
---|
283 |
|
---|
284 | $self->appendAttribute($node);
|
---|
285 | }
|
---|
286 |
|
---|
287 | sub getNamespace {
|
---|
288 | my $self = shift;
|
---|
289 | my ($prefix) = @_;
|
---|
290 | $prefix ||= $self->getPrefix || '#default';
|
---|
291 | my $namespaces = $self->[node_namespaces] || [];
|
---|
292 | foreach my $ns (@$namespaces) {
|
---|
293 | return $ns if $ns->getPrefix eq $prefix;
|
---|
294 | }
|
---|
295 | my $parent = $self->getParentNode;
|
---|
296 |
|
---|
297 | return $parent->getNamespace($prefix) if $parent;
|
---|
298 | }
|
---|
299 |
|
---|
300 | sub getNamespaces {
|
---|
301 | my $self = shift;
|
---|
302 | if ($self->[node_namespaces]) {
|
---|
303 | return wantarray ? @{$self->[node_namespaces]} : $self->[node_namespaces];
|
---|
304 | }
|
---|
305 | return wantarray ? () : [];
|
---|
306 | }
|
---|
307 |
|
---|
308 | sub getNamespaceNodes { goto &getNamespaces }
|
---|
309 |
|
---|
310 | sub appendNamespace {
|
---|
311 | my $self = shift;
|
---|
312 | my ($ns) = @_;
|
---|
313 | push @{$self->[node_namespaces]}, $ns;
|
---|
314 | $ns->setParentNode($self);
|
---|
315 | $ns->set_pos($#{$self->[node_namespaces]});
|
---|
316 | }
|
---|
317 |
|
---|
318 | sub getPrefix {
|
---|
319 | my $self = shift;
|
---|
320 | $self->[node_prefix];
|
---|
321 | }
|
---|
322 |
|
---|
323 | sub getExpandedName {
|
---|
324 | my $self = shift;
|
---|
325 | warn "Expanded name not implemented for ", ref($self), "\n";
|
---|
326 | return;
|
---|
327 | }
|
---|
328 |
|
---|
329 | sub _to_sax {
|
---|
330 | my $self = shift;
|
---|
331 | my ($doch, $dtdh, $enth) = @_;
|
---|
332 |
|
---|
333 | my $tag = $self->getName;
|
---|
334 | my @attr;
|
---|
335 |
|
---|
336 | for my $attr ($self->getAttributes) {
|
---|
337 | push @attr, $attr->getName, $attr->getValue;
|
---|
338 | }
|
---|
339 |
|
---|
340 | my $ns = $self->getNamespace($self->[node_prefix]);
|
---|
341 | if ($ns) {
|
---|
342 | $doch->start_element(
|
---|
343 | {
|
---|
344 | Name => $tag,
|
---|
345 | Attributes => { @attr },
|
---|
346 | NamespaceURI => $ns->getExpanded,
|
---|
347 | Prefix => $ns->getPrefix,
|
---|
348 | LocalName => $self->getLocalName,
|
---|
349 | }
|
---|
350 | );
|
---|
351 | }
|
---|
352 | else {
|
---|
353 | $doch->start_element(
|
---|
354 | {
|
---|
355 | Name => $tag,
|
---|
356 | Attributes => { @attr },
|
---|
357 | }
|
---|
358 | );
|
---|
359 | }
|
---|
360 |
|
---|
361 | for my $kid ($self->getChildNodes) {
|
---|
362 | $kid->_to_sax($doch, $dtdh, $enth);
|
---|
363 | }
|
---|
364 |
|
---|
365 | if ($ns) {
|
---|
366 | $doch->end_element(
|
---|
367 | {
|
---|
368 | Name => $tag,
|
---|
369 | NamespaceURI => $ns->getExpanded,
|
---|
370 | Prefix => $ns->getPrefix,
|
---|
371 | LocalName => $self->getLocalName
|
---|
372 | }
|
---|
373 | );
|
---|
374 | }
|
---|
375 | else {
|
---|
376 | $doch->end_element( { Name => $tag } );
|
---|
377 | }
|
---|
378 | }
|
---|
379 |
|
---|
380 | sub string_value {
|
---|
381 | my $self = shift;
|
---|
382 | my $string = '';
|
---|
383 | foreach my $kid (@{$self->[node_children]}) {
|
---|
384 | if ($kid->getNodeType == ELEMENT_NODE
|
---|
385 | || $kid->getNodeType == TEXT_NODE) {
|
---|
386 | $string .= $kid->string_value;
|
---|
387 | }
|
---|
388 | }
|
---|
389 | return $string;
|
---|
390 | }
|
---|
391 |
|
---|
392 | sub toString {
|
---|
393 | my $self = shift;
|
---|
394 | my $norecurse = shift;
|
---|
395 | my $string = '';
|
---|
396 | if (! $self->[node_name] ) {
|
---|
397 | # root node
|
---|
398 | return join('', map { $_->toString($norecurse) } @{$self->[node_children]});
|
---|
399 | }
|
---|
400 | $string .= "<" . $self->[node_name];
|
---|
401 |
|
---|
402 | $string .= join('', map { $_->toString } @{$self->[node_namespaces]});
|
---|
403 |
|
---|
404 | $string .= join('', map { $_->toString } @{$self->[node_attribs]});
|
---|
405 |
|
---|
406 | if (@{$self->[node_children]}) {
|
---|
407 | $string .= ">";
|
---|
408 |
|
---|
409 | if (!$norecurse) {
|
---|
410 | $string .= join('', map { $_->toString($norecurse) } @{$self->[node_children]});
|
---|
411 | }
|
---|
412 |
|
---|
413 | $string .= "</" . $self->[node_name] . ">";
|
---|
414 | }
|
---|
415 | else {
|
---|
416 | $string .= " />";
|
---|
417 | }
|
---|
418 |
|
---|
419 | return $string;
|
---|
420 | }
|
---|
421 |
|
---|
422 | 1;
|
---|
423 | __END__
|
---|
424 |
|
---|
425 | =head1 NAME
|
---|
426 |
|
---|
427 | Element - an <element>
|
---|
428 |
|
---|
429 | =head1 API
|
---|
430 |
|
---|
431 | =head2 new ( name, prefix )
|
---|
432 |
|
---|
433 | Create a new Element node with name "name" and prefix "prefix". The name
|
---|
434 | be "prefix:local" if prefix is defined. I know that sounds wierd, but it
|
---|
435 | works ;-)
|
---|
436 |
|
---|
437 | =head2 getName
|
---|
438 |
|
---|
439 | Returns the name (including "prefix:" if defined) of this element.
|
---|
440 |
|
---|
441 | =head2 getLocalName
|
---|
442 |
|
---|
443 | Returns just the local part of the name (the bit after "prefix:").
|
---|
444 |
|
---|
445 | =head2 getChildNodes
|
---|
446 |
|
---|
447 | Returns the children of this element. In list context returns a list. In
|
---|
448 | scalar context returns an array ref.
|
---|
449 |
|
---|
450 | =head2 getChildNode ( pos )
|
---|
451 |
|
---|
452 | Returns the child at position pos.
|
---|
453 |
|
---|
454 | =head2 appendChild ( childnode )
|
---|
455 |
|
---|
456 | Appends the child node to the list of current child nodes.
|
---|
457 |
|
---|
458 | =head2 getAttribute ( name )
|
---|
459 |
|
---|
460 | Returns the attribute node with key name.
|
---|
461 |
|
---|
462 | =head2 getAttributes / getAttributeNodes
|
---|
463 |
|
---|
464 | Returns the attribute nodes. In list context returns a list. In scalar
|
---|
465 | context returns an array ref.
|
---|
466 |
|
---|
467 | =head2 appendAttribute ( attrib_node)
|
---|
468 |
|
---|
469 | Appends the attribute node to the list of attributes (XML::XPath stores
|
---|
470 | attributes in order).
|
---|
471 |
|
---|
472 | =head2 getNamespace ( prefix )
|
---|
473 |
|
---|
474 | Returns the namespace node by the given prefix
|
---|
475 |
|
---|
476 | =head2 getNamespaces / getNamespaceNodes
|
---|
477 |
|
---|
478 | Returns the namespace nodes. In list context returns a list. In scalar
|
---|
479 | context returns an array ref.
|
---|
480 |
|
---|
481 | =head2 appendNamespace ( ns_node )
|
---|
482 |
|
---|
483 | Appends the namespace node to the list of namespaces.
|
---|
484 |
|
---|
485 | =head2 getPrefix
|
---|
486 |
|
---|
487 | Returns the prefix of this element
|
---|
488 |
|
---|
489 | =head2 getExpandedName
|
---|
490 |
|
---|
491 | Returns the expanded name of this element (not yet implemented right).
|
---|
492 |
|
---|
493 | =head2 string_value
|
---|
494 |
|
---|
495 | For elements, the string_value is the concatenation of all string_values
|
---|
496 | of all text-descendants of the element node in document order.
|
---|
497 |
|
---|
498 | =head2 toString ( [ norecurse ] )
|
---|
499 |
|
---|
500 | Output (and all children) the node to a string. Doesn't process children
|
---|
501 | if the norecurse option is a true value.
|
---|
502 |
|
---|
503 | =cut
|
---|