Guest User

Untitled

a guest
Apr 20th, 2018
242
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 10.93 KB | None | 0 0
  1. <?php
  2. //------------------------------------------------------------------------------
  3.  
  4. /**
  5. * Interactive Bulletin Board Code Parser
  6. *
  7. * IBBC provides an interface for highly flexable BBCode parsing by allowing
  8. * developers to include their own BBCode elements.
  9. *
  10. * @author Rowan Lewis <rowan@pixelcarnage.com>
  11. * @version 0.012
  12. * @package IBBC
  13. */
  14.  
  15. define("IBBC_EXP_OPEN", '\[[a-z]+[^\]]*\]');
  16. define("IBBC_EXP_OPENBITS", '\[([a-z]+)([\s=:](\s*([^\s]+))*)?\]');
  17. define("IBBC_EXP_CLOSE", '\[\/[a-z]+\]');
  18. define("IBBC_EXP_CLOSEBITS", '\[\/([a-z]+)\]');
  19. define("IBBC_EXP_VALIDNAME", '/^[a-z][a-z\-_]*$/i');
  20.  
  21. /**
  22. * The child base class.
  23. */
  24. class IBBCChild {
  25. private $__parent = null;
  26.  
  27. public function __construct($parent) {
  28. $this->__parent = $parent;
  29. }
  30.  
  31. /**
  32. * Insert an object after the current.
  33. *
  34. * @param object $element An instance of IBBCChild.
  35. * @return object
  36. */
  37. public function insertAfter($element) {
  38. if (!$this->parent()) return null;
  39.  
  40. $children = $this->parent()->children();
  41. $position = array_search($this, $children);
  42.  
  43. if ($position === false) return null;
  44.  
  45. $before = array_slice($children, 0, $position + 1);
  46. $after = array_slice($children, $position + 1);
  47.  
  48. $children = array_merge(
  49. $before, array($element), $after
  50. );
  51.  
  52. $this->parent()->children($children);
  53.  
  54. return $this;
  55. }
  56.  
  57. /**
  58. * Insert an object before the current.
  59. *
  60. * @param object $element An instance of IBBCChild.
  61. * @return object
  62. */
  63. public function insertBefore($element) {
  64. if (!$this->parent()) return null;
  65.  
  66. $children = $this->parent()->children();
  67. $position = array_search($this, $children);
  68.  
  69. if ($position === false) return null;
  70.  
  71. $before = array_slice($children, 0, $position);
  72. $after = array_slice($children, $position);
  73.  
  74. $children = array_merge(
  75. $before, array($element), $after
  76. );
  77.  
  78. $this->parent()->children($children);
  79.  
  80. return $this;
  81. }
  82.  
  83. /**
  84. * Get or set the parent object.
  85. *
  86. * @param object $parent An instance of IBBCParent.
  87. * @return object
  88. */
  89. public function parent($parent = false) {
  90. if (is_object($parent)) {
  91. $this->__parent = $parent;
  92.  
  93. return $this;
  94.  
  95. } else {
  96. return $this->__parent;
  97. }
  98. }
  99.  
  100. /**
  101. * Replace the current object with another.
  102. *
  103. * @param object $element An instance of IBBCChild.
  104. * @return object
  105. */
  106. public function replace($element) {
  107. if (!$this->parent()) return null;
  108.  
  109. $children = $this->parent()->children();
  110. $position = array_search($this, $children);
  111.  
  112. if ($position === false) return null;
  113.  
  114. $children[$position] = $element;
  115.  
  116. $this->parent()->children($children);
  117. $this->parent(null);
  118.  
  119. return $this;
  120. }
  121.  
  122. /**
  123. * Wrap the current object in another.
  124. *
  125. * @param object $wrapper An instance of IBBCParent.
  126. * @return object
  127. */
  128. public function wrap($wrapper) {
  129. $this->replace($wrapper);
  130. $wrapper->children($this);
  131. }
  132. }
  133.  
  134. /**
  135. * The parent base class.
  136. *
  137. * @extends IBBCChild
  138. */
  139. class IBBCParent extends IBBCChild {
  140. private $__children = array();
  141.  
  142. public function __toString() {
  143. $output = "";
  144.  
  145. foreach ($this->__children as $child) {
  146. $output .= $child;
  147. }
  148.  
  149. return $output;
  150. }
  151.  
  152. /**
  153. * Get a child object with a specific index.
  154. *
  155. * @param number $index A valid integer.
  156. * @return object
  157. */
  158. public function child($index) {
  159. if ($index < 0) {
  160. $index = count($this->__children) - (0 - $index);
  161. }
  162.  
  163. if (is_int($index) and count($this->__children) >= $index) {
  164. return $this->__children[$index];
  165. }
  166.  
  167. return null;
  168. }
  169.  
  170. /**
  171. * Get the first child object.
  172. *
  173. * @return object
  174. */
  175. public function childFirst() {
  176. if (count($this->__children)) {
  177. return $this->__children[0];
  178. }
  179.  
  180. return null;
  181. }
  182.  
  183. /**
  184. * Get the last child object.
  185. *
  186. * @return object
  187. */
  188. public function childLast() {
  189. if (count($this->__children)) {
  190. return $this->__children[count($this->__children) - 1];
  191. }
  192.  
  193. return null;
  194. }
  195.  
  196. /**
  197. * Get or set the current objects children.
  198. *
  199. * @param array $children An array of IBBCChild.
  200. * @return object
  201. */
  202. public function children($children = false) {
  203. if (is_array($children) or is_object($children)) {
  204. foreach ($this->__children as $child) {
  205. $child->parent(null);
  206. }
  207.  
  208. if (!is_array($children)) {
  209. $children = array($children);
  210. }
  211.  
  212. $this->__children = $children;
  213.  
  214. foreach ($this->__children as $child) {
  215. $child->parent($this);
  216. }
  217.  
  218. return $this;
  219.  
  220. } else {
  221. return $this->__children;
  222. }
  223. }
  224.  
  225. /**
  226. * Insert a child object before all others.
  227. *
  228. * @param array $child An instance of IBBCChild.
  229. * @return object
  230. */
  231. public function insertFirst($child) {
  232. $child->parent($this);
  233.  
  234. array_unshift($this->__children, $child);
  235.  
  236. return $this;
  237. }
  238.  
  239. /**
  240. * Insert a child object after all others.
  241. *
  242. * @param array $child An instance of IBBCChild.
  243. * @return object
  244. */
  245. public function insertLast($child) {
  246. $child->parent($this);
  247.  
  248. array_push($this->__children, $child);
  249.  
  250. return $this;
  251. }
  252.  
  253. /**
  254. * Wrap the current object in another.
  255. *
  256. * @param object $wrapper An instance of IBBCParent.
  257. * @return object
  258. */
  259. public function wrap($wrapper) {
  260. $wrapper->children($this->children());
  261. $this->children($wrapper);
  262. }
  263. }
  264.  
  265. /**
  266. * A data object.
  267. *
  268. * @extends IBBCParent
  269. */
  270. class IBBCElement extends IBBCParent {
  271. private $__name = "";
  272. private $__args = array();
  273.  
  274. public function __construct($parent, $name, $args) {
  275. parent::__construct($parent);
  276.  
  277. $this->__name = strtolower($name); $this->__args = $args;
  278. }
  279.  
  280. public function __toString() {
  281. $output = "";
  282.  
  283. foreach ($this->children() as $child) {
  284. $output .= $child;
  285. }
  286.  
  287. return $output;
  288. }
  289.  
  290. /**
  291. * Get an array of arguments.
  292. *
  293. * @return array
  294. */
  295. public function args() {
  296. return $this->__args;
  297. }
  298.  
  299. /**
  300. * Get or set the element name.
  301. *
  302. * @param string $name An alphabetic string.
  303. * @return string
  304. */
  305. public function name($name = false) {
  306. if (is_string($name)) {
  307. if (!preg_match(IBBC_EXP_VALIDNAME, $name) or !$name) {
  308. throw new Exception("Invalid element name '{$name}'.");
  309. }
  310.  
  311. $this->__name = $name; return $this;
  312.  
  313. } else {
  314. return $this->__name;
  315. }
  316. }
  317. }
  318.  
  319. /**
  320. * A textual object.
  321. *
  322. * @extends IBBCChild
  323. */
  324. class IBBCContent extends IBBCChild {
  325. private $__value = "";
  326.  
  327. public function __construct($parent, $value) {
  328. parent::__construct($parent);
  329.  
  330. $this->__value = $value;
  331. }
  332.  
  333. public function __toString() {
  334. return htmlentities($this->value());
  335. }
  336.  
  337. /**
  338. * Get or set the content value.
  339. *
  340. * @param string $value A string of user input.
  341. * @return string
  342. */
  343. public function value($value = false) {
  344. if (is_string($value)) {
  345. $this->__value = $value; return $this;
  346.  
  347. } else {
  348. return $this->__value;
  349. }
  350. }
  351. }
  352.  
  353. /**
  354. * Create a new BBCode parser.
  355. */
  356. class IBBCParser {
  357. private $elements = array();
  358. private $stack = array();
  359. private $source = "";
  360. private $output = null;
  361.  
  362. /**
  363. * Register a class as an element.
  364. *
  365. * @param string $name An alphabetic string.
  366. * @param string $class The name of a class.
  367. */
  368. public function addElement($name, $class) {
  369. if (!preg_match(IBBC_EXP_VALIDNAME, $name) or !$name) {
  370. throw new Exception("Invalid element name '{$name}'.");
  371. }
  372.  
  373. $this->elements["$name"] = $class;
  374. }
  375.  
  376. /**
  377. * Parse a chunk of text.
  378. *
  379. * @param string $source The string to parse.
  380. */
  381. public function parse($source) {
  382. $this->source = $source;
  383. $this->stack = $this->stack();
  384. $this->output = $this->tree();
  385.  
  386. return $this->output;
  387. }
  388.  
  389. private function stack() {
  390. $chunks = preg_split('/(' . IBBC_EXP_OPEN . '|' . IBBC_EXP_CLOSE . ')/i', $this->source, -1, PREG_SPLIT_DELIM_CAPTURE);
  391. $order = array(); $stack = array(); $matches = array();
  392.  
  393. // Create single dimensional stack:
  394. foreach ($chunks as $chunk) {
  395. // Open?
  396. if (preg_match('/^' . IBBC_EXP_OPENBITS . '$/i', $chunk, $matches)) {
  397. $name = strtolower($matches[1]); $args = array();
  398.  
  399. if (count($matches) > 2) {
  400. $args = preg_split('/\s+/', substr($matches[2], 1));
  401. }
  402.  
  403. array_push($order, $name);
  404. array_push($stack, array(
  405. "type" => "open",
  406. "name" => $name,
  407. "args" => $args
  408. ));
  409.  
  410. // Close?
  411. } else if (preg_match('/^' . IBBC_EXP_CLOSEBITS . '$/i', $chunk, $matches)) {
  412. $name = strtolower($matches[1]); $open = array_pop($order);
  413.  
  414. // Tag mismatch:
  415. if ($open != $name and !(array_search($name, $order) === false)) {
  416. array_push($order, $open);
  417.  
  418. while ($current = array_pop($order)) {
  419. if ($current == $name) break;
  420.  
  421. array_push($stack, array(
  422. "type" => "close",
  423. "name" => $current
  424. ));
  425. }
  426.  
  427. $open = $current;
  428. }
  429.  
  430. // Not open:
  431. if ($open != $name) {
  432. array_push($stack, array(
  433. "type" => "text",
  434. "value" => $chunk
  435. ));
  436. continue;
  437. }
  438.  
  439. array_push($stack, array(
  440. "type" => "close",
  441. "name" => $name
  442. ));
  443.  
  444. // Text:
  445. } else {
  446. array_push($stack, array(
  447. "type" => "text",
  448. "value" => $chunk
  449. ));
  450. }
  451. }
  452.  
  453. // Force all elements to close:
  454. while ($current = array_pop($order)) {
  455. array_push($stack, array(
  456. "type" => "close",
  457. "name" => $current
  458. ));
  459. }
  460.  
  461. return $stack;
  462. }
  463.  
  464. private function tree() {
  465. $output = new IBBCParent(null);
  466. $parent = $output; $content = null;
  467.  
  468. foreach ($this->stack as $current) {
  469. // Open:
  470. if ($current["type"] == "open") {
  471. if (array_key_exists($current["name"], $this->elements)) {
  472. $class = $this->elements[$current["name"]];
  473.  
  474. } else {
  475. $class = "IBBCElement";
  476. }
  477.  
  478. $temp = new $class(null, $current["name"], $current["args"]);
  479. $parent->insertLast($temp); $parent = $temp; $content = null;
  480.  
  481. // Close:
  482. } else if ($current["type"] == "close") {
  483. $parent = $parent->parent(); $content = null;
  484.  
  485. // Text:
  486. } else {
  487. if ($content) {
  488. $content->value($content->value() . $current["value"]);
  489.  
  490. } else {
  491. $content = new IBBCContent(null, $current["value"]);
  492. $parent->insertLast($content);
  493. }
  494. }
  495. }
  496.  
  497. return $output;
  498. }
  499. }
  500.  
  501. //------------------------------------------------------------------------------
  502. ?>
Add Comment
Please, Sign In to add comment