views:

201

answers:

3

I want to perform the following vim substitution as a one-liner in the terminal with Perl. I would prefer to allow for any occurences of whitespace or newlines, rather than explicitly catering for them as I am below.

%s/blockDontForget">\n*\s*<p><span><a\(.*\)<\/span>/blockDontForget"><p><a\1/g 

I've tried this:

perl -pi -e 's/blockDontForget"><p><span><a(.*)<\/span>/blockDontForget"><p><a$1/msg'

I presume I am misinterpreting the flags. Where am I going wrong? Thanks.

EDIT:

The above example is to strip the spans out of the following html:

<div class="block blockDontForget">
    <p><span><a href="../../../foo/bar/x/x.html">Lorem Ipsum</a></span></p>      

EDIT:

It's just the <span>'s and </span>'s that are inbetween <p> and <a> from the "blockDontForget" class
</div> that I want to remove (there are lots or these blockDontForget divs with spans inside anchors that I want to keep).

A: 

As per your original snippet:

perl -0777 -pi -e 's{blockDontForget">.*?<p>.*?<span>.*?<a(.*?)>.*?</span>}{blockDontForget"><p><a$1}sg' fileName
  • The -0777 command switch slurps the whole file in, rather than deal with it line-by-line.
  • No need for the m modifier in this case.
  • The s modifier matches newlines (\n ) with . as well, which allows you to use .*? to match intermediate newlines and spaces 0 or more times, but as few times as possible.

If you need to strip all <span>s and </span>s out, then there is a much easier way to do it:

perl -pi -e 's#</?span>##g' fileName

And if it's just the <span>s and </span>s from the "block blockDontForget" class:

perl -0777 -pi -e 's{(blockDontForget">.*?<p>).*?<span>(.*?)</span>.*?(</div>)}{$1$2$3}sg' fileName
Zaid
Thanks, is there not a way that does not require me to add in all those `.*?`'s?
Dr. Frankenstein
it's just the <span>s and </span>s that are inbetween <p> and <a> from the "block blockDontForget" class - I'm adding to this to the question now, sorry should of been clearer.
Dr. Frankenstein
A: 

I've been looking for a way to do a "zoned replacement" myself for a while now. This is the closest that I came up with:

use English qw<@LAST_MATCH_START @LAST_MATCH_END>;

#...

$snippet =~ m|\Q<div class="block blockDontForget">\E(.*?)</div>|msx 
and substr( $snippet
          , $LAST_MATCH_START[1]
          , $LAST_MATCH_END[1] - $LAST_MATCH_START[1]
      )
      =~ s|(?i:\s*</?span\b[^>]*>\s*)||msg
      ;

The more compact version would be:

     m|\Q<div class="block blockDontForget">\E(.*?)</div>|msx 
and substr( $_, $-[1], $+[1] - $-[1] ) =~ s|\s*</?span\b[^>]*>\s*||gimsx
;
Axeman
+1  A: 

Instead of limiting yourself to one-liners and regexes, which are really the wrong tools for this job (see RegEx match open tags except XHTML self-contained tags), use a tree parser. Here's your task with HTML::TreeBuilder:

#!perl
use strict;
use warnings;

use HTML::TreeBuilder;

my $html  = HTML::TreeBuilder->new;
my $root  = $html->parse_file( *DATA ); # or <>

foreach my $div ( $root->look_down( '_tag', 'div' ) ) {
    next unless class_selector( $div, 'blockDontForget' );
    foreach my $p ( $div->look_down( '_tag', 'p' ) ) {
        foreach my $span ( $p->look_down( '_tag', 'span' ) ) {
            my $a = $span->look_down( '_tag', 'a' );
            $span->replace_with( $a );
            }
        }
    };

print $root->as_HTML;

sub class_selector
    {
    my( $elem, $class ) = @_;

    scalar
    grep { /\A$class\z/ } 
    split /\s+/, 
    $elem->attr( 'class' );
    }

__END__

<div class="block">
    <p><span><a href="../../../foo/bar/x/x.html">Stay spanned</a></span></p> 
</div>

<p><span><a href="../../../foo/bar/x/x.html">Spanned</a></span></p> 

<div class="block blockDontForget">
    <p><span><a href="../../../foo/bar/x/x.html">No span</a></span></p>      
</div>

There are shorter ways to write this (without obfuscation or golfing) and many ways to generalize it, but this is probably the easiest to read and enough to get you started on a proper solution. Save this in a file and you have your one liner. It's up to you to fix up the bits to handle the argument list, pretty printing the HTML, and saving the result.

brian d foy
Thanks - this is a real eye opener.
Dr. Frankenstein