1

I'm missing somethings that make me fail on using recursive (?R).

An example to explain my problem 'clearly':

$str1 = "somes text -start bla bla FIND bla bla bla FIND bla FIND bla end-";
$str2 = "somes text -start bla bla FIND bla bla bla FIND bla FIND bla end-";
$my_pattern = "-start .*(FIND).* end-";

preg_replace_callback($my_pattern, 'callback', $str1.$str2);

It will only match the very last FIND.

With the 'ungreedy' option i'll match the 1st FIND of both $str.

But how can i get all of them ? I tried to used '(?R)' but i dont really understand how it work.

Thank.

EDIT: The real work is to find all the 'title' property betweem <a> & </a>. I know it's not optimise to use regex to parse html but it's just a work from school to learn regex.

That's why i didnt put the real work, i wanted to understand and be able to do it myself.

<html>
 <head><title>Nice page</title></head>
<body>
    Hello World
 <a href=http://cyan.com title="a link">
                this is a link
 </a>
<br />
<a href=http://www.riven.com> Here too <img src=wrong.image title="and again">
    <span>Even that<div title="same">all the same</div></span>
</a>
</body>
</html>

My job is too put every titles in uppercase (title="A LINK" for example) using regex.

My last pattern was:

#<a .* title=\"(.*)\".*</a>#Uis

Made me catch (title="a link") and (title="and again"). Your method should work (stribizhev) but i didnt succeed to implement it, i'm still on it.

10
  • what do you want? to get all the FIND or want to replace all FIND? Commented Sep 12, 2015 at 10:46
  • 1
    Several problems: Since pattern delimiters are missing, hyphens are seen as delimiters and not as literal characters. I think you confuse repetition and recursion, you don't need recursion here. You need to search about greedy quantifiers too. See the PHP manual and this post: stackoverflow.com/questions/5319840/… Commented Sep 12, 2015 at 11:09
  • @MubinKhalid : Yeah i want to replace them all using callback function. to CasimiretHippolyte : Yeah forgot the delimiter on my example, i'm using '#' as delimiter. Commented Sep 12, 2015 at 11:41
  • You need a solution based on \G operator. I wish I could help but I'm on a mobile now. The regex would look like (?:-start|(?!^)\G).*?(FIND)(?=.*end-). Instead of .*, you might need a tempered greedy token: (?:(?!-start|end-|FIND).)*. And even a sinleline flag #s at the end. Commented Sep 12, 2015 at 12:02
  • @stribizhev Hmm its seems to be right but i dont understand your pattern very well... Could you please explain it steps by steps ? I'm not familliar with the \G operator Commented Sep 14, 2015 at 18:26

2 Answers 2

1

UPDATED ANSWER - CHANGING CASE IN HTML

You need to use DOMDocument with DOMXPath to safely get all title attributes and change them with mb_strtoupper:

$html = "<<YOUR_HTML>>";
$dom = new DOMDocument('1.0', 'UTF-8');
$dom->loadHTML($html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);

$xpath = new DOMXPath($dom);
$titles = $xpath->query('//a[@title]');

foreach($titles as $title) { 
   $title->setAttribute("title", mb_strtoupper($title->getAttribute("title"), 'UTF-8'));
}

echo $dom->saveHTML();

See IDEONE demo.

The //a[@title] xpath gets <a> elements (a) with an attribute title.

I use mb_strtoupper assuming you have UTF8 input. Please adjust accordingly, or if you are not planning to use Unicode, just use strtoupper.

ORIGINAL ANSWER BEFORE UPDATE

Here is a regex that will let you replace all FIND substrings inside the -start and -end:

(-start|(?!^)\G)(.*?)FIND(?=.*end-)

See demo

Replace with $1$2NEW_WORD.

PHP code:

$re = "#(-start|(?!^)\G)(.*?)FIND(?=.*end-)#"; 
$str = "somes text -start bla bla FIND bla bla bla FIND bla FIND bla end-"; 
$subst = "$1$2NEW_WORD"; 
$result = preg_replace($re, $subst, $str);
echo $result;

NOTE: If you have several start-end blocks, you will most probably need a tempered greedy token (?:(?!-start|end-|FIND).)* instead of .*? and .*.

The regex breakdown:

  • (-start|(?!^)\G) - This group contains two alternatives:
    • -start - matches the literal string -start
    • (?!^)\G - asserts the position in the original input string right after the last successful match. \G can also assert the beginning of the string, but we exclude it with the negative look-ahead.
  • (.*?) - Match any number of characters but as few as possible
  • FIND - literal string FIND
  • (?=.*end-) - only if there is literal string end- after the FIND.

For more information on \G operator, see When is \G useful application in a regex? and What good is \G in a regular expression?.

Sign up to request clarification or add additional context in comments.

8 Comments

Could you please explain to me how this "|(?!^)\G)" works pls ?
\G asserts the position at the beginning of the string (as ^) or the end of the last successful match. That means, it enforces all multiple matches to be consecutive in the input string. The negative lookahead before \G restricts it to match just consecutive matches. Since -start is the first alternative in the first group, it is checked for first. If a match is found (i.e. somethingFIND) then all consecutive matches (anything after the first FIND and again FIND) are matched.
i do not succeed to adapt it on my work... i'm missing somethings :x
Just as a bonus for you. But do not use it: with large pages this will lead to catastrophic backtracking one day.
Please check this updated code with '/(<a\s+[^<]*\btitle=")([^"<]*)(?="[^<]*>)/i'. I will delete it, and you must delete the "school" comment.
|
0

If using preg_replace_callback why wouldn't reluctant .*? be convenient.

$my_pattern = "/-start(.*?)end-/s";

$str = preg_replace_callback($my_pattern, function($matches) {
  return str_replace("FIND", "<b>FIND</b>", $matches[0]);
}, $str1.$str2);

Or do something else in callback. What are you trying to achieve?

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.