0

I'm building a hand-made function in php that searches for specific tags ( [b][/b] for bold, [i][/i] for italic and [img][/img] for pictures ) in a string, to replace them into their html equivalent. I also have to treat what's between [img] [/img] tags (can be more than one in a string) in a seperate function before the replacement, i've called it foo here :

<?php
function convert($txt){
    $patterns = array(  '/\[b\](.*?)\[\/b\]/' ,
                        '/\[i\](.*?)\[\/i\]/' ,
                        '/\[img\](.*?)\[\/img\]/');
    $replace = array("<b>$1</b>" , "<i>$1</i>" , foo("$1") );
    $res = preg_replace($patterns,$replace, $txt);
    return $res;
}

It works correctly for the b and i tags, but not img.

The issue here is that : When i put the captured group (referenced by "$1" i think) in a function, it treats "$1" as a string, and not what is referenced by it. For example, if foo is declared like so :

function foo($var){
    echo $var;
}

If i put the string text1 [img]path[/img] text2 in convert()

Then "$1" will be echoed, instead of "path"


Therefore here is my question : How can i "evaluate" the string i've captured in a different function. In the previous example, to echo in foo what is between [img][/img] tags?

Thank you for anyone taking time to reply.

RenardBOT
  • 11
  • 3
  • Use single quotes or escape the `$` symbols. https://www.php.net/manual/en/language.types.string.php – Sammitch May 03 '22 at 23:01
  • [Here an idea](https://tio.run/##TZDBioMwEIbveYpBCtGlVNrjttbrHnuPocR01NBoQozLwrZ9dXcqu2Vvw/cn3/yM7/w8H8rTx4mxZhp0NG4A7YZPDDFdxa@YsW8GEDBOYQAfsD0H9FZpPGtlba30NeWPSqT1zdxM32aVTDdvZVaJvNrKB1/DnzVd9RmZnjaAVS92EgroYm9Hj9ooqzsVxnQJsj3kOX0MscNAS53GcQStPJVAcA20wU0edmAaGBAveFmkvyVJsZVFkVCbBErgBxpgDLpI@GbRb3hy5PBOyQK2BI6v6JD/g3vy3tew3GHP7oyh7tzrPFyQWZpetQijm4JGkT/JGkQta2hcgNrZi8jrJzLSLMhEZY2ml5KTk5XHef4B) with `preg_replace_callback` – bobble bubble May 03 '22 at 23:30
  • 1
    https://stackoverflow.com/a/2082209/2943403 , https://stackoverflow.com/a/35200542/2943403 , https://stackoverflow.com/a/13047070/2943403 – mickmackusa May 03 '22 at 23:47
  • https://stackoverflow.com/q/70983730/2943403 – mickmackusa May 04 '22 at 00:09
  • 1
    There is so much relatable content on Stack Overflow that simply isn't related. The Linked side-list is only populated after adding these comments and the Related side-list is always absolute garbage @bobblebubble I don't think that this question Needs Focus; it seems pretty well-narrowed to me. – mickmackusa May 04 '22 at 01:06
  • @Renard Do you see why it is inappropriate for `foo()` to `echo`? The function should be `return`ing a value, not echoing it. – mickmackusa May 04 '22 at 03:39
  • Why don't you use a library that do bbcode to html for you? BTW, regex isn't the way to manage this. Search for tokenization. – Braiam May 04 '22 at 11:09
  • @mickmackusa yes! i just used foo as an example. In my code it actually returns a value after having fetched something in my database with the value passed as a parameter. But the issue was the same. – RenardBOT May 04 '22 at 12:02
  • @Braiam I wished i could use libraries but it's a school project and we are not allowed to use them. only raw php, which is why i'm struggling a bit! I'll try to look into tokenization. – RenardBOT May 04 '22 at 12:03

3 Answers3

1

Try this

<?php
function convert($txt){
    $pattern = array('/\[b\](.*?)\[\/b\]/' => function($matches) { return " 
<b>$matches[1]</b>"; },
                     '/\[i\](.*?)\[\/i\]/' => function($matches) { return " 
<i>$matches[1]</i>"; },
                     '/\[img\](.*?)\[\/img\]/' => function($matches) { echo 
$matches[1]; return "<img>$matches[1]</img>"; });
    $res = preg_replace_callback_array($pattern, $txt);
    return $res;
}
$result = convert("text1 [img]path[/img] text2");
echo "\n$result\n";

Output:

path
text1 <img>path</img> text2
  • This is a good answer, but it would be much better if it explained the changes it makes to the code in the question. As someone coming in without an understanding of `preg_replace_callback_array`, it's not immediately clear what it does. This could even be in the form of code comments explaining each step. – Ryan M May 04 '22 at 00:12
  • If who have never seen `preg_replace_callback_array`, all they have to do is open the PHP manual. Is that something a beginner can do, and Stack Overflow has to pay attention to such a basically thing? https://www.php.net/manual/en/function.preg-replace-callback-array – Itagaki Fumihiko May 04 '22 at 00:24
  • That's true. Adding the explanation is _optional_, but it makes the answer _better_, because it spares people the time of doing that. You could even write something as simple as "`preg_replace_callback_array` does exactly this: it takes an array of regex patterns paired with functions that return the replacement for that pattern." – Ryan M May 04 '22 at 00:27
  • This answer does not attempt to populate the `` tag, it merely appends the bbcode string after it. The `foo()` function is never called. Using a pattern delimiter other than a forward slash will cut down on some escaping inside the pattern. Square brace array syntax and PHP7.4 arrow function syntax will cut down on some of the syntactic bloat. `$res` is an unnecessary, single-use variable declaration. – mickmackusa May 04 '22 at 01:14
  • I think your obsession doesn't make sense to me, the questioner, and the other visitors, but I've revised and cleaned up the answer. How's that sound? – Itagaki Fumihiko May 04 '22 at 04:42
  • It is very clean, preg_replace_callback_array does seem to make everything simpler. – RenardBOT May 04 '22 at 12:14
1

First and foremost, it is strongly recommended to use a legitimate BBCode parser (library) instead of a regex approach. A custom-developed parser should be expected to better handle fringe cases much better than a basic regex pattern.

Now that that disclaimer is given, the way to resolve your issue of calling a function from the replacement parameter of preg_replace() is to call preg_replace_callback(), or in your case, perhaps better coded via preg_replace_callback_array() since you are seeking different callbacks for different patterns.

Code: (Demo)

function convert(string $txt): string {
    do {
        $txt = preg_replace_callback_array(
            [
                '~\[([bi])](.*?)\[/\1]~' => fn($m) => sprintf('<%1$s>%2$s</%1$s>', $m[1], $m[2]),
                '~\[img](.*?)\[/img]~' => 'foo',
            ],
            $txt,
            -1,
            $count
        );
    } while ($count);
    return $txt;
}

function foo(array $m): string {
    return '<img src="' . $m[1] . '">';
}

echo convert("text1 [img]path/src[/img] text2 [b]bold [i]nested string[/i][/b] [img]another/path/to/file[/img] [b]nice[/b] lingering end bbtag [/b] and [b]unclosed");

Output:

text1 <img src="path/src"> text2 <b>bold <i>nested string</i></b> <img src="another/path/to/file"> <b>nice</b> lingering end bbtag [/b] and [b]unclosed

You will notice that calling foo() is done by using its string name as the callback value. The matches array is sent to the custom function despite not being explicitly mentioned in the 'foo' value.

I am calling preg_replace_callback_array() in a do-while() loop to ensure that nested bbcode tags are replaced (which otherwise would have been overlooked because their parent tag completely enclosed them).

If you wish to handle [u] tags, simply add u after bi in the first regex pattern.

mickmackusa
  • 43,625
  • 12
  • 83
  • 136
  • It's a school project so we are not allowed to use libraries sadly! It's raw php only. – RenardBOT May 04 '22 at 10:56
  • Kind of. Or at least it is much less obscure to me! I looked up for preg_replace_callback related stuff and it is what i was looking for for what i am trying to achieve. – RenardBOT May 04 '22 at 12:09
  • If you would like me to further explain any part of my answer, just ask. – mickmackusa May 04 '22 at 12:14
  • Alright, there's one single point i'm curious about. So, function foo() : string { . . . } is a way to say we return a string? Is it good practice to always do that with php functions, as in indicate return type? For it is dynamically typed, I didn't know you could do that. – RenardBOT May 04 '22 at 12:31
  • Yes, I highly recommend declaring the types on both the incoming arguments and the return data. – mickmackusa May 04 '22 at 12:33
  • 1
    Ha! There are still people out there, that promote understanding and go into detail :) So we don't need to be desperate, as long there is someone out there teaching us to to fish. – bobble bubble May 04 '22 at 13:37
0

You can grab the string first and then run the function:

<?php
function convert($txt){
    preg_match('/\[img\](.*?)\[\/img\]/', $txt, $match);
    $patterns = array(  '/\[b\](.*?)\[\/b\]/' ,
                        '/\[i\](.*?)\[\/i\]/' ,
                        '/\[img\](.*?)\[\/img\]/');
    $replace = array("<b>$1</b>" , "<i>$1</i>" , foo($match[1]) );
    $res = preg_replace($patterns,$replace, $txt);
    return $res;
}
Lucas
  • 394
  • 2
  • 13
  • `preg_match()` is probably not suitable because it only makes one match, but it is easily assumed that a realistic string may contain more than one img tag. – mickmackusa May 04 '22 at 03:39
  • As @mickmackusa said, a string can contain multiple [img] tags. It's on my end, as I should have specified it in my question! – RenardBOT May 04 '22 at 12:17