What I would like to do
In php, something like this:
$input = '23:10';
$pattern = '/^(\d?\d):(\d?\d)(:(\d?\d))?$/';
$rewrite = '$1h$2m$4s';
$output = preg_replace( $pattern, $rewrite, $input );
echo $output;
$input
can be a time with or without the seconds, but without seconds it should return '0' as default for $4
in the replacement string. So the output, which is now:
23h10ms
should be:
23h10m0s
Important:
For the solution it's really important that the input of the final function consists of an arbitrary $pattern
and $rewrite
string that transform the $input
string to the output format. So no hard-coded checks for that seconds part, but really a general way of inserting a default value for references in the $rewrite
string that refer to optional parts in the $pattern
string. The function should also work for a case with (for example):
$input = '3 hours';
$pattern = '/^(\d+) hours( and (\d+) minutes)?( and (\d+) seconds)?$/';
$rewrite = '$1h$3m$5s';
As this example illustrates, I have no control over the desired formats ($pattern
+ $rewrite
), so they can vary quite a lot. Most important reason for this: input and output can/will be in other languages too and therefore $pattern
and $rewrite
are obtained from language files (Joomla).
Possible direction to solution
The closest I have come so far is the following:
// Get a $matches array:
preg_match( $pattern, $input, $matches );
// Replace the references ($1, $2, etc.) in the rewrite string by these matches:
$output = preg_replace_callback(
'/\$(\d+)/g',
function( $m ) {
$i = $m[1];
return ( isset( $matches[$i] ) ? $matches[$i] : '0' );
},
$rewrite
);
Two things about this: 1. It does not work yet, because $matches is not available in the callback function. 2. It becomes very tricky using the rewrite string as input for preg_replace_callback().
Update
For the wanted functionality to be dynamic, I'd suggest writing a new class in which all needed information are attributes. Inside that class you can call preg_replace_callback
with a member method and there check for the existence of all needed groups - if one does not exist, it shall be replaced with the defined $defaultValue
.
class ReplaceWithDefault {
private $pattern, $replacement, $subject, $defaultValue;
public function __construct($pattern, $replacement, $subject, $defaultValue) {
$this->pattern = $pattern;
$this->replacement = $replacement;
$this->subject = $subject;
$this->defaultValue = $defaultValue;
}
public function replace() {
return preg_replace_callback($this->pattern, 'self::callbackReplace', $this->subject);
//alternative: return preg_replace_callback($this->pattern, array($this, 'callbackReplace'), $this->subject);
}
private function callbackReplace($match) {
// fill not found groups with defaultValue
if (preg_match_all('/\$\d{1,}/i', $this->replacement, $values)) {
foreach ($values[0] as $value) {
$index = substr($value, 1);//get rid of $ sign
if (!array_key_exists($index, $match)) {
$match[$index] = $this->defaultValue;
}
}
}
$result = $this->replacement;
// do the actual replacement
krsort($match);
foreach ($match as $key=>$value) {
$result = str_replace('$'.$key, $value, $result);
}
return $result;
}
}
You'll need krsort()
, so it doesn't mistake e.g. $15
for {$1}5
.
The class can now be used like this
$originalQuestion = new ReplaceWithDefault('/^(\d?\d):(\d?\d)(:(\d?\d))?$/',
'$1h$2m$4s',
'23:10',
'00');
$updatedQuestion = new ReplaceWithDefault('/^(\d+) hours( and (\d+) minutes)?( and (\d+) seconds)?$/',
'$1h$3m$5s',
'3 hours',
'0');
print "<pre>";
var_dump($originalQuestion->replace());
var_dump($updatedQuestion->replace());
And will produce the wanted result.
original post
I'm not sure if this can be done with a regular preg_replace()
. However, PHP offers another possibility: preg_replace_callback()
. When defining the function, you can use more complex logic.
$input = '23:10';
$pattern = '/^(\d?\d):(\d?\d)(:(\d?\d))?$/';
function correctFormat($hit) {
if (isSet($hit[4]))
$seconds = $hit[4];
else
$seconds = "0";
return $hit[1]."h".$hit[2]."m".$seconds."s";
}
$output = preg_replace_callback( $pattern, "correctFormat" , $input );
echo $output;
I'd suggest a solution as shown by Chinnu R, since a regex is obviously not necessary and has no advantages to a regular explode in this case.