Add formatting libraries to composer.json

This commit is contained in:
Kijin Sung 2016-03-17 20:37:43 +09:00
parent 59ee4a7387
commit 2b008f7be6
202 changed files with 28022 additions and 133 deletions

2
vendor/jbbcode/jbbcode/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
composer.lock
vendor

View file

@ -0,0 +1,328 @@
<?php
namespace JBBCode;
/**
* This class represents a BBCode Definition. You may construct instances of this class directly,
* usually through the CodeDefinitionBuilder class, to create text replacement bbcodes, or you
* may subclass it to create more complex bbcode definitions.
*
* @author jbowens
*/
class CodeDefinition
{
/* NOTE: THIS PROPERTY SHOULD ALWAYS BE LOWERCASE; USE setTagName() TO ENSURE THIS */
protected $tagName;
/* Whether or not this CodeDefinition uses an option parameter. */
protected $useOption;
/* The replacement text to be used for simple CodeDefinitions */
protected $replacementText;
/* Whether or not to parse elements of this definition's contents */
protected $parseContent;
/* How many of this element type may be nested within each other */
protected $nestLimit;
/* How many of this element type have been seen */
protected $elCounter;
/* The input validator to run options through */
protected $optionValidator;
/* The input validator to run the body ({param}) through */
protected $bodyValidator;
/**
* Constructs a new CodeDefinition.
*/
public static function construct($tagName, $replacementText, $useOption = false,
$parseContent = true, $nestLimit = -1, $optionValidator = array(),
$bodyValidator = null)
{
$def = new CodeDefinition();
$def->elCounter = 0;
$def->setTagName($tagName);
$def->setReplacementText($replacementText);
$def->useOption = $useOption;
$def->parseContent = $parseContent;
$def->nestLimit = $nestLimit;
$def->optionValidator = $optionValidator;
$def->bodyValidator = $bodyValidator;
return $def;
}
/**
* Constructs a new CodeDefinition.
*
* This constructor is deprecated. You should use the static construct() method or the
* CodeDefinitionBuilder class to construct a new CodeDefiniton.
*
* @deprecated
*/
public function __construct()
{
/* WARNING: This function is deprecated and will be made protected in a future
* version of jBBCode. */
$this->parseContent = true;
$this->useOption = false;
$this->nestLimit = -1;
$this->elCounter = 0;
$this->optionValidator = array();
$this->bodyValidator = null;
}
/**
* Determines if the arguments to the given element are valid based on
* any validators attached to this CodeDefinition.
*
* @param $el the ElementNode to validate
* @return true if the ElementNode's {option} and {param} are OK, false if they're not
*/
public function hasValidInputs(ElementNode $el)
{
if ($this->usesOption() && $this->optionValidator) {
$att = $el->getAttribute();
foreach($att as $name => $value){
if(isset($this->optionValidator[$name]) && !$this->optionValidator[$name]->validate($value)){
return false;
}
}
}
if (!$this->parseContent() && $this->bodyValidator) {
/* We only evaluate the content if we're not parsing the content. */
$content = "";
foreach ($el->getChildren() as $child) {
$content .= $child->getAsBBCode();
}
if (!$this->bodyValidator->validate($content)) {
/* The content of the element is not valid. */
return false;
}
}
return true;
}
/**
* Accepts an ElementNode that is defined by this CodeDefinition and returns the HTML
* markup of the element. This is a commonly overridden class for custom CodeDefinitions
* so that the content can be directly manipulated.
*
* @param $el the element to return an html representation of
*
* @return the parsed html of this element (INCLUDING ITS CHILDREN)
*/
public function asHtml(ElementNode $el)
{
if (!$this->hasValidInputs($el)) {
return $el->getAsBBCode();
}
$html = $this->getReplacementText();
if ($this->usesOption()) {
$options = $el->getAttribute();
if(count($options)==1){
$vals = array_values($options);
$html = str_ireplace('{option}', reset($vals), $html);
}
else{
foreach($options as $key => $val){
$html = str_ireplace('{' . $key . '}', $val, $html);
}
}
}
$content = $this->getContent($el);
$html = str_ireplace('{param}', $content, $html);
return $html;
}
protected function getContent(ElementNode $el){
if ($this->parseContent()) {
$content = "";
foreach ($el->getChildren() as $child)
$content .= $child->getAsHTML();
} else {
$content = "";
foreach ($el->getChildren() as $child)
$content .= $child->getAsBBCode();
}
return $content;
}
/**
* Accepts an ElementNode that is defined by this CodeDefinition and returns the text
* representation of the element. This may be overridden by a custom CodeDefinition.
*
* @param $el the element to return a text representation of
*
* @return the text representation of $el
*/
public function asText(ElementNode $el)
{
if (!$this->hasValidInputs($el)) {
return $el->getAsBBCode();
}
$s = "";
foreach ($el->getChildren() as $child)
$s .= $child->getAsText();
return $s;
}
/**
* Returns the tag name of this code definition
*
* @return this definition's associated tag name
*/
public function getTagName()
{
return $this->tagName;
}
/**
* Returns the replacement text of this code definition. This usually has little, if any meaning if the
* CodeDefinition class was extended. For default, html replacement CodeDefinitions this returns the html
* markup for the definition.
*
* @return the replacement text of this CodeDefinition
*/
public function getReplacementText()
{
return $this->replacementText;
}
/**
* Returns whether or not this CodeDefinition uses the optional {option}
*
* @return true if this CodeDefinition uses the option, false otherwise
*/
public function usesOption()
{
return $this->useOption;
}
/**
* Returns whether or not this CodeDefnition parses elements contained within it,
* or just treats its children as text.
*
* @return true if this CodeDefinition parses elements contained within itself
*/
public function parseContent()
{
return $this->parseContent;
}
/**
* Returns the limit of how many elements defined by this CodeDefinition may be
* nested together. If after parsing elements are nested beyond this limit, the
* subtrees formed by those nodes will be removed from the parse tree. A nest
* limit of -1 signifies no limit.
*/
public function getNestLimit()
{
return $this->nestLimit;
}
/**
* Sets the tag name of this CodeDefinition
*
* @deprecated
*
* @param the new tag name of this definition
*/
public function setTagName($tagName)
{
$this->tagName = strtolower($tagName);
}
/**
* Sets the html replacement text of this CodeDefinition
*
* @deprecated
*
* @param the new replacement text
*/
public function setReplacementText($txt)
{
$this->replacementText = $txt;
}
/**
* Sets whether or not this CodeDefinition uses the {option}
*
* @deprecated
*
* @param boolean $bool
*/
public function setUseOption($bool)
{
$this->useOption = $bool;
}
/**
* Sets whether or not this CodeDefinition allows its children to be parsed as html
*
* @deprecated
*
* @param boolean $bool
*/
public function setParseContent($bool)
{
$this->parseContent = $bool;
}
/**
* Increments the element counter. This is used for tracking depth of elements of the same type for next limits.
*
* @deprecated
*
* @return void
*/
public function incrementCounter()
{
$this->elCounter++;
}
/**
* Decrements the element counter.
*
* @deprecated
*
* @return void
*/
public function decrementCounter()
{
$this->elCounter--;
}
/**
* Resets the element counter.
*
* @deprecated
*/
public function resetCounter()
{
$this->elCounter = 0;
}
/**
* Returns the current value of the element counter.
*
* @deprecated
*
* @return int
*/
public function getCounter()
{
return $this->elCounter;
}
}

View file

@ -0,0 +1,160 @@
<?php
namespace JBBCode;
require_once "CodeDefinition.php";
/**
* Implements the builder pattern for the CodeDefinition class. A builder
* is the recommended way of constructing CodeDefinition objects.
*
* @author jbowens
*/
class CodeDefinitionBuilder
{
protected $tagName;
protected $useOption = false;
protected $replacementText;
protected $parseContent = true;
protected $nestLimit = -1;
protected $optionValidator = array();
protected $bodyValidator = null;
/**
* Construct a CodeDefinitionBuilder.
*
* @param $tagName the tag name of the definition to build
* @param $replacementText the replacement text of the definition to build
*/
public function __construct($tagName, $replacementText)
{
$this->tagName = $tagName;
$this->replacementText = $replacementText;
}
/**
* Sets the tag name the CodeDefinition should be built with.
*
* @param $tagName the tag name for the new CodeDefinition
*/
public function setTagName($tagName)
{
$this->tagName = $tagName;
return $this;
}
/**
* Sets the replacement text that the new CodeDefinition should be
* built with.
*
* @param $replacementText the replacement text for the new CodeDefinition
*/
public function setReplacementText($replacementText)
{
$this->replacementText = $replacementText;
return $this;
}
/**
* Set whether or not the built CodeDefinition should use the {option} bbcode
* argument.
*
* @param $option ture iff the definition includes an option
*/
public function setUseOption($option)
{
$this->useOption = $option;
return $this;
}
/**
* Set whether or not the built CodeDefinition should allow its content
* to be parsed and evaluated as bbcode.
*
* @param $parseContent true iff the content should be parsed
*/
public function setParseContent($parseContent)
{
$this->parseContent = $parseContent;
return $this;
}
/**
* Sets the nest limit for this code definition.
*
* @param $nestLimit a positive integer, or -1 if there is no limit.
* @throws \InvalidArgumentException if the nest limit is invalid
*/
public function setNestLimit($limit)
{
if(!is_int($limit) || ($limit <= 0 && -1 != $limit)) {
throw new \InvalidArgumentException("A nest limit must be a positive integer " .
"or -1.");
}
$this->nestLimit = $limit;
return $this;
}
/**
* Sets the InputValidator that option arguments should be validated with.
*
* @param $validator the InputValidator instance to use
*/
public function setOptionValidator(\JBBCode\InputValidator $validator, $option=null)
{
if(empty($option)){
$option = $this->tagName;
}
$this->optionValidator[$option] = $validator;
return $this;
}
/**
* Sets the InputValidator that body ({param}) text should be validated with.
*
* @param $validator the InputValidator instance to use
*/
public function setBodyValidator(\JBBCode\InputValidator $validator)
{
$this->bodyValidator = $validator;
return $this;
}
/**
* Removes the attached option validator if one is attached.
*/
public function removeOptionValidator()
{
$this->optionValidator = array();
return $this;
}
/**
* Removes the attached body validator if one is attached.
*/
public function removeBodyValidator()
{
$this->bodyValidator = null;
return $this;
}
/**
* Builds a CodeDefinition with the current state of the builder.
*
* @return a new CodeDefinition instance
*/
public function build()
{
$definition = CodeDefinition::construct($this->tagName,
$this->replacementText,
$this->useOption,
$this->parseContent,
$this->nestLimit,
$this->optionValidator,
$this->bodyValidator);
return $definition;
}
}

View file

@ -0,0 +1,22 @@
<?php
namespace JBBCode;
require_once 'CodeDefinition.php';
use JBBCode\CodeDefinition;
/**
* An interface for sets of code definitons.
*
* @author jbowens
*/
interface CodeDefinitionSet
{
/**
* Retrieves the CodeDefinitions within this set as an array.
*/
public function getCodeDefinitions();
}

View file

@ -0,0 +1,75 @@
<?php
namespace JBBCode;
require_once 'CodeDefinition.php';
require_once 'CodeDefinitionBuilder.php';
require_once 'CodeDefinitionSet.php';
require_once 'validators/CssColorValidator.php';
require_once 'validators/UrlValidator.php';
/**
* Provides a default set of common bbcode definitions.
*
* @author jbowens
*/
class DefaultCodeDefinitionSet implements CodeDefinitionSet
{
/* The default code definitions in this set. */
protected $definitions = array();
/**
* Constructs the default code definitions.
*/
public function __construct()
{
/* [b] bold tag */
$builder = new CodeDefinitionBuilder('b', '<strong>{param}</strong>');
array_push($this->definitions, $builder->build());
/* [i] italics tag */
$builder = new CodeDefinitionBuilder('i', '<em>{param}</em>');
array_push($this->definitions, $builder->build());
/* [u] underline tag */
$builder = new CodeDefinitionBuilder('u', '<u>{param}</u>');
array_push($this->definitions, $builder->build());
$urlValidator = new \JBBCode\validators\UrlValidator();
/* [url] link tag */
$builder = new CodeDefinitionBuilder('url', '<a href="{param}">{param}</a>');
$builder->setParseContent(false)->setBodyValidator($urlValidator);
array_push($this->definitions, $builder->build());
/* [url=http://example.com] link tag */
$builder = new CodeDefinitionBuilder('url', '<a href="{option}">{param}</a>');
$builder->setUseOption(true)->setParseContent(true)->setOptionValidator($urlValidator);
array_push($this->definitions, $builder->build());
/* [img] image tag */
$builder = new CodeDefinitionBuilder('img', '<img src="{param}" />');
$builder->setUseOption(false)->setParseContent(false)->setBodyValidator($urlValidator);
array_push($this->definitions, $builder->build());
/* [img=alt text] image tag */
$builder = new CodeDefinitionBuilder('img', '<img src="{param}" alt="{option}" />');
$builder->setUseOption(true)->setParseContent(false)->setBodyValidator($urlValidator);
array_push($this->definitions, $builder->build());
/* [color] color tag */
$builder = new CodeDefinitionBuilder('color', '<span style="color: {option}">{param}</span>');
$builder->setUseOption(true)->setOptionValidator(new \JBBCode\validators\CssColorValidator());
array_push($this->definitions, $builder->build());
}
/**
* Returns an array of the default code definitions.
*/
public function getCodeDefinitions()
{
return $this->definitions;
}
}

View file

@ -0,0 +1,67 @@
<?php
namespace JBBCode;
require_once 'ElementNode.php';
/**
* A DocumentElement object represents the root of a document tree. All
* documents represented by this document model should have one as its root.
*
* @author jbowens
*/
class DocumentElement extends ElementNode
{
/**
* Constructs the document element node
*/
public function __construct()
{
parent::__construct();
$this->setTagName("Document");
$this->setNodeId(0);
}
/**
* (non-PHPdoc)
* @see JBBCode.ElementNode::getAsBBCode()
*
* Returns the BBCode representation of this document
*
* @return this document's bbcode representation
*/
public function getAsBBCode()
{
$s = "";
foreach($this->getChildren() as $child){
$s .= $child->getAsBBCode();
}
return $s;
}
/**
* (non-PHPdoc)
* @see JBBCode.ElementNode::getAsHTML()
*
* Documents don't add any html. They only exist as a container for their
* children, so getAsHTML() simply iterates through the document's children,
* returning their html.
*
* @return the HTML representation of this document
*/
public function getAsHTML()
{
$s = "";
foreach($this->getChildren() as $child)
$s .= $child->getAsHTML();
return $s;
}
public function accept(NodeVisitor $visitor)
{
$visitor->visitDocumentElement($this);
}
}

View file

@ -0,0 +1,241 @@
<?php
namespace JBBCode;
require_once 'Node.php';
/**
* An element within the tree. Consists of a tag name which defines the type of the
* element and any number of Node children. It also contains a CodeDefinition matching
* the tag name of the element.
*
* @author jbowens
*/
class ElementNode extends Node
{
/* The tagname of this element, for i.e. "b" in [b]bold[/b] */
protected $tagName;
/* The attribute, if any, of this element node */
protected $attribute;
/* The child nodes contained within this element */
protected $children;
/* The code definition that defines this element's behavior */
protected $codeDefinition;
/* How deeply this node is nested */
protected $nestDepth;
/**
* Constructs the element node
*/
public function __construct()
{
$this->children = array();
$this->nestDepth = 0;
}
/**
* Accepts the given NodeVisitor. This is part of an implementation
* of the Visitor pattern.
*
* @param $nodeVisitor the visitor attempting to visit this node
*/
public function accept(NodeVisitor $nodeVisitor)
{
$nodeVisitor->visitElementNode($this);
}
/**
* Gets the CodeDefinition that defines this element.
*
* @return this element's code definition
*/
public function getCodeDefinition()
{
return $this->codeDefinition;
}
/**
* Sets the CodeDefinition that defines this element.
*
* @param codeDef the code definition that defines this element node
*/
public function setCodeDefinition(CodeDefinition $codeDef)
{
$this->codeDefinition = $codeDef;
$this->setTagName($codeDef->getTagName());
}
/**
* Returns the tag name of this element.
*
* @return the element's tag name
*/
public function getTagName()
{
return $this->tagName;
}
/**
* Returns the attribute (used as the option in bbcode definitions) of this element.
*
* @return the attribute of this element
*/
public function getAttribute()
{
return $this->attribute;
}
/**
* Returns all the children of this element.
*
* @return an array of this node's child nodes
*/
public function getChildren()
{
return $this->children;
}
/**
* (non-PHPdoc)
* @see JBBCode.Node::getAsText()
*
* Returns the element as text (not including any bbcode markup)
*
* @return the plain text representation of this node
*/
public function getAsText()
{
if ($this->codeDefinition) {
return $this->codeDefinition->asText($this);
} else {
$s = "";
foreach ($this->getChildren() as $child)
$s .= $child->getAsText();
return $s;
}
}
/**
* (non-PHPdoc)
* @see JBBCode.Node::getAsBBCode()
*
* Returns the element as bbcode (with all unclosed tags closed)
*
* @return the bbcode representation of this element
*/
public function getAsBBCode()
{
$str = "[".$this->tagName;
if (!empty($this->attribute)) {
foreach($this->attribute as $key => $value){
if($key == $this->tagName){
$str .= "=".$value;
}
else{
$str .= " ".$key."=" . $value;
}
}
}
$str .= "]";
foreach ($this->getChildren() as $child) {
$str .= $child->getAsBBCode();
}
$str .= "[/".$this->tagName."]";
return $str;
}
/**
* (non-PHPdoc)
* @see JBBCode.Node::getAsHTML()
*
* Returns the element as html with all replacements made
*
* @return the html representation of this node
*/
public function getAsHTML()
{
if($this->codeDefinition) {
return $this->codeDefinition->asHtml($this);
} else {
return "";
}
}
/**
* Adds a child to this node's content. A child may be a TextNode, or
* another ElementNode... or anything else that may extend the
* abstract Node class.
*
* @param child the node to add as a child
*/
public function addChild(Node $child)
{
array_push($this->children, $child);
$child->setParent($this);
}
/**
* Removes a child from this node's contnet.
*
* @param child the child node to remove
*/
public function removeChild(Node $child)
{
foreach ($this->children as $key => $value) {
if ($value == $child)
unset($this->children[$key]);
}
}
/**
* Sets the tag name of this element node.
*
* @param tagName the element's new tag name
*/
public function setTagName($tagName)
{
$this->tagName = $tagName;
}
/**
* Sets the attribute (option) of this element node.
*
* @param attribute the attribute of this element node
*/
public function setAttribute($attribute)
{
$this->attribute = $attribute;
}
/**
* Traverses the parse tree upwards, going from parent to parent, until it finds a
* parent who has the given tag name. Returns the parent with the matching tag name
* if it exists, otherwise returns null.
*
* @param str the tag name to search for
*
* @return the closest parent with the given tag name
*/
public function closestParentOfType($str)
{
$str = strtolower($str);
$currentEl = $this;
while (strtolower($currentEl->getTagName()) != $str && $currentEl->hasParent()) {
$currentEl = $currentEl->getParent();
}
if (strtolower($currentEl->getTagName()) != $str) {
return null;
} else {
return $currentEl;
}
}
}

View file

@ -0,0 +1,20 @@
<?php
namespace JBBCode;
/**
* Defines an interface for validation filters for bbcode options and
* parameters.
*
* @author jbowens
* @since May 2013
*/
interface InputValidator
{
/**
* Returns true iff the given input is valid, false otherwise.
*/
public function validate($input);
}

109
vendor/jbbcode/jbbcode/JBBCode/Node.php vendored Normal file
View file

@ -0,0 +1,109 @@
<?php
namespace JBBCode;
/**
* A node within the document tree.
*
* Known subclasses: TextNode, ElementNode
*
* @author jbowens
*/
abstract class Node
{
/* Pointer to the parent node of this node */
protected $parent;
/* The node id of this node */
protected $nodeid;
/**
* Returns the node id of this node. (Not really ever used. Dependent upon the parse tree the node exists within.)
*
* @return this node's id
*/
public function getNodeId()
{
return $this->nodeid;
}
/**
* Returns this node's immediate parent.
*
* @return the node's parent
*/
public function getParent()
{
return $this->parent;
}
/**
* Determines if this node has a parent.
*
* @return true if this node has a parent, false otherwise
*/
public function hasParent()
{
return $this->parent != null;
}
/**
* Returns true if this is a text node. Returns false otherwise.
* (Overridden by TextNode to return true)
*
* @return true if this node is a text node
*/
public function isTextNode()
{
return false;
}
/**
* Accepts a NodeVisitor
*
* @param nodeVisitor the NodeVisitor traversing the graph
*/
abstract public function accept(NodeVisitor $nodeVisitor);
/**
* Returns this node as text (without any bbcode markup)
*
* @return the plain text representation of this node
*/
abstract public function getAsText();
/**
* Returns this node as bbcode
*
* @return the bbcode representation of this node
*/
abstract public function getAsBBCode();
/**
* Returns this node as HTML
*
* @return the html representation of this node
*/
abstract public function getAsHTML();
/**
* Sets this node's parent to be the given node.
*
* @param parent the node to set as this node's parent
*/
public function setParent(Node $parent)
{
$this->parent = $parent;
}
/**
* Sets this node's nodeid
*
* @param nodeid this node's node id
*/
public function setNodeId($nodeid)
{
$this->nodeid = $nodeid;
}
}

View file

@ -0,0 +1,20 @@
<?php
namespace JBBCode;
/**
* Defines an interface for a visitor to traverse the node graph.
*
* @author jbowens
* @since January 2013
*/
interface NodeVisitor
{
public function visitDocumentElement(DocumentElement $documentElement);
public function visitTextNode(TextNode $textNode);
public function visitElementNode(ElementNode $elementNode);
}

View file

@ -0,0 +1,662 @@
<?php
namespace JBBCode;
require_once 'ElementNode.php';
require_once 'TextNode.php';
require_once 'DefaultCodeDefinitionSet.php';
require_once 'DocumentElement.php';
require_once 'CodeDefinition.php';
require_once 'CodeDefinitionBuilder.php';
require_once 'CodeDefinitionSet.php';
require_once 'NodeVisitor.php';
require_once 'ParserException.php';
require_once 'Tokenizer.php';
require_once 'visitors/NestLimitVisitor.php';
require_once 'InputValidator.php';
use JBBCode\CodeDefinition;
/**
* BBCodeParser is the main parser class that constructs and stores the parse tree. Through this class
* new bbcode definitions can be added, and documents may be parsed and converted to html/bbcode/plaintext, etc.
*
* @author jbowens
*/
class Parser
{
const OPTION_STATE_DEFAULT = 0;
const OPTION_STATE_TAGNAME = 1;
const OPTION_STATE_KEY = 2;
const OPTION_STATE_VALUE = 3;
const OPTION_STATE_QUOTED_VALUE = 4;
const OPTION_STATE_JAVASCRIPT = 5;
/* The root element of the parse tree */
protected $treeRoot;
/* The list of bbcodes to be used by the parser. */
protected $bbcodes;
/* The next node id to use. This is used while parsing. */
protected $nextNodeid;
/**
* Constructs an instance of the BBCode parser
*/
public function __construct()
{
$this->reset();
$this->bbcodes = array();
}
/**
* Adds a simple (text-replacement only) bbcode definition
*
* @param string $tagName the tag name of the code (for example the b in [b])
* @param string $replace the html to use, with {param} and optionally {option} for replacements
* @param boolean $useOption whether or not this bbcode uses the secondary {option} replacement
* @param boolean $parseContent whether or not to parse the content within these elements
* @param integer $nestLimit an optional limit of the number of elements of this kind that can be nested within
* each other before the parser stops parsing them.
* @param InputValidator $optionValidator the validator to run {option} through
* @param BodyValidator $bodyValidator the validator to run {param} through (only used if $parseContent == false)
*
* @return Parser
*/
public function addBBCode($tagName, $replace, $useOption = false, $parseContent = true, $nestLimit = -1,
InputValidator $optionValidator = null, InputValidator $bodyValidator = null)
{
$builder = new CodeDefinitionBuilder($tagName, $replace);
$builder->setUseOption($useOption);
$builder->setParseContent($parseContent);
$builder->setNestLimit($nestLimit);
if ($optionValidator) {
$builder->setOptionValidator($optionValidator);
}
if ($bodyValidator) {
$builder->setBodyValidator($bodyValidator);
}
$this->addCodeDefinition($builder->build());
return $this;
}
/**
* Adds a complex bbcode definition. You may subclass the CodeDefinition class, instantiate a definition of your new
* class and add it to the parser through this method.
*
* @param CodeDefinition $definition the bbcode definition to add
*
* @return Parser
*/
public function addCodeDefinition(CodeDefinition $definition)
{
array_push($this->bbcodes, $definition);
return $this;
}
/**
* Adds a set of CodeDefinitions.
*
* @param CodeDefinitionSet $set the set of definitions to add
*
* @return Parser
*/
public function addCodeDefinitionSet(CodeDefinitionSet $set) {
foreach ($set->getCodeDefinitions() as $def) {
$this->addCodeDefinition($def);
}
return $this;
}
/**
* Returns the entire parse tree as text. Only {param} content is returned. BBCode markup will be ignored.
*
* @return string a text representation of the parse tree
*/
public function getAsText()
{
return $this->treeRoot->getAsText();
}
/**
* Returns the entire parse tree as bbcode. This will be identical to the inputted string, except unclosed tags
* will be closed.
*
* @return string a bbcode representation of the parse tree
*/
public function getAsBBCode()
{
return $this->treeRoot->getAsBBCode();
}
/**
* Returns the entire parse tree as HTML. All BBCode replacements will be made. This is generally the method
* you will want to use to retrieve the parsed bbcode.
*
* @return string a parsed html string
*/
public function getAsHTML()
{
return $this->treeRoot->getAsHTML();
}
/**
* Accepts the given NodeVisitor at the root.
*
* @param NodeVisitor a NodeVisitor
*
* @return Parser
*/
public function accept(NodeVisitor $nodeVisitor)
{
$this->treeRoot->accept($nodeVisitor);
return $this;
}
/**
* Constructs the parse tree from a string of bbcode markup.
*
* @param string $str the bbcode markup to parse
*
* @return Parser
*/
public function parse($str)
{
/* Set the tree root back to a fresh DocumentElement. */
$this->reset();
$parent = $this->treeRoot;
$tokenizer = new Tokenizer($str);
while ($tokenizer->hasNext()) {
$parent = $this->parseStartState($parent, $tokenizer);
if ($parent->getCodeDefinition() && false ===
$parent->getCodeDefinition()->parseContent()) {
/* We're inside an element that does not allow its contents to be parseable. */
$this->parseAsTextUntilClose($parent, $tokenizer);
$parent = $parent->getParent();
}
}
/* We parsed ignoring nest limits. Do an O(n) traversal to remove any elements that
* are nested beyond their CodeDefinition's nest limit. */
$this->removeOverNestedElements();
return $this;
}
/**
* Removes any elements that are nested beyond their nest limit from the parse tree. This
* method is now deprecated. In a future release its access privileges will be made
* protected.
*
* @deprecated
*/
public function removeOverNestedElements()
{
$nestLimitVisitor = new \JBBCode\visitors\NestLimitVisitor();
$this->accept($nestLimitVisitor);
}
/**
* Removes the old parse tree if one exists.
*/
protected function reset()
{
// remove any old tree information
$this->treeRoot = new DocumentElement();
/* The document element is created with nodeid 0. */
$this->nextNodeid = 1;
}
/**
* Determines whether a bbcode exists based on its tag name and whether or not it uses an option
*
* @param string $tagName the bbcode tag name to check
* @param boolean $usesOption whether or not the bbcode accepts an option
*
* @return bool true if the code exists, false otherwise
*/
public function codeExists($tagName, $usesOption = false)
{
foreach ($this->bbcodes as $code) {
if (strtolower($tagName) == $code->getTagName() && $usesOption == $code->usesOption()) {
return true;
}
}
return false;
}
/**
* Returns the CodeDefinition of a bbcode with the matching tag name and usesOption parameter
*
* @param string $tagName the tag name of the bbcode being searched for
* @param boolean $usesOption whether or not the bbcode accepts an option
*
* @return CodeDefinition if the bbcode exists, null otherwise
*/
public function getCode($tagName, $usesOption = false)
{
foreach ($this->bbcodes as $code) {
if (strtolower($tagName) == $code->getTagName() && $code->usesOption() == $usesOption) {
return $code;
}
}
return null;
}
/**
* Adds a set of default, standard bbcode definitions commonly used across the web.
*
* This method is now deprecated. Please use DefaultCodeDefinitionSet and
* addCodeDefinitionSet() instead.
*
* @deprecated
*/
public function loadDefaultCodes()
{
$defaultSet = new DefaultCodeDefinitionSet();
$this->addCodeDefinitionSet($defaultSet);
}
/**
* Creates a new text node with the given parent and text string.
*
* @param $parent the parent of the text node
* @param $string the text of the text node
*
* @return TextNode the newly created TextNode
*/
protected function createTextNode(ElementNode $parent, $string)
{
if (count($parent->getChildren())) {
$children = $parent->getChildren();
$lastElement = end($children);
reset($children);
if ($lastElement->isTextNode()) {
$lastElement->setValue($lastElement->getValue() . $string);
return $lastElement;
}
}
$textNode = new TextNode($string);
$textNode->setNodeId(++$this->nextNodeid);
$parent->addChild($textNode);
return $textNode;
}
/**
* jBBCode parsing logic is loosely modelled after a FSM. While not every function maps
* to a unique DFSM state, each function handles the logic of one or more FSM states.
* This function handles the beginning parse state when we're not currently in a tag
* name.
*
* @param ElementNode $parent the current parent node we're under
* @param Tokenizer $tokenizer the tokenizer we're using
*
* @return ElementNode the new parent we should use for the next iteration.
*/
protected function parseStartState(ElementNode $parent, Tokenizer $tokenizer)
{
$next = $tokenizer->next();
if ('[' == $next) {
return $this->parseTagOpen($parent, $tokenizer);
}
else {
$this->createTextNode($parent, $next);
/* Drop back into the main parse loop which will call this
* same method again. */
return $parent;
}
}
/**
* This function handles parsing the beginnings of an open tag. When we see a [
* at an appropriate time, this function is entered.
*
* @param ElementNode $parent the current parent node
* @param Tokenizer $tokenizer the tokenizer we're using
*
* @return ElementNode the new parent node
*/
protected function parseTagOpen(ElementNode $parent, Tokenizer $tokenizer)
{
if (!$tokenizer->hasNext()) {
/* The [ that sent us to this state was just a trailing [, not the
* opening for a new tag. Treat it as such. */
$this->createTextNode($parent, '[');
return $parent;
}
$next = $tokenizer->next();
/* This while loop could be replaced by a recursive call to this same method,
* which would likely be a lot clearer but I decided to use a while loop to
* prevent stack overflow with a string like [[[[[[[[[...[[[.
*/
while ('[' == $next) {
/* The previous [ was just a random bracket that should be treated as text.
* Continue until we get a non open bracket. */
$this->createTextNode($parent, '[');
if (!$tokenizer->hasNext()) {
$this->createTextNode($parent, '[');
return $parent;
}
$next = $tokenizer->next();
}
if (!$tokenizer->hasNext()) {
$this->createTextNode($parent, '['.$next);
return $parent;
}
$after_next = $tokenizer->next();
$tokenizer->stepBack();
if ($after_next != ']')
{
$this->createTextNode($parent, '['.$next);
return $parent;
}
/* At this point $next is either ']' or plain text. */
if (']' == $next) {
$this->createTextNode($parent, '[');
$this->createTextNode($parent, ']');
return $parent;
} else {
/* $next is plain text... likely a tag name. */
return $this->parseTag($parent, $tokenizer, $next);
}
}
protected function parseOptions($tagContent)
{
$buffer = "";
$tagName = "";
$state = static::OPTION_STATE_TAGNAME;
$keys = array();
$values = array();
$options = array();
$len = strlen($tagContent);
$done = false;
$idx = 0;
try{
while(!$done){
$char = $idx < $len ? $tagContent[$idx]:null;
switch($state){
case static::OPTION_STATE_TAGNAME:
switch($char){
case '=':
$state = static::OPTION_STATE_VALUE;
$tagName = $buffer;
$keys[] = $tagName;
$buffer = "";
break;
case ' ':
$state = static::OPTION_STATE_DEFAULT;
$tagName = $buffer;
$buffer = '';
$keys[] = $tagName;
break;
case null:
$tagName = $buffer;
$buffer = '';
$keys[] = $tagName;
break;
default:
$buffer .= $char;
}
break;
case static::OPTION_STATE_DEFAULT:
switch($char){
case ' ':
// do nothing
default:
$state = static::OPTION_STATE_KEY;
$buffer .= $char;
}
break;
case static::OPTION_STATE_VALUE:
switch($char){
case '"':
$state = static::OPTION_STATE_QUOTED_VALUE;
break;
case null: // intentional fall-through
case ' ': // key=value<space> delimits to next key
$values[] = $buffer;
$buffer = "";
$state = static::OPTION_STATE_KEY;
break;
case ":":
if($buffer=="javascript"){
$state = static::OPTION_STATE_JAVASCRIPT;
}
$buffer .= $char;
break;
default:
$buffer .= $char;
}
break;
case static::OPTION_STATE_JAVASCRIPT:
switch($char){
case ";":
$buffer .= $char;
$values[] = $buffer;
$buffer = "";
$state = static::OPTION_STATE_KEY;
break;
default:
$buffer .= $char;
}
break;
case static::OPTION_STATE_KEY:
switch($char){
case '=':
$state = static::OPTION_STATE_VALUE;
$keys[] = $buffer;
$buffer = '';
break;
case ' ': // ignore <space>key=value
break;
default:
$buffer .= $char;
break;
}
break;
case static::OPTION_STATE_QUOTED_VALUE:
switch($char){
case null:
case '"':
$state = static::OPTION_STATE_KEY;
$values[] = $buffer;
$buffer = '';
// peek ahead. If the next character is not a space or a closing brace, we have a bad tag and need to abort
if(isset($tagContent[$idx+1]) && $tagContent[$idx+1]!=" " && $tagContent[$idx+1]!="]" ){
throw new ParserException("Badly formed attribute: $tagContent");
}
break;
default:
$buffer .= $char;
break;
}
break;
default:
if(!empty($char)){
$state = static::OPTION_STATE_KEY;
}
}
if($idx >= $len){
$done = true;
}
$idx++;
}
if(count($keys) && count($values)){
if(count($keys)==(count($values)+1)){
array_unshift($values, "");
}
$options = array_combine($keys, $values);
}
}
catch(ParserException $e){
// if we're in this state, then something evidently went wrong. We'll consider everything that came after the tagname to be the attribute for that keyname
$options[$tagName]= substr($tagContent, strpos($tagContent, "=")+1);
}
return array($tagName, $options);
}
/**
* This is the next step in parsing a tag. It's possible for it to still be invalid at this
* point but many of the basic invalid tag name conditions have already been handled.
*
* @param ElementNode $parent the current parent element
* @param Tokenizer $tokenizer the tokenizer we're using
* @param string $tagContent the text between the [ and the ], assuming there is actually a ]
*
* @return ElementNode the new parent element
*/
protected function parseTag(ElementNode $parent, Tokenizer $tokenizer, $tagContent)
{
$next;
if (!$tokenizer->hasNext() || ($next = $tokenizer->next()) != ']') {
/* This is a malformed tag. Both the previous [ and the tagContent
* is really just plain text. */
$this->createTextNode($parent, '[');
$this->createTextNode($parent, $tagContent);
return $parent;
}
/* This is a well-formed tag consisting of [something] or [/something], but
* we still need to ensure that 'something' is a valid tag name. Additionally,
* if it's a closing tag, we need to ensure that there was a previous matching
* opening tag.
*/
/* There could be attributes. */
list($tmpTagName, $options) = $this->parseOptions($tagContent);
// $tagPieces = explode('=', $tagContent);
// $tmpTagName = $tagPieces[0];
$actualTagName;
if ('' != $tmpTagName && '/' == $tmpTagName[0]) {
/* This is a closing tag name. */
$actualTagName = substr($tmpTagName, 1);
} else {
$actualTagName = $tmpTagName;
}
if ('' != $tmpTagName && '/' == $tmpTagName[0]) {
/* This is attempting to close an open tag. We must verify that there exists an
* open tag of the same type and that there is no option (options on closing
* tags don't make any sense). */
$elToClose = $parent->closestParentOfType($actualTagName);
if (null == $elToClose || count($options) > 1) {
/* Closing an unopened tag or has an option. Treat everything as plain text. */
$this->createTextNode($parent, '[');
$this->createTextNode($parent, $tagContent);
$this->createTextNode($parent, ']');
return $parent;
} else {
/* We're closing $elToClose. In order to do that, we just need to return
* $elToClose's parent, since that will change our effective parent to be
* elToClose's parent. */
return $elToClose->getParent();
}
}
/* Verify that this is a known bbcode tag name. */
if ('' == $actualTagName || !$this->codeExists($actualTagName, !empty($options))) {
/* This is an invalid tag name! Treat everything we've seen as plain text. */
$this->createTextNode($parent, '[');
$this->createTextNode($parent, $tagContent);
$this->createTextNode($parent, ']');
return $parent;
}
/* If we're here, this is a valid opening tag. Let's make a new node for it. */
$el = new ElementNode();
$el->setNodeId(++$this->nextNodeid);
$code = $this->getCode($actualTagName, !empty($options));
$el->setCodeDefinition($code);
if (!empty($options)) {
/* We have an attribute we should save. */
$el->setAttribute($options);
}
$parent->addChild($el);
return $el;
}
/**
* Handles parsing elements whose CodeDefinitions disable parsing of element
* contents. This function uses a rolling window of 3 tokens until it finds the
* appropriate closing tag or reaches the end of the token stream.
*
* @param ElementNode $parent the current parent element
* @param Tokenizer $tokenizer the tokenizer we're using
*
* @return ElementNode the new parent element
*/
protected function parseAsTextUntilClose(ElementNode $parent, Tokenizer $tokenizer)
{
/* $parent's code definition doesn't allow its contents to be parsed. Here we use
* a sliding window of three tokens until we find [ /tagname ], signifying the
* end of the parent. */
if (!$tokenizer->hasNext()) {
return $parent;
}
$prevPrev = $tokenizer->next();
if (!$tokenizer->hasNext()) {
$this->createTextNode($parent, $prevPrev);
return $parent;
}
$prev = $tokenizer->next();
if (!$tokenizer->hasNext()) {
$this->createTextNode($parent, $prevPrev);
$this->createTextNode($parent, $prev);
return $parent;
}
$curr = $tokenizer->next();
while ('[' != $prevPrev || '/'.$parent->getTagName() != strtolower($prev) ||
']' != $curr) {
$this->createTextNode($parent, $prevPrev);
$prevPrev = $prev;
$prev = $curr;
if (!$tokenizer->hasNext()) {
$this->createTextNode($parent, $prevPrev);
$this->createTextNode($parent, $prev);
return $parent;
}
$curr = $tokenizer->next();
}
}
}

View file

@ -0,0 +1,7 @@
<?php
namespace JBBCode;
use Exception;
class ParserException extends Exception{
}

View file

@ -0,0 +1,102 @@
<?php
namespace JBBCode;
require_once 'Node.php';
/**
* Represents a piece of text data. TextNodes never have children.
*
* @author jbowens
*/
class TextNode extends Node
{
/* The value of this text node */
protected $value;
/**
* Constructs a text node from its text string
*
* @param string $val
*/
public function __construct($val)
{
$this->value = $val;
}
public function accept(NodeVisitor $visitor)
{
$visitor->visitTextNode($this);
}
/**
* (non-PHPdoc)
* @see JBBCode.Node::isTextNode()
*
* returns true
*/
public function isTextNode()
{
return true;
}
/**
* Returns the text string value of this text node.
*
* @return string
*/
public function getValue()
{
return $this->value;
}
/**
* (non-PHPdoc)
* @see JBBCode.Node::getAsText()
*
* Returns the text representation of this node.
*
* @return this node represented as text
*/
public function getAsText()
{
return $this->getValue();
}
/**
* (non-PHPdoc)
* @see JBBCode.Node::getAsBBCode()
*
* Returns the bbcode representation of this node. (Just its value)
*
* @return this node represented as bbcode
*/
public function getAsBBCode()
{
return $this->getValue();
}
/**
* (non-PHPdoc)
* @see JBBCode.Node::getAsHTML()
*
* Returns the html representation of this node. (Just its value)
*
* @return this node represented as HTML
*/
public function getAsHTML()
{
return $this->getValue();
}
/**
* Edits the text value contained within this text node.
*
* @param newValue the new text value of the text node
*/
public function setValue($newValue)
{
$this->value = $newValue;
}
}

View file

@ -0,0 +1,105 @@
<?php
namespace JBBCode;
/**
* This Tokenizer is used while constructing the parse tree. The tokenizer
* handles splitting the input into brackets and miscellaneous text. The
* parser is then built as a FSM ontop of these possible inputs.
*
* @author jbowens
*/
class Tokenizer
{
protected $tokens = array();
protected $i = -1;
/**
* Constructs a tokenizer from the given string. The string will be tokenized
* upon construction.
*
* @param $str the string to tokenize
*/
public function __construct($str)
{
$strStart = 0;
for ($index = 0; $index < strlen($str); ++$index) {
if (']' == $str[$index] || '[' == $str[$index]) {
/* Are there characters in the buffer from a previous string? */
if ($strStart < $index) {
array_push($this->tokens, substr($str, $strStart, $index - $strStart));
$strStart = $index;
}
/* Add the [ or ] to the tokens array. */
array_push($this->tokens, $str[$index]);
$strStart = $index+1;
}
}
if ($strStart < strlen($str)) {
/* There are still characters in the buffer. Add them to the tokens. */
array_push($this->tokens, substr($str, $strStart, strlen($str) - $strStart));
}
}
/**
* Returns true if there is another token in the token stream.
*/
public function hasNext()
{
return count($this->tokens) > 1 + $this->i;
}
/**
* Advances the token stream to the next token and returns the new token.
*/
public function next()
{
if (!$this->hasNext()) {
return null;
} else {
return $this->tokens[++$this->i];
}
}
/**
* Retrieves the current token.
*/
public function current()
{
if ($this->i < 0) {
return null;
} else {
return $this->tokens[$this->i];
}
}
/**
* Moves the token stream back a token.
*/
public function stepBack()
{
if ($this->i > -1) {
$this->i--;
}
}
/**
* Restarts the tokenizer, returning to the beginning of the token stream.
*/
public function restart()
{
$this->i = -1;
}
/**
* toString method that returns the entire string from the current index on.
*/
public function toString()
{
return implode('', array_slice($this->tokens, $this->i + 1));
}
}

View file

@ -0,0 +1,12 @@
<?php
require_once "/path/to/jbbcode/Parser.php";
$parser = new JBBCode\Parser();
$parser->addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
$text = "The default codes include: [b]bold[/b], [i]italics[/i], [u]underlining[/u], ";
$text .= "[url=http://jbbcode.com]links[/url], [color=red]color![/color] and more.";
$parser->parse($text);
print $parser->getAsHtml();

View file

@ -0,0 +1,10 @@
<?php
require_once "/path/to/jbbcode/Parser.php";
$parser = new JBBCode\Parser();
$parser->addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
$text = "The bbcode in here [b]is never closed!";
$parser->parse($text);
print $parser->getAsBBCode();

View file

@ -0,0 +1,11 @@
<?php
require_once "/path/to/jbbcode/Parser.php";
$parser = new JBBCode\Parser();
$parser->addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
$text = "[b][u]There is [i]a lot[/i] of [url=http://en.wikipedia.org/wiki/Markup_language]markup[/url] in this";
$text .= "[color=#333333]text[/color]![/u][/b]";
$parser->parse($text);
print $parser->getAsText();

View file

@ -0,0 +1,7 @@
<?php
require_once "/path/to/jbbcode/Parser.php";
$parser = new JBBCode\Parser();
$parser->addBBCode("quote", '<div class="quote">{param}</div>');
$parser->addBBCode("code", '<pre class="code">{param}</pre>', false, false, 1);

View file

@ -0,0 +1,22 @@
<?php
require_once("../Parser.php");
require_once("../visitors/SmileyVisitor.php");
error_reporting(E_ALL);
$parser = new JBBCode\Parser();
$parser->addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
if (count($argv) < 2) {
die("Usage: " . $argv[0] . " \"bbcode string\"\n");
}
$inputText = $argv[1];
$parser->parse($inputText);
$smileyVisitor = new \JBBCode\visitors\SmileyVisitor();
$parser->accept($smileyVisitor);
echo $parser->getAsHTML() . "\n";

View file

@ -0,0 +1,23 @@
<?php
require_once("../Parser.php");
require_once("../visitors/TagCountingVisitor.php");
error_reporting(E_ALL);
$parser = new JBBCode\Parser();
$parser->addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
if (count($argv) < 3) {
die("Usage: " . $argv[0] . " \"bbcode string\" <tag name to check>\n");
}
$inputText = $argv[1];
$tagName = $argv[2];
$parser->parse($inputText);
$tagCountingVisitor = new \JBBCode\visitors\TagCountingVisitor();
$parser->accept($tagCountingVisitor);
echo $tagCountingVisitor->getFrequency($tagName) . "\n";

View file

@ -0,0 +1,85 @@
<?php
require_once(dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'Parser.php');
/**
* Test cases testing the functionality of parsing bbcode and
* retrieving a bbcode well-formed bbcode representation.
*
* @author jbowens
*/
class BBCodeToBBCodeTest extends PHPUnit_Framework_TestCase
{
/**
* A utility method for these tests that will evaluate its arguments as bbcode with
* a fresh parser loaded with only the default bbcodes. It returns the
* bbcode output, which in most cases should be in the input itself.
*/
private function defaultBBCodeParse($bbcode)
{
$parser = new JBBCode\Parser();
$parser->addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
$parser->parse($bbcode);
return $parser->getAsBBCode();
}
/**
* Asserts that the given bbcode matches the given text when
* the bbcode is run through defaultBBCodeParse
*/
private function assertBBCodeOutput($bbcode, $text)
{
$this->assertEquals($this->defaultBBCodeParse($bbcode), $text);
}
public function testEmptyString()
{
$this->assertBBCodeOutput('', '');
}
public function testOneTag()
{
$this->assertBBCodeOutput('[b]this is bold[/b]', '[b]this is bold[/b]');
}
public function testOneTagWithSurroundingText()
{
$this->assertBBCodeOutput('buffer text [b]this is bold[/b] buffer text',
'buffer text [b]this is bold[/b] buffer text');
}
public function testMultipleTags()
{
$bbcode = 'this is some text with [b]bold tags[/b] and [i]italics[/i] and ' .
'things like [u]that[/u].';
$bbcodeOutput = 'this is some text with [b]bold tags[/b] and [i]italics[/i] and ' .
'things like [u]that[/u].';
$this->assertBBCodeOutput($bbcode, $bbcodeOutput);
}
public function testCodeOptions()
{
$code = 'This contains a [url=http://jbbcode.com]url[/url] which uses an option.';
$codeOutput = 'This contains a [url=http://jbbcode.com]url[/url] which uses an option.';
$this->assertBBCodeOutput($code, $codeOutput);
}
/**
* @depends testCodeOptions
*/
public function testOmittedOption()
{
$code = 'This doesn\'t use the url option [url]http://jbbcode.com[/url].';
$codeOutput = 'This doesn\'t use the url option [url]http://jbbcode.com[/url].';
$this->assertBBCodeOutput($code, $codeOutput);
}
public function testUnclosedTags()
{
$code = '[b]bold';
$codeOutput = '[b]bold[/b]';
$this->assertBBCodeOutput($code, $codeOutput);
}
}

View file

@ -0,0 +1,78 @@
<?php
require_once(dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'Parser.php');
/**
* Test cases testing the ability to parse bbcode and retrieve a
* plain text representation without any markup.
*
* @author jbowens
*/
class BBCodeToTextTest extends PHPUnit_Framework_TestCase
{
/**
* A utility method for these tests that will evaluate
* its arguments as bbcode with a fresh parser loaded
* with only the default bbcodes. It returns the
* text output.
*/
private function defaultTextParse($bbcode)
{
$parser = new JBBCode\Parser();
$parser->addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
$parser->parse($bbcode);
return $parser->getAsText();
}
/**
* Asserts that the given bbcode matches the given text when
* the bbcode is run through defaultTextParse
*/
private function assertTextOutput($bbcode, $text)
{
$this->assertEquals($text, $this->defaultTextParse($bbcode));
}
public function testEmptyString()
{
$this->assertTextOutput('', '');
}
public function testOneTag()
{
$this->assertTextOutput('[b]this is bold[/b]', 'this is bold');
}
public function testOneTagWithSurroundingText()
{
$this->assertTextOutput('buffer text [b]this is bold[/b] buffer text',
'buffer text this is bold buffer text');
}
public function testMultipleTags()
{
$bbcode = 'this is some text with [b]bold tags[/b] and [i]italics[/i] and ' .
'things like [u]that[/u].';
$text = 'this is some text with bold tags and italics and things like that.';
$this->assertTextOutput($bbcode, $text);
}
public function testCodeOptions()
{
$code = 'This contains a [url=http://jbbcode.com]url[/url] which uses an option.';
$text = 'This contains a url which uses an option.';
$this->assertTextOutput($code, $text);
}
/**
* @depends testCodeOptions
*/
public function testOmittedOption()
{
$code = 'This doesn\'t use the url option [url]http://jbbcode.com[/url].';
$text = 'This doesn\'t use the url option http://jbbcode.com.';
$this->assertTextOutput($code, $text);
}
}

View file

@ -0,0 +1,54 @@
<?php
require_once dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'Parser.php';
/**
* Test cases for the default bbcode set.
*
* @author jbowens
* @since May 2013
*/
class DefaultCodesTest extends PHPUnit_Framework_TestCase
{
/**
* Asserts that the given bbcode string produces the given html string
* when parsed with the default bbcodes.
*/
public function assertProduces($bbcode, $html)
{
$parser = new \JBBCode\Parser();
$parser->addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
$parser->parse($bbcode);
$this->assertEquals($html, $parser->getAsHtml());
}
/**
* Tests the [b] bbcode.
*/
public function testBold()
{
$this->assertProduces('[b]this should be bold[/b]', '<strong>this should be bold</strong>');
}
/**
* Tests the [color] bbcode.
*/
public function testColor()
{
$this->assertProduces('[color=red]red[/color]', '<span style="color: red">red</span>');
}
/**
* Tests the example from the documentation.
*/
public function testExample()
{
$text = "The default codes include: [b]bold[/b], [i]italics[/i], [u]underlining[/u], ";
$text .= "[url=http://jbbcode.com]links[/url], [color=red]color![/color] and more.";
$html = 'The default codes include: <strong>bold</strong>, <em>italics</em>, <u>underlining</u>, ';
$html .= '<a href="http://jbbcode.com">links</a>, <span style="color: red">color!</span> and more.';
$this->assertProduces($text, $html);
}
}

View file

@ -0,0 +1,77 @@
<?php
require_once(dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'Parser.php');
/**
* Test cases testing the HTMLSafe visitor, which escapes all html characters in the source text
*
* @author astax-t
*/
class HTMLSafeTest extends PHPUnit_Framework_TestCase
{
/**
* Asserts that the given bbcode string produces the given html string
* when parsed with the default bbcodes.
*/
public function assertProduces($bbcode, $html)
{
$parser = new \JBBCode\Parser();
$parser->addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
$parser->parse($bbcode);
$htmlsafer = new JBBCode\visitors\HTMLSafeVisitor();
$parser->accept($htmlsafer);
$this->assertEquals($html, $parser->getAsHtml());
}
/**
* Tests escaping quotes and ampersands in simple text
*/
public function testQuoteAndAmp()
{
$this->assertProduces('te"xt te&xt', 'te&quot;xt te&amp;xt');
}
/**
* Tests escaping quotes and ampersands inside a BBCode tag
*/
public function testQuoteAndAmpInTag()
{
$this->assertProduces('[b]te"xt te&xt[/b]', '<strong>te&quot;xt te&amp;xt</strong>');
}
/**
* Tests escaping HTML tags
*/
public function testHtmlTag()
{
$this->assertProduces('<b>not bold</b>', '&lt;b&gt;not bold&lt;/b&gt;');
$this->assertProduces('[b]<b>bold</b>[/b] <hr>', '<strong>&lt;b&gt;bold&lt;/b&gt;</strong> &lt;hr&gt;');
}
/**
* Tests escaping ampersands in URL using [url]...[/url]
*/
public function testUrlParam()
{
$this->assertProduces('text [url]http://example.com/?a=b&c=d[/url] more text', 'text <a href="http://example.com/?a=b&amp;c=d">http://example.com/?a=b&amp;c=d</a> more text');
}
/**
* Tests escaping ampersands in URL using [url=...] tag
*/
public function testUrlOption()
{
$this->assertProduces('text [url=http://example.com/?a=b&c=d]this is a "link"[/url]', 'text <a href="http://example.com/?a=b&amp;c=d">this is a &quot;link&quot;</a>');
}
/**
* Tests escaping ampersands in URL using [url=...] tag when URL is in quotes
*/
public function testUrlOptionQuotes()
{
$this->assertProduces('text [url="http://example.com/?a=b&c=d"]this is a "link"[/url]', 'text <a href="http://example.com/?a=b&amp;c=d">this is a &quot;link&quot;</a>');
}
}

View file

@ -0,0 +1,46 @@
<?php
require_once dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'Parser.php';
/**
* Test cases for CodeDefinition nest limits. If an element is nested beyond
* its CodeDefinition's nest limit, it should be removed from the parse tree.
*
* @author jbowens
* @since May 2013
*/
class NestLimitTest extends PHPUnit_Framework_TestCase
{
/**
* Tests that when elements have no nest limits they may be
* nested indefinitely.
*/
public function testIndefiniteNesting()
{
$parser = new JBBCode\Parser();
$parser->addBBCode('b', '<strong>{param}</strong>', false, true, -1);
$parser->parse('[b][b][b][b][b][b][b][b]bold text[/b][/b][/b][/b][/b][/b][/b][/b]');
$this->assertEquals('<strong><strong><strong><strong><strong><strong><strong><strong>' .
'bold text' .
'</strong></strong></strong></strong></strong></strong></strong></strong>',
$parser->getAsHtml());
}
/**
* Test over nesting.
*/
public function testOverNesting()
{
$parser = new JBBCode\Parser();
$parser->addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
$parser->addBBCode('quote', '<blockquote>{param}</blockquote>', false, true, 2);
$bbcode = '[quote][quote][quote]wut[/quote] huh?[/quote] i don\'t know[/quote]';
$parser->parse($bbcode);
$expectedBbcode = '[quote][quote] huh?[/quote] i don\'t know[/quote]';
$expectedHtml = '<blockquote><blockquote> huh?</blockquote> i don\'t know</blockquote>';
$this->assertEquals($expectedBbcode, $parser->getAsBBCode());
$this->assertEquals($expectedHtml, $parser->getAsHtml());
}
}

View file

@ -0,0 +1,97 @@
<?php
require_once dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'Parser.php';
/**
* Test cases for the code definition parameter that disallows parsing
* of an element's content.
*
* @author jbowens
*/
class ParseContentTest extends PHPUnit_Framework_TestCase
{
/**
* Tests that when a bbcode is created with parseContent = false,
* its contents actually are not parsed.
*/
public function testSimpleNoParsing()
{
$parser = new JBBCode\Parser();
$parser->addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
$parser->addBBCode('verbatim', '{param}', false, false);
$parser->parse('[verbatim]plain text[/verbatim]');
$this->assertEquals('plain text', $parser->getAsHtml());
$parser->parse('[verbatim][b]bold[/b][/verbatim]');
$this->assertEquals('[b]bold[/b]', $parser->getAsHtml());
}
public function testNoParsingWithBufferText()
{
$parser = new JBBCode\Parser();
$parser->addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
$parser->addBBCode('verbatim', '{param}', false, false);
$parser->parse('buffer text[verbatim]buffer text[b]bold[/b]buffer text[/verbatim]buffer text');
$this->assertEquals('buffer textbuffer text[b]bold[/b]buffer textbuffer text', $parser->getAsHtml());
}
/**
* Tests that when a tag is not closed within an unparseable tag,
* the BBCode output does not automatically close that tag (because
* the contents were not parsed).
*/
public function testUnclosedTag()
{
$parser = new JBBCode\Parser();
$parser->addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
$parser->addBBCode('verbatim', '{param}', false, false);
$parser->parse('[verbatim]i wonder [b]what will happen[/verbatim]');
$this->assertEquals('i wonder [b]what will happen', $parser->getAsHtml());
$this->assertEquals('[verbatim]i wonder [b]what will happen[/verbatim]', $parser->getAsBBCode());
}
/**
* Tests that an unclosed tag with parseContent = false ends cleanly.
*/
public function testUnclosedVerbatimTag()
{
$parser = new JBBCode\Parser();
$parser->addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
$parser->addBBCode('verbatim', '{param}', false, false);
$parser->parse('[verbatim]yo this [b]text should not be bold[/b]');
$this->assertEquals('yo this [b]text should not be bold[/b]', $parser->getAsHtml());
}
/**
* Tests a malformed closing tag for a verbatim block.
*/
public function testMalformedVerbatimClosingTag()
{
$parser = new JBBCode\Parser();
$parser->addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
$parser->addBBCode('verbatim', '{param}', false, false);
$parser->parse('[verbatim]yo this [b]text should not be bold[/b][/verbatim');
$this->assertEquals('yo this [b]text should not be bold[/b][/verbatim', $parser->getAsHtml());
}
/**
* Tests an immediate end after a verbatim.
*/
public function testVerbatimThenEof()
{
$parser = new JBBCode\Parser();
$parser->addBBCode('verbatim', '{param}', false, false);
$parser->parse('[verbatim]');
$this->assertEquals('', $parser->getAsHtml());
}
}

View file

@ -0,0 +1,130 @@
<?php
require_once(dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'Parser.php');
/**
* A series of test cases for various potential parsing edge cases. This
* includes a lot of tests using brackets for things besides genuine tag
* names.
*
* @author jbowens
*
*/
class ParsingEdgeCaseTest extends PHPUnit_Framework_TestCase
{
/**
* A utility method for these tests that will evaluate
* its arguments as bbcode with a fresh parser loaded
* with only the default bbcodes. It returns the
* html output.
*/
private function defaultParse($bbcode)
{
$parser = new JBBCode\Parser();
$parser->addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
$parser->parse($bbcode);
return $parser->getAsHtml();
}
/**
* Asserts that the given bbcode matches the given html when
* the bbcode is run through defaultParse.
*/
private function assertProduces($bbcode, $html)
{
$this->assertEquals($html, $this->defaultParse($bbcode));
}
/**
* Tests attempting to use a code that doesn't exist.
*/
public function testNonexistentCodeMalformed()
{
$this->assertProduces('[wat]', '[wat]');
}
/**
* Tests attempting to use a code that doesn't exist, but this
* time in a well-formed fashion.
*
* @depends testNonexistentCodeMalformed
*/
public function testNonexistentCodeWellformed()
{
$this->assertProduces('[wat]something[/wat]', '[wat]something[/wat]');
}
/**
* Tests a whole bunch of meaningless left brackets.
*/
public function testAllLeftBrackets()
{
$this->assertProduces('[[[[[[[[', '[[[[[[[[');
}
/**
* Tests a whole bunch of meaningless right brackets.
*/
public function testAllRightBrackets()
{
$this->assertProduces(']]]]]', ']]]]]');
}
/**
* Intermixes well-formed, meaningful tags with meaningless brackets.
*/
public function testRandomBracketsInWellformedCode()
{
$this->assertProduces('[b][[][[i]heh[/i][/b]',
'<strong>[[][<em>heh</em></strong>');
}
/**
* Tests an unclosed tag within a closed tag.
*/
public function testUnclosedWithinClosed()
{
$this->assertProduces('[url=http://jbbcode.com][b]oh yeah[/url]',
'<a href="http://jbbcode.com"><strong>oh yeah</strong></a>');
}
/**
* Tests half completed opening tag.
*/
public function testHalfOpenTag()
{
$this->assertProduces('[b', '[b');
$this->assertProduces('wut [url=http://jbbcode.com',
'wut [url=http://jbbcode.com');
}
/**
* Tests half completed closing tag.
*/
public function testHalfClosingTag()
{
$this->assertProduces('[b]this should be bold[/b',
'<strong>this should be bold[/b</strong>');
}
/**
* Tests lots of left brackets before the actual tag. For example:
* [[[[[[[[b]bold![/b]
*/
public function testLeftBracketsThenTag()
{
$this->assertProduces('[[[[[b]bold![/b]',
'[[[[<strong>bold!</strong>');
}
/**
* Tests a whitespace after left bracket.
*/
public function testWhitespaceAfterLeftBracketWhithoutTag()
{
$this->assertProduces('[ ABC ] ',
'[ ABC ] ');
}
}

View file

@ -0,0 +1,131 @@
<?php
require_once(dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'Parser.php');
class SimpleEvaluationTest extends PHPUnit_Framework_TestCase
{
/**
* A utility method for these tests that will evaluate
* its arguments as bbcode with a fresh parser loaded
* with only the default bbcodes. It returns the
* html output.
*/
private function defaultParse($bbcode)
{
$parser = new JBBCode\Parser();
$parser->addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
$parser->parse($bbcode);
return $parser->getAsHtml();
}
/**
* Asserts that the given bbcode matches the given html when
* the bbcode is run through defaultParse.
*/
private function assertProduces($bbcode, $html)
{
$this->assertEquals($html, $this->defaultParse($bbcode));
}
public function testEmptyString()
{
$this->assertProduces('', '');
}
public function testOneTag()
{
$this->assertProduces('[b]this is bold[/b]', '<strong>this is bold</strong>');
}
public function testOneTagWithSurroundingText()
{
$this->assertProduces('buffer text [b]this is bold[/b] buffer text',
'buffer text <strong>this is bold</strong> buffer text');
}
public function testMultipleTags()
{
$bbcode = <<<EOD
this is some text with [b]bold tags[/b] and [i]italics[/i] and
things like [u]that[/u].
EOD;
$html = <<<EOD
this is some text with <strong>bold tags</strong> and <em>italics</em> and
things like <u>that</u>.
EOD;
$this->assertProduces($bbcode, $html);
}
public function testCodeOptions()
{
$code = 'This contains a [url=http://jbbcode.com/?b=2]url[/url] which uses an option.';
$html = 'This contains a <a href="http://jbbcode.com/?b=2">url</a> which uses an option.';
$this->assertProduces($code, $html);
}
public function testAttributes()
{
$parser = new JBBCode\Parser();
$builder = new JBBCode\CodeDefinitionBuilder('img', '<img src="{param}" height="{height}" alt="{alt}" />');
$parser->addCodeDefinition($builder->setUseOption(true)->setParseContent(false)->build());
$expected = 'Multiple <img src="http://jbbcode.com/img.png" height="50" alt="alt text" /> options.';
$code = 'Multiple [img height="50" alt="alt text"]http://jbbcode.com/img.png[/img] options.';
$parser->parse($code);
$result = $parser->getAsHTML();
$this->assertEquals($expected, $result);
$code = 'Multiple [img height=50 alt="alt text"]http://jbbcode.com/img.png[/img] options.';
$parser->parse($code);
$result = $parser->getAsHTML();
$this->assertEquals($expected, $result);
}
/**
* @depends testCodeOptions
*/
public function testOmittedOption()
{
$code = 'This doesn\'t use the url option [url]http://jbbcode.com[/url].';
$html = 'This doesn\'t use the url option <a href="http://jbbcode.com">http://jbbcode.com</a>.';
$this->assertProduces($code, $html);
}
public function testUnclosedTag()
{
$code = 'hello [b]world';
$html = 'hello <strong>world</strong>';
$this->assertProduces($code, $html);
}
public function testNestingTags()
{
$code = '[url=http://jbbcode.com][b]hello [u]world[/u][/b][/url]';
$html = '<a href="http://jbbcode.com"><strong>hello <u>world</u></strong></a>';
$this->assertProduces($code, $html);
}
public function testBracketInTag()
{
$this->assertProduces('[b]:-[[/b]', '<strong>:-[</strong>');
}
public function testBracketWithSpaceInTag()
{
$this->assertProduces('[b]:-[ [/b]', '<strong>:-[ </strong>');
}
public function testBracketWithTextInTag()
{
$this->assertProduces('[b]:-[ foobar[/b]', '<strong>:-[ foobar</strong>');
}
public function testMultibleBracketsWithTextInTag()
{
$this->assertProduces('[b]:-[ [fo[o[bar[/b]', '<strong>:-[ [fo[o[bar</strong>');
}
}

View file

@ -0,0 +1,74 @@
<?php
require_once(dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'Tokenizer.php');
/**
* Test cases testing the functionality of the Tokenizer. The tokenizer
* is used by the parser to make parsing simpler.
*
* @author jbowens
*/
class TokenizerTest extends PHPUnit_Framework_TestCase
{
public function testEmptyString()
{
$tokenizer = new JBBCode\Tokenizer('');
$this->assertFalse($tokenizer->hasNext());
}
public function testPlainTextOnly()
{
$tokenizer = new JBBCode\Tokenizer('this is some plain text.');
$this->assertEquals('this is some plain text.', $tokenizer->next());
$this->assertEquals('this is some plain text.', $tokenizer->current());
$this->assertFalse($tokenizer->hasNext());
}
public function testStartingBracket()
{
$tokenizer = new JBBCode\Tokenizer('[this has a starting bracket.');
$this->assertEquals('[', $tokenizer->next());
$this->assertEquals('[', $tokenizer->current());
$this->assertEquals('this has a starting bracket.', $tokenizer->next());
$this->assertEquals('this has a starting bracket.', $tokenizer->current());
$this->assertFalse($tokenizer->hasNext());
$this->assertEquals(null, $tokenizer->next());
}
public function testOneTag()
{
$tokenizer = new JBBCode\Tokenizer('[b]');
$this->assertEquals('[', $tokenizer->next());
$this->assertEquals('b', $tokenizer->next());
$this->assertEquals(']', $tokenizer->next());
$this->assertFalse($tokenizer->hasNext());
}
public function testMatchingTags()
{
$tokenizer = new JBBCode\Tokenizer('[url]http://jbbcode.com[/url]');
$this->assertEquals('[', $tokenizer->next());
$this->assertEquals('url', $tokenizer->next());
$this->assertEquals(']', $tokenizer->next());
$this->assertEquals('http://jbbcode.com', $tokenizer->next());
$this->assertEquals('[', $tokenizer->next());
$this->assertEquals('/url', $tokenizer->next());
$this->assertEquals(']', $tokenizer->next());
$this->assertFalse($tokenizer->hasNext());
}
public function testLotsOfBrackets()
{
$tokenizer = new JBBCode\Tokenizer('[[][]][');
$this->assertEquals('[', $tokenizer->next());
$this->assertEquals('[', $tokenizer->next());
$this->assertEquals(']', $tokenizer->next());
$this->assertEquals('[', $tokenizer->next());
$this->assertEquals(']', $tokenizer->next());
$this->assertEquals(']', $tokenizer->next());
$this->assertEquals('[', $tokenizer->next());
$this->assertFalse($tokenizer->hasNext());
}
}

View file

@ -0,0 +1,151 @@
<?php
require_once dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'Parser.php';
require_once dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'validators' . DIRECTORY_SEPARATOR . 'UrlValidator.php';
require_once dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'validators' . DIRECTORY_SEPARATOR . 'CssColorValidator.php';
/**
* Test cases for InputValidators.
*
* @author jbowens
* @since May 2013
*/
class ValidatorTest extends PHPUnit_Framework_TestCase
{
/**
* Tests an invalid url directly on the UrlValidator.
*/
public function testInvalidUrl()
{
$urlValidator = new \JBBCode\validators\UrlValidator();
$this->assertFalse($urlValidator->validate('#yolo#swag'));
$this->assertFalse($urlValidator->validate('giehtiehwtaw352353%3'));
}
/**
* Tests a valid url directly on the UrlValidator.
*/
public function testValidUrl()
{
$urlValidator = new \JBBCode\validators\UrlValidator();
$this->assertTrue($urlValidator->validate('http://google.com'));
$this->assertTrue($urlValidator->validate('http://jbbcode.com/docs'));
$this->assertTrue($urlValidator->validate('https://www.maps.google.com'));
}
/**
* Tests an invalid url as an option to a url bbcode.
*
* @depends testInvalidUrl
*/
public function testInvalidOptionUrlBBCode()
{
$parser = new JBBCode\Parser();
$parser->addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
$parser->parse('[url=javascript:alert("HACKED!");]click me[/url]');
$this->assertEquals('[url=javascript:alert("HACKED!");]click me[/url]',
$parser->getAsHtml());
}
/**
* Tests an invalid url as the body to a url bbcode.
*
* @depends testInvalidUrl
*/
public function testInvalidBodyUrlBBCode()
{
$parser = new JBBCode\Parser();
$parser->addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
$parser->parse('[url]javascript:alert("HACKED!");[/url]');
$this->assertEquals('[url]javascript:alert("HACKED!");[/url]', $parser->getAsHtml());
}
/**
* Tests a valid url as the body to a url bbcode.
*
* @depends testValidUrl
*/
public function testValidUrlBBCode()
{
$parser = new JBBCode\Parser();
$parser->addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
$parser->parse('[url]http://jbbcode.com[/url]');
$this->assertEquals('<a href="http://jbbcode.com">http://jbbcode.com</a>',
$parser->getAsHtml());
}
/**
* Tests valid english CSS color descriptions on the CssColorValidator.
*/
public function testCssColorEnglish()
{
$colorValidator = new JBBCode\validators\CssColorValidator();
$this->assertTrue($colorValidator->validate('red'));
$this->assertTrue($colorValidator->validate('yellow'));
$this->assertTrue($colorValidator->validate('LightGoldenRodYellow'));
}
/**
* Tests valid hexadecimal CSS color values on the CssColorValidator.
*/
public function testCssColorHex()
{
$colorValidator = new JBBCode\validators\CssColorValidator();
$this->assertTrue($colorValidator->validate('#000'));
$this->assertTrue($colorValidator->validate('#ff0000'));
$this->assertTrue($colorValidator->validate('#aaaaaa'));
}
/**
* Tests valid rgba CSS color values on the CssColorValidator.
*/
public function testCssColorRgba()
{
$colorValidator = new JBBCode\validators\CssColorValidator();
$this->assertTrue($colorValidator->validate('rgba(255, 0, 0, 0.5)'));
$this->assertTrue($colorValidator->validate('rgba(50, 50, 50, 0.0)'));
}
/**
* Tests invalid CSS color values on the CssColorValidator.
*/
public function testInvalidCssColor()
{
$colorValidator = new JBBCode\validators\CssColorValidator();
$this->assertFalse($colorValidator->validate('" onclick="javascript: alert(\"gotcha!\");'));
$this->assertFalse($colorValidator->validate('"><marquee scrollamount="100'));
}
/**
* Tests valid css colors in a color bbcode.
*
* @depends testCssColorEnglish
* @depends testCssColorHex
*/
public function testValidColorBBCode()
{
$parser = new JBBCode\Parser();
$parser->addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
$parser->parse('[color=red]colorful text[/color]');
$this->assertEquals('<span style="color: red">colorful text</span>',
$parser->getAsHtml());
$parser->parse('[color=#00ff00]green[/color]');
$this->assertEquals('<span style="color: #00ff00">green</span>', $parser->getAsHtml());
}
/**
* Tests invalid css colors in a color bbcode.
*
* @depends testInvalidCssColor
*/
public function testInvalidColorBBCode()
{
$parser = new JBBCode\Parser();
$parser->addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
$parser->parse('[color=" onclick="alert(\'hey ya!\');]click me[/color]');
$this->assertEquals('[color=" onclick="alert(\'hey ya!\');]click me[/color]',
$parser->getAsHtml());
}
}

View file

@ -0,0 +1,30 @@
<?php
namespace JBBCode\validators;
require_once dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'InputValidator.php';
/**
* An InputValidator for CSS color values. This is a very rudimentary
* validator. It will allow a lot of color values that are invalid. However,
* it shouldn't allow any invalid color values that are also a security
* concern.
*
* @author jbowens
* @since May 2013
*/
class CssColorValidator implements \JBBCode\InputValidator
{
/**
* Returns true if $input uses only valid CSS color value
* characters.
*
* @param $input the string to validate
*/
public function validate($input)
{
return (bool) preg_match('/^[A-z0-9\-#., ()%]+$/', $input);
}
}

View file

@ -0,0 +1,27 @@
<?php
namespace JBBCode\validators;
require_once dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'InputValidator.php';
/**
* An InputValidator for urls. This can be used to make [url] bbcodes secure.
*
* @author jbowens
* @since May 2013
*/
class UrlValidator implements \JBBCode\InputValidator
{
/**
* Returns true iff $input is a valid url.
*
* @param $input the string to validate
*/
public function validate($input)
{
$valid = filter_var($input, FILTER_VALIDATE_URL);
return !!$valid;
}
}

View file

@ -0,0 +1,52 @@
<?php
namespace JBBCode\visitors;
/**
* This visitor escapes html content of all strings and attributes
*
* @author Alexander Polyanskikh
*/
class HTMLSafeVisitor implements \JBBCode\NodeVisitor
{
public function visitDocumentElement(\JBBCode\DocumentElement $documentElement)
{
foreach ($documentElement->getChildren() as $child) {
$child->accept($this);
}
}
public function visitTextNode(\JBBCode\TextNode $textNode)
{
$textNode->setValue($this->htmlSafe($textNode->getValue()));
}
public function visitElementNode(\JBBCode\ElementNode $elementNode)
{
$attrs = $elementNode->getAttribute();
if (is_array($attrs))
{
foreach ($attrs as &$el)
$el = $this->htmlSafe($el);
$elementNode->setAttribute($attrs);
}
foreach ($elementNode->getChildren() as $child) {
$child->accept($this);
}
}
protected function htmlSafe($str, $options = null)
{
if (is_null($options))
{
if (defined('ENT_DISALLOWED'))
$options = ENT_QUOTES | ENT_DISALLOWED | ENT_HTML401; // PHP 5.4+
else
$options = ENT_QUOTES; // PHP 5.3
}
return htmlspecialchars($str, $options, 'UTF-8');
}
}

View file

@ -0,0 +1,65 @@
<?php
namespace JBBCode\visitors;
require_once dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'CodeDefinition.php';
require_once dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'DocumentElement.php';
require_once dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'ElementNode.php';
require_once dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'NodeVisitor.php';
require_once dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'TextNode.php';
/**
* This visitor is used by the jBBCode core to enforce nest limits after
* parsing. It traverses the parse graph depth first, removing any subtrees
* that are nested deeper than an element's code definition allows.
*
* @author jbowens
* @since May 2013
*/
class NestLimitVisitor implements \JBBCode\NodeVisitor
{
/* A map from tag name to current depth. */
protected $depth = array();
public function visitDocumentElement(\JBBCode\DocumentElement $documentElement)
{
foreach($documentElement->getChildren() as $child) {
$child->accept($this);
}
}
public function visitTextNode(\JBBCode\TextNode $textNode)
{
/* Nothing to do. Text nodes don't have tag names or children. */
}
public function visitElementNode(\JBBCode\ElementNode $elementNode)
{
$tagName = strtolower($elementNode->getTagName());
/* Update the current depth for this tag name. */
if (isset($this->depth[$tagName])) {
$this->depth[$tagName]++;
} else {
$this->depth[$tagName] = 1;
}
/* Check if $elementNode is nested too deeply. */
if ($elementNode->getCodeDefinition()->getNestLimit() != -1 &&
$elementNode->getCodeDefinition()->getNestLimit() < $this->depth[$tagName]) {
/* This element is nested too deeply. We need to remove it and not visit any
* of its children. */
$elementNode->getParent()->removeChild($elementNode);
} else {
/* This element is not nested too deeply. Visit all of its children. */
foreach ($elementNode->getChildren() as $child) {
$child->accept($this);
}
}
/* Now that we're done visiting this node, decrement the depth. */
$this->depth[$tagName]--;
}
}

View file

@ -0,0 +1,42 @@
<?php
namespace JBBCode\visitors;
/**
* This visitor is an example of how to implement smiley parsing on the JBBCode
* parse graph. It converts :) into image tags pointing to /smiley.png.
*
* @author jbowens
* @since April 2013
*/
class SmileyVisitor implements \JBBCode\NodeVisitor
{
function visitDocumentElement(\JBBCode\DocumentElement $documentElement)
{
foreach($documentElement->getChildren() as $child) {
$child->accept($this);
}
}
function visitTextNode(\JBBCode\TextNode $textNode)
{
/* Convert :) into an image tag. */
$textNode->setValue(str_replace(':)',
'<img src="/smiley.png" alt=":)" />',
$textNode->getValue()));
}
function visitElementNode(\JBBCode\ElementNode $elementNode)
{
/* We only want to visit text nodes within elements if the element's
* code definition allows for its content to be parsed.
*/
if ($elementNode->getCodeDefinition()->parseContent()) {
foreach ($elementNode->getChildren() as $child) {
$child->accept($this);
}
}
}
}

View file

@ -0,0 +1,60 @@
<?php
namespace JBBCode\visitors;
/**
* This visitor traverses parse graph, counting the number of times each
* tag name occurs.
*
* @author jbowens
* @since January 2013
*/
class TagCountingVisitor implements \JBBcode\NodeVisitor
{
protected $frequencies = array();
public function visitDocumentElement(\JBBCode\DocumentElement $documentElement)
{
foreach ($documentElement->getChildren() as $child) {
$child->accept($this);
}
}
public function visitTextNode(\JBBCode\TextNode $textNode)
{
// Nothing to do here, text nodes do not have tag names or children
}
public function visitElementNode(\JBBCode\ElementNode $elementNode)
{
$tagName = strtolower($elementNode->getTagName());
// Update this tag name's frequency
if (isset($this->frequencies[$tagName])) {
$this->frequencies[$tagName]++;
} else {
$this->frequencies[$tagName] = 1;
}
// Visit all the node's childrens
foreach ($elementNode->getChildren() as $child) {
$child->accept($this);
}
}
/**
* Retrieves the frequency of the given tag name.
*
* @param $tagName the tag name to look up
*/
public function getFrequency($tagName)
{
if (!isset($this->frequencies[$tagName])) {
return 0;
} else {
return $this->frequencies[$tagName];
}
}
}

78
vendor/jbbcode/jbbcode/README.md vendored Normal file
View file

@ -0,0 +1,78 @@
jBBCode
=======
jBBCode is a bbcode parser written in php 5.3. It's relatively lightweight and parses
bbcodes without resorting to expensive regular expressions.
Documentation
-------------
For complete documentation and examples visit [jbbcode.com](http://jbbcode.com).
###A basic example
jBBCode includes a few optional, default bbcode definitions that may be loaded through the
`DefaultCodeDefinitionSet` class. Below is a simple example of using these codes to convert
a bbcode string to html.
```php
<?php
require_once "/path/to/jbbcode/Parser.php";
$parser = new JBBCode\Parser();
$parser->addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
$text = "The default codes include: [b]bold[/b], [i]italics[/i], [u]underlining[/u], ";
$text .= "[url=http://jbbcode.com]links[/url], [color=red]color![/color] and more.";
$parser->parse($text);
print $parser->getAsHtml();
```
Contribute
----------
I would love help maintaining jBBCode. Look at [open issues](http://github.com/jbowens/jBBCode/issues) for ideas on
what needs to be done. Before submitting a pull request, verify that all unit tests still pass.
#### Running unit tests
To run the unit tests,
ensure that [phpunit](http://github.com/sebastianbergmann/phpunit) is installed, or install it through the composer
dev dependencies. Then run `phpunit ./tests` from the project directory. If you're adding new functionality, writing
additional unit tests is a great idea.
Author
------
jBBCode was written by Jackson Owens. You may reach him at [jackson_owens@brown.edu](mailto:jackson_owens@brown.edu).
Other languages
---------------
This library is in the process of being ported to java. For more information, see [jBBCode-java](https://github.com/jbowens/jBBCode-java).
License
-------
The MIT License
Copyright (c) 2011 Jackson Owens
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

31
vendor/jbbcode/jbbcode/composer.json vendored Normal file
View file

@ -0,0 +1,31 @@
{
"name": "jbbcode/jbbcode",
"type": "library",
"description": "A lightweight but extensible BBCode parser written in PHP 5.3.",
"keywords": ["BBCode", "BB"],
"homepage": "http://jbbcode.com/",
"license": "MIT",
"require": {
"php": ">=5.3.0"
},
"require-dev": {
"phpunit/phpunit": "3.7.*"
},
"config": {
"bin-dir": "/usr/local/bin"
},
"authors": [
{
"name": "Jackson Owens",
"email": "jackson_owens@brown.edu",
"homepage": "http://jbowens.org/",
"role": "Developer"
}
],
"autoload": {
"psr-0": {
"JBBCode": "."
}
},
"minimum-stability": "stable"
}