preg_replace_callback com Spoilers em Spoilers

Eu escrevo algo como [spoiler=Spoiler Title]text inside the Spoiler[/spoiler] e use preg_replace_callback("/\[spoiler=(.*)\](.*)\[\/spoiler\]/Usi", 'BBCode_spoiler', $text); para criar um spoiler real, o resultado com um ou mais spoiler é:

  function show_0() { if(document.getElementById("0").style.display == "inline-block") document.getElementById("0").style.display = "none"; else document.getElementById("0").style.display = "inline-block"; }  Show Spiler: Spoiler Title
Text inside the Spoiler

Como faço para que ele funcione com um spoiler em um spoiler como [spoiler=Spoiler Title][spoiler=Second Spoiler]Another Text[/spoiler][/spoiler] minha function atual retorna por nenhum spoiler em um spoiler

  function show_0() { if(document.getElementById("0").style.display == "inline-block") document.getElementById("0").style.display = "none"; else document.getElementById("0").style.display = "inline-block"; }  Show Spiler: Spoiler Title
[spoiler=Second Spoiler]Another Text
[/spoiler]

minha function de retorno de chamada é

 <?php // Spoiler $counter = 0; function BBCode_spoiler($hits) { global $central_lang; global $counter; $title = htmlentities(trim($hits[1])); $text = htmlentities($hits[2]); $return = ""; $return .= "function show_".$counter."() {"; $return .= "if(document.getElementById(\"".$counter."\").style.display == \"inline-block\") document.getElementById(\"".$counter."\").style.display = \"none\";"; $return .= "else document.getElementById(\"".$counter."\").style.display = \"inline-block\"; }"; $return .= ""; $return .= "".$central_lang['bbcodes']['spoiler']['text'].": ".$title."
"; $return .= "
".$text."
"; $counter++; return $return; } ?>

A saída que eu tento fazer parece assim

  function show_0() { if(document.getElementById("0").style.display == "inline-block") document.getElementById("0").style.display = "none"; else document.getElementById("0").style.display = "inline-block"; }  Show Spoiler: Spoiler Title
function show_1() { if(document.getElementById("1").style.display == "inline-block") document.getElementById("1").style.display = "none"; else document.getElementById("1").style.display = "inline-block"; } Show Spoiler: Second Spoiler
Another text

Espero que seja uma resposta para a minha pergunta, obrigado!

Isso se aplica ao BBCode em geral: re-execute o código de substituição até parar de alterá-lo.

preg_replace_callback aceita uma variável de referência “contagem”, que será preenchida com o número de substituições feitas. Enquanto esse número não for zero, você deve voltar a executar a substituição (faz … do..while é perfeito para isso)

Não importa que eles sejam atravessados. Digamos que temos um BBCode que substitui [div] por

 [div]Blah[div]123[/div]Fish[/div] 

Após uma substituição:

 
Blah[div]123
Fish[/div]

Após outra substituição:

 
Blah
123
Fish

Então, mesmo que eles foram processados ​​em uma ordem cruzada, o resultado está corretamente nested.

Você pode usar o parâmetro de contagem de preg_replace_callback para processar a substituição do interior para o extremo, até que não haja mais tags para replace:

 $pattern = '~\[spoiler=([^]]*)]((?>[^[]+|\[(?!/?spoiler\b))*)\[/spoiler]~i'; do { $result = preg_replace_callback($pattern, 'BBCode_spoiler', $text, -1, $count); } while ($count>0); 

Altero um pouco o padrão para garantir que a partida seja a marca mais interna.