Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I match text in HTML that's not inside tags?

Tags:

html

regex

perl

Given a string like this:

<a href="http://blah.com/foo/blah">This is the foo link</a>

... and a search string like "foo", I would like to highlight all occurrences of "foo" in the text of the HTML -- but not inside a tag. In other words, I want to get this:

<a href="http://blah.com/foo/blah">This is the <b>foo</b> link</a>

However, a simple search-and-replace won't work, because it will match part of the URL in the <a> tag's href.

So, to express the above in the form of a question: How do I restrict a regex so that it only matches text outside of HTML tags?

Note: I promise that the HTML in question will never be anything pathological like:

<img title="Haha! Here are some angle brackets to screw you up: ><" />

Edit: Yes, of course I'm aware that there are complex libraries in CPAN that can parse even the most heinous HTML, and thus alleviate the need for such a regex. On many occasions, that's what I would use. However, this is not one of those occasions, since keeping this script short and simple, without external dependencies, is important. I just want a one-line regex.

Edit 2: Again, I know that Template::Refine::Fragment can parse all my HTML for me. If I were writing an application I would certainly use a solution like that. But this isn't an application. It's barely more than a shell script. It's a piece of disposable code. Being a single, self-contained file that can be passed around is of great value in this case. "Hey, run this program" is a much simpler instruction than, "Hey, install a Perl module and then run this-- wait, what, you've never used CPAN before? Okay, run perl -MCPAN -e shell (preferably as root) and then it's going to ask you a bunch of questions, but you don't really need to answer them. No, don't be afraid, this isn't going to break anything. Look, you don't need to answer every question carefully -- just hit enter over and over. No, I promise, it's not going to break anything."

Now multiply the above across a great deal of users who are wondering why the simple script they've been using isn't so simple anymore, when all that's changed is to make the search term boldface.

So while Template::Refine::Fragment may be the answer to someone else's HTML parsing question, it's not the answer to this question. I just want a regular expression that works on the very limited subset of HTML that the script will actually be asked to parse.

like image 844
raldi Avatar asked Feb 22 '09 03:02

raldi


People also ask

How do I get the contents between HTML tags?

The preg_match() function is the best option to extract text between HTML tags with REGEX in PHP. If you want to get content between tags, use regular expressions with preg_match() function in PHP. You can also extract the content inside element based on class name or ID using PHP.

Do all HTML tags have an intact?

No, all HTML tags don't have end tag. Example- <br> tag is used to break the line. It doesn't have an end tag. However most of the tags have end tags.

What tag allows the text to move on the page without the use of Javascript?

Definition and Usage The <noscript> tag defines an alternate content to be displayed to users that have disabled scripts in their browser or have a browser that doesn't support script.


2 Answers

If you can absolutely guarantee that there are no angle brackets in the HTML other than those used to open and close tags, this should work:

s%(>|\G)([^<]*?)($key)%$1$2<b>$3</b>%g
like image 104
David Z Avatar answered Oct 03 '22 15:10

David Z


In general, you want to parse the HTML into a DOM, and then traverse the text nodes. I would use Template::Refine for this:

#!/usr/bin/env perl

use strict;
use warnings;
use feature ':5.10';

use Template::Refine::Fragment;

my $frag = Template::Refine::Fragment->new_from_string('<p>Hello, world.  <a href="http://foo.com/">This is a test of foo finding.</a>  Here is another foo.');

say $frag->process(
    simple_replace {
        my $n = shift;
        my $text = $n->textContent;
        $text =~ s/foo/<foo>/g;
        return XML::LibXML::Text->new($text);
    } '//text()',
)->render;

This outputs:

<p>Hello, world.  <a href="http://foo.com/">This is a test of &lt;foo&gt; finding.</a>  Here is another &lt;foo&gt;.</p> 

Anyway, don't parse structured data with regular expressions. HTML is not "regular", it's "context-free".

Edit: finally, if you are generating the HTML inside your program, and you have to do transformations like this on strings, "UR DOIN IT WRONG". You should build a DOM, and only serialize it when everything has been transformed. (You can still use TR, however, via the new_from_dom constructor.)

like image 31
jrockway Avatar answered Oct 03 '22 15:10

jrockway