PageRenderTime 56ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/usr/plugins/Creole/Creole_Wiki.php

http://typecho.googlecode.com/
PHP | 1599 lines | 549 code | 192 blank | 858 comment | 113 complexity | 5e69c4a724563c37f37a80f88209af4e MD5 | raw file
Possible License(s): AGPL-1.0
  1. <?php
  2. // vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
  3. /**
  4. * Parse structured wiki text and render into arbitrary formats such as XHTML.
  5. *
  6. * PHP versions 4 and 5
  7. *
  8. * @category Text
  9. * @package Text_Wiki
  10. * @author Paul M. Jones <pmjones@php.net>
  11. * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
  12. * @version CVS: $Id: Creole_Wiki.php 182 2008-09-14 15:56:00Z i.feelinglucky $
  13. * @link http://pear.php.net/package/Text_Wiki
  14. */
  15. /**
  16. * The baseline abstract parser class.
  17. */
  18. require_once 'Parse.inc.php';
  19. /**
  20. * The baseline abstract render class.
  21. */
  22. require_once 'Render.inc.php';
  23. /**
  24. * Parse structured wiki text and render into arbitrary formats such as XHTML.
  25. *
  26. * This is the "master" class for handling the management and convenience
  27. * functions to transform Wiki-formatted text.
  28. *
  29. * @category Text
  30. * @package Text_Wiki
  31. * @author Paul M. Jones <pmjones@php.net>
  32. * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
  33. * @version Release: 1.2.0
  34. * @link http://pear.php.net/package/Text_Wiki
  35. */
  36. class Creole_Wiki {
  37. // *single newlines* are handled as in most wikis (ignored)
  38. // if Newline is removed from rules, they will be handled as in word-processors (meaning a paragraph break)
  39. protected $rules = array(
  40. 'Prefilter',
  41. 'Delimiter',
  42. 'Preformatted',
  43. 'Tt',
  44. //'Trim',
  45. 'Break',
  46. 'Raw',
  47. 'Box',
  48. //'Footnote',
  49. 'Table',
  50. 'Newline',
  51. 'Blockquote',
  52. 'Newline',
  53. //'Wikilink',
  54. 'Heading',
  55. 'Center',
  56. 'Horiz',
  57. 'List',
  58. 'Address',
  59. 'Paragraph',
  60. 'Superscript',
  61. 'Subscript',
  62. 'Underline',
  63. 'Strong',
  64. 'Tighten',
  65. 'Image',
  66. 'Url',
  67. 'Emphasis'
  68. );
  69. /**
  70. *
  71. * The list of rules to not-apply to the source text.
  72. *
  73. * @access public
  74. *
  75. * @var array
  76. *
  77. */
  78. public $disable = array(
  79. 'Html',
  80. 'Include',
  81. 'Embed'
  82. );
  83. /**
  84. *
  85. * Custom configuration for rules at the parsing stage.
  86. *
  87. * In this array, the key is the parsing rule name, and the value is
  88. * an array of key-value configuration pairs corresponding to the $conf
  89. * property in the target parsing rule.
  90. *
  91. * For example:
  92. *
  93. * <code>
  94. * $parseConf = array(
  95. * 'Include' => array(
  96. * 'base' => '/path/to/scripts/'
  97. * )
  98. * );
  99. * </code>
  100. *
  101. * Note that most default rules do not need any parsing configuration.
  102. *
  103. * @access public
  104. *
  105. * @var array
  106. *
  107. */
  108. public $parseConf = array();
  109. /**
  110. *
  111. * Custom configuration for rules at the rendering stage.
  112. *
  113. * Because rendering may be different for each target format, the
  114. * first-level element in this array is always a format name (e.g.,
  115. * 'Xhtml').
  116. *
  117. * Within that first level element, the subsequent elements match the
  118. * $parseConf format. That is, the sub-key is the rendering rule name,
  119. * and the sub-value is an array of key-value configuration pairs
  120. * corresponding to the $conf property in the target rendering rule.
  121. *
  122. * @access public
  123. *
  124. * @var array
  125. *
  126. */
  127. public $renderConf = array(
  128. 'Docbook' => array(),
  129. 'Latex' => array(),
  130. 'Pdf' => array(),
  131. 'Plain' => array(),
  132. 'Rtf' => array(),
  133. 'Xhtml' => array()
  134. );
  135. /**
  136. *
  137. * Custom configuration for the output format itself.
  138. *
  139. * Even though Text_Wiki will render the tokens from parsed text,
  140. * the format itself may require some configuration. For example,
  141. * RTF needs to know font names and sizes, PDF requires page layout
  142. * information, and DocBook needs a section hierarchy. This array
  143. * matches the $conf property of the the format-level renderer
  144. * (e.g., Text_Wiki_Render_Xhtml).
  145. *
  146. * In this array, the key is the rendering format name, and the value is
  147. * an array of key-value configuration pairs corresponding to the $conf
  148. * property in the rendering format rule.
  149. *
  150. * @access public
  151. *
  152. * @var array
  153. *
  154. */
  155. public $formatConf = array(
  156. 'Docbook' => array(),
  157. 'Latex' => array(),
  158. 'Pdf' => array(),
  159. 'Plain' => array(),
  160. 'Rtf' => array(),
  161. 'Xhtml' => array()
  162. );
  163. /**
  164. *
  165. * The delimiter for token numbers of parsed elements in source text.
  166. *
  167. * @access public
  168. *
  169. * @var string
  170. *
  171. */
  172. public $delim = "\xFF";
  173. /**
  174. *
  175. * The tokens generated by rules as the source text is parsed.
  176. *
  177. * As Text_Wiki applies rule classes to the source text, it will
  178. * replace portions of the text with a delimited token number. This
  179. * is the array of those tokens, representing the replaced text and
  180. * any options set by the parser for that replaced text.
  181. *
  182. * The tokens array is sequential; each element is itself a sequential
  183. * array where element 0 is the name of the rule that generated the
  184. * token, and element 1 is an associative array where the key is an
  185. * option name and the value is an option value.
  186. *
  187. * @access private
  188. *
  189. * @var array
  190. *
  191. */
  192. public $tokens = array();
  193. /**
  194. * How many tokens generated pro rules.
  195. *
  196. * Intended to load only necessary render objects
  197. *
  198. * @access private
  199. * @var array
  200. */
  201. private $_countRulesTokens = array();
  202. /**
  203. *
  204. * The source text to which rules will be applied.
  205. *
  206. * This text will be transformed in-place, which means that it will
  207. * change as the rules are applied.
  208. *
  209. * @access public
  210. *
  211. * @var string
  212. *
  213. */
  214. public $source = '';
  215. /**
  216. * The output text
  217. *
  218. * @var string
  219. */
  220. protected $output = '';
  221. /**
  222. *
  223. * Array of rule parsers.
  224. *
  225. * Text_Wiki creates one instance of every rule that is applied to
  226. * the source text; this array holds those instances. The array key
  227. * is the rule name, and the array value is an instance of the rule
  228. * class.
  229. *
  230. * @access private
  231. *
  232. * @var array
  233. *
  234. */
  235. protected $parseObj = array();
  236. /**
  237. *
  238. * Array of rule renderers.
  239. *
  240. * Text_Wiki creates one instance of every rule that is applied to
  241. * the source text; this array holds those instances. The array key
  242. * is the rule name, and the array value is an instance of the rule
  243. * class.
  244. *
  245. * @access private
  246. *
  247. * @var array
  248. *
  249. */
  250. protected $renderObj = array();
  251. /**
  252. *
  253. * Array of format renderers.
  254. *
  255. * @access private
  256. *
  257. * @var array
  258. *
  259. */
  260. protected $formatObj = array();
  261. /**
  262. *
  263. * Array of paths to search, in order, for parsing and rendering rules.
  264. *
  265. * @access private
  266. *
  267. * @var array
  268. *
  269. */
  270. protected $path = array(
  271. 'parse' => array(),
  272. 'render' => array()
  273. );
  274. /**
  275. *
  276. * The directory separator character.
  277. *
  278. * @access private
  279. *
  280. * @var string
  281. *
  282. */
  283. private $_dirSep = DIRECTORY_SEPARATOR;
  284. /**
  285. * Temporary configuration variable
  286. *
  287. * @var string
  288. */
  289. protected $renderingType = 'preg';
  290. /**
  291. * Stack of rendering callbacks
  292. *
  293. * @var Array
  294. */
  295. private $_renderCallbacks = array();
  296. /**
  297. * Current output block
  298. *
  299. * @var string
  300. */
  301. private $_block;
  302. /**
  303. * A stack of blocks
  304. *
  305. * @param Array
  306. */
  307. private $_blocks;
  308. /**
  309. *
  310. * Constructor.
  311. *
  312. * **DEPRECATED**
  313. * Please use the singleton() or factory() methods.
  314. *
  315. * @access public
  316. *
  317. * @param array $rules The set of rules to load for this object. Defaults
  318. * to null, which will load the default ruleset for this parser.
  319. */
  320. function __construct($rules = null)
  321. {
  322. if (is_array($rules)) {
  323. $this->rules = array();
  324. foreach ($rules as $rule) {
  325. $this->rules[] = ucfirst($rule);
  326. }
  327. }
  328. /*
  329. $this->addPath(
  330. 'parse',
  331. $this->fixPath(dirname(__FILE__)) . 'Wiki/Parse/Default/'
  332. );
  333. */
  334. $this->addPath(
  335. 'parse', $this->fixPath(dirname(__FILE__) . '/Parse/')
  336. );
  337. $this->addPath(
  338. 'render',
  339. $this->fixPath(dirname(__FILE__) . '/Render/' )
  340. );
  341. $this->renderingType = 'char';
  342. $this->setRenderConf('xhtml', 'center', 'css', 'center');
  343. $this->setRenderConf('xhtml', 'url', 'target', null);
  344. }
  345. /**
  346. * Singleton.
  347. *
  348. * This avoids instantiating multiple Text_Wiki instances where a number
  349. * of objects are required in one call, e.g. to save memory in a
  350. * CMS invironment where several parsers are required in a single page.
  351. *
  352. * $single = & singleton();
  353. *
  354. * or
  355. *
  356. * $single = & singleton('Parser', array('Prefilter', 'Delimiter', 'Code', 'Function',
  357. * 'Html', 'Raw', 'Include', 'Embed', 'Anchor', 'Heading', 'Toc', 'Horiz',
  358. * 'Break', 'Blockquote', 'List', 'Deflist', 'Table', 'Image', 'Phplookup',
  359. * 'Center', 'Newline', 'Paragraph', 'Url', 'Freelink', 'Interwiki', 'Wikilink',
  360. * 'Colortext', 'Strong', 'Bold', 'Emphasis', 'Italic', 'Underline', 'Tt',
  361. * 'Superscript', 'Subscript', 'Revise', 'Tighten'));
  362. *
  363. * Call using a subset of this list. The order of passing rulesets in the
  364. * $rules array is important!
  365. *
  366. * After calling this, call $single->setParseConf(), setRenderConf() or setFormatConf()
  367. * as usual for a constructed object of this class.
  368. *
  369. * The internal static array of singleton objects has no index on the parser
  370. * rules, the only index is on the parser name. So if you call this multiple
  371. * times with different rules but the same parser name, you will get the same
  372. * static parser object each time.
  373. *
  374. * @access public
  375. * @static
  376. * @since Method available since Release 1.1.0
  377. * @param string $parser The parser to be used (defaults to 'Default').
  378. * @param array $rules The set of rules to instantiate the object. This
  379. * will only be used when the first call to singleton is made, if included
  380. * in further calls it will be effectively ignored.
  381. * @return &object a reference to the Text_Wiki unique instantiation.
  382. */
  383. /*
  384. public function &singleton($parser = 'Default', $rules = null)
  385. {
  386. static $only = array();
  387. if (!isset($only[$parser])) {
  388. $ret = & Text_Wiki::factory($parser, $rules);
  389. if (Text_Wiki::isError($ret)) {
  390. return $ret;
  391. }
  392. $only[$parser] =& $ret;
  393. }
  394. return $only[$parser];
  395. }
  396. */
  397. /**
  398. * Returns a Text_Wiki Parser class for the specified parser.
  399. *
  400. * @access public
  401. * @static
  402. * @param string $parser The name of the parse to instantiate
  403. * you need to have Text_Wiki_XXX installed to use $parser = 'XXX', it's E_FATAL
  404. * @param array $rules The rules to pass into the constructor
  405. * {@see Text_Wiki::singleton} for a list of rules
  406. * @return Text_Wiki a Parser object extended from Text_Wiki
  407. */
  408. /*
  409. public function &factory($parser = 'Default', $rules = null)
  410. {
  411. $class = 'Text_Wiki_' . ucfirst(strtolower($parser));
  412. $file = str_replace('_', '/', $class).'.php';
  413. if (!class_exists($class)) {
  414. require_once $file;
  415. if (!class_exists($class)) {
  416. return Text_Wiki::error(
  417. 'Class ' . $class . ' does not exist after requiring '. $file .
  418. ', install package ' . $class . "\n");
  419. }
  420. }
  421. $obj =& new $class($rules);
  422. return $obj;
  423. }
  424. */
  425. /**
  426. *
  427. * Set parser configuration for a specific rule and key.
  428. *
  429. * @access public
  430. *
  431. * @param string $rule The parse rule to set config for.
  432. *
  433. * @param array|string $arg1 The full config array to use for the
  434. * parse rule, or a conf key in that array.
  435. *
  436. * @param string $arg2 The config value for the key.
  437. *
  438. * @return void
  439. *
  440. */
  441. public function setParseConf($rule, $arg1, $arg2 = null)
  442. {
  443. $rule = ucwords(strtolower($rule));
  444. if (! isset($this->parseConf[$rule])) {
  445. $this->parseConf[$rule] = array();
  446. }
  447. // if first arg is an array, use it as the entire
  448. // conf array for the rule. otherwise, treat arg1
  449. // as a key and arg2 as a value for the rule conf.
  450. if (is_array($arg1)) {
  451. $this->parseConf[$rule] = $arg1;
  452. } else {
  453. $this->parseConf[$rule][$arg1] = $arg2;
  454. }
  455. }
  456. /**
  457. *
  458. * Get parser configuration for a specific rule and key.
  459. *
  460. * @access public
  461. *
  462. * @param string $rule The parse rule to get config for.
  463. *
  464. * @param string $key A key in the conf array; if null,
  465. * returns the entire conf array.
  466. *
  467. * @return mixed The whole conf array if no key is specified,
  468. * or the specific conf key value.
  469. *
  470. */
  471. public function getParseConf($rule, $key = null)
  472. {
  473. $rule = ucwords(strtolower($rule));
  474. // the rule does not exist
  475. if (! isset($this->parseConf[$rule])) {
  476. return null;
  477. }
  478. // no key requested, return the whole array
  479. if (is_null($key)) {
  480. return $this->parseConf[$rule];
  481. }
  482. // does the requested key exist?
  483. if (isset($this->parseConf[$rule][$key])) {
  484. // yes, return that value
  485. return $this->parseConf[$rule][$key];
  486. } else {
  487. // no
  488. return null;
  489. }
  490. }
  491. /**
  492. *
  493. * Set renderer configuration for a specific format, rule, and key.
  494. *
  495. * @access public
  496. *
  497. * @param string $format The render format to set config for.
  498. *
  499. * @param string $rule The render rule to set config for in the format.
  500. *
  501. * @param array|string $arg1 The config array, or the config key
  502. * within the render rule.
  503. *
  504. * @param string $arg2 The config value for the key.
  505. *
  506. * @return void
  507. *
  508. */
  509. function setRenderConf($format, $rule, $arg1, $arg2 = null)
  510. {
  511. $format = ucwords(strtolower($format));
  512. $rule = ucwords(strtolower($rule));
  513. if (! isset($this->renderConf[$format])) {
  514. $this->renderConf[$format] = array();
  515. }
  516. if (! isset($this->renderConf[$format][$rule])) {
  517. $this->renderConf[$format][$rule] = array();
  518. }
  519. // if first arg is an array, use it as the entire
  520. // conf array for the render rule. otherwise, treat arg1
  521. // as a key and arg2 as a value for the render rule conf.
  522. if (is_array($arg1)) {
  523. $this->renderConf[$format][$rule] = $arg1;
  524. } else {
  525. $this->renderConf[$format][$rule][$arg1] = $arg2;
  526. }
  527. }
  528. /**
  529. *
  530. * Get renderer configuration for a specific format, rule, and key.
  531. *
  532. * @access public
  533. *
  534. * @param string $format The render format to get config for.
  535. *
  536. * @param string $rule The render format rule to get config for.
  537. *
  538. * @param string $key A key in the conf array; if null,
  539. * returns the entire conf array.
  540. *
  541. * @return mixed The whole conf array if no key is specified,
  542. * or the specific conf key value.
  543. *
  544. */
  545. function getRenderConf($format, $rule, $key = null)
  546. {
  547. $format = ucwords(strtolower($format));
  548. $rule = ucwords(strtolower($rule));
  549. if (! isset($this->renderConf[$format]) ||
  550. ! isset($this->renderConf[$format][$rule])) {
  551. return null;
  552. }
  553. // no key requested, return the whole array
  554. if (is_null($key)) {
  555. return $this->renderConf[$format][$rule];
  556. }
  557. // does the requested key exist?
  558. if (isset($this->renderConf[$format][$rule][$key])) {
  559. // yes, return that value
  560. return $this->renderConf[$format][$rule][$key];
  561. } else {
  562. // no
  563. return null;
  564. }
  565. }
  566. /**
  567. *
  568. * Set format configuration for a specific rule and key.
  569. *
  570. * @access public
  571. *
  572. * @param string $format The format to set config for.
  573. *
  574. * @param string $key The config key within the format.
  575. *
  576. * @param string $val The config value for the key.
  577. *
  578. * @return void
  579. *
  580. */
  581. function setFormatConf($format, $arg1, $arg2 = null)
  582. {
  583. if (! is_array($this->formatConf[$format])) {
  584. $this->formatConf[$format] = array();
  585. }
  586. // if first arg is an array, use it as the entire
  587. // conf array for the format. otherwise, treat arg1
  588. // as a key and arg2 as a value for the format conf.
  589. if (is_array($arg1)) {
  590. $this->formatConf[$format] = $arg1;
  591. } else {
  592. $this->formatConf[$format][$arg1] = $arg2;
  593. }
  594. }
  595. /**
  596. *
  597. * Get configuration for a specific format and key.
  598. *
  599. * @access public
  600. *
  601. * @param string $format The format to get config for.
  602. *
  603. * @param mixed $key A key in the conf array; if null,
  604. * returns the entire conf array.
  605. *
  606. * @return mixed The whole conf array if no key is specified,
  607. * or the specific conf key value.
  608. *
  609. */
  610. function getFormatConf($format, $key = null)
  611. {
  612. // the format does not exist
  613. if (! isset($this->formatConf[$format])) {
  614. return null;
  615. }
  616. // no key requested, return the whole array
  617. if (is_null($key)) {
  618. return $this->formatConf[$format];
  619. }
  620. // does the requested key exist?
  621. if (isset($this->formatConf[$format][$key])) {
  622. // yes, return that value
  623. return $this->formatConf[$format][$key];
  624. } else {
  625. // no
  626. return null;
  627. }
  628. }
  629. /**
  630. *
  631. * Inserts a rule into to the rule set.
  632. *
  633. * @access public
  634. *
  635. * @param string $name The name of the rule. Should be different from
  636. * all other keys in the rule set.
  637. *
  638. * @param string $tgt The rule after which to insert this new rule. By
  639. * default (null) the rule is inserted at the end; if set to '', inserts
  640. * at the beginning.
  641. *
  642. * @return void
  643. *
  644. */
  645. function insertRule($name, $tgt = null)
  646. {
  647. $name = ucwords(strtolower($name));
  648. if (! is_null($tgt)) {
  649. $tgt = ucwords(strtolower($tgt));
  650. }
  651. // does the rule name to be inserted already exist?
  652. if (in_array($name, $this->rules)) {
  653. // yes, return
  654. return null;
  655. }
  656. // the target name is not null, and not '', but does not exist
  657. // in the list of rules. this means we're trying to insert after
  658. // a target key, but the target key isn't there.
  659. if (! is_null($tgt) && $tgt != '' &&
  660. ! in_array($tgt, $this->rules)) {
  661. return false;
  662. }
  663. // if $tgt is null, insert at the end. We know this is at the
  664. // end (instead of resetting an existing rule) becuase we exited
  665. // at the top of this method if the rule was already in place.
  666. if (is_null($tgt)) {
  667. $this->rules[] = $name;
  668. return true;
  669. }
  670. // save a copy of the current rules, then reset the rule set
  671. // so we can insert in the proper place later.
  672. // where to insert the rule?
  673. if ($tgt == '') {
  674. // insert at the beginning
  675. array_unshift($this->rules, $name);
  676. return true;
  677. }
  678. // insert after the named rule
  679. $tmp = $this->rules;
  680. $this->rules = array();
  681. foreach ($tmp as $val) {
  682. $this->rules[] = $val;
  683. if ($val == $tgt) {
  684. $this->rules[] = $name;
  685. }
  686. }
  687. return true;
  688. }
  689. /**
  690. *
  691. * Delete (remove or unset) a rule from the $rules property.
  692. *
  693. * @access public
  694. *
  695. * @param string $rule The name of the rule to remove.
  696. *
  697. * @return void
  698. *
  699. */
  700. function deleteRule($name)
  701. {
  702. $name = ucwords(strtolower($name));
  703. $key = array_search($name, $this->rules);
  704. if ($key !== false) {
  705. unset($this->rules[$key]);
  706. }
  707. }
  708. /**
  709. *
  710. * Change from one rule to another in-place.
  711. *
  712. * @access public
  713. *
  714. * @param string $old The name of the rule to change from.
  715. *
  716. * @param string $new The name of the rule to change to.
  717. *
  718. * @return void
  719. *
  720. */
  721. function changeRule($old, $new)
  722. {
  723. $old = ucwords(strtolower($old));
  724. $new = ucwords(strtolower($new));
  725. $key = array_search($old, $this->rules);
  726. if ($key !== false) {
  727. // delete the new name , case it was already there
  728. $this->deleteRule($new);
  729. $this->rules[$key] = $new;
  730. }
  731. }
  732. /**
  733. *
  734. * Enables a rule so that it is applied when parsing.
  735. *
  736. * @access public
  737. *
  738. * @param string $rule The name of the rule to enable.
  739. *
  740. * @return void
  741. *
  742. */
  743. function enableRule($name)
  744. {
  745. $name = ucwords(strtolower($name));
  746. $key = array_search($name, $this->disable);
  747. if ($key !== false) {
  748. unset($this->disable[$key]);
  749. }
  750. }
  751. /**
  752. *
  753. * Disables a rule so that it is not applied when parsing.
  754. *
  755. * @access public
  756. *
  757. * @param string $rule The name of the rule to disable.
  758. *
  759. * @return void
  760. *
  761. */
  762. function disableRule($name)
  763. {
  764. $name = ucwords(strtolower($name));
  765. $key = array_search($name, $this->disable);
  766. if ($key === false) {
  767. $this->disable[] = $name;
  768. }
  769. }
  770. /**
  771. *
  772. * Parses and renders the text passed to it, and returns the results.
  773. *
  774. * First, the method parses the source text, applying rules to the
  775. * text as it goes. These rules will modify the source text
  776. * in-place, replacing some text with delimited tokens (and
  777. * populating the $this->tokens array as it goes).
  778. *
  779. * Next, the method renders the in-place tokens into the requested
  780. * output format.
  781. *
  782. * Finally, the method returns the transformed text. Note that the
  783. * source text is transformed in place; once it is transformed, it is
  784. * no longer the same as the original source text.
  785. *
  786. * @access public
  787. *
  788. * @param string $text The source text to which wiki rules should be
  789. * applied, both for parsing and for rendering.
  790. *
  791. * @param string $format The target output format, typically 'xhtml'.
  792. * If a rule does not support a given format, the output from that
  793. * rule is rule-specific.
  794. *
  795. * @return string The transformed wiki text.
  796. *
  797. */
  798. function transform($text, $format = 'Xhtml')
  799. {
  800. $this->parse($text);
  801. return $this->render($format);
  802. }
  803. /**
  804. *
  805. * Sets the $_source text property, then parses it in place and
  806. * retains tokens in the $_tokens array property.
  807. *
  808. * @access public
  809. *
  810. * @param string $text The source text to which wiki rules should be
  811. * applied, both for parsing and for rendering.
  812. *
  813. * @return void
  814. *
  815. */
  816. function parse($text)
  817. {
  818. // set the object property for the source text
  819. $this->source = $text;
  820. // reset the tokens.
  821. $this->tokens = array();
  822. $this->_countRulesTokens = array();
  823. // apply the parse() method of each requested rule to the source
  824. // text.
  825. foreach ($this->rules as $name) {
  826. // do not parse the rules listed in $disable
  827. if (! in_array($name, $this->disable)) {
  828. // load the parsing object
  829. $this->loadParseObj($name);
  830. // load may have failed; only parse if
  831. // an object is in the array now
  832. if (is_object($this->parseObj[$name])) {
  833. $this->parseObj[$name]->parse();
  834. }
  835. }
  836. }
  837. }
  838. /**
  839. *
  840. * Renders tokens back into the source text, based on the requested format.
  841. *
  842. * @access public
  843. *
  844. * @param string $format The target output format, typically 'xhtml'.
  845. * If a rule does not support a given format, the output from that
  846. * rule is rule-specific.
  847. *
  848. * @return string The transformed wiki text.
  849. *
  850. */
  851. function render($format = 'Xhtml')
  852. {
  853. // the rendering method we're going to use from each rule
  854. $format = ucwords(strtolower($format));
  855. // the eventual output text
  856. $this->output = '';
  857. // when passing through the parsed source text, keep track of when
  858. // we are in a delimited section
  859. $in_delim = false;
  860. // when in a delimited section, capture the token key number
  861. $key = '';
  862. // load the format object, or crap out if we can't find it
  863. $result = $this->loadFormatObj($format);
  864. if ($this->isError($result)) {
  865. return $result;
  866. }
  867. /*
  868. * hunked by feelinglucky..
  869. // pre-rendering activity
  870. if (is_object($this->formatObj[$format])) {
  871. $this->output .= $this->formatObj[$format]->pre();
  872. }
  873. */
  874. // load the render objects
  875. foreach (array_keys($this->_countRulesTokens) as $rule) {
  876. $this->loadRenderObj($format, $rule);
  877. }
  878. if ($this->renderingType == 'preg') {
  879. $this->output = preg_replace_callback('/'.$this->delim.'(\d+)'.$this->delim.'/',
  880. array(&$this, '_renderToken'),
  881. $this->source);
  882. /*
  883. //Damn strtok()! Why does it "skip" empty parts of the string. It's useless now!
  884. } elseif ($this->renderingType == 'strtok') {
  885. echo '<pre>'.htmlentities($this->source).'</pre>';
  886. $t = strtok($this->source, $this->delim);
  887. $inToken = true;
  888. $i = 0;
  889. while ($t !== false) {
  890. echo 'Token: '.$i.'<pre>"'.htmlentities($t).'"</pre><br/><br/>';
  891. if ($inToken) {
  892. //$this->output .= $this->renderObj[$this->tokens[$t][0]]->token($this->tokens[$t][1]);
  893. } else {
  894. $this->output .= $t;
  895. }
  896. $inToken = !$inToken;
  897. $t = strtok($this->delim);
  898. ++$i;
  899. }
  900. */
  901. } else {
  902. // pass through the parsed source text character by character
  903. $this->_block = '';
  904. $tokenStack = array();
  905. $k = strlen($this->source);
  906. for ($i = 0; $i < $k; $i++) {
  907. // the current character
  908. $char = $this->source{$i};
  909. // are alredy in a delimited section?
  910. if ($in_delim) {
  911. // yes; are we ending the section?
  912. if ($char == $this->delim) {
  913. if (count($this->_renderCallbacks) == 0) {
  914. $this->output .= $this->_block;
  915. $this->_block = '';
  916. }
  917. if (isset($opts['type'])) {
  918. if ($opts['type'] == 'start') {
  919. array_push($tokenStack, $rule);
  920. } elseif ($opts['type'] == 'end') {
  921. if ($tokenStack[count($tokenStack) - 1] != $rule) {
  922. return Text_Wiki::error('Unbalanced tokens, check your syntax');
  923. } else {
  924. array_pop($tokenStack);
  925. }
  926. }
  927. }
  928. // yes, get the replacement text for the delimited
  929. // token number and unset the flag.
  930. $key = (int)$key;
  931. $rule = $this->tokens[$key][0];
  932. $opts = $this->tokens[$key][1];
  933. $this->_block .= $this->renderObj[$rule]->token($opts);
  934. $in_delim = false;
  935. } else {
  936. // no, add to the delimited token key number
  937. $key .= $char;
  938. }
  939. } else {
  940. // not currently in a delimited section.
  941. // are we starting into a delimited section?
  942. if ($char == $this->delim) {
  943. // yes, reset the previous key and
  944. // set the flag.
  945. $key = '';
  946. $in_delim = true;
  947. } else {
  948. // no, add to the output as-is
  949. $this->_block .= $char;
  950. }
  951. }
  952. }
  953. }
  954. if (count($this->_renderCallbacks)) {
  955. return $this->error('Render callbacks left over after processing finished');
  956. }
  957. /*
  958. while (count($this->_renderCallbacks)) {
  959. $this->popRenderCallback();
  960. }
  961. */
  962. if (strlen($this->_block)) {
  963. $this->output .= $this->_block;
  964. $this->_block = '';
  965. }
  966. /* tunk by feelinglucky
  967. // post-rendering activity
  968. if (is_object($this->formatObj[$format])) {
  969. $this->output .= $this->formatObj[$format]->post();
  970. }
  971. */
  972. // return the rendered source text.
  973. return $this->output;
  974. }
  975. /**
  976. * Renders a token, for use only as an internal callback
  977. *
  978. * @param array Matches from preg_rpelace_callback, [1] is the token number
  979. * @return string The rendered text for the token
  980. * @access private
  981. */
  982. function _renderToken($matches) {
  983. return $this->renderObj[$this->tokens[$matches[1]][0]]->token($this->tokens[$matches[1]][1]);
  984. }
  985. function registerRenderCallback($callback) {
  986. $this->_blocks[] = $this->_block;
  987. $this->_block = '';
  988. $this->_renderCallbacks[] = $callback;
  989. }
  990. function popRenderCallback() {
  991. if (count($this->_renderCallbacks) == 0) {
  992. return Text_Wiki::error('Render callback popped when no render callbacks in stack');
  993. } else {
  994. $callback = array_pop($this->_renderCallbacks);
  995. $this->_block = call_user_func($callback, $this->_block);
  996. if (count($this->_blocks)) {
  997. $parentBlock = array_pop($this->_blocks);
  998. $this->_block = $parentBlock.$this->_block;
  999. }
  1000. if (count($this->_renderCallbacks) == 0) {
  1001. $this->output .= $this->_block;
  1002. $this->_block = '';
  1003. }
  1004. }
  1005. }
  1006. /**
  1007. *
  1008. * Returns the parsed source text with delimited token placeholders.
  1009. *
  1010. * @access public
  1011. *
  1012. * @return string The parsed source text.
  1013. *
  1014. */
  1015. function getSource()
  1016. {
  1017. return $this->source;
  1018. }
  1019. /**
  1020. *
  1021. * Returns tokens that have been parsed out of the source text.
  1022. *
  1023. * @access public
  1024. *
  1025. * @param array $rules If an array of rule names is passed, only return
  1026. * tokens matching these rule names. If no array is passed, return all
  1027. * tokens.
  1028. *
  1029. * @return array An array of tokens.
  1030. *
  1031. */
  1032. function getTokens($rules = null)
  1033. {
  1034. if (is_null($rules)) {
  1035. return $this->tokens;
  1036. } else {
  1037. settype($rules, 'array');
  1038. $result = array();
  1039. foreach ($this->tokens as $key => $val) {
  1040. if (in_array($val[0], $rules)) {
  1041. $result[$key] = $val;
  1042. }
  1043. }
  1044. return $result;
  1045. }
  1046. }
  1047. /**
  1048. *
  1049. * Add a token to the Text_Wiki tokens array, and return a delimited
  1050. * token number.
  1051. *
  1052. * @access public
  1053. *
  1054. * @param array $options An associative array of options for the new
  1055. * token array element. The keys and values are specific to the
  1056. * rule, and may or may not be common to other rule options. Typical
  1057. * options keys are 'text' and 'type' but may include others.
  1058. *
  1059. * @param boolean $id_only If true, return only the token number, not
  1060. * a delimited token string.
  1061. *
  1062. * @return string|int By default, return the number of the
  1063. * newly-created token array element with a delimiter prefix and
  1064. * suffix; however, if $id_only is set to true, return only the token
  1065. * number (no delimiters).
  1066. *
  1067. */
  1068. function addToken($rule, $options = array(), $id_only = false)
  1069. {
  1070. // increment the token ID number. note that if you parse
  1071. // multiple times with the same Text_Wiki object, the ID number
  1072. // will not reset to zero.
  1073. static $id;
  1074. if (! isset($id)) {
  1075. $id = 0;
  1076. } else {
  1077. $id ++;
  1078. }
  1079. // force the options to be an array
  1080. settype($options, 'array');
  1081. // add the token
  1082. $this->tokens[$id] = array(
  1083. 0 => $rule,
  1084. 1 => $options
  1085. );
  1086. if (!isset($this->_countRulesTokens[$rule])) {
  1087. $this->_countRulesTokens[$rule] = 1;
  1088. } else {
  1089. ++$this->_countRulesTokens[$rule];
  1090. }
  1091. // return a value
  1092. if ($id_only) {
  1093. // return the last token number
  1094. return $id;
  1095. } else {
  1096. // return the token number with delimiters
  1097. return $this->delim . $id . $this->delim;
  1098. }
  1099. }
  1100. /**
  1101. *
  1102. * Set or re-set a token with specific information, overwriting any
  1103. * previous rule name and rule options.
  1104. *
  1105. * @access public
  1106. *
  1107. * @param int $id The token number to reset.
  1108. *
  1109. * @param int $rule The rule name to use.
  1110. *
  1111. * @param array $options An associative array of options for the
  1112. * token array element. The keys and values are specific to the
  1113. * rule, and may or may not be common to other rule options. Typical
  1114. * options keys are 'text' and 'type' but may include others.
  1115. *
  1116. * @return void
  1117. *
  1118. */
  1119. function setToken($id, $rule, $options = array())
  1120. {
  1121. $oldRule = $this->tokens[$id][0];
  1122. // reset the token
  1123. $this->tokens[$id] = array(
  1124. 0 => $rule,
  1125. 1 => $options
  1126. );
  1127. if ($rule != $oldRule) {
  1128. if (!($this->_countRulesTokens[$oldRule]--)) {
  1129. unset($this->_countRulesTokens[$oldRule]);
  1130. }
  1131. if (!isset($this->_countRulesTokens[$rule])) {
  1132. $this->_countRulesTokens[$rule] = 1;
  1133. } else {
  1134. ++$this->_countRulesTokens[$rule];
  1135. }
  1136. }
  1137. }
  1138. /**
  1139. *
  1140. * Load a rule parser class file.
  1141. *
  1142. * @access public
  1143. *
  1144. * @return bool True if loaded, false if not.
  1145. *
  1146. */
  1147. function loadParseObj($rule)
  1148. {
  1149. $rule = ucwords(strtolower($rule));
  1150. $file = $rule . '.php';
  1151. $class = "Text_Wiki_Parse_$rule";
  1152. if (!Typecho_Common::isAvailableClass($class)) {
  1153. $loc = $this->findFile('parse', $file);
  1154. if ($loc) {
  1155. // found the class
  1156. include_once $loc;
  1157. } else {
  1158. // can't find the class
  1159. $this->parseObj[$rule] = null;
  1160. // can't find the class
  1161. return $this->error(
  1162. "Parse rule '$rule' not found"
  1163. );
  1164. }
  1165. }
  1166. $this->parseObj[$rule] = new $class($this);
  1167. }
  1168. /**
  1169. *
  1170. * Load a rule-render class file.
  1171. *
  1172. * @access public
  1173. *
  1174. * @return bool True if loaded, false if not.
  1175. *
  1176. */
  1177. function loadRenderObj($format, $rule)
  1178. {
  1179. $format = ucwords(strtolower($format));
  1180. $rule = ucwords(strtolower($rule));
  1181. $file = "$format/$rule.php";
  1182. $class = "Text_Wiki_Render_$format" . "_$rule";
  1183. if (! Typecho_Common::isAvailableClass($class)) {
  1184. // load the class
  1185. $loc = $this->findFile('render', $file);
  1186. if ($loc) {
  1187. // found the class
  1188. include_once $loc;
  1189. } else {
  1190. // can't find the class
  1191. return $this->error(
  1192. "Render rule '$rule' in format '$format' not found"
  1193. );
  1194. }
  1195. }
  1196. $this->renderObj[$rule] = new $class($this);
  1197. }
  1198. /**
  1199. *
  1200. * Load a format-render class file.
  1201. *
  1202. * @access public
  1203. *
  1204. * @return bool True if loaded, false if not.
  1205. *
  1206. */
  1207. function loadFormatObj($format)
  1208. {
  1209. $format = ucwords(strtolower($format));
  1210. $file = $format . '.php';
  1211. $class = "Text_Wiki_Render_$format";
  1212. if (! Typecho_Common::isAvailableClass($class)) {
  1213. $loc = $this->findFile('render', $file);
  1214. if ($loc) {
  1215. // found the class
  1216. include_once $loc;
  1217. } else {
  1218. // can't find the class
  1219. return $this->error(
  1220. "Rendering format class '$class' not found"
  1221. );
  1222. }
  1223. }
  1224. $this->formatObj[$format] = new $class($this);
  1225. }
  1226. /**
  1227. *
  1228. * Add a path to a path array.
  1229. *
  1230. * @access public
  1231. *
  1232. * @param string $type The path-type to add (parse or render).
  1233. *
  1234. * @param string $dir The directory to add to the path-type.
  1235. *
  1236. * @return void
  1237. *
  1238. */
  1239. function addPath($type, $dir)
  1240. {
  1241. $dir = $this->fixPath($dir);
  1242. if (! isset($this->path[$type])) {
  1243. $this->path[$type] = array($dir);
  1244. } else {
  1245. array_unshift($this->path[$type], $dir);
  1246. }
  1247. }
  1248. /**
  1249. *
  1250. * Get the current path array for a path-type.
  1251. *
  1252. * @access public
  1253. *
  1254. * @param string $type The path-type to look up (plugin, filter, or
  1255. * template). If not set, returns all path types.
  1256. *
  1257. * @return array The array of paths for the requested type.
  1258. *
  1259. */
  1260. function getPath($type = null)
  1261. {
  1262. if (is_null($type)) {
  1263. return $this->path;
  1264. } elseif (! isset($this->path[$type])) {
  1265. return array();
  1266. } else {
  1267. return $this->path[$type];
  1268. }
  1269. }
  1270. /**
  1271. *
  1272. * Searches a series of paths for a given file.
  1273. *
  1274. * @param array $type The type of paths to search (template, plugin,
  1275. * or filter).
  1276. *
  1277. * @param string $file The file name to look for.
  1278. *
  1279. * @return string|bool The full path and file name for the target file,
  1280. * or boolean false if the file is not found in any of the paths.
  1281. *
  1282. */
  1283. function findFile($type, $file)
  1284. {
  1285. // get the set of paths
  1286. $set = $this->getPath($type);
  1287. // start looping through them
  1288. foreach ($set as $path) {
  1289. $fullname = $path . $this->_dirSep . $file;
  1290. if (file_exists($fullname) && is_readable($fullname)) {
  1291. return realpath($fullname);
  1292. }
  1293. }
  1294. // could not find the file in the set of paths
  1295. return false;
  1296. }
  1297. /**
  1298. *
  1299. * Append a trailing '/' to paths, unless the path is empty.
  1300. *
  1301. * @access private
  1302. *
  1303. * @param string $path The file path to fix
  1304. *
  1305. * @return string The fixed file path
  1306. *
  1307. */
  1308. function fixPath($path)
  1309. {
  1310. if (realpath($path)){
  1311. return realpath($path); // . (is_dir($path) ? $this->_dirSep : '');
  1312. } else {
  1313. return '';
  1314. }
  1315. /*
  1316. $len = strlen($this->_dirSep);
  1317. if (! empty($path) &&
  1318. substr($path, -1 * $len, $len) != $this->_dirSep) {
  1319. return realpath($path) . $this->_dirSep;
  1320. } else {
  1321. return realpath($path);
  1322. }
  1323. */
  1324. }
  1325. /**
  1326. *
  1327. * Simple error-object generator.
  1328. *
  1329. * @access public
  1330. *
  1331. * @param string $message The error message.
  1332. *
  1333. * @return object PEAR_Error
  1334. *
  1335. */
  1336. function &error($message)
  1337. {
  1338. /*
  1339. if (! class_exists('PEAR_Error')) {
  1340. include_once 'PEAR.php';
  1341. }
  1342. */
  1343. throw new Exception($message);
  1344. return false;
  1345. //return PEAR::throwError($message);
  1346. }
  1347. /**
  1348. *
  1349. * Simple error checker.
  1350. *
  1351. * @access public
  1352. *
  1353. * @param mixed $obj Check if this is a PEAR_Error object or not.
  1354. *
  1355. * @return bool True if a PEAR_Error, false if not.
  1356. *
  1357. */
  1358. function isError(&$obj)
  1359. {
  1360. return is_a($obj, 'PEAR_Error');
  1361. }
  1362. /**
  1363. * Constructor: just adds the path to Creole rules
  1364. *
  1365. * @access public
  1366. * @param array $rules The set of rules to load for this object.
  1367. */
  1368. function checkInnerTags(&$text) {
  1369. $started = array();
  1370. $i = false;
  1371. while (($i = strpos($text, $this->delim, $i)) !== false) {
  1372. $j = strpos($text, $this->delim, $i + 1);
  1373. $t = substr($text, $i + 1, $j - $i - 1);
  1374. $i = $j + 1;
  1375. $rule = strtolower($this->tokens[$t][0]);
  1376. $type = $this->tokens[$t][1]['type'];
  1377. if ($type == 'start') {
  1378. if (empty($started[$rule])) {
  1379. $started[$rule] = 0;
  1380. }
  1381. $started[$rule] += 1;
  1382. }
  1383. else if ($type == 'end') {
  1384. if (! $started[$rule]) return false;
  1385. $started[$rule] -= 1;
  1386. if (! $started[$rule]) unset($started[$rule]);
  1387. }
  1388. }
  1389. return ! (count($started) > 0);
  1390. }
  1391. function restoreRaw($text) {
  1392. $i = false;
  1393. while (($i = strpos($text, $this->delim, $i)) !== false) {
  1394. $j = strpos($text, $this->delim, $i + 1);
  1395. $t = substr($text, $i + 1, $j - $i - 1);
  1396. $rule = strtolower($this->tokens[$t][0]);
  1397. if ($rule == 'raw') {
  1398. $text = str_replace($this->delim. $t. $this->delim, $this->tokens[$t][1]['text'], $text);
  1399. } else {
  1400. $i = $j + 1;
  1401. }
  1402. }
  1403. return $text;
  1404. }
  1405. }
  1406. ?>