php.de

Zurück   php.de > Webentwicklung > Software-Design

Software-Design Diskussionen auf Profi-Niveau: PHP Lösungen auf konzeptioneller Ebene

Antwort
 
LinkBack Themen-Optionen Thema bewerten
Alt 15.01.2010, 18:01  
Erfahrener Benutzer
 
Registriert seit: 05.09.2004
Beiträge: 106
PHP-Kenntnisse:
Fortgeschritten
MaMo-Net
Standard Stackbasierter BB-Code-Parser/Compiler

Hi,

ich arbeite gerade an einer Überarbeitung für meine Forensoftware und damit auch am BB-Code-Parser. Der bisherige Parser war im Grunde Suchen/Ersetzen (RegExp, tw. mit Callback) basiert, führte jedoch häufiger zu diversen Problemen, daher hab ich mir überlegt den neuen Parser/Compiler auf Stack-Basis zu schreiben und habe dafür Ideen gesammelt.

Zuerst habe ich mir überlegt, was soll der Parser können und wie sollen die Tags aussehen.
Folgende Spezifikation habe ich für die Syntax der BB-Code-Tags:
- Die Tags sehen so aus: [ element][ /element]
- Die Tags können bis zu 3 Attribute vereinen, wobei das erste Attribut den Namen des Tags hat, also z.B. [ element=x attr=y weiteresattr=z][ /element]
- Element und Attributschlüssel dürfen nur aus Buchstaben bestehen
- Attributwerte können mit ' und " eingeschlossen werden (z.B. um innerhalb des Werts [ und ] nutzen zu können. Beispiel: [ url="http://www.x.de/?a[]=b"]Test[ /url]
- "Standalone"-Tags sollen ebenfalls möglich sein, z.B. [hr]

Folgende Funktionalität soll erreicht werden:
- Korrekte Umwandlung
- Unterscheidung in Inline- und Block-Tags (ähnlich display-Eigenschaft aus CSS)
- Inlinelemente sollen korrigiert werden, sobald ein Block-Element folgt, z.B. [ b]as[i]df [ hr] wird zu [ b]ad[ i]df [ /i][ /b][ hr].
- Entfernen von einzelnen Block-Elementen (z.B. es gibt ein übriges [/quote] o.ä.
- Die BB-Codes sind in Klassen modelliert, mehrere Codes können sich eine Klasse teilen (z.B. b, i und u-Tags)
- Schachtelung soll möglich sein (hängt ab von den Klassenimplementierungen ab), Beispiel [ quote]x[ quote]y[ /quote]x[ /quote]
- Filter sollen später möglich sein (z.B. für Zensur und Smileys)
- ... irgendwas vergessen?

Nun habe ich angefangen zu implementieren, die Struktur ähnelt einem Compiler (Scanner, Parser, Compiler).

Ich habe bereits folgendes erfolgreich implementiert:
BBCodeCompiler.php - Hauptklasse die in den Programmen genutzt würde, der Scanner funktioniert schon ziemlich gut, leider sind Parser und Compiler absolut nicht so, wie ich mir das vorstelle, die
PHP-Code:
<?php
class BBCodeCompiler {

    private 
$tags;
    private 
$prependFilter;
    private 
$appendFilter;

    public function  
__construct() {
        
$this->tags = array();
        
$this->prependFilter = array();
        
$this->appendFilter = array();
    }

    public function 
appendFilter(BBCodeFilter $filter) {
        
$this->appendFilter[$tag] = $filter;
    }

    public function 
prependFilter(BBCodeFilter $filter) {
        
$this->prependFilter[$tag] = $filter;
    }

    public function 
registerTag(BBCodeTag $object) {
        foreach (
$object->getTagNames() as $tag) {
            
$this->tags[$tag] = $object;
        }
    }

    public function 
convert($text) {
        
// $text = $this->executePrependFilter($text); // Noch nicht implementiert
        
$tokens $this->executeScanner($text);
        
$tree $this->executeParser($tokens);
        
$text $this->compile($tree);
        
// $text = $this->executeAppendFilter($text); // Noch nicht implementiert
        
return $text;
    }

    public function 
compile(array $tree) {
        return 
$this->executeCompiler($tree);
    }

    private function 
newToken($text$tagName$opening false, array $attributes = array()) {
        return array(
            
'text' => $text,
            
'name' => $tagName,
            
'opening' => $opening,
            
'attributes' => $attributes
        
);
    }

    
/**
     * Tokenize the text into peaces containing the tags (opening or closing) and the texts.
     *
     * @param string Text to tokenize
     * @return array Array containing the tokens
     */
    
private function executeScanner($text) {
        if (
strpos($text'[') === false) {
            
// If no [ char then there is no bb code,. shorten the procedure / save cpu
            
return array($text);
        }
        else {
            
// Build regexp
            
$attributeValue '(\'.+?(?<!(?<!\\\\)\\\\)\'|".+?(?<!(?<!\\\\)\\\\)"|[^\]\s]+)';
            
$attribute '(?:\s+([a-z]+)='.$attributeValue.')?';
            
$regexp '~\[(?:([a-z]+)(?:='.$attributeValue.')?'.$attribute.$attribute.'|/[a-z]+)\]~i';
            
// Parse the tags
            
preg_match_all($regexp$text$matchesPREG_SET_ORDER|PREG_OFFSET_CAPTURE);

            
// Iterate through tags and add missing texts
            
$tokens = array();
            
$lastOffset 0;
            foreach (
$matches as $match) {
                
// Calculate the length of the missing text (difference of the both tag offsets)
                
$length $match[0][1] - $lastOffset;
                
// Add missing text when there is something missing
                
if ($length 0) {
                    
$tokens[] = substr($text$lastOffset$length);
                }

                
// Add token with additional information from the scanning process
                // We decide what type of token it is on the number of array elements / matches
                
$type count($match);
                if (
$type == 1) { // Its a closing tag
                    // Create new token, remove the [/ and ] for the tag name
                    
$tokens[] = $this->newToken($match[0][0], substr($match[0][0], 2, -1));
                }
                else {
                    
// Add new opening tag with attributes
                    
$attributes = array();
                    switch (
$type) {
                        case 
7:
                            
$attributes[$match[5][0]] = $match[6][0];
                        case 
5:
                            
$attributes[$match[3][0]] = $match[4][0];
                        case 
3:
                            
$attributes[$match[1][0]] = $match[2][0];
                    }
                    
$tokens[] = $this->newToken($match[0][0], $match[1][0], true$attributes);
                }

                
// Change the lastOffset to the current offset plus the length of the tag
                
$lastOffset $match[0][1] + strlen($match[0][0]);
            }
            
// Append the rest after the last tag
            
$tokens[] = substr($text$lastOffset);

            return 
$tokens;
        }
    }

    private function 
executeParser(array $tokens) {
        
// If it's plain text return it
        
if (count($tokens) == 1) {
            return 
$tokens;
        }

        
$inlineStack = array();
        
$blockStack = array();
        
$treeIndex 0;
        
$tree = array();
        foreach (
$tokens as $token) {
            if (
is_array($token) == true) { // It's an array/tag
                
if (isset($this->tags[$token['name']]) == false) {
                    
// No class for this tag, handle it as normal text
                    
$token $token['text'];
                }
                elseif (
$this->tags[$token['name']]->isStandalone() == false) {
                    
// Tag is not standalone, go through them, save standalone tags without change
                    
$tag $this->tags[$token['name']];
                    
// Clean up on block tags, close mission tags etc.
                    
if ($tag->getDisplayType() == BBCodeTag::DT_BLOCK) {
                        
// Revere array for easier iteration
                        
$inlineStack array_reverse($inlineStack);
                        
// Close all unclosed tags before the closing block element
                        
foreach ($inlineStack as $element) {
                            
$tree[++$treeIndex] = $this->newToken("[/{$element}]"$element);
                        }
                        
// Reset stack as all tags are closed now
                        
$inlineStack = array();

                        
// Handle tags, add to stack, remove from stack etc.
                        
if ($token['opening'] == true) {
                            
// Add opening tag to stack
                            
$blockStack[$treeIndex+1] = $token['name'];
                        }
                        else {
                            
// Check strict whether there is a opening tag for the closing tag
                            
$last end($blockStack); // Move array to last element
                            
if ($last == $token['name']) {
                                
// Opening tag found, remove it from the stack
                                
array_pop($blockStack);
                            }
                            else {
                                
// No opening tag found, remove the closing tag
                                
continue;
                            }
                        }
                    }
                    else {
                        
// Handle tags, add to stack, remove from stack etc.
                        
if ($token['opening'] == true) {
                            
// Add opening tag to stack
                            
$inlineStack[] = $token['name'];
                        }
                        else {
                            
// Check whether there is a opening tag for the closing tag
                            // Order can be swapped for inline tage
                            // Example: [b][i][/i][/b][i][/i] instead of correct [b][i][/i][/b]
                            
$key array_search($token['name'], array_reverse($inlineStacktrue));
                            if (
$key === false) {
                                
// No opening tag found, remove the closing tag
                                
continue;
                            }
                            else {
                                
// Opening tag found, remove it from the stack
                                
unset($inlineStack[$key]);
                            }
                        }
                    }
                }
            }

            
// Add token to tree
            
$tree[++$treeIndex] = $token;
        }
        
// Close all open tag at the end of the text in order of their occurance.
        
$inlineStack array_reverse($inlineStack);
        foreach (
$inlineStack as $element) {
            
$tree[++$treeIndex] = $this->newToken("[/{$element}]"$element);
        }
        
// Remove open but unclosed block elements from tree
        
foreach ($blockStack as $key => $element) {
            unset(
$tree[$key]);
        }
        return 
$tree;
    }

    private function 
executeCompiler(array $tree) {
        
$text '';
        
$collect = array();
        
$level 0;
        foreach (
$tree as $element) {
            if (
is_array($element) == true) {
                if (
$element['opening'] == true) {
                    if (
$this->tags[$element['name']]->isStandalone() == false) {
                        
$level++;
                        
$collect[] = $element;
                    }
                    else {
                        
$text .= $this->tags[$element['name']]->getOpeningReplacement($element);
                    }
                }
                else {
                    
$level--;
                    if (
$level == 0) {
                        
$first array_shift($collect);
                        
$text .= $this->tags[$first['name']]->getOpeningReplacement($first);
                        
$text .= $this->executeCompiler($collect);
                        
$text .= $this->tags[$element['name']]->getClosingReplacement($element);
                        
$collect = array();
                    }
                }
            }
            else {
                if (
$level == 0) {
                    
$text .= $element;
                }
                else {
                    
$collect[] = $element;
                }
            }
        }
        return 
$text;
    }

}
?>
interface.BBCodeTag.php - Wird von den BB-Code-Klassen (i, hr, img, etc.) implementiert.
PHP-Code:
<?php
interface BBCodeTag {

    const 
DT_INLINE 0;
    const 
DT_BLOCK 1;

// Gibt true zurück, wenn der Tag standalone ist
    
public function isStandalone();

// 
    
public function getOpeningReplacement($tree);

    public function 
getClosingReplacement($tree);
    
    public function 
getTagNames();

    public function 
getDisplayType();

}
?>
class.BasicBBCodeTag.php - Implementiert beispielhaft den i, b und u-Tag
PHP-Code:
<?php
class BasicBBCodeTag implements BBCodeTag {

    public function 
isStandalone() {
        return 
false;
    }

    public function 
getTagNames() {
        return array(
'b''i''u');
    }

    public function 
getDisplayType() {
        return 
BBCodeTag::DT_INLINE;
    }

    public function 
getOpeningReplacement($tree) {
        return 
"<{$tree['name']}>";
    }

    public function 
getClosingReplacement($tree) {
        return 
"</{$tree['name']}>";
    }

}
?>
Nutzung des BBCodeCompiler, wobei die Registrierung der Tags wohl später über eine Datenbank laufen würde...
PHP-Code:
<?php
$bbcode 
= new BBCodeCompiler();
$bbcode->registerTag(new BasicBBCodeTag());
echo 
$bbcode->convert('This is a [i]sample[/i] Text! :)');
?>
Hat jemand Ideen oder Ansätze, wie man so einen Parser schreiben könnte? Oder wie man das compilieren flexibler gestalten kann?
Jegliche Ideen etc. sind willkommen, bei Fragen fragt.

Vielen Dank für etwaige Beiträge...

Gruß
MaMo

Ich habe mir bereits andere Parser angesehen, z.B. diesen http://www.phphulp.nl/php/scripts/7/567/ , aber die waren mir entweder zu rudimentär oder gingen grob an meiner Anforderung vorbei.

Geändert von MaMo-Net (15.01.2010 um 18:04 Uhr).
MaMo-Net ist offline   Mit Zitat antworten
Sponsor Mitteilung
PHP Code Flüsterer

Registriert seit: 21.08.2005
Beiträge: 4682
PHP-Kenntnisse:
Fortgeschritten

Alt 15.01.2010, 18:48  
Erfahrener Benutzer
 
Benutzerbild von mermshaus
 
Registriert seit: 14.06.2009
Beiträge: 1.578
PHP-Kenntnisse:
Fortgeschritten
mermshaus ist ein wunderbarer Anblickmermshaus ist ein wunderbarer Anblickmermshaus ist ein wunderbarer Anblickmermshaus ist ein wunderbarer Anblickmermshaus ist ein wunderbarer Anblickmermshaus ist ein wunderbarer Anblickmermshaus ist ein wunderbarer Anblick
Standard

Ich finde die "Einschränkungen" (drei Attribute pro Tag) unnötig.

Sieht doch sonst auf den ersten Blick ganz gut aus. Was gefällt dir denn daran nicht? Dein Eingangspost ist arg meta. Versuch mal, konkreter zu werden. Diese Art Parser gibt es gefühlt wie Sand am Meer und jeder hat sicher schon mal irgendwann sowas geschrieben. Deshalb dürfte die Lust nicht besonders groß sein, sich jetzt gerade in deinen reinzudenken.
mermshaus ist gerade online   Mit Zitat antworten
Alt 15.01.2010, 18:55  
moderatives Dielektrikum
 
Benutzerbild von nikosch
 
Registriert seit: 21.05.2008
Beiträge: 34.248
PHP-Kenntnisse:
Fortgeschritten
nikosch kann auf vieles stolz seinnikosch kann auf vieles stolz seinnikosch kann auf vieles stolz seinnikosch kann auf vieles stolz seinnikosch kann auf vieles stolz seinnikosch kann auf vieles stolz seinnikosch kann auf vieles stolz seinnikosch kann auf vieles stolz seinnikosch kann auf vieles stolz seinnikosch kann auf vieles stolz sein
Standard

Ich halte den Versuch der Vervollständigung (Entfernung übriger Tags, Umbau von Inline/Block-Strukturen) für kaum umsetzbar. Ich finde allerdings das gesamte Thema BBCode auch ziemlich ausgelutscht. HTML mit anderen Mitteln aber ohne Mehrwert nachbauen ist irgendwie...
__________________
--
One pixel is still too big. Please make it smaller. ASAP.

Initiative Mittelstand.
Die wichtigste Gestaltungsregel im Screendesign ist Pi mal Daumen des Arbeitgebers.
--
nikosch ist offline   Mit Zitat antworten
Alt 15.01.2010, 19:10  
Moderator
 
Benutzerbild von robo47
 
Registriert seit: 03.09.2004
Beiträge: 11.798
PHP-Kenntnisse:
Fortgeschritten
robo47 ist ein wunderbarer Anblickrobo47 ist ein wunderbarer Anblickrobo47 ist ein wunderbarer Anblickrobo47 ist ein wunderbarer Anblickrobo47 ist ein wunderbarer Anblickrobo47 ist ein wunderbarer Anblickrobo47 ist ein wunderbarer Anblickrobo47 ist ein wunderbarer Anblick
Standard

Zitat:
Zitat von nikosch Beitrag anzeigen
HTML mit anderen Mitteln aber ohne Mehrwert nachbauen ist irgendwie...
Seh ich ähnlich, vor allem muss man sich wenn man etwas komfort will auch den WYSIWYG-Editor dazu wieder selbst erfinden oder einen umschreiben, was im endeffekt entweder AJAX oder eine zusätzliche Javascript-Implementierung des Parsers vorraussetzt.

Wenn man sicher html filtern will sollte man sich mal htmlpurifier anschauen.


Ansonsten verweise ich mal hierauf:
Was genau bringt mir der Einsatz von ANTLR? - Developer's Guide

hab damit zwar noch nciht gearbeitet aber rein von der theorie her sollte das wohl in die richtung gehen.


Mit zf 1.10 kommt die Komponente Zend_Markup ( Zend_Markup - Pieter Kokx - Zend Framework Wiki )die Parser + Renderer unterstützt um aus einem Format ein anderes zu erstellen, in der beta ist der code schon drin, manual gibt es bis 1.10 raus ist wohl nur hier: 45.2.Â*Getting Started With Zend_Markup
robo47 ist offline   Mit Zitat antworten
Alt 15.01.2010, 19:20  
moderatives Dielektrikum
 
Benutzerbild von nikosch
 
Registriert seit: 21.05.2008
Beiträge: 34.248
PHP-Kenntnisse:
Fortgeschritten
nikosch kann auf vieles stolz seinnikosch kann auf vieles stolz seinnikosch kann auf vieles stolz seinnikosch kann auf vieles stolz seinnikosch kann auf vieles stolz seinnikosch kann auf vieles stolz seinnikosch kann auf vieles stolz seinnikosch kann auf vieles stolz seinnikosch kann auf vieles stolz seinnikosch kann auf vieles stolz sein
Standard

[OT] Oh, den Ben gibts auch noch? Der war doch auch mal bei den PHP-Superfreunden..
__________________
--
One pixel is still too big. Please make it smaller. ASAP.

Initiative Mittelstand.
Die wichtigste Gestaltungsregel im Screendesign ist Pi mal Daumen des Arbeitgebers.
--
nikosch ist offline   Mit Zitat antworten
Antwort


Themen-Optionen
Thema bewerten
Thema bewerten:

Forumregeln
Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are an
Gehe zu

Ähnliche Themen
Thema Autor Forum Antworten Letzter Beitrag
Probleme mit PHP Code HappyDieMuschel PHP Tipps 2008 7 28.05.2008 06:34
[Erledigt] PHP Code aus Datenbank ayti PHP Tipps 2008 8 26.05.2008 19:40
Design und Code Trennen TeazY PHP Tipps 2008 29 21.05.2008 12:08
Nur bestimmten Html Code zulassen? litterauspirna PHP Tipps 2008 5 29.04.2008 12:30
einfacher Code zum Einbinden von "Inhaltsbereichen" MaxDittmann PHP-Fortgeschrittene 6 01.10.2007 15:54
PHP: Wie parst der Server den Code am schnellsten? SvenLittkowski PHP Tipps 2007 4 17.12.2005 10:58
bb code in htm code wandeln janni PHP Tipps 2007 2 04.11.2005 22:36
JS Code in PHP Code?? HTML, Usability und Barrierefreiheit 12 08.08.2005 15:45
Seite 1/2/3..Code was haltet ihr davon? Matthiasnet PHP Tipps 2005-2 4 29.07.2005 20:29
[Erledigt] Lesbarkeit von Code Off-Topic Diskussionen 6 14.07.2005 14:48
code aus db mit eval replacen chief-thomson PHP Tipps 2005-2 4 08.07.2005 15:33
Benutzereingaben von Formular prüfen (Sicherer Code?) PHP Tipps 2005 6 27.01.2005 10:16
code verstecken Skazi PHP Tipps 2005 4 19.01.2005 13:51
[Erledigt] bb code innerhalb von bb code geht nicht PHP Tipps 2007 2 12.01.2005 17:29
Problem mit COde PHP Tipps 2004 5 28.08.2004 16:04

Besucher kamen über folgende Suchanfragen bei Google auf diese Seite
bbc parser php, code parsen, php bbcode parser, php bbcode klasse, php compiler class, php compiler, bbcode-tag vervollständigen, parsen php codeschnipsel, convert bbcode php, bbcode parser stack, compiler stack einbinden, preg_match compiler nachbau, bbc parser klasse php, ubb code php class, bbcode php compiler, parser-compiler php javascript, program php bb tags, bcc2 compiler, javascript regex compile bbcode, \no opening tag found\

Alle Zeitangaben in WEZ +1. Es ist jetzt 17:26 Uhr.




Powered by vBulletin® Version 3.7.2 (Deutsch)
Copyright ©2000 - 2012, Jelsoft Enterprises Ltd.
Search Engine Optimization by vBSEO 3.2.0
Aprilia-Forum, Aquaristik-Forum, Liebeskummer-Forum, Zierfisch-Forum, Geizkragen-Forum