views:

68

answers:

1

Folks,

There is so much info out there on HTML::Treebuilder that I'm surprised I can't find the answer, hopefully I'm not just missing it.

What I'm trying to do is simply parse between parent nodes, so given a html doc like this

<html>
<body>
   <a id="111" name="111"></a>
   <p>something</p>
   <p>something</p>
   <p>something</p>
   <a href=xxx">something</a>
   <a id="222" name="222"></a>
   <p>something</p>
   <p>something</p>
   <p>something</p>
   ....
 </body>
 </html>

I want to be able to get the info about that 1st anchor tag (111), then process the 3 p tags and then get the next anchor tag (222) and then process those p tags etc etc.

Its easy to get to each anchor tag

use HTML::TreeBuilder;
my $tree = HTML::TreeBuilder->new();
$tree->parse_file("index-01.htm");
foreach my $atag ( $tree->look_down( '_tag', 'a' ) ) {
    if ($atag->attr('id')) {
        # Found 'a' tag, now process the p tags until the next 'a'
    }
}

But once I find that tag how do I then get all the p tags until the next anchor?

TIA!!

+2  A: 

HTML::TreeBuilder version

#!/usr/bin/perl

use strict; use warnings;
use HTML::TreeBuilder;

my $tree = HTML::TreeBuilder->new;

$tree->parse_file(\*DATA);
$tree->elementify;
$tree->objectify_text;

foreach my $atag ( $tree->look_down( '_tag', 'a' ) ) {
    if ($atag->attr('id')) {
        printf "Found %s\n", $atag->as_XML;
        process_p( $atag );
    }
}

sub process_p {
    my ($tag) = @_;
    while ( defined( $tag ) and defined( my $next = $tag->right ) ) {
        last if lc $next->tag eq 'a';
        if ( lc $next->tag eq 'p') {
            $next->deobjectify_text;
            print $next->as_text, "\n";
        }
        $tag = $next;
    }
}

__DATA__
<html>
<body>
   <a id="111" name="111"></a>
   <p>something</p>
   <p>something</p>
   <p>something</p>sometext
   <a href=xxx">something</a>
   <a id="222" name="222"></a>
   <p>something</p>
   <p>something</p>
   <p>something</p>
 </body>
 </html>

Output:

Found <a id="111" name="111"></a>

something
something
something
Found <a id="222" name="222"></a>

something
something
something

HTML::TokeParser::Simple version

#!/usr/bin/perl

use strict; use warnings;
use HTML::TokeParser::Simple;

my $parser = HTML::TokeParser::Simple->new(\*DATA);

while ( my $tag = $parser->get_tag('a') ) {
    next unless $tag->get_attr('id');
    printf "Found %s\n", $tag->as_is;
    process_p($parser);
}

sub process_p {
    my ($parser) = @_;
    while ( my $next = $parser->get_token ) {
        if ( $next->is_start_tag('a') ) {
            $parser->unget_token($next);
            return;
        }
        elsif ( $next->is_start_tag('p') ) {
            print $parser->get_text('/p'), "\n";
        }
    }
    return;
}

Output:

Found <a id="111" name="111">
something
something
something
Found <a id="222" name="222">
something
something
something
Sinan Ünür
Thanks Sinan, this works almost perfect. I just noticed a issue in the html though, some of the "<p>something</p>" tags in the HTML actually look like "<p>something</p>sometext". When I try to run the above I get "Can't locate object method "tag" via package sometext".
Chris
Is there some way to get examine the "sometext" that is appear and also be able to continue on without a error? TIA!!
Chris
That throws a monkey wrench in things. That's because the string is not wrapped in an `HTML::Element`. I'll post a solution in a few minutes.
Sinan Ünür
That would be awesome Sinan, thanks!!
Chris
@Chris Done! However, I am beginning to think `HTML::TokeParser::Simple` might be more appropriate for this task.
Sinan Ünür
Hmm, I still get the same error, the only difference now is I'm getting a warning of "$text->attr('text')" being a uninitialized value. Do you think I should be using TokeParser::Simple? I thought it also might have a problem with text outside of a tag, but I'll give it a shot.
Chris
@Chris Fixed. The original script worked for the content you provided, but presumably, the `<p>...</p>` contain other elements than just text nodes.
Sinan Ünür
Thanks Sinan, I'm going to give them both a try and see what works best. Thanks Again!!
Chris
Sinan, I wonder if I could trouble you for one more bit of advise, Using the treebuilder method is there some way to have "$next->as_text" be the whole parent p tag? Right now if there is a nested P tag within the parent P tag then it stops at the end of the 1st nested P tag (if that makes sense).
Chris
See http://www.w3.org/TR/html401/struct/text.html#h-9.3.1 "The P element represents a paragraph. It cannot contain block-level elements (including P itself)." Therefore, the HTML is invalid. However, in my tests, it seems like all the text in the nested `P` tags is printed. I am not inclined to investigate this further unless you post a separate question with applicable data.
Sinan Ünür