How to get preg_match to not cross the results?
I've got a problem. I am raplacing something in between [%
and %]
. I can catch [%some var%]
with no problems (it catches "some var"
).
However, when I have this:
[%something|[%with parameter that should be parsed before/after as well%]%]
then it catches
"something|[%with parameter that should be parsed before/after as well"
How can I fix this? In my opinion I could check for [% %]%]
match first, but it is no sollution when it's like
[%something|[%with parameter that should be parsed before/after as well%] and something unparsed%]
Maybe if I could manage to rewrite the regexp to ignore when there's [%
and %]
but isn't another [%
or %]
in between. Anyway, my knowledge of regexps is poor, but I decided to use regexps instead of strpos's...
EDIT0
Well, I 'd prefer to use while cycle to replace [% %]'s that has no other [% or %] inside on the fly...
I mean:
for example: I have certain replacements defined:
post.date = 1. 1. 1970
post.time = 00:00:00
post.creator.name = John Smith
post.creator.age = (computed) 64
replacement function is already created (and works properly if there's no recursion). Does this
[%<replacement variable name>|(optional) prefix|(optional) suffix%]
result:
prefix<replacement variable's value>suffix
this already works.
Sample text:
"This post was created on [%post.date%][%post.time%| at ][%post.creator.name| by [%post.creator.age||years old %]user%]."
Therefore the cycle should to this with the sample text:
Step 0: "This post was created on 1. 1. 1970[%post.time%| at ][%post.creator.name| by [%post.creator.age||years old %]user%]."
Step 1: "This post was created on 1. 1. 1970 at 00:00:00[%post.creator.name| by [%post.creator.age|| years old %]user%]."
Step 2: "This post was created on 1. 1. 1970 at 00:00:00[%post.creator.name| by 64 years old user%]."
Step 3: "This post was created on 1. 1. 1970 at 00:00:00 by 64 years old user John Smith."
Hope you now see the point.
EDIT1
Probably I just don't need regexps as this is way too much complex. Maybe I just need to write my own parser. After it hits %] it would basically check whether there is no double, unclosed [% before. Yep... That should do the trick but PLEASE, still try t开发者_如何学运维o help me though. :) Thanks!
EDIT2
Finally got a sollution!
Now it really does
This is a post[%post.date| on %][%post.time| at %][%post.creator.name| by [%post.creator.age|| years old %]user %].
Step 0: This is a post on 1. 1. 1970[%post.time| at %][%post.creator.name| by [%post.creator.age|| years old %]user %].
Step 1: This is a post on 1. 1. 1970 at 00:00:00[%post.creator.name| by [%post.creator.age|| years old %]user %].
Step 2: This is a post on 1. 1. 1970 at 00:00:00[%post.creator.name| by 64 years old user %].
Step 3: This is a post on 1. 1. 1970 at 00:00:00 by 64 years old user John Smith.
Sam Graham's advice is 100% applicable with minor editing. Thanks, Sam Graham!
I'd use (\[%((?:[^\[]|\[(?!%))*?)%\])
.
To break it down:
( // Start capturing group 1 for the entire [%...%] block
\[% // Match a literal [%
( // Start capturing group 2 for the inner contents of the [%...%] block
(?: // Start a non-capturing group of alternative matches
[^\[] // Match anything that isn't a literal [
| // or
\[(?!%) // Match a literal [ that isn't followed by a %
) // End list of alternative matches
*? // Match as few as possible of the previous item
) // End capture group 2
%\] // Match a literal %]
) // End capture group 1
Or to put it in english, match a [% then anything that isn't another [% until you find the first %], remembering the bit in the middle of the [% %] and the entire thing including [% %].
You can test with the following php script:
<?
$tests = array(
"[%something|[%with parameter that should be parsed before/after as well%]%]",
"[%something%][%something else%]",
);
foreach ($tests as $test) {
echo "Testing $test:\n";
$loop = 0;
while (preg_match("/(\[%((?:[^\[]|\[(?!%))*?)%\])/", $test, $matches)) {
$loop++;
echo " First loop, looking at $test:\n";
echo " group 1: $matches[1]\n group 2: $matches[2]\n";
// Do whatever here...
$test = str_replace($matches[1], "REPLACED!", $test);
echo " replaced: $test\n";
}
}
?>
Should give you output:
Testing [%something|[%with parameter that should be parsed before/after as well%]%]:
First loop, looking at [%something|[%with parameter that should be parsed before/after as well%]%]:
group 1: [%with parameter that should be parsed before/after as well%]
group 2: with parameter that should be parsed before/after as well
replaced: [%something|REPLACED!%]
First loop, looking at [%something|REPLACED!%]:
group 1: [%something|REPLACED!%]
group 2: something|REPLACED!
replaced: REPLACED!
Testing [%something%][%something else%]:
First loop, looking at [%something%][%something else%]:
group 1: [%something%]
group 2: something
replaced: REPLACED![%something else%]
First loop, looking at REPLACED![%something else%]:
group 1: [%something else%]
group 2: something else
replaced: REPLACED!REPLACED!
If you're prepared to do one cycle of regex for each level of nesting (as suggested by your edits) then it's easy:
$result = preg_replace_callback(
'/\[% # Match [%
( # Match and capture...
(?: # the following:
(?! # If the next part of the string is neither...
\[% # [%
| # nor
%\] # %]
) # (End of lookahead)
. # then match any character.
)* # Do this any number of times.
) # End of capturing group.
%\] # Match %]
/x',
'compute_replacement', $subject);
function compute_replacement($groups) {
// $groups[1] holds the text between [%...%]
return 'myreplacement';
}
Do this once for every level of nesting.
To match nested tags like those you could use:
\[%((?:[^[%]++|\[(?!%)|%(?!])|(?R))*)%]
精彩评论