0

I am trying to parse a "wrong html" to fix it using perl regex. The wrong html is the following: <p>foo<p>bar</p>foo</p>

I would like perl regex to return me the : <p>foo<p>

I tried something like: '|(<p\b[^>]*>(?!</p>)*?<p[^>]*>)|' with no success because I cannot repeat (?!</p>)*?

Is there a way in Perl Regex to say all charactère except the following sequence (in my case </p>)

Andy Lester
  • 91,102
  • 13
  • 100
  • 152
Tompap
  • 1
  • When writing questions, you need to escape HTML/XML and code using backticks (`). – Marcelo Cantos Feb 18 '11 at 12:06
  • 3
    Obligatory http://stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags/1732454#1732454 – BoltClock Feb 18 '11 at 12:06
  • 1
    @BoltClock, +1 and the response http://stackoverflow.com/questions/4840988/the-recognizing-power-of-modern-regexes it's impressive what a modern regex can match, if you can be bothered writing one ;) – tobyodavies Feb 18 '11 at 12:27
  • @Obligatory: Please don't use that link any more. It is confusing to beginners, and only clever to those who understand. – Andy Lester Mar 01 '11 at 22:18

5 Answers5

1

Try something like:

<p>(?:(?!</?p>).)*</p>(?!(?:(?!</?p>).)*(<p>|$))

A quick break down:

<p>(?:(?!</?p>).)*</p>

matches <p> ... </p> that does not contain either <p> and </p>. And the part:

(?!(?:(?!</?p>).)*(<p>|$))

is "true" when looking ahead ((?! ... )) there is no <p> or the end of the input ((<p>|$)), without any <p> and </p> in between ((?:(?!</?p>).)*).

A demo:

my $txt="<p>aaa aa a</p> <p>foo <p>bar</p> foo</p> <p> bb <p>x</p> bb</p>";
while($txt =~ m/(<p>(?:(?!<\/?p>).)*<\/p>)(?!(?:(?!<\/?p>).)*(<p>|$))/g) {
  print "Found: $1\n";
}

prints:

Found: <p>bar</p>
Found: <p>x</p>

Note that this regex trickery only works for <p>baz</p> in the string:

<p>foo <p>bar</p> <p>baz</p> foo</p>

<p>bar</p> is not matched! After replacing <p>baz</p>, you could do a 2nd run on the input, in which case <p>bar</p> will be matched.

Bart Kiers
  • 166,582
  • 36
  • 299
  • 288
  • Thanks for your quick help :) Based on your solution, I tried also: `|(

    ]*>(?:(?!

    ).)*

    ]*>)|` seems to works correctly, is there something wrong that I should use your solution ?

    – Tompap Feb 18 '11 at 13:03
  • @Tompap, but that matches `

    foo

    ` from your string `

    foo

    bar

    foo`...
    – Bart Kiers Feb 18 '11 at 14:54
1

I concur with Andy. Parsing nontrivial HTML with regexps is a world of pain.

Have a good look at HTML::TreeBuilder::XPath and HTML::DOM for making structural changes to HTML documents.

0

This regexp:

<p>(?:(?!</p>).)*?<p>

when matched with

<p>foo<p>bar</p>foo</p>

results in

<p>foo<p>
Kamil Szot
  • 17,436
  • 6
  • 62
  • 65
0

If you're trying to validate HTML then consider a module like HTML::Tidy or HTML::Lint.

Andy Lester
  • 91,102
  • 13
  • 100
  • 152
0

Perhaps Marpa::HTML would help you. Read some interesting abilities it has on the author's blog about it. The short of it is that the parser works with the interpreter (I probably am getting some of the semantics incorrect) to figure out what should be present based on what CAN be present at a certain logical place in the code.

The examples shown therein fix similar problems as you seem to be dealing with in a much more consistent way than employing regexes which will inevitably suffer from edge cases.

Marpa::HTML comes with a command-line utility, built using the module, called html_fmt. This implements a parsing engine to fix and pretty-print html. Here is an example. If 'bad.html' contains <p>foo<p>bar</p>foo</p> then html_fmt bad.html gives:

<!-- Following start tag is replacement for a missing one -->
<html>
  <!-- Following start tag is replacement for a missing one -->
  <head>
  </head>
  <!-- Preceding end tag is replacement for a missing one -->
  <!-- Following start tag is replacement for a missing one -->
  <body>
    <p>
      foo
    </p>
    <!-- Preceding end tag is replacement for a missing one -->
    <p>
      bar
    </p>
    foo
    <!-- Next line is cruft -->
    </p>
  </body>
  <!-- Preceding end tag is replacement for a missing one -->
</html>
<!-- Preceding end tag is replacement for a missing one -->
Joel Berger
  • 20,180
  • 5
  • 49
  • 104