parser.php 25 KB

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