Thursday, 18 May 2017

Reverse regular expression, create string from regex



I am working on a multilingual site and have chosen to use custom URLs per language as well, so for example:



/en/cities/paris/
/nl/steden/paris/



Both point to the Index method of the Cities controller.



On every page there's an option to switch language and it will look in my routes to match controller, view and language.



So if I'm on the Dutch page, it will find the proper url for the English version, which will be 'cities' instead of 'steden'.



All worked fine, up until I started using more complex regular expressions.



I have these regular expressions that will match my desired URLs:




#^en/cities/([^/]+?)/$#
#^nl/steden/([^/]+?)/$#


In my code I have access to the variable that's being matched, 'paris' in this example. Would it be possible to 'reverse' this regular expression and have it print 'en/cities/paris/'



If not.. how would I go about having links to different versions of the same page, considering the URLs are different.. preferably have it as programmable as possible.



In a somewhat similar question, someone answered (http://stackoverflow.com/a/7070734/616398) that the essense of regex is to match an infinite number of results.. so it might not be possible.




It's quite easy to go from a string/URL to a set of matched criteria to use in a MVC, but the other way around.. not so much, unfortunately.


Answer



Yes that's possible ! For this case I've coded the following solution:



$regex = '#^en/cities/([^/]+?)/$#';
$replace = array('paris');

$result = preg_replace_callback('#^\^|\([^)]*\)|\$$#', function($m)use($replace){
static $index = 0;

if($m[0] === '^' || $m[0] === '$'){return '';}
if(isset($replace[$index])){
return $replace[$index++];
}
return $m[0];
}, substr($regex, 1, -1));
echo $result; // en/cities/paris/


Online demo




I've made it "flexible" so you can add more values to it !



$regex = '#^en/cities/([^/]+?)/region/([^/]+?)$#'; // <<< changed
$replace = array('paris', 'nord'); // <<< changed

$result = preg_replace_callback('#^\^|\([^)]*\)|\$$#', function($m)use($replace){
static $index = 0;
if($m[0] === '^' || $m[0] === '$'){return '';}
if(isset($replace[$index])){

return $replace[$index++];
}
return $m[0];
}, substr($regex, 1, -1));
echo $result; // en/cities/paris/region/nord


Online demo







Explanation:



$regex = '#^en/cities/([^/]+?)/region/([^/]+?)$#'; // Regex to "reverse"
$replace = array('paris', 'nord'); // Values to "inject"

/* Regex explanation:
# Start delimiter
^\^ Match "^" at the begin (we want to get ride of this)
| Or

\([^)]*\) Match "(", anything zero or more times until ")" is found, ")"
| Or
\$$ Match "$" at the end (we want to get ride of this)
# End delimiter
*/

$result = preg_replace_callback('#^\^|\([^)]*\)|\$$#', function($m)use($replace){
static $index = 0; // Set index 0, note that this variable is only accessible in this (anonymous) function
if($m[0] === '^' || $m[0] === '$'){return '';} // Get ride of ^/$ at the begin and the end
if(isset($replace[$index])){ // Always check if it exists, for example if there were not enough values in $replace, this will prevent an error ...

return $replace[$index++]; // Return the injected value, at the same time increment $index by 1
}
return $m[0]; // In case there isn't enough values, this will return ([^/]+?) in this case, you may want to remove it to not include it in the output
}, substr($regex, 1, -1)); // substr($regex, 1, -1) => Get ride of the delimiters
echo $result; // output o_o



Note: This works only on PHP 5.3+




No comments:

Post a Comment

c++ - Does curly brackets matter for empty constructor?

Those brackets declare an empty, inline constructor. In that case, with them, the constructor does exist, it merely does nothing more than t...