Reacties op het script BBCode parser, v. 0.1
|
Gepost op: 19 februari 2009 - 11:42 |
|
|
|
Lid
|
Wat komt dit goed uit .
Ben bezig met mijn website te vernieuwen, kan ik mooi deze gebruiken . (Die Q-bb is niet helemaal volmaakt, die gebruik ik nu )
list en quote sluit goed af (heb je bij vele parsers niet)
Klein vraagje. Komt er nog een smilie functie in? Zo nee, dan bouw ik hem zelf wel erin. Heb het idee van wel want je gebruikt nogal wat smilie tags
Kan zo 123 geen bugs vinden. Kleine idee misschien.
Goede reseizer erin verwerken voor plaatjes? |
|
|
|
Gepost op: 19 februari 2009 - 12:51 |
|
|
|
Crew algemeen
|
Resizer doe je client-side, niet server-side. Kost veel te veel load. ;)
Smilies kun je heel makkelijk verwerken: in de TextNode kun je bij getContents (net als ik al heb gedaan voor autolinking) de smilies met bijvoorbeeld str_replace vervangen. :-) |
|
|
|
Gepost op: 19 februari 2009 - 13:20 |
|
|
|
Lid
|
JeXuS schreef: Resizer doe je client-side, niet server-side. Kost veel te veel load. ;)
Smilies kun je heel makkelijk verwerken: in de TextNode kun je bij getContents (net als ik al heb gedaan voor autolinking) de smilies met bijvoorbeeld str_replace vervangen. :-)
Thnx het werkt |
|
|
|
Gepost op: 19 februari 2009 - 17:40 |
|
|
|
PHP expert
|
|
|
|
Gepost op: 07 mei 2009 - 22:50 |
|
|
|
PHP expert
|
Prachtig script, werkelijk waar geen woord te veel in de omschrijving.
Makkelijk aan te passen, uit te breiden, etc. Voor de rest sluit ik mij aan bij Meulenhof wat betreft het afsluiten van alle tags. Tevens is het een knap staaltje werk wat betreft het quote systeem en de [table]-tags.
|
|
|
|
Gepost op: 07 juni 2009 - 22:40 |
|
|
|
Nieuw lid
|
ik ben vandaag tegen een probleem aan gelopen door die aanpassing toen ik bezig was om te zorgen dat binnen een tag met TagRule::LITERAL het er nog steeds stack based aan toe gaat.
hij sloot de code tag bij de eerste eind tag al af, ook al stond er nog een openings tag voor...
ook wanneer ik een tekst laat parsen zonder tags maar wel met enters er in, dan zitten de output geen enters.
dus heb ik maar de str_replace van WhitespaceNode naar TextNode verplaatst.
hier mijn huidige Parser, WhitespaceNode en TextNode class, waardoor je dus dus wel code tags binnen code tags kunt zetten:
08-06-2009 Edit:
nog even een fix, regel 146 is nieuw.
<?php
/**
* Simpele parser gebaseerd op bijbehorende klassen
*
* <code><?php
*$parser = new Parser;
*$parser->setLexer(new Lexer_Split)
* ->addRule(
* // doe je ding hier
* );</code>
* @package Bbcode
* @subpackage Parser
*
* @todo Optimalisatie! Vooral het cachen van tagnamen zou al schelen :-)
*/
class Parser {
/**
* De gebruikte {@link Lexer}
*
* @var Lexer
*/
private $_lexer = null;
/**
* De node waarin we nu bewerkingen doen
*
* @var Node_Point
*/
private $_currentNode = null;
/**
* De rootnode van deze tekst
*
* @var Node_Root
*/
private $_rootNode = null;
/**
* Een lijst van rules die we tot onze beschikking hebben
*
* @var array Array van {@link Rule_Tag}s.
*/
private $_tagRules = array();
/**
* Lijst van geopende tags
*
* Let op: dit is meer optimalisatie, uiteindelijk zou alles prima
* via {@link Node::getParent()} te doen zijn, maar dit scheelt
* ontzettend veel tijd
*
* @var array Array van namen
*/
private $_openedTags = array();
/**
* Voeg een rule toe, waar mee kan worden geparsed
*
* @param Rule_Tag $rule
* @return Parser
*/
public function addRule(Rule_Tag $rule) {
$this->_tagRules[$rule->getName()] = $rule;
return $this;
}
/**
* Stel de lexer in die we gaan gebruiken
*
* @param Lexer $lexer
* @return Parser
*/
public function setLexer(Lexer $lexer) {
$this->_lexer = $lexer;
return $this;
}
/**
* De kern van de parser.
*
* Men neme een stuk tekst en insert het, en krijgt een root node met
* compleet geparsede tree terug die door middel van de __toString methode
* direct geoutput kan worden.
*
* @param string $text
* @return Node_Root
*/
public function parse($text) {
$this->_rootNode = new RootNode();
$this->_currentNode = $this->_rootNode;
$this->_lexer->lex($text, array_keys($this->_tagRules));
while(false !== $token = $this->_lexer->getToken()) {
if($token instanceof BeginTagToken) {
$tagRule = $this->_tagRules[$token->getTagName()];
$oldNode = $this->_currentNode;
while(! $tagRule->isPermissableIn($this->_currentNode)) {
if($this->_currentNode instanceof RootNode) {
// we zijn bovenaan, tag is echt NIET te matchen
$this->_currentNode->addChildNode(new TextNode($token->getContent()));
$this->_rootNode = $oldNode;
continue 2;
}
$this->_currentNode = $this->_currentNode->getParent();
}
if($tagRule->getTrimWhitespace() & TagRule::TRIM_BEFORE) {
$oldNode->removeFinalWhitespace();
}
$newNode = new TagNode($tagRule, $token->getTagName(), $token->getArguments());
$newNode->setParent($this->_currentNode);
$this->_currentNode->addChildNode($newNode);
$this->_setTagOpen($token->getTagName());
if($tagRule->getParseType() == TagRule::PARSE) {
$this->_currentNode = $newNode;
} else {
$nodeContent = '';
$finished = false;
while(false !== $newToken = $this->_lexer->getToken()) {
if($newToken instanceof TagToken && $newToken->getTagName() == $token->getTagName()) {
$this->_setTagOpen($newToken->getTagName(), $newToken instanceof BeginTagToken);
if($newToken instanceof EndTagToken && ! isset($this->_openedTags[$token->getTagName()])) {
$finished = true;
$this->_setTagOpen($newToken->getTagName(), false);
$newNode->addChildNode(new LiteralTextNode($nodeContent));
if($this->_tagRules[$token->getTagName()]->getTrimWhitespace() & TagRule::TRIM_AFTER) {
// probeer whitespace weg te halen bij volgende input
if(false !== ($peek = $this->_lexer->peekToken())) {
if($peek instanceof WhitespaceToken) {
$this->_lexer->peekToken();
}
}
}
break;
}
}
$nodeContent .= $newToken->getContent();
}
if(!$finished) {
$this->_setTagOpen($token->getTagName(), false);
$newNode->addChildNode(new LiteralTextNode($nodeContent));
}
unset($finished);
}
} elseif($token instanceof EndTagToken && $this->_currentNode instanceof TagNode) {
if(isset($this->_openedTags[$token->getTagName()])) {
if($this->_tagRules[$token->getTagName()]->getTrimWhitespace() & TagRule::TRIM_AFTER) {
// probeer whitespace weg te halen bij volgende input
if(false !== ($peek = $this->_lexer->peekToken())) {
if($peek instanceof WhitespaceToken) {
$this->_lexer->peekToken();
}
}
}
// check of de eindtag dezelfde naam heeft als de huidige opentag
while($this->_currentNode instanceof TagNode && $token->getTagName() != $this->_currentNode->getTagName()) {
$this->_setTagOpen($token->getTagName(), false);
$this->_currentNode = $this->_currentNode->getParent();
}
if(! $this->_currentNode instanceof RootNode) {
$this->_currentNode = $this->_currentNode->getParent();
}
} else {
$this->_currentNode->addChildNode(new TextNode($token->getContent()));
}
} elseif($token instanceof WhitespaceToken) {
// simpele whitespace
$this->_currentNode->addChildNode(new WhitespaceNode($token->getContent()));
} else {
// alleen nog tekst
$this->_currentNode->addChildNode(new TextNode($token->getContent()));
}
}
return $this->_rootNode;
}
/**
* Stel in of een tag geopend of gesloten is
*
* @param string $tagName
* @param boolean $add true bij nieuw, false bij gesloten
*/
protected function _setTagOpen($tagName, $add = true) {
if($add) {
if(! isset($this->_openedTags[$tagName])) {
$this->_openedTags[$tagName] = 1;
} else {
++$this->_openedTags[$tagName];
}
} else if(isset($this->_openedTags[$tagName])) {
--$this->_openedTags[$tagName];
if(! $this->_openedTags[$tagName]) {
unset($this->_openedTags[$tagName]);
}
}
}
}
/**
* Node die whitespace representeert
*
* @package Bbcode
* @subpackage Node
*/
class WhitespaceNode extends TextNode {}
/**
* Tekstnode
*
* Representatie van een gedeelte met alleen tekst
*
* @author Richard van Velzen
* @package Bbcode
* @subpackage Node
*/
class TextNode extends Node {
/**
* De inhoud van deze node
*
* @var string
*/
protected $_contents = '';
/**
* Stel de contents van deze node in
*
* @param string $contents
*/
public function __construct($contents) {
$this->_contents = $contents;
parent::__construct();
}
/**
* Haal de content van deze node op
*
* @return string
*/
public function getContents() {
$output = htmlspecialchars($this->_contents);
// automatisch linken naar urls
$output = preg_replace_callback(
'{(?<=\b)((?:https?|ftp)://|www\.)[\w.]+[;#&/~=\w+()?.,:%-]*[;#&/~=\w+(-]}i',
array($this, '_linkReplacement'),
$output
);
return str_replace("\n", '<br />', $output);
}
/**
* Haal de stringrepresenatie van deze node op
*
* @return string
*/
public function __toString() {
return $this->getContents();
}
/**
* Link replacement voor {@link TextNode::getContents()}
*
* @param array $match Input vanuit preg_match_callback
* @return string
*/
protected function _linkReplacement(array $match) {
$link = $match[0];
if($match[1] == 'www.') {
$link = 'http://' . $match[0];
}
return '<a href="' . $link . '">' . $match[0] . '</a>';
}
}
<?php /** * Simpele parser gebaseerd op bijbehorende klassen * * <code><?php *$parser = new Parser; *$parser->setLexer(new Lexer_Split) * ->addRule( * // doe je ding hier * );</code> * @package Bbcode * @subpackage Parser * * @todo Optimalisatie! Vooral het cachen van tagnamen zou al schelen :-) */ class Parser { /** * De gebruikte {@link Lexer} * * @var Lexer */ private $_lexer = null; /** * De node waarin we nu bewerkingen doen * * @var Node_Point */ private $_currentNode = null; /** * De rootnode van deze tekst * * @var Node_Root */ private $_rootNode = null; /** * Een lijst van rules die we tot onze beschikking hebben * * @var array Array van {@link Rule_Tag}s. */ private $_tagRules = array(); /** * Lijst van geopende tags * * Let op: dit is meer optimalisatie, uiteindelijk zou alles prima * via {@link Node::getParent()} te doen zijn, maar dit scheelt * ontzettend veel tijd * * @var array Array van namen */ private $_openedTags = array(); /** * Voeg een rule toe, waar mee kan worden geparsed * * @param Rule_Tag $rule * @return Parser */ public function addRule(Rule_Tag $rule) { $this->_tagRules[$rule->getName()] = $rule; return $this; } /** * Stel de lexer in die we gaan gebruiken * * @param Lexer $lexer * @return Parser */ public function setLexer(Lexer $lexer) { $this->_lexer = $lexer; return $this; } /** * De kern van de parser. * * Men neme een stuk tekst en insert het, en krijgt een root node met * compleet geparsede tree terug die door middel van de __toString methode * direct geoutput kan worden. * * @param string $text * @return Node_Root */ public function parse($text) { $this->_rootNode = new RootNode(); $this->_currentNode = $this->_rootNode; $this->_lexer ->lex($text, array_keys($this->_tagRules )); while(false !== $token = $this->_lexer->getToken()) { if($token instanceof BeginTagToken) { $tagRule = $this->_tagRules[$token->getTagName()]; $oldNode = $this->_currentNode; while(! $tagRule->isPermissableIn($this->_currentNode)) { if($this->_currentNode instanceof RootNode) { // we zijn bovenaan, tag is echt NIET te matchen $this->_currentNode->addChildNode(new TextNode($token->getContent())); $this->_rootNode = $oldNode; continue 2; } $this->_currentNode = $this->_currentNode->getParent(); } if($tagRule->getTrimWhitespace() & TagRule::TRIM_BEFORE) { $oldNode->removeFinalWhitespace(); } $newNode = new TagNode($tagRule, $token->getTagName(), $token->getArguments()); $newNode->setParent($this->_currentNode); $this->_currentNode->addChildNode($newNode); $this->_setTagOpen($token->getTagName()); if($tagRule->getParseType() == TagRule::PARSE) { $this->_currentNode = $newNode; } else { $nodeContent = ''; $finished = false; while(false !== $newToken = $this->_lexer->getToken()) { if($newToken instanceof TagToken && $newToken->getTagName() == $token->getTagName()) { $this->_setTagOpen($newToken->getTagName(), $newToken instanceof BeginTagToken); if($newToken instanceof EndTagToken && ! isset($this->_openedTags [$token->getTagName()])) { $finished = true; $this->_setTagOpen($newToken->getTagName(), false); $newNode->addChildNode(new LiteralTextNode($nodeContent)); if($this->_tagRules[$token->getTagName()]->getTrimWhitespace() & TagRule::TRIM_AFTER) { // probeer whitespace weg te halen bij volgende input if(false !== ($peek = $this->_lexer->peekToken())) { if($peek instanceof WhitespaceToken) { $this->_lexer->peekToken(); } } } break; } } $nodeContent .= $newToken->getContent(); } if(!$finished) { $this->_setTagOpen($token->getTagName(), false); $newNode->addChildNode(new LiteralTextNode($nodeContent)); } } } elseif($token instanceof EndTagToken && $this->_currentNode instanceof TagNode) { if(isset($this->_openedTags [$token->getTagName()])) { if($this->_tagRules[$token->getTagName()]->getTrimWhitespace() & TagRule::TRIM_AFTER) { // probeer whitespace weg te halen bij volgende input if(false !== ($peek = $this->_lexer->peekToken())) { if($peek instanceof WhitespaceToken) { $this->_lexer->peekToken(); } } } // check of de eindtag dezelfde naam heeft als de huidige opentag while($this->_currentNode instanceof TagNode && $token->getTagName() != $this->_currentNode->getTagName()) { $this->_setTagOpen($token->getTagName(), false); $this->_currentNode = $this->_currentNode->getParent(); } if(! $this->_currentNode instanceof RootNode) { $this->_currentNode = $this->_currentNode->getParent(); } } else { $this->_currentNode->addChildNode(new TextNode($token->getContent())); } } elseif($token instanceof WhitespaceToken) { // simpele whitespace $this->_currentNode->addChildNode(new WhitespaceNode($token->getContent())); } else { // alleen nog tekst $this->_currentNode->addChildNode(new TextNode($token->getContent())); } } return $this->_rootNode; } /** * Stel in of een tag geopend of gesloten is * * @param string $tagName * @param boolean $add true bij nieuw, false bij gesloten */ protected function _setTagOpen($tagName, $add = true) { if($add) { if(! isset($this->_openedTags [$tagName])) { $this->_openedTags[$tagName] = 1; } else { ++$this->_openedTags[$tagName]; } } else if(isset($this->_openedTags [$tagName])) { --$this->_openedTags[$tagName]; if(! $this->_openedTags[$tagName]) { unset($this->_openedTags [$tagName]); } } } } /** * Node die whitespace representeert * * @package Bbcode * @subpackage Node */ class WhitespaceNode extends TextNode {} /** * Tekstnode * * Representatie van een gedeelte met alleen tekst * * @author Richard van Velzen * @package Bbcode * @subpackage Node */ class TextNode extends Node { /** * De inhoud van deze node * * @var string */ protected $_contents = ''; /** * Stel de contents van deze node in * * @param string $contents */ public function __construct($contents) { $this->_contents = $contents; parent::__construct(); } /** * Haal de content van deze node op * * @return string */ public function getContents() { // automatisch linken naar urls '{(?<=\b)((?:https?|ftp)://|www\.)[\w.]+[;#&/~=\w+()?.,:%-]*[;#&/~=\w+(-]}i', array($this, '_linkReplacement'), $output ); } /** * Haal de stringrepresenatie van deze node op * * @return string */ public function __toString() { return $this->getContents(); } /** * Link replacement voor {@link TextNode::getContents()} * * @param array $match Input vanuit preg_match_callback * @return string */ protected function _linkReplacement (array $match) { $link = $match[0]; if($match[1] == 'www.') { $link = 'http://' . $match[0]; } return '<a href="' . $link . '">' . $match[0] . '</a>'; } }
verder heb ik zelf elke class in een appart bestand gezet die de autoloader automatisch include als dat nodig is.
(ik heb zelf alle namen van de classes aangepast zodat wanneer je de underscores vervangt door slashes je de juiste include pad hebt, dus Token_Tag_Begin extends Token_Tag ipv BeginTagToken extends TagToken)
en ik vond het eigenlijk niet leuk om in elk script waar ik deze bbcode parser wil gebruiken die functies te zetten, of een script te includen met al die functies er in.
ik wilde dit dus ook aan de autoloader overlaten, en dus classes hebben in plaats van functies...
simpel weg de class TagTemplate extenden heb ik niet gedaan aangezien alleen de methode render() echt verplicht is.
dus heb ik maar een abstacte class Tag aangemaakt
hier volgen dus 4 nieuwe classes (Tag, CodeTag, TagLink en ImageTag) en de aangepaste classes TagTemplate (alleen maar extends Tag) en TagRule (TagTemplate vervangen door Tag)
<?php
/**
* Een generieke regel qua wat er moet gebeuren
*
* @author Richard van Velzen
* @package Bbcode
* @subpackage TagRule
*/
class TagRule {
/**
* Geen whitespace trimmen
*/
const TRIM_NONE = 0;
/**
* Whitespace voor de tag trimmen
*/
const TRIM_BEFORE = 1;
/**
* Whitespace na de tag trimmen
*/
const TRIM_AFTER = 2;
/**
* Aan beide kanten trimmen
*/
const TRIM_BOTH = 3;
/**
* Binnen deze tag verder parsen
*/
const PARSE = 3;
/**
* Niet parsen binnen deze tag
*/
const LITERAL = 2;
/**
* Naam van de tag
*
* @var string
*/
private $_name = '';
/**
* In welke state zitten we binnen deze tag
*
* @var string
*/
private $_state = '';
/**
* De states bninen welke deze tag is toegestaan
*
* @var array
*/
private $_permissableIn = array();
/**
* Bijbehorende processor
*
* @var Tag|callback
*/
private $_processor = null;
/**
* Manier waarop deze tag wordt geparsed
*
* @var integer Een van {@link TagRule::PARSE} en {@link TagRule::LITERAL}
*/
private $_parseType = self::PARSE;
/**
* Bepaalt of en zo ja, aan welke kanten van de tag whitespace wordt getrimmed
*
* @var integer
*/
private $_trimWhitespace = self::TRIM_NONE;
/**
* Stel de waarden voor deze rule in
*
* @param string $name De naam van de rule
* @param string $state De state waarin we in deze tag zitten
* @param array $permissableIn States waarin deze rule zich mag bevinden
* @param callback|Tag $processor Ofwel een callback ofwel een TagTemplate
* @param integer $trimWhitespace Op welke wijze moeten we whitespace rondom trimmen?
* @param integer $parseType Is dit een letterlijke of parsende rule?
*/
public function __construct($name, $state, array $permissableIn, $processor, $trimWhitespace = self::TRIM_NONE, $parseType = self::PARSE) {
if(!$processor instanceof Tag && !is_callable($processor)) {
throw new InvalidArgumentException('Geen geldige processor voor tag "' . $name . '"');
}
if($trimWhitespace < 0 || $trimWhitespace > 3) {
throw new InvalidArgumentException('TrimWhitespace is niet geldig voor "' . $name . '"');
}
if($parseType != self::PARSE && $parseType != self::LITERAL) {
throw new InvalidArgumentException('Parsetype is niet geldig voor "' . $name . '"');
}
$this->_name = $name;
$this->_state = $state;
$this->_permissableIn = $permissableIn;
$this->_processor = $processor;
$this->_trimWhitespace = $trimWhitespace;
$this->_parseType = $parseType;
}
/**
* Process de output van deze rule
*
* @param string $tagName
* @param string $content
* @param array $arguments
* @return string
*/
public function processOutput($tagName, $content, array $arguments) {
if($this->_processor instanceof Tag) {
return $this->_processor->render($tagName, $content, $arguments);
}
return call_user_func($this->_processor, $tagName, $content, $arguments);
}
/**
* Haal de tagnaam op
*
* @return string
*/
public function getName() {
return $this->_name;
}
/**
* Haal de state op
*
* @return string
*/
public function getState() {
return $this->_state;
}
/**
* Check of deze state is toegestaan binnen een node
*
* @param PointNode $node
* @return boolean
*/
public function isPermissableIn(PointNode $node) {
return in_array($node->getRule()->getState(), $this->_permissableIn);
}
/**
* Kijk of er whitespace moet worden weggehaald voor of na
*
* @return integer
*/
public function getTrimWhitespace() {
return $this->_trimWhitespace;
}
/**
* Hoe moet deze tag worden geparsed
*
* @return integer Een van {@link TagRule::PARSE} en
* {@link TagRule::LITERAL}
*/
public function getParseType() {
return $this->_parseType;
}
}
/**
* Tag
*
* @author Dos Moonen
* @package Bbcode
* @subpackage Tag
*/
abstract class Tag {
/**
* Render de tag
*
* Voer de rendering uit met behulp van de argumenten
*
* @param string $tagName
* @param string $content
* @param array $arguments
* @return string
*/
abstract function render($tagName, $content, array $arguments);
}
/**
* Code Tag
*
* @author Dos Moonen
* @package Bbcode
* @subpackage Tag
*/
class CodeTag extends Tag
{
/**
* Verwerking voor de code tag
*
* @param string $tagName
* @param string $content
* @param array $arguments
* @return string
*/
public function render($tagName, $content, array $arguments) {
$lines = '<pre style="float: left; margin-top: 0px; margin-bottom: 0px;">'.implode('<br />', range(1, substr_count($content, "\n") + 1)).'</pre>';
$code = str_replace(
array('<code>', '</code>', "\n"),
array('<pre style="float: left; width: 90%; margin-left: 5px; margin-top: 0px; margin-bottom: 0px; white-space: nowrap; overflow: auto;">', '</pre>', ''),
highlight_string(
$content,
true
)
);
$return = '<div style="border: 1px dotted #333; text-align: left; width: 400px;"><div style="clear: both;"></div>' . $lines . $code . '<div style="clear: both;"></div></div>';
return $return;
}
}
/**
* Image Tag
*
* @author Dos Moonen
* @package Bbcode
* @subpackage Tag
*/
class ImageTag extends Tag
{
/**
* Verwerking voor een image tag
*
* @param string $tagName
* @param string $content
* @param array $arguments
* @return string
*/
public function render($tagName, $content, array $arguments) {
$content = (empty($content) AND isset($arguments[$tagName])) ? $arguments[$tagName] : $content;
$match = array();
if(!preg_match('{^(?:((?:https?|ftp)://(?:www\.)?)|\w+\.)[\w.]+[;#&/~=\w+()?.,:%-]*$}i', $content, $match)) {
return htmlentities($content, ENT_QUOTES);
}
$url = $content;
if(empty($match[1])) {
$url = 'http://' . $url;
}
$alt = isset($arguments['alt']) ? htmlentities($arguments['alt'], ENT_QUOTES) : '';
return '<img src="' . htmlentities($url, ENT_QUOTES) . '" alt="' . $alt . '" />';
}
}
/**
* Link tag
*
* @author Dos Moonen
* @package Bbcode
* @subpackage Tag
*/
class TagLink extends Tag
{
/**
* Verwerking voor de url tag
*
* @param string $tagName
* @param string $content
* @param array $arguments
* @return string
*/
public function render($tagName, $content, array $arguments) {
$argName = $tagName;
$link = &$arguments[$argName];
$match = array();
if(!isset($link) || !preg_match('{^(?:((?:https?|ftp)://)|\w+\.)[\w.]+[;#&/~=\w+()?.,:%-]*$}i', $link, $match)) {
return $content;
}
if(empty($match[1])) {
$link = 'http://' . $link;
}
return '<a href="' . $link . '">' . (!empty($content) ? $content : $link) . '</a>';
}
}
/**
* TagTemplate
*
* Een template die kan worden gebruikt bij een tag
*
* @author Richard van Velzen
* @package Bbcode
* @subpackage Tag
*/
class TagTemplate extends Tag {
/**
* De originele template
*
* @var string
*/
private $_template = '';
/**
* Argumenten bij de aanroep
*
* @var array
*/
private $_arguments = array();
/**
* Voer de template in
*
* @param string $template
*/
public function __construct($template) {
$this->_template = $template;
}
/**
* Render de template
*
* Voer de templaterendering uit met behulp van de argumenten
*
* @param string $tagName
* @param string $content
* @param array $arguments
* @return string
*/
public function render($tagName, $content, array $arguments) {
$this->_arguments = $arguments;
$this->_arguments['_tag'] = $tagName;
$this->_arguments['_content'] = $content;
$return = preg_replace_callback(
'~{\$([A-Za-z_][A-Za-z\d_]*)(?:/([a-z\d/]+))?}~',
array($this, '_variableReplace'),
$this->_template
);
// geheugen vrijmaken, blij blij blij :-)
$this->_arguments = null;
return $return;
}
/**
* Callback voor het vervangen van template variabelen
*
* @param array $match
* @return string
*/
private function _variableReplace(array $match) {
if(! isset($this->_arguments[$match[1]])) {
return '';
}
$return = $this->_arguments[$match[1]];
if(isset($match[2])) {
$return = $this->_applyModifiers($return, explode('/', $match[2]));
}
return $return;
}
/**
* Voer verschillende modifiers uit over de tekst
*
* @param string $text
* @param array $modifiers
* @return string
*/
private function _applyModifiers($text, array $modifiers) {
$usedModifiers = array();
foreach($modifiers as $modifier) {
// even checken, niet dubbel uitvoeren
if(isset($usedModifiers[$modifier])) {
continue;
}
$usedModifiers[$modifier] = 0;
switch(strtolower(trim($modifier))) {
case 'trim':
$text = preg_replace(array('{^(?:<br />|\s+)+}', '{(?:<br />|\s+)+$}'), '', $text);
break;
case 'nl2br':
$text = str_replace("\n", '<br />', $text);
break;
case 'html':
$text = htmlentities($text, ENT_QUOTES);
break;
default:
// oei, foutje zeker?
throw new RuntimeException('Onbekende modifier "' . $modifier . '"');
}
}
return $text;
}
}
<?php /** * Een generieke regel qua wat er moet gebeuren * * @author Richard van Velzen * @package Bbcode * @subpackage TagRule */ class TagRule { /** * Geen whitespace trimmen */ const TRIM_NONE = 0; /** * Whitespace voor de tag trimmen */ const TRIM_BEFORE = 1; /** * Whitespace na de tag trimmen */ const TRIM_AFTER = 2; /** * Aan beide kanten trimmen */ const TRIM_BOTH = 3; /** * Binnen deze tag verder parsen */ const PARSE = 3; /** * Niet parsen binnen deze tag */ const LITERAL = 2; /** * Naam van de tag * * @var string */ private $_name = ''; /** * In welke state zitten we binnen deze tag * * @var string */ private $_state = ''; /** * De states bninen welke deze tag is toegestaan * * @var array */ private $_permissableIn = array(); /** * Bijbehorende processor * * @var Tag|callback */ private $_processor = null; /** * Manier waarop deze tag wordt geparsed * * @var integer Een van {@link TagRule::PARSE} en {@link TagRule::LITERAL} */ private $_parseType = self::PARSE; /** * Bepaalt of en zo ja, aan welke kanten van de tag whitespace wordt getrimmed * * @var integer */ private $_trimWhitespace = self::TRIM_NONE; /** * Stel de waarden voor deze rule in * * @param string $name De naam van de rule * @param string $state De state waarin we in deze tag zitten * @param array $permissableIn States waarin deze rule zich mag bevinden * @param callback|Tag $processor Ofwel een callback ofwel een TagTemplate * @param integer $trimWhitespace Op welke wijze moeten we whitespace rondom trimmen? * @param integer $parseType Is dit een letterlijke of parsende rule? */ public function __construct ($name, $state, array $permissableIn, $processor, $trimWhitespace = self::TRIM_NONE, $parseType = self::PARSE) { if(!$processor instanceof Tag && !is_callable($processor)) { throw new InvalidArgumentException('Geen geldige processor voor tag "' . $name . '"'); } if($trimWhitespace < 0 || $trimWhitespace > 3) { throw new InvalidArgumentException('TrimWhitespace is niet geldig voor "' . $name . '"'); } if($parseType != self::PARSE && $parseType != self::LITERAL) { throw new InvalidArgumentException('Parsetype is niet geldig voor "' . $name . '"'); } $this->_name = $name; $this->_state = $state; $this->_permissableIn = $permissableIn; $this->_processor = $processor; $this->_trimWhitespace = $trimWhitespace; $this->_parseType = $parseType; } /** * Process de output van deze rule * * @param string $tagName * @param string $content * @param array $arguments * @return string */ public function processOutput ($tagName, $content, array $arguments) { if($this->_processor instanceof Tag) { return $this->_processor->render($tagName, $content, $arguments); } return call_user_func($this->_processor , $tagName, $content, $arguments); } /** * Haal de tagnaam op * * @return string */ public function getName() { return $this->_name; } /** * Haal de state op * * @return string */ public function getState() { return $this->_state; } /** * Check of deze state is toegestaan binnen een node * * @param PointNode $node * @return boolean */ public function isPermissableIn(PointNode $node) { return in_array($node->getRule()->getState(), $this->_permissableIn ); } /** * Kijk of er whitespace moet worden weggehaald voor of na * * @return integer */ public function getTrimWhitespace() { return $this->_trimWhitespace; } /** * Hoe moet deze tag worden geparsed * * @return integer Een van {@link TagRule::PARSE} en * {@link TagRule::LITERAL} */ public function getParseType() { return $this->_parseType; } } /** * Tag * * @author Dos Moonen * @package Bbcode * @subpackage Tag */ abstract class Tag { /** * Render de tag * * Voer de rendering uit met behulp van de argumenten * * @param string $tagName * @param string $content * @param array $arguments * @return string */ abstract function render ($tagName, $content, array $arguments); } /** * Code Tag * * @author Dos Moonen * @package Bbcode * @subpackage Tag */ class CodeTag extends Tag { /** * Verwerking voor de code tag * * @param string $tagName * @param string $content * @param array $arguments * @return string */ public function render ($tagName, $content, array $arguments) { $lines = '<pre style="float: left; margin-top: 0px; margin-bottom: 0px;">'.implode('<br />', range(1, substr_count($content, "\n") + 1)).'</pre>'; array('<code>', '</code>', "\n"), array('<pre style="float: left; width: 90%; margin-left: 5px; margin-top: 0px; margin-bottom: 0px; white-space: nowrap; overflow: auto;">', '</pre>', ''), $content, true ) ); $return = '<div style="border: 1px dotted #333; text-align: left; width: 400px;"><div style="clear: both;"></div>' . $lines . $code . '<div style="clear: both;"></div></div>'; return $return; } } /** * Image Tag * * @author Dos Moonen * @package Bbcode * @subpackage Tag */ class ImageTag extends Tag { /** * Verwerking voor een image tag * * @param string $tagName * @param string $content * @param array $arguments * @return string */ public function render ($tagName, $content, array $arguments) { $content = (empty($content) AND isset($arguments[$tagName])) ? $arguments[$tagName] : $content; if(!preg_match('{^(?:((?:https?|ftp)://(?:www\.)?)|\w+\.)[\w.]+[;#&/~=\w+()?.,:%-]*$}i', $content, $match)) { } $url = $content; $url = 'http://' . $url; } return '<img src="' . htmlentities($url, ENT_QUOTES ) . '" alt="' . $alt . '" />'; } } /** * Link tag * * @author Dos Moonen * @package Bbcode * @subpackage Tag */ class TagLink extends Tag { /** * Verwerking voor de url tag * * @param string $tagName * @param string $content * @param array $arguments * @return string */ public function render ($tagName, $content, array $arguments) { $argName = $tagName; $link = &$arguments[$argName]; if(!isset($link) || !preg_match('{^(?:((?:https?|ftp)://)|\w+\.)[\w.]+[;#&/~=\w+()?.,:%-]*$}i', $link, $match)) { return $content; } $link = 'http://' . $link; } return '<a href="' . $link . '">' . (!empty($content) ? $content : $link) . '</a>'; } } /** * TagTemplate * * Een template die kan worden gebruikt bij een tag * * @author Richard van Velzen * @package Bbcode * @subpackage Tag */ class TagTemplate extends Tag { /** * De originele template * * @var string */ private $_template = ''; /** * Argumenten bij de aanroep * * @var array */ private $_arguments = array(); /** * Voer de template in * * @param string $template */ public function __construct($template) { $this->_template = $template; } /** * Render de template * * Voer de templaterendering uit met behulp van de argumenten * * @param string $tagName * @param string $content * @param array $arguments * @return string */ public function render ($tagName, $content, array $arguments) { $this->_arguments = $arguments; $this->_arguments['_tag'] = $tagName; $this->_arguments['_content'] = $content; '~{\$([A-Za-z_][A-Za-z\d_]*)(?:/([a-z\d/]+))?}~', array($this, '_variableReplace'), $this->_template ); // geheugen vrijmaken, blij blij blij :-) $this->_arguments = null; return $return; } /** * Callback voor het vervangen van template variabelen * * @param array $match * @return string */ private function _variableReplace (array $match) { if(! isset($this->_arguments [$match[1]])) { return ''; } $return = $this->_arguments[$match[1]]; $return = $this->_applyModifiers ($return, explode('/', $match[2])); } return $return; } /** * Voer verschillende modifiers uit over de tekst * * @param string $text * @param array $modifiers * @return string */ private function _applyModifiers ($text, array $modifiers) { $usedModifiers = array(); foreach($modifiers as $modifier) { // even checken, niet dubbel uitvoeren if(isset($usedModifiers[$modifier])) { continue; } $usedModifiers[$modifier] = 0; case 'trim': break; case 'nl2br': break; case 'html': break; default: // oei, foutje zeker? throw new RuntimeException('Onbekende modifier "' . $modifier . '"'); } } return $text; } }
|
|
|
Enkel aanvullende informatie, vragen en antwoorden op vragen zijn welkom. |
|
|
|