how to replace string with another one by one with preg_replace - php

I have this code and my question is how can I use preg_replace to replace string one by one
$arr = ["{A}","{B}","{C}","{A}"];
$string = "{A}{B}{C}{A}";
foreach ($arr as $item){
$replacement = "<span class=\"c\">{$item}</span>";
$new_String = preg_replace("/$item/",$replacement ,$string);
}
the result is this :
<span class="c">
<span class="c">{A}</span>
</span>
<span class="c">{B}</span>
<span class="c">{C}</span>
<span class="c">
<span class="c">{A}</span>
</span>
Because I have 2 {A} in my string preg_replace make 2 span for both of the {A} .
how to fix this ?

You are using preg_replace a wrong way: the main interest of this function is to take a regex pattern as parameter, and a pattern isn't to describe a fixed string but can describe several kind of strings (and this way you don't have to use a foreach loop since you can replace the whole string in 1 pass), example:
$result = preg_replace('/{[A-G]}/', '<span class="c">$0</span>', $string);
Other way, since you want to replace only fixed strings, you can use strtr that also does the job in one pass:
$arr = ["{A}","{B}","{C}"];
$rep = array_map(function($i) { return '<span class="c">' . $i . '</span>'; }, $arr);
$trans = array_combine($arr, $rep);
$result = strtr($string, $trans);

In addition to Casimir's suggested snippets, you can also use this one:
Code: (Demo)
$arr = ["{A}", "{B}", "{C}", "{A}"];
$start = '<span class="c">';
$end = '</span>';
$string = "{A}{B}{C}{A}";
foreach (array_unique($arr) as $item) {
$string = str_replace($item, $start . $item . $end, $string);
}
echo $string;
It alters how you define the replacement, but avoids regex.
As a more robust, solution (if your project requires it), using strtr() is ideal for multiple replacements on the same string because it avoid replacing a replacement. As you can see in Casimir's answer, there's a bit of preparation required.
Here's a language-construct version of the Casimir's technique: (Demo)
$arr = ["{A}", "{B}", "{C}", "{A}"];
foreach (array_unique($arr) as $item) {
$translator[$item] = '<span class="c">' . $item . '</span>';
}
var_export($translator);
echo "\n\n---\n\n";
$string = "{A}{B}{C}{A}";
echo strtr($string, $translator);
Output:
array (
'{A}' => '<span class="c">{A}</span>',
'{B}' => '<span class="c">{B}</span>',
'{C}' => '<span class="c">{C}</span>',
)
---
<span class="c">{A}</span><span class="c">{B}</span><span class="c">{C}</span><span class="c">{A}</span>

Related

PHP: Find substrings, wrap in tags with dynamic attributes

I'm working on a Glossary auto-tooltip function for a site. This will allow the admin to enter Glossary terms with corresponding definitions, then those words will be made into hoverable tooltip triggers anywhere they appear on the front end.
Here's the method I'm using:
All content is filtered before output through a function which searches with a regular expression for glossary terms and wraps them in tags with a class that is then picked up by javascript to handle the hover display of the tooltip.
What I need it to do, and can't figure out, is to also include the appropriate definition as a title attribute on the span.
Here is my code so far: (the initial arrays are just examples, there are many more entries in each)
function glossary_highlight( $content ) {
$glossary = ['Lorem', 'Ipsum'];
$definitions = ['Lorem is a type of crocodile', 'Ipsum is a kitchen utensil'];
$patterns = [];
foreach ($glossary as $term) {
$patterns[] = '/\b' . $term . '\b/i';
}
$wrapper = '<span class="tooltip" title="XXXX">$0</span>';
$wrapped = preg_replace($patterns, $wrapper, $content);
return $wrapped;
}
I need XXXX to be replaced with the appropriate entry in the $definitions array. I can't perform the regular expression within the foreach loop because then it may start adding spans within the titles of other spans if a definition contains another glossary term, etc.
The desired effect - given a $content of "Lorem ipsum dolor sit amet", the return from this function should be:
<span class="tooltip" title="Lorem is a type of crocodile">Lorem</span> <span class="tooltip" title="Ipsum is a kitchen utensil">ipsum</span> dolor sit amet
My first inclination was to do something like:
$wrapper = '<span class="tooltip" title="' . $definitions[$key] . '">$0</span>';
where $key would be relative to the index of the glossary term in question, but I can't figure out how to work that into the foreach.
If I can't figure it all out in PHP I guess I will just apply the tooltip span and send the definition array to javascript to append the title attribute from there, but I'd rather not split the basic task across two languages...
I'm sure there is a totally simple answer here but my brain will not see it! Can anyone help?
EDIT: thanks to lampyridae for his help below the code works as follows:
function glossary_highlight( $content ) {
$glossary = ['Lorem', 'Ipsum'];
$definitions = ['Lorem is a type of crocodile', 'Ipsum is a kitchen utensil'];
$patterns = [];
foreach ($glossary as $term) {
$patterns[] = '/\b' . $term . '\b/i';
}
$wrappers = [];
foreach ($definitions as $definition) {
$wrappers[] = '<span class="tooltip" title="' . $definition . '">$0</span>';
}
$wrapped = preg_replace($patterns, $wrappers, $content);
return $wrapped;
}
I see two options:
Run preg_replace once for each term. If the texts are long, the terms many and this is done in real time this may become a performance issue.
Supply an array of replacements just as you do for patterns.
I think number 2 would be generally faster and still simple. The idea is to build wrappers like so:
$wrappers = [];
foreach ($definitions as $definition) {
$wrappers[] = '<span class="tooltip" title="' . $definition . '">$0</span>';
}
…then use the wrappers:
$wrapped = preg_replace($patterns, $wrappers, $content);
I would store your tokens in a single associative array. It's just cleaner and much easier to see matches.
$words = [
'lorem' => 'Lorem is a type of crocodile',
'ipsum' => 'Ipsum is a kitchen utensil',
];
Then build patterns. Note the use of \b word boundary anchor here so that we match lorem but not splorems.
$patterns = [];
foreach (array_keys($words) as $word) {
$patterns[] = '/\b' . $word . '\b/i';
}
Then use preg_replace_callback() to generate a custom replacement string on the fly:
$newContent = preg_replace_callback($patterns, function ($matches) use ($words) {
return
'<span class="tooltip" title="' .
$words[strtolower($matches[0])] .
'">' . $matches[0] . '</span>';
}, $content);

PHP find multiple words in string and wrap in <span> tags

Im finding keyword "paintball" in a string, and wrapping it in span tags to change it colour to red like this...
$newoutput = str_replace("Paintball", "<span style=\"color:red;\">Paintball</span>", $output);
echo $newoutput;
Which works, but people are writing it in the field as "Paintball", "paintball", "Paint Ball", "paint ball" etc.
Is there a better way of doing this rather than repeating it for every word?
Ideally something like...
$words = "Paintball", "paintball", "Paint Ball", "paint ball";
$newoutput = str_replace("($words)", "<span>$1</span>", $output);
But im not sure how to write it.
Ok, so a mixture of answers got me here...
$newoutput = preg_replace("/(paint\s*ball|airsoft|laser\s*tag)/i", "<span>$1</span>", $output);
echo $newoutput;
And it works perfectly, thank you very much!
This should work for you:
(Here I just use preg_replace() with the modifier i for case insensitivity)
<?php
$output = "LaSer Tag";
$newoutput = preg_replace("/(Airsoft|Paintball|laser tag)/i", "<span style=\"color:red;\">$1</span>", $output);
echo $newoutput;
?>
EDIT:
Besides that this is invalid syntax:
$words = "Paintball", "paintball", "Paint Ball", "paint ball";
and you probably meant this:
$words = ["Paintball", "paintball", "Paint Ball", "paint ball"];
//^ See here array syntax ^
You can use something like this then
$newoutput = preg_replace("/(" . implode("|", $words) . ")/i", "<span style=\"color:red;\">$1</span>", $output);
You could use preg_replace, passing it an array of words and doing a case-insensitive match using the i modifier:
$patterns = array('/paint\s?ball/i', '/airsoft/i', '/laser tag/i');
$newoutput = preg_replace($patterns, '<span style="color:red;">$0</span>', $string);
The \s? in /paint\s?ball/ matches zero or one spaces - you could use \s* to match zero or more instead if you preferred.
Simple and easy to use
$title = get_the_title($post->ID);
$arraytitle = explode(" ", $title);
for($i=0;$i<sizeof($arraytitle);$i++){
if($i == 0){
echo $arraytitle[0].' ';
}elseif($i >= 0){
echo '<span>'.$arraytitle[$i].'</span>'." ";
}
}
use this:
function colorMyWord($word, $output)
{
$target_words = array('paintball', 'paint ball', 'airsoft');
if(in_array($target_words, $word))
{
$newoutput = str_ireplace($word, "<span style=\"color:red;\">$word</span>", $output);
return $newoutput;
}
Usage :
echo colorMyWord('Paintball', 'I need a Paintball');

preg_replace : how get what is replaced

I want to show cards from regex codes :
ah displays As of hearts,
kc displays King of clubs
...
I used preg_replace() to do that in this way :
$arr = array('ah', 'kh', 'qh', ..., '3c', '2c');
$regex = '';
foreach ($arr as $i => $card)
{
$regex .= $card;
if ($i < count($arr) - 1)
$regex .= '|';
}
$message = preg_replace('#('.$regex.')#', '<img src="'.$dontknow.'.png" class="card" alt="" />', $message);
I don't know what value put in the src attribute, I want to tell to preg_replace() "when you find 'ah' you put ah.png, if it's kc then $dontknow == 'kc' etc.
Someone could be bring me some help ?
You can do this:
$message = preg_replace('#('.$regex.')#',
'<img src="$1.png" class="card" alt="" />', $message);
Use $1 reference - it is a link to a first group that PHP matched through preg_replace
You just need to use $n in your replacement to reference to a certain matching group (n is a number).
Since we pulled out the big guns:
Let's use preg_quote() to escape regex reserved characters in your array
PHP has a great set of function to juggle with arrays, let's use implode() instead of that ugly loop
From the comments, I realised that you need to add word boundaries \b to prevent false matches like yeah being replaced to ye<img...>. See this demo
Code:
$message = 'foo qh bar';
$arr = array('ah', 'kh', 'qh', '3c', '2c');
$escaped_arr = array_map(function($v){
return preg_quote($v, '#');
}, $arr); // Anonymous function requires PHP 5.3+
$message = preg_replace('#\b('.implode('|', $escaped_arr).')\b#', '<img src="$1.png" class="card" alt="" />', $message);
echo $message;
Online demo
You don't need that for loop. Here's a slightly improved version with the correct regex.
$arr = array('ah', 'kh', 'qh', ..., '3c', '2c');
$message = preg_replace('/('. implode('|'. $regex) .')/is', '<img src="$1.png" class="card" alt="" />', $message);
You should improve your 'markers' indicating the playing cards, by adding a special character (like # in the following example) to them. This will protect you from inadvertently changing other text passages:
$arr = array('ah', 'kh', 'qh', 'ac', '3c', '2c');
$txt = 'This is a line with cards #ah and #qh but with a potential head-ache.';
$rx = '##('.join('|',$arr).')#';
echo preg_replace($rx,"<img src=$1.jpg>",$txt);

Replace text ignoring HTML tags

I have a simple text with HTML tags, for example:
Once <u>the</u> activity reaches the resumed state, you can freely add and remove fragments to the activity. Thus, <i>only</i> while the activity is in the resumed state can the <b>lifecycle</b> of a <hr/> fragment change independently.
I need to replace some parts of this text ignoring its html tags when I do this replace, for example this string - Thus, <i>only</i> while I need to replace with my string Hello, <i>its only</i> while . Text and strings to be replaced are dynamically. I need your help with my preg_replace pattern
$text = '<b>Some html</b> tags with <u>and</u> there are a lot of tags <i>in</i> this text';
$arrayKeys= array('Some html' => 'My html', 'and there' => 'is there', 'in this text' => 'in this code');
foreach ($arrayKeys as $key => $value)
$text = preg_replace('...$key...', '...$value...', $text);
echo $text; // output should be: <b>My html</b> tags with <u>is</u> there are a lot of tags <i>in</i> this code';
Please help me to find solution. Thank you
Basically we're going to build dynamic arrays of matches and patterns off of plain text using Regex. This code only matches what was originally asked for, but you should be able to get an idea of how to edit the code from the way I've spelled it all out. We're catching either an open or a close tag and white space as a passthru variable and replacing the text around it. This is setup based on two and three word combinations.
<?php
$text = '<b>Some html</b> tags with <u>and</u> there are a lot of tags <i>in</i> this text';
$arrayKeys= array(
'Some html' => 'My html',
'and there' => 'is there',
'in this text' =>'in this code');
function make_pattern($string){
$patterns = array(
'!(\w+)!i',
'#^#',
'! !',
'#$#');
$replacements = array(
"($1)",
'!',
//This next line is where we capture the possible tag or
//whitespace so we can ignore it and pass it through.
'(\s?<?/?[^>]*>?\s?)',
'!i');
$new_string = preg_replace($patterns,$replacements,$string);
return $new_string;
}
function make_replacement($replacement){
$patterns = array(
'!^(\w+)(\s+)(\w+)(\s+)(\w+)$!',
'!^(\w+)(\s+)(\w+)$!');
$replacements = array(
'$1\$2$3\$4$5',
'$1\$2$3');
$new_replacement = preg_replace($patterns,$replacements,$replacement);
return $new_replacement;
}
foreach ($arrayKeys as $key => $value){
$new_Patterns[] = make_pattern($key);
$new_Replacements[] = make_replacement($value);
}
//For debugging
//print_r($new_Patterns);
//print_r($new_Replacements);
$new_text = preg_replace($new_Patterns,$new_Replacements,$text);
echo $new_text."\n";
echo $text;
?>
Output
<b>My html</b> tags with <u>is</u> there are a lot of tags <i>in</i> this code
<b>Some html</b> tags with <u>and</u> there are a lot of tags <i>in</i> this text
Here we go. this piece of code should work, assuming you're respecting only twp constraints :
Pattern and replacement must have the same number of words. (Logical, since you want to keep position)
You must not split a word around a tag. (<b>Hel</b>lo World won't work.)
But if these are respected, this should work just fine !
<?php
// Splits a string in parts delimited with the sequence.
// '<b>Hey</b> you' becomes '~-=<b>~-=Hey~-=</b>~-= you' that make us get
// array ("<b>", "Hey" " you")
function getTextArray ($text, $special) {
$text = preg_replace ('#(<.*>)#isU', $special . '$1' . $special, $text); // Adding spaces to make explode work fine.
return preg_split ('#' . $special . '#', $text, -1, PREG_SPLIT_NO_EMPTY);
}
$text = "
<html>
<div>
<p>
<b>Hey</b> you ! No, you don't have <em>to</em> go!
</p>
</div>
</html>";
$replacement = array (
"Hey you" => "Bye me",
"have to" => "need to",
"to go" => "to run");
// This is a special sequence that you must be sure to find nowhere in your code. It is used to split sequences, and will disappear.
$special = '~-=';
$text_array = getTextArray ($text, $special);
// $restore is the array that will finally contain the result.
// Now we're only storing the tags.
// We'll be story the text later.
//
// $clean_text is the text without the tags, but with the special sequence instead.
$restore = array ();
for ($i = 0; $i < sizeof ($text_array); $i++) {
$str = $text_array[$i];
if (preg_match('#<.+>#', $str)) {
$restore[$i] = $str;
$clean_text .= $special;
}
else {
$clean_text .= $str;
}
}
// Here comes the tricky part.
// We wanna keep the position of each part of the text so the tags don't
// move after.
// So we're making the regex look like (~-=)*Hey(~-=)* you(~-=)*
// And the replacement look like $1Bye$2 me $3.
// So that we keep the separators at the right place.
foreach ($replacement as $regex => $newstr) {
$regex_array = explode (' ', $regex);
$regex = '(' . $special . '*)' . implode ('(' . $special . '*) ', $regex_array) . '(' . $special . '*)';
$newstr_array = explode (' ', $newstr);
$newstr = "$1";
for ($i = 0; $i < count ($regex_array) - 1; $i++) {
$newstr .= $newstr_array[$i] . '$' . ($i + 2) . ' ';
}
$newstr .= $newstr_array[count($regex_array) - 1] . '$' . (count ($regex_array) + 1);
$clean_text = preg_replace ('#' . $regex . '#isU', $newstr, $clean_text);
}
// Here we re-split one last time.
$clean_text_array = preg_split ('#' . $special . '#', $clean_text, -1, PREG_SPLIT_NO_EMPTY);
// And we merge with $restore.
for ($i = 0, $j = 0; $i < count ($text_array); $i++) {
if (!isset($restore[$i])) {
$restore[$i] = $clean_text_array[$j];
$j++;
}
}
// Now we reorder everything, and make it go back to a string.
ksort ($restore);
$result = implode ($restore);
echo $result;
?>
Will output Bye me ! No, you don't need to run!
[EDIT] Now supporting a custom pattern, which allows to avoid adding useless spaces.

Add id attribute to hyperlinks through PHP Regular Expressions

I am still relatively new to Regular Expressions and feel My code is being too greedy. I am trying to add an id attribute to existing links in a piece of code. My functions is like so:
function addClassHref($str) {
//$str = stripslashes($str);
$preg = "/<[\s]*a[\s]*href=[\s]*[\"\']?([\w.-]*)[\"\']?[^>]*>(.*?)<\/a>/i";
preg_match_all($preg, $str, $match);
foreach ($match[1] as $key => $val) {
$pattern[] = '/' . preg_quote($match[0][$key], '/') . '/';
$replace[] = "<a id='buttonRed' href='$val'>{$match[2][$key]}</a>";
}
return preg_replace($pattern, $replace, $str);
}
This adds the id tag like I want but it breaks the hyperlink. For example:
If the original code is : Link
Instead of <a id="class" href="http://www.google.com">Link</a>
It is giving
<a id="class" href="http">Link</a>
Any suggestions or thoughts?
Do not use regular expressions to parse XML or HTML.
$doc = new DOMDocument();
$doc->loadHTML($html);
$all_a = $doc->getElementsByTagName('a');
$firsta = $all_a->item(0);
$firsta->setAttribute('id', 'idvalue');
echo $doc->saveHTML($firsta);
You've got some overcomplications in your regex :)
Also, there's no need for the loop as preg_replace() will hit all the instances of the search pattern in the relevant string. The first regex below will take everything in the a tag and simply add the id attribute on at the end.
$str = 'Link' . "\n" .
'Link' . "\n" .
'Link';
$p = "{<\s*a\s*(href=[^>]*)>([^<]*)</a>}i";
$r = "<a $1 id=\"class\">$2</a>";
echo preg_replace($p, $r, $str);
If you only want to capture the href attribute you could do the following:
$p = '{<\s*a\s*href=["\']([^"\']*)["\'][^>]*>([^<]*)</a>}i';
$r = "<a href='$1' id='class'>$2</a>";
Your first subpattern ([\w.-]*) doesn't match :, thus it stops at "http".
Couldn't you just use a simple str_replace() for this? Regex seems like overkill if this is all you're doing.
$str = str_replace('<a ', '<a id="someID" ', $str);

Categories