Reputation: 36957
I am trying to match parts of a dynamically generated string and make a replacement based on the extracted information. My problem is that I can't fathom how to craft the regex.
What I have is a few different placeholder strings like the following.:
[@img:1234567890]
[@user:1234567890]
[@file:file_name-with.ext]
Strings like this are passed through with the intent of being replaced with links, and/or more readable names. But again, I can't come up with a regex for any given one of them.
I am looking for the format: [@word:]
of which I will strip the [, ], @, and word from the string so I can then turn around an query my DB accordingly for whatever it is and work with it accordingly. Just the regex bit is holding me back.
Upvotes: 0
Views: 116
Reputation: 48071
Making dynamic string replacements involving function calls is a job for preg_replace_callback()
. Depending on the placeholder encountered, execute the necessary query or filesystem check and return the desired HTML element.
The regex pattern should be narrowly crafted to only acknowledge expected/programmed actions -- using \w+
is too vague when you know exactly which words are valid. When processing matches, use the match()
construct to determine which replacement process should be executed.
Code: (PHPize Demo)
$text = <<<TEXT
Here we have some test text to show [@img:1234567890]
belonging to [@user:1234567890] and a reference
to [@file:file_name-with.ext].
TEXT;
echo preg_replace_callback(
'/\[@(?|(user|img):(\d+)|(file):([\w.-]+))\]/',
fn($m) => match($m[1]) {
'user' => (
fn($row) => $row
? '<a href="/user/' . $m[2] . '">' . htmlentities($row['name']) . '</a>'
: $m[0]
)(
$mysqli->execute_query('SELECT name FROM users WHERE id = ?', [$m[2]])->fetch_assoc() ?: []
),
'img' => (
fn($row) => $row
? '<img src="/images/' . $m[2] . '" alt="' . htmlentities($row['name']) . '">'
: $m[0]
)(
$mysqli->execute_query('SELECT name FROM images WHERE id = ?', [$m[2]])->fetch_assoc() ?: []
),
'file' => file_exists('your/system/path/' . $m[2])
? '<a href="/your/system/path/' . $m[2] . '">' . $m[2] . '</a>'
: $m[0],
},
$text
);
Output:
Here we have some test text to show <img src="/images/1234567890" alt="some_image.png">
belonging to <a href="/user/1234567890">Chad</a> and a reference
to [@file:file_name-with.ext].
Upvotes: 0
Reputation: 7470
Here's what I would do.
<pre>
<?php
$subj = 'An image:[@img:1234567890], a user:[@user:1234567890] and a file:[@file:file_name-with.ext]';
preg_match_all('~(?<match>\[@(?<type>[^:]+):(?<value>[^\]]+)\])~',$subj,$matches,PREG_SET_ORDER);
foreach ($matches as &$arr) unset($arr[0],$arr[1],$arr[2],$arr[3]);
print_r($matches);
?>
</pre>
This will output
Array
(
[0] => Array
(
[match] => [@img:1234567890]
[type] => img
[value] => 1234567890
)
[1] => Array
(
[match] => [@user:1234567890]
[type] => user
[value] => 1234567890
)
[2] => Array
(
[match] => [@file:file_name-with.ext]
[type] => file
[value] => file_name-with.ext
)
)
And here's a pseudo version of how I would use the preg_replace_callback()
function:
function replace_shortcut($matches) {
global $users;
switch (strtolower($matches['type'])) {
case 'img' : return '<img src="images/img_'.$matches['value'].'jpg" />';
case 'file' : return '<a href="files/'.$matches['value'].'" target="_blank">'.$matches['value'].'</a>';
// add id of each user in array
case 'user' : $users[] = (int) $matches['value']; return '%s';
default : return $matches['match'];
}
}
$users = array();
$replaceArr = array();
$subj = 'An image:[@img:1234567890], a user:[@user:1234567890] and a file:[@file:file_name-with.ext]';
// escape percentage signs to avoid complications in the vsprintf function call later
$subj = strtr($subj,array('%'=>'%%'));
$subj = preg_replace_callback('~(?<match>\[@(?<type>[^:]+):(?<value>[^\]]+)\])~',replace_shortcut,$subj);
if (!empty($users)) {
// connect to DB and check users
$query = " SELECT `id`,`nick`,`date_deleted` IS NOT NULL AS 'deleted'
FROM `users` WHERE `id` IN ('".implode("','",$users)."')";
// query
// ...
// and catch results
while ($row = $con->fetch_array()) {
// position of this id in users array:
$idx = array_search($row['id'],$users);
$nick = htmlspecialchars($row['nick']);
$replaceArr[$idx] = $row['deleted'] ?
"<span class=\"user_deleted\">{$nick}</span>" :
"<a href=\"users/{$row['id']}\">{$nick}</a>";
// delete this key so that we can check id's not found later...
unset($users[$idx]);
}
// in here:
foreach ($users as $key => $value) {
$replaceArr[$key] = '<span class="user_unknown">User'.$value.'</span>';
}
// replace each user reference marked with %s in $subj
$subj = vsprintf($subj,$replaceArr);
} else {
// remove extra percentage signs we added for vsprintf function
$subj = preg_replace('~%{2}~','%',$subj);
}
unset($query,$row,$nick,$idx,$key,$value,$users,$replaceArr);
echo $subj;
Upvotes: 1
Reputation: 7086
Not sure what you mean by generators. I always use online matchers to see that my test cases work. @Virendra almost had it except forgot to escape the []
charaters.
/\[@(\w+):(.*)\]/
You need to start and end with a regex delimeter, in this case the '/' character.
Then we escape the '[]' which is use by regex to match ranges of characters hence the '['.
Next we match a literal '@' symbol.
Now we want to save this next match so we can use it later so we surround it with ()
.
\w
matches a word
. Basically any characters that aren't spaces, punctuation, or line characters.
Again match a literal :
.
Maybe useful to have the second part in a match group as well so (.*)
will match any character any number of times, and save it for you.
Then we escape the closing ]
as we did earlier.
Since it sounds like you want to use the matches later in a query we can use preg_match to save the matches to an array.
$pattern = '/\[@(\w+):(.*)\]/';
$subject = '[@user:1234567890]';
preg_match($pattern, $subject, $matches);
print_r($matches);
Would output
array(
[0] => '[@user:1234567890]', // Full match
[1] => 'user', // First match
[2] => '1234567890' // Second match
)
An especially helpful tool I've found is txt2re
Upvotes: 1
Reputation: 179717
You can try something like this:
/\[@(\w+):([^]]*)\]/
\[
escapes the [
character (otherwise interpreted as a character set); \w
means any "word" character, and [^]]*
means any non-]
character (to avoid matching past the end of the tag, as .*
might). The parens group the various matched parts so that you can use $1
and $2
in preg_replace
to generate the replacement text:
echo preg_replace('/\[@(\w+):([^]]*)\]/', '$1 $2', '[@link:abcdef]');
prints link abcdef
Upvotes: 0