├── .coveralls.yml
├── .gitignore
├── .travis.yml
├── JBBCode
├── CodeDefinition.php
├── CodeDefinitionBuilder.php
├── CodeDefinitionSet.php
├── DefaultCodeDefinitionSet.php
├── DocumentElement.php
├── ElementNode.php
├── InputValidator.php
├── Node.php
├── NodeVisitor.php
├── Parser.php
├── TextNode.php
├── Tokenizer.php
├── examples
│ ├── 1-GettingStarted.php
│ ├── 2-ClosingUnclosedTags.php
│ ├── 3-MarkuplessText.php
│ ├── 4-CreatingNewCodes.php
│ ├── SmileyVisitorTest.php
│ └── TagCountingVisitorTest.php
├── tests
│ ├── CodeDefinitionBuilderTest.php
│ ├── DefaultCodeDefinitionSetTest.php
│ ├── DocumentElementTest.php
│ ├── ElementNodeTest.php
│ ├── ParseContentTest.php
│ ├── ParserTest.php
│ ├── ParsingEdgeCaseTest.php
│ ├── SimpleEvaluationTest.php
│ ├── TextNodeTest.php
│ ├── TokenizerTest.php
│ ├── bootstrap.php
│ ├── validators
│ │ ├── CssColorValidatorTest.php
│ │ ├── FnValidatorTest.php
│ │ ├── UrlValidatorTest.php
│ │ └── ValidatorTest.php
│ └── visitors
│ │ ├── HTMLSafeVisitorTest.php
│ │ ├── NestLimitVisitorTest.php
│ │ ├── SmileyVisitorTest.php
│ │ └── TagCountingVisitorTest.php
├── validators
│ ├── CssColorValidator.php
│ ├── FnValidator.php
│ └── UrlValidator.php
└── visitors
│ ├── HTMLSafeVisitor.php
│ ├── NestLimitVisitor.php
│ ├── SmileyVisitor.php
│ └── TagCountingVisitor.php
├── LICENSE.md
├── README.md
├── composer.json
└── phpunit.xml.dist
/.coveralls.yml:
--------------------------------------------------------------------------------
1 | src_dir: .
2 | coverage_clover: clover.xml
3 | json_path: clover.json
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | composer.lock
2 | vendor
3 | clover.xml
4 | clover.json
5 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 | php:
3 | - 5.6
4 | - hhvm
5 | - nightly
6 |
7 | matrix:
8 | fast_finish: true
9 | allow_failures:
10 | - php: hhvm
11 | - php: nightly
12 |
13 | git:
14 | depth: 10
15 |
16 | cache:
17 | directories:
18 | - vendor
19 | - $HOME/.composer/cache
20 |
21 | sudo: false
22 |
23 | install:
24 | - composer self-update
25 | - composer install --prefer-source --no-interaction
26 |
27 | after_success:
28 | - php vendor/bin/coveralls -v
29 |
--------------------------------------------------------------------------------
/JBBCode/CodeDefinition.php:
--------------------------------------------------------------------------------
1 | elCounter = 0;
47 | $def->setTagName($tagName);
48 | $def->setReplacementText($replacementText);
49 | $def->useOption = $useOption;
50 | $def->parseContent = $parseContent;
51 | $def->nestLimit = $nestLimit;
52 | $def->optionValidator = $optionValidator;
53 | $def->bodyValidator = $bodyValidator;
54 | return $def;
55 | }
56 |
57 | /**
58 | * Constructs a new CodeDefinition.
59 | *
60 | * This constructor is deprecated. You should use the static construct() method or the
61 | * CodeDefinitionBuilder class to construct a new CodeDefiniton.
62 | *
63 | * @deprecated
64 | */
65 | public function __construct()
66 | {
67 | /* WARNING: This function is deprecated and will be made protected in a future
68 | * version of jBBCode. */
69 | $this->parseContent = true;
70 | $this->useOption = false;
71 | $this->nestLimit = -1;
72 | $this->elCounter = 0;
73 | $this->optionValidator = array();
74 | $this->bodyValidator = null;
75 | }
76 |
77 | /**
78 | * Determines if the arguments to the given element are valid based on
79 | * any validators attached to this CodeDefinition.
80 | *
81 | * @param ElementNode $el the ElementNode to validate
82 | * @return boolean true if the ElementNode's {option} and {param} are OK, false if they're not
83 | */
84 | public function hasValidInputs(ElementNode $el)
85 | {
86 | if ($this->usesOption() && $this->optionValidator) {
87 | $att = $el->getAttribute();
88 |
89 | foreach ($att as $name => $value) {
90 | if (isset($this->optionValidator[$name]) && !$this->optionValidator[$name]->validate($value)) {
91 | return false;
92 | }
93 | }
94 | }
95 |
96 | if (!$this->parseContent() && $this->bodyValidator) {
97 | /* We only evaluate the content if we're not parsing the content. */
98 | $content = "";
99 | foreach ($el->getChildren() as $child) {
100 | $content .= $child->getAsBBCode();
101 | }
102 | if (!$this->bodyValidator->validate($content)) {
103 | /* The content of the element is not valid. */
104 | return false;
105 | }
106 | }
107 |
108 | return true;
109 | }
110 |
111 | /**
112 | * Accepts an ElementNode that is defined by this CodeDefinition and returns the HTML
113 | * markup of the element. This is a commonly overridden class for custom CodeDefinitions
114 | * so that the content can be directly manipulated.
115 | *
116 | * @param ElementNode $el the element to return an html representation of
117 | *
118 | * @return string the parsed html of this element (INCLUDING ITS CHILDREN)
119 | */
120 | public function asHtml(ElementNode $el)
121 | {
122 | if (!$this->hasValidInputs($el)) {
123 | return $el->getAsBBCode();
124 | }
125 |
126 | $html = $this->getReplacementText();
127 |
128 | if ($this->usesOption()) {
129 | $options = $el->getAttribute();
130 | if (count($options)==1) {
131 | $vals = array_values($options);
132 | $html = str_ireplace('{option}', reset($vals), $html);
133 | } else {
134 | foreach ($options as $key => $val) {
135 | $html = str_ireplace('{' . $key . '}', $val, $html);
136 | }
137 | }
138 | }
139 |
140 | $content = $this->getContent($el);
141 |
142 | $html = str_ireplace('{param}', $content, $html);
143 |
144 | return $html;
145 | }
146 |
147 | protected function getContent(ElementNode $el)
148 | {
149 | if ($this->parseContent()) {
150 | $content = "";
151 | foreach ($el->getChildren() as $child) {
152 | $content .= $child->getAsHTML();
153 | }
154 | } else {
155 | $content = "";
156 | foreach ($el->getChildren() as $child) {
157 | $content .= $child->getAsBBCode();
158 | }
159 | }
160 | return $content;
161 | }
162 |
163 | /**
164 | * Accepts an ElementNode that is defined by this CodeDefinition and returns the text
165 | * representation of the element. This may be overridden by a custom CodeDefinition.
166 | *
167 | * @param ElementNode $el the element to return a text representation of
168 | *
169 | * @return string the text representation of $el
170 | */
171 | public function asText(ElementNode $el)
172 | {
173 | if (!$this->hasValidInputs($el)) {
174 | return $el->getAsBBCode();
175 | }
176 |
177 | $s = "";
178 | foreach ($el->getChildren() as $child) {
179 | $s .= $child->getAsText();
180 | }
181 | return $s;
182 | }
183 |
184 | /**
185 | * Returns the tag name of this code definition
186 | *
187 | * @return string this definition's associated tag name
188 | */
189 | public function getTagName()
190 | {
191 | return $this->tagName;
192 | }
193 |
194 | /**
195 | * Returns the replacement text of this code definition. This usually has little, if any meaning if the
196 | * CodeDefinition class was extended. For default, html replacement CodeDefinitions this returns the html
197 | * markup for the definition.
198 | *
199 | * @return string the replacement text of this CodeDefinition
200 | */
201 | public function getReplacementText()
202 | {
203 | return $this->replacementText;
204 | }
205 |
206 | /**
207 | * Returns whether or not this CodeDefinition uses the optional {option}
208 | *
209 | * @return boolean true if this CodeDefinition uses the option, false otherwise
210 | */
211 | public function usesOption()
212 | {
213 | return $this->useOption;
214 | }
215 |
216 | /**
217 | * Returns whether or not this CodeDefinition parses elements contained within it,
218 | * or just treats its children as text.
219 | *
220 | * @return boolean true if this CodeDefinition parses elements contained within itself
221 | */
222 | public function parseContent()
223 | {
224 | return $this->parseContent;
225 | }
226 |
227 | /**
228 | * Returns the limit of how many elements defined by this CodeDefinition may be
229 | * nested together. If after parsing elements are nested beyond this limit, the
230 | * subtrees formed by those nodes will be removed from the parse tree. A nest
231 | * limit of -1 signifies no limit.
232 | *
233 | * @return integer
234 | */
235 | public function getNestLimit()
236 | {
237 | return $this->nestLimit;
238 | }
239 |
240 | /**
241 | * Sets the tag name of this CodeDefinition
242 | *
243 | * @deprecated
244 | *
245 | * @param string $tagName the new tag name of this definition
246 | */
247 | public function setTagName($tagName)
248 | {
249 | $this->tagName = strtolower($tagName);
250 | }
251 |
252 | /**
253 | * Sets the html replacement text of this CodeDefinition
254 | *
255 | * @deprecated
256 | *
257 | * @param string $txt the new replacement text
258 | */
259 | public function setReplacementText($txt)
260 | {
261 | $this->replacementText = $txt;
262 | }
263 |
264 | /**
265 | * Sets whether or not this CodeDefinition uses the {option}
266 | *
267 | * @deprecated
268 | *
269 | * @param boolean $bool
270 | */
271 | public function setUseOption($bool)
272 | {
273 | $this->useOption = $bool;
274 | }
275 |
276 | /**
277 | * Sets whether or not this CodeDefinition allows its children to be parsed as html
278 | *
279 | * @deprecated
280 | *
281 | * @param boolean $bool
282 | */
283 | public function setParseContent($bool)
284 | {
285 | $this->parseContent = $bool;
286 | }
287 |
288 | /**
289 | * Increments the element counter. This is used for tracking depth of elements of the same type for next limits.
290 | *
291 | * @deprecated
292 | *
293 | * @return void
294 | */
295 | public function incrementCounter()
296 | {
297 | $this->elCounter++;
298 | }
299 |
300 | /**
301 | * Decrements the element counter.
302 | *
303 | * @deprecated
304 | *
305 | * @return void
306 | */
307 | public function decrementCounter()
308 | {
309 | $this->elCounter--;
310 | }
311 |
312 | /**
313 | * Resets the element counter.
314 | *
315 | * @deprecated
316 | */
317 | public function resetCounter()
318 | {
319 | $this->elCounter = 0;
320 | }
321 |
322 | /**
323 | * Returns the current value of the element counter.
324 | *
325 | * @deprecated
326 | *
327 | * @return int
328 | */
329 | public function getCounter()
330 | {
331 | return $this->elCounter;
332 | }
333 | }
334 |
--------------------------------------------------------------------------------
/JBBCode/CodeDefinitionBuilder.php:
--------------------------------------------------------------------------------
1 | tagName = $tagName;
40 | $this->replacementText = $replacementText;
41 | }
42 |
43 | /**
44 | * Sets the tag name the CodeDefinition should be built with.
45 | *
46 | * @param string $tagName the tag name for the new CodeDefinition
47 | * @return self
48 | */
49 | public function setTagName($tagName)
50 | {
51 | $this->tagName = $tagName;
52 | return $this;
53 | }
54 |
55 | /**
56 | * Sets the replacement text that the new CodeDefinition should be
57 | * built with.
58 | *
59 | * @param string $replacementText the replacement text for the new CodeDefinition
60 | * @return self
61 | */
62 | public function setReplacementText($replacementText)
63 | {
64 | $this->replacementText = $replacementText;
65 | return $this;
66 | }
67 |
68 | /**
69 | * Set whether or not the built CodeDefinition should use the {option} bbcode
70 | * argument.
71 | *
72 | * @param boolean $option true iff the definition includes an option
73 | * @return self
74 | */
75 | public function setUseOption($option)
76 | {
77 | $this->useOption = $option;
78 | return $this;
79 | }
80 |
81 | /**
82 | * Set whether or not the built CodeDefinition should allow its content
83 | * to be parsed and evaluated as bbcode.
84 | *
85 | * @param boolean $parseContent true iff the content should be parsed
86 | * @return self
87 | */
88 | public function setParseContent($parseContent)
89 | {
90 | $this->parseContent = $parseContent;
91 | return $this;
92 | }
93 |
94 | /**
95 | * Sets the nest limit for this code definition.
96 | *
97 | * @param integer $limit a positive integer, or -1 if there is no limit.
98 | * @throws \InvalidArgumentException if the nest limit is invalid
99 | * @return self
100 | */
101 | public function setNestLimit($limit)
102 | {
103 | if (!is_int($limit) || ($limit <= 0 && -1 != $limit)) {
104 | throw new \InvalidArgumentException("A nest limit must be a positive integer " .
105 | "or -1.");
106 | }
107 | $this->nestLimit = $limit;
108 | return $this;
109 | }
110 |
111 | /**
112 | * Sets the InputValidator that option arguments should be validated with.
113 | *
114 | * @param InputValidator $validator the InputValidator instance to use
115 | * @return self
116 | */
117 | public function setOptionValidator(\JBBCode\InputValidator $validator, $option=null)
118 | {
119 | if (empty($option)) {
120 | $option = $this->tagName;
121 | }
122 | $this->optionValidator[$option] = $validator;
123 | return $this;
124 | }
125 |
126 | /**
127 | * Sets the InputValidator that body ({param}) text should be validated with.
128 | *
129 | * @param InputValidator $validator the InputValidator instance to use
130 | * @return self
131 | */
132 | public function setBodyValidator(\JBBCode\InputValidator $validator)
133 | {
134 | $this->bodyValidator = $validator;
135 | return $this;
136 | }
137 |
138 | /**
139 | * Removes the attached option validator if one is attached.
140 | * @return self
141 | */
142 | public function removeOptionValidator()
143 | {
144 | $this->optionValidator = array();
145 | return $this;
146 | }
147 |
148 | /**
149 | * Removes the attached body validator if one is attached.
150 | * @return self
151 | */
152 | public function removeBodyValidator()
153 | {
154 | $this->bodyValidator = null;
155 | return $this;
156 | }
157 |
158 | /**
159 | * Builds a CodeDefinition with the current state of the builder.
160 | *
161 | * @return CodeDefinition a new CodeDefinition instance
162 | */
163 | public function build()
164 | {
165 | $definition = CodeDefinition::construct($this->tagName,
166 | $this->replacementText,
167 | $this->useOption,
168 | $this->parseContent,
169 | $this->nestLimit,
170 | $this->optionValidator,
171 | $this->bodyValidator);
172 | return $definition;
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/JBBCode/CodeDefinitionSet.php:
--------------------------------------------------------------------------------
1 | {param}');
29 | $this->definitions[] = $builder->build();
30 |
31 | /* [i] italics tag */
32 | $builder = new CodeDefinitionBuilder('i', '{param}');
33 | $this->definitions[] = $builder->build();
34 |
35 | /* [u] underline tag */
36 | $builder = new CodeDefinitionBuilder('u', '{param}');
37 | $this->definitions[] = $builder->build();
38 |
39 | $urlValidator = new \JBBCode\validators\UrlValidator();
40 |
41 | /* [url] link tag */
42 | $builder = new CodeDefinitionBuilder('url', '{param}');
43 | $builder->setParseContent(false)->setBodyValidator($urlValidator);
44 | $this->definitions[] = $builder->build();
45 |
46 | /* [url=http://example.com] link tag */
47 | $builder = new CodeDefinitionBuilder('url', '{param}');
48 | $builder->setUseOption(true)->setParseContent(true)->setOptionValidator($urlValidator);
49 | $this->definitions[] = $builder->build();
50 |
51 | /* [img] image tag */
52 | $builder = new CodeDefinitionBuilder('img', '
');
53 | $builder->setUseOption(false)->setParseContent(false)->setBodyValidator($urlValidator);
54 | $this->definitions[] = $builder->build();
55 |
56 | /* [img=alt text] image tag */
57 | $builder = new CodeDefinitionBuilder('img', '
');
58 | $builder->setUseOption(true)->setParseContent(false)->setBodyValidator($urlValidator);
59 | $this->definitions[] = $builder->build();
60 |
61 | /* [color] color tag */
62 | $builder = new CodeDefinitionBuilder('color', '{param}');
63 | $builder->setUseOption(true)->setOptionValidator(new \JBBCode\validators\CssColorValidator());
64 | $this->definitions[] = $builder->build();
65 | }
66 |
67 | /**
68 | * Returns an array of the default code definitions.
69 | *
70 | * @return CodeDefinition[]
71 | */
72 | public function getCodeDefinitions()
73 | {
74 | return $this->definitions;
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/JBBCode/DocumentElement.php:
--------------------------------------------------------------------------------
1 | setTagName("Document");
22 | }
23 |
24 | /**
25 | * (non-PHPdoc)
26 | * @see JBBCode.ElementNode::getAsBBCode()
27 | *
28 | * Returns the BBCode representation of this document
29 | *
30 | * @return string this document's bbcode representation
31 | */
32 | public function getAsBBCode()
33 | {
34 | $s = "";
35 | foreach ($this->getChildren() as $child) {
36 | $s .= $child->getAsBBCode();
37 | }
38 |
39 | return $s;
40 | }
41 |
42 | /**
43 | * (non-PHPdoc)
44 | * @see JBBCode.ElementNode::getAsHTML()
45 | *
46 | * Documents don't add any html. They only exist as a container for their
47 | * children, so getAsHTML() simply iterates through the document's children,
48 | * returning their html.
49 | *
50 | * @return string the HTML representation of this document
51 | */
52 | public function getAsHTML()
53 | {
54 | $s = "";
55 | foreach ($this->getChildren() as $child) {
56 | $s .= $child->getAsHTML();
57 | }
58 |
59 | return $s;
60 | }
61 |
62 | public function accept(NodeVisitor $visitor)
63 | {
64 | $visitor->visitDocumentElement($this);
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/JBBCode/ElementNode.php:
--------------------------------------------------------------------------------
1 | children = array();
37 | $this->nestDepth = 0;
38 | }
39 |
40 | public function accept(NodeVisitor $nodeVisitor)
41 | {
42 | $nodeVisitor->visitElementNode($this);
43 | }
44 |
45 | /**
46 | * Gets the CodeDefinition that defines this element.
47 | *
48 | * @return CodeDefinition this element's code definition
49 | */
50 | public function getCodeDefinition()
51 | {
52 | return $this->codeDefinition;
53 | }
54 |
55 | /**
56 | * Sets the CodeDefinition that defines this element.
57 | *
58 | * @param CodeDefinition $codeDef the code definition that defines this element node
59 | */
60 | public function setCodeDefinition(CodeDefinition $codeDef)
61 | {
62 | $this->codeDefinition = $codeDef;
63 | $this->setTagName($codeDef->getTagName());
64 | }
65 |
66 | /**
67 | * Returns the tag name of this element.
68 | *
69 | * @return string the element's tag name
70 | */
71 | public function getTagName()
72 | {
73 | return $this->tagName;
74 | }
75 |
76 | /**
77 | * Returns the attribute (used as the option in bbcode definitions) of this element.
78 | *
79 | * @return array the attributes of this element
80 | */
81 | public function getAttribute()
82 | {
83 | return $this->attribute;
84 | }
85 |
86 | /**
87 | * Returns all the children of this element.
88 | *
89 | * @return Node[] an array of this node's child nodes
90 | */
91 | public function getChildren()
92 | {
93 | return $this->children;
94 | }
95 |
96 | /**
97 | * (non-PHPdoc)
98 | * @see JBBCode.Node::getAsText()
99 | *
100 | * Returns the element as text (not including any bbcode markup)
101 | *
102 | * @return string the plain text representation of this node
103 | */
104 | public function getAsText()
105 | {
106 | if ($this->codeDefinition) {
107 | return $this->codeDefinition->asText($this);
108 | } else {
109 | $s = "";
110 | foreach ($this->getChildren() as $child) {
111 | $s .= $child->getAsText();
112 | }
113 | return $s;
114 | }
115 | }
116 |
117 | /**
118 | * (non-PHPdoc)
119 | * @see JBBCode.Node::getAsBBCode()
120 | *
121 | * Returns the element as bbcode (with all unclosed tags closed)
122 | *
123 | * @return string the bbcode representation of this element
124 | */
125 | public function getAsBBCode()
126 | {
127 | $str = "[".$this->tagName;
128 | if (!empty($this->attribute)) {
129 | if (isset($this->attribute[$this->tagName])) {
130 | $str .= "=".$this->attribute[$this->tagName];
131 | }
132 |
133 | foreach ($this->attribute as $key => $value) {
134 | if ($key == $this->tagName) {
135 | continue;
136 | } else {
137 | $str .= " ".$key."=" . $value;
138 | }
139 | }
140 | }
141 | $str .= "]";
142 | foreach ($this->getChildren() as $child) {
143 | $str .= $child->getAsBBCode();
144 | }
145 | $str .= "[/".$this->tagName."]";
146 |
147 | return $str;
148 | }
149 |
150 | /**
151 | * (non-PHPdoc)
152 | * @see JBBCode.Node::getAsHTML()
153 | *
154 | * Returns the element as html with all replacements made
155 | *
156 | * @return string the html representation of this node
157 | */
158 | public function getAsHTML()
159 | {
160 | if ($this->codeDefinition) {
161 | return $this->codeDefinition->asHtml($this);
162 | } else {
163 | return "";
164 | }
165 | }
166 |
167 | /**
168 | * Adds a child to this node's content. A child may be a TextNode, or
169 | * another ElementNode... or anything else that may extend the
170 | * abstract Node class.
171 | *
172 | * @param Node $child the node to add as a child
173 | */
174 | public function addChild(Node $child)
175 | {
176 | $this->children[] = $child;
177 | $child->setParent($this);
178 | }
179 |
180 | /**
181 | * Removes a child from this node's content.
182 | *
183 | * @param Node $child the child node to remove
184 | */
185 | public function removeChild(Node $child)
186 | {
187 | foreach ($this->children as $key => $value) {
188 | if ($value === $child) {
189 | unset($this->children[$key]);
190 | }
191 | }
192 | }
193 |
194 | /**
195 | * Sets the tag name of this element node.
196 | *
197 | * @param string $tagName the element's new tag name
198 | */
199 | public function setTagName($tagName)
200 | {
201 | $this->tagName = $tagName;
202 | }
203 |
204 | /**
205 | * Sets the attribute (option) of this element node.
206 | *
207 | * @param string[] $attribute the attribute(s) of this element node
208 | */
209 | public function setAttribute($attribute)
210 | {
211 | $this->attribute = $attribute;
212 | }
213 |
214 | /**
215 | * Traverses the parse tree upwards, going from parent to parent, until it finds a
216 | * parent who has the given tag name. Returns the parent with the matching tag name
217 | * if it exists, otherwise returns null.
218 | *
219 | * @param string $str the tag name to search for
220 | *
221 | * @return ElementNode|null the closest parent with the given tag name
222 | */
223 | public function closestParentOfType($str)
224 | {
225 | $str = strtolower($str);
226 | $currentEl = $this;
227 |
228 | while (strtolower($currentEl->getTagName()) != $str && $currentEl->hasParent()) {
229 | $currentEl = $currentEl->getParent();
230 | }
231 |
232 | if (strtolower($currentEl->getTagName()) != $str) {
233 | return null;
234 | } else {
235 | return $currentEl;
236 | }
237 | }
238 | }
239 |
--------------------------------------------------------------------------------
/JBBCode/InputValidator.php:
--------------------------------------------------------------------------------
1 | parent;
25 | }
26 |
27 | /**
28 | * Determines if this node has a parent.
29 | *
30 | * @return boolean true if this node has a parent, false otherwise
31 | */
32 | public function hasParent()
33 | {
34 | return $this->parent != null;
35 | }
36 |
37 | /**
38 | * Returns true if this is a text node. Returns false otherwise.
39 | * (Overridden by TextNode to return true)
40 | *
41 | * @return boolean true if this node is a text node
42 | */
43 | public function isTextNode()
44 | {
45 | return false;
46 | }
47 |
48 | /**
49 | * Accepts the given NodeVisitor. This is part of an implementation
50 | * of the Visitor pattern.
51 | *
52 | * @param NodeVisitor $nodeVisitor the NodeVisitor traversing the graph
53 | */
54 | abstract public function accept(NodeVisitor $nodeVisitor);
55 |
56 | /**
57 | * Returns this node as text (without any bbcode markup)
58 | *
59 | * @return string the plain text representation of this node
60 | */
61 | abstract public function getAsText();
62 |
63 | /**
64 | * Returns this node as bbcode
65 | *
66 | * @return string the bbcode representation of this node
67 | */
68 | abstract public function getAsBBCode();
69 |
70 | /**
71 | * Returns this node as HTML
72 | *
73 | * @return string the html representation of this node
74 | */
75 | abstract public function getAsHTML();
76 |
77 | /**
78 | * Sets this node's parent to be the given node.
79 | *
80 | * @param Node $parent the node to set as this node's parent
81 | */
82 | public function setParent(Node $parent)
83 | {
84 | $this->parent = $parent;
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/JBBCode/NodeVisitor.php:
--------------------------------------------------------------------------------
1 | treeRoot = new DocumentElement();
46 | }
47 |
48 | /**
49 | * Adds a simple (text-replacement only) bbcode definition
50 | *
51 | * @param string $tagName the tag name of the code (for example the b in [b])
52 | * @param string $replace the html to use, with {param} and optionally {option} for replacements
53 | * @param boolean $useOption whether or not this bbcode uses the secondary {option} replacement
54 | * @param boolean $parseContent whether or not to parse the content within these elements
55 | * @param integer $nestLimit an optional limit of the number of elements of this kind that can be nested within
56 | * each other before the parser stops parsing them.
57 | * @param InputValidator $optionValidator the validator to run {option} through
58 | * @param InputValidator $bodyValidator the validator to run {param} through (only used if $parseContent == false)
59 | *
60 | * @return Parser
61 | */
62 | public function addBBCode($tagName, $replace, $useOption = false, $parseContent = true, $nestLimit = -1,
63 | InputValidator $optionValidator = null, InputValidator $bodyValidator = null)
64 | {
65 | $builder = new CodeDefinitionBuilder($tagName, $replace);
66 |
67 | $builder->setUseOption($useOption);
68 | $builder->setParseContent($parseContent);
69 | $builder->setNestLimit($nestLimit);
70 |
71 | if ($optionValidator) {
72 | $builder->setOptionValidator($optionValidator);
73 | }
74 |
75 | if ($bodyValidator) {
76 | $builder->setBodyValidator($bodyValidator);
77 | }
78 |
79 | $this->addCodeDefinition($builder->build());
80 |
81 | return $this;
82 | }
83 |
84 | /**
85 | * Adds a complex bbcode definition. You may subclass the CodeDefinition class, instantiate a definition of your new
86 | * class and add it to the parser through this method.
87 | *
88 | * @param CodeDefinition $definition the bbcode definition to add
89 | *
90 | * @return Parser
91 | */
92 | public function addCodeDefinition(CodeDefinition $definition)
93 | {
94 | $this->bbcodes[$definition->getTagName()][$definition->usesOption()] = $definition;
95 | return $this;
96 | }
97 |
98 | /**
99 | * Adds a set of CodeDefinitions.
100 | *
101 | * @param CodeDefinitionSet $set the set of definitions to add
102 | *
103 | * @return Parser
104 | */
105 | public function addCodeDefinitionSet(CodeDefinitionSet $set)
106 | {
107 | foreach ($set->getCodeDefinitions() as $def) {
108 | $this->addCodeDefinition($def);
109 | }
110 |
111 | return $this;
112 | }
113 |
114 | /**
115 | * Returns the entire parse tree as text. Only {param} content is returned. BBCode markup will be ignored.
116 | *
117 | * @return string a text representation of the parse tree
118 | */
119 | public function getAsText()
120 | {
121 | return $this->treeRoot->getAsText();
122 | }
123 |
124 | /**
125 | * Returns the entire parse tree as bbcode. This will be identical to the inputted string, except unclosed tags
126 | * will be closed.
127 | *
128 | * @return string a bbcode representation of the parse tree
129 | */
130 | public function getAsBBCode()
131 | {
132 | return $this->treeRoot->getAsBBCode();
133 | }
134 |
135 | /**
136 | * Returns the entire parse tree as HTML. All BBCode replacements will be made. This is generally the method
137 | * you will want to use to retrieve the parsed bbcode.
138 | *
139 | * @return string a parsed html string
140 | */
141 | public function getAsHTML()
142 | {
143 | return $this->treeRoot->getAsHTML();
144 | }
145 |
146 | /**
147 | * Accepts the given NodeVisitor at the root.
148 | *
149 | * @param NodeVisitor $nodeVisitor a NodeVisitor
150 | *
151 | * @return Parser
152 | */
153 | public function accept(NodeVisitor $nodeVisitor)
154 | {
155 | $this->treeRoot->accept($nodeVisitor);
156 |
157 | return $this;
158 | }
159 | /**
160 | * Constructs the parse tree from a string of bbcode markup.
161 | *
162 | * @param string $str the bbcode markup to parse
163 | *
164 | * @return Parser
165 | */
166 | public function parse($str)
167 | {
168 | /* Set the tree root back to a fresh DocumentElement. */
169 | $this->reset();
170 |
171 | $parent = $this->treeRoot;
172 | $tokenizer = new Tokenizer($str);
173 |
174 | while ($tokenizer->hasNext()) {
175 | $parent = $this->parseStartState($parent, $tokenizer);
176 | if ($parent->getCodeDefinition() && false ===
177 | $parent->getCodeDefinition()->parseContent()) {
178 | /* We're inside an element that does not allow its contents to be parseable. */
179 | $this->parseAsTextUntilClose($parent, $tokenizer);
180 | $parent = $parent->getParent();
181 | }
182 | }
183 |
184 | /* We parsed ignoring nest limits. Do an O(n) traversal to remove any elements that
185 | * are nested beyond their CodeDefinition's nest limit. */
186 | $this->removeOverNestedElements();
187 |
188 | return $this;
189 | }
190 |
191 | /**
192 | * Removes any elements that are nested beyond their nest limit from the parse tree. This
193 | * method is now deprecated. In a future release its access privileges will be made
194 | * protected.
195 | *
196 | * @deprecated
197 | */
198 | public function removeOverNestedElements()
199 | {
200 | $nestLimitVisitor = new \JBBCode\visitors\NestLimitVisitor();
201 | $this->accept($nestLimitVisitor);
202 | }
203 |
204 | /**
205 | * Removes the old parse tree if one exists.
206 | */
207 | protected function reset()
208 | {
209 | // remove any old tree information
210 | $this->treeRoot = new DocumentElement();
211 | }
212 |
213 | /**
214 | * Determines whether a bbcode exists based on its tag name and whether or not it uses an option
215 | *
216 | * @param string $tagName the bbcode tag name to check
217 | * @param boolean $usesOption whether or not the bbcode accepts an option
218 | *
219 | * @return bool true if the code exists, false otherwise
220 | */
221 | public function codeExists($tagName, $usesOption = false)
222 | {
223 | return isset($this->bbcodes[strtolower($tagName)][$usesOption]);
224 | }
225 |
226 | /**
227 | * Returns the CodeDefinition of a bbcode with the matching tag name and usesOption parameter
228 | *
229 | * @param string $tagName the tag name of the bbcode being searched for
230 | * @param boolean $usesOption whether or not the bbcode accepts an option
231 | *
232 | * @return CodeDefinition if the bbcode exists, null otherwise
233 | */
234 | public function getCode($tagName, $usesOption = false)
235 | {
236 | if ($this->codeExists($tagName, $usesOption)) {
237 | return $this->bbcodes[strtolower($tagName)][$usesOption];
238 | }
239 |
240 | return null;
241 | }
242 |
243 | /**
244 | * Adds a set of default, standard bbcode definitions commonly used across the web.
245 | *
246 | * This method is now deprecated. Please use DefaultCodeDefinitionSet and
247 | * addCodeDefinitionSet() instead.
248 | *
249 | * @deprecated
250 | */
251 | public function loadDefaultCodes()
252 | {
253 | $defaultSet = new DefaultCodeDefinitionSet();
254 | $this->addCodeDefinitionSet($defaultSet);
255 | }
256 |
257 | /**
258 | * Creates a new text node with the given parent and text string.
259 | *
260 | * @param ElementNode $parent the parent of the text node
261 | * @param string $string the text of the text node
262 | *
263 | * @return TextNode the newly created TextNode
264 | */
265 | protected function createTextNode(ElementNode $parent, $string)
266 | {
267 | $children = $parent->getChildren();
268 | if (!empty($children)) {
269 | $lastElement = end($children);
270 | reset($children);
271 |
272 | if ($lastElement->isTextNode()) {
273 | $lastElement->setValue($lastElement->getValue() . $string);
274 | return $lastElement;
275 | }
276 | }
277 |
278 | $textNode = new TextNode($string);
279 | $parent->addChild($textNode);
280 | return $textNode;
281 | }
282 |
283 | /**
284 | * jBBCode parsing logic is loosely modelled after a FSM. While not every function maps
285 | * to a unique DFSM state, each function handles the logic of one or more FSM states.
286 | * This function handles the beginning parse state when we're not currently in a tag
287 | * name.
288 | *
289 | * @param ElementNode $parent the current parent node we're under
290 | * @param Tokenizer $tokenizer the tokenizer we're using
291 | *
292 | * @return ElementNode the new parent we should use for the next iteration.
293 | */
294 | protected function parseStartState(ElementNode $parent, Tokenizer $tokenizer)
295 | {
296 | $next = $tokenizer->next();
297 |
298 | if ('[' == $next) {
299 | return $this->parseTagOpen($parent, $tokenizer);
300 | } else {
301 | $this->createTextNode($parent, $next);
302 | /* Drop back into the main parse loop which will call this
303 | * same method again. */
304 | return $parent;
305 | }
306 | }
307 |
308 | /**
309 | * This function handles parsing the beginnings of an open tag. When we see a [
310 | * at an appropriate time, this function is entered.
311 | *
312 | * @param ElementNode $parent the current parent node
313 | * @param Tokenizer $tokenizer the tokenizer we're using
314 | *
315 | * @return ElementNode the new parent node
316 | */
317 | protected function parseTagOpen(ElementNode $parent, Tokenizer $tokenizer)
318 | {
319 | if (!$tokenizer->hasNext()) {
320 | /* The [ that sent us to this state was just a trailing [, not the
321 | * opening for a new tag. Treat it as such. */
322 | $this->createTextNode($parent, '[');
323 | return $parent;
324 | }
325 |
326 | $next = $tokenizer->next();
327 |
328 | /* This while loop could be replaced by a recursive call to this same method,
329 | * which would likely be a lot clearer but I decided to use a while loop to
330 | * prevent stack overflow with a string like [[[[[[[[[...[[[.
331 | */
332 | while ('[' == $next) {
333 | /* The previous [ was just a random bracket that should be treated as text.
334 | * Continue until we get a non open bracket. */
335 | $this->createTextNode($parent, '[');
336 | if (!$tokenizer->hasNext()) {
337 | $this->createTextNode($parent, '[');
338 | return $parent;
339 | }
340 | $next = $tokenizer->next();
341 | }
342 |
343 | if (!$tokenizer->hasNext()) {
344 | $this->createTextNode($parent, '['.$next);
345 | return $parent;
346 | }
347 |
348 | $after_next = $tokenizer->next();
349 | $tokenizer->stepBack();
350 |
351 | if ($after_next != ']') {
352 | $this->createTextNode($parent, '['.$next);
353 | return $parent;
354 | }
355 |
356 | /* At this point $next is either ']' or plain text. */
357 | if (']' == $next) {
358 | $this->createTextNode($parent, '[');
359 | $this->createTextNode($parent, ']');
360 | return $parent;
361 | } else {
362 | /* $next is plain text... likely a tag name. */
363 | return $this->parseTag($parent, $tokenizer, $next);
364 | }
365 | }
366 |
367 | protected function parseOptions($tagContent)
368 | {
369 | $buffer = "";
370 | $tagName = "";
371 | $state = static::OPTION_STATE_TAGNAME;
372 | $keys = array();
373 | $values = array();
374 | $options = array();
375 |
376 | $len = strlen($tagContent);
377 | $done = false;
378 | $idx = 0;
379 |
380 | try {
381 | while (!$done) {
382 | $char = $idx < $len ? $tagContent[$idx]:null;
383 | switch ($state) {
384 | case static::OPTION_STATE_TAGNAME:
385 | switch ($char) {
386 | case '=':
387 | $state = static::OPTION_STATE_VALUE;
388 | $tagName = $buffer;
389 | $keys[] = $tagName;
390 | $buffer = "";
391 | break;
392 | case ' ':
393 | if ($buffer) {
394 | $state = static::OPTION_STATE_DEFAULT;
395 | $tagName = $buffer;
396 | $buffer = '';
397 | $keys[] = $tagName;
398 | }
399 | break;
400 | case "\n":
401 | case "\r":
402 | break;
403 |
404 | case null:
405 | $tagName = $buffer;
406 | $buffer = '';
407 | $keys[] = $tagName;
408 | break;
409 | default:
410 | $buffer .= $char;
411 | }
412 | break;
413 |
414 | case static::OPTION_STATE_DEFAULT:
415 | switch ($char) {
416 | case ' ':
417 | // do nothing
418 | default:
419 | $state = static::OPTION_STATE_KEY;
420 | $buffer .= $char;
421 | }
422 | break;
423 |
424 | case static::OPTION_STATE_VALUE:
425 | switch ($char) {
426 | case '"':
427 | $state = static::OPTION_STATE_QUOTED_VALUE;
428 | break;
429 | case null: // intentional fall-through
430 | case ' ': // key=value delimits to next key
431 | $values[] = trim($buffer);
432 | $buffer = "";
433 | $state = static::OPTION_STATE_KEY;
434 | break;
435 | case ":":
436 | if ($buffer=="javascript") {
437 | $state = static::OPTION_STATE_JAVASCRIPT;
438 | }
439 | $buffer .= $char;
440 | break;
441 | default:
442 | $buffer .= $char;
443 |
444 | }
445 | break;
446 |
447 | case static::OPTION_STATE_JAVASCRIPT:
448 | switch ($char) {
449 | case ";":
450 | $buffer .= $char;
451 | $values[] = $buffer;
452 | $buffer = "";
453 | $state = static::OPTION_STATE_KEY;
454 |
455 | break;
456 | default:
457 | $buffer .= $char;
458 | }
459 | break;
460 |
461 | case static::OPTION_STATE_KEY:
462 | switch ($char) {
463 | case '=':
464 | $state = static::OPTION_STATE_VALUE;
465 | $keys[] = trim($buffer);
466 | $buffer = '';
467 | break;
468 | case ' ': // ignore key=value
469 | break;
470 | default:
471 | $buffer .= $char;
472 | break;
473 | }
474 | break;
475 |
476 | case static::OPTION_STATE_QUOTED_VALUE:
477 | switch ($char) {
478 | case null:
479 | case '"':
480 | $state = static::OPTION_STATE_KEY;
481 | $values[] = $buffer;
482 | $buffer = '';
483 |
484 | // peek ahead. If the next character is not a space or a closing brace, we have a bad tag and need to abort
485 | if (isset($tagContent[$idx+1]) && $tagContent[$idx+1]!=" " && $tagContent[$idx+1]!="]") {
486 | throw new \DomainException("Badly formed attribute: $tagContent");
487 | }
488 | break;
489 | default:
490 | $buffer .= $char;
491 | break;
492 | }
493 | break;
494 | default:
495 | if (!empty($char)) {
496 | $state = static::OPTION_STATE_KEY;
497 | }
498 |
499 | }
500 | if ($idx >= $len) {
501 | $done = true;
502 | }
503 | $idx++;
504 | }
505 |
506 | if (!empty($keys) && !empty($values)) {
507 | if (count($keys)==(count($values)+1)) {
508 | array_unshift($values, "");
509 | }
510 |
511 | $options = array_combine($keys, $values);
512 | }
513 | } catch (\DomainException $e) {
514 | // 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
515 | $options[$tagName]= substr($tagContent, strpos($tagContent, "=")+1);
516 | }
517 | return array($tagName, $options);
518 | }
519 |
520 | /**
521 | * This is the next step in parsing a tag. It's possible for it to still be invalid at this
522 | * point but many of the basic invalid tag name conditions have already been handled.
523 | *
524 | * @param ElementNode $parent the current parent element
525 | * @param Tokenizer $tokenizer the tokenizer we're using
526 | * @param string $tagContent the text between the [ and the ], assuming there is actually a ]
527 | *
528 | * @return ElementNode the new parent element
529 | */
530 | protected function parseTag(ElementNode $parent, Tokenizer $tokenizer, $tagContent)
531 | {
532 | if (!$tokenizer->hasNext() || ($next = $tokenizer->next()) != ']') {
533 | /* This is a malformed tag. Both the previous [ and the tagContent
534 | * is really just plain text. */
535 | $this->createTextNode($parent, '[');
536 | $this->createTextNode($parent, $tagContent);
537 | return $parent;
538 | }
539 |
540 | /* This is a well-formed tag consisting of [something] or [/something], but
541 | * we still need to ensure that 'something' is a valid tag name. Additionally,
542 | * if it's a closing tag, we need to ensure that there was a previous matching
543 | * opening tag.
544 | */
545 | /* There could be attributes. */
546 | list($tmpTagName, $options) = $this->parseOptions($tagContent);
547 |
548 | // $tagPieces = explode('=', $tagContent);
549 | // $tmpTagName = $tagPieces[0];
550 |
551 | $actualTagName = $tmpTagName;
552 | if ('' != $tmpTagName && '/' == $tmpTagName[0]) {
553 | /* This is a closing tag name. */
554 | $actualTagName = substr($tmpTagName, 1);
555 | }
556 |
557 | if ('' != $tmpTagName && '/' == $tmpTagName[0]) {
558 | /* This is attempting to close an open tag. We must verify that there exists an
559 | * open tag of the same type and that there is no option (options on closing
560 | * tags don't make any sense). */
561 | $elToClose = $parent->closestParentOfType($actualTagName);
562 | if (null == $elToClose || count($options) > 1) {
563 | /* Closing an unopened tag or has an option. Treat everything as plain text. */
564 | $this->createTextNode($parent, '[');
565 | $this->createTextNode($parent, $tagContent);
566 | $this->createTextNode($parent, ']');
567 | return $parent;
568 | } else {
569 | /* We're closing $elToClose. In order to do that, we just need to return
570 | * $elToClose's parent, since that will change our effective parent to be
571 | * elToClose's parent. */
572 | return $elToClose->getParent();
573 | }
574 | }
575 |
576 | /* Verify that this is a known bbcode tag name. */
577 | if ('' == $actualTagName || !$this->codeExists($actualTagName, !empty($options))) {
578 | /* This is an invalid tag name! Treat everything we've seen as plain text. */
579 | $this->createTextNode($parent, '[');
580 | $this->createTextNode($parent, $tagContent);
581 | $this->createTextNode($parent, ']');
582 | return $parent;
583 | }
584 |
585 | /* If we're here, this is a valid opening tag. Let's make a new node for it. */
586 | $el = new ElementNode();
587 | $code = $this->getCode($actualTagName, !empty($options));
588 | $el->setCodeDefinition($code);
589 | if (!empty($options)) {
590 | /* We have an attribute we should save. */
591 | $el->setAttribute($options);
592 | }
593 | $parent->addChild($el);
594 | return $el;
595 | }
596 |
597 | /**
598 | * Handles parsing elements whose CodeDefinitions disable parsing of element
599 | * contents. This function uses a rolling window of 3 tokens until it finds the
600 | * appropriate closing tag or reaches the end of the token stream.
601 | *
602 | * @param ElementNode $parent the current parent element
603 | * @param Tokenizer $tokenizer the tokenizer we're using
604 | *
605 | * @return ElementNode the new parent element
606 | */
607 | protected function parseAsTextUntilClose(ElementNode $parent, Tokenizer $tokenizer)
608 | {
609 | /* $parent's code definition doesn't allow its contents to be parsed. Here we use
610 | * a sliding window of three tokens until we find [ /tagname ], signifying the
611 | * end of the parent. */
612 | if (!$tokenizer->hasNext()) {
613 | return $parent;
614 | }
615 | $prevPrev = $tokenizer->next();
616 | if (!$tokenizer->hasNext()) {
617 | $this->createTextNode($parent, $prevPrev);
618 | return $parent;
619 | }
620 | $prev = $tokenizer->next();
621 | if (!$tokenizer->hasNext()) {
622 | $this->createTextNode($parent, $prevPrev);
623 | $this->createTextNode($parent, $prev);
624 | return $parent;
625 | }
626 | $curr = $tokenizer->next();
627 | while ('[' != $prevPrev || '/'.$parent->getTagName() != strtolower($prev) ||
628 | ']' != $curr) {
629 | $this->createTextNode($parent, $prevPrev);
630 | $prevPrev = $prev;
631 | $prev = $curr;
632 | if (!$tokenizer->hasNext()) {
633 | $this->createTextNode($parent, $prevPrev);
634 | $this->createTextNode($parent, $prev);
635 | return $parent;
636 | }
637 | $curr = $tokenizer->next();
638 | }
639 | }
640 | }
641 |
--------------------------------------------------------------------------------
/JBBCode/TextNode.php:
--------------------------------------------------------------------------------
1 | value = $val;
25 | }
26 |
27 | public function accept(NodeVisitor $visitor)
28 | {
29 | $visitor->visitTextNode($this);
30 | }
31 |
32 | /**
33 | * (non-PHPdoc)
34 | * @see JBBCode.Node::isTextNode()
35 | *
36 | * @returns boolean true
37 | */
38 | public function isTextNode()
39 | {
40 | return true;
41 | }
42 |
43 | /**
44 | * Returns the text string value of this text node.
45 | *
46 | * @return string
47 | */
48 | public function getValue()
49 | {
50 | return $this->value;
51 | }
52 |
53 | /**
54 | * (non-PHPdoc)
55 | * @see JBBCode.Node::getAsText()
56 | *
57 | * Returns the text representation of this node.
58 | *
59 | * @return string this node represented as text
60 | */
61 | public function getAsText()
62 | {
63 | return $this->getValue();
64 | }
65 |
66 | /**
67 | * (non-PHPdoc)
68 | * @see JBBCode.Node::getAsBBCode()
69 | *
70 | * Returns the bbcode representation of this node. (Just its value)
71 | *
72 | * @return string this node represented as bbcode
73 | */
74 | public function getAsBBCode()
75 | {
76 | return $this->getValue();
77 | }
78 |
79 | /**
80 | * (non-PHPdoc)
81 | * @see JBBCode.Node::getAsHTML()
82 | *
83 | * Returns the html representation of this node. (Just its value)
84 | *
85 | * @return string this node represented as HTML
86 | */
87 | public function getAsHTML()
88 | {
89 | return $this->getValue();
90 | }
91 |
92 | /**
93 | * Edits the text value contained within this text node.
94 | *
95 | * @param string $newValue the new text value of the text node
96 | */
97 | public function setValue($newValue)
98 | {
99 | $this->value = $newValue;
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/JBBCode/Tokenizer.php:
--------------------------------------------------------------------------------
1 | tokens[] = $str[$position];
37 | $position++;
38 | } else {
39 | $this->tokens[] = substr($str, $position, $offset);
40 | $position += $offset;
41 | }
42 | }
43 | }
44 |
45 | /**
46 | * Returns true if there is another token in the token stream.
47 | * @return boolean
48 | */
49 | public function hasNext()
50 | {
51 | return isset($this->tokens[$this->i + 1]);
52 | }
53 |
54 | /**
55 | * Advances the token stream to the next token and returns the new token.
56 | * @return null|string
57 | */
58 | public function next()
59 | {
60 | if (!$this->hasNext()) {
61 | return null;
62 | } else {
63 | return $this->tokens[++$this->i];
64 | }
65 | }
66 |
67 | /**
68 | * Retrieves the current token.
69 | * @return null|string
70 | */
71 | public function current()
72 | {
73 | if ($this->i < 0) {
74 | return null;
75 | } else {
76 | return $this->tokens[$this->i];
77 | }
78 | }
79 |
80 | /**
81 | * Moves the token stream back a token.
82 | */
83 | public function stepBack()
84 | {
85 | if ($this->i > -1) {
86 | $this->i--;
87 | }
88 | }
89 |
90 | /**
91 | * Restarts the tokenizer, returning to the beginning of the token stream.
92 | */
93 | public function restart()
94 | {
95 | $this->i = -1;
96 | }
97 |
98 | /**
99 | * toString method that returns the entire string from the current index on.
100 | * @return string
101 | */
102 | public function toString()
103 | {
104 | return implode('', array_slice($this->tokens, $this->i + 1));
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/JBBCode/examples/1-GettingStarted.php:
--------------------------------------------------------------------------------
1 | addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
6 |
7 | $text = "The default codes include: [b]bold[/b], [i]italics[/i], [u]underlining[/u], ";
8 | $text .= "[url=http://jbbcode.com]links[/url], [color=red]color![/color] and more.";
9 |
10 | $parser->parse($text);
11 |
12 | print $parser->getAsHtml();
13 |
--------------------------------------------------------------------------------
/JBBCode/examples/2-ClosingUnclosedTags.php:
--------------------------------------------------------------------------------
1 | addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
6 |
7 | $text = "The bbcode in here [b]is never closed!";
8 | $parser->parse($text);
9 |
10 | print $parser->getAsBBCode();
11 |
--------------------------------------------------------------------------------
/JBBCode/examples/3-MarkuplessText.php:
--------------------------------------------------------------------------------
1 | addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
6 |
7 | $text = "[b][u]There is [i]a lot[/i] of [url=http://en.wikipedia.org/wiki/Markup_language]markup[/url] in this";
8 | $text .= "[color=#333333]text[/color]![/u][/b]";
9 | $parser->parse($text);
10 |
11 | print $parser->getAsText();
12 |
--------------------------------------------------------------------------------
/JBBCode/examples/4-CreatingNewCodes.php:
--------------------------------------------------------------------------------
1 | addBBCode("quote", '{param}
');
7 | $parser->addBBCode("code", '{param}
', false, false, 1);
8 |
--------------------------------------------------------------------------------
/JBBCode/examples/SmileyVisitorTest.php:
--------------------------------------------------------------------------------
1 | addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
10 |
11 | if (count($argv) < 2) {
12 | die("Usage: " . $argv[0] . " \"bbcode string\"\n");
13 | }
14 |
15 | $inputText = $argv[1];
16 |
17 | $parser->parse($inputText);
18 |
19 | $smileyVisitor = new \JBBCode\visitors\SmileyVisitor();
20 | $parser->accept($smileyVisitor);
21 |
22 | echo $parser->getAsHTML() . "\n";
23 |
--------------------------------------------------------------------------------
/JBBCode/examples/TagCountingVisitorTest.php:
--------------------------------------------------------------------------------
1 | addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
10 |
11 | if (count($argv) < 3) {
12 | die("Usage: " . $argv[0] . " \"bbcode string\" \n");
13 | }
14 |
15 | $inputText = $argv[1];
16 | $tagName = $argv[2];
17 |
18 | $parser->parse($inputText);
19 |
20 | $tagCountingVisitor = new \JBBCode\visitors\TagCountingVisitor();
21 | $parser->accept($tagCountingVisitor);
22 |
23 | echo $tagCountingVisitor->getFrequency($tagName) . "\n";
24 |
--------------------------------------------------------------------------------
/JBBCode/tests/CodeDefinitionBuilderTest.php:
--------------------------------------------------------------------------------
1 | _builder = new CodeDefinitionBuilderStub('foo', 'bar');
14 | }
15 |
16 | public function testConstructor()
17 | {
18 | $codeDefinition = $this->_builder->build();
19 | $this->assertInstanceOf('JBBCode\CodeDefinition', $codeDefinition);
20 | $this->assertEquals('foo', $codeDefinition->getTagName());
21 | $this->assertEquals('bar', $codeDefinition->getReplacementText());
22 | }
23 |
24 | public function testSetTagName()
25 | {
26 | $this->assertSame($this->_builder, $this->_builder->setTagName('baz'));
27 | $this->assertEquals('baz', $this->_builder->build()->getTagName());
28 | }
29 |
30 | public function testSetReplacementText()
31 | {
32 | $this->assertSame($this->_builder, $this->_builder->setReplacementText('baz'));
33 | $this->assertEquals('baz', $this->_builder->build()->getReplacementText());
34 | }
35 |
36 | public function testSetUseOption()
37 | {
38 | $this->assertFalse($this->_builder->build()->usesOption());
39 | $this->assertSame($this->_builder, $this->_builder->setUseOption(true));
40 | $this->assertTrue($this->_builder->build()->usesOption());
41 | }
42 |
43 | public function testSetParseContent()
44 | {
45 | $this->assertTrue($this->_builder->build()->parseContent());
46 | $this->assertSame($this->_builder, $this->_builder->setParseContent(false));
47 | $this->assertFalse($this->_builder->build()->parseContent());
48 | }
49 |
50 | public function testSetNestLimit()
51 | {
52 | $this->assertEquals(-1, $this->_builder->build()->getNestLimit());
53 | $this->assertSame($this->_builder, $this->_builder->setNestLimit(1));
54 | $this->assertEquals(1, $this->_builder->build()->getNestLimit());
55 | }
56 |
57 | /**
58 | * @expectedException InvalidArgumentException
59 | * @dataProvider invalidNestLimitProvider
60 | */
61 | public function testSetInvalidNestLimit($limit)
62 | {
63 | $this->_builder->setNestLimit($limit);
64 | }
65 |
66 | public function testSetOptionValidator()
67 | {
68 | $this->assertEmpty($this->_builder->getOptionValidators());
69 | $urlValidator = new JBBCode\validators\UrlValidator();
70 | $this->assertSame($this->_builder, $this->_builder->setOptionValidator($urlValidator));
71 | $this->assertArrayHasKey('foo', $this->_builder->getOptionValidators());
72 | $this->assertContains($urlValidator, $this->_builder->getOptionValidators());
73 |
74 | $otherUrlValidator = new JBBCode\validators\UrlValidator();
75 | $this->assertSame($this->_builder, $this->_builder->setOptionValidator($otherUrlValidator, 'url'));
76 | $this->assertArrayHasKey('url', $this->_builder->getOptionValidators());
77 | $this->assertContains($urlValidator, $this->_builder->getOptionValidators());
78 | $this->assertContains($otherUrlValidator, $this->_builder->getOptionValidators());
79 | }
80 |
81 | public function testSetBodyValidator()
82 | {
83 | $this->assertNull($this->_builder->getBodyValidator());
84 | $validator = new JBBCode\validators\UrlValidator();
85 | $this->assertSame($this->_builder, $this->_builder->setBodyValidator($validator));
86 | $this->assertSame($validator, $this->_builder->getBodyValidator());
87 | }
88 |
89 | /**
90 | * @depends testSetOptionValidator
91 | */
92 | public function testRemoveOptionValidator()
93 | {
94 | $this->assertSame($this->_builder, $this->_builder->removeOptionValidator());
95 | $this->assertEmpty($this->_builder->getOptionValidators());
96 | $this->_builder->setOptionValidator(new JBBCode\validators\UrlValidator());
97 | $this->assertSame($this->_builder, $this->_builder->removeOptionValidator());
98 | $this->assertEmpty($this->_builder->getOptionValidators());
99 | }
100 |
101 | /**
102 | * @depends testSetBodyValidator
103 | */
104 | public function testRemoveBodyValidator()
105 | {
106 | $this->assertSame($this->_builder, $this->_builder->removeBodyValidator());
107 | $this->assertNull($this->_builder->getBodyValidator());
108 | $this->_builder->setOptionValidator(new JBBCode\validators\UrlValidator());
109 | $this->assertSame($this->_builder, $this->_builder->removeBodyValidator());
110 | $this->assertNull($this->_builder->getBodyValidator());
111 | }
112 |
113 | public function invalidNestLimitProvider()
114 | {
115 | return array(
116 | array(-2),
117 | array(null),
118 | array(false),
119 | );
120 | }
121 | }
122 |
123 | class CodeDefinitionBuilderStub extends \JBBCode\CodeDefinitionBuilder
124 | {
125 |
126 | /**
127 | * @return \JBBCode\InputValidator
128 | */
129 | public function getBodyValidator()
130 | {
131 | return $this->bodyValidator;
132 | }
133 |
134 | /**
135 | * @return \JBBCode\InputValidator[]
136 | */
137 | public function getOptionValidators()
138 | {
139 | return $this->optionValidator;
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/JBBCode/tests/DefaultCodeDefinitionSetTest.php:
--------------------------------------------------------------------------------
1 | getCodeDefinitions();
15 | $this->assertInternalType('array', $definitions);
16 |
17 | $parser = new JBBCode\Parser();
18 |
19 | $this->assertFalse($parser->codeExists('b'));
20 | $this->assertFalse($parser->codeExists('i'));
21 | $this->assertFalse($parser->codeExists('u'));
22 | $this->assertFalse($parser->codeExists('url', true));
23 | $this->assertFalse($parser->codeExists('img'));
24 | $this->assertFalse($parser->codeExists('img', true));
25 | $this->assertFalse($parser->codeExists('color', true));
26 |
27 | $parser->addCodeDefinitionSet($dcds);
28 |
29 | $this->assertTrue($parser->codeExists('b'));
30 | $this->assertTrue($parser->codeExists('i'));
31 | $this->assertTrue($parser->codeExists('u'));
32 | $this->assertTrue($parser->codeExists('url', true));
33 | $this->assertTrue($parser->codeExists('img'));
34 | $this->assertTrue($parser->codeExists('img', true));
35 | $this->assertTrue($parser->codeExists('color', true));
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/JBBCode/tests/DocumentElementTest.php:
--------------------------------------------------------------------------------
1 | _documentElement = new DocumentElement();
15 | }
16 |
17 | public function testGetTagName()
18 | {
19 | $this->assertEquals('Document', $this->_documentElement->getTagName());
20 | }
21 |
22 | public function testGetAsText()
23 | {
24 | $this->assertEmpty($this->_documentElement->getAsText());
25 | $mock = $this->getMock('JBBCode\ElementNode', array('getAsText'));
26 | $mock->expects($this->once())
27 | ->method('getAsText')
28 | ->will($this->returnValue('foo'));
29 | $this->_documentElement->addChild($mock);
30 | $this->assertEquals('foo', $this->_documentElement->getAsText());
31 | }
32 |
33 | public function testGetAsHTML()
34 | {
35 | $this->assertEmpty($this->_documentElement->getAsHTML());
36 | $mock = $this->getMock('JBBCode\ElementNode', array('getAsHTML'));
37 | $mock->expects($this->once())
38 | ->method('getAsHTML')
39 | ->will($this->returnValue('foo'));
40 | $this->_documentElement->addChild($mock);
41 | $this->assertEquals('foo', $this->_documentElement->getAsHTML());
42 | }
43 |
44 | public function testGetAsBBCode()
45 | {
46 | $this->assertEmpty($this->_documentElement->getAsBBCode());
47 | $mock = $this->getMock('JBBCode\ElementNode', array('getAsBBCOde'));
48 | $mock->expects($this->once())
49 | ->method('getAsBBCode')
50 | ->will($this->returnValue('[b]foo[/b]'));
51 | $this->_documentElement->addChild($mock);
52 | $this->assertEquals('[b]foo[/b]', $this->_documentElement->getAsBBCode());
53 | }
54 |
55 | public function testAccept()
56 | {
57 | $mock = $this->getMock('JBBCode\NodeVisitor',
58 | array('visitDocumentElement', 'visitTextNode', 'visitElementNode'));
59 | $mock->expects($this->once())
60 | ->method('visitDocumentElement')
61 | ->with($this->equalTo($this->_documentElement));
62 | $mock->expects($this->never())
63 | ->method('visitTextNode');
64 | $mock->expects($this->never())
65 | ->method('visitElementNode');
66 | $this->_documentElement->accept($mock);
67 | }
68 |
69 | public function testIsTextNode()
70 | {
71 | $this->assertFalse($this->_documentElement->isTextNode());
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/JBBCode/tests/ElementNodeTest.php:
--------------------------------------------------------------------------------
1 | _elementNode = new JBBCode\ElementNode();
11 | }
12 |
13 | public function testConstructor()
14 | {
15 | $this->assertNull($this->_elementNode->getCodeDefinition());
16 | $this->assertEmpty($this->_elementNode->getTagName());
17 | $this->assertEmpty($this->_elementNode->getAttribute());
18 | $this->assertEmpty($this->_elementNode->getChildren());
19 | $this->assertEmpty($this->_elementNode->getAsText());
20 | $this->assertEmpty($this->_elementNode->getAsHTML());
21 | }
22 |
23 | public function testAccept()
24 | {
25 | $mock = $this->getMock('JBBCode\NodeVisitor',
26 | array('visitDocumentElement', 'visitTextNode', 'visitElementNode'));
27 | $mock->expects($this->never())
28 | ->method('visitDocumentElement');
29 | $mock->expects($this->never())
30 | ->method('visitTextNode');
31 | $mock->expects($this->once())
32 | ->method('visitElementNode')
33 | ->with($this->equalTo($this->_elementNode));
34 | $this->_elementNode->accept($mock);
35 | }
36 |
37 | public function testSetCodeDefinition()
38 | {
39 | $mock = $this->getMock('JBBCode\CodeDefinition', array('getTagName'));
40 | $mock->expects($this->once())
41 | ->method('getTagName')
42 | ->will($this->returnValue('foo'));
43 | $this->_elementNode->setCodeDefinition($mock);
44 | $this->assertSame($mock, $this->_elementNode->getCodeDefinition());
45 | $this->assertEquals('foo', $this->_elementNode->getTagName());
46 | }
47 |
48 | public function testAddChild()
49 | {
50 | $mock = $this->getMock('JBBCode\ElementNode', array('setParent'));
51 | $mock->expects($this->once())
52 | ->method('setParent')
53 | ->with($this->equalTo($this->_elementNode));
54 | $this->_elementNode->addChild($mock);
55 | $this->assertContains($mock, $this->_elementNode->getChildren());
56 | }
57 |
58 | public function testIsTextNode()
59 | {
60 | $this->assertFalse($this->_elementNode->isTextNode());
61 | }
62 |
63 | public function testGetAsBBCode()
64 | {
65 | $builder = new JBBCode\CodeDefinitionBuilder('foo', 'bar');
66 | $codeDefinition = $builder->build();
67 | $this->_elementNode->setCodeDefinition($codeDefinition);
68 | $this->assertEquals('[foo][/foo]', $this->_elementNode->getAsBBCode());
69 |
70 | $this->_elementNode->setAttribute(array('bar' => 'baz'));
71 | $this->assertEquals('[foo bar=baz][/foo]', $this->_elementNode->getAsBBCode());
72 |
73 | /** @ticket 55 */
74 | $this->_elementNode->setAttribute(array(
75 | 'bar' => 'baz',
76 | 'foo' => 'bar'
77 | ));
78 | $this->assertEquals('[foo=bar bar=baz][/foo]', $this->_elementNode->getAsBBCode());
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/JBBCode/tests/ParseContentTest.php:
--------------------------------------------------------------------------------
1 | _parser = new JBBCode\Parser();
20 | $this->_parser->addCodeDefinitionSet(new JBBcode\DefaultCodeDefinitionSet());
21 | }
22 |
23 | /**
24 | * Tests that when a bbcode is created with parseContent = false,
25 | * its contents actually are not parsed.
26 | */
27 | public function testSimpleNoParsing()
28 | {
29 | $this->_parser->addBBCode('verbatim', '{param}', false, false);
30 |
31 | $this->_parser->parse('[verbatim]plain text[/verbatim]');
32 | $this->assertEquals('plain text', $this->_parser->getAsHtml());
33 |
34 | $this->_parser->parse('[verbatim][b]bold[/b][/verbatim]');
35 | $this->assertEquals('[b]bold[/b]', $this->_parser->getAsHtml());
36 | }
37 |
38 | public function testNoParsingWithBufferText()
39 | {
40 | $this->_parser->addBBCode('verbatim', '{param}', false, false);
41 |
42 | $this->_parser->parse('buffer text[verbatim]buffer text[b]bold[/b]buffer text[/verbatim]buffer text');
43 | $this->assertEquals('buffer textbuffer text[b]bold[/b]buffer textbuffer text', $this->_parser->getAsHtml());
44 | }
45 |
46 | /**
47 | * Tests that when a tag is not closed within an unparseable tag,
48 | * the BBCode output does not automatically close that tag (because
49 | * the contents were not parsed).
50 | */
51 | public function testUnclosedTag()
52 | {
53 | $this->_parser->addBBCode('verbatim', '{param}', false, false);
54 |
55 | $this->_parser->parse('[verbatim]i wonder [b]what will happen[/verbatim]');
56 | $this->assertEquals('i wonder [b]what will happen', $this->_parser->getAsHtml());
57 | $this->assertEquals('[verbatim]i wonder [b]what will happen[/verbatim]', $this->_parser->getAsBBCode());
58 | }
59 |
60 | /**
61 | * Tests that an unclosed tag with parseContent = false ends cleanly.
62 | */
63 | public function testUnclosedVerbatimTag()
64 | {
65 | $this->_parser->addBBCode('verbatim', '{param}', false, false);
66 |
67 | $this->_parser->parse('[verbatim]yo this [b]text should not be bold[/b]');
68 | $this->assertEquals('yo this [b]text should not be bold[/b]', $this->_parser->getAsHtml());
69 | }
70 |
71 | /**
72 | * Tests a malformed closing tag for a verbatim block.
73 | */
74 | public function testMalformedVerbatimClosingTag()
75 | {
76 | $this->_parser->addBBCode('verbatim', '{param}', false, false);
77 | $this->_parser->parse('[verbatim]yo this [b]text should not be bold[/b][/verbatim');
78 | $this->assertEquals('yo this [b]text should not be bold[/b][/verbatim', $this->_parser->getAsHtml());
79 | }
80 |
81 | /**
82 | * Tests an immediate end after a verbatim.
83 | */
84 | public function testVerbatimThenEof()
85 | {
86 | $parser = new JBBCode\Parser();
87 | $parser->addBBCode('verbatim', '{param}', false, false);
88 | $parser->parse('[verbatim]');
89 | $this->assertEquals('', $parser->getAsHtml());
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/JBBCode/tests/ParserTest.php:
--------------------------------------------------------------------------------
1 | _parser = new JBBCode\Parser();
13 | $this->_parser->addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
14 | }
15 |
16 | public function testAddCodeDefinition()
17 | {
18 | $parser = new JBBCode\Parser();
19 |
20 | $this->assertFalse($parser->codeExists('foo', true));
21 | $this->assertFalse($parser->codeExists('foo', false));
22 | }
23 |
24 | public function testAddBBCode()
25 | {
26 | $parser = new JBBCode\Parser();
27 |
28 | $this->assertFalse($parser->codeExists('foo', true));
29 | $this->assertFalse($parser->codeExists('foo', false));
30 |
31 | $this->assertSame($parser, $parser->addBBCode('foo', 'bar', true));
32 |
33 | $this->assertTrue($parser->codeExists('foo', true));
34 | $this->assertFalse($parser->codeExists('foo', false));
35 |
36 | $this->assertSame($parser, $parser->addBBCode('foo', 'bar', true));
37 |
38 | $this->assertTrue($parser->codeExists('foo', true));
39 | $this->assertFalse($parser->codeExists('foo', false));
40 |
41 | $this->assertSame($parser, $parser->addBBCode('foo', 'bar', false));
42 |
43 | $this->assertTrue($parser->codeExists('foo', true));
44 | $this->assertTrue($parser->codeExists('foo', false));
45 | }
46 |
47 | /**
48 | * Check for empty strings being the result of empty input
49 | */
50 | public function testParseEmptyString()
51 | {
52 | $parser = $this->_parser->parse('');
53 | $this->assertEmpty($parser->getAsBBCode());
54 | $this->assertEmpty($parser->getAsText());
55 | $this->assertEmpty($parser->getAsHTML());
56 | }
57 |
58 | /**
59 | * Test for artifacts of previous parses
60 | */
61 | public function testParseContentCleared()
62 | {
63 | $parser = $this->_parser->parse('foo');
64 |
65 | $this->assertEquals('foo', $parser->getAsText());
66 | $this->assertEquals('foo', $parser->getAsHTML());
67 | $this->assertEquals('foo', $parser->getAsBBCode());
68 |
69 | $parser->parse('bar');
70 |
71 | $this->assertEquals('bar', $parser->getAsText());
72 | $this->assertEquals('bar', $parser->getAsHTML());
73 | $this->assertEquals('bar', $parser->getAsBBCode());
74 | }
75 |
76 | /**
77 | * @param string $code
78 | * @param string[] $expected
79 | * @dataProvider textCodeProvider
80 | */
81 | public function testParse($code, $expected)
82 | {
83 | $parser = $this->_parser->parse($code);
84 | $this->assertEquals($expected['text'], $parser->getAsText());
85 | $this->assertEquals($expected['html'], $parser->getAsHTML());
86 | $this->assertEquals($expected['bbcode'], $parser->getAsBBCode());
87 | }
88 |
89 | public function textCodeProvider()
90 | {
91 | return array(
92 | array(
93 | 'foo',
94 | array(
95 | 'text' => 'foo',
96 | 'html' => 'foo',
97 | 'bbcode' => 'foo',
98 | )
99 | ),
100 | array(
101 | '[b]this is bold[/b]',
102 | array(
103 | 'text' => 'this is bold',
104 | 'html' => 'this is bold',
105 | 'bbcode' => '[b]this is bold[/b]',
106 | )
107 | ),
108 | array(
109 | '[b]this is bold',
110 | array(
111 | 'text' => 'this is bold',
112 | 'html' => 'this is bold',
113 | 'bbcode' => '[b]this is bold[/b]',
114 | )
115 | ),
116 | array(
117 | 'buffer text [b]this is bold[/b] buffer text',
118 | array(
119 | 'text' => 'buffer text this is bold buffer text',
120 | 'html' => 'buffer text this is bold buffer text',
121 | 'bbcode' => 'buffer text [b]this is bold[/b] buffer text',
122 | )
123 | ),
124 | array(
125 | 'this is some text with [b]bold tags[/b] and [i]italics[/i] and things like [u]that[/u].',
126 | array(
127 | 'text' => 'this is some text with bold tags and italics and things like that.',
128 | 'html' => 'this is some text with bold tags and italics and things like that.',
129 | 'bbcode' => 'this is some text with [b]bold tags[/b] and [i]italics[/i] and things like [u]that[/u].',
130 | )
131 | ),
132 | array(
133 | 'This contains a [url=http://jbbcode.com]url[/url] which uses an option.',
134 | array(
135 | 'text' => 'This contains a url which uses an option.',
136 | 'html' => 'This contains a url which uses an option.',
137 | 'bbcode' => 'This contains a [url=http://jbbcode.com]url[/url] which uses an option.',
138 | )
139 | ),
140 | array(
141 | 'This doesn\'t use the url option [url]http://jbbcode.com[/url].',
142 | array(
143 | 'text' => 'This doesn\'t use the url option http://jbbcode.com.',
144 | 'html' => 'This doesn\'t use the url option http://jbbcode.com.',
145 | 'bbcode' => 'This doesn\'t use the url option [url]http://jbbcode.com[/url].',
146 | )
147 | ),
148 | );
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/JBBCode/tests/ParsingEdgeCaseTest.php:
--------------------------------------------------------------------------------
1 | addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
24 | $parser->parse($bbcode);
25 | return $parser->getAsHtml();
26 | }
27 |
28 | /**
29 | * Asserts that the given bbcode matches the given html when
30 | * the bbcode is run through defaultParse.
31 | */
32 | private function assertProduces($bbcode, $html)
33 | {
34 | $this->assertEquals($html, $this->defaultParse($bbcode));
35 | }
36 |
37 | /**
38 | * Tests attempting to use a code that doesn't exist.
39 | */
40 | public function testNonexistentCodeMalformed()
41 | {
42 | $this->assertProduces('[wat]', '[wat]');
43 | }
44 |
45 | /**
46 | * Tests attempting to use a code that doesn't exist, but this
47 | * time in a well-formed fashion.
48 | *
49 | * @depends testNonexistentCodeMalformed
50 | */
51 | public function testNonexistentCodeWellformed()
52 | {
53 | $this->assertProduces('[wat]something[/wat]', '[wat]something[/wat]');
54 | }
55 |
56 | /**
57 | * Tests a whole bunch of meaningless left brackets.
58 | */
59 | public function testAllLeftBrackets()
60 | {
61 | $this->assertProduces('[[[[[[[[', '[[[[[[[[');
62 | }
63 |
64 | /**
65 | * Tests a whole bunch of meaningless right brackets.
66 | */
67 | public function testAllRightBrackets()
68 | {
69 | $this->assertProduces(']]]]]', ']]]]]');
70 | }
71 |
72 | /**
73 | * Intermixes well-formed, meaningful tags with meaningless brackets.
74 | */
75 | public function testRandomBracketsInWellformedCode()
76 | {
77 | $this->assertProduces('[b][[][[i]heh[/i][/b]',
78 | '[[][heh');
79 | }
80 |
81 | /**
82 | * Tests an unclosed tag within a closed tag.
83 | */
84 | public function testUnclosedWithinClosed()
85 | {
86 | $this->assertProduces('[url=http://jbbcode.com][b]oh yeah[/url]',
87 | 'oh yeah');
88 | }
89 |
90 | /**
91 | * Tests half completed opening tag.
92 | */
93 | public function testHalfOpenTag()
94 | {
95 | $this->assertProduces('[b', '[b');
96 | $this->assertProduces('wut [url=http://jbbcode.com',
97 | 'wut [url=http://jbbcode.com');
98 | }
99 |
100 | /**
101 | * Tests half completed closing tag.
102 | */
103 | public function testHalfClosingTag()
104 | {
105 | $this->assertProduces('[b]this should be bold[/b',
106 | 'this should be bold[/b');
107 | }
108 |
109 | /**
110 | * Tests lots of left brackets before the actual tag. For example:
111 | * [[[[[[[[b]bold![/b]
112 | */
113 | public function testLeftBracketsThenTag()
114 | {
115 | $this->assertProduces('[[[[[b]bold![/b]',
116 | '[[[[bold!');
117 | }
118 |
119 | /**
120 | * Tests a whitespace after left bracket.
121 | */
122 | public function testWhitespaceAfterLeftBracketWhithoutTag()
123 | {
124 | $this->assertProduces('[ ABC ] ',
125 | '[ ABC ] ');
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/JBBCode/tests/SimpleEvaluationTest.php:
--------------------------------------------------------------------------------
1 | addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
16 | $parser->parse($bbcode);
17 | return $parser->getAsHtml();
18 | }
19 |
20 | /**
21 | * Asserts that the given bbcode matches the given html when
22 | * the bbcode is run through defaultParse.
23 | */
24 | private function assertProduces($bbcode, $html)
25 | {
26 | $this->assertEquals($html, $this->defaultParse($bbcode));
27 | }
28 |
29 | public function testCodeOptions()
30 | {
31 | $code = 'This contains a [url=http://jbbcode.com/?b=2]url[/url] which uses an option.';
32 | $html = 'This contains a url which uses an option.';
33 | $this->assertProduces($code, $html);
34 | }
35 |
36 | public function testAttributes()
37 | {
38 | $parser = new JBBCode\Parser();
39 | $builder = new JBBCode\CodeDefinitionBuilder('img', '
');
40 | $parser->addCodeDefinition($builder->setUseOption(true)->setParseContent(false)->build());
41 |
42 | $expected = 'Multiple
options.';
43 |
44 | $code = 'Multiple [img height="50" alt="alt text"]http://jbbcode.com/img.png[/img] options.';
45 | $parser->parse($code);
46 | $result = $parser->getAsHTML();
47 | $this->assertEquals($expected, $result);
48 |
49 | $code = 'Multiple [img height=50 alt="alt text"]http://jbbcode.com/img.png[/img] options.';
50 | $parser->parse($code);
51 | $result = $parser->getAsHTML();
52 | $this->assertEquals($expected, $result);
53 | }
54 |
55 | public function testNestingTags()
56 | {
57 | $code = '[url=http://jbbcode.com][b]hello [u]world[/u][/b][/url]';
58 | $html = 'hello world';
59 | $this->assertProduces($code, $html);
60 | }
61 |
62 | public function testBracketInTag()
63 | {
64 | $this->assertProduces('[b]:-[[/b]', ':-[');
65 | }
66 |
67 | public function testBracketWithSpaceInTag()
68 | {
69 | $this->assertProduces('[b]:-[ [/b]', ':-[ ');
70 | }
71 |
72 | public function testBracketWithTextInTag()
73 | {
74 | $this->assertProduces('[b]:-[ foobar[/b]', ':-[ foobar');
75 | }
76 |
77 | public function testMultibleBracketsWithTextInTag()
78 | {
79 | $this->assertProduces('[b]:-[ [fo[o[bar[/b]', ':-[ [fo[o[bar');
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/JBBCode/tests/TextNodeTest.php:
--------------------------------------------------------------------------------
1 | _textNode = new JBBCode\TextNode('');
11 | }
12 |
13 | public function accept()
14 | {
15 | $mock = $this->getMock('JBBCode\NodeVisitor',
16 | array('visitDocumentElement', 'visitTextNode', 'visitElementNode'));
17 | $mock->expects($this->never())
18 | ->method('visitDocumentElement');
19 | $mock->expects($this->once())
20 | ->method('visitTextNode')
21 | ->with($this->equalTo($this->_textNode));
22 | $mock->expects($this->never())
23 | ->method('visitElementNode');
24 | $this->_textNode->accept($mock);
25 | }
26 |
27 | public function testIsTextNode()
28 | {
29 | $this->assertTrue($this->_textNode->isTextNode());
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/JBBCode/tests/TokenizerTest.php:
--------------------------------------------------------------------------------
1 | assertFalse($tokenizer->hasNext());
15 | $this->assertNull($tokenizer->current());
16 | $this->assertNull($tokenizer->next());
17 | $this->assertEmpty($tokenizer->toString());
18 | }
19 |
20 | public function testHasNext()
21 | {
22 | $tokenizer = new JBBCode\Tokenizer('');
23 | $this->assertFalse($tokenizer->hasNext());
24 |
25 | $tokenizer = new JBBCode\Tokenizer('[');
26 | $this->assertTrue($tokenizer->hasNext());
27 | $tokenizer->next();
28 | $this->assertFalse($tokenizer->hasNext());
29 | }
30 |
31 | public function testNext()
32 | {
33 | $tokenizer = new JBBCode\Tokenizer('[');
34 | $this->assertEquals('[', $tokenizer->next());
35 | $this->assertNull($tokenizer->next());
36 | }
37 |
38 | public function testCurrent()
39 | {
40 | $tokenizer = new JBBCode\Tokenizer('[');
41 | $this->assertNull($tokenizer->current());
42 | $tokenizer->next();
43 | $this->assertEquals('[', $tokenizer->current());
44 | }
45 |
46 | public function testStepBack()
47 | {
48 | $tokenizer = new JBBCode\Tokenizer('');
49 | $tokenizer->stepBack();
50 | $this->assertFalse($tokenizer->hasNext());
51 |
52 | $tokenizer = new JBBCode\Tokenizer('[');
53 | $this->assertTrue($tokenizer->hasNext());
54 | $this->assertEquals('[', $tokenizer->next());
55 | $this->assertFalse($tokenizer->hasNext());
56 | $tokenizer->stepBack();
57 | $this->assertTrue($tokenizer->hasNext());
58 | $this->assertEquals('[', $tokenizer->next());
59 | }
60 |
61 | public function testRestart()
62 | {
63 | $tokenizer = new JBBCode\Tokenizer('');
64 | $tokenizer->restart();
65 | $this->assertFalse($tokenizer->hasNext());
66 |
67 | $tokenizer = new JBBCode\Tokenizer('[');
68 | $tokenizer->next();
69 | $tokenizer->restart();
70 | $this->assertTrue($tokenizer->hasNext());
71 | }
72 |
73 | public function testToString()
74 | {
75 | $tokenizer = new JBBCode\Tokenizer('[');
76 | $this->assertEquals('[', $tokenizer->toString());
77 | $tokenizer->next();
78 | $this->assertEmpty($tokenizer->toString());
79 | }
80 |
81 | /**
82 | * @param string[] $tokens
83 | * @dataProvider tokenProvider()
84 | */
85 | public function testTokenize($tokens)
86 | {
87 | $string = implode('', $tokens);
88 | $tokenizer = new JBBCode\Tokenizer($string);
89 | $this->assertEquals($string, $tokenizer->toString());
90 |
91 | $this->assertTrue($tokenizer->hasNext());
92 | $this->assertNull($tokenizer->current());
93 |
94 | foreach ($tokens as $token) {
95 | $this->assertEquals($token, $tokenizer->next());
96 | }
97 |
98 | $this->assertNull($tokenizer->next());
99 | $this->assertFalse($tokenizer->hasNext());
100 | }
101 |
102 | public function tokenProvider()
103 | {
104 | return array(
105 | array(
106 | array('foo'),
107 | ),
108 | array(
109 | array('foo', '[', 'b', ']', 'bar'),
110 | ),
111 | array(
112 | array('[', 'foo', ']'),
113 | ),
114 | );
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/JBBCode/tests/bootstrap.php:
--------------------------------------------------------------------------------
1 | _validator = new JBBCode\validators\CssColorValidator();
14 | }
15 |
16 | /**
17 | * @param string $color
18 | * @dataProvider validColorProvider
19 | */
20 | public function testValidColors($color)
21 | {
22 | $this->assertTrue($this->_validator->validate($color));
23 | }
24 |
25 | public function validColorProvider()
26 | {
27 | return array(
28 | array('red'),
29 | array('yellow'),
30 | array('LightGoldenRodYellow'),
31 | array('#000'),
32 | array('#00ff00'),
33 | array('rgba(255, 0, 0, 0.5)'),
34 | array('rgba(50, 50, 50, 0.0)'),
35 | );
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/JBBCode/tests/validators/FnValidatorTest.php:
--------------------------------------------------------------------------------
1 | assertTrue($validator->validate('1234567890'));
14 | $this->assertFalse($validator->validate('QWERTZUIOP'));
15 | }
16 |
17 | /**
18 | * Provide custom numeric string validator implementations.
19 | *
20 | */
21 | public function validatorProvider()
22 | {
23 | return array(
24 | array(new JBBCode\validators\FnValidator('is_numeric')),
25 | array(new JBBCode\validators\FnValidator(function ($input) {
26 | return is_numeric($input);
27 | })),
28 | );
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/JBBCode/tests/validators/UrlValidatorTest.php:
--------------------------------------------------------------------------------
1 | _validator = new JBBCode\validators\UrlValidator();
14 | }
15 |
16 | /**
17 | * @param string $url
18 | * @dataProvider invalidUrlProvider
19 | */
20 | public function testInvalidUrl($url)
21 | {
22 | $this->assertFalse($this->_validator->validate($url));
23 | }
24 |
25 | /**
26 | * @param string $url
27 | * @dataProvider validUrlProvider
28 | */
29 | public function testValidUrl($url)
30 | {
31 | $this->assertTrue($this->_validator->validate($url));
32 | }
33 |
34 | public function invalidUrlProvider()
35 | {
36 | return array(
37 | array('#yolo#swag'),
38 | array('giehtiehwtaw352353%3'),
39 | );
40 | }
41 |
42 | public function validUrlProvider()
43 | {
44 | return array(
45 | array('http://google.com'),
46 | array('http://jbbcode.com/docs'),
47 | array('https://www.maps.google.com'),
48 | );
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/JBBCode/tests/validators/ValidatorTest.php:
--------------------------------------------------------------------------------
1 | addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
19 | $parser->parse('[url=javascript:alert("HACKED!");]click me[/url]');
20 | $this->assertEquals('[url=javascript:alert("HACKED!");]click me[/url]',
21 | $parser->getAsHtml());
22 | }
23 |
24 | /**
25 | * Tests an invalid url as the body to a url bbcode.
26 | *
27 | */
28 | public function testInvalidBodyUrlBBCode()
29 | {
30 | $parser = new JBBCode\Parser();
31 | $parser->addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
32 | $parser->parse('[url]javascript:alert("HACKED!");[/url]');
33 | $this->assertEquals('[url]javascript:alert("HACKED!");[/url]', $parser->getAsHtml());
34 | }
35 |
36 | /**
37 | * Tests a valid url as the body to a url bbcode.
38 | *
39 | */
40 | public function testValidUrlBBCode()
41 | {
42 | $parser = new JBBCode\Parser();
43 | $parser->addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
44 | $parser->parse('[url]http://jbbcode.com[/url]');
45 | $this->assertEquals('http://jbbcode.com',
46 | $parser->getAsHtml());
47 | }
48 |
49 | /**
50 | * Tests invalid CSS color values on the CssColorValidator.
51 | */
52 | public function testInvalidCssColor()
53 | {
54 | $colorValidator = new JBBCode\validators\CssColorValidator();
55 | $this->assertFalse($colorValidator->validate('" onclick="javascript: alert(\"gotcha!\");'));
56 | $this->assertFalse($colorValidator->validate('">