Page 2 of 2
Re: bb code library - create your own bbcode system
Posted: Sun Oct 31, 2010 2:19 am
by s.dot
Thanks for the suggestion.
I'm also adding in emoticon support.
Dang, I'm embarassed to have the original version up there now

I've done so many improvements already but probably won't have the next version done for another week or so.
Re: bb code library - create your own bbcode system
Posted: Sun Oct 31, 2010 2:28 am
by s.dot
josh wrote:This syntax also irks me:
Code: Select all
$img = new BB_Tag('img');
$img->setReplacements(array('<img src="', '" alt="Image">'));
(for example what if I wanted both src="" and alt="" to be sourced from the BB codes)
I suppose you could do it like this:
Code: Select all
$img = new BB_Tag('img');
$img->hasParameter();
$img->setReplacements(array(
array('<img src="', '" alt="Image">'),
array('<img alt="{placeholder}" src="', '">')
);
Re: bb code library - create your own bbcode system
Posted: Sun Oct 31, 2010 4:57 am
by josh
s.dot wrote:Dang, I'm embarassed to have the original version up there now

If you were not, you'd be doing it wrong (not improving as time moves forward)
s.dot wrote:
I suppose you could do it like this:
Code: Select all
$img = new BB_Tag('img');
$img->hasParameter();
$img->setReplacements(array(
array('<img src="', '" alt="Image">'),
array('<img alt="{placeholder}" src="', '">')
);
How about:
Code: Select all
$img = new BB_Tag('img');
$img->hasParameter();
$img->setReplacements(array(
array('<img src="{img_src}" alt="{alt_txt}">'),
);
Re: bb code library - create your own bbcode system
Posted: Sun Oct 31, 2010 3:39 pm
by Jonah Bron
But how would it know what {img_src} and {alt_txt} are? With the method we discussed earlier in which you set the replacement text?
If you're working on a significantly improved version, I'll probably hold off on the paragraph thing.

Re: bb code library - create your own bbcode system
Posted: Sun Oct 31, 2010 7:15 pm
by Christopher
How about:
Code: Select all
$img->setParameters(array('url', 'alt_txt'));
// sprintf like character
$img->setReplacement('<img src="%" alt="%">');
// php template like tags using above names
$img->setReplacement('<img src="{url}" alt="{alt_txt}">')
Re: bb code library - create your own bbcode system
Posted: Sun Oct 31, 2010 8:11 pm
by Jonah Bron
Okay, here's the results. Just one bug: there can be empty paragraphs at beginning and end (can, not always).
The tag class has a new setter and corresponding variable: setInside() and $inside.
The logic is very simple. It starts by wrapping the text with "<p>" and "</p>". In each makeTag loop, it wraps each tag that's has stated it can't be in a paragraph with "</p>" and "<p>". If parseContents is true, it also plasters "<p>" and "</p>" to the inside of the tag. Yes, the orders of <p> and </p> are correct.
Here are the files:
bb.class.php:
Code: Select all
<?php
/**
* This package allows one to create their own bb code tags and define the
* replacements for them.
*
* Package: BB
* File: bb.class.php
* Version: 1.0.0
* Author: Scott Martin <sjm.dev1[at]gmail[dot]com>
* Date: October 28, 2010
* License: GNU GPL 3.0
*
* Features:
* - Create your own BB code style tags and define the HTML replacements for
* them.
* - Allows tags with parameters.. eg [ quote="name" ] text [ /quote ]
* - Allows text between tags to be used as a parameter..
* eg [ url ] http://www.example.com [ /url ]
* - Allows for text between tags to not be parsed for bb code style tags,
* useful for tags such as [ code ] text [ /code ]
* - Allows functions to be called on the text between tags
* - Correctly parses nested bb code style tags of the same kind (assuming
* input string is correctly nested, see Todo)
* - Correctly parses nested bb code style tags of any kind (assuming input
* string is correctly nested, see Todo)
*
* Todo:
* - Create a single regex to match all BB code style tags rather than
* recursively calling preg_match_all()
* - Iterate through the string to be parsed and check for (and fix)
* incorrect nesting prior to parsing
* - Cleanse parameters for URLs, or allow a custom cleansing function to be
* called on any parameter.
* - Add more error checking and verbosity
*
<BB creation and definition system>
Copyright (C) <2010> <Scott Martin>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
class BB
{
//string holder
private $str = '';
//bbtags holder
private $tags = array();
/**
* Converts allowed BB code tags to HTML.
*
* @access public
* @param string $str
* @return string
*/
public function make($str)
{
$this->str = '<p>' . str_replace("\n\n", "</p>\n\n<p>", $str) . '</p>';
if ($matches = $this->pregMatchRecursive($str))
{
foreach ($matches AS $match)
{
$this->makeTag($match);
}
}
return $this->str;
}
/**
* Recursively parses $str for bb code tags. This function would be entirely
* unnecessary if I could write a good enough regex.
*
* @access private
* @param string $str
* @return mixed - array of matches or boolean false when no matches are found
*/
private function pregMatchRecursive($str)
{
$ret = array();
if ((strpos($str, '[') !== false) && (strpos($str, ']') !== false))
{
if (preg_match_all('#\[(\w+)(=.+?)?\]((?:[^[]|\[(?!/?\\1(=.+?)?\])|(?R))+)\[/\\1\]#s', $str, $matches, PREG_SET_ORDER))
{
$ret = array_merge($ret, $matches);
foreach ($matches AS $match)
{
$match[1] = strtolower($match[1]);
if (array_key_exists($match[1], $this->tags))
{
if ($this->tags[$match[1]]->parseContents)
{
if ($newMatches = $this->pregMatchRecursive($match[3]))
{
$ret = array_merge($ret, $newMatches);
}
}
}
}
return $ret;
}
}
return false;
}
/**
* Adds a BB code tag to the list of allowed tags.
*
* @access public
* @param object $tag - object of type BB_Tag
* @return void
*/
public function addTag($tag)
{
$this->tags[$tag->tag] = $tag;
}
/**
* Does the replacing of BB code tags to their HTML counterparts.. or
* manipulation of content between BB code tags, depending on the tags'
* parameters.
*
* @access private
* @param array $match
* @return void
*/
private function makeTag($match)
{
//apply function to matched content
if (count($this->tags[$match[1]]->applyFunction))
{
$params = $match[3];
if (!empty($this->tags[$match[1]]->applyFunction[1]))
{
$params = $this->tags[$match[1]]->applyFunction[1];
array_unshift($params, $match[3]);
}
$match[3] = call_user_func_array($this->tags[$match[1]]->applyFunction[0], $params);
}
$inside = array('', '');
$parse = array('', '');
if (isset($this->tags[$match[1]]) && $this->tags[$match[1]]->inside == false)
{
$inside = array("</p>\n\n", '<p>');
if ($this->tags[$match[1]]->parseContents == true)
{
$parse = array('<p>', "</p>\n\n");
}
}
//simple tag (e.g. <span style="font-weight: bold">text</span>)
if (!$this->tags[$match[1]]->parameter)
{
$this->str = str_replace(
$match[0],
$inside[0] . $this->tags[$match[1]]->replacements[0] . $parse[0] . $match[3] . $parse[1] . $this->tags[$match[1]]->replacements[1] . $inside[1],
$this->str
);
return;
}
//tags with parameter (e.g. <blockquote class="uncited"><div>text</div></blockquote> or <blockquote><div><cite>name wrote:</cite>text</div></blockquote>)
if ($this->tags[$match[1]]->parameter == 1)
{
if (empty($match[2]) === true)
{
if (empty($this->tags[$match[1]]->replacements[0]) !== true)
{
if ($this->tags[$match[1]]->useTextAsParameter)
{
$this->str = str_replace(
$match[0],
$inside[0] . str_replace(
'{placeholder}',
$match[3],
$this->tags[$match[1]]->replacements[0][0]
) . $parse[0] . $match[3] . $parse[1] . $this->tags[$match[1]]->replacements[0][1] . $inside[1],
$this->str
);
} else
{
$this->str = str_replace(
$match[0],
$inside[0] . $this->tags[$match[1]]->replacements[0][0] . $parse[0] . $match[3] . $parse[1] . $this->tags[$match[1]]->replacements[0][1] . $inside[1],
$this->str
);
}
}
} else
{
//remove = and "" or '' in parameter supplied
$match[2] = substr($match[2], 1);
if ((substr($match[2], 0, 1) == '"' && substr($match[2], -1) == '"')|| (substr($match[2], 0, 1) == '\'' && substr($match[2], -1) == '\''))
{
$match[2] = substr(substr($match[2], 0, strlen($match[2]) - 1), 1);
}
$this->str = str_replace(
$match[0],
$inside[0] . str_replace(
'{placeholder}',
$match[2],
$this->tags[$match[1]]->replacements[1][0]
) . $parse[0] . $match[3] . $parse[1] . $this->tags[$match[1]]->replacements[1][1] . $inside[1],
$this->str
);
}
return;
}
}
}
bb_tag.class.php:
Code: Select all
<?php
/**
* This package allows one to create their own bb code tags and define the
* replacements for them.
*
* Package: BB
* File: bb_tag.class.php
* Version: 1.0.0
* Author: Scott Martin <sjm.dev1[at]gmail[dot]com>
* Date: October 28, 2010
*
* Features:
* - Create your own BB code style tags and define the HTML replacements for
* them.
* - Allows tags with parameters.. eg [ quote="name" ] text [ /quote ]
* - Allows text between tags to be used as a parameter..
* eg [ url ] http://www.example.com [ /url ]
* - Allows for text between tags to not be parsed for bb code style tags,
* useful for tags such as [ code ] text [ /code ]
* - Allows functions to be called on the text between tags
* - Correctly parses nested bb code style tags of the same kind (assuming
* input string is correctly nested, see Todo)
* - Correctly parses nested bb code style tags of any kind (assuming input
* string is correctly nested, see Todo)
*
* Todo:
* - Create a single regex to match all BB code style tags rather than
* recursively calling preg_match_all()
* - Iterate through the string to be parsed and check for (and fix)
* incorrect nesting prior to parsing
* - Cleanse parameters for URLs, or allow a custom cleansing function to be
* called on any parameter.
* - Add more error checking and verbosity
*
<BB creation and definition system>
Copyright (C) <2010> <Scott Martin>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
class BB_Tag
{
//tag name
private $tag;
//tag has parameter?
private $parameter = false;
//use text between tags as parameter?
private $useTextAsParameter = false;
//html replacements for bb code tags
private $replacements;
//inside of <p> tag?
private $inside = true;
//parse the contents between bb code tags for additional bbcode tags?
private $parseContents = true;
//apply a function to contents between bb code tags?
private $applyFunction = array();
/**
* Sets the tag name
*
* @access public
* @param string $tag - the name of the BB code tag
* @return void
*/
public function __construct($tag)
{
$this->tag = $tag;
}
/**
* Sets the HTML replacement tags for the opening and closing BB code tags
*
* @access public
* @param array $replacement
* @return void
*/
public function setReplacements($replacement)
{
$this->replacements = $replacement;
}
/**
* Sets whether or not the tag can be inside of a <p> tag
*
* @author Jonah Dahlquint <jonahbron.d[at]gmail[dot]com>
* @access public
* @param boolean $inside
* @return void
*/
public function setInside($inside)
{
$this->inside = (bool) $inside;
}
/**
* Notifies this object that this bb code tag has a parameter
*
* @access public
* @param void
* @access void
*/
public function hasParameter()
{
$this->parameter = true;
}
/**
* Notifies this object to use the text between the opening and closing BB
* code tags as a parameter.
*
* @access public
* @param void
* @return void
*/
public function useTextAsParameter()
{
$this->useTextAsParameter = true;
}
/**
* Tells this object whether or not to parse the contents between this tags'
* open and close for more BB code tags.
*
* @access public
* @param boolean $bool
* @return void
*/
public function parseContents($bool)
{
$this->parseContents = $bool;
}
/**
* Tells the object to apply a function to the contents between this tags'
* open and close.
*
* @access public
* @param mixed $function - string function name or array($objectInstance, 'methodName')
* @param array $params - parameters to pass to the function
* @return void
*/
public function applyFunction($function, $params=array())
{
$this->applyFunction = array($function, $params);
}
//accessor for object properties
public function __get($var)
{
return $this->$var;
}
}
Examples:
Code: Select all
$bold = new BB_Tag('b');
$bold->setReplacements(array('<strong>', '</strong>'));
$bold->setInside(true);
$div = new BB_Tag('div');
$div->setReplacements(array('<div style="border:solid 1px #000000;">', '</div>'));
$div->setInside(false);
I realize you're working on a new version. Just thought I'd say it might improve readability to reduce the number of If levels (especially in pregMatchRecursive()).
Re: bb code library - create your own bbcode system
Posted: Sun Oct 31, 2010 9:53 pm
by s.dot
Thanks Jonah
The new version has actually been quite restructured and factored down better. I'm very proud of it and realize it might be useful so I'm being very eager to work on it!
Thanks to your guys' suggestions I feel like this is going to be one awesome library.
by the way, i don't think leading/trailing empty <p></p>'s are a bug. If that's the way it is entered that is the way it should be parsed, right? I'll see if I can work the p into the new version
Re: bb code library - create your own bbcode system
Posted: Sun Oct 31, 2010 10:01 pm
by Jonah Bron
It only occurs if the very first and/or last thing in the input text is a tag that has $inside set to false. For example:
Code: Select all
$div = new BB_Tag('div');
$div->setReplacements(array('<div>', '</div>'));
$div->setInside(false);
$bb = new BB();
$bb->addTag($div);
echo $bb->make("[div] hello world [/div]");
/* outputs:
<p></p><div> hello world</div><p></p>
*/
As you can see, the user didn't really want those paragraphs there. I suppose that could be fixed with a few more IFs. Anticipating the new version!

Re: bb code library - create your own bbcode system
Posted: Mon Nov 01, 2010 1:27 am
by s.dot
The paragraph thing is very interesting.
Not only because of the challenge of block/inline elements, but because of the need to set an additional parameter AND the margins it creates at the top of say, a TD or DIV.
I may leave the default set to use nl2br() but make an additional parameter for those that wish to use paragraph formatting. It will be challenging. I may define all block level elements and check the replacement for existence of a block level element. It will be interesting.
Another thing: Checking input for correct nesting and closing tags.
Is this really needed? The regexp checks for full matches (start tag, text, end tag) and only replaces if the whole match is found. I realize it may be beneficial to potentially create valid html output, but that doesn't stop someone from putting in [ b ][ quote ]text[ /quote ][ /b ] which would break the validity anyways.
Re: bb code library - create your own bbcode system
Posted: Mon Nov 01, 2010 11:10 am
by josh
The level of nesting in the code is still horrendous. I would't be confident at all to modify your code as it is now. You may or may not be aware. We all know what clean code is, but some of us could use a few pointers on how to maintain clean code. I'd recommend trying out the testing thing. (or maybe you can do this with the tests you already have?). Once you have tests that allow you to detect breakages at the push of a button, you can then confidently refactor the code and confidentially say you didn't change any behavior. Here are the first 2 refactorings this code needs.
http://www.refactoring.com/catalog/deco ... ional.html
http://www.refactoring.com/catalog/repl ... auses.html
Work in very small changes, run the tests after every little changes & commit the code at any sign of progress or improvement (but never when the tests fail). Just follow these rules and apply refactorings the moment you think of them (but never while also working on functionality). You will realize great benefit. If you break the tests, revert the code after 10-20 minutes. Keep your changes smaller than that amount of time or keep the tests passing, if you can't keep the tests passing you aren't skilled enough to do a change that large. These are the rules I live by.
Re: bb code library - create your own bbcode system
Posted: Mon Nov 01, 2010 11:53 am
by Jonah Bron
s.dot wrote:The paragraph thing is very interesting.
Not only because of the challenge of block/inline elements, but because of the need to set an additional parameter AND the margins it creates at the top of say, a TD or DIV.
I may leave the default set to use nl2br() but make an additional parameter for those that wish to use paragraph formatting. It will be challenging. I may define all block level elements and check the replacement for existence of a block level element. It will be interesting.
Another thing: Checking input for correct nesting and closing tags.
Is this really needed? The regexp checks for full matches (start tag, text, end tag) and only replaces if the whole match is found. I realize it may be beneficial to potentially create valid html output, but that doesn't stop someone from putting in [ b ][ quote ]text[ /quote ][ /b ] which would break the validity anyways.
Yes, it gets very complicated. There are two routes: the easy route, or the thorough route. I usually lean toward the thorough route, but that's not always a good thing.
You could try to come up with something more simple yet powerful, and do everything possible to keep it from being too complex. What I mean by that is, for example:
Each BB_Tag object knows what kind of tag it's in. Either a "super level" tag that can only be on top, or inside of another "super level" tag. Then, there is the other kind, "sub level" tags. Sub level tags cannot be in the top level: they must be inside of a paragraph. They can be inside of "super level" tags, but still inside of a <p> tag.
This system could be handled in a very similar way how it handles paragraphs now. If you don't feel like doing it, I just might. And yes, this probably should be optional.
Re: bb code library - create your own bbcode system
Posted: Mon Nov 01, 2010 2:26 pm
by s.dot
Those are sweet links, thanks.
I have refactored a lot in the newer version I'm working on, for example here is the makeTags() and makeSimpleTag() functions
I'll look for areas I can refactor further
Code: Select all
private function makeTags($matches)
{
foreach ($matches AS $match)
{
#each $match consists of the following
#$match[0] = full match [tagname]contents[/tagname]
#$match[1] = tag name
#$match[2] = optional parameter (e.g. ="name")
#$match[3] = the contents between the opening and closing bb code tag
//applying a function to the tag contents
if (count($this->tags[$match[1]]->applyFunction))
{
$match[3] = $this->applyFunction($match);
}
//tag with no parameter replacement (e.g. [b]text[/b])
if (!$this->tags[$match[1]]->parameter)
{
$this->str = $this->makeSimpleTag($match);
continue;
}
//tags with parameter (e.g. [quote]text[/quote] or [quote="name"]text[/quote])
if ($this->tags[$match[1]]->parameter == 1)
{
$this->str = $this->makeParameterTag($match);
}
}
}
private function makeSimpleTag($match)
{
return str_replace(
$match[0],
$this->tags[$match[1]]->replacements[0] . $match[3] . $this->tags[$match[1]]->replacements[1],
$this->str
);
}
Re: bb code library - create your own bbcode system
Posted: Mon Nov 01, 2010 2:57 pm
by Jonah Bron

Major improvement.
Um, it looks like it doesn't check if that tag exists in $this->tags, but I could be wrong. Seems like it might throw an "undefined index" error?
Re: bb code library - create your own bbcode system
Posted: Mon Nov 01, 2010 3:14 pm
by s.dot
Jonah Bron wrote:
Major improvement.
Um, it looks like it doesn't check if that tag exists in $this->tags, but I could be wrong. Seems like it might throw an "undefined index" error?
The new regexp will only make matches for defined tags, if there are any. eg "/(b|u|i|etc)..../i", so any match that makes it to the makeTags() function will be present in the tag list
Re: bb code library - create your own bbcode system
Posted: Tue Nov 02, 2010 6:18 am
by josh
Jonah Bron wrote: Seems like it might throw an "undefined index" error?
s.dot wrote:any match that makes it to the makeTags() function will be present in the tag list
Gotta love having tests. If you didn't have tests, you'd be writing 10x as much code out of paranoia

(and therefore introducing more bugs. More code = more bugs.) Instead of asking yourself if it works, and writing code "just in case" - you can just press a button to "ask" the computer if it works. Nice. The only problem is your tests only touch the 'happy' path. If that method is not protected someone could maybe get it to execute under a different path by calling that function directly. You should ideally have a test that shows the thing handles 'undefined tags' by throwing an exception, escaping it, etc.. If the method is protected then I guess its not a problem right now