How can I remove HTML span tags with a Perl one liner?
I want to perform the following vim substitution as a one-liner in the terminal with Perl. I would prefer to all开发者_开发技巧ow 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).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.
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
;
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
精彩评论