PHP preg_match() doesn't match all subpatterns - php

I have a preg_match() which matches the pattern but doesn't receive the expected matches (in third param).
My regex patterns have multiple subpatterns.
$pattern = "~^&multi&[^&]+(&(?:(p-(?<sad>[1-9]\d*)|page-(?<sad>[1-9]\d*))))?&[^&]+(&(?:(p-(?<gogosi>[1-9]\d*)|page-(?<gogosi>[1-9]\d*))))?&?$~J";
$string = "&multi&mickael&p-23&george&page-34";
preg_match($pattern, $string, $matches);
This is what $matches contains:
Array
(
[0] => &multi&mickael&p-23&george&page-34
[1] => &p-23
[2] => p-23
[sad] =>
[3] => 23
[4] =>
[5] => &page-34
[6] => page-34
[gogosi] => 34
[7] =>
[8] => 34
)
The problem is [sad] should have 23 value.
If I don't include in $string second page (page-34), 'cause is optional [...]
$string = "&multi&mickael&p-23&george";
[...] I have good $matches 'cause my [sad] got his value:
Array
(
[0] => &multi&mickael&p-23&george
[1] => &p-23
[2] => p-23
[sad] => 23
[3] => 23
)
But I want regex to return properly value even when I have both paginations in $string.
What to do such that all subpatterns will have their value ?
Note: Words as ('p', 'page') are only examples. Can be any words there.
Note: Above data is just an example. Don't give me workaround solutions, but something good for any input data.

You may use a branch reset group, (?|...|...):
'~^&multi&[^&]+(&((?|p-(?<sad>[1-9]\d*)|page-(?<sad>[1-9]\d*))))?&[^&]+(&((?|p-(?<gogosi>[1-9]\d*)|page-(?<gogosi>[1-9]\d*))))?&?$~J'
See the regex demo.
See the PHP demo:
$pattern = "~^&multi&[^&]+(&((?|p-(?<sad>[1-9]\d*)|page-(?<sad>[1-9]\d*))))?&[^&]+(&((?|p-(?<gogosi>[1-9]\d*)|page-(?<gogosi>[1-9]\d*))))?&?$~J";
$string = "&multi&mickael&p-23&george&page-34";
if (preg_match($pattern, $string, $matches)) {
print_r($matches);
}
Output:
Array
(
[0] => &multi&mickael&p-23&george&page-34
[1] => &p-23
[2] => p-23
[sad] => 23
[3] => 23
[4] => &page-34
[5] => page-34
[gogosi] => 34
[6] => 34
)

Related

How to make this weird string explode in PHP?

I have a string like the following
DAS-1111[DR-Helpfull-R]-RUN--[121668688374]-N-[+helpfull_+string]
The above string is a kind of formatted in groups that looks like the following:
A-B[C]-D-E-[F]-G-[H]
The think is that I like to process some of those groups, and I like to make something like explode.
I say like, because I have try this code:
$string = 'DAS-1111[DR-Helpfull-R]-RUN--[121668688374]-N-[+helpfull_+string]';
$parts = explode( '-', $string );
print_r( $parts );
and I get the following result:
Array
(
[0] => DAS
[1] => 1111[DR
[2] => Helpfull
[3] => R]
[4] => RUN
[5] =>
[6] => [121668688374]
[7] => N
[8] => [+helpfull_+string]
)
that it is not what I need.
What I need is the following output:
Array
(
[0] => DAS
[1] => 1111[DR-Helpfull-R]
[2] => RUN
[3] =>
[4] => [121668688374]
[5] => N
[6] => [+helpfull_+string]
)
Can someone please suggest a nice and elegant way to explode this string in the way I need it ?
what I forgot to mention, is that the string can have more or less groups. Examples:
DAS-1111[DR-Helpfull-R]-RUN--[121668688374]-N-[+helpfull_+string]
DAS-1111[DR-Helpfull-R]-RUN--[121668688374]
DAS-1111[DR-Helpfull-R]-RUN--[121668688374]-N-[+helpfull_+string]-anotherPart
Update 1
As mentioned by #axiac, the preg_split can do the work. But can you please help with the regex now ?
I have try this but it seems that it is incorrect:
(?!\]\-)\-
The code:
$str = 'DAS-1111[DR-Helpfull-R]-RUN--[121668688374]-N-[+helpfull_+string]';
$re = '/([^-[]*(?:\[[^\]]*\])?[^-]*)-?/';
$matches = array();
preg_match_all($re, $str, $matches);
print_r($matches[1]);
Its output:
Array
(
[0] => DAS
[1] => 1111[DR-Helpfull-R]
[2] => RUN
[3] =>
[4] => [121668688374]
[5] => N
[6] => [+helpfull_+string]
[7] =>
)
There is an extra empty value at position 7 in the output. It appears because of the zero-or-one repetitions quantifier (?) placed at the end of the regex. The quantifier is needed because without it the last piece (at index 6) is not matched.
You can remove the ? after the last - and ask this way the dash (-) always match. In this case you must append an extra - to your input string.
The regex
( # start of the 1st subpattern
# the captured value is returned in $matches[1]
[^-[]* # match any character but '-' and '[', zero or more times
(?: # start of a non-capturing subpattern
\[ # match an opening square bracket ('[')
[^\]]* # match any character but ']', zero or more times
\] # match a closing square bracket (']')
)? # end of the subpattern; it is optional (can appear 0 or 1 times)
[^-]* # match any character but '-', zero or more times
) # end of the 1st subpattern
-? # match an optional dash ('-')
Instead of exploding you should try to match the following pattern:
(?:^|-)([^-\[]*(?:\[[^\]]+\])?)
Here is an example:
$regex = '/(?:^|-)([^-\[]*(?:\[[^\]]+\])?)/';
$tests = array(
'DAS-1111[DR-Helpfull-R]-RUN--[121668688374]-N-[+helpfull_+string]',
'DAS-1111[DR-Helpfull-R]-RUN--[121668688374]',
'DAS-1111[DR-Helpfull-R]-RUN--[121668688374]-N-[+helpfull_+string]-anotherPart'
);
foreach ($tests as $test) {
preg_match_all($regex, $test, $result);
print_r($result[1]);
}
Output:
// DAS-1111[DR-Helpfull-R]-RUN--[121668688374]-N-[+helpfull_+string]
Array
(
[0] => DAS
[1] => 1111[DR-Helpfull-R]
[2] => RUN
[3] =>
[4] => [121668688374]
[5] => N
[6] => [+helpfull_+string]
)
// DAS-1111[DR-Helpfull-R]-RUN--[121668688374]
Array
(
[0] => DAS
[1] => 1111[DR-Helpfull-R]
[2] => RUN
[3] =>
[4] => [121668688374]
)
// DAS-1111[DR-Helpfull-R]-RUN--[121668688374]-N-[+helpfull_+string]-anotherPart
Array
(
[0] => DAS
[1] => 1111[DR-Helpfull-R]
[2] => RUN
[3] =>
[4] => [121668688374]
[5] => N
[6] => [+helpfull_+string]
[7] => anotherPart
)
This case is perfect for the (*SKIP)(*FAIL) method. You want to split your string on the hyphens, so long as they aren't inside of square brackets.
Easy. Just disqualify these hyphens as delimiters like so:
Pattern: ~\[[^]]+\](*SKIP)(*FAIL)|-~ (Pattern Demo)
Code: (Demo)
$strings=['DAS-1111[DR-Helpfull-R]-RUN--[121668688374]-N-[+helpfull_+string]',
'DAS-1111[DR-Helpfull-R]-RUN--[121668688374]',
'DAS-1111[DR-Helpfull-R]-RUN--[121668688374]-N-[+helpfull_+string]-anotherPart'];
foreach($strings as $string){
var_export(preg_split('~\[[^]]+\](*SKIP)(*FAIL)|-~',$string));
echo "\n\n";
}
Output:
array (
0 => 'DAS',
1 => '1111[DR-Helpfull-R]',
2 => 'RUN',
3 => '',
4 => '[121668688374]',
5 => 'N',
6 => '[+helpfull_+string]',
)
array (
0 => 'DAS',
1 => '1111[DR-Helpfull-R]',
2 => 'RUN',
3 => '',
4 => '[121668688374]',
)
array (
0 => 'DAS',
1 => '1111[DR-Helpfull-R]',
2 => 'RUN',
3 => '',
4 => '[121668688374]',
5 => 'N',
6 => '[+helpfull_+string]',
7 => 'anotherPart',
)

get all words from string with regular expression

I need preg_match that can find me all words from string.
for example:
$str = "string: hi, it is string.";
I would like get this:
[0] => string
[1] => hi
[2] => it
[3] => is
[4] => string
I use with '/[a-z]+/ui', but I get this:
[0] => string:
[1] => hi,
[2] => it
[3] => is
[4] => string.
You said preg_match(), instead you should be using preg_match_all() and there is no need to use the u modifier in your regular expression here.
$str = "string: hi, it is string.";
preg_match_all('/[a-z]+/i', $str, $matches);
print_r($matches[0]);
Output
Array
(
[0] => string
[1] => hi
[2] => it
[3] => is
[4] => string
)

Explode values in a word/string with Regex

i have this sample string and i want to explode the day,month,hour,minutes,(am/pm) wih regex:
SunDec 16 00:00am
Using this:
(\w+)\s+(\w+)\s+(\d+)\:(\d+)(..)
But it gives me:
Array
(
[0] => SunDec 16 00:00am
[1] => SunDec
[2] => 16
[3] => 00
[4] => 00
[5] => am
)
I cant figure it out..Can i explode SubDec into two?
You could try this:
$str = 'SunDec 16 00:00am';
preg_match('/([A-Z]{1}\w+)([A-Z]{1}\w+)\s+(\w+)\s+(\d+):(\d+)(..)/', $str, $ret);
print_r($ret);
You can try this Regular Expression, This might help you
([A-Z][a-z]+)+\s(\d+)?\s?(\d+)\:(\d+)([a-z]+)
If your string is always going to follow that example you can use this:
$string = 'SunDec 16 00:00am';
$pattern = '!([a-zA-Z]{3})([a-zA-Z]{3})\s+(\w+)\s+(\d+)\:(\d+)(..)!';
preg_match($pattern, $string, $chunks);
print_r($chunks);
Which outputs:
Array
(
[0] => SunDec 16 00:00am
[1] => Sun
[2] => Dec
[3] => 16
[4] => 00
[5] => 00
[6] => am
)
You could use this pattern too which is slightly smaller
'!([a-z]{3})([a-z]{3})\s+(\w+)\s+(\d+)\:(\d+)(..)!i'

How to preg_split using PREG_SPLIT_DELIM_CAPTURE

$str = "blabla and, some more blah";
$delimiters = " ,¶.\n";
$char_buff = preg_split("/(,) /", $str, -1, PREG_SPLIT_DELIM_CAPTURE);
print_r($char_buff);
I get:
Array (
[0] => blabla and
[1] => ,
[2] => some more blah
)
I was able to figure out how to use the parenthesis to get the comma to show up in its own array element -- but how can I do this with multiple different delimiters (for example, those in the $delimiters variable)?
You need to create a character class by wrapping the delimiters with [ and ].
<?php
$str = "blabla and, some more blah. Blah.\nSecond line.";
$delimiters = " ,¶.\n";
$char_buff = preg_split('/([' . $delimiters . '])/', $str, -1,
PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
print_r($char_buff);
You also need to use PREG_SPLIT_NO_EMPTY so that in places where you get two matches in a row, for instance a comma followed by a space, you don't get an empty match.
Output
Array
(
[0] => blabla
[1] =>
[2] => and
[3] => ,
[4] =>
[5] => some
[6] =>
[7] => more
[8] =>
[9] => blah
[10] => .
[11] =>
[12] => Blah
[13] => .
[14] =>
[15] => Second
[16] =>
[17] => line
[18] => .
)
Depending on what you are doing, using strtok may be a more appropriate way of doing it though.
Use something like:
'/([,.])/'
That is put each delimiter in that square bracket.
Each delimiter expression needs to be inside its own group.
print_r(preg_split('/2\d4/' , '12345', null, PREG_SPLIT_DELIM_CAPTURE));
Array ( [0] => 1 [1] => 5 )
print_r(preg_split('/(2)(\d)(4)/', '12345', null, PREG_SPLIT_DELIM_CAPTURE));
Array ( [0] => 1 [1] => 2 [2] => 3 [3] => 4 [4] => 5 )

preg_match return all parts in array

I've got following php code:
$match = array();
if (preg_match("%^(/\d+)(/test)(/\w+)*$%", "/25/test/t1/t2/t3/t4", $match))
print_r($match);
I'm getting this result:
Array ( [0] => /25/test/t1/t2/t3/t4 [1] => /25 [2] => /test [3] => /t4 )
What do i need to change in my regexp to get this result:
Array ( [0] => /25/test/t1/t2/t3/t4 [1] => /25 [2] => /test [3] => /t1 [4] => /t2 [5] => /t3 [6] => /t4)
you need preg_match_all
preg_match_all( '~(/\w+)~', $str, $matches );
in your situation you can use explode too
<?php
$str = '/a/b/1/2/3/4';
if(preg_match('/^(\/\w+)*$/', $str) && preg_match_all('/\/\w+/', $str, $matches)) {
$matches = $matches[0];
print_r($matches);
}
?>
Prints:
Array
(
[0] => /a
[1] => /b
[2] => /1
[3] => /2
[4] => /3
[5] => /4
)
Using your original example, you could use a recursive expression:
"%(/\w+)(?>[^(/\w+)]?|(?R))%"
This works my matching (/\w+) subexpressions in turn. Therfore the match for
"/a/b/1/2/3/4"
Would be:
Array
(
[0] => Array
(
[0] => /a [1] => /b [2] => /1 [3] => /2 [4] => /3 [5] => /4
)
...
However your later examples complicate things. A simple 0 or more match will only return the last (greedy) or first (ungreedy) match - not all submatches. preg_match_all won't be able to handle your dynamic expression.
You will have to clarify what you're trying to achieve in more detail before a suitable solution can be provided.

Categories