PageRenderTime 49ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/modules/phpunit-1.0/vendor/PHPUnit/Util/XML.php

https://github.com/robertleeplummerjr/bluebox
PHP | 784 lines | 446 code | 120 blank | 218 comment | 114 complexity | 32743ba81290a1781bc7d753d0626f51 MD5 | raw file
  1. <?php
  2. /**
  3. * PHPUnit
  4. *
  5. * Copyright (c) 2002-2009, Sebastian Bergmann <sb@sebastian-bergmann.de>.
  6. * All rights reserved.
  7. *
  8. * Redistribution and use in source and binary forms, with or without
  9. * modification, are permitted provided that the following conditions
  10. * are met:
  11. *
  12. * * Redistributions of source code must retain the above copyright
  13. * notice, this list of conditions and the following disclaimer.
  14. *
  15. * * Redistributions in binary form must reproduce the above copyright
  16. * notice, this list of conditions and the following disclaimer in
  17. * the documentation and/or other materials provided with the
  18. * distribution.
  19. *
  20. * * Neither the name of Sebastian Bergmann nor the names of his
  21. * contributors may be used to endorse or promote products derived
  22. * from this software without specific prior written permission.
  23. *
  24. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  25. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  26. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
  27. * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
  28. * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  29. * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  30. * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  31. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  32. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  33. * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
  34. * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  35. * POSSIBILITY OF SUCH DAMAGE.
  36. *
  37. * @category Testing
  38. * @package PHPUnit
  39. * @author Sebastian Bergmann <sb@sebastian-bergmann.de>
  40. * @copyright 2002-2009 Sebastian Bergmann <sb@sebastian-bergmann.de>
  41. * @license http://www.opensource.org/licenses/bsd-license.php BSD License
  42. * @version SVN: $Id: XML.php 4645 2009-02-17 12:10:52Z sb $
  43. * @link http://www.phpunit.de/
  44. * @since File available since Release 3.2.0
  45. */
  46. require_once 'PHPUnit/Util/Filter.php';
  47. PHPUnit_Util_Filter::addFileToFilter(__FILE__, 'PHPUNIT');
  48. /**
  49. * XML helpers.
  50. *
  51. * @category Testing
  52. * @package PHPUnit
  53. * @author Sebastian Bergmann <sb@sebastian-bergmann.de>
  54. * @copyright 2002-2009 Sebastian Bergmann <sb@sebastian-bergmann.de>
  55. * @license http://www.opensource.org/licenses/bsd-license.php BSD License
  56. * @version Release: @package_version@
  57. * @link http://www.phpunit.de/
  58. * @since Class available since Release 3.2.0
  59. */
  60. class PHPUnit_Util_XML
  61. {
  62. /**
  63. * Converts a string to UTF-8 encoding.
  64. *
  65. * @param string $string
  66. * @return string
  67. * @since Method available since Release 3.2.19
  68. */
  69. public static function convertToUtf8($string)
  70. {
  71. if (!self::isUtf8($string)) {
  72. if (function_exists('mb_convert_encoding')) {
  73. $string = mb_convert_encoding($string, 'UTF-8');
  74. } else {
  75. $string = utf8_encode($string);
  76. }
  77. }
  78. return $string;
  79. }
  80. /**
  81. * Checks a string for UTF-8 encoding.
  82. *
  83. * @param string $string
  84. * @return boolean
  85. * @since Method available since Release 3.3.0
  86. */
  87. public static function isUtf8($string)
  88. {
  89. $length = strlen($string);
  90. for ($i = 0; $i < $length; $i++) {
  91. if (ord($string[$i]) < 0x80) $n = 0;
  92. elseif ((ord($string[$i]) & 0xE0) == 0xC0) $n = 1;
  93. elseif ((ord($string[$i]) & 0xF0) == 0xE0) $n = 2;
  94. elseif ((ord($string[$i]) & 0xF0) == 0xF0) $n = 3;
  95. else return FALSE;
  96. for ($j = 0; $j < $n; $j++) {
  97. if ((++$i == $length) || ((ord($string[$i]) & 0xC0) != 0x80)) return FALSE;
  98. }
  99. }
  100. return TRUE;
  101. }
  102. /**
  103. * Loads an XML (or HTML) file into a DOMDocument object.
  104. *
  105. * @param string $filename
  106. * @param boolean $isHtml
  107. * @return DOMDocument
  108. * @since Method available since Release 3.3.0
  109. */
  110. public static function loadFile($filename, $isHtml = FALSE)
  111. {
  112. $reporting = error_reporting(0);
  113. $contents = file_get_contents($filename);
  114. error_reporting($reporting);
  115. if ($contents === FALSE) {
  116. throw new RuntimeException(
  117. sprintf(
  118. 'Could not read "%s".',
  119. $filename
  120. )
  121. );
  122. }
  123. return self::load($contents, $isHtml, $filename);
  124. }
  125. /**
  126. * Load an $actual document into a DOMDocument. This is called
  127. * from the selector assertions.
  128. *
  129. * If $actual is already a DOMDocument, it is returned with
  130. * no changes. Otherwise, $actual is loaded into a new DOMDocument
  131. * as either HTML or XML, depending on the value of $isHtml.
  132. *
  133. * Note: prior to PHPUnit 3.3.0, this method loaded a file and
  134. * not a string as it currently does. To load a file into a
  135. * DOMDocument, use loadFile() instead.
  136. *
  137. * @param string|DOMDocument $actual
  138. * @param boolean $isHtml
  139. * @param string $filename
  140. * @return DOMDocument
  141. * @since Method available since Release 3.3.0
  142. * @author Mike Naberezny <mike@maintainable.com>
  143. * @author Derek DeVries <derek@maintainable.com>
  144. */
  145. public static function load($actual, $isHtml = FALSE, $filename = '')
  146. {
  147. if ($actual instanceof DOMDocument) {
  148. return $actual;
  149. }
  150. $internal = libxml_use_internal_errors(TRUE);
  151. $reporting = error_reporting(0);
  152. $dom = new DOMDocument;
  153. if ($isHtml) {
  154. $loaded = $dom->loadHTML($actual);
  155. } else {
  156. $loaded = $dom->loadXML($actual);
  157. }
  158. libxml_use_internal_errors($internal);
  159. error_reporting($reporting);
  160. if ($loaded === FALSE) {
  161. $message = '';
  162. foreach (libxml_get_errors() as $error) {
  163. $message .= $error->message;
  164. }
  165. if ($filename != '') {
  166. throw new RuntimeException(
  167. sprintf(
  168. 'Could not load "%s".%s',
  169. $filename,
  170. $message != '' ? "\n" . $message : ''
  171. )
  172. );
  173. } else {
  174. throw new RuntimeException($message);
  175. }
  176. }
  177. return $dom;
  178. }
  179. /**
  180. *
  181. *
  182. * @param DOMNode $node
  183. * @since Method available since Release 3.3.0
  184. * @author Mattis Stordalen Flister <mattis@xait.no>
  185. */
  186. public static function removeCharacterDataNodes(DOMNode $node)
  187. {
  188. if ($node->hasChildNodes()) {
  189. for ($i = $node->childNodes->length - 1; $i >= 0; $i--) {
  190. if (($child = $node->childNodes->item($i)) instanceof DOMCharacterData) {
  191. $node->removeChild($child);
  192. }
  193. }
  194. }
  195. }
  196. /**
  197. * Validate list of keys in the associative array.
  198. *
  199. * @param array $hash
  200. * @param array $validKeys
  201. * @return array
  202. * @throws InvalidArgumentException
  203. * @since Method available since Release 3.3.0
  204. * @author Mike Naberezny <mike@maintainable.com>
  205. * @author Derek DeVries <derek@maintainable.com>
  206. */
  207. public static function assertValidKeys(array $hash, array $validKeys)
  208. {
  209. $valids = array();
  210. // Normalize validation keys so that we can use both indexed and
  211. // associative arrays.
  212. foreach ($validKeys as $key => $val) {
  213. is_int($key) ? $valids[$val] = NULL : $valids[$key] = $val;
  214. }
  215. $validKeys = array_keys($valids);
  216. // Check for invalid keys.
  217. foreach ($hash as $key => $value) {
  218. if (!in_array($key, $validKeys)) {
  219. $unknown[] = $key;
  220. }
  221. }
  222. if (!empty($unknown)) {
  223. throw new InvalidArgumentException(
  224. 'Unknown key(s): ' . implode(', ', $unknown)
  225. );
  226. }
  227. // Add default values for any valid keys that are empty.
  228. foreach ($valids as $key => $value) {
  229. if (!isset($hash[$key])) {
  230. $hash[$key] = $value;
  231. }
  232. }
  233. return $hash;
  234. }
  235. /**
  236. * Parse a CSS selector into an associative array suitable for
  237. * use with findNodes().
  238. *
  239. * @param string $selector
  240. * @param mixed $content
  241. * @return array
  242. * @since Method available since Release 3.3.0
  243. * @author Mike Naberezny <mike@maintainable.com>
  244. * @author Derek DeVries <derek@maintainable.com>
  245. */
  246. public static function convertSelectToTag($selector, $content = TRUE)
  247. {
  248. $selector = trim(preg_replace("/\s+/", " ", $selector));
  249. // substitute spaces within attribute value
  250. while (preg_match('/\[[^\]]+"[^"]+\s[^"]+"\]/', $selector)) {
  251. $selector = preg_replace('/(\[[^\]]+"[^"]+)\s([^"]+"\])/', "$1__SPACE__$2", $selector);
  252. }
  253. $elements = strstr($selector, ' ') ? explode(' ', $selector) : array($selector);
  254. $previousTag = array();
  255. foreach (array_reverse($elements) as $element) {
  256. $element = str_replace('__SPACE__', ' ', $element);
  257. // child selector
  258. if ($element == '>') {
  259. $previousTag = array('child' => $previousTag['descendant']);
  260. continue;
  261. }
  262. $tag = array();
  263. // match element tag
  264. preg_match("/^([^\.#\[]*)/", $element, $eltMatches);
  265. if (!empty($eltMatches[1])) {
  266. $tag['tag'] = $eltMatches[1];
  267. }
  268. // match attributes (\[[^\]]*\]*), ids (#[^\.#\[]*), and classes (\.[^\.#\[]*))
  269. preg_match_all("/(\[[^\]]*\]*|#[^\.#\[]*|\.[^\.#\[]*)/", $element, $matches);
  270. if (!empty($matches[1])) {
  271. $classes = array();
  272. $attrs = array();
  273. foreach ($matches[1] as $match) {
  274. // id matched
  275. if (substr($match, 0, 1) == '#') {
  276. $tag['id'] = substr($match, 1);
  277. }
  278. // class matched
  279. else if (substr($match, 0, 1) == '.') {
  280. $classes[] = substr($match, 1);
  281. }
  282. // attribute matched
  283. else if (substr($match, 0, 1) == '[' && substr($match, -1, 1) == ']') {
  284. $attribute = substr($match, 1, strlen($match) - 2);
  285. $attribute = str_replace('"', '', $attribute);
  286. // match single word
  287. if (strstr($attribute, '~=')) {
  288. list($key, $value) = explode('~=', $attribute);
  289. $value = "regexp:/.*\b$value\b.*/";
  290. }
  291. // match substring
  292. else if (strstr($attribute, '*=')) {
  293. list($key, $value) = explode('*=', $attribute);
  294. $value = "regexp:/.*$value.*/";
  295. }
  296. // exact match
  297. else {
  298. list($key, $value) = explode('=', $attribute);
  299. }
  300. $attrs[$key] = $value;
  301. }
  302. }
  303. if ($classes) {
  304. $tag['class'] = join(' ', $classes);
  305. }
  306. if ($attrs) {
  307. $tag['attributes'] = $attrs;
  308. }
  309. }
  310. // tag content
  311. if (is_string($content)) {
  312. $tag['content'] = $content;
  313. }
  314. // determine previous child/descendants
  315. if (!empty($previousTag['descendant'])) {
  316. $tag['descendant'] = $previousTag['descendant'];
  317. }
  318. else if (!empty($previousTag['child'])) {
  319. $tag['child'] = $previousTag['child'];
  320. }
  321. $previousTag = array('descendant' => $tag);
  322. }
  323. return $tag;
  324. }
  325. /**
  326. * Parse an $actual document and return an array of DOMNodes
  327. * matching the CSS $selector. If an error occurs, it will
  328. * return FALSE.
  329. *
  330. * To only return nodes containing a certain content, give
  331. * the $content to match as a string. Otherwise, setting
  332. * $content to TRUE will return all nodes matching $selector.
  333. *
  334. * The $actual document may be a DOMDocument or a string
  335. * containing XML or HTML, identified by $isHtml.
  336. *
  337. * @param array $selector
  338. * @param string $content
  339. * @param mixed $actual
  340. * @param boolean $isHtml
  341. * @return false|array
  342. * @since Method available since Release 3.3.0
  343. * @author Mike Naberezny <mike@maintainable.com>
  344. * @author Derek DeVries <derek@maintainable.com>
  345. */
  346. public static function cssSelect($selector, $content, $actual, $isHtml = TRUE)
  347. {
  348. $matcher = self::convertSelectToTag($selector, $content);
  349. $dom = self::load($actual, $isHtml);
  350. $tags = self::findNodes($dom, $matcher);
  351. return $tags;
  352. }
  353. /**
  354. * Parse out the options from the tag using DOM object tree.
  355. *
  356. * @param DOMDocument $dom
  357. * @param array $options
  358. * @return array
  359. * @since Method available since Release 3.3.0
  360. * @author Mike Naberezny <mike@maintainable.com>
  361. * @author Derek DeVries <derek@maintainable.com>
  362. */
  363. public static function findNodes(DOMDocument $dom, array $options)
  364. {
  365. $valid = array(
  366. 'id', 'class', 'tag', 'content', 'attributes', 'parent',
  367. 'child', 'ancestor', 'descendant', 'children'
  368. );
  369. $filtered = array();
  370. $options = self::assertValidKeys($options, $valid);
  371. // find the element by id
  372. if ($options['id']) {
  373. $options['attributes']['id'] = $options['id'];
  374. }
  375. if ($options['class']) {
  376. $options['attributes']['class'] = $options['class'];
  377. }
  378. // find the element by a tag type
  379. if ($options['tag']) {
  380. $elements = $dom->getElementsByTagName($options['tag']);
  381. foreach ($elements as $element) {
  382. $nodes[] = $element;
  383. }
  384. if (empty($nodes)) {
  385. return FALSE;
  386. }
  387. // no tag selected, get them all
  388. } else {
  389. $tags = array(
  390. 'a', 'abbr', 'acronym', 'address', 'area', 'b', 'base', 'bdo',
  391. 'big', 'blockquote', 'body', 'br', 'button', 'caption', 'cite',
  392. 'code', 'col', 'colgroup', 'dd', 'del', 'div', 'dfn', 'dl',
  393. 'dt', 'em', 'fieldset', 'form', 'frame', 'frameset', 'h1', 'h2',
  394. 'h3', 'h4', 'h5', 'h6', 'head', 'hr', 'html', 'i', 'iframe',
  395. 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'link',
  396. 'map', 'meta', 'noframes', 'noscript', 'object', 'ol', 'optgroup',
  397. 'option', 'p', 'param', 'pre', 'q', 'samp', 'script', 'select',
  398. 'small', 'span', 'strong', 'style', 'sub', 'sup', 'table',
  399. 'tbody', 'td', 'textarea', 'tfoot', 'th', 'thead', 'title',
  400. 'tr', 'tt', 'ul', 'var'
  401. );
  402. foreach ($tags as $tag) {
  403. $elements = $dom->getElementsByTagName($tag);
  404. foreach ($elements as $element) {
  405. $nodes[] = $element;
  406. }
  407. }
  408. if (empty($nodes)) {
  409. return FALSE;
  410. }
  411. }
  412. // filter by attributes
  413. if ($options['attributes']) {
  414. foreach ($nodes as $node) {
  415. $invalid = FALSE;
  416. foreach ($options['attributes'] as $name => $value) {
  417. // match by regexp if like "regexp:/foo/i"
  418. if (preg_match('/^regexp\s*:\s*(.*)/i', $value, $matches)) {
  419. if (!preg_match($matches[1], $node->getAttribute($name))) {
  420. $invalid = TRUE;
  421. }
  422. }
  423. // class can match only a part
  424. else if ($name == 'class') {
  425. // split to individual classes
  426. $findClasses = explode(' ', preg_replace("/\s+/", " ", $value));
  427. $allClasses = explode(' ', preg_replace("/\s+/", " ", $node->getAttribute($name)));
  428. // make sure each class given is in the actual node
  429. foreach ($findClasses as $findClass) {
  430. if (!in_array($findClass, $allClasses)) {
  431. $invalid = TRUE;
  432. }
  433. }
  434. }
  435. // match by exact string
  436. else {
  437. if ($node->getAttribute($name) != $value) {
  438. $invalid = TRUE;
  439. }
  440. }
  441. }
  442. // if every attribute given matched
  443. if (!$invalid) {
  444. $filtered[] = $node;
  445. }
  446. }
  447. $nodes = $filtered;
  448. $filtered = array();
  449. if (empty($nodes)) {
  450. return FALSE;
  451. }
  452. }
  453. // filter by content
  454. if ($options['content'] !== NULL) {
  455. foreach ($nodes as $node) {
  456. $invalid = FALSE;
  457. // match by regexp if like "regexp:/foo/i"
  458. if (preg_match('/^regexp\s*:\s*(.*)/i', $options['content'], $matches)) {
  459. if (!preg_match($matches[1], self::getNodeText($node))) {
  460. $invalid = TRUE;
  461. }
  462. }
  463. // match by exact string
  464. else if (strstr(self::getNodeText($node), $options['content']) === FALSE) {
  465. $invalid = TRUE;
  466. }
  467. if (!$invalid) {
  468. $filtered[] = $node;
  469. }
  470. }
  471. $nodes = $filtered;
  472. $filtered = array();
  473. if (empty($nodes)) {
  474. return FALSE;
  475. }
  476. }
  477. // filter by parent node
  478. if ($options['parent']) {
  479. $parentNodes = self::findNodes($dom, $options['parent']);
  480. $parentNode = isset($parentNodes[0]) ? $parentNodes[0] : NULL;
  481. foreach ($nodes as $node) {
  482. if ($parentNode !== $node->parentNode) {
  483. break;
  484. }
  485. $filtered[] = $node;
  486. }
  487. $nodes = $filtered;
  488. $filtered = array();
  489. if (empty($nodes)) {
  490. return FALSE;
  491. }
  492. }
  493. // filter by child node
  494. if ($options['child']) {
  495. $childNodes = self::findNodes($dom, $options['child']);
  496. $childNodes = !empty($childNodes) ? $childNodes : array();
  497. foreach ($nodes as $node) {
  498. foreach ($node->childNodes as $child) {
  499. foreach ($childNodes as $childNode) {
  500. if ($childNode === $child) {
  501. $filtered[] = $node;
  502. }
  503. }
  504. }
  505. }
  506. $nodes = $filtered;
  507. $filtered = array();
  508. if (empty($nodes)) {
  509. return FALSE;
  510. }
  511. }
  512. // filter by ancestor
  513. if ($options['ancestor']) {
  514. $ancestorNodes = self::findNodes($dom, $options['ancestor']);
  515. $ancestorNode = isset($ancestorNodes[0]) ? $ancestorNodes[0] : NULL;
  516. foreach ($nodes as $node) {
  517. $parent = $node->parentNode;
  518. while ($parent->nodeType != XML_HTML_DOCUMENT_NODE) {
  519. if ($parent === $ancestorNode) {
  520. $filtered[] = $node;
  521. }
  522. $parent = $parent->parentNode;
  523. }
  524. }
  525. $nodes = $filtered;
  526. $filtered = array();
  527. if (empty($nodes)) {
  528. return FALSE;
  529. }
  530. }
  531. // filter by descendant
  532. if ($options['descendant']) {
  533. $descendantNodes = self::findNodes($dom, $options['descendant']);
  534. $descendantNodes = !empty($descendantNodes) ? $descendantNodes : array();
  535. foreach ($nodes as $node) {
  536. foreach (self::getDescendants($node) as $descendant) {
  537. foreach ($descendantNodes as $descendantNode) {
  538. if ($descendantNode === $descendant) {
  539. $filtered[] = $node;
  540. }
  541. }
  542. }
  543. }
  544. $nodes = $filtered;
  545. $filtered = array();
  546. if (empty($nodes)) {
  547. return FALSE;
  548. }
  549. }
  550. // filter by children
  551. if ($options['children']) {
  552. $validChild = array('count', 'greater_than', 'less_than', 'only');
  553. $childOptions = self::assertValidKeys($options['children'], $validChild);
  554. foreach ($nodes as $node) {
  555. $childNodes = $node->childNodes;
  556. foreach ($childNodes as $childNode) {
  557. if ($childNode->nodeType !== XML_CDATA_SECTION_NODE &&
  558. $childNode->nodeType !== XML_TEXT_NODE) {
  559. $children[] = $childNode;
  560. }
  561. }
  562. // we must have children to pass this filter
  563. if (!empty($children)) {
  564. // exact count of children
  565. if ($childOptions['count'] !== NULL) {
  566. if (count($children) !== $childOptions['count']) {
  567. break;
  568. }
  569. }
  570. // range count of children
  571. else if ($childOptions['less_than'] !== NULL &&
  572. $childOptions['greater_than'] !== NULL) {
  573. if (count($children) >= $childOptions['less_than'] ||
  574. count($children) <= $childOptions['greater_than']) {
  575. break;
  576. }
  577. }
  578. // less than a given count
  579. else if ($childOptions['less_than'] !== NULL) {
  580. if (count($children) >= $childOptions['less_than']) {
  581. break;
  582. }
  583. }
  584. // more than a given count
  585. else if ($childOptions['greater_than'] !== NULL) {
  586. if (count($children) <= $childOptions['greater_than']) {
  587. break;
  588. }
  589. }
  590. // match each child against a specific tag
  591. if ($childOptions['only']) {
  592. $onlyNodes = self::findNodes($dom, $childOptions['only']);
  593. // try to match each child to one of the 'only' nodes
  594. foreach ($children as $child) {
  595. $matched = FALSE;
  596. foreach ($onlyNodes as $onlyNode) {
  597. if ($onlyNode === $child) {
  598. $matched = TRUE;
  599. }
  600. }
  601. if (!$matched) {
  602. break(2);
  603. }
  604. }
  605. }
  606. $filtered[] = $node;
  607. }
  608. }
  609. $nodes = $filtered;
  610. $filtered = array();
  611. if (empty($nodes)) {
  612. return;
  613. }
  614. }
  615. // return the first node that matches all criteria
  616. return !empty($nodes) ? $nodes : array();
  617. }
  618. /**
  619. * Recursively get flat array of all descendants of this node.
  620. *
  621. * @param DOMNode $node
  622. * @return array
  623. * @since Method available since Release 3.3.0
  624. * @author Mike Naberezny <mike@maintainable.com>
  625. * @author Derek DeVries <derek@maintainable.com>
  626. */
  627. protected static function getDescendants(DOMNode $node)
  628. {
  629. $allChildren = array();
  630. $childNodes = $node->childNodes ? $node->childNodes : array();
  631. foreach ($childNodes as $child) {
  632. if ($child->nodeType === XML_CDATA_SECTION_NODE ||
  633. $child->nodeType === XML_TEXT_NODE) {
  634. continue;
  635. }
  636. $children = self::getDescendants($child);
  637. $allChildren = array_merge($allChildren, $children, array($child));
  638. }
  639. return isset($allChildren) ? $allChildren : array();
  640. }
  641. /**
  642. * Get the text value of this node's child text node.
  643. *
  644. * @param DOMNode $node
  645. * @return string
  646. * @since Method available since Release 3.3.0
  647. * @author Mike Naberezny <mike@maintainable.com>
  648. * @author Derek DeVries <derek@maintainable.com>
  649. */
  650. protected static function getNodeText(DOMNode $node)
  651. {
  652. $childNodes = $node->childNodes instanceof DOMNodeList ? $node->childNodes : array();
  653. $text = '';
  654. foreach ($childNodes as $child) {
  655. if ($child->nodeType === XML_TEXT_NODE) {
  656. $text .= trim($child->data).' ';
  657. } else {
  658. $text .= self::getNodeText($child);
  659. }
  660. }
  661. return str_replace(' ', ' ', $text);
  662. }
  663. }
  664. ?>