PageRenderTime 51ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/Zend/Markup/Parser/Bbcode.php

https://bitbucket.org/andrewjleavitt/magestudy
PHP | 504 lines | 276 code | 63 blank | 165 comment | 39 complexity | 989a3fb3b9f5f61a808da073142ab0ea MD5 | raw file
Possible License(s): CC-BY-SA-3.0, LGPL-2.1, GPL-2.0, WTFPL
  1. <?php
  2. /**
  3. * Zend Framework
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the new BSD license that is bundled
  8. * with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://framework.zend.com/license/new-bsd
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@zend.com so we can send you a copy immediately.
  14. *
  15. * @category Zend
  16. * @package Zend_Markup
  17. * @subpackage Parser
  18. * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
  19. * @license http://framework.zend.com/license/new-bsd New BSD License
  20. * @version $Id: Bbcode.php 21127 2010-02-21 15:35:03Z kokx $
  21. */
  22. /**
  23. * @see Zend_Markup_TokenList
  24. */
  25. #require_once 'Zend/Markup/TokenList.php';
  26. /**
  27. * @see Zend_Markup_Parser_ParserInterface
  28. */
  29. #require_once 'Zend/Markup/Parser/ParserInterface.php';
  30. /**
  31. * @category Zend
  32. * @package Zend_Markup
  33. * @subpackage Parser
  34. * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
  35. * @license http://framework.zend.com/license/new-bsd New BSD License
  36. */
  37. class Zend_Markup_Parser_Bbcode implements Zend_Markup_Parser_ParserInterface
  38. {
  39. const NEWLINE = "[newline\0]";
  40. // there is a parsing difference between the default tags and single tags
  41. const TYPE_DEFAULT = 'default';
  42. const TYPE_SINGLE = 'single';
  43. const NAME_CHARSET = '^\[\]=\s';
  44. const STATE_SCAN = 0;
  45. const STATE_SCANATTRS = 1;
  46. const STATE_PARSEVALUE = 2;
  47. /**
  48. * Token tree
  49. *
  50. * @var Zend_Markup_TokenList
  51. */
  52. protected $_tree;
  53. /**
  54. * Current token
  55. *
  56. * @var Zend_Markup_Token
  57. */
  58. protected $_current;
  59. /**
  60. * Source to tokenize
  61. *
  62. * @var string
  63. */
  64. protected $_value = '';
  65. /**
  66. * Length of the value
  67. *
  68. * @var int
  69. */
  70. protected $_valueLen = 0;
  71. /**
  72. * Current pointer
  73. *
  74. * @var int
  75. */
  76. protected $_pointer = 0;
  77. /**
  78. * The buffer
  79. *
  80. * @var string
  81. */
  82. protected $_buffer = '';
  83. /**
  84. * Temporary tag storage
  85. *
  86. * @var array
  87. */
  88. protected $_temp;
  89. /**
  90. * Stoppers that we are searching for
  91. *
  92. * @var array
  93. */
  94. protected $_searchedStoppers = array();
  95. /**
  96. * Tag information
  97. *
  98. * @var array
  99. */
  100. protected $_tags = array(
  101. 'Zend_Markup_Root' => array(
  102. 'type' => self::TYPE_DEFAULT,
  103. 'stoppers' => array(),
  104. ),
  105. '*' => array(
  106. 'type' => self::TYPE_DEFAULT,
  107. 'stoppers' => array(self::NEWLINE, '[/*]', '[/]'),
  108. ),
  109. 'hr' => array(
  110. 'type' => self::TYPE_SINGLE,
  111. 'stoppers' => array(),
  112. ),
  113. 'code' => array(
  114. 'type' => self::TYPE_DEFAULT,
  115. 'stoppers' => array('[/code]', '[/]'),
  116. 'parse_inside' => false
  117. )
  118. );
  119. /**
  120. * Token array
  121. *
  122. * @var array
  123. */
  124. protected $_tokens = array();
  125. /**
  126. * State
  127. *
  128. * @var int
  129. */
  130. protected $_state = self::STATE_SCAN;
  131. /**
  132. * Prepare the parsing of a bbcode string, the real parsing is done in {@link _parse()}
  133. *
  134. * @param string $value
  135. * @return Zend_Markup_TokenList
  136. */
  137. public function parse($value)
  138. {
  139. if (!is_string($value)) {
  140. /**
  141. * @see Zend_Markup_Parser_Exception
  142. */
  143. #require_once 'Zend/Markup/Parser/Exception.php';
  144. throw new Zend_Markup_Parser_Exception('Value to parse should be a string.');
  145. }
  146. if (empty($value)) {
  147. /**
  148. * @see Zend_Markup_Parser_Exception
  149. */
  150. #require_once 'Zend/Markup/Parser/Exception.php';
  151. throw new Zend_Markup_Parser_Exception('Value to parse cannot be left empty.');
  152. }
  153. $this->_value = str_replace(array("\r\n", "\r", "\n"), self::NEWLINE, $value);
  154. // variable initialization for tokenizer
  155. $this->_valueLen = strlen($this->_value);
  156. $this->_pointer = 0;
  157. $this->_buffer = '';
  158. $this->_temp = array();
  159. $this->_state = self::STATE_SCAN;
  160. $this->_tokens = array();
  161. $this->_tokenize();
  162. // variable initialization for treebuilder
  163. $this->_searchedStoppers = array();
  164. $this->_tree = new Zend_Markup_TokenList();
  165. $this->_current = new Zend_Markup_Token(
  166. '',
  167. Zend_Markup_Token::TYPE_NONE,
  168. 'Zend_Markup_Root'
  169. );
  170. $this->_tree->addChild($this->_current);
  171. $this->_createTree();
  172. return $this->_tree;
  173. }
  174. /**
  175. * Tokenize
  176. *
  177. * @param string $input
  178. *
  179. * @return void
  180. */
  181. protected function _tokenize()
  182. {
  183. $attribute = '';
  184. while ($this->_pointer < $this->_valueLen) {
  185. switch ($this->_state) {
  186. case self::STATE_SCAN:
  187. $matches = array();
  188. $regex = '#\G(?<text>[^\[]*)(?<open>\[(?<name>[' . self::NAME_CHARSET . ']+)?)?#';
  189. preg_match($regex, $this->_value, $matches, null, $this->_pointer);
  190. $this->_pointer += strlen($matches[0]);
  191. if (!empty($matches['text'])) {
  192. $this->_buffer .= $matches['text'];
  193. }
  194. if (!isset($matches['open'])) {
  195. // great, no tag, we are ending the string
  196. break;
  197. }
  198. if (!isset($matches['name'])) {
  199. $this->_buffer .= $matches['open'];
  200. break;
  201. }
  202. $this->_temp = array(
  203. 'tag' => '[' . $matches['name'],
  204. 'name' => $matches['name'],
  205. 'attributes' => array()
  206. );
  207. if ($this->_pointer >= $this->_valueLen) {
  208. // damn, no tag
  209. $this->_buffer .= $this->_temp['tag'];
  210. break 2;
  211. }
  212. if ($this->_value[$this->_pointer] == '=') {
  213. $this->_pointer++;
  214. $this->_temp['tag'] .= '=';
  215. $this->_state = self::STATE_PARSEVALUE;
  216. $attribute = $this->_temp['name'];
  217. } else {
  218. $this->_state = self::STATE_SCANATTRS;
  219. }
  220. break;
  221. case self::STATE_SCANATTRS:
  222. $matches = array();
  223. $regex = '#\G((?<end>\s*\])|\s+(?<attribute>[' . self::NAME_CHARSET . ']+)(?<eq>=?))#';
  224. if (!preg_match($regex, $this->_value, $matches, null, $this->_pointer)) {
  225. break 2;
  226. }
  227. $this->_pointer += strlen($matches[0]);
  228. if (!empty($matches['end'])) {
  229. if (!empty($this->_buffer)) {
  230. $this->_tokens[] = array(
  231. 'tag' => $this->_buffer,
  232. 'type' => Zend_Markup_Token::TYPE_NONE
  233. );
  234. $this->_buffer = '';
  235. }
  236. $this->_temp['tag'] .= $matches['end'];
  237. $this->_temp['type'] = Zend_Markup_Token::TYPE_TAG;
  238. $this->_tokens[] = $this->_temp;
  239. $this->_temp = array();
  240. $this->_state = self::STATE_SCAN;
  241. } else {
  242. // attribute name
  243. $attribute = $matches['attribute'];
  244. $this->_temp['tag'] .= $matches[0];
  245. $this->_temp['attributes'][$attribute] = '';
  246. if (empty($matches['eq'])) {
  247. $this->_state = self::STATE_SCANATTRS;
  248. } else {
  249. $this->_state = self::STATE_PARSEVALUE;
  250. }
  251. }
  252. break;
  253. case self::STATE_PARSEVALUE:
  254. $matches = array();
  255. $regex = '#\G((?<quote>"|\')(?<valuequote>.*?)\\2|(?<value>[^\]\s]+))#';
  256. if (!preg_match($regex, $this->_value, $matches, null, $this->_pointer)) {
  257. $this->_state = self::STATE_SCANATTRS;
  258. break;
  259. }
  260. $this->_pointer += strlen($matches[0]);
  261. if (!empty($matches['quote'])) {
  262. $this->_temp['attributes'][$attribute] = $matches['valuequote'];
  263. } else {
  264. $this->_temp['attributes'][$attribute] = $matches['value'];
  265. }
  266. $this->_temp['tag'] .= $matches[0];
  267. $this->_state = self::STATE_SCANATTRS;
  268. break;
  269. }
  270. }
  271. if (!empty($this->_buffer)) {
  272. $this->_tokens[] = array(
  273. 'tag' => $this->_buffer,
  274. 'type' => Zend_Markup_Token::TYPE_NONE
  275. );
  276. }
  277. }
  278. /**
  279. * Parse the token array into a tree
  280. *
  281. * @param array $tokens
  282. *
  283. * @return void
  284. */
  285. public function _createTree()
  286. {
  287. foreach ($this->_tokens as $token) {
  288. // first we want to know if this tag is a stopper, or at least a searched one
  289. if ($this->_isStopper($token['tag'])) {
  290. // find the stopper
  291. $oldItems = array();
  292. while (!in_array($token['tag'], $this->_tags[$this->_current->getName()]['stoppers'])) {
  293. $oldItems[] = clone $this->_current;
  294. $this->_current = $this->_current->getParent();
  295. }
  296. // we found the stopper, so stop the tag
  297. $this->_current->setStopper($token['tag']);
  298. $this->_removeFromSearchedStoppers($this->_current);
  299. $this->_current = $this->_current->getParent();
  300. // add the old items again if there are any
  301. if (!empty($oldItems)) {
  302. foreach (array_reverse($oldItems) as $item) {
  303. /* @var $token Zend_Markup_Token */
  304. $this->_current->addChild($item);
  305. $item->setParent($this->_current);
  306. $this->_current = $item;
  307. }
  308. }
  309. } else {
  310. if ($token['type'] == Zend_Markup_Token::TYPE_TAG) {
  311. if ($token['tag'] == self::NEWLINE) {
  312. // this is a newline tag, add it as a token
  313. $this->_current->addChild(new Zend_Markup_Token(
  314. "\n",
  315. Zend_Markup_Token::TYPE_NONE,
  316. '',
  317. array(),
  318. $this->_current
  319. ));
  320. } elseif (isset($token['name']) && ($token['name'][0] == '/')) {
  321. // this is a stopper, add it as a empty token
  322. $this->_current->addChild(new Zend_Markup_Token(
  323. $token['tag'],
  324. Zend_Markup_Token::TYPE_NONE,
  325. '',
  326. array(),
  327. $this->_current
  328. ));
  329. } elseif (isset($this->_tags[$this->_current->getName()]['parse_inside'])
  330. && !$this->_tags[$this->_current->getName()]['parse_inside']
  331. ) {
  332. $this->_current->addChild(new Zend_Markup_Token(
  333. $token['tag'],
  334. Zend_Markup_Token::TYPE_NONE,
  335. '',
  336. array(),
  337. $this->_current
  338. ));
  339. } else {
  340. // add the tag
  341. $child = new Zend_Markup_Token(
  342. $token['tag'],
  343. $token['type'],
  344. $token['name'],
  345. $token['attributes'],
  346. $this->_current
  347. );
  348. $this->_current->addChild($child);
  349. // add stoppers for this tag, if its has stoppers
  350. if ($this->_getType($token['name']) == self::TYPE_DEFAULT) {
  351. $this->_current = $child;
  352. $this->_addToSearchedStoppers($this->_current);
  353. }
  354. }
  355. } else {
  356. // no tag, just add it as a simple token
  357. $this->_current->addChild(new Zend_Markup_Token(
  358. $token['tag'],
  359. Zend_Markup_Token::TYPE_NONE,
  360. '',
  361. array(),
  362. $this->_current
  363. ));
  364. }
  365. }
  366. }
  367. }
  368. /**
  369. * Check if there is a tag declaration, and if it isnt there, add it
  370. *
  371. * @param string $name
  372. *
  373. * @return void
  374. */
  375. protected function _checkTagDeclaration($name)
  376. {
  377. if (!isset($this->_tags[$name])) {
  378. $this->_tags[$name] = array(
  379. 'type' => self::TYPE_DEFAULT,
  380. 'stoppers' => array(
  381. '[/' . $name . ']',
  382. '[/]'
  383. )
  384. );
  385. }
  386. }
  387. /**
  388. * Check the tag's type
  389. *
  390. * @param string $name
  391. * @return string
  392. */
  393. protected function _getType($name)
  394. {
  395. $this->_checkTagDeclaration($name);
  396. return $this->_tags[$name]['type'];
  397. }
  398. /**
  399. * Check if the tag is a stopper
  400. *
  401. * @param string $tag
  402. * @return bool
  403. */
  404. protected function _isStopper($tag)
  405. {
  406. $this->_checkTagDeclaration($this->_current->getName());
  407. if (!empty($this->_searchedStoppers[$tag])) {
  408. return true;
  409. }
  410. return false;
  411. }
  412. /**
  413. * Add to searched stoppers
  414. *
  415. * @param Zend_Markup_Token $token
  416. * @return void
  417. */
  418. protected function _addToSearchedStoppers(Zend_Markup_Token $token)
  419. {
  420. $this->_checkTagDeclaration($token->getName());
  421. foreach ($this->_tags[$token->getName()]['stoppers'] as $stopper) {
  422. if (!isset($this->_searchedStoppers[$stopper])) {
  423. $this->_searchedStoppers[$stopper] = 0;
  424. }
  425. ++$this->_searchedStoppers[$stopper];
  426. }
  427. }
  428. /**
  429. * Remove from searched stoppers
  430. *
  431. * @param Zend_Markup_Token $token
  432. * @return void
  433. */
  434. protected function _removeFromSearchedStoppers(Zend_Markup_Token $token)
  435. {
  436. $this->_checkTagDeclaration($token->getName());
  437. foreach ($this->_tags[$token->getName()]['stoppers'] as $stopper) {
  438. --$this->_searchedStoppers[$stopper];
  439. }
  440. }
  441. }