Question

I need to search through some content and enclose certain words with HTML. The words to search for are in an array, and I want to only replace the 2nd to last occurrence of each set of matches. So, as an example, if there are 5 matches, I want to replace the 4th match. If there are 3 matches, replace the 2nd. If there are 2 matches, replace the 2nd/last match, and if only one match then use that one.

The callback function is external to the function using the callback.

I'm thinking I need an array count -- minus one -- and then pass that number to the callback function and use it there, but maybe there's an easier way to do this?

Simplified functions are:

function repWords($content) {
    foreach ($words_array as $w) {
        $content = preg_replace_callback('/\b('.$w.'[s]?)\b/S', array(&$this,'addHtml'), $content);
    }

    return $content;
}

function addHtml($format){
   return '<span>' . $format[1] . '</span>'
}

This works as desired to replaced all words it finds, and I can set it up to randomly replace some, but I want to either replace the nth occurrence and/or replace only one occurrence (not always the first occurrence).

Was it helpful?

Solution

Based on your requirements, here is what I came up with:

// needs to be global given that you are using non-anonymous function syntax
$count = 0;

function repWords($content) {
  global $count;

  $words_array = array('some','content','foobar');

  foreach ($words_array as $w) {
    $pattern = '~\b('.$w.'s?)\b~';
    $count=0;
    preg_match_all($pattern,$content,$matches);
    $count=count($matches[0]);
    if ($count>0)
      $content = preg_replace_callback($pattern,'addHtml',$content);
  }
  return $content;
}

function addHtml ($format) {
  global $count;
  static $cc=1;
  static $pf='';

  $val=$format[1];
  $cc = ($pf!=$val) ? 1 : ++$cc;

  if ((1==$count)||($cc==($count-1)))
    $format[1]= '<span>' . $val . '</span>';

  $pf=$val;
  return $format[1];
}


$content = <<<EOC
this is some content 
some more content
this content foobar
EOC;

echo repWords($content);

output

this is <span>some</span> content 
some more <span>content</span>
this content <span>foobar</span>
  • the first "some" is wrapped because there are 2 instances of it
  • the 2nd "content" is wrapped because there are 3 instances of it
  • "foobar" is wrapped because there is only 1 instance of it

Note: this utilizes a global variable $count. In general, it's a bad idea to use global variables. But this is one of the few examples where it's grudgingly accepted, due to limitation of php and preg_replace_callback (or any function that you can specify a callback) when you opt to define the callback separately instead of use an anonymous function. If you are on php5.3+ and are willing to make addHtml an anonymous function instead, $count can be passed to the anonymous function with use. Alternatively, if all this is actually in a class, make it a class property and use $this->count instead.

Another note: The very last thing you said was "(not always the first occurrence)" I was somewhat confused by this and took it to possibly mean that you want to somehow be able to specify the nth* instead of it always being "2nd to last (or first if there's only 1" e.g. in my code example perhaps you want to change it to be every 3rd to last word instead of 2nd to last. I asked you to clarify but you didn't respond by the time I posted this solution, so I did not write the code to be flexible for that. However, hopefully you should be able to take this and alter it to suit you if that's what you want. Basically it will involve passing another arg to repWords and it will also involve using another global variable to use in addHtml, where $cc==($count-1) is used.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top