Autor Zpráva
Tori
Profil
Pěkný večer.
Prosím o radu, jak zlepšit tuto funkci. Funguje správně, ale mám pocit, že by šla napsat i efektivněji (a vůbec je moc dlouhá).
Díky moc za ochotu.

/**
 * @param string $text Vstupni text
 * @param array $open Zacatecni tag. array([search], [replace])
 * @param array $close Ukoncovaci tag. array([search], [replace])
 * @param string $tagName Jmeno tagu (aby se nemusel rozebirat regular).
 * @return string
 */
function replaceNestedTags($text, $openTag, $closeTag, $tagName)	{

	// Zjisti pocet tagu a offsety
	preg_match_all('~'.str_replace('~','\~',$openTag['search']).'~i', $text, $matchesOpen, PREG_OFFSET_CAPTURE | PREG_PATTERN_ORDER);
	preg_match_all('~'.str_replace('~','\~',$closeTag['search']).'~i', $text, $matchesClose, PREG_OFFSET_CAPTURE | PREG_PATTERN_ORDER);
	$found = array_merge($matchesOpen[0], $matchesClose[0]);
	if (empty($found))
		return $text;
	$queue = array();
	foreach ($found as $item)
		$queue[$item[1]] = $item[0];
	ksort($queue);
	
	// Oprav neuzavrene tagy
	$depth = 0;
	$offsetFix = 0;
	foreach($queue as $offset=>$tag)	{
		$level = substr($tag,1,1) == '/' ? -1 : 1; // Koncovy nebo zacatecni tag?
		if ($depth == 0 && $level == -1)	{
			$text = substr_replace($text, '', $offset-$offsetFix, $ln = strlen($tag));
			$offsetFix += $ln;
			continue;
		}
		$depth += $level;
	}
	if ($depth > 0)
		$text = substr_replace($text, str_repeat("[/$tagName]", $depth), $offset-$offsetFix+strlen($tag), 0);
		
	// Nahrazeni tagu HTML
	$text = preg_replace('~'.str_replace('~','\~',$openTag['search']).'~i', $openTag['replace'], $text);
	$text = preg_replace('~'.str_replace('~','\~',$closeTag['search']).'~i', $closeTag['replace'], $text);
	return $text;
}
Příklad použití:
$openTag = array('search'=>'\[quote(?:=([^\]]+))?\]', 'replace'=>'<div class="quote"><b>Citace $1</b><br>');
$closeTag = array('search'=>'\[/quote\]', 'replace'=>'</div>');
$text = replaceNestedTags($text, $openTag, $closeTag, 'quote');
peta
Profil
Tori: Rozumis te funkci? Prevod znacek jsou na radku (1). To ostatni resi otevrene a uzavrene tagy a snazi se je doplnit. Nahrazeni tagu muzes udelat i pres str_replace (2), http://cz.php.net/str_replace. Samotne nahrazeni by slo napsat jako (3).
(1)
    // Nahrazeni tagu HTML
    $text = preg_replace('~'.str_replace('~','\~',$openTag['search']).'~i', $openTag['replace'], $text);
    $text = preg_replace('~'.str_replace('~','\~',$closeTag['search']).'~i', $closeTag['replace'], $text);
    return $text;
(2)
a)
$bodytag = str_replace("%body%", "<body>", "%html%%body%");
b)
$search = array("[quote]", "[/quote]","", ""); // MODERATOR? Rozbils to. je tam " [ b ] "," [ / b ]" (bez mezer, proste b tag)
$replace = array("<div class=\"quote\">", "</div>", "<b>", "</b>");
$text  = "You should eat fruits, vegetables, and fiber every day.";
$text = str_replace($search, $replace, $text);
(3)
$text = preg_replace('\[quote(?:=([^\]]+))?\]','<div class="quote"><b>Citace $1</b><br>', $text);
$text = preg_replace('\[/quote\]', '</div>', $text);
Tori
Profil
peta:
Ale jistě, že bych mohla řešit nahrazování jedním nebo dvěma reguláry. Původně se to i na tom fóru používalo, jenže:
- regulár [tag](.+?)[/tag] neřeší vnořené tagy, což se hlavně u citací hodně používá.
- samostatný převod [tag] => <div>, [/tag] => </div> neřeší neuzavřené tagy, a pár </div> navíc může nadělat docela paseku.
- rekurzivní zpracování regulárem [tag](.+?)[/tag] rovněž neo(d)praví neuzavřené tagy. Proto jsem to zkoušela udělat takhle.

Možná by šlo použít rekurzi a pak odmazat všechny přebývající začáteční/koncové tagy, ale nezkoušela jsem to. Celkově mi rekurzivní zpracovávání regulárem přišlo míň efektivní než 2x vyhledání + 2x nahrazení (+výjimečně pár str_replace). Na druhou stranu, pokud by se to aplikovalo na jednotlivé příspěvky o průměrně 10 řádcích, tak ten rozdíl asi nebude tak velký. Asi to pro jistotu taky zkusím.
Alphard
Profil
Tori:
Přesně uvedené chování asi moc zjednodušit nejde, nebo mě žádné přímé zkrácení nenapadá.
Podobného výsledku jde dosáhnout typickým replace [b]neco[/b] na <b>neco</b>, jak sama píšeš. Co zbyde se buď nechá jako BB, nebo smaže. Délku kódu bych neřešil, bude schovaný v nějaké knihovně, rychlejší je lepší.

function replaceNestedTags($text, $openTag, $closeTag, $tagName)    
{
  $c = 0;
  do
  {
    $text = preg_replace(
      "~$openTag[search](.*)$closeTag[search]~U", 
      "$openTag[replace]\\2$closeTag[replace]", // tady \\2 bohužel nebude fungovat obecně
      $text,
      -1,
      $c
    );
  }
  while ($c > 0);
  $text = preg_replace("~$openTag[search]|$closeTag[search]~", '', $text);
  return $text;
}
(moc jsem to netestoval, ale snad by to šlo)

Stejně je ale problém s křížením tagů, nevyrovná se s ním ani tato diskuse Hřiště.
Ale zvládá to třeba Texy! http://texy.info/cs/try/jgkpq.
Majkl578
Profil
Pokud by šlo o to jak to napsat pěkněji, tak by to šlo třeba takto (rekurzí):
function replaceNestedTags($text, $openTag, $closeTag)
{
    return preg_replace_callback('~' . str_replace('~', '\~', $openTag[0]) . '(.+)' . str_replace('~', '\~', $closeTag[0]) . '~is',
        function (array $m) use ($openTag, $closeTag) {
            return sprintf($openTag[1], $m[1]) . replaceNestedTags($m[2], $openTag, $closeTag) . $closeTag[1];
        },
        $text
    );
}

$text = <<<EOS
[quote]
    [quote=Tori]
        Lorem ipsum.
    [/quote]
    Dolor sit amet.
[/quote]
blablabla
EOS;

replaceNestedTags(
    $text,
    ['\[quote(?:=([^\]]+))?\]', '<div class="quote"><b>Citace %s</b><br>'],
    ['\[/quote\]', '</div>']
);
Rychlostně je to o trochu lepší než tvoje verze. Alphardova verze je rychlejší o dost, bohužel mi ale nefunguje (místo HTML je nic).

Má ale smysl řešit rychlost na úkor přehlednosti? Stejně je žádoucí, aby se zkompilovaná verze kešovala.
Alphard
Profil
Majkl578:
bohužel mi ale nefunguje (místo HTML je nic)
Píšeš to, jako by to byl detailní problém :-)
Já to trochu testoval a šlo to, možná nějaká změna v nové verzi PHP? Jen jsem tam nedal dost parametrů na víceřádkový vstup.

function replaceNestedTags($text, $openTag, $closeTag)    {

    $c = 0;
    do
    {
        $text = preg_replace(
            "~$openTag[search](.*)$closeTag[search]~siU", 
            "$openTag[replace]\\2$closeTag[replace]", 
            $text,
            -1,
            $c
        );
    }
    while ($c > 0);
    $text = preg_replace("~$openTag[search]|$closeTag[search]~", '', $text);
    return $text;
}

$text = <<<EOS
[quote]
    [quote=Tori]
        Lorem ipsum.
    [/quote]
    Dolor sit amet.
[/quote]
blablabla
EOS;
$openTag = array('search'=>'\[quote(?:=([^\]]+))?\]', 'replace'=>'<div class="quote"><b>Citace $1</b><br>');
$closeTag = array('search'=>'\[/quote\]', 'replace'=>'</div>');
$text = replaceNestedTags($text, $openTag, $closeTag, 'quote');

<div class="quote"><b>Citace </b><br>
    <div class="quote"><b>Citace Tori</b><br>
        Lorem ipsum.
    </div>
    Dolor sit amet.
</div>
blablabla

Nemám teda escapované ~, ale to už by se doladilo.
Majkl578
Profil
[#6] Alphard: No jasně, modifikátor s to vyřešil, nenapadlo mě kouknout se na to rovnou.
Znovu jsem to porovnal tímto scriptem:

$m = microtime(TRUE);
for($i = 0; $i < 10000; $i++) {
    $openTag = ['\[quote(?:=([^\]]+))?\]', '<div class="quote"><b>Citace %s</b><br>'];
    $closeTag = array(['\[/quote\]', '</div>']);
    replaceNestedTagsM($text, $openTag, $closeTag);
}
echo microtime(TRUE) - $m;
echo "\n";

$a = microtime(TRUE);
for($i = 0; $i < 10000; $i++) {
    $openTag = ['search'=>'\[quote(?:=([^\]]+))?\]', 'replace'=>'<div class="quote"><b>Citace $1</b><br>'];
    $closeTag = ['search'=>'\[/quote\]', 'replace'=>'</div>'];
    replaceNestedTagsA($text, $openTag, $closeTag, 'quote');
}
echo microtime(TRUE) - $a;
echo "\n";

$t = microtime(TRUE);
for($i = 0; $i < 10000; $i++) {
    $openTag = ['search'=>'\[quote(?:=([^\]]+))?\]', 'replace'=>'<div class="quote"><b>Citace $1</b><br>'];
    $closeTag = ['search'=>'\[/quote\]', 'replace'=>'</div>'];
    replaceNestedTagsT($text, $openTag, $closeTag, 'quote');
}
echo microtime(TRUE) - $t;
Hodnota $text byla vždycky stejná, totožná s tím v [#5].
A vychází mi:
replaceNestedTagsM 0.5869s (moje verze)
replaceNestedTagsA 0.9700s (Alphardova verze)
replaceNestedTagsT 1.8297s (původní verze z [#1])

Jo a nenechte se zmást zápisem pole jako [...], to je novinka v PHP 5.4.
Alphard
Profil
To by mě zajímalo, jak jsi to naměřil. Mně to na PHP 5.3.6 vyšlo jinak.

Nejdřív jsem otestoval funkčnost všech řešení:
// zvýrazňovač to blbě formátuje, tak to sem dávat nebudu, všude je to pro daný text ok

a pak jsem zvýšil iterace na 1e5 (ať neřešíme desetiny) a otestoval čas:
>php bbCode.php 
Majkl: 3.2695510387421
Alphard: 2.4737310409546
Tori: 3.9173190593719
>Exit code: 0    Time: 12.245
>php bbCode.php 
Majkl: 3.2842628955841
Alphard: 2.6889259815216
Tori: 4.1712620258331
>Exit code: 0    Time: 12.757
>php bbCode.php 
Majkl: 3.3238320350647
Alphard: 2.6214458942413
Tori: 3.9816880226135
>Exit code: 0    Time: 12.438

Kompletní zdroják. Prakticky je jedno, která verze se použije, spíš by mě zajímalo, proč jsi to naměřil jinak, že by PHP 5.4?
Majkl578
Profil
Aha, mně se tam totiž vloudila jedna chybička, která to zkreslila a které jsem si nevšiml (copy&paste je zlo):
$closeTag = array(['\[/quote\]', '</div>']); má být $closeTag = ['\[/quote\]', '</div>'];
Tak to pardon.

Jinak ještě výsledky tvého scriptu z Pastebinu pro 1e5 iterací:
$ php /www/temp.php 
Majkl: 16.467011928558
Alphard: 11.874949932098
Tori: 17.485752820969

$ php -v
PHP 5.4.0beta1-dev (cli) (built: Aug  3 2011 21:53:58) (DEBUG)
Tori
Profil
Alphard, Majkl578:
No páni, děkuju moc za pomoc i inspiraci :)
Na tom fóru použiju nakonec Alphardovu verzi (kvůli rychlosti + PHP 5.2 na serveru).

Vaše odpověď

Mohlo by se hodit

Odkud se sem odkazuje


Prosím používejte diakritiku a interpunkci.

Ochrana proti spamu. Napište prosím číslo dvě-sta čtyřicet-sedm:

0