How preg_match_all() processes strings? - php

I'm still learning a lot about PHP and string alteration is something that is of interest to me. I've used preg_match before for things like validating an email address or just searching for inquiries.
I just came from this post What's wrong in my regular expression? and was curious as to why the preg_match_all function produces 2 strings, 1 w/ some of the characters stripped and then the other w/ the desired output.
From what I understand about the function is that it goes over the string character by character using the RegEx to evaluate what to do with it. Could this RegEx have been structured in such a way as to bypass the first array entry and just produce the desired result?
and so you don't have to go to the other thread
$str = 'text^name1^Jony~text^secondname1^Smith~text^email1^example-
free#wpdevelop.com~';
preg_match_all('/\^([^^]*?)\~/', $str, $newStr);
for($i=0;$i<count($newStr[0]);$i++)
{
echo $newStr[0][$i].'<br>';
}
echo '<br><br><br>';
for($i=0;$i<count($newStr[1]);$i++)
{
echo $newStr[1][$i].'<br>';
}
This will output
^Jony~^Smith~^example-free#wpdevelop.com~JonySmithexample-free#wpdevelop.com
I'm curious if the reason for 2 array entries was due to the original sytax of the string or if it is the normal processing response of the function. Sorry if this shouldn't be here, but I'm really curious as to how this works.
thanks,
Brodie

It's standard behavior for preg_match and preg_match_all - the first string in the "matched values" array is the FULL string that was caught by the regex pattern. The subsequent array values are the 'capture groups', whose existence depends on the placement/position of () pairs in the regex pattern.
In your regex's case, /\^([^^]*?)\~/, the full matching string would be
^ Jony ~
| | |
^ ([^^]*?) ~ -> $newstr[0] = ^Jony~
-> $newstr[1] = Jony (due to the `()` capture group).

Could this RegEx have been structured in such a way as to bypass the first array entry and just produce the desired result?
Absolutely. Use assertions. This regex:
preg_match_all('/(?<=\^)[^^]*?(?=~)/', $str, $newStr);
Results in:
Array
(
[0] => Array
(
[0] => Jony
[1] => Smith
[2] => example-free#wpdevelop.com
)
)

As the manual states, this is the expected result (for the default PREG_PATTERN_ORDER flag). The first entry of $newStr contains all full pattern matches, the next result all matches for the first subpattern (in parentheses) and so on.

The first array in the result of preg_match_all returns the strings that match the whole pattern you passed to the preg_match_all() function, in your case /\^([^^]*?)\~/. Subsequent arrays in the result contain the matches for the parentheses in your pattern. Maybe it is easier to understand with an example:
$string = 'abcdefg';
preg_match_all('/ab(cd)e(fg)/', $string, $matches);
The $matches array will be
array(3) {
[0]=>
array(1) {
[0]=>
string(7) "abcdefg"
}
[1]=>
array(1) {
[0]=>
string(2) "cd"
}
[2]=>
array(1) {
[0]=>
string(2) "fg"
}
}
The first array will contain the match of the entire pattern, in this case 'abcdefg'. The second array will contain the match for the first set of parentheses, in this case 'cd'. The third array will contain the match for the second set of parentheses, in this case 'fg'.

[0] contains entire match, while [1] only a portion (the part you want to extract)...
You can do var_dump($newStr) to see the array structure, you'll figure it out.
$str = 'text^name1^Jony~text^secondname1^Smith~text^email1^example-
free#wpdevelop.com~';
preg_match_all('/\^([^^]*?)\~/', $str, $newStr);
$newStr = $newStr[1];
foreach($newStr as $key => $value)
{
echo $value."\n";
}
This will result in... (weird result, haven't modified expression)
Jony
Smith
example-
free#wpdevelop.com

Whenever you have problems to imagine the function of preg_match_all you should use an evaluator like preg_match_all tester # regextester.net
This shows you the result in realtime and you can configure things like the result order, meta instructions, offset capturing and many more.

Related

PHP preg_split curly brackets

Who can help me out?
I have a string like this:
$string = '<p>{titleInformation}<p>';
I want to split this string so that I get the following array:
array (
0 => '<p>',
1 => '{titleInformation}',
2 => '<p>',
)
I'm new to regular expressions and I tried multiple patterns with the preg_match_all() function but I cant get the correct one. Also looked at this question PHP preg_split if not inside curly brackets, but I don't have spaces in my string.
Thank you in advance.
Use preg_match() with capture groups. You need to escape the curly braces because they have special meaning in regular expressions.
preg_match('/(.*?)(\\{[^}]*\\})(.*)/', $string, $match);
var_dump($match);
Result:
array(4) {
[0]=>
string(24) "<p>{titleInformation}<p>"
[1]=>
string(3) "<p>"
[2]=>
string(18) "{titleInformation}"
[3]=>
string(3) "<p>"
}
$match[0] contains the match for the entire regexp, elements 1-3 contain the parts that you want.
In my opinion, the best function to call for your task is: preg_split(). It has a flag called PREG_SPLIT_DELIM_CAPTURE which allows you to retain your chosen delimiter in the output array. It is a very simple technique to follow and using negated character classes ([^}]*) is a great way to speed up your code. Further benefits of using preg_split() versus preg_match() include:
improved efficiency due to less capture groups
shorter pattern which is easier to read
no useless "fullstring" match in the output array
Code: (PHP Demo) (Pattern Demo)
$string = '<p>{titleInformation}<p>';
var_export(
preg_split('/({[^}]*})/', $string, 0, PREG_SPLIT_DELIM_CAPTURE)
);
Output:
array (
0 => '<p>',
1 => '{titleInformation}',
2 => '<p>',
)
If this answer doesn't work for all of your use cases, please edit your question to include the sample input strings and ping me -- I will update my answer.
With preg_split it can be done this way
preg_split('/[{}]+/', $myString);

php preg_match_all returning array of arrays

I want to replace some template tags:
$tags = '{name} text {first}';
preg_match_all('~\{(\w+)\}~', $tags, $matches);
var_dump($matches);
output is:
array(2) {
[0]=> array(2) {
[0]=> string(6) "{name}"
[1]=> string(7) "{first}"
}
[1]=> array(2) {
[0]=> string(4) "name"
[1]=> string(5) "first"
}
}
why are there inside 2 arrays? How to achieve only second one?
The sort answer:
Is there an alternative? Of course there is: lookaround assertions allow you to use zero-width (non-captured) single char matches easily:
preg_match_all('/(?<=\{)\w+(?=})/', $tags, $matches);
var_dump($matches);
Will dump this:
array(1) {
[0]=>
array(2) {
[0]=>
string(4) "name"
[1]=>
string(5) "first"
}
}
The pattern:
(?<=\{): positive lookbehind - only match the rest of the pattern if there's a { character in front of it (but don't capture it)
\w+: word characters are matches
(?=}): only match preceding pattern if it is followed by a } character (but don't capture the } char)
It's that simple: the pattern uses the {} delimiter chars as conditions for the matches, but doesn't capture them
Explaining this $matches array structure a bit:
The reason why $matches looks the way it does is quite simple: when using preg_match(_all), the first entry in the match array will always be the entire string matched by the given regex. That's why I used zero-width lookaround assertions, instead of groups. Your expression matches "{name}" in its entirety, and extracts "name" through grouping.
The matches array will hold the full match on index 0, and add groups at every subsequent index, in your case that means that:
$matches[0] will contain all substrings matching /\{\w+\}/ as a pattern.
$matches[1] will contain all substrings that were captured (/\{(\w+)\}/ captures (\w+)).
If you were to have a regex like this: /\{((\w)([^}]+))}/ the matches array will look something like this:
[
0 => [
'{name}',//as if you'd written /\{\w[^}]+}/
],
1 => [
'name',//matches group (\w)([^}]+), as if you wrote (\w[^}]+)
],
2 => [
'n',//matches (\w) group
],
3 => [
'ame',//and this is the ([^}]+) group obviously
]
]
Why? simple because the pattern contains 3 matching groups. Like I said: the first index in the matches array will always be the full match, regardless of capture groups. The groups are then appended to the array in the order the appear in in the expression. So if we analyze the expression:
\{: not matches, but part of the pattern, will only be in the $matches[0] values
((\w)([^}]+)): Start of first matching group, \w[^}]+ match is grouped here, $matches[1] will contain these values
(\w): Second group, a single \w char (ie first character after {. $matches[2] will therefore contain all first characters after a {
([^}]+): Third group, matches rest of string after {\w until a } is encountered, this will make out the $matches[3] values
To better understand, and be able to predict the way $matches will get populated, I'd strongly recommend you use this site: regex101. Write your expression there, and it'll break it all down for you on the right hand side, listing the groups. For example:
/\{((\w)([^}]+))}/
Is broken down like this:
/\{((\w)([^}]+))}/
\{ matches the character { literally
1st Capturing group ((\w)([^}]+))
2nd Capturing group (\w)
\w match any word character [a-zA-Z0-9_]
3rd Capturing group ([^}]+)
[^}]+ match a single character not present in the list below
Quantifier: + Between one and unlimited times, as many times as possible, giving back as needed [greedy]
} the literal character }
} matches the character } literally
Looking at the capturing groups, you can now confidently say what $matches will look like, and you can safely say that $matches[2] will be an array of single characters.
Of course, this may leave you wondering why $matches is a 2D array. Well, that again is really quite easy: What you can predict is how many match indexes a $matches array will contain: 1 for the full pattern, then +1 for each capture group. What you Can't predict, though, is how many matches you'll find.
So what preg_match_all does is really quite simple: fill $matches[0] with all substrings that match the entire pattern, then extract each group substring from these matches and append that value onto the respective $matches arrays. In other words, the number of arrays that you can find in $matches is a given: it depends on the pattern. The number of keys you can find in the sub-arrays of $matches is an unknown, it depends on the string you're processing. If preg_match_all were to return a 1D array, it would be a lot harder to process the matches, now you can simply write this:
$total = count($matches);
foreach ($matches[0] as $k => $full) {
echo $full . ' contains: ' . PHP_EOL;
for ($i=1;$i<$total;++$i) {
printf(
'Group %d: %s' . PHP_EOL,
$i, $matches[$i][$k]
);
}
}
If preg_match_all created a flat array, you'd have to keep track of the amount of groups in your pattern. Whenever the pattern changes, you'd also have make sure to update the rest of the code to reflect the changes made to the pattern, making your code harder to maintain, whilst making it more error-prone, too
Thats because your regex could have multiple match groups - if you have more (..) you would have more entries in your array. The first one[0] ist always the whole match.
If you want an other order of the array, you could use PREG_SET_ORDER as the 4. argument for preg_match_all. Doing this would result in the following
array(2) {
[0]=> array(2) {
[0]=> string(6) "{name}"
[1]=> string(7) "name"
}
[1]=> array(2) {
[0]=> string(4) "{first}"
[1]=> string(5) "first"
}
}
this could be easier if you loop over your result in a foreach loop.
If you only interessted in the first match - you should stay with the default PREG_PATTERN_ORDER and just use $matches[1]

Why is preg_match_all returning two matches?

I am trying to identify if a string has any words between double quotes using preg_match_all, however it's duplicating results and the first result has two sets of double quotes either side, where as the string being searched only has the one set.
Here is my code:
$str = 'Test start. "Test match this". Test end.';
$groups = array();
preg_match_all('/"([^"]+)"/', $str, $groups);
var_dump($groups);
And the var dump produces:
array(2) {
[0]=>
array(1) {
[0]=>
string(17) ""Test match this""
}
[1]=>
array(1) {
[0]=>
string(15) "Test match this"
}
}
As you can see the first array is wrong, why is preg_match_all returning this?
It returns 2 elements because:
Element 0 captures the whole matched string
Elements 1..N capture dedicated matches.
PS: another way of expressing the same could be
(?<=")[^"]+(?=")
which would capture exactly the same but in that case you don't need additional capturing group.
Demo: http://regex101.com/r/lF3kP7/1
Hi if your are using print_r instead of vardump you will see the differences in a better way.
Array
(
[0] => Array
(
[0] => "Test match this"
)
[1] => Array
(
[0] => Test match this
)
)
The first contains whole string and the second is your match.
Remove the parenthesis.
you can write the pattern as '/"[^"]+"/'
This is because you're using group matching. take the parentheses out of your pattern and you'll get one array back. Something like:
preg_match_all('/\"[^"]+\"/', $str, $groups);

PHP preg_match from string

I have this string :
$content = 'Hello <!--blapi[getinfoprix("prix_p1"=>"1-6048df6;image/purchase-small.png"]--> Hello<!--blapi[prix_p1->description]-->';
How can i get the two string <!--*--> in an array[2]?
I've made this :
$pattern = '/<!--blapi\[(.*)\]-->/sU';
preg_match($pattern, $content, $matches);
But I have this result :
array(2) {
[0]=>
string(74) "<!--blapi[getinfoprix("prix_p1"=>"1-6048df6;image/purchase-small.png")]-->"
[1]=>
string(60) "getinfoprix("prix_p1"=>"1-6048df6;image/purchase-small.png")"
}
I don't understand why it's ignoring the second string <!--blapi[prix_p1->description]-->...
I've used the flag "U". Maybe there is a better pattern for what I want to do?
EDITION :
I expect this result :
Array
(
[0] => getinfoprix("prix_p1"=>"1-6048df6;image/purchase-small.png"]
[1] => prix_p1->description
)
This preg_match_all should work:
$content = 'Hello <!--blapi[getinfoprix("prix_p1"=>"1-6048df6;image/purchase-small.png"]--> Hello<!--blapi[prix_p1->description]-->';
if ( preg_match_all('/<!--.*?\[(.*?)\]-->/', $content, $matches) )
print_r($matches[0]);
OUTPUT
Array
(
[0] => getinfoprix("prix_p1"=>"1-6048df6;image/purchase-small.png"
[1] => prix_p1->description
)
$pattern = '~<!--(blapi\[(?:.*?)\])-->~si';
Does this pattern produce the expected results? I understand you want to capture the blapi part too. But not sure...
Changed .*U to .*? and added i for case-insensitive at the end. The inner blapi is a non-capture group and the blapi[...] is now the capture group.
Also avoid wrapping a regex in / as it conflicts with URLs and HTML. Use ~ as it's seldom used and much safer. It's not nice to escape http:// to http:\/\/ just because of the wrap character.
You also need preg_match_all as preg_match capture only one match. It's mostly used for match testing, single-match search but not multiple match search.

preg_replace_callback regex issue, match with (.*?) returns array

Given the string {{esc}}"Content"{{/esc}} ... {{esc}}"More content"{{/esc}} I would like to output \"Content\" ... \"More content\" e.g., I am trying to escape the quotes inside a string. (This is a contrived example, though, so an answer with something like 'just use this library to do it' would be unhelpful.)
Here is my current solution:
return preg_replace_callback(
'/{{esc}}(.*?){{\/esc}}/',
function($m) {
return str_replace('"', '\\"', $m[1]);
},
$text
);
As you can see, I need to say $m[1], because a print_r reveals that $m looks like this:
Array
(
[0] => {{esc}}"Content"{{/esc}}
[1] => "Content"
)
or, for the second match,
Array
(
[0] => {{esc}}"More content"{{/esc}}
[1] => "More content"
)
My question is: why does my regex cause $m to be an array? Is there any way I can get the result of $m[1] as just a single variable $m?
The regex matches the string and puts the result into array. If match, the first index store the whole match string, the rest elements of the array are the string captured.
preg_replace_callback() acts like preg_match():
$result = array();
preg_match('/{{esc}}(.*?){{\/esc}}/', $input_str, $result);
// $result will be an array if match.
With the help of Jack, I answered my own question here since srain did not make this point clear: The second element of the array is the result captured by the parenthesized subexpression (.*?), per the PHP manual. Indeed, there does not appear to be a convenient way to extract the string matched by this subexpression otherwise.

Categories