1############################################################################ 2# Copyright (c) 1998 Enno Derksen 3# All rights reserved. 4# This program is free software; you can redistribute it and/or modify it 5# under the same terms as Perl itself. 6############################################################################ 7# 8# Functions added to the XML::DOM implementation for XQL support 9# 10# NOTE: This code is a bad example of how to use XML::DOM. 11# I'm accessing internal (private) data members for a little gain in performance. 12# When the internal DOM implementation changes, this code will no longer work. 13# But since I maintain XML::DOM, it's easy for me to keep them in sync. 14# Regular users are adviced to use the XML::DOM API as described in the 15# documentation. 16# 17 18use strict; 19package XML::XQL::DOM; 20 21BEGIN 22{ 23 require XML::DOM; 24 25 # import constant field definitions, e.g. _Doc 26 import XML::DOM::Node qw{ :Fields }; 27} 28 29package XML::DOM::Node; 30 31sub xql 32{ 33 my $self = shift; 34 35 # Odd number of args, assume first is XQL expression without 'Expr' key 36 unshift @_, 'Expr' if (@_ % 2 == 1); 37 my $query = new XML::XQL::Query (@_); 38 my @result = $query->solve ($self); 39 $query->dispose; 40 41 @result; 42} 43 44sub xql_sortKey 45{ 46 my $key = $_[0]->[_SortKey]; 47 return $key if defined $key; 48 49 $key = XML::XQL::createSortKey ($_[0]->[_Parent]->xql_sortKey, 50 $_[0]->xql_childIndex, 1); 51#print "xql_sortKey $_[0] ind=" . $_[0]->xql_childIndex . " key=$key str=" . XML::XQL::keyStr($key) . "\n"; 52 $_[0]->[_SortKey] = $key; 53} 54 55# Find previous sibling that is not a text node with ignorable whitespace 56sub xql_prevNonWS 57{ 58 my $self = shift; 59 my $parent = $self->[_Parent]; 60 return unless $parent; 61 62 for (my $i = $parent->getChildIndex ($self) - 1; $i >= 0; $i--) 63 { 64 my $node = $parent->getChildAtIndex ($i); 65 return $node unless $node->xql_isIgnorableWS; # skip whitespace 66 } 67 undef; 68} 69 70# True if it's a Text node with just whitespace and xml::space != "preserve" 71sub xql_isIgnorableWS 72{ 73 0; 74} 75 76# Whether the node should preserve whitespace 77# It should if it has attribute xml:space="preserve" 78sub xql_preserveSpace 79{ 80 $_[0]->[_Parent]->xql_preserveSpace; 81} 82 83sub xql_element 84{ 85#?? I wonder which implemention is used for e.g. DOM::Text, since XML::XQL::Node also has an implementation 86 []; 87} 88 89sub xql_document 90{ 91 $_[0]->[_Doc]; 92} 93 94sub xql_node 95{ 96 my $kids = $_[0]->[_C]; 97 if (defined $kids) 98 { 99 # Must copy the list or else we return a blessed reference 100 # (which causes trouble later on) 101 my @list = @$kids; 102 return \@list; 103 } 104 105 []; 106} 107 108#?? implement something to support NamedNodeMaps in DocumentType 109sub xql_childIndex 110{ 111 $_[0]->[_Parent]->getChildIndex ($_[0]); 112} 113 114#?? implement something to support NamedNodeMaps in DocumentType 115sub xql_childCount 116{ 117 my $ch = $_[0]->[_C]; 118 defined $ch ? scalar(@$ch) : 0; 119} 120 121sub xql_parent 122{ 123 $_[0]->[_Parent]; 124} 125 126sub xql_DOM_nodeType 127{ 128 $_[0]->getNodeType; 129} 130 131sub xql_nodeType 132{ 133 $_[0]->getNodeType; 134} 135 136# As it appears in the XML document 137sub xql_xmlString 138{ 139 $_[0]->toString; 140} 141 142package XML::DOM::Element; 143 144sub xql_attribute 145{ 146 my ($node, $attrName) = @_; 147 148 if (defined $attrName) 149 { 150 my $attr = $node->getAttributeNode ($attrName); 151 defined ($attr) ? [ $attr ] : []; 152 } 153 else 154 { 155 defined $node->[_A] ? $node->[_A]->getValues : []; 156 } 157} 158 159# Used by XML::XQL::Union::genSortKey to generate sort keys 160# Returns the maximum of the number of children and the number of Attr nodes. 161sub xql_childCount 162{ 163 my $n = scalar @{$_[0]->[_C]}; 164 my $m = defined $_[0]->[_A] ? $_[0]->[_A]->getLength : 0; 165 return $n > $m ? $n : $m; 166} 167 168sub xql_element 169{ 170 my ($node, $elem) = @_; 171 172 my @list; 173 if (defined $elem) 174 { 175 for my $kid (@{$node->[_C]}) 176 { 177 push @list, $kid if $kid->isElementNode && $kid->[_TagName] eq $elem; 178 } 179 } 180 else 181 { 182 for my $kid (@{$node->[_C]}) 183 { 184 push @list, $kid if $kid->isElementNode; 185 } 186 } 187 \@list; 188} 189 190sub xql_nodeName 191{ 192 $_[0]->[_TagName]; 193} 194 195sub xql_baseName 196{ 197 my $name = $_[0]->[_TagName]; 198 $name =~ s/^\w*://; 199 $name; 200} 201 202sub xql_prefix 203{ 204 my $name = $_[0]->[_TagName]; 205 $name =~ /([^:]+):/; 206 $1; 207} 208 209sub xql_rawText 210{ 211 my ($self, $recurse) = @_; 212 $recurse = 1 unless defined $recurse; 213 214 my $text = ""; 215 216 for my $kid (@{$self->xql_node}) 217 { 218 my $type = $kid->xql_nodeType; 219 220 # type=1: element 221 # type=3: text (Text, CDATASection, EntityReference) 222 if (($type == 1 && $recurse) || $type == 3) 223 { 224 $text .= $kid->xql_rawText ($recurse); 225 } 226 } 227 $text; 228} 229 230sub xql_text 231{ 232 my ($self, $recurse) = @_; 233 $recurse = 1 unless defined $recurse; 234 235 my $j = -1; 236 my @text; 237 my $last_was_text = 0; 238 239 # Collect text blocks. Consecutive blocks of Text, CDataSection and 240 # EntityReference nodes should be merged without stripping and without 241 # putting spaces in between. 242 for my $kid (@{$self->xql_node}) 243 { 244 my $type = $kid->xql_nodeType; 245 246 if ($type == 1) # 1: element 247 { 248 if ($recurse) 249 { 250 $text[++$j] = $kid->xql_text ($recurse); 251 } 252 $last_was_text = 0; 253 } 254 elsif ($type == 3) # 3: text (Text, CDATASection, EntityReference) 255 { 256 ++$j unless $last_was_text; # next text block 257 $text[$j] .= $kid->getData; 258 $last_was_text = 1; 259 } 260 else # e.g. Comment 261 { 262 $last_was_text = 0; 263 } 264 } 265 266 # trim whitespace and remove empty blocks 267 my $i = 0; 268 my $n = @text; 269 while ($i < $n) 270 { 271 # similar to XML::XQL::trimSpace 272 $text[$i] =~ s/^\s+//; 273 $text[$i] =~ s/\s+$//; 274 275 if ($text[$i] eq "") 276 { 277 splice (@text, $i, 1); # remove empty block 278 $n--; 279 } 280 else 281 { 282 $i++; 283 } 284 } 285 join (" ", @text); 286} 287 288# 289# Returns a list of text blocks for this Element. 290# A text block is a concatenation of consecutive text-containing nodes (i.e. 291# Text, CDATASection or EntityReference nodes.) 292# For each text block a reference to an array is returned with the following 293# 3 items: 294# [0] index of first node of the text block 295# [1] index of last node of the text block 296# [2] concatenation of the raw text (of the nodes in this text block) 297# 298# The text blocks are returned in reverse order for the convenience of 299# the routines that want to modify the text blocks. 300# 301sub xql_rawTextBlocks 302{ 303 my ($self) = @_; 304 305 my @result; 306 my $curr; 307 my $prevWasText = 0; 308 my $kids = $self->[_C]; 309 my $n = @$kids; 310 for (my $i = 0; $i < $n; $i++) 311 { 312 my $node = $kids->[$i]; 313 # 3: text (Text, CDATASection, EntityReference) 314 if ($node->xql_nodeType == 3) 315 { 316 if ($prevWasText) 317 { 318 $curr->[1] = $i; 319 $curr->[2] .= $node->getData; 320 } 321 else 322 { 323 $curr = [$i, $i, $node->getData]; 324 unshift @result, $curr; 325 $prevWasText = 1; 326 } 327 } 328 else 329 { 330 $prevWasText = 0; 331 } 332 } 333 @result; 334} 335 336sub xql_replaceBlockWithText 337{ 338 my ($self, $start, $end, $text) = @_; 339 for (my $i = $end; $i > $start; $i--) 340 { 341 # dispose of the old nodes 342 $self->removeChild ($self->[_C]->[$i])->dispose; 343 } 344 my $node = $self->[_C]->[$start]; 345 my $newNode = $self->[_Doc]->createTextNode ($text); 346 $self->replaceChild ($newNode, $node)->dispose; 347} 348 349sub xql_setValue 350{ 351 my ($self, $str) = @_; 352 # Remove all children 353 for my $kid (@{$self->[_C]}) 354 { 355 $self->removeChild ($kid); 356 } 357 # Add a (single) text node 358 $self->appendChild ($self->[_Doc]->createTextNode ($str)); 359} 360 361sub xql_value 362{ 363 XML::XQL::elementValue ($_[0]); 364} 365 366sub xql_preserveSpace 367{ 368 # attribute value should be "preserve" (1), "default" (0) or "" (ask parent) 369 my $space = $_[0]->getAttribute ("xml:space"); 370 $space eq "" ? $_[0]->[_Parent]->xql_preserveSpace : ($space eq "preserve"); 371} 372 373package XML::DOM::Attr; 374 375sub xql_sortKey 376{ 377 my $key = $_[0]->[_SortKey]; 378 return $key if defined $key; 379 380 $_[0]->[_SortKey] = XML::XQL::createSortKey ($_[0]->xql_parent->xql_sortKey, 381 $_[0]->xql_childIndex, 0); 382} 383 384sub xql_nodeName 385{ 386 $_[0]->getNodeName; 387} 388 389sub xql_text 390{ 391 XML::XQL::trimSpace ($_[0]->getValue); 392} 393 394sub xql_rawText 395{ 396 $_[0]->getValue; 397} 398 399sub xql_value 400{ 401 XML::XQL::attrValue ($_[0]); 402} 403 404sub xql_setValue 405{ 406 $_[0]->setValue ($_[1]); 407} 408 409sub xql_baseName 410{ 411 my $name = $_[0]->getNodeName; 412 $name =~ s/^\w*://; 413 $name; 414} 415 416sub xql_prefix 417{ 418 my $name = $_[0]->getNodeName; 419 $name =~ s/:\w*$//; 420 $name; 421} 422 423sub xql_parent 424{ 425 $_[0]->[_UsedIn]->{''}->{Parent}; 426} 427 428sub xql_childIndex 429{ 430 my $map = $_[0]->[_UsedIn]; 431 $map ? $map->getChildIndex ($_[0]) : 0; 432} 433 434package XML::DOM::Text; 435 436sub xql_rawText 437{ 438 $_[0]->[_Data]; 439} 440 441sub xql_text 442{ 443 XML::XQL::trimSpace ($_[0]->[_Data]); 444} 445 446sub xql_setValue 447{ 448 $_[0]->setData ($_[1]); 449} 450 451sub xql_isIgnorableWS 452{ 453 $_[0]->[_Data] =~ /^\s*$/ && 454 !$_[0]->xql_preserveSpace; 455} 456 457package XML::DOM::CDATASection; 458 459sub xql_rawText 460{ 461 $_[0]->[_Data]; 462} 463 464sub xql_text 465{ 466 XML::XQL::trimSpace ($_[0]->[_Data]); 467} 468 469sub xql_setValue 470{ 471 $_[0]->setData ($_[1]); 472} 473 474sub xql_nodeType 475{ 476 3; # it contains text, so XQL spec states it's a text node 477} 478 479package XML::DOM::EntityReference; 480 481BEGIN 482{ 483 # import constant field definitions, e.g. _Data 484 import XML::DOM::CharacterData qw{ :Fields }; 485} 486 487sub xql_text 488{ 489 $_[0]->getData; 490} 491 492sub xql_rawText 493{ 494 XML::XQL::trimSpace ($_[0]->[_Data]); 495} 496 497sub xql_setValue 498{ 499 $_[0]->setData ($_[1]); 500} 501 502sub xql_nodeType 503{ 504 3; # it contains text, so XQL spec states it's a text node 505} 506 507package XML::DOM::Document; 508 509BEGIN 510{ 511 # import constant field definitions, e.g. _TagName 512 import XML::DOM::Element qw{ :Fields }; 513} 514 515sub xql_sortKey 516{ 517 ""; 518} 519 520sub xql_element 521{ 522 my ($node, $elem) = @_; 523 524 my @list; 525 if (defined $elem) 526 { 527 for my $kid (@{$node->[_C]}) 528 { 529 push @list, $kid if $kid->isElementNode && $kid->[_TagName] eq $elem; 530 } 531 } 532 else 533 { 534 for my $kid (@{$node->[_C]}) 535 { 536 push @list, $kid if $kid->isElementNode; 537 } 538 } 539 \@list; 540} 541 542sub xql_parent 543{ 544 undef; 545} 546 547# By default the elements in a document don't preserve whitespace 548sub xql_preserveSpace 549{ 550 0; 551} 552 553package XML::DOM::DocumentFragment; 554 555BEGIN 556{ 557 # import constant field definitions, e.g. _TagName 558 import XML::DOM::Element qw{ :Fields }; 559} 560 561sub xql_element 562{ 563 my ($node, $elemName) = @_; 564 565 my @list; 566 if (defined $elemName) 567 { 568 for my $kid (@{$node->[_C]}) 569 { 570 push @list, $kid if $kid->isElementNode && $kid->[_TagName] eq $elemName; 571 } 572 } 573 else 574 { 575 for my $kid (@{$node->[_C]}) 576 { 577 push @list, $kid if $kid->isElementNode; 578 } 579 } 580 \@list; 581} 582 583sub xql_parent 584{ 585 undef; 586} 587 5881; # module loaded successfuly 589 590__END__ 591 592=head1 NAME 593 594XML::XQL::DOM - Adds XQL support to XML::DOM nodes 595 596=head1 SYNOPSIS 597 598 use XML::XQL; 599 use XML::XQL::DOM; 600 601 $parser = new XML::DOM::Parser; 602 $doc = $parser->parsefile ("file.xml"); 603 604 # Return all elements with tagName='title' under the root element 'book' 605 $query = new XML::XQL::Query (Expr => "book/title"); 606 @result = $query->solve ($doc); 607 608 # Or (to save some typing) 609 @result = XML::XQL::solve ("book/title", $doc); 610 611 # Or (see XML::DOM::Node) 612 @result = $doc->xql ("book/title"); 613 614=head1 DESCRIPTION 615 616XML::XQL::DOM adds methods to L<XML::DOM> nodes to support XQL queries 617on XML::DOM document structures. 618 619See L<XML::XQL> and L<XML::XQL::Query> for more details. 620L<XML::DOM::Node> describes the B<xql()> method. 621 622 623