Changeset 346

Show
Ignore:
Timestamp:
03/08/08 09:55:39 (2 months ago)
Author:
ingy
Message:
Complete pQuery::DOM.
Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/src/ingy/pQuery/lib/pQuery/DOM.pm

    r345 r346  
    4040    $self->{'_ignore_text'}        = 0; 
    4141    $self->{'_warn'}               = 0; 
    42     $self->{'_no_space_compacting'}= 0
     42    $self->{'_no_space_compacting'}= 1
    4343    $self->{'_store_comments'}     = 0; 
    44     $self->{'_store_declarations'} = 1
     44    $self->{'_store_declarations'} = 0
    4545    $self->{'_store_pis'}          = 0; 
    46     $self->{'_p_strict'} = 0; 
     46    $self->{'_p_strict'}           = 0; 
    4747 
    4848    # Parse attributes passed in as arguments 
     
    6565    $self->{'_pos'} = undef; # pull it back up again 
    6666    $self->ignore_ignorable_whitespace(0); 
     67    $self->store_comments(1); 
    6768    $self->no_space_compacting(1); 
    6869 
     
    104105        } 
    105106        $_; 
    106     } @{$dom->{_body}{_content}}; 
     107    } @{$dom->{_body}{_content} || [$dom->{_content}[-1]]}; 
    107108    return wantarray ? @dom : $dom[0]; 
     109} 
     110 
     111sub createElement { 
     112    my ($class, $tag) = @_; 
     113    return unless $tag =~ /^\w+$/; 
     114    return $class->fromHTML('<' . $tag . '>'); 
     115} 
     116 
     117sub createComment { 
     118    my ($class, $comment) = @_; 
     119    return $class->fromHTML('<!--' . $comment . '-->'); 
    108120} 
    109121 
     
    123135sub innerHTML { 
    124136    my $self = shift; 
     137 
     138    return if $self->{_tag} eq '~comment'; 
     139 
    125140    if (@_) { 
    126         my $dom = $self->html_to_dom(@_)
    127         die "XXX - need to insert dom here"
     141        $self->{_content} = [pQuery::DOM->fromHTML($_[0])]
     142        return $_[0]
    128143    } 
    129144 
    130145    my $html = ''; 
    131146 
    132     my @list = @{$self->{_content}}; 
     147    my @list = @{$self->{_content} || []}; 
    133148    for (@list) { 
    134149        _to_html($_, \$html); 
     
    139154 
    140155sub getElementsByTagName { 
    141     die "Not yet implemented"; 
     156    my ($self, $tag) = @_; 
     157    my $found = []; 
     158    _find($self, $found, sub { $_->{_tag} eq $tag}); 
     159    return wantarray ? @$found : $found->[0]; 
    142160} 
    143161 
    144162sub getElementById { 
    145     die "Not yet implemented"; 
     163    my ($self, $id) = @_; 
     164    my $found = []; 
     165    _find($self, $found, sub { $_->{id} and $_->{id} eq $id}); 
     166    return wantarray ? @$found : $found->[0]; 
    146167} 
    147168 
    148169sub nodeType { 
    149     return 1; 
     170    return $_[0]->{_tag} eq '~comment' ? 8 : 1; 
    150171} 
    151172 
    152173sub nodeName { 
     174    return '#comment' if $_[0]->{_tag} eq '~comment'; 
    153175    return uc($_[0]->{_tag}); 
     176} 
     177 
     178sub tagName { 
     179    return '' if $_[0]->{_tag} eq '~comment'; 
     180    return $_[0]->nodeName; 
     181} 
     182 
     183sub nodeValue { 
     184    my $self = shift; 
     185    return $self->{text} if $self->{_tag} eq '~comment'; 
     186    return; 
    154187} 
    155188 
     
    159192 
    160193sub setAttribute { 
    161     die "Not yet implemented"; 
     194    $_[0]->{lc($_[1])} = $_[2]; 
     195    return; 
     196
     197 
     198sub removeAttribute { 
     199    delete $_[0]->{lc($_[1])}; 
    162200} 
    163201 
    164202sub hasAttributes { 
    165     die "Not yet implemented"; 
    166 
    167  
    168 sub removeAttribute { 
    169     die "Not yet implemented"; 
    170 
    171  
    172 sub tagName { 
    173     die "Not yet implemented"; 
     203    my $self = shift; 
     204    return 0 if $self->{_tag} eq '~comment'; 
     205    return scalar(grep /^[a-z0-9]/, keys %$self) ? 1 : 0; 
    174206} 
    175207 
    176208sub className { 
     209    if ($_[1]) { 
     210        return $_[0]->setAttribute(class => $_[1]); 
     211    } 
    177212    $_[0]->getAttribute("class"); 
    178213} 
    179214 
    180 sub nodeValue { 
    181     die "Not yet implemented"; 
    182 } 
    183  
    184215sub parentNode { 
    185     die "Not yet implemented"
     216    return $_[0]->{_parent}
    186217} 
    187218 
    188219sub childNodes { 
    189     die "Not yet implemented"
     220    return @{$_[0]->{_content} || []}
    190221} 
    191222 
    192223sub firstChild { 
    193     die "Not yet implemented"; 
     224    return unless $_[0]->{_content}; 
     225    return $_[0]->{_content}[0]; 
    194226} 
    195227 
    196228sub lastChild { 
    197     die "Not yet implemented"; 
     229    return unless $_[0]->{_content}; 
     230    return $_[0]->{_content}[-1]; 
     231
     232 
     233sub appendChild { 
     234    my ($self, $elem) = @_; 
     235    return unless defined $elem; 
     236    my $content = $self->{_content} ||= []; 
     237    push @$content, $elem; 
     238    return $elem; 
    198239} 
    199240 
    200241sub previousSibling { 
    201     die "Not yet implemented"; 
     242    die "pQuery::DOM does not support the previousSibling method"; 
    202243} 
    203244 
    204245sub nextSibling { 
    205     die "Not yet implemented"; 
     246    die "pQuery::DOM does not support the nextSibling method"; 
    206247} 
    207248 
    208249sub attributes { 
    209     die "Not yet implemented"; 
    210 
    211  
     250    die "pQuery::DOM::attributes not yet implemented"; 
     251
    212252 
    213253################################################################################ 
     
    216256sub _to_html { 
    217257    my ($elem, $html) = @_; 
    218     if (ref $elem) { 
    219         $$html .= '<' . $elem->{_tag}; 
    220         $$html .= qq{ id="$elem->{id}"} 
    221             if $elem->{id}; 
    222         $$html .= qq{ class="$elem->{class}"} 
    223             if $elem->{class}; 
    224         for (sort keys %$elem) { 
    225             next if /^(_|id$|class$)/i; 
    226             $$html .= qq{ $_="$elem->{$_}"}; 
    227         } 
    228         
    229         $$html .= '>'; 
    230         for my $child (@{$elem->{_content}}) { 
    231             _to_html($child, $html); 
    232         } 
    233         $$html .= '</' . $elem->{_tag} . '>'; 
    234     } 
    235     else { 
     258    if (not ref $elem) { 
    236259        $$html .= $elem; 
    237     } 
     260        return; 
     261    } 
     262    if ($elem->{_tag} eq '~comment') { 
     263        $$html .= '<!--' . $elem->{text} . '-->'; 
     264        return; 
     265    } 
     266    $$html .= '<' . $elem->{_tag}; 
     267    $$html .= qq{ id="$elem->{id}"} 
     268        if $elem->{id}; 
     269    $$html .= qq{ class="$elem->{class}"} 
     270        if $elem->{class}; 
     271    for (sort keys %$elem) { 
     272        next if /^(_|id$|class$)/i; 
     273        $$html .= qq{ $_="$elem->{$_}"}; 
     274    } 
     275    
     276    $$html .= '>'; 
     277    for my $child (@{$elem->{_content} || []}) { 
     278        _to_html($child, $html); 
     279    } 
     280    $$html .= '</' . $elem->{_tag} . '>'; 
     281
     282 
     283sub _find { 
     284    my ($elem, $found, $test) = @_; 
     285    $_ = $elem; 
     286    if (&$test()) { 
     287        push @$found, $_; 
     288    } 
     289 
     290    map _find($_, $found, $test), grep ref($_), @{$elem->{_content} || []}; 
    238291} 
    239292 
     
    255308pQuery needs a DOM to represent its content. Since there is no standard 
    256309DOM class in Perl, pQuery implements its own. 
     310 
     311=head1 DOM MODEL 
     312 
     313It is important to note that pQuery::DOM is essentially a subclass of 
     314HTML::TreeBuilder and HTML::Element. As such, text nodes are just 
     315strings and therefore cannot have methods called on them. 
     316 
     317This implies that the DOM methods previousSibling and nextSibling 
     318wouldn't really work correctly. Therefore they are not implemented. 
     319 
     320To deal with children, use the childNodes method which returns a list 
     321of all the child nodes. Then you can you standard Perl idioms to 
     322process them. 
     323 
     324Note that all pQuery::DOM objects are either HTML Element nodes or HTML 
     325Comment nodes. 
    257326 
    258327=head1 METHODS 
     
    268337=item fromHTML($html) 
    269338 
    270 This is the main constructor method. It takes any HTML string and returns the 
    271 DOM object tree that represents that HTML. 
     339This is the main constructor method. It takes any HTML string and 
     340returns the DOM object tree that represents that HTML. 
     341 
     342=item createElement($tag) 
     343 
     344Create a new HTML Element node with the specified tag. This node will be empty 
     345and have no attributes. 
     346 
     347=item createComment($text) 
     348 
     349Create a new HTML Comment node with the given text value. 
    272350 
    273351=back 
     
    290368with the tree created from the HTML. 
    291369 
     370=item getElementById($id) 
     371 
     372Returns a list of all the elements with the given id. Normally this 
     373should be one or zero elements, since two nodes should not have the same 
     374id in the same DOM. 
     375 
     376=item getElementsByTagName($tag) 
     377 
     378Returns a list of all elements in the tree that have the given tag name. 
     379 
     380=item nodeType 
     381 
     382Returns 1 if the node is an HTML Element and 8 if it is a comment node. 
     383Never returns 3 (the type value of a text node) since text nodes in the 
     384DOM are just strings. 
     385 
    292386=item nodeName 
    293387 
     
    295389HTML tag name. 
    296390 
     391Returns '#comment' if the node is a comment node. 
     392 
     393=item tagName 
     394 
     395Returns the nodeName of the element if it is an HTML Element. (Returns 
     396'' for comment nodes.) 
     397 
     398=item nodeValue 
     399 
     400This method returns undef unless the node is a comment. In most DOMs 
     401this attribute contains the value for Text nodes (which are just 
     402strings here). 
     403 
     404=item getAttribute($attr) 
     405 
     406Returns the value of the specified attribute. 
     407 
     408=item setAttribute($attr, $value) 
     409 
     410Sets the specified attribute to the given value. 
     411 
     412=item removeAttribute($attr) 
     413 
     414Removes the specified attribute. 
     415 
     416=item hasAttributes 
     417 
     418Returns 1 if the node has attributes. Otherwise returns 0. 
     419 
     420=item id id($value) 
     421 
     422Same as C<getAttribute('id')> or C<setAttribute('id', $value)>. 
     423 
     424=item className className($value) 
     425 
     426Same as C<getAttribute('class')> or C<setAttribute('class', $value)>. 
     427 
     428=item parentNode 
     429 
     430Returns the node's parent node. 
     431 
     432=item childNodes 
     433 
     434Returns a list of the node's child nodes. 
     435 
     436=item firstChild 
     437 
     438Returns the node's first child node. May be a string (aka a text node). 
     439 
     440=item lastChild 
     441 
     442Returns the node's last child node. May be a string (aka a text node). 
     443 
     444=item appendChild($node) 
     445 
     446Adds a node (or a string) to the end of the current node's children. 
     447 
    297448=back 
    298449 
  • trunk/src/ingy/pQuery/t/dom.t

    r344 r346  
    1 use Test::More tests => 8; 
     1use Test::More tests => 33; 
     2use strict; 
     3use warnings; 
    24 
    35use pQuery::DOM; 
     
    1719is $dom->innerHTML, 'I <b>Like</b> <ul>Pie</ul>!', 
    1820    'innerHTML works'; 
     21 
     22$dom = pQuery::DOM->fromHTML(<<'...'); 
     23<div id="div1"> 
     24    <div id="div2"> 
     25        <span id="span1">Foo</span>  
     26        <span id="span2">Bar</span> 
     27        <span id="span3">Baz</span> 
     28    </div> 
     29</div> 
     30... 
     31my $span = $dom->getElementById('span1'); 
     32is $span->innerHTML, "Foo", 'getElementById works'; 
     33is $span->nodeValue, undef, 'nodeValue is undefined for Element'; 
     34is $span->tagName, "SPAN", 'tagName works'; 
     35 
     36my @spans = $dom->getElementsByTagName('span'); 
     37is scalar(@spans), 3, "Found 3 spans"; 
     38is $spans[0]->innerHTML, "Foo", '1st value is correct'; 
     39is $spans[1]->innerHTML, "Bar", '2nd value is correct'; 
     40 
     41$span->setAttribute('Foo', 'Bar'); 
     42is $span->toHTML, '<span id="span1" foo="Bar">Foo</span>', 
     43    'setAttribute works'; 
     44 
     45is (pQuery::DOM->fromHTML('<div>')->hasAttributes, 0, 'hasAttributes works'); 
     46is (pQuery::DOM->fromHTML('<div XXX="yyy">')->hasAttributes, 1, 'hasAttributes works'); 
     47 
     48$span->removeAttribute('Id'); 
     49is $span->toHTML, '<span foo="Bar">Foo</span>', 
     50    'removeAttribute works'; 
     51 
     52is $span->parentNode->id, 'div2', 
     53    'parentNode works'; 
     54 
     55my $div2 = $dom->getElementById('div2'); 
     56my @children = $div2->childNodes; 
     57is scalar(@children), 7, 'div2 has 7 children'; 
     58is ref($children[0]), '', 'child 1 is text node'; 
     59 
     60is $div2->firstChild, "\n        ", "firstChild works"; 
     61is $div2->lastChild, "\n    ", "lastChild works"; 
     62 
     63$dom = pQuery::DOM->fromHTML('<div>xxx<!-- yyy -->zzz</div>'); 
     64my @elems = pQuery::DOM->fromHTML('<div>xxx<!-- yyy -->zzz</div>')->childNodes; 
     65my $comment = $elems[1]; 
     66is $comment->nodeType, 8, 'Handle comment nodes'; 
     67is $comment->nodeValue, ' yyy ', 'Handle comment node value'; 
     68is $comment->hasAttributes, 0, "Comments don't have attributes"; 
     69is $comment->nodeName, '#comment', 'Comment has proper nodeName'; 
     70is $comment->tagName, '', 'Comment has no tagName'; 
     71is $comment->parentNode->tagName, 'DIV', 'Comment has parentNode'; 
     72is $dom->toHTML, '<div>xxx<!-- yyy -->zzz</div>', 'Comments work in toHTML'; 
     73is $comment->innerHTML, undef, 'Comments have no innerHTML'; 
     74 
     75$dom->innerHTML('I <b>Like</b> Pie'); 
     76 
     77is $dom->toHTML, '<div>I <b>Like</b> Pie</div>', 'Setting innerHTML works'; 
     78 
     79my $div = pQuery::DOM->createElement('div'); 
     80$div->id('new-div'); 
     81$div->className('classy'); 
     82$comment = pQuery::DOM->createComment('I am remarkable'); 
     83$div->appendChild('Foo'); 
     84$div->appendChild($comment); 
     85is $div->toHTML, '<div id="new-div" class="classy">Foo<!--I am remarkable--></div>', 
     86        'createElement, createComment and appendChild work';