morgon has asked for the wisdom of the Perl Monks concerning the following question:

Hi,

recently I've done things like this (sorry, I am a bit drunk and too lazy to look up the actual code - but you get the gist...):

my $subtree = $dom->find($whatever); $subtree->delete if $substree;
And I was wondering if there was a better way to do this, some kind of Groovy "safe navigation" operator that would allow one to write:
#pseudo-code - does not run!!! $dom->find($whatever)?->delete;
i.e. a method-invocation that would check if the receicer was defined at all and simply does nothing if the receiver is undefined.

I ask because something in the back of my mind tells me that I may have heard something to this effect at a conference, but as I said - I am a bit drunk...

Many thanks!

Replies are listed 'Best First'.
Re: safe navigation in perl?
by marto (Cardinal) on Dec 17, 2017 at 09:30 UTC

    Booze aside, are you using Mojo::DOM for this? Below is a very short example (it's way too early on a Sunday, and I'm very low on sleep) based upon this:

    #!/usr/bin/perl use strict; use warnings; use Mojo::DOM; my $html = <<"END_HTML"; <html> <head> <title>test</title> </head> <body> <ul> <li><a href="http://perlmonks.org" class="first">perlmonks</a></li> <li><a href="http://archive.org" class="second">archnive.org</a></li> <li><a href="http://sitedoesnotexist9999.net" class="third">fakesite</ +a></li> </ul> </body> </html> END_HTML # Create DOM my $dom = Mojo::DOM->new( $html ); # Find the first 'a' tag with 'second' class, remove the parent tag $dom->find('a.second')->first->parent->remove; print $dom->content;

    Output:

    <html> <head> <title>test</title> </head> <body> <ul> <li><a class="first" href="http://perlmonks.org">perlmonks</a></li> <li><a class="third" href="http://sitedoesnotexist9999.net">fakesite</ +a></li> </ul> </body> </html>

    So the output removes the <li> containing the URL selected. If you want to keep it remove ->parent from the code above and only the URL will be removed:

    <html> <head> <title>test</title> </head> <body> <ul> <li><a class="first" href="http://perlmonks.org">perlmonks</a></li> <li></li> <li><a class="third" href="http://sitedoesnotexist9999.net">fakesite</ +a></li> </ul> </body> </html>

    Replace 'first' for whatever (the Mojo::DOM docs are super helpful) if you want to parse a whole page for your selector. Either use eval or Try::Tiny to warn/log/ignore failure to match.

    Personally I find Mojo::DOM easy to read and deal with all sorts of crazy HTML/XML issues.

Re: safe navigation in perl?
by AnomalousMonk (Archbishop) on Dec 17, 2017 at 04:31 UTC

    Maybe something like (untested)
        $_ and $_->delete for $dom->find($whatever);
    although I'm not convinced this syntax is preferable to the two-statement version you give initially in the OP, which seems more readable/maintainable.

    Update: Of course, the for-loop expands easily into
        $_ and $_->delete for map $dom->find($_), $whatever, $whomever, @etc;
    or
        $_->delete for grep $_, map $dom->find($_), $whatever, $whomever, @etc;
    (also untested), although the nested aliasing of  $_ may make these a bit hard to follow.


    Give a man a fish:  <%-{-{-{-<

Re: safe navigation in perl?
by Your Mother (Archbishop) on Dec 17, 2017 at 05:37 UTC

    I'm with AnomalousMonk. I use that kind of idiom sometimes but I like the two statement version fine. There is (mostly) nothing wrong with this either, eval { $dom->find($whatever)->delete }, though I wouldn't do it in case of __DIE__ handling getting noisy somewhere else in the code or the consumers of it.

    If you are remembering right and there is an interesting trick to do it inline, I'd certainly like to see/learn it.

Re: safe navigation in perl?
by choroba (Cardinal) on Dec 17, 2017 at 13:44 UTC
Re: safe navigation in perl?
by marto (Cardinal) on Dec 27, 2017 at 15:34 UTC

    Further to my previous answer, and remembering this question, here's an alternative approach:

    my @products = $ua->get( $target )->res->dom->find('.product')->each; if( @products ){ # parse products here, print things etc }else{ warn "No products found"; # or don't bother with the else to be sile +nt. }

    See also Mojo::Collection and it's size method.