2

I have a list of several paragraph tags. Each is without any attributes e.g.

<p>First paragraph</p>
<p>Second paragraph</p>
<p>Third paragraph</p>

My goal is to find the last opening <p> tag – no matter if I only have a single paragraph or ten. I always want the last paragraph opening tag.

With

/<p>/

I get the first paragraph tag. I thought $ inverts the search direction from left-to-right to right-to-left. So basically

/<p>$/

should return the opening paragraph tag for the third paragraph from my example above; but the regex finds nothing at all.

So how to best target the last paragraph?

J0e3gan
  • 8,740
  • 10
  • 53
  • 80
rkoller
  • 1,424
  • 3
  • 26
  • 39
  • 5
    obligatory http://stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags/1732454#1732454 – Jasper Jan 18 '15 at 22:14
  • To be clear, you want **1)** the last paragraph opening tag or **2)** *the last paragraph element, beginning with its opening tag*? The difference is important, but **1)** seems to make little sense because the last opening paragraph tag...is of course *no different than any preceding opening paragraph tag* in this case (unless you are interested in its index within the input string). – J0e3gan Jan 18 '15 at 23:05
  • @j0e3gan i want case 1). I perform a `preg_replace` within a wordpress function. I want to apply a class to the very last opening `p` tag and add a padding after. The function targets only fields of the ACF plugin using wrapping `p` tags, which are wyiwyg fields only. I could have gone with a wrapping div for the fields and use :last-child in the CSS but i am not a fan of extending the html markup. That way i use the function only on a certain page and only on the wysiwyg fields and the user is able to enter extra paragraphs afterwards and the class is still applied to each last `p` tag. – rkoller Jan 18 '15 at 23:26

6 Answers6

4

$ does not change the search direction, it simply matches at the end of the text, that's all.

If you want to find the last <p>, you have to use a negative lookahead to assert there aren't any more occurences of <p>:

(?s)<p>(?!.*<p>)
Aran-Fey
  • 39,665
  • 11
  • 104
  • 149
3

use this pattern

[\s\S]*\K(<p>)

Demo

alpha bravo
  • 7,838
  • 1
  • 19
  • 23
1

This should work:

preg_match_all('#<p>.*</p>#',$string,$results);
$last_paragraph = array_pop($results[0]);
theglossy1
  • 543
  • 3
  • 13
0

Try something like this:

(<p>)([^<]*)<\/p>$

Regular expression visualization

Debuggex Demo

Here is a PHP demo to test the pattern with the sample input you provided...

<?php 

$test = <<<TEST
<p>First paragraph</p>
<p>Second paragraph</p>
<p>Third paragraph</p>
TEST;

preg_match('/<p>([^<]*)<\/p>$/', $test, $matches);

var_dump($matches);

?>

..., which outputs:

array(3) {
  [0]=>
  string(22) "<p>Third paragraph</p>"
  [1]=>
  string(3) "<p>"
  [2]=>
  string(15) "Third paragraph"
}

You can run the PHP demo on Ideone too.

As you can see, the pattern matches the last paragraph element. I just added parentheses for capture groups to give you the option of getting the last paragraph element itself or its opening tag or its text.

J0e3gan
  • 8,740
  • 10
  • 53
  • 80
0
$str = <<<MYFILE
<p>First paragraph</p>
<p>Second paragraph</p>
<p>Third paragraph</p>
MYFILE;
$matches = array();
$pattern = '/.*?(<p>(.*)<\/p>)$/';
preg_match($pattern,$str,$matches);

var_dump($matches);
Alex
  • 16,739
  • 1
  • 28
  • 51
0

A DOM Method with XPath

$doc = new DOMDocument();
$doc->loadHTML("<p>First paragraph</p><p>Second paragraph</p><p>Third paragraph</p>");

$xpath = new DOMXpath($doc);
if($elements = $xpath->query("//p[last()]"))
{
  echo $elements->item(0)->nodeName; // p
  echo $elements->item(0)->nodeValue; // Third paragraph
}

If you had a more complex HTML structure you would have to start getting explicit with your XPath Query however.

Scuzzy
  • 12,186
  • 1
  • 46
  • 46