Textile.php 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919
  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$
  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_Textile implements Zend_Markup_Parser_ParserInterface
  38. {
  39. /**
  40. * Token tree
  41. *
  42. * @var Zend_Markup_TokenList
  43. */
  44. protected $_tree;
  45. /**
  46. * Current token
  47. *
  48. * @var Zend_Markup_Token
  49. */
  50. protected $_current;
  51. /**
  52. * Source to tokenize
  53. *
  54. * @var string
  55. */
  56. protected $_value = '';
  57. /**
  58. * Length of the value
  59. *
  60. * @var int
  61. */
  62. protected $_valueLen = 0;
  63. /**
  64. * Current pointer
  65. *
  66. * @var int
  67. */
  68. protected $_pointer = 0;
  69. /**
  70. * The buffer
  71. *
  72. * @var string
  73. */
  74. protected $_buffer = '';
  75. /**
  76. * Simple tag translation
  77. *
  78. * @var array
  79. */
  80. protected $_simpleTags = array(
  81. '*' => 'strong',
  82. '**' => 'bold',
  83. '_' => 'emphasized',
  84. '__' => 'italic',
  85. '??' => 'citation',
  86. '-' => 'deleted',
  87. '+' => 'insert',
  88. '^' => 'superscript',
  89. '~' => 'subscript',
  90. '%' => 'span',
  91. // these are a little more complicated
  92. '@' => 'code',
  93. '!' => 'img',
  94. );
  95. /**
  96. * The list's level
  97. *
  98. * @var int
  99. */
  100. protected $_listLevel = 0;
  101. /**
  102. * Prepare the parsing of a Textile string, the real parsing is done in {@link _parse()}
  103. *
  104. * @param string $value
  105. * @return array
  106. */
  107. public function parse($value)
  108. {
  109. if (!is_string($value)) {
  110. /**
  111. * @see Zend_Markup_Parser_Exception
  112. */
  113. require_once 'Zend/Markup/Parser/Exception.php';
  114. throw new Zend_Markup_Parser_Exception('Value to parse should be a string.');
  115. }
  116. if (empty($value)) {
  117. /**
  118. * @see Zend_Markup_Parser_Exception
  119. */
  120. require_once 'Zend/Markup/Parser/Exception.php';
  121. throw new Zend_Markup_Parser_Exception('Value to parse cannot be left empty.');
  122. }
  123. // first make we only have LF newlines
  124. $this->_value = str_replace(array("\r\n", "\r"), "\n", $value);
  125. // trim and add a leading LF to make sure that headings on the first line
  126. // are parsed correctly
  127. $this->_value = trim($this->_value);
  128. // initialize variables
  129. $this->_tree = new Zend_Markup_TokenList();
  130. $this->_valueLen = strlen($this->_value);
  131. $this->_pointer = 0;
  132. $this->_buffer = '';
  133. $this->_temp = array();
  134. $this->_current = new Zend_Markup_Token('', Zend_Markup_Token::TYPE_NONE, 'Zend_Markup_Root');
  135. $this->_tree->addChild($this->_current);
  136. $info = array(
  137. 'tag' => '',
  138. 'attributes' => array()
  139. );
  140. // check if the base paragraph has some extra information
  141. if ($this->_value[$this->_pointer] == 'p') {
  142. $this->_pointer++;
  143. $info = $this->_parseTagInfo('p', '.', true);
  144. // check if the paragraph definition is correct
  145. if (substr($this->_value, $this->_pointer, 2) != '. ') {
  146. $this->_pointer = 0;
  147. } else {
  148. $this->_pointer += 2;
  149. }
  150. }
  151. // add the base paragraph
  152. $paragraph = new Zend_Markup_Token(
  153. $info['tag'],
  154. Zend_Markup_Token::TYPE_TAG,
  155. 'p',
  156. $info['attributes'],
  157. $this->_current
  158. );
  159. $this->_current->addChild($paragraph);
  160. $this->_current = $paragraph;
  161. // start the parsing process
  162. $this->_parse(true);
  163. return $this->_tree;
  164. }
  165. /**
  166. * Parse a Textile string
  167. *
  168. * @return void
  169. */
  170. protected function _parse($checkStartTags = false)
  171. {
  172. // just keep looping until the parsing is done
  173. if ($checkStartTags) {
  174. // check the starter tags (newlines)
  175. switch ($this->_value[$this->_pointer]) {
  176. case 'h':
  177. $this->_parseHeading();
  178. break;
  179. case '*':
  180. case '#':
  181. $this->_parseList();
  182. break;
  183. }
  184. }
  185. while ($this->_pointer < $this->_valueLen) {
  186. $this->_parseTag();
  187. }
  188. $this->_processBuffer();
  189. }
  190. /**
  191. * Parse an inline string
  192. *
  193. * @param string $value
  194. * @param Zend_Markup_Token $token
  195. * @return Zend_Markup_Token
  196. */
  197. protected function _parseInline($value, Zend_Markup_Token $token)
  198. {
  199. // save the old values
  200. $oldValue = $this->_value;
  201. $oldValueLen = $this->_valueLen;
  202. $oldPointer = $this->_pointer;
  203. $oldTree = $this->_tree;
  204. $oldCurrent = $this->_current;
  205. $oldBuffer = $this->_buffer;
  206. // set the new values
  207. $this->_value = $value;
  208. $this->_valueLen = strlen($value);
  209. $this->_pointer = 0;
  210. $this->_tree = $token;
  211. $this->_current = $token;
  212. $this->_buffer = '';
  213. // parse
  214. $this->_parse();
  215. // set the old values
  216. $this->_value = $oldValue;
  217. $this->_valueLen = $oldValueLen;
  218. $this->_pointer = $oldPointer;
  219. $this->_tree = $oldTree;
  220. $this->_current = $oldCurrent;
  221. $this->_buffer = $oldBuffer;
  222. return $token;
  223. }
  224. /**
  225. * Parse a tag
  226. *
  227. * @return void
  228. */
  229. protected function _parseTag()
  230. {
  231. switch ($this->_value[$this->_pointer]) {
  232. case '*':
  233. case '_':
  234. case '?':
  235. case '-':
  236. case '+':
  237. case '^':
  238. case '~':
  239. case '%':
  240. // simple tags like bold, italic
  241. $this->_parseSimpleTag($this->_value[$this->_pointer]);
  242. break;
  243. case '@':
  244. case '!':
  245. // simple tags, only they don't have anything inside them
  246. $this->_parseEmptyTag($this->_value[$this->_pointer]);
  247. break;
  248. case '"':
  249. $this->_parseLink();
  250. break;
  251. case '(':
  252. $this->_parseAcronym();
  253. break;
  254. case "\n":
  255. // this could mean multiple things, let this function check what
  256. $this->_parseNewline();
  257. break;
  258. default:
  259. // just add this token to the buffer
  260. $this->_buffer .= $this->_value[$this->_pointer++];
  261. break;
  262. }
  263. }
  264. /**
  265. * Parse tag information
  266. *
  267. * @todo use $tagEnd property for identation
  268. * @param string $tag
  269. * @param string $tagEnd
  270. * @param bool $block
  271. * @return array
  272. */
  273. protected function _parseTagInfo($tag = '', $tagEnd = '', $block = false)
  274. {
  275. $info = array(
  276. 'attributes' => array(),
  277. 'tag' => $tag
  278. );
  279. if ($this->_pointer >= $this->_valueLen) {
  280. return $info;
  281. }
  282. // check which attribute
  283. switch ($this->_value[$this->_pointer]) {
  284. case '(':
  285. // class or id
  286. $attribute = $this->_parseAttributeEnd(')', $tagEnd);
  287. if (!$attribute) {
  288. break;
  289. }
  290. $info['tag'] .= '(' . $attribute . ')';
  291. if ($attribute[0] == '#') {
  292. $info['attributes']['id'] = substr($attribute, 1);
  293. } elseif (($len = strpos($attribute, '#')) !== false) {
  294. $info['attributes']['class'] = substr($attribute, 0, $len);
  295. $info['attributes']['id'] = substr($attribute, $len + 1);
  296. } else {
  297. $info['attributes']['class'] = $attribute;
  298. }
  299. $this->_pointer++;
  300. break;
  301. case '{':
  302. // style
  303. $attribute = $this->_parseAttributeEnd('}', $tagEnd);
  304. if (!$attribute) {
  305. break;
  306. }
  307. $info['tag'] .= '{' . $attribute . '}';
  308. $info['attributes']['style'] = $attribute;
  309. $this->_pointer++;
  310. break;
  311. case '[':
  312. // style
  313. $attribute = $this->_parseAttributeEnd(']', $tagEnd);
  314. if (!$attribute) {
  315. break;
  316. }
  317. $info['tag'] .= '[' . $attribute . ']';
  318. $info['attributes']['lang'] = $attribute;
  319. $this->_pointer++;
  320. break;
  321. case '<':
  322. if ($block) {
  323. $info['tag'] .= '<';
  324. if (($this->_value[++$this->_pointer] == '>')) {
  325. $info['attributes']['align'] = 'justify';
  326. $info['tag'] .= '>';
  327. $this->_pointer++;
  328. } else {
  329. $info['attributes']['align'] = 'left';
  330. }
  331. }
  332. break;
  333. case '>':
  334. if ($block) {
  335. $info['attributes']['align'] = 'right';
  336. $info['tag'] .= '>';
  337. $this->_pointer++;
  338. }
  339. break;
  340. case '=':
  341. if ($block) {
  342. $info['attributes']['align'] = 'center';
  343. $info['tag'] .= '=';
  344. $this->_pointer++;
  345. }
  346. break;
  347. default:
  348. // simply do nothing, there are no attributes
  349. break;
  350. }
  351. return $info;
  352. }
  353. /**
  354. * Parse the attribute's end
  355. *
  356. * @todo use $tagEnd property for indentation
  357. * @param string $endToken
  358. * @param string $tagEnd
  359. * @param bool $block
  360. * @return string|bool
  361. */
  362. protected function _parseAttributeEnd($endToken, $tagEnd = '', $block = false)
  363. {
  364. $value = '';
  365. $oldPointer = $this->_pointer;
  366. while ($this->_pointer < $this->_valueLen) {
  367. if ($this->_pointer + 1 >= $this->_valueLen) {
  368. $this->_pointer = $oldPointer;
  369. return false;
  370. }
  371. if ($this->_value[++$this->_pointer] == $endToken) {
  372. return $value;
  373. }
  374. $value .= $this->_value[$this->_pointer];
  375. }
  376. }
  377. /**
  378. * Parse a simple markup tag
  379. *
  380. * @param string $tag
  381. * @return void
  382. */
  383. protected function _parseSimpleTag($tag)
  384. {
  385. if (++$this->_pointer >= $this->_valueLen) {
  386. // could be a stopper
  387. if ($this->_isSimpleStopper($tag)) {
  388. $this->_processBuffer();
  389. $this->_current->setStopper($tag);
  390. $this->_current = $this->_current->getParent();
  391. } else {
  392. $this->_buffer .= $tag;
  393. }
  394. return;
  395. }
  396. if ($this->_value[$this->_pointer] == $tag) {
  397. $tag = $tag . $tag;
  398. $this->_pointer++;
  399. }
  400. // check if this is a stopper
  401. if ($this->_isSimpleStopper($tag)) {
  402. $this->_processBuffer();
  403. $this->_current->setStopper($tag);
  404. $this->_current = $this->_current->getParent();
  405. return;
  406. }
  407. // check if this is a tag
  408. if (isset($this->_simpleTags[$tag])) {
  409. $name = $this->_simpleTags[$tag];
  410. // process the buffer and add the tag
  411. $this->_processBuffer();
  412. // parse a possible attribute
  413. $info = $this->_parseTagInfo($tag);
  414. $token = new Zend_Markup_Token(
  415. $info['tag'],
  416. Zend_Markup_Token::TYPE_TAG,
  417. $name,
  418. $info['attributes'],
  419. $this->_current
  420. );
  421. $this->_current->addChild($token);
  422. $this->_current = $token;
  423. } else {
  424. $this->_buffer .= $tag;
  425. }
  426. }
  427. /**
  428. * Parse an 'empty' markup tag
  429. *
  430. * @todo implement support for attributes
  431. * @param string $tag
  432. * @return void
  433. */
  434. protected function _parseEmptyTag($tag)
  435. {
  436. if (!isset($this->_simpleTags[$tag])) {
  437. $this->_buffer .= $tag;
  438. return;
  439. }
  440. // add the tag
  441. $this->_processBuffer();
  442. $this->_pointer++;
  443. $info = $this->_parseTagInfo($tag);
  444. $name = $this->_simpleTags[$tag];
  445. $token = new Zend_Markup_Token(
  446. $info['tag'],
  447. Zend_Markup_Token::TYPE_TAG,
  448. $name,
  449. $info['attributes'],
  450. $this->_current
  451. );
  452. $this->_current->addChild($token);
  453. $this->_current = $token;
  454. // find the stopper
  455. while ($this->_valueLen > $this->_pointer) {
  456. if ($this->_value[$this->_pointer] == $tag) {
  457. // found the stopper, set it and return
  458. $this->_pointer++;
  459. $this->_processBuffer();
  460. $this->_current->setStopper($tag);
  461. $this->_current = $this->_current->getParent();
  462. return;
  463. } else {
  464. // not yet found, add the character to the buffer and go to the next one
  465. $this->_buffer .= $this->_value[$this->_pointer++];
  466. }
  467. }
  468. }
  469. /**
  470. * Parse a link
  471. *
  472. * @return void
  473. */
  474. protected function _parseLink()
  475. {
  476. // first find the other "
  477. $len = strcspn($this->_value, '"', ++$this->_pointer);
  478. $text = substr($this->_value, $this->_pointer, $len);
  479. // not a link tag
  480. if (($this->_pointer + $len >= $this->_valueLen) || ($this->_value[$this->_pointer + $len++] != '"')) {
  481. $this->_buffer .= '"' . $text;
  482. $this->_pointer += $len;
  483. return;
  484. }
  485. // not a link tag
  486. if (($this->_pointer + $len >= $this->_valueLen) || ($this->_value[$this->_pointer + $len++] != ':')) {
  487. $this->_buffer .= '"' . $text . '"';
  488. $this->_pointer += $len;
  489. return;
  490. }
  491. // update the pointer
  492. $this->_pointer += $len;
  493. // now, get the URL
  494. $len = strcspn($this->_value, "\n\t ", $this->_pointer);
  495. $url = substr($this->_value, $this->_pointer, $len);
  496. $this->_pointer += $len;
  497. // gather the attributes
  498. $attributes = array(
  499. 'url' => $url,
  500. );
  501. // add the tag
  502. $this->_processBuffer();
  503. $token = new Zend_Markup_Token(
  504. '"',
  505. Zend_Markup_Token::TYPE_TAG,
  506. 'url',
  507. $attributes,
  508. $this->_current
  509. );
  510. $token->addChild(new Zend_Markup_Token(
  511. $text,
  512. Zend_Markup_Token::TYPE_NONE,
  513. '',
  514. array(),
  515. $token
  516. ));
  517. $token->setStopper('":');
  518. $this->_current->addChild($token);
  519. }
  520. /**
  521. * Parse a newline
  522. *
  523. * A newline could mean multiple things:
  524. * - Heading {@link _parseHeading()}
  525. * - List {@link _parseList()}
  526. * - Paragraph {@link _parseParagraph()}
  527. *
  528. * @return void
  529. */
  530. protected function _parseNewline()
  531. {
  532. if (!empty($this->_buffer) && ($this->_buffer[strlen($this->_buffer) - 1] == "\n")) {
  533. $this->_parseParagraph();
  534. } else {
  535. switch ($this->_value[++$this->_pointer]) {
  536. case 'h':
  537. $this->_parseHeading();
  538. break;
  539. case '*':
  540. case '#':
  541. $this->_parseList();
  542. break;
  543. default:
  544. $this->_buffer .= "\n";
  545. break;
  546. }
  547. }
  548. }
  549. /**
  550. * Parse a paragraph declaration
  551. *
  552. * @return void
  553. */
  554. protected function _parseParagraph()
  555. {
  556. // remove the newline from the buffer and increase the pointer
  557. $this->_buffer = substr($this->_buffer, 0, -1);
  558. $this->_pointer++;
  559. // check if we are in the current paragraph
  560. if ($this->_current->getName() == 'p') {
  561. $this->_processBuffer();
  562. $this->_current->setStopper("\n");
  563. $this->_current = $this->_current->getParent();
  564. $info = array(
  565. 'tag' => "\n",
  566. 'attributes' => array()
  567. );
  568. $oldPointer = $this->_pointer;
  569. if ($this->_value[$this->_pointer] == 'p') {
  570. $this->_pointer++;
  571. $info = $this->_parseTagInfo("\np", '.', true);
  572. if (substr($this->_value, $this->_pointer, 2) == '. ') {
  573. $this->_pointer += 2;
  574. } else {
  575. // incorrect declaration of paragraph, reset the pointer and use default info
  576. $this->_pointer = $oldPointer;
  577. $info = array(
  578. 'tag' => "\n",
  579. 'attributes' => array()
  580. );
  581. }
  582. }
  583. // create a new one and jump onto it
  584. $paragraph = new Zend_Markup_Token(
  585. $info['tag'],
  586. Zend_Markup_Token::TYPE_TAG,
  587. 'p',
  588. $info['attributes'],
  589. $this->_current
  590. );
  591. $this->_current->addChild($paragraph);
  592. $this->_current = $paragraph;
  593. } else {
  594. /**
  595. * @todo Go down in the tree until you find the paragraph
  596. * while remembering every step. After that, close the
  597. * paragraph, add a new one and climb back up by re-adding
  598. * every step
  599. */
  600. }
  601. }
  602. /**
  603. * Parse a heading
  604. *
  605. * @todo implement support for attributes
  606. * @return void
  607. */
  608. protected function _parseHeading()
  609. {
  610. // check if it is a valid heading
  611. if (in_array($this->_value[++$this->_pointer], range(1, 6))) {
  612. $name = 'h' . $this->_value[$this->_pointer++];
  613. $info = $this->_parseTagInfo($name, '.', true);
  614. // now, the next char should be a dot
  615. if ($this->_value[$this->_pointer] == '.') {
  616. $info['tag'] .= '.';
  617. // add the tag
  618. $this->_processBuffer();
  619. $token = new Zend_Markup_Token(
  620. $info['tag'],
  621. Zend_Markup_Token::TYPE_TAG,
  622. $name,
  623. $info['attributes'],
  624. $this->_current
  625. );
  626. $this->_current->addChild($token);
  627. $this->_current = $token;
  628. if ($this->_value[++$this->_pointer] != ' ') {
  629. $this->_buffer .= $this->_value[$this->_pointer];
  630. }
  631. // find the end
  632. $len = strcspn($this->_value, "\n", ++$this->_pointer);
  633. $this->_buffer.= substr($this->_value, $this->_pointer, $len);
  634. $this->_pointer += $len;
  635. // end the tag and return
  636. $this->_processBuffer();
  637. $this->_current = $this->_current->getParent();
  638. return;
  639. }
  640. $this->_buffer .= "\n" . $name;
  641. return;
  642. }
  643. // not a valid heading
  644. $this->_buffer .= "\nh";
  645. }
  646. /**
  647. * Parse a list
  648. *
  649. * @todo allow a deeper list level
  650. * @todo add support for markup inside the list items
  651. * @return void
  652. */
  653. protected function _parseList()
  654. {
  655. // for this operation, we need the entire line
  656. $len = strcspn($this->_value, "\n", $this->_pointer);
  657. $line = substr($this->_value, $this->_pointer, $len);
  658. // add the list tag
  659. $this->_processBuffer();
  660. // maybe we have to rewind
  661. $oldPointer = $this->_pointer;
  662. // attributes array
  663. $attrs = array();
  664. if ($line[0] == '#') {
  665. $attrs['list'] = 'decimal';
  666. }
  667. if ((strlen($line) <= 1) || ($line[1] != ' ')) {
  668. // rewind and return
  669. unset($list);
  670. $this->_pointer = $oldPointer;
  671. return;
  672. }
  673. // add the token
  674. $list = new Zend_Markup_Token('', Zend_Markup_Token::TYPE_TAG, 'list', $attrs, $this->_current);
  675. // loop through every next line, until there are no list items any more
  676. while ($this->_valueLen > $this->_pointer) {
  677. // add the li-tag with contents
  678. $item = new Zend_Markup_Token(
  679. $line[0],
  680. Zend_Markup_Token::TYPE_TAG,
  681. 'li',
  682. array(),
  683. $list
  684. );
  685. // parse and add the content
  686. $this->_parseInline(substr($line, 2), $item);
  687. $list->addChild($item);
  688. $this->_pointer += $len;
  689. // check if the next line is a list item too
  690. if (($this->_pointer + 1 >= $this->_valueLen) || $this->_value[++$this->_pointer] != $line[0]) {
  691. // there is no new list item coming
  692. break;
  693. }
  694. // get the next line
  695. $len = strcspn($this->_value, "\n", $this->_pointer);
  696. $line = substr($this->_value, $this->_pointer, $len);
  697. }
  698. // end the list tag
  699. $this->_current->addChild($list);
  700. $this->_current = $this->_current->getParent();
  701. }
  702. /**
  703. * Parse an acronym
  704. *
  705. * @return void
  706. */
  707. protected function _parseAcronym()
  708. {
  709. $this->_pointer++;
  710. // first find the acronym itself
  711. $acronym = '';
  712. $pointer = 0;
  713. if (empty($this->_buffer)) {
  714. $this->_buffer .= '(';
  715. return;
  716. }
  717. $bufferLen = strlen($this->_buffer);
  718. while (($bufferLen > $pointer) && ctype_upper($this->_buffer[$bufferLen - ++$pointer])) {
  719. $acronym = $this->_buffer[strlen($this->_buffer) - $pointer] . $acronym;
  720. }
  721. if (strlen($acronym) < 3) {
  722. // just add the '(' to the buffer, this isn't an acronym
  723. $this->_buffer .= '(';
  724. return;
  725. }
  726. // now, find the closing ')'
  727. $title = '';
  728. while ($this->_pointer < $this->_valueLen) {
  729. if ($this->_value[$this->_pointer] == ')') {
  730. break;
  731. } else {
  732. $title .= $this->_value[$this->_pointer];
  733. }
  734. $this->_pointer++;
  735. }
  736. if ($this->_pointer >= $this->_valueLen) {
  737. $this->_buffer .= '(';
  738. return;
  739. }
  740. $this->_pointer++;
  741. if (empty($title)) {
  742. $this->_buffer .= '()';
  743. return;
  744. }
  745. $this->_buffer = substr($this->_buffer, 0, -$this->_pointer);
  746. $this->_processBuffer();
  747. // now add the tag
  748. $token = new Zend_Markup_Token(
  749. '',
  750. Zend_Markup_Token::TYPE_TAG,
  751. 'acronym',
  752. array('title' => $title),
  753. $this->_current
  754. );
  755. $token->setStopper('(' . $title . ')');
  756. $token->addChild(new Zend_Markup_Token(
  757. $acronym,
  758. Zend_Markup_Token::TYPE_NONE,
  759. '',
  760. array(),
  761. $token
  762. ));
  763. $this->_current->addChild($token);
  764. }
  765. /**
  766. * Check if the tag is a simple stopper
  767. *
  768. * @param string $tag
  769. * @return bool
  770. */
  771. protected function _isSimpleStopper($tag)
  772. {
  773. if ($tag == substr($this->_current->getTag(), 0, strlen($tag))) {
  774. return true;
  775. }
  776. return false;
  777. }
  778. /**
  779. * Process the current buffer
  780. *
  781. * @return void
  782. */
  783. protected function _processBuffer()
  784. {
  785. if (!empty($this->_buffer)) {
  786. // no tag start found, add the buffer to the current tag and stop parsing
  787. $token = new Zend_Markup_Token(
  788. $this->_buffer,
  789. Zend_Markup_Token::TYPE_NONE,
  790. '',
  791. array(),
  792. $this->_current
  793. );
  794. $this->_current->addChild($token);
  795. $this->_buffer = '';
  796. }
  797. }
  798. }