Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <?php
- //------------------------------------------------------------------------------
- /**
- * Interactive Bulletin Board Code Parser
- *
- * IBBC provides an interface for highly flexable BBCode parsing by allowing
- * developers to include their own BBCode elements.
- *
- * @author Rowan Lewis <rowan@pixelcarnage.com>
- * @version 0.012
- * @package IBBC
- */
- define("IBBC_EXP_OPEN", '\[[a-z]+[^\]]*\]');
- define("IBBC_EXP_OPENBITS", '\[([a-z]+)([\s=:](\s*([^\s]+))*)?\]');
- define("IBBC_EXP_CLOSE", '\[\/[a-z]+\]');
- define("IBBC_EXP_CLOSEBITS", '\[\/([a-z]+)\]');
- define("IBBC_EXP_VALIDNAME", '/^[a-z][a-z\-_]*$/i');
- /**
- * The child base class.
- */
- class IBBCChild {
- private $__parent = null;
- public function __construct($parent) {
- $this->__parent = $parent;
- }
- /**
- * Insert an object after the current.
- *
- * @param object $element An instance of IBBCChild.
- * @return object
- */
- public function insertAfter($element) {
- if (!$this->parent()) return null;
- $children = $this->parent()->children();
- $position = array_search($this, $children);
- if ($position === false) return null;
- $before = array_slice($children, 0, $position + 1);
- $after = array_slice($children, $position + 1);
- $children = array_merge(
- $before, array($element), $after
- );
- $this->parent()->children($children);
- return $this;
- }
- /**
- * Insert an object before the current.
- *
- * @param object $element An instance of IBBCChild.
- * @return object
- */
- public function insertBefore($element) {
- if (!$this->parent()) return null;
- $children = $this->parent()->children();
- $position = array_search($this, $children);
- if ($position === false) return null;
- $before = array_slice($children, 0, $position);
- $after = array_slice($children, $position);
- $children = array_merge(
- $before, array($element), $after
- );
- $this->parent()->children($children);
- return $this;
- }
- /**
- * Get or set the parent object.
- *
- * @param object $parent An instance of IBBCParent.
- * @return object
- */
- public function parent($parent = false) {
- if (is_object($parent)) {
- $this->__parent = $parent;
- return $this;
- } else {
- return $this->__parent;
- }
- }
- /**
- * Replace the current object with another.
- *
- * @param object $element An instance of IBBCChild.
- * @return object
- */
- public function replace($element) {
- if (!$this->parent()) return null;
- $children = $this->parent()->children();
- $position = array_search($this, $children);
- if ($position === false) return null;
- $children[$position] = $element;
- $this->parent()->children($children);
- $this->parent(null);
- return $this;
- }
- /**
- * Wrap the current object in another.
- *
- * @param object $wrapper An instance of IBBCParent.
- * @return object
- */
- public function wrap($wrapper) {
- $this->replace($wrapper);
- $wrapper->children($this);
- }
- }
- /**
- * The parent base class.
- *
- * @extends IBBCChild
- */
- class IBBCParent extends IBBCChild {
- private $__children = array();
- public function __toString() {
- $output = "";
- foreach ($this->__children as $child) {
- $output .= $child;
- }
- return $output;
- }
- /**
- * Get a child object with a specific index.
- *
- * @param number $index A valid integer.
- * @return object
- */
- public function child($index) {
- if ($index < 0) {
- $index = count($this->__children) - (0 - $index);
- }
- if (is_int($index) and count($this->__children) >= $index) {
- return $this->__children[$index];
- }
- return null;
- }
- /**
- * Get the first child object.
- *
- * @return object
- */
- public function childFirst() {
- if (count($this->__children)) {
- return $this->__children[0];
- }
- return null;
- }
- /**
- * Get the last child object.
- *
- * @return object
- */
- public function childLast() {
- if (count($this->__children)) {
- return $this->__children[count($this->__children) - 1];
- }
- return null;
- }
- /**
- * Get or set the current objects children.
- *
- * @param array $children An array of IBBCChild.
- * @return object
- */
- public function children($children = false) {
- if (is_array($children) or is_object($children)) {
- foreach ($this->__children as $child) {
- $child->parent(null);
- }
- if (!is_array($children)) {
- $children = array($children);
- }
- $this->__children = $children;
- foreach ($this->__children as $child) {
- $child->parent($this);
- }
- return $this;
- } else {
- return $this->__children;
- }
- }
- /**
- * Insert a child object before all others.
- *
- * @param array $child An instance of IBBCChild.
- * @return object
- */
- public function insertFirst($child) {
- $child->parent($this);
- array_unshift($this->__children, $child);
- return $this;
- }
- /**
- * Insert a child object after all others.
- *
- * @param array $child An instance of IBBCChild.
- * @return object
- */
- public function insertLast($child) {
- $child->parent($this);
- array_push($this->__children, $child);
- return $this;
- }
- /**
- * Wrap the current object in another.
- *
- * @param object $wrapper An instance of IBBCParent.
- * @return object
- */
- public function wrap($wrapper) {
- $wrapper->children($this->children());
- $this->children($wrapper);
- }
- }
- /**
- * A data object.
- *
- * @extends IBBCParent
- */
- class IBBCElement extends IBBCParent {
- private $__name = "";
- private $__args = array();
- public function __construct($parent, $name, $args) {
- parent::__construct($parent);
- $this->__name = strtolower($name); $this->__args = $args;
- }
- public function __toString() {
- $output = "";
- foreach ($this->children() as $child) {
- $output .= $child;
- }
- return $output;
- }
- /**
- * Get an array of arguments.
- *
- * @return array
- */
- public function args() {
- return $this->__args;
- }
- /**
- * Get or set the element name.
- *
- * @param string $name An alphabetic string.
- * @return string
- */
- public function name($name = false) {
- if (is_string($name)) {
- if (!preg_match(IBBC_EXP_VALIDNAME, $name) or !$name) {
- throw new Exception("Invalid element name '{$name}'.");
- }
- $this->__name = $name; return $this;
- } else {
- return $this->__name;
- }
- }
- }
- /**
- * A textual object.
- *
- * @extends IBBCChild
- */
- class IBBCContent extends IBBCChild {
- private $__value = "";
- public function __construct($parent, $value) {
- parent::__construct($parent);
- $this->__value = $value;
- }
- public function __toString() {
- return htmlentities($this->value());
- }
- /**
- * Get or set the content value.
- *
- * @param string $value A string of user input.
- * @return string
- */
- public function value($value = false) {
- if (is_string($value)) {
- $this->__value = $value; return $this;
- } else {
- return $this->__value;
- }
- }
- }
- /**
- * Create a new BBCode parser.
- */
- class IBBCParser {
- private $elements = array();
- private $stack = array();
- private $source = "";
- private $output = null;
- /**
- * Register a class as an element.
- *
- * @param string $name An alphabetic string.
- * @param string $class The name of a class.
- */
- public function addElement($name, $class) {
- if (!preg_match(IBBC_EXP_VALIDNAME, $name) or !$name) {
- throw new Exception("Invalid element name '{$name}'.");
- }
- $this->elements["$name"] = $class;
- }
- /**
- * Parse a chunk of text.
- *
- * @param string $source The string to parse.
- */
- public function parse($source) {
- $this->source = $source;
- $this->stack = $this->stack();
- $this->output = $this->tree();
- return $this->output;
- }
- private function stack() {
- $chunks = preg_split('/(' . IBBC_EXP_OPEN . '|' . IBBC_EXP_CLOSE . ')/i', $this->source, -1, PREG_SPLIT_DELIM_CAPTURE);
- $order = array(); $stack = array(); $matches = array();
- // Create single dimensional stack:
- foreach ($chunks as $chunk) {
- // Open?
- if (preg_match('/^' . IBBC_EXP_OPENBITS . '$/i', $chunk, $matches)) {
- $name = strtolower($matches[1]); $args = array();
- if (count($matches) > 2) {
- $args = preg_split('/\s+/', substr($matches[2], 1));
- }
- array_push($order, $name);
- array_push($stack, array(
- "type" => "open",
- "name" => $name,
- "args" => $args
- ));
- // Close?
- } else if (preg_match('/^' . IBBC_EXP_CLOSEBITS . '$/i', $chunk, $matches)) {
- $name = strtolower($matches[1]); $open = array_pop($order);
- // Tag mismatch:
- if ($open != $name and !(array_search($name, $order) === false)) {
- array_push($order, $open);
- while ($current = array_pop($order)) {
- if ($current == $name) break;
- array_push($stack, array(
- "type" => "close",
- "name" => $current
- ));
- }
- $open = $current;
- }
- // Not open:
- if ($open != $name) {
- array_push($stack, array(
- "type" => "text",
- "value" => $chunk
- ));
- continue;
- }
- array_push($stack, array(
- "type" => "close",
- "name" => $name
- ));
- // Text:
- } else {
- array_push($stack, array(
- "type" => "text",
- "value" => $chunk
- ));
- }
- }
- // Force all elements to close:
- while ($current = array_pop($order)) {
- array_push($stack, array(
- "type" => "close",
- "name" => $current
- ));
- }
- return $stack;
- }
- private function tree() {
- $output = new IBBCParent(null);
- $parent = $output; $content = null;
- foreach ($this->stack as $current) {
- // Open:
- if ($current["type"] == "open") {
- if (array_key_exists($current["name"], $this->elements)) {
- $class = $this->elements[$current["name"]];
- } else {
- $class = "IBBCElement";
- }
- $temp = new $class(null, $current["name"], $current["args"]);
- $parent->insertLast($temp); $parent = $temp; $content = null;
- // Close:
- } else if ($current["type"] == "close") {
- $parent = $parent->parent(); $content = null;
- // Text:
- } else {
- if ($content) {
- $content->value($content->value() . $current["value"]);
- } else {
- $content = new IBBCContent(null, $current["value"]);
- $parent->insertLast($content);
- }
- }
- }
- return $output;
- }
- }
- //------------------------------------------------------------------------------
- ?>
Add Comment
Please, Sign In to add comment