Vae
Vae

Reputation: 656

preg_replace : getting a html tag inside an other html tag from BBCode

So I'm trying to make a php function to get HTML tags from a BBCode-style form. The fact is, I was able to get tags pretty easily with preg_replace. But I have some troubles when I have a bbcode inside the same bbcode...

Like this :

[blue]My [black]house is [blue]very[/blue] beautiful[/black] today[/blue]

So, when I "parse" it, I always have remains bbcode for the blue ones. Something like :

My house is [blue]very[/blue] beautiful today

Everything is colored except for the blue-tag inside the black-tag inside the first blue-tag.

How the hell can I do that ?

With more informations, I tried :

Regex: "/\[blue\](.*)\[\/blue\]/si" or "/\[blue\](.*)\[\/blue\]/i"
Getting : "My house is [blue]very[/blue] beautiful today"

Regex : "/\[blue\](.*?)\[\/blue\]/si" or "/\[blue\](.*)\[\/blue\]/Ui"
Getting : "My house is [blue]very beautiful today[/blue]"

Do I have to loop the preg_replace ? Isn't there a way to do it, regex-style, without looping the thing ?

Thx for your concern. :)

Upvotes: 2

Views: 848

Answers (2)

Umur Karagöz
Umur Karagöz

Reputation: 3190

It is right that you should not reinvent the wheel on products and rather choose well-tested plugins. However, if you are experimenting or working on pet projects, by all means, go ahead and experiment with things, have fun and obtain important knowledge in the process.

With that said, you may try following regex. I'll break it down for you on below.

(\[(.*?)\])(.*?)(\[/\2\])

Philosophy

While parsing markup like this, what you are actually seeking is to match tags with their pairs.

So, a clean approach you can take would be running a loop and capturing the most outer tag pair each time and replacing it.

So, on the given regex above, capture groups will give you following info;

  1. Opening tag (complete) [black]
  2. Opening tag (tag name) black
  3. Content between opening and closing tag My [black]house is [blue]very[/blue] beautiful[/black] today
  4. Closing tag [/blue]

So, you can use $2 to determine the tag you are processing, and replace it with

<tag>$3</tag>
// or even
<$2>$3</$2>

Which will give you;

// in first iteration
<tag>My [black]house is [blue]very[/blue] beautiful[/black] today</tag>

// in second iteration
<tag>My <tag2>house is [blue]very[/blue] beautiful</tag2> today</tag>

// in third iteration
<tag>My <tag2>house is <tag3>very</tag3> beautiful</tag2> today</tag>

Code

$text = "[blue]My [black]house is [blue]very[/blue] beautiful[/black] today[/blue]";

function convert($input)
{
    $control = $input;

    while (true) {
        $input = preg_replace('~(\[(.*?)\])(.*)(\[/\2\])~s', '<$2>$3</$2>', $input);

        if ($control == $input) {
            break;
        }

        $control = $input;
    }

    return $input;
}


echo convert($text);

Upvotes: 1

Jan
Jan

Reputation: 43169

As others mentionned, don't try to reinvent the wheel.
However, you could use a recursive approach:

<?php

$text = "[blue]My [black]house is [blue]very[/blue] beautiful[/black] today[/blue]";

$regex = '~(\[ ( (?>[^\[\]]+) | (?R) )* \])~x';

$replacements = array(  "blue" => "<bleu>", 
                        "black" => "<noir>", 
                        "/blue" => "</bleu>",
                        "/black" => "</noir>");

$text = preg_replace_callback($regex,
    function($match) use ($replacements) {
        return $replacements[$match[2]];
    },
    $text);

echo $text;
# <bleu>My <noir>house is <bleu>very</bleu> beautiful</noir> today</bleu>

?>

Here, every colour tag is replaced by its French (just made it up) counterpart, see a demo on ideone.com. To learn more about recursive patterns, have a look at the PHP documentation on the subject.

Upvotes: 0

Related Questions