PageRenderTime 43ms CodeModel.GetById 11ms RepoModel.GetById 1ms app.codeStats 0ms

/inc/simpletest/parser.php

https://github.com/chregu/fluxcms
PHP | 761 lines | 344 code | 48 blank | 369 comment | 52 complexity | 8666f74ef47d7c5cf0792deeed8b14d9 MD5 | raw file
Possible License(s): GPL-2.0, BSD-3-Clause, Apache-2.0, LGPL-2.1
  1. <?php
  2. /**
  3. * base include file for SimpleTest
  4. * @package SimpleTest
  5. * @subpackage MockObjects
  6. * @version $Id: parser.php,v 1.70 2005/06/11 16:29:11 lastcraft Exp $
  7. */
  8. /**#@+
  9. * Lexer mode stack constants
  10. */
  11. if (! defined('LEXER_ENTER')) {
  12. define('LEXER_ENTER', 1);
  13. }
  14. if (! defined('LEXER_MATCHED')) {
  15. define('LEXER_MATCHED', 2);
  16. }
  17. if (! defined('LEXER_UNMATCHED')) {
  18. define('LEXER_UNMATCHED', 3);
  19. }
  20. if (! defined('LEXER_EXIT')) {
  21. define('LEXER_EXIT', 4);
  22. }
  23. if (! defined('LEXER_SPECIAL')) {
  24. define('LEXER_SPECIAL', 5);
  25. }
  26. /**#@-*/
  27. /**
  28. * Compounded regular expression. Any of
  29. * the contained patterns could match and
  30. * when one does, it's label is returned.
  31. * @package SimpleTest
  32. * @subpackage WebTester
  33. */
  34. class ParallelRegex {
  35. var $_patterns;
  36. var $_labels;
  37. var $_regex;
  38. var $_case;
  39. /**
  40. * Constructor. Starts with no patterns.
  41. * @param boolean $case True for case sensitive, false
  42. * for insensitive.
  43. * @access public
  44. */
  45. function ParallelRegex($case) {
  46. $this->_case = $case;
  47. $this->_patterns = array();
  48. $this->_labels = array();
  49. $this->_regex = null;
  50. }
  51. /**
  52. * Adds a pattern with an optional label.
  53. * @param string $pattern Perl style regex, but ( and )
  54. * lose the usual meaning.
  55. * @param string $label Label of regex to be returned
  56. * on a match.
  57. * @access public
  58. */
  59. function addPattern($pattern, $label = true) {
  60. $count = count($this->_patterns);
  61. $this->_patterns[$count] = $pattern;
  62. $this->_labels[$count] = $label;
  63. $this->_regex = null;
  64. }
  65. /**
  66. * Attempts to match all patterns at once against
  67. * a string.
  68. * @param string $subject String to match against.
  69. * @param string $match First matched portion of
  70. * subject.
  71. * @return boolean True on success.
  72. * @access public
  73. */
  74. function match($subject, &$match) {
  75. if (count($this->_patterns) == 0) {
  76. return false;
  77. }
  78. if (! preg_match($this->_getCompoundedRegex(), $subject, $matches)) {
  79. $match = '';
  80. return false;
  81. }
  82. $match = $matches[0];
  83. for ($i = 1; $i < count($matches); $i++) {
  84. if ($matches[$i]) {
  85. return $this->_labels[$i - 1];
  86. }
  87. }
  88. return true;
  89. }
  90. /**
  91. * Compounds the patterns into a single
  92. * regular expression separated with the
  93. * "or" operator. Caches the regex.
  94. * Will automatically escape (, ) and / tokens.
  95. * @param array $patterns List of patterns in order.
  96. * @access private
  97. */
  98. function _getCompoundedRegex() {
  99. if ($this->_regex == null) {
  100. for ($i = 0, $count = count($this->_patterns); $i < $count; $i++) {
  101. $this->_patterns[$i] = '(' . str_replace(
  102. array('/', '(', ')'),
  103. array('\/', '\(', '\)'),
  104. $this->_patterns[$i]) . ')';
  105. }
  106. $this->_regex = "/" . implode("|", $this->_patterns) . "/" . $this->_getPerlMatchingFlags();
  107. }
  108. return $this->_regex;
  109. }
  110. /**
  111. * Accessor for perl regex mode flags to use.
  112. * @return string Perl regex flags.
  113. * @access private
  114. */
  115. function _getPerlMatchingFlags() {
  116. return ($this->_case ? "msS" : "msSi");
  117. }
  118. }
  119. /**
  120. * States for a stack machine.
  121. * @package SimpleTest
  122. * @subpackage WebTester
  123. */
  124. class SimpleStateStack {
  125. var $_stack;
  126. /**
  127. * Constructor. Starts in named state.
  128. * @param string $start Starting state name.
  129. * @access public
  130. */
  131. function SimpleStateStack($start) {
  132. $this->_stack = array($start);
  133. }
  134. /**
  135. * Accessor for current state.
  136. * @return string State.
  137. * @access public
  138. */
  139. function getCurrent() {
  140. return $this->_stack[count($this->_stack) - 1];
  141. }
  142. /**
  143. * Adds a state to the stack and sets it
  144. * to be the current state.
  145. * @param string $state New state.
  146. * @access public
  147. */
  148. function enter($state) {
  149. array_push($this->_stack, $state);
  150. }
  151. /**
  152. * Leaves the current state and reverts
  153. * to the previous one.
  154. * @return boolean False if we drop off
  155. * the bottom of the list.
  156. * @access public
  157. */
  158. function leave() {
  159. if (count($this->_stack) == 1) {
  160. return false;
  161. }
  162. array_pop($this->_stack);
  163. return true;
  164. }
  165. }
  166. /**
  167. * Accepts text and breaks it into tokens.
  168. * Some optimisation to make the sure the
  169. * content is only scanned by the PHP regex
  170. * parser once. Lexer modes must not start
  171. * with leading underscores.
  172. * @package SimpleTest
  173. * @subpackage WebTester
  174. */
  175. class SimpleLexer {
  176. var $_regexes;
  177. var $_parser;
  178. var $_mode;
  179. var $_mode_handlers;
  180. var $_case;
  181. /**
  182. * Sets up the lexer in case insensitive matching
  183. * by default.
  184. * @param SimpleSaxParser $parser Handling strategy by
  185. * reference.
  186. * @param string $start Starting handler.
  187. * @param boolean $case True for case sensitive.
  188. * @access public
  189. */
  190. function SimpleLexer(&$parser, $start = "accept", $case = false) {
  191. $this->_case = $case;
  192. $this->_regexes = array();
  193. $this->_parser = &$parser;
  194. $this->_mode = &new SimpleStateStack($start);
  195. $this->_mode_handlers = array($start => $start);
  196. }
  197. /**
  198. * Adds a token search pattern for a particular
  199. * parsing mode. The pattern does not change the
  200. * current mode.
  201. * @param string $pattern Perl style regex, but ( and )
  202. * lose the usual meaning.
  203. * @param string $mode Should only apply this
  204. * pattern when dealing with
  205. * this type of input.
  206. * @access public
  207. */
  208. function addPattern($pattern, $mode = "accept") {
  209. if (! isset($this->_regexes[$mode])) {
  210. $this->_regexes[$mode] = new ParallelRegex($this->_case);
  211. }
  212. $this->_regexes[$mode]->addPattern($pattern);
  213. if (! isset($this->_mode_handlers[$mode])) {
  214. $this->_mode_handlers[$mode] = $mode;
  215. }
  216. }
  217. /**
  218. * Adds a pattern that will enter a new parsing
  219. * mode. Useful for entering parenthesis, strings,
  220. * tags, etc.
  221. * @param string $pattern Perl style regex, but ( and )
  222. * lose the usual meaning.
  223. * @param string $mode Should only apply this
  224. * pattern when dealing with
  225. * this type of input.
  226. * @param string $new_mode Change parsing to this new
  227. * nested mode.
  228. * @access public
  229. */
  230. function addEntryPattern($pattern, $mode, $new_mode) {
  231. if (! isset($this->_regexes[$mode])) {
  232. $this->_regexes[$mode] = new ParallelRegex($this->_case);
  233. }
  234. $this->_regexes[$mode]->addPattern($pattern, $new_mode);
  235. if (! isset($this->_mode_handlers[$new_mode])) {
  236. $this->_mode_handlers[$new_mode] = $new_mode;
  237. }
  238. }
  239. /**
  240. * Adds a pattern that will exit the current mode
  241. * and re-enter the previous one.
  242. * @param string $pattern Perl style regex, but ( and )
  243. * lose the usual meaning.
  244. * @param string $mode Mode to leave.
  245. * @access public
  246. */
  247. function addExitPattern($pattern, $mode) {
  248. if (! isset($this->_regexes[$mode])) {
  249. $this->_regexes[$mode] = new ParallelRegex($this->_case);
  250. }
  251. $this->_regexes[$mode]->addPattern($pattern, "__exit");
  252. if (! isset($this->_mode_handlers[$mode])) {
  253. $this->_mode_handlers[$mode] = $mode;
  254. }
  255. }
  256. /**
  257. * Adds a pattern that has a special mode. Acts as an entry
  258. * and exit pattern in one go, effectively calling a special
  259. * parser handler for this token only.
  260. * @param string $pattern Perl style regex, but ( and )
  261. * lose the usual meaning.
  262. * @param string $mode Should only apply this
  263. * pattern when dealing with
  264. * this type of input.
  265. * @param string $special Use this mode for this one token.
  266. * @access public
  267. */
  268. function addSpecialPattern($pattern, $mode, $special) {
  269. if (! isset($this->_regexes[$mode])) {
  270. $this->_regexes[$mode] = new ParallelRegex($this->_case);
  271. }
  272. $this->_regexes[$mode]->addPattern($pattern, "_$special");
  273. if (! isset($this->_mode_handlers[$special])) {
  274. $this->_mode_handlers[$special] = $special;
  275. }
  276. }
  277. /**
  278. * Adds a mapping from a mode to another handler.
  279. * @param string $mode Mode to be remapped.
  280. * @param string $handler New target handler.
  281. * @access public
  282. */
  283. function mapHandler($mode, $handler) {
  284. $this->_mode_handlers[$mode] = $handler;
  285. }
  286. /**
  287. * Splits the page text into tokens. Will fail
  288. * if the handlers report an error or if no
  289. * content is consumed. If successful then each
  290. * unparsed and parsed token invokes a call to the
  291. * held listener.
  292. * @param string $raw Raw HTML text.
  293. * @return boolean True on success, else false.
  294. * @access public
  295. */
  296. function parse($raw) {
  297. if (! isset($this->_parser)) {
  298. return false;
  299. }
  300. $length = strlen($raw);
  301. while (is_array($parsed = $this->_reduce($raw))) {
  302. list($raw, $unmatched, $matched, $mode) = $parsed;
  303. if (! $this->_dispatchTokens($unmatched, $matched, $mode)) {
  304. return false;
  305. }
  306. if ($raw === '') {
  307. return true;
  308. }
  309. if (strlen($raw) == $length) {
  310. return false;
  311. }
  312. $length = strlen($raw);
  313. }
  314. if (! $parsed) {
  315. return false;
  316. }
  317. return $this->_invokeParser($raw, LEXER_UNMATCHED);
  318. }
  319. /**
  320. * Sends the matched token and any leading unmatched
  321. * text to the parser changing the lexer to a new
  322. * mode if one is listed.
  323. * @param string $unmatched Unmatched leading portion.
  324. * @param string $matched Actual token match.
  325. * @param string $mode Mode after match. A boolean
  326. * false mode causes no change.
  327. * @return boolean False if there was any error
  328. * from the parser.
  329. * @access private
  330. */
  331. function _dispatchTokens($unmatched, $matched, $mode = false) {
  332. if (! $this->_invokeParser($unmatched, LEXER_UNMATCHED)) {
  333. return false;
  334. }
  335. if (is_bool($mode)) {
  336. return $this->_invokeParser($matched, LEXER_MATCHED);
  337. }
  338. if ($this->_isModeEnd($mode)) {
  339. if (! $this->_invokeParser($matched, LEXER_EXIT)) {
  340. return false;
  341. }
  342. return $this->_mode->leave();
  343. }
  344. if ($this->_isSpecialMode($mode)) {
  345. $this->_mode->enter($this->_decodeSpecial($mode));
  346. if (! $this->_invokeParser($matched, LEXER_SPECIAL)) {
  347. return false;
  348. }
  349. return $this->_mode->leave();
  350. }
  351. $this->_mode->enter($mode);
  352. return $this->_invokeParser($matched, LEXER_ENTER);
  353. }
  354. /**
  355. * Tests to see if the new mode is actually to leave
  356. * the current mode and pop an item from the matching
  357. * mode stack.
  358. * @param string $mode Mode to test.
  359. * @return boolean True if this is the exit mode.
  360. * @access private
  361. */
  362. function _isModeEnd($mode) {
  363. return ($mode === "__exit");
  364. }
  365. /**
  366. * Test to see if the mode is one where this mode
  367. * is entered for this token only and automatically
  368. * leaves immediately afterwoods.
  369. * @param string $mode Mode to test.
  370. * @return boolean True if this is the exit mode.
  371. * @access private
  372. */
  373. function _isSpecialMode($mode) {
  374. return (strncmp($mode, "_", 1) == 0);
  375. }
  376. /**
  377. * Strips the magic underscore marking single token
  378. * modes.
  379. * @param string $mode Mode to decode.
  380. * @return string Underlying mode name.
  381. * @access private
  382. */
  383. function _decodeSpecial($mode) {
  384. return substr($mode, 1);
  385. }
  386. /**
  387. * Calls the parser method named after the current
  388. * mode. Empty content will be ignored. The lexer
  389. * has a parser handler for each mode in the lexer.
  390. * @param string $content Text parsed.
  391. * @param boolean $is_match Token is recognised rather
  392. * than unparsed data.
  393. * @access private
  394. */
  395. function _invokeParser($content, $is_match) {
  396. if (($content === '') || ($content === false)) {
  397. return true;
  398. }
  399. $handler = $this->_mode_handlers[$this->_mode->getCurrent()];
  400. return $this->_parser->$handler($content, $is_match);
  401. }
  402. /**
  403. * Tries to match a chunk of text and if successful
  404. * removes the recognised chunk and any leading
  405. * unparsed data. Empty strings will not be matched.
  406. * @param string $raw The subject to parse. This is the
  407. * content that will be eaten.
  408. * @return array/boolean Three item list of unparsed
  409. * content followed by the
  410. * recognised token and finally the
  411. * action the parser is to take.
  412. * True if no match, false if there
  413. * is a parsing error.
  414. * @access private
  415. */
  416. function _reduce($raw) {
  417. if ($action = $this->_regexes[$this->_mode->getCurrent()]->match($raw, $match)) {
  418. $unparsed_character_count = strpos($raw, $match);
  419. $unparsed = substr($raw, 0, $unparsed_character_count);
  420. $raw = substr($raw, $unparsed_character_count + strlen($match));
  421. return array($raw, $unparsed, $match, $action);
  422. }
  423. return true;
  424. }
  425. }
  426. /**
  427. * Converts HTML tokens into selected SAX events.
  428. * @package SimpleTest
  429. * @subpackage WebTester
  430. */
  431. class SimpleSaxParser {
  432. var $_lexer;
  433. var $_listener;
  434. var $_tag;
  435. var $_attributes;
  436. var $_current_attribute;
  437. /**
  438. * Sets the listener.
  439. * @param SimpleSaxListener $listener SAX event handler.
  440. * @access public
  441. */
  442. function SimpleSaxParser(&$listener) {
  443. $this->_listener = &$listener;
  444. $this->_lexer = &$this->createLexer($this);
  445. $this->_tag = '';
  446. $this->_attributes = array();
  447. $this->_current_attribute = '';
  448. }
  449. /**
  450. * Runs the content through the lexer which
  451. * should call back to the acceptors.
  452. * @param string $raw Page text to parse.
  453. * @return boolean False if parse error.
  454. * @access public
  455. */
  456. function parse($raw) {
  457. return $this->_lexer->parse($raw);
  458. }
  459. /**
  460. * Sets up the matching lexer. Starts in 'text' mode.
  461. * @param SimpleSaxParser $parser Event generator, usually $self.
  462. * @return SimpleLexer Lexer suitable for this parser.
  463. * @access public
  464. * @static
  465. */
  466. function &createLexer(&$parser) {
  467. $lexer = &new SimpleLexer($parser, 'text');
  468. $lexer->mapHandler('text', 'acceptTextToken');
  469. SimpleSaxParser::_addSkipping($lexer);
  470. foreach (SimpleSaxParser::_getParsedTags() as $tag) {
  471. SimpleSaxParser::_addTag($lexer, $tag);
  472. }
  473. SimpleSaxParser::_addInTagTokens($lexer);
  474. return $lexer;
  475. }
  476. /**
  477. * List of parsed tags. Others are ignored.
  478. * @return array List of searched for tags.
  479. * @access private
  480. */
  481. function _getParsedTags() {
  482. return array('a', 'title', 'form', 'input', 'button', 'textarea', 'select',
  483. 'option', 'frameset', 'frame', 'label');
  484. }
  485. /**
  486. * The lexer has to skip certain sections such
  487. * as server code, client code and styles.
  488. * @param SimpleLexer $lexer Lexer to add patterns to.
  489. * @access private
  490. * @static
  491. */
  492. function _addSkipping(&$lexer) {
  493. $lexer->mapHandler('css', 'ignore');
  494. $lexer->addEntryPattern('<style', 'text', 'css');
  495. $lexer->addExitPattern('</style>', 'css');
  496. $lexer->mapHandler('js', 'ignore');
  497. $lexer->addEntryPattern('<script', 'text', 'js');
  498. $lexer->addExitPattern('</script>', 'js');
  499. $lexer->mapHandler('comment', 'ignore');
  500. $lexer->addEntryPattern('<!--', 'text', 'comment');
  501. $lexer->addExitPattern('-->', 'comment');
  502. }
  503. /**
  504. * Pattern matches to start and end a tag.
  505. * @param SimpleLexer $lexer Lexer to add patterns to.
  506. * @param string $tag Name of tag to scan for.
  507. * @access private
  508. * @static
  509. */
  510. function _addTag(&$lexer, $tag) {
  511. $lexer->addSpecialPattern("</$tag>", 'text', 'acceptEndToken');
  512. $lexer->addEntryPattern("<$tag", 'text', 'tag');
  513. }
  514. /**
  515. * Pattern matches to parse the inside of a tag
  516. * including the attributes and their quoting.
  517. * @param SimpleLexer $lexer Lexer to add patterns to.
  518. * @access private
  519. * @static
  520. */
  521. function _addInTagTokens(&$lexer) {
  522. $lexer->mapHandler('tag', 'acceptStartToken');
  523. $lexer->addSpecialPattern('\s+', 'tag', 'ignore');
  524. SimpleSaxParser::_addAttributeTokens($lexer);
  525. $lexer->addExitPattern('>', 'tag');
  526. }
  527. /**
  528. * Matches attributes that are either single quoted,
  529. * double quoted or unquoted.
  530. * @param SimpleLexer $lexer Lexer to add patterns to.
  531. * @access private
  532. * @static
  533. */
  534. function _addAttributeTokens(&$lexer) {
  535. $lexer->mapHandler('dq_attribute', 'acceptAttributeToken');
  536. $lexer->addEntryPattern('=\s*"', 'tag', 'dq_attribute');
  537. $lexer->addPattern("\\\\\"", 'dq_attribute');
  538. $lexer->addExitPattern('"', 'dq_attribute');
  539. $lexer->mapHandler('sq_attribute', 'acceptAttributeToken');
  540. $lexer->addEntryPattern("=\s*'", 'tag', 'sq_attribute');
  541. $lexer->addPattern("\\\\'", 'sq_attribute');
  542. $lexer->addExitPattern("'", 'sq_attribute');
  543. $lexer->mapHandler('uq_attribute', 'acceptAttributeToken');
  544. $lexer->addSpecialPattern('=\s*[^>\s]*', 'tag', 'uq_attribute');
  545. }
  546. /**
  547. * Accepts a token from the tag mode. If the
  548. * starting element completes then the element
  549. * is dispatched and the current attributes
  550. * set back to empty. The element or attribute
  551. * name is converted to lower case.
  552. * @param string $token Incoming characters.
  553. * @param integer $event Lexer event type.
  554. * @return boolean False if parse error.
  555. * @access public
  556. */
  557. function acceptStartToken($token, $event) {
  558. if ($event == LEXER_ENTER) {
  559. $this->_tag = strtolower(substr($token, 1));
  560. return true;
  561. }
  562. if ($event == LEXER_EXIT) {
  563. $success = $this->_listener->startElement(
  564. $this->_tag,
  565. $this->_attributes);
  566. $this->_tag = "";
  567. $this->_attributes = array();
  568. return $success;
  569. }
  570. if ($token != "=") {
  571. $this->_current_attribute = strtolower(SimpleSaxParser::decodeHtml($token));
  572. $this->_attributes[$this->_current_attribute] = "";
  573. }
  574. return true;
  575. }
  576. /**
  577. * Accepts a token from the end tag mode.
  578. * The element name is converted to lower case.
  579. * @param string $token Incoming characters.
  580. * @param integer $event Lexer event type.
  581. * @return boolean False if parse error.
  582. * @access public
  583. */
  584. function acceptEndToken($token, $event) {
  585. if (! preg_match('/<\/(.*)>/', $token, $matches)) {
  586. return false;
  587. }
  588. return $this->_listener->endElement(strtolower($matches[1]));
  589. }
  590. /**
  591. * Part of the tag data.
  592. * @param string $token Incoming characters.
  593. * @param integer $event Lexer event type.
  594. * @return boolean False if parse error.
  595. * @access public
  596. */
  597. function acceptAttributeToken($token, $event) {
  598. if ($event == LEXER_UNMATCHED) {
  599. $this->_attributes[$this->_current_attribute] .=
  600. SimpleSaxParser::decodeHtml($token);
  601. }
  602. if ($event == LEXER_SPECIAL) {
  603. $this->_attributes[$this->_current_attribute] .=
  604. preg_replace('/^=\s*/' , '', SimpleSaxParser::decodeHtml($token));
  605. }
  606. return true;
  607. }
  608. /**
  609. * A character entity.
  610. * @param string $token Incoming characters.
  611. * @param integer $event Lexer event type.
  612. * @return boolean False if parse error.
  613. * @access public
  614. */
  615. function acceptEntityToken($token, $event) {
  616. }
  617. /**
  618. * Character data between tags regarded as
  619. * important.
  620. * @param string $token Incoming characters.
  621. * @param integer $event Lexer event type.
  622. * @return boolean False if parse error.
  623. * @access public
  624. */
  625. function acceptTextToken($token, $event) {
  626. return $this->_listener->addContent($token);
  627. }
  628. /**
  629. * Incoming data to be ignored.
  630. * @param string $token Incoming characters.
  631. * @param integer $event Lexer event type.
  632. * @return boolean False if parse error.
  633. * @access public
  634. */
  635. function ignore($token, $event) {
  636. return true;
  637. }
  638. /**
  639. * Decodes any HTML entities.
  640. * @param string $html Incoming HTML.
  641. * @return string Outgoing plain text.
  642. * @access public
  643. * @static
  644. */
  645. function decodeHtml($html) {
  646. static $translations;
  647. if (! isset($translations)) {
  648. $translations = array_flip(get_html_translation_table(HTML_ENTITIES));
  649. }
  650. return strtr($html, $translations);
  651. }
  652. /**
  653. * Turns HTML into text browser visible text. Images
  654. * are converted to their alt text and tags are supressed.
  655. * Entities are converted to their visible representation.
  656. * @param string $html HTML to convert.
  657. * @return string Plain text.
  658. * @access public
  659. * @static
  660. */
  661. function normalise($html) {
  662. $text = preg_replace('|<!--.*?-->|', '', $html);
  663. $text = preg_replace('|<img.*?alt\s*=\s*"(.*?)".*?>|', ' \1 ', $text);
  664. $text = preg_replace('|<img.*?alt\s*=\s*\'(.*?)\'.*?>|', ' \1 ', $text);
  665. $text = preg_replace('|<img.*?alt\s*=\s*([a-zA-Z_]+).*?>|', ' \1 ', $text);
  666. $text = preg_replace('|<.*?>|', '', $text);
  667. $text = SimpleSaxParser::decodeHtml($text);
  668. $text = preg_replace('|\s+|', ' ', $text);
  669. return trim($text);
  670. }
  671. }
  672. /**
  673. * SAX event handler.
  674. * @package SimpleTest
  675. * @subpackage WebTester
  676. * @abstract
  677. */
  678. class SimpleSaxListener {
  679. /**
  680. * Sets the document to write to.
  681. * @access public
  682. */
  683. function SimpleSaxListener() {
  684. }
  685. /**
  686. * Start of element event.
  687. * @param string $name Element name.
  688. * @param hash $attributes Name value pairs.
  689. * Attributes without content
  690. * are marked as true.
  691. * @return boolean False on parse error.
  692. * @access public
  693. */
  694. function startElement($name, $attributes) {
  695. }
  696. /**
  697. * End of element event.
  698. * @param string $name Element name.
  699. * @return boolean False on parse error.
  700. * @access public
  701. */
  702. function endElement($name) {
  703. }
  704. /**
  705. * Unparsed, but relevant data.
  706. * @param string $text May include unparsed tags.
  707. * @return boolean False on parse error.
  708. * @access public
  709. */
  710. function addContent($text) {
  711. }
  712. }
  713. ?>