Regex to Match Passed Function/Method Parameters - php

I've had a good look around for a question that asked this before; alas, my search for a PHP preg_match search returned no results (maybe my searching skills fell short, I suppose justified considering it's a Regex question!).
Consider the text below:
The quick __("brown ") fox jumps __('over the') lazy __("dog")
Now currently I need to 'scan' for the given method __('') above, whereas it could include the spacing and different quotations ('|"). My best attempt after numerous 'iterations':
(__\("(.*?)"\))|(__\('(.*?)'\))
Or at its simplest form:
__\((.*?)\)
To break this down:
Anything that starts with __
Escaped ( and quotation mark " or '. Thus, \(\"
(.*?) Non-greedy match of all characters
Escaped closing " and last bracket.
| between the two expressions match either/or.
However, this only gets partial matches, and spaces are throwing off the search entirely. Apologies if this has been asked before, please link me if so!
Tester Link for the pattern provided above:
PHP Live Regex Test Tool

When the searched method string uses single quotes it will end up in another capture group than if it has double quotes. So in fact, your regular expression works (except for the spaces, see further down), but you'd have to look at a different index in your result array:
$input = 'The quick __("brown ") fox jumps __(\'over the\') lazy __("dog")';
// using your regular expression:
$res = preg_match_all("/(__\(\"(.*?)\"\))|(__\('(.*?)'\))/", $input, $matches);
print_r ($matches);
Note that you need preg_match_all instead of preg_match to get all matches.
Output:
Array
(
[0] => Array
(
[0] => __("brown ")
[1] => __('over the')
[2] => __("dog")
)
[1] => Array
(
[0] => __("brown ")
[1] =>
[2] => __("dog")
)
[2] => Array
(
[0] => brown
[1] =>
[2] => dog
)
[3] => Array
(
[0] =>
[1] => __('over the')
[2] =>
)
[4] => Array
(
[0] =>
[1] => over the
[2] =>
)
)
So, the result array has 5 elements, the first one representing the complete match, and all the others correspond to the 4 capture groups you have in your regular expression. As the capture groups for single quotes are not those of the double quotes, you'll find the matches at different places.
To "solve" this, you could use a back reference in your regular expression, which would look back to see which was the opening quote (single or double) and require the same to be repeated at the end:
$res = preg_match_all("/__\(([\"'])(.*?)\\1\)/", $input, $matches);
Note the back reference \1 (the backslash had to be escaped with another one). This refers back to the first capture group, where we have ["'] (again an escape was necessary) to match both kinds of quotes.
You also wanted to deal with spaces. On your PHP Live Regex you used a test string that had such spaces between the brackets and quotes. To deal with these so they still match the method strings correctly, the regular expression should get two additional \s*:
$res = preg_match_all("/__\(\s*([\"'])(.*?)\\1\s*\)/", $input, $matches);
Now the output is:
Array
(
[0] => Array
(
[0] => __("brown ")
[1] => __('over the')
[2] => __("dog")
)
[1] => Array
(
[0] => "
[1] => '
[2] => "
)
[2] => Array
(
[0] => brown
[1] => over the
[2] => dog
)
)
... and the text captured by the groups is now nicely arranged.
See this code run on eval.in and PHP Live Regex.

When working with stuff like this, don't forget about escaping:
<?php
ob_start();
?>
The quick __("brown ") fox jumps __( 'over the' ) lazy __("dog").
And __("everyone says \"hi\"").
<?php
$content = ob_get_clean();
$re = <<<RE
/__ \(
\s*
" ( (?: \\\\. | [^"])+ ) "
|
' ( (?: \\\\. | [^'])+ ) '
\s*
\)
/x
RE;
preg_match_all($re, $content, $matches, PREG_SET_ORDER);
foreach($matches as $match)
echo end($match), "\n";

How about this:
(__(\('[^']+'\)|\("[^"]+"\)))
Instead of the non greedy ., use any char but the quotes [^'] or [^"]

Enclose double and single quotes with square brackets as a character class:
$str = 'The quick __( "brown ") fox jumps __(\'over the\') lazy __("dog")';
preg_match_all("/__\(\s*([\"']).*?\\1\s*\)/ium", $str, $matches);
echo '<pre>';
var_dump($matches[0]);
// the output:
array (size=3)
0 => string '__( "brown ")'
1 => string '__('over the')'
2 => string '__("dog")'
And here is example with the same solution on phpliveregex.com:
http://www.phpliveregex.com/p/exF
(section preg_match_all)

Related

How to extend regex to find multiple matches?

This is my current regex (used in parsing an iCal file):
/(.*?)(?:;(?=(?:[^"]*"[^"]*")*[^"]*$))([\w\W]*)/
The current output using preg_match() is this:
//Output 1 - `preg_match()`
Array
(
[0] => TZID="Greenwich Mean Time:Dublin; Edinburgh; Lisbon; London"
[1] => VALUE=DATE;RSVP=FALSE;LANGUAGE=en-gb
)
I would like to extend my regex to output this (i.e. find multiple matches):
//Output 2
Array
(
[0] => TZID="Greenwich Mean Time:Dublin; Edinburgh; Lisbon; London"
[1] => VALUE=DATE
[2] => RSVP=FALSE
[3] => LANGUAGE=en-gb
)
The regex should search for each semicolon not contained within a quoted substring and provide that as a match.
Cannot just swap to preg_match_all() as gives this unwanted output
//Output 3 - `preg_match_all()`
Array
(
[0] => Array
(
[0] => TZID="Greenwich Mean Time:Dublin; Edinburgh; Lisbon; London";VALUE=DATE;RSVP=FALSE;LANGUAGE=en-gb
)
[1] => Array
(
[0] => TZID="Greenwich Mean Time:Dublin; Edinburgh; Lisbon; London"
)
[2] => Array
(
[0] => VALUE=DATE;RSVP=FALSE;LANGUAGE=en-gb
)
)
You need to use preg_match_all to get all the match of the string.
The pattern you use isn't designed to get several results since [\w\W]* matches everything until the end of the string.
But it's only one of your problems, a pattern designed like this need to check (for each colon) if the number of quotes is odd or even until the end of the file!: (?=(?:[^"]*"[^"]*")*[^"]*$). Imagine a minute how many times the whole string is parsed with this lookahead.
To avoid the problem, you can use a different approach that doesn't try to find colons, but that tries to describe everything that is not a colon: So you are looking for every parts of text that doesn't contains quotes or colon + quoted parts whatever the content.
You can use this kind of pattern:
$pattern = '~[^\r\n";]+(?:"[^"\\\]*(?:\\\.[^"\\\]*)*"[^\r\n";]*)*~';
if (preg_match_all($pattern, $str, $matches))
print_r($matches[0]);
pattern details:
~ # pattern delimiter
[^\r\n";]+ #" # all that is not a newline, a double quote or a colon
(?: # non-capturing group: to include eventual quoted parts
" #"# a literal quote
[^"\\\]* #"# all that is not a quote or a backslash
(?:\\\.[^"\\\]*)* #"# optional group to deal with escaped characters
" #"#
[^\r\n";]* #"#
)* # repeat zero or more times
~
demo
(.+?)(?:;(?=(?:[^"]*"[^"]*")*[^"]*$)|$)
Try this.See demo.
https://regex101.com/r/pG1kU1/18
You can use the following to match:
(.*?(?:;|$))(?![^"]*")
See DEMO
or split by:
;(?![^"]*")
See DEMO

Split this values part of an insert query

Is there any way to achieve the following? I need to take this $query and split it into its various elements (the reason is because I am having to reprocess an insert query). As you can see this will work for regular string blocks or numbers, but not where a number, occurs in the string. Is there a way to say |\d but not where that \d occurs within a ' quoted string '?
$query = "('this is\'nt very, funny (I dont think)','is it',12345,'nope','like with 2,4,6')";
$matches = preg_split("#',|\d,#",substr($query,1,-1));
echo $query;
print'<pre>[';print_r($matches);print']</pre>';
So just to be clear about expected results:
0:'this is\'nt very, funny (I dont think)'
1:'it is'
2:12345
3:'nope'
4:'like with 2,4,6'.
** Additionally I don't mind if each string is not quoted - I can requote them myself.
Could (*SKIP)(*F) parts that are inside single quotes and match , outside:
'(?:\\'|[^'])*'(*SKIP)(*F)|,
(?:\\'|[^']) Inside the single quotes matches escaped \' or a character that is not a single quote.
See Test at regex101.com
$query = "('this is\'nt very, funny (I dont think)','is it',12345,'nope','like with 2,4,6')";
$matches = preg_split("~'(?:\\\\'|[^'])*'(*SKIP)(*F)|,~", substr($query,1,-1));
print_r($matches);
outputs to (test at eval.in)
Array
(
[0] => 'this is\'nt very, funny (I dont think)'
[1] => 'is it'
[2] => 12345
[3] => 'nope'
[4] => 'like with 2,4,6'
)
Not absolutely sure, if that is what you mean :)
('(?:(?!(?<!\\)').)*')|(\d+)
Try this.Grab the captures.Each string is quoted as well.See demo.
http://regex101.com/r/dK1xR4/3
You could try matching through preg_match_all instead of splitting.
<?php
$data = "('this is\'nt very, funny (I dont think)','is it',12345,'nope','like with 2,4,6')";
$regex = "~'(?:\\\\'|[^'])+'|(?<=,|\()[^',)]*(?=,|\))~";
preg_match_all($regex, $data, $matches);
print_r($matches[0]);
?>
Output:
Array
(
[0] => 'this is\'nt very, funny (I dont think)'
[1] => 'is it'
[2] => 12345
[3] => 'nope'
[4] => 'like with 2,4,6'
)
If you don't mind using preg_match, then the solution could look like this. This regex uses lookbehind with negative assertions (?<!\\\\), it will match strings inside quotes that is not preceded by slash, and the alternation with the vertical bar ensures that numbers that are part of larger match will be ignored.
$query = "('this is\'nt very, funny (I dont think)','is it',12345,'nope','like with 2,4,6',6789)";
preg_match_all( "/(?<!\\\\)\'.+?(?<!\\\\)\'|\d+/", substr( $query, 1, -1 ), $matches );
print_r( $matches );
/* output:
Array (
[0] => Array
(
[0] => 'this is\'nt very, funny (I dont think)'
[1] => 'is it'
[2] => 12345
[3] => 'nope'
[4] => 'like with 2,4,6'
[5] => 6789
)
)
*/
,(?=(?:[^']*'[^']*')*[^']*$)
Try this.This will split according to what you want.Replace by \n.See demo.
http://regex101.com/r/dK1xR4/4

PHP preg_split with two delimiters unless a delimiter is within quotes

Further on from my previous question about preg_split which was answers super fast, thanks to nick; I would really like to extend the scenario to no split the string when a delimiter is within quotes. For example:
If I have the string foo = bar AND bar=foo OR foobar="foo bar", I'd wish to split the sting on every space or = character but include the = character in the returned array (which works great currently), but I don't want to split the string either of the delimiters are within quotes.
I've got this so far:
<!doctype html>
<?php
$string = 'foo = bar AND bar=foo';
$array = preg_split('/ +|(=)/', $string, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
?>
<pre>
<?php
print_r($array);
?>
</pre>
Which gets me:
Array
(
[0] => foo
[1] => =
[2] => bar
[3] => AND
[4] => bar
[5] => =
[6] => foo
)
But if I changed the string to:
$string = 'foo = bar AND bar=foo OR foobar = "foo bar"';
I'd really like the array to be:
Array
(
[0] => foo
[1] => =
[2] => bar
[3] => AND
[4] => bar
[5] => =
[6] => foo
[6] => OR
[6] => foobar
[6] => =
[6] => "foo bar"
)
Notice the "foo bar" wasn't split on the space because it's in quotes?
Really not sure how to do this within the RegEx or if there is even a better way but all your help would be very much appreciated!
Thank you all in advance!
Try
$array = preg_split('/(?: +|(=))(?=(?:[^"]*"[^"]*")*[^"]*$)/', $string, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
The
(?=(?:[^"]*"[^"]*")*[^"]*$)
part is a lookahead assertion making sure that there is an even number of quote characters ahead in the string, therefore it will fail if the current position is between quotes:
(?= # Assert that the following can be matched:
(?: # A group containing...
[^"]*" # any number of non-quote characters followed by one quote
[^"]*" # the same (to ensure an even number of quotes)
)* # ...repeated zero or more times,
[^"]* # followed by any number of non-quotes
$ # until the end of the string
)
I was able to do this by adding quoted strings as a delimiter a-la
"(.*?)"| +|(=)
The quoted part will be captured. It seems like this is a bit tenuous and I did not test it extensively, but it at least works on your example.
But why bother splitting?
After a look at this old question, this simple solution comes to mind, using a preg_match_all rather than a preg_split. We can use this simple regex to specify what we want:
"[^"]*"|\b\w+\b|=
See online demo.

PHP Pattern Modifier: $ for End-of-Lines in Multi-Line Strings

Note: See the bottom of this post for an explanation for why this wasn't originally working.
In PHP, I am attempting to match lower-case characters at the end of every line in a string buffer.
The regex pattern should be [a-z]$. But that only matches the last letter of the string. I believe this a regex modifier issue; I have experimented with /s /m /D, but nothing appears to match as expected.
<?php
$pattern = '/[a-z]$/';
$string = "this
is
a
broken
sentence";
preg_match_all($pattern, $string, $matches);
print_r($matches);
?>
Here's the output:
Array
(
[0] => Array
(
[0] => e
)
)
Here's what I expect the output to be:
Array (
[0] => Array (
[0] => s
[1] => s
[2] => a
[3] => n
[4] => e
)
)
Any advice?
Update: The PHP source code was written on a Windows machine; text editors in Windows, by convention, represent newlines differently than text editors on Unix system.
It appears that the byte-code representation of Windows text files (inheriting from DOS) was not respected by the PHP regex engine. Converting the end-of-line byte-code format to Unix solved the original problem.
Adam Wagner (see below) has posted a pattern that matches regardless of end-of-line byte-representation.
zerkms has the canonical regular expression, to which I am awarding the answer.
$pattern = '/[a-z]$/m';
$string = "this
is
a
broken
sentence";
preg_match_all($pattern, $string, $matches);
print_r($matches);
http://ideone.com/XkeD2
This will return exactly what you want
As #Will points out, it appears you either want the first char of each string, or your example is wrong. If you want the last char of each line (only if it's a lower-case char) you could try this:
/[a-z](?:\n)|[a-z]$/
The first segment [a-z](?:\n), checks to for lowercase chars before newlines. Then [a-z]$ get the last char of the string (in-case it's not followed by a newline.
With your example string, the output is:
Array
(
[0] => Array
(
[0] => s
[1] => a
[2] => n
[3] => e
)
)
Note - The 's' from 'is' is not present because it is followed by a space. To capture this 's' as well (ignoring trailing spaces), you can update the regex to: /[a-z](?:[ ]*\n)|[a-z](?:[ ]*)$/, which checks for 0 or more spaces immediately before the newline (or end of string). Which outputs:
Array
(
[0] => Array
(
[0] => s
[1] => s
[2] => a
[3] => n
[4] => e
)
)
Update
It appears the line-ending style wasn't liking your regex. To account for crazy line-endings (an other unsavory white-space at the end of the lines), you can use this (and still get the /m goodness).
/[a-z](?:\W*)$/m
It looks like you want to match before every newline, not at the end of the file. Perhaps you want
$pattern = '/[a-z]\n/';

Regular Expressions: get what is outside of the brackets

I'm using PHP and I have text like:
first [abc] middle [xyz] last
I need to get what's inside and outside of the brackets. Searching in StackOverflow I found a pattern to get what's inside:
preg_match_all('/\[.*?\]/', $m, $s)
Now I'd like to know the pattern to get what's outside.
Regards!
You can use preg_split for this as:
$input ='first [abc] middle [xyz] last';
$arr = preg_split('/\[.*?\]/',$input);
print_r($arr);
Output:
Array
(
[0] => first
[1] => middle
[2] => last
)
This allows some surrounding spaces in the output. If you don't want them you can use:
$arr = preg_split('/\s*\[.*?\]\s*/',$input);
preg_split splits the string based on a pattern. The pattern here is [ followed by anything followed by ]. The regex to match anything is .*. Also [ and ] are regex meta char used for char class. Since we want to match them literally we need to escape them to get \[.*\]. .* is by default greedy and will try to match as much as possible. In this case it will match abc] middle [xyz. To avoid this we make it non greedy by appending it with a ? to give \[.*?\]. Since our def of anything here actually means anything other than ] we can also use \[[^]]*?\]
EDIT:
If you want to extract words that are both inside and outside the [], you can use:
$arr = preg_split('/\[|\]/',$input);
which split the string on a [ or a ]
$inside = '\[.+?\]';
$outside = '[^\[\]]+';
$or = '|';
preg_match_all(
"~ $inside $or $outside~x",
"first [abc] middle [xyz] last",
$m);
print_r($m);
or less verbose
preg_match_all("~\[.+?\]|[^\[\]]+~", $str, $matches)
Use preg_split instead of preg_match.
preg_split('/\[.*?\]/', 'first [abc] middle [xyz] last');
Result:
array(3) {
[0]=>
string(6) "first "
[1]=>
string(8) " middle "
[2]=>
string(5) " last"
}
ideone
As every one says that you should use preg_split, but only one person replied with an expression that meets your needs, and i think that is a little complex - not complex, a little to verbose but he has updated his answer to counter that.
This expression is what most of the replies have stated.
/\[.*?\]/
But that only prints out
Array
(
[0] => first
[1] => middle
[2] => last
)
and you stated you wanted whats inside and outside the braces, sio an update would be:
/[\[.*?\]]/
This gives you:
Array
(
[0] => first
[1] => abc
[2] => middle
[3] => xyz
[4] => last
)
but as you can see that its capturing white spaces as well, so lets go a step further and get rid of those:
/[\s]*[\[.*?\]][\s]*/
This will give you a desired result:
Array
(
[0] => first
[1] => abc
[2] => middle
[3] => xyz
[4] => last
)
This i think is the expression your looking for.
Here is a LIVE Demonstration of the above Regex

Categories