6

A better example would be this:

$string = "That is a very nice ford mustang, if only every other ford was quite as nice as this honda";

I want to replace the car names with a link for manufacturer and model if they match, or just manufacturer, but if there is a make and model it puts links within links if you use str replace for example....

$remove = array("ford mustang","ford","honda");
$replaceWith = array("<a href='fordID'>ford</a>","<a href='fordmustangID'>ford mustang</a>","<a href='hondaID'>honda</a>");

This gives the result:

"That is a very nice <a href='<a href='fordmustangID'>ford mustang</a>ID'><a href='fordmustangID'>ford mustang</a></a>, if only every other <a href='fordmustangID'>ford mustang</a> was quite as nice as this <a href='hondaID'>honda</a>"

I only want it to create a hyperlink if there isn't one already like this:

  "That is a very nice <a href='fordmustangID'>ford mustang</a>, if only every other <a href='fordID'>ford</a> was quite as nice as this <a href='hondaID'>honda</a>"
Chris
  • 5,516
  • 1
  • 26
  • 30
  • How do you expect your found words to be replaced? "dog" with "sqid", "cat" with "bird"? If so, what is "monkey" replaced with? Or, incrementally replace with the new animals until all have been used without repeating? Please, clarify. – Josh Jan 25 '12 at 17:24
  • Ah, then I suggest using `preg_replace` and iterate over each `$remove`. Negative lookaheads should do it. – Josh Jan 25 '12 at 19:33

1 Answers1

4

edit:

Took me a good while, but here's what I came up with:

function replaceLinks($replacements, $string){
    foreach($replacements as $key=>$val){
        $key=strtolower((string)$key);
        $newReplacements[$key]=array();
        $newReplacements[$key]['id']=$val;
        //strings to make sure the search isn't in front of
        $newReplacements[$key]['behinds']=array();
        //strings to make sure the search isn't behind
        $newReplacements[$key]['aheads']=array();
        //check for other searches this is a substring of
        foreach($replacements as $key2=>$val2){
            $key2=(string)$key2;
            /* 
            //debugging
            $b = ($key=='11 22'&&$key2=='11 22 33');
            if($b){
                l('strlen $key2: '.strlen($key2));
                l('strlen $key: '.strlen($key));
                l('strpos: '.(strpos($key2,$key)));

            }
            */
            //the second search is longer and the first is a substring of it
            if(strlen($key2)>strlen($key) && ($pos=strpos($key2,$key))!==false){
                //the first search isn't at the start of the second search ('the ford' and 'ford')
                if($pos!=0){
                    $newReplacements[$key]['behinds'][]=substr($key2,0,$pos);
                }
                //it's not at the end ('ford' and 'fords')
                if(($end=$pos+strlen($key))!=strlen($key2)){
                    $newReplacements[$key]['aheads'][]=substr($key2,$end);
                }
            }
        }
    }
    foreach($newReplacements as $key=>$item){
        //negative lookbehind for words or >
        $tmp="/(?<![\w>=])";
        //negative lookbehinds for the beginnings of other searches that this search is a subtring of
        foreach($item['behinds'] as $b){
            $tmp.="(?<!$b)";
        }
        //the actual search
        $tmp.="($key)";
        //negative lookaheads for ends of other searches that this is a substring of.
        foreach($item['aheads'] as $a){
            $tmp.="(?!$a)";
        }
        //case insensitive
        $tmp.='/ie';
        $replacementMatches[]=$tmp;
    }
    return preg_replace($replacementMatches,'"<a href=\"".$newReplacements[strtolower("$1")]["id"]."\">$1</a>"' ,$string);

}

Pass it an array such as the one you were talking about:

$replaceWith = array('ford mustang'=>123,'ford'=>42,'honda'=>324);

and a string:

$string = "That is a very nice ford mustang, if only every other ford was quite as nice as this honda";

echo replaceLinks($replaceWith,$string);

It gives precedence to larger string keys, so if you have ford and ford mustang, it will replace ford mustang with the link.




Not very practical, but might work.

$string = "That is a very nice ford mustang, if only every other ford was quite as nice as this honda";
$remove = array("/(?<![\w>])ford mustang(?![\w<])/",'/(?<![>\w])ford(?! mustang)(?![<\w])/',"/(?<![>\w])honda(?![<\w])/");
$replaceWith = array("<a href='fordmustangID'>ford mustang</a>","<a href='fordID'>ford</a>","<a href='hondaID'>honda</a>");
echo preg_replace($remove, $replaceWith,$string);

I used regular expressions with negative lookaheads and lookbehinds to make sure the portion of the string we're replacing isn't part of an alphanumeric sequence (like 12ford23 or afford) or touching the start or end tag of an element.

mowwwalker
  • 16,634
  • 25
  • 104
  • 157
  • That is pretty much exactly what I am aiming to do. Is it at all possible to make it more efficient by say... //////// I know the two lines of code below are inaccurate bit it is just to show what I mean $replaceWith = array(array(123,ford mustang),array(42,ford),(324,honda)); echo preg_replace($remove, "".$replaceWith[0][1]."",$string); The reason for the need for efficiency is as there will be several thousand makes and models. I know there needs to be a look in there - possibly a preg match or something similar to make the loop. – Chris Jan 25 '12 at 20:17
  • WOW!!!!! That is some crazy stuff, works like a dream. Thank you very very much for taking the time to work that out for me!!!! Last and final question, is it possible to make it case insensitive when comparing? "That is a very nice ford mustang, if only every other ford was quite as nice as this honda, but if you had a Ford mustang or a classic mini you would be a very happy man!" else it only compares lower or upper case. Thank you again – Chris Jan 26 '12 at 13:48
  • If you use: "$replaceWith = array('Ford Mustang'=>123,'Ford'=>42,'Honda'=>324);" seems to only happen when the string's case doesn't match the array's case The hyperlinks are empty :S – Chris Jan 27 '12 at 00:21
  • @chris, Alright, added a negative lookbehind for the equals sign. – mowwwalker Mar 13 '12 at 20:19
  • THANK YOU VERY MUCH!!!!!!! I couldn't work out how to message you and wasn't sure if you would find this. But thank you very much! – Chris Mar 13 '12 at 20:30
  • @chris, Yeah, it notifies people when you comment on their posts, or tag them with the @ sign. Happy coding! – mowwwalker Mar 13 '12 at 20:42