Improving speed of regex and using preg_replace for slugs - php

Previously I have been echoing $obj->html but a current project requires that the HTML be examined for slugs like {whatever} and replacing these with other content.
I have two problems. The first is that this code is slower than I would like:
class Foo {
function draw_content() {
$slug = "/(?<=\{).*(?=\})/";
if (preg_match($slug, $this->html, $matches)) {
foreach ($matches as $match) {
if (method_exists($this,$match)) {
$replacement = $this->$match();
$this->html = preg_replace("/\{$match\}/", $replacement, $this->html);
}
}
}
return $this->html;
} // fn
function new_releases() {
echo "new release book covers";
} // fn
} // class
Is there a better way to get the slug contents? I presume the regex is what is slowing this down?
The second issue is stranger to me. Given this $obj->html:
<p class="headline">New Releases</p>
<p>xxx{new_releases}xxxx</p>
The processed output of $obj->draw_content() is drawn by <?=$obj->draw_content()?>
new release book covers<p class="headline">New Releases</p>
<p>xxxxxxx</p>
Why is the new_releases() output prepended? The slug is gone but the replacement is not in it's place!

you can replace your pattern by:
$slug = '~{\K[^}]*+(?=})~';
IMHO, you should replace your preg_match test and your preg_replace by an only preg_replace_callback function, try something like this (and correct the bugs :).
function draw_content() {
$slug = '~{([^}]*+)}~';
$that = $this;
$this->html = preg_replace_callback( $slug, function ($m) use ($that) {
if (method_exists($that, $m[1]))
return $that->$m[1]();
return $m[0]; }, $this->html);
return $this->html;
}

Related

Proper way to replace string with callback

I'm working on implementation of a CMS and want to include functionality similar to how word press uses short codes, but I am having trouble replacing the "shortcode" with the function callback.
I'm using the below regex to find all "shortcodes" in the code and it works, I just can't figure out how to go about replacing it with function callbacks.
Regex: /[([^]]*)]/
What I have so far(not working)
function runShortcodes($input){
return preg_replace_callback(
'/\[([^\]]*)\]/', function ($matches)
{
$function = $matches[1];
ob_start();
$function();
$return = ob_get_contents();
ob_end_clean();
return $return;
}, $input
);
}
function event(){
return 'it worked';
}
echo runShortcodes('test [event]');
Right now i'm just trying to replace the [event] with the return data of the event function.
As you are using output buffering to capture the value from the short code function, you will need to actually output something from the event() function...
function event(){
return 'it worked';
}
just passes the value back, try...
function event(){
echo 'it worked';
}
Or remove the output buffering and just return the value from the short code...
function runShortcodes($input){
return preg_replace_callback(
'/\[([^\]]*)\]/', function ($matches)
{
$function = $matches[1];
return $function();
}, $input
);
}
function event(){
return 'it worked';
}
echo runShortcodes('test [event]');

PHP Using preg_replace_callback shortcodes function returns wrong elements order

I'm defining a function which can handle shortcodes for users wisiwyg made posts.
Using a function based on preg_replace_callback works great, but the returned replaced values prints before the initial string
This is the handler function
function shortcodify($string){
return preg_replace_callback('#\[\[(.*?)\]\]#', function($matches){
$parts = explode(':',$matches[1]);
$fnName = array_shift($parts);
if(function_exists($fnName)){
return call_user_func_array($fnName,$parts);
} else {
return $matches[0];
}
},$string);
}
This is the function which will replace the shortcode
function slider($tag){
//search $tag in DB
echo '<div>...'.$sliderContentFromDB.'...</div>';
}
The usage:
$postContent = "<h1>Super Slider</h1> [[slider:super-slider]] <p>Slider Description</p>";
shortcodify($postContent);
The expected result is:
<h1>Super Slider</h1>
<div>...super slider content...</div>
<p>Slider Description</p>
The actual result is:
<div>...super slider content...</div>
<h1>Super Slider</h1>
<p>Slider Description</p>
What could I be doing wrong?
You should return the value rather than echoing it. What you're seeing is correct, in that the callback function should evaluate before the preg_replace_callback() result is returned to your variable.
function slider($tag){
//search $tag in DB
return '<div>...'.$sliderContentFromDB.'...</div>';
}
Returning will ensure it gets aggregated into the rest the results from preg_replace_callback(), and returned in the correct order. Example.
This is how I would do it:
function shortcodify($string){
return preg_replace_callback('#\[\[(.*?)\]\]#', function($matches){
//start output buffering
ob_start();
$parts = explode(':',$matches[1]);
$fnName = array_shift($parts);
//echo values in case they return instead of echo
if(function_exists($fnName)){
echo call_user_func_array($fnName,$parts);
} else {
echo $matches[0];
}
//return contents of buffer
return ob_get_clean();
},$string);
}
Now if you return or echo from the shortcode it makes no difference, because in either case it will get swept up by the buffer.

How to call a function in an preg_replace?

How can I call a function within preg_replace()?
$template = '<div> [BILD="123"][BILD="246"] </div>';
$pat = '/\[BILD="(.*?)"\]\[BILD ="(.*?)"\]/';
$ret = getImageArea($id, $config, '$1', '$2');
return preg_replace($pat, $rep, $template);
It fails opening the function and doesn't sends 123 or 246.
It just sends $1 and $2.
As I (and many others) told in comments, you have to use preg_replace_callback in this case. One possible approach:
$template = '<div> [BILD="123"][BILD="246"] </div>';
$pat = '/\[BILD="(.*?)"\]\[BILD="(.*?)"\]/';
return preg_replace_callback($pat, function($matches) use($id, $config) {
return getImageArea($id, $config, $matches[1], $matches[2]);
}, $template);
Demo. As you see, it's quite straight-forward; the only catch is making $id and $config variables available inside the callback (which is done with use op).

mustache i18n with parameters

I'm trying to use Mustache together with i18n (php, within Wordpress). I've got the basic __ functionality working nicely, something like this
class my_i18n {
public function __trans($string) {
return __($string, 'theme-name');
}
}
class mytache {
public function __()
{
return array('my_i18n', '__trans');
}
}
Then to output a template with an i18n string, I can simply do this
$context = new mytache;
$template = "<div>{{#__}}String to translate{{/__}}</div>";
$m = new Mustache;
echo $m->render($template, $context);
So far everything is fine. However, I want to be able to translate strings with parameters. i.e. the equivalent of sprint_f(__('Account Balance: %s'), $balance);.
It seems that if I do something like {{#__}}Account Balance: {{balance}}{{/__}} it doesn't work. I'm guessing because the inner tag gets converted first and therefore the translation cannot be found for the phrase.
Any ideas how to achieve this cleanly with Mustache?
UPDATE: here's the end-result snippet (with massive help from bobthecow):
class I18nMapper {
public static function translate($str) {
$matches = array();
// searching for all {{tags}} in the string
if (preg_match_all('/{{\s*.*?\s*}}/',$str, &$matches)) {
// first we remove ALL tags and replace with %s and retrieve the translated version
$result = __(preg_replace('/{{\s*.*?\s*}}/','%s', $str), 'theme-name');
// then replace %s back to {{tag}} with the matches
return vsprintf($result, $matches[0]);
}
else
return __($str, 'theme-name');
}
}
class mytache {
public function __()
{
return array('I18nMapper', 'trans');
}
}
I added an i18n example here... it's pretty cheesy, but the test passes. It looks like that's almost the same as what you're doing. Is it possible that you're using an outdated version of Mustache? The spec used to specify different variable interpolation rules, which would make this use case not work as expected.
On my behalf I would suggest using normal, fully functional template engine. I understand, that small is great and everything, but for example Twig is much more advanced. So I would recommend it.
About mustache. Can't you just extend you translation method! For example you pass {{#__}}Account Balance: #balance#{{/__}}
function __( $string, $replacement )
{
$replaceWith = '';
if ( 'balance' == $replacement )
{
$replaceWith = 234.56;
}
return str_replace( '#' . $replacement . '#', $replaceWith, $string );
}
class my_i18n
{
public function __trans( $string )
{
$matches = array();
$replacement = '';
preg_match( '~(\#[a-zA-Z0-9]+\#)~', $string, $matches );
if ( ! empty( $matches ) )
{
$replacement = trim( $matches[0], '#' );
}
return __( $string, $replacement );
}
}
$Mustache = new Mustache();
$template = '{{#__}}Some lime #tag#{{/__}}';
$MyTache = new mytache();
echo $Mustache->render( $template, $MyTache );
This is a very ugly example, but you can make it fancy yourself. As I see Mustache on it's own will not be able to do what you want.
Hope that helped.

Named backreferences with preg_replace

Pretty straightforward; I can't seem to find anything definitive regarding PHP's preg_replace() supporting named backreferences:
// should match, replace, and output: user/profile/foo
$string = 'user/foo';
echo preg_replace('#^user/(?P<id>[^/]+)$#Di', 'user/profile/(?P=id)', $string);
This is a trivial example, but I'm wondering if this syntax, (?P=name) is simply not supported. Syntactical issue, or non-existent functionality?
They exist:
http://www.php.net/manual/en/regexp.reference.back-references.php
With preg_replace_callback:
function my_replace($matches) {
return '/user/profile/' . $matches['id'];
}
$newandimproved = preg_replace_callback('#^user/(?P<id>[^/]+)$#Di', 'my_replace', $string);
Or even quicker
$newandimproved = preg_replace('#^user/([^/]+)$#Di', '/user/profile/$1', $string);
preg_replace does not support named backreferences.
preg_replace_callback supports named backreferences, but after PHP 5.3, so expect it to fail on PHP 5.2 and below.
preg_replace does not supported named subpatterns yet.
you can use this :
class oreg_replace_helper {
const REGEXP = '~
(?<!\x5C)(\x5C\x5C)*+
(?:
(?:
\x5C(?P<num>\d++)
)
|
(?:
\$\+?{(?P<name1>\w++)}
)
|
(?:
\x5Cg\<(?P<name2>\w++)\>
)
)?
~xs';
protected $replace;
protected $matches;
public function __construct($replace) {
$this->replace = $replace;
}
public function replace($matches) {
var_dump($matches);
$this->matches = $matches;
return preg_replace_callback(self::REGEXP, array($this, 'map'), $this->replace);
}
public function map($matches) {
foreach (array('num', 'name1', 'name2') as $name) {
if (isset($this->matches[$matches[$name]])) {
return stripslashes($matches[1]) . $this->matches[$matches[$name]];
}
}
return stripslashes($matches[1]);
}
}
function oreg_replace($pattern, $replace, $subject) {
return preg_replace_callback($pattern, array(new oreg_replace_helper($replace), 'replace'), $subject);
}
then you can use either \g<name> ${name} or $+{name} as reference in your replace statement.
cf (http://www.rexegg.com/regex-disambiguation.html#namedcapture)

Categories