Just finished making this function. Basically it is suppose to look through a string and try to find any placeholder variables, which would be place between two curly brackets {}. It grabs the value between the curly brackets and uses it to look through an array where it should match the key. Then it replaces the curly bracket variable in the string with the value in the array of the matching key.
It has a few problems though. First is when I var_dump($matches) it puts puts the results in an array, inside an array. So I have to use two foreach() just the reach the correct data.
I also feel like its heavy and I've been looking over it trying to make it better but I'm somewhat stumped. Any optimizations I missed?
function dynStr($str,$vars) {
preg_match_all("/\{[A-Z0-9_]+\}+/", $str, $matches);
foreach($matches as $match_group) {
foreach($match_group as $match) {
$match = str_replace("}", "", $match);
$match = str_replace("{", "", $match);
$match = strtolower($match);
$allowed = array_keys($vars);
$match_up = strtoupper($match);
$str = (in_array($match, $allowed)) ? str_replace("{".$match_up."}", $vars[$match], $str) : str_replace("{".$match_up."}", '', $str);
}
}
return $str;
}
$variables = array("first_name"=>"John","last_name"=>"Smith","status"=>"won");
$string = 'Dear {FIRST_NAME} {LAST_NAME}, we wanted to tell you that you {STATUS} the competition.';
echo dynStr($string,$variables);
//Would output: 'Dear John Smith, we wanted to tell you that you won the competition.'
I think for such a simple task you don't need to use RegEx:
$variables = array("first_name"=>"John","last_name"=>"Smith","status"=>"won");
$string = 'Dear {FIRST_NAME} {LAST_NAME}, we wanted to tell you that you {STATUS} the competition.';
foreach($variables as $key => $value){
$string = str_replace('{'.strtoupper($key).'}', $value, $string);
}
echo $string; // Dear John Smith, we wanted to tell you that you won the competition.
I hope I'm not too late to join the party — here is how I would do it:
function template_substitution($template, $data)
{
$placeholders = array_map(function ($placeholder) {
return strtoupper("{{$placeholder}}");
}, array_keys($data));
return strtr($template, array_combine($placeholders, $data));
}
$variables = array(
'first_name' => 'John',
'last_name' => 'Smith',
'status' => 'won',
);
$string = 'Dear {FIRST_NAME} {LAST_NAME}, we wanted to tell you that you have {STATUS} the competition.';
echo template_substitution($string, $variables);
And, if by any chance you could make your $variables keys to match your placeholders exactly, the solution becomes ridiculously simple:
$variables = array(
'{FIRST_NAME}' => 'John',
'{LAST_NAME}' => 'Smith',
'{STATUS}' => 'won',
);
$string = 'Dear {FIRST_NAME} {LAST_NAME}, we wanted to tell you that you have {STATUS} the competition.';
echo strtr($string, $variables);
(See strtr() in PHP manual.)
Taking in account the nature of the PHP language, I believe that this approach should yield the best performance from all listed in this thread.
EDIT: After revisiting this answer 7 years later, I noticed a potentially dangerous oversight on my side, which was also pointed out by another user. Be sure to give them a pat on the back in the form of an upvote!
If you are interested in what this answer looked like before this edit, check out the revision history
I think you can greatly simplify your code, with this (unless I'm misinterpreting some of the requirements):
$allowed = array("first_name"=>"John","last_name"=>"Smith","status"=>"won");
$resultString = preg_replace_callback(
// the pattern, no need to escape curly brackets
// uses a group (the parentheses) that will be captured in $matches[ 1 ]
'/{([A-Z0-9_]+)}/',
// the callback, uses $allowed array of possible variables
function( $matches ) use ( $allowed )
{
$key = strtolower( $matches[ 1 ] );
// return the complete match (captures in $matches[ 0 ]) if no allowed value is found
return array_key_exists( $key, $allowed ) ? $allowed[ $key ] : $matches[ 0 ];
},
// the input string
$yourString
);
PS.: if you want to remove placeholders that are not allowed from the input string, replace
return array_key_exists( $key, $allowed ) ? $allowed[ $key ] : $matches[ 0 ];
with
return array_key_exists( $key, $allowed ) ? $allowed[ $key ] : '';
Just a heads up for future people who land on this page: All the answers (including the accepted answer) using foreach loops and/or the str_replace method are susceptible to replacing good ol' Johnny {STATUS}'s name with Johnny won.
Decent Dabbler's preg_replace_callback approach and U-D13's second option (but not the first) are the only ones currently posted I see that aren't vulnerable to this, but since I don't have enough reputation to add a comment I'll just write up a whole different answer I guess.
If your replacement values contain user-input, a safer solution is to use the strtr function instead of str_replace to avoid re-replacing any placeholders that may show up in your values.
$string = 'Dear {FIRST_NAME} {LAST_NAME}, we wanted to tell you that you {STATUS} the competition.';
$variables = array(
"first_name"=>"John",
// Note the value here
"last_name"=>"{STATUS}",
"status"=>"won"
);
// bonus one-liner for transforming the placeholders
// but it's ugly enough I broke it up into multiple lines anyway :)
$replacement = array_combine(
array_map(function($k) { return '{'.strtoupper($k).'}'; }, array_keys($variables)),
array_values($variables)
);
echo strtr($string, $replacement);
Outputs: Dear John {STATUS}, we wanted to tell you that you won the competition.
Whereas str_replace outputs: Dear John won, we wanted to tell you that you won the competition.
This is the function that I use:
function searchAndReplace($search, $replace){
preg_match_all("/\{(.+?)\}/", $search, $matches);
if (isset($matches[1]) && count($matches[1]) > 0){
foreach ($matches[1] as $key => $value) {
if (array_key_exists($value, $replace)){
$search = preg_replace("/\{$value\}/", $replace[$value], $search);
}
}
}
return $search;
}
$array = array(
'FIRST_NAME' => 'John',
'LAST_NAME' => 'Smith',
'STATUS' => 'won'
);
$paragraph = 'Dear {FIRST_NAME} {LAST_NAME}, we wanted to tell you that you {STATUS} the competition.';
// outputs: Dear John Smith, we wanted to tell you that you won the competition.
Just pass it some text to search for, and an array with the replacements in.
/**
replace placeholders with object
**/
$user = new stdClass();
$user->first_name = 'Nick';
$user->last_name = 'Trom';
$message = 'This is a {{first_name}} of a user. The user\'s {{first_name}} is replaced as well as the user\'s {{last_name}}.';
preg_match_all('/{{([0-9A-Za-z_]+)}}/', $message, $matches);
foreach($matches[1] as $match)
{
if(isset($user->$match))
$rep = $user->$match;
else
$rep = '';
$message = str_replace('{{'.$match.'}}', $rep, $message);
}
echo $message;
Related
Looking at how WP uses shortcodes I thoufght I could implement the same structure into a project, I assumed this would be availble somwehere but have yet to track down.
I started to parse myself starting with a preg_match_all
preg_match_all('/[[^]]*]/', $content, $match);
and that return the array with all the shortcodes inside content as expected but then looking at parsing the name, variables or array keys with values I start getting real heavy on parsing.
My current thought is to break up on spaces, then parse each but then i run into spaces in the values even though they are in quotes. So if i parse quoted data first then spaces to re-construct it seems very wasteful. I don't need to re-invent the wheel here so any input is fantastic.
example
[shortcodename key1="this is a value" key2="34"]
would like to have
Array
(
[shortcodename] => Array
(
[key1] => this is a value
[key2] => 34
)
)
here is the complete function that is working if anyone else is looking to do the same, obviously this is not meant to run user content but the called function should do any checks as this only replaces the shortcode if the funtction has a return value.
function processShortCodes($content){ // locate data inside [ ] and
//process the output, place back into content and returns
preg_match_all('/\[[^\]]*\]/', $content, $match);
$regex = '~"[^"]*"(*SKIP)(*F)|\s+~';
foreach ($match[0] as $key => $val){
$valOrig = $val; // keep uncleaned value to replace later
$val = trim(substr($val, 1, -1));
$replaced = preg_replace($regex,":",$val);
$exploded = explode(':',$replaced);
if (is_array($exploded)){
$fcall = array();
$fcallName = array_shift($exploded); // function name
if (function_exists($fcallName)){ // If function exsist then go
foreach ($exploded as $aKey => $aVal){
$arr = explode("=", $aVal);
if (substr($arr[1], 0, 1) == '&'){
$fCall[$arr[0]]=substr($arr[1], 6, -6); // quotes can be "
}else{
$fCall[$arr[0]]=substr($arr[1], 1, -1);
}
}
if ( is_array($fCall) && $fcallName ){
$replace = call_user_func($fcallName, $fCall);
if ($replace){
$content = str_replace($valOrig,$replace,$content);
}
}
}
}
}
You can try this to change all spaces not wrapped in quotes to let's say a semicolon then explode by semicolon
$regex = '~"[^"]*"(*SKIP)(*F)|\s+~';
$subject = 'hola hola "pepsi cola" yay';
$replaced = preg_replace($regex,";",$subject);
$exploded = explode(';', $replaced);
Credits
I'm getting stuck at this problem, which is
I have an array like this:
$array = [
'name' => 'John',
'email' => john#gmail.com
];
And a string sample like this:
$string = 'Hi [[name]], your email is [[email]]';
The problem is obvious, replace name with John and email with john#gmail.com.
What i attempted:
//check if $string has [[ ]] pattern
$stringHasBrackets = preg_match_all('/\[\[(.*?)\]\]/i', $string, $matchOutput);
if ($stringHasBrackets) {
foreach ($matchOutput[1] as $matchOutputKey => $stringToBeReplaced) {
if (array_key_exists($stringToBeReplaced, $array)) {
$newString = preg_replace("/\[\[(.+?)\]\]/i",
$array[$stringToBeReplaced],
$string);
}
}
}
Which led me to a new string like this:
Hi john#gmail.com, your email is john#gmail.com
Makes sense because that's what the pattern is for, but not what I wanted.
How can I solve this? I thought of using a variable in the pattern but don't know how to do it. I've read about preg_replace_callback but also don't really know how to implement it.
Thanks!
You may use preg_replace_callback like this:
$array = ['name' => 'John', 'email' => 'john#gmail.com'];
$string = 'Hi [[name]], your email is [[email]]';
echo preg_replace_callback('/\[\[(.*?)]]/', function ($m) use ($array) {
return isset($array[$m[1]]) ? $array[$m[1]] : $m[0];
}, $string);
See PHP demo.
Details
'/\[\[(.*?)]]/' matches [[...]] substrings putting what is inside the brackets into Group 1
$m holds the match object
use ($array) allows the callback to access $array variable
isset($array[$m[1]]) checks if there is a value corresponding to the found key in the $array variable. If it is found, the value is returned, else, the found match is pasted back.
preg_replace accepts arrays as regex and replacement so you can use this simpler approach:
$array = ['name' => 'John', 'email' => 'john#gmail.com'];
$string = 'Hi [[name]], your email is [[email]]';
// create array of regex using array keys
$rearr = array_map(function($k) { return '/\[\[' . $k . ']]/'; },
array_keys($array));
# pass 2 arrays to preg_replace
echo preg_replace($rearr, $array, $string) . '\n';
Output:
Hi John, your email is john#gmail.com
PHP Code Demo
You can try this,
$array = ['name' => 'John', 'email' => 'john#gmail.com'];
$string = 'Hi [[name]], your email is [[email]]';
$stringHasBrackets = preg_match_all('/\[\[(.*?)\]\]/i', $string, $matchOutput);
if ($stringHasBrackets) {
$newString = $string;
foreach ($matchOutput[1] as $matchOutputKey => $stringToBeReplaced) {
if (array_key_exists($stringToBeReplaced, $array)) {
$newString = preg_replace("/\[\[$stringToBeReplaced\]\]/i", $array[$stringToBeReplaced], $newString);
}
}
echo $newString;
}
I think here more simply would be to use str_replace function, like:
$array = [
'name' => 'John',
'email' => 'john#gmail.com'
];
$string = 'Hi [[name]], your email is [[email]]';
$string = str_replace(array_map(function ($v) {return "[[{$v}]]";},
array_keys($array)), $array, $string);
echo $string;
Updated for $array to be "untouchable"
There is a string like this:
$string = 'connector:rtp-monthly direction:outbound message:error writing data: xxxx yyyy zzzz date:2015-11-02 10:20:30';
This string is from user Input. So it will never have the same order. It's an input field which I need to split to build a DB query.
Now I would like to split the string based on words given in a array() which is like a mapper containing the words I need to find in the string. Looking like so:
$mapper = array(
'connector' => array('type' => 'string'),
'direction' => array('type' => 'string'),
'message' => array('type' => 'string'),
'date' => array('type' => 'date'),
);
Only the keys of the $mapper will be relevant. I've tried with foreach and explode like:
$parts = explode(':', $string);
But the problem is: There can be colons somewhere in the string so I don't need to explode there. I only need to explode if a colon is followed right after the mapper key. The mapper keys in this case are:
connector // in this case split if "connector:" is found
direction // untill "direction:" is found
message // untill "message:" is found
date // untill "date:" is found
But remember also, the user input can varey. So the string will always change ant the order of the string and the mapper array() will never be in the same order. So I'm not sure if explode is the right way to go, or if I should use a regex. And if so how to do it.
The desired result should be an array looking something like this:
$desired_result = array(
'connector' => 'rtp-monthly',
'direction' => 'outbound',
'message' => 'error writing data: xxxx yyyy zzzz',
'date' => '2015-11-02 10:20:30',
);
Help is much appreciated.
The trickier part of this is matching the original string. You can do it with Regex with the help of lookahead positive assertions:
$pattern = "/(connector|direction|message|date):(.+?)(?= connector:| direction:| message:| date:|$)/";
$subject = 'connector:rtp-monthly direction:outbound message:error writing data: xxxx yyyy zzzz date:2015-11-02 10:20:30';
preg_match_all($pattern, $subject, $matches, PREG_SET_ORDER );
$returnArray = array();
foreach($matches as $item)
{
$returnArray[$item[1]] = $item[2];
}
In this Regex /(connector|direction|message|date):(.+?)(?= connector:| direction:| message:| date:|$)/, you're matching:
(connector|direction|message|date) - find a keyword and capture it;
: - followed by a colon;
(.+?) - followed by any character many times non greedy, and capture it;
(?= connector:| direction:| message:| date:|$) - up until the next keyword or the end of the string, using a non-capturing look-ahead positive assertion.
The result is:
Array
(
[connector] => rtp-monthly
[direction] => outbound
[message] => error writing data: xxxx yyyy zzzz
[date] => 2015-11-02 10:20:30
)
I didn't use the mapper array just to make the example clear, but you could use implode to put the keywords together.
Our aim isto make one array that contains the values of two arrays that we would extract from the string. It is neccesary to have two arrays since there are two string delimeters we wish to consider.
Try this:
$parts = array();
$large_parts = explode(" ", $string);
for($i=0; $i<count($large_parts); $i++){
$small_parts = explode(":", $large_parts[$i]);
$parts[$small_parts[0]] = $small_parts[1];
}
$parts should now contain the desired array
Hope you get sorted out.
Here you are. The regex is there to "catch" the key (any sequence of characters, excluding blank space and ":"). Starting from there, I use "explode" to "recursively" split the string. Tested ad works good
$string = 'connector:rtp-monthly direction:outbound message:error writing data date:2015-11-02';
$element = "(.*?):";
preg_match_all( "/([^\s:]*?):/", $string, $matches);
$result = array();
$keys = array();
$values = array();
$counter = 0;
foreach( $matches[0] as $id => $match ) {
$exploded = explode( $matches[ 0 ][ $id ], $string );
$keys[ $counter ] = $matches[ 1 ][ $id ];
if( $counter > 0 ) {
$values[ $counter - 1 ] = $exploded[ 0 ];
}
$string = $exploded[ 1 ];
$counter++;
}
$values[] = $string;
$result = array();
foreach( $keys as $id => $key ) {
$result[ $key ] = $values[ $id ];
}
print_r( $result );
You could use a combination of a regular expression and explode(). Consider the following code:
$str = "connector:rtp-monthly direction:outbound message:error writing data date:2015-11-02";
$regex = "/([^:\s]+):(\S+)/i";
// first group: match any character except ':' and whitespaces
// delimiter: ':'
// second group: match any character which is not a whitespace
// will not match writing and data
preg_match_all($regex, $str, $matches);
$mapper = array();
foreach ($matches[0] as $match) {
list($key, $value) = explode(':', $match);
$mapper[$key][] = $value;
}
Additionally, you might want to think of a better way to store the strings in the first place (JSON? XML?).
Using preg_split() to explode() by multiple delimiters in PHP
Just a quick note here. To explode() a string using multiple delimiters in PHP you will have to make use of the regular expressions. Use pipe character to separate your delimiters.
$string = 'connector:rtp-monthly direction:outbound message:error writing data: xxxx yyyy zzzz date:2015-11-02 10:20:30';
$chunks = preg_split('/(connector|direction|message)/',$string,-1, PREG_SPLIT_NO_EMPTY);
// Print_r to check response output.
echo '<pre>';
print_r($chunks);
echo '</pre>';
PREG_SPLIT_NO_EMPTY – To return only non-empty pieces.
I have a piece of PHP code as follows:
$words = array(
'Art' => '1',
'Sport' => '2',
'Big Animals' => '3',
'World Cup' => '4',
'David Fincher' => '5',
'Torrentino' => '6',
'Shakes' => '7',
'William Shakespeare' => '8'
);
$text = "I like artists, and I like sports. Can you call the name of a big animal? Brazil World Cup matchers are very good. William Shakespeare is very famous in the world.";
$all_keywords = $all_keys = array();
foreach ($words as $word => $key) {
if (strpos(strtolower($text), strtolower($word)) !== false) {
$all_keywords[] = $word;
$all_keys[] = $key;
}
}
echo $keywords_list = implode(',', $all_keywords) ."<br>";
echo $keys_list = implode(',', $all_keys) . "<br>";
The code echos Art,Sport,World Cup,Shakes,William Shakespeare and 1,2,4,7,8; however, the code is very simple and is not accurate enough to echo the right keywords. For example, the code returns 'Shakes' => '7' because of the Shakespeare word in $text, but as you can see, "Shakes" can not represent "Shakespeare" as a proper keyword. Basically I want to return Art,Sport,World Cup,William Shakespeare and 1,2,4,8 instead of Art,Sport,World Cup,Shakes,William Shakespeare and 1,2,4,7,8. So, could you please help me how to develop a better code to extract the keywords without having similar problems? thanks for your help.
You may want to look at regular expressions to weed out partial matches:
// create regular expression by using alternation
// of all given words
$re = '/\b(?:' . join('|', array_map(function($keyword) {
return preg_quote($keyword, '/');
}, array_keys($words))) . ')\b/i';
preg_match_all($re, $text, $matches);
foreach ($matches[0] as $keyword) {
echo $keyword, " ", $words[$keyword], "\n";
}
The expression uses the \b assertion to match word boundaries, i.e. the word must be on its own.
Output
World Cup 4
William Shakespeare 8
You're better off using regular expressions if you want accurate matches.
I modified your original code to use them instead of strpos() as it will result in partial matches, as was the case with your code.
There's room for improvement, but hopefully you get the basic gist of it.
Let me know if you have any questions.
Code was modified to a shell script, so save to demo.php and chmod +x demo.php && ./demo.php
`
#!/usr/bin/php
//array of regular expressions to match your words/phrases
$words = array(
'/\b[Aa]rt\b/',
'/\bI\b/',
'/\bSport\b/',
'/\bBig Animals\b/' ,
'/\bWorld Cup\b/' ,
'/\bDavid Fincher\b/',
'/\bTorrentino\b/' ,
'/\bShakes\b/' ,
'/\b[sS]port[s]{0,1}\b/' ,
'/\bWilliam Shakespeare\b/',
);
$text = "I like artists and art, and I like sports. Can you call the name of a big animal? Brazil World Cup matchers are very good. William Shakespeare is very famous in the world.";
$all_keywords = array(); //changed formatting for clarity
$all_keys = array();
foreach ($words as $regex) {
$m = array();
if (preg_match_all($regex, $text, $m, PREG_OFFSET_CAPTURE)>=1)
for ($n=0;$n<count($m); ++$n) {
$match = $m[0];
foreach($match as $mm) {
$key = $mm[1]; //key is the offset in $text where the match begins
$word = $mm[0]; //the matched word/phrase
$all_keywords[] = $word;
$all_keys[] = $key;
}
}
}
echo "\$text = \"$text\"\n";
echo $keywords_list = implode(',', $all_keywords) ."<br>\n";
echo $keys_list = implode(',', $all_keys) . "<br>\n";
`
Replace
strpos(strtolower($text), strtolower($word)
With
preg_match('/\b'.$word.'\b/',$text)
Or, since you don't seem to care about capital letters:
preg_match('/\b'.strtolower($word).'\b/', strtolower($text))
I suggest in that case that you perform strtolower($text) beforehand, for instance just before the beginning of foreach.
From the top of my head, I think there are two additional steps to make this function a bit robust.
If we somehow sort the $words array by strlen (descending, bigger words at the top and smaller at the bottom) there would be greater chance for desired "match".
In the for loop, when a word "matches" or strcmp returns true, we can remove the matched word from the string to avoid further unnecessary match. (e.g. Shakes will always match where William Shakespeare matches.)
P.S. SO ios app rocks! But still not easy to code(bloody autocorrect!)
I am analysing informal chat style message for sentiment and other information. I need all of the emoticons to be replaced with their actual meaning, to make it easier for the system to parse the message.
At the moment I have the following code:
$str = "Am I :) or :( today?";
$emoticons = array(
':)' => 'happy',
':]' => 'happy',
':(' => 'sad',
':[' => 'sad',
);
$str = str_replace(array_keys($emoticons), array_values($emoticons), $str);
This does a direct string replacement, and therefore does not take into account if the emoticon is surrounded by other characters.
How can I use regex and preg_replace to determine if it is actually an emoticon and not part of a string?
Also how can I extend my array so that happy element for example can contain both entries; :) and :]?
For maintainability and readability, I would change your emoticons array to:
$emoticons = array(
'happy' => array( ':)', ':]'),
'sad' => array( ':(', ':[')
);
Then, you can form a look-up table just like you originally had, like this:
$emoticon_lookup = array();
foreach( $emoticons as $name => $values) {
foreach( $values as $emoticon) {
$emoticon_lookup[ $emoticon ] = $name;
}
}
Now, you can dynamically form a regex from the emoticon lookup array. Note that this regex requires a non-word-boundary surrounding the emoticon, change it to what you need.
$escaped_emoticons = array_map( 'preg_quote', array_keys( $emoticon_lookup), array_fill( 0, count( $emoticon_lookup), '/'));
$regex = '/\B(' . implode( '|', $escaped_emoticons) . ')\B/';
And then use preg_replace_callback() with a custom callback to implement the replacement:
$str = preg_replace_callback( $regex, function( $match) use( $emoticon_lookup) {
return $emoticon_lookup[ $match[1] ];
}, $str);
You can see from this demo that this outputs:
Am I happy or sad today?