PageRenderTime 62ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 1ms

/modules/System/includes/XPath.class.php

https://bitbucket.org/thomashii/vtigercrm-5.4-for-postgresql
PHP | 6355 lines | 3144 code | 532 blank | 2679 comment | 670 complexity | e34a8630fd3d4a3a20450f2d4e8e5fe2 MD5 | raw file
Possible License(s): LGPL-2.1, GPL-2.0

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /**
  3. * Php.XPath
  4. *
  5. * +======================================================================================================+
  6. * | A php class for searching an XML document using XPath, and making modifications using a DOM
  7. * | style API. Does not require the DOM XML PHP library.
  8. * |
  9. * +======================================================================================================+
  10. * | What Is XPath:
  11. * | --------------
  12. * | - "What SQL is for a relational database, XPath is for an XML document." -- Sam Blum
  13. * | - "The primary purpose of XPath is to address parts of an XML document. In support of this
  14. * | primary purpose, it also provides basic facilities for manipulting it." -- W3C
  15. * |
  16. * | XPath in action and a very nice intro is under:
  17. * | http://www.zvon.org/xxl/XPathTutorial/General/examples.html
  18. * | Specs Can be found under:
  19. * | http://www.w3.org/TR/xpath W3C XPath Recommendation
  20. * | http://www.w3.org/TR/xpath20 W3C XPath Recommendation
  21. * |
  22. * | NOTE: Most of the XPath-spec has been realized, but not all. Usually this should not be
  23. * | problem as the missing part is either rarely used or it's simpler to do with PHP itself.
  24. * +------------------------------------------------------------------------------------------------------+
  25. * | Requires PHP version 4.0.5 and up
  26. * +------------------------------------------------------------------------------------------------------+
  27. * | Main Active Authors:
  28. * | --------------------
  29. * | Nigel Swinson <nigelswinson@users.sourceforge.net>
  30. * | Started around 2001-07, saved phpxml from near death and renamed to Php.XPath
  31. * | Restructured XPath code to stay in line with XPath spec.
  32. * | Sam Blum <bs_php@infeer.com>
  33. * | Started around 2001-09 1st major restruct (V2.0) and testbench initiator.
  34. * | 2nd (V3.0) major rewrite in 2002-02
  35. * | Daniel Allen <bigredlinux@yahoo.com>
  36. * | Started around 2001-10 working to make Php.XPath adhere to specs
  37. * | Main Former Author: Michael P. Mehl <mpm@phpxml.org>
  38. * | Inital creator of V 1.0. Stoped activities around 2001-03
  39. * +------------------------------------------------------------------------------------------------------+
  40. * | Code Structure:
  41. * | --------------_
  42. * | The class is split into 3 main objects. To keep usability easy all 3
  43. * | objects are in this file (but may be split in 3 file in future).
  44. * | +-------------+
  45. * | | XPathBase | XPathBase holds general and debugging functions.
  46. * | +------+------+
  47. * | v
  48. * | +-------------+ XPathEngine is the implementation of the W3C XPath spec. It contains the
  49. * | | XPathEngine | XML-import (parser), -export and can handle xPathQueries. It's a fully
  50. * | +------+------+ functional class but has no functions to modify the XML-document (see following).
  51. * | v
  52. * | +-------------+
  53. * | | XPath | XPath extends the functionality with actions to modify the XML-document.
  54. * | +-------------+ We tryed to implement a DOM - like interface.
  55. * +------------------------------------------------------------------------------------------------------+
  56. * | Usage:
  57. * | ------
  58. * | Scroll to the end of this php file and you will find a short sample code to get you started
  59. * +------------------------------------------------------------------------------------------------------+
  60. * | Glossary:
  61. * | ---------
  62. * | To understand how to use the functions and to pass the right parameters, read following:
  63. * |
  64. * | Document: (full node tree, XML-tree)
  65. * | After a XML-source has been imported and parsed, it's stored as a tree of nodes sometimes
  66. * | refered to as 'document'.
  67. * |
  68. * | AbsoluteXPath: (xPath, xPathSet)
  69. * | A absolute XPath is a string. It 'points' to *one* node in the XML-document. We use the
  70. * | term 'absolute' to emphasise that it is not an xPath-query (see xPathQuery). A valid xPath
  71. * | has the form like '/AAA[1]/BBB[2]/CCC[1]'. Usually functions that require a node (see Node)
  72. * | will also accept an abs. XPath.
  73. * |
  74. * | Node: (node, nodeSet, node-tree)
  75. * | Some funtions require or return a node (or a whole node-tree). Nodes are only used with the
  76. * | XPath-interface and have an internal structure. Every node in a XML document has a unique
  77. * | corresponding abs. xPath. That's why public functions that accept a node, will usually also
  78. * | accept a abs. xPath (a string) 'pointing' to an existing node (see absolutXPath).
  79. * |
  80. * | XPathQuery: (xquery, query)
  81. * | A xPath-query is a string that is matched against the XML-document. The result of the match
  82. * | is a xPathSet (vector of xPath's). It's always possible to pass a single absoluteXPath
  83. * | instead of a xPath-query. A valid xPathQuery could look like this:
  84. * | '//XXX/*[contains(., "foo")]/..' (See the link in 'What Is XPath' to learn more).
  85. * |
  86. * |
  87. * +------------------------------------------------------------------------------------------------------+
  88. * | Internals:
  89. * | ----------
  90. * | - The Node Tree
  91. * | -------------
  92. * | A central role of the package is how the XML-data is stored. The whole data is in a node-tree.
  93. * | A node can be seen as the equvalent to a tag in the XML soure with some extra info.
  94. * | For instance the following XML
  95. * | <AAA foo="x">***<BBB/><CCC/>**<BBB/>*</AAA>
  96. * | Would produce folowing node-tree:
  97. * | 'super-root' <-- $nodeRoot (Very handy)
  98. * | |
  99. * | 'depth' 0 AAA[1] <-- top node. The 'textParts' of this node would be
  100. * | / | \ 'textParts' => array('***','','**','*')
  101. * | 'depth' 1 BBB[1] CCC[1] BBB[2] (NOTE: Is always size of child nodes+1)
  102. * | - The Node
  103. * | --------
  104. * | The node itself is an structure desiged mainly to be used in connection with the interface of PHP.XPath.
  105. * | That means it's possible for functions to return a sub-node-tree that can be used as input of an other
  106. * | PHP.XPath function.
  107. * |
  108. * | The main structure of a node is:
  109. * | $node = array(
  110. * | 'name' => '', # The tag name. E.g. In <FOO bar="aaa"/> it would be 'FOO'
  111. * | 'attributes' => array(), # The attributes of the tag E.g. In <FOO bar="aaa"/> it would be array('bar'=>'aaa')
  112. * | 'textParts' => array(), # Array of text parts surrounding the children E.g. <FOO>aa<A>bb<B/>cc</A>dd</FOO> -> array('aa','bb','cc','dd')
  113. * | 'childNodes' => array(), # Array of refences (pointers) to child nodes.
  114. * |
  115. * | For optimisation reasions some additional data is stored in the node too:
  116. * | 'parentNode' => NULL # Reference (pointer) to the parent node (or NULL if it's 'super root')
  117. * | 'depth' => 0, # The tag depth (or tree level) starting with the root tag at 0.
  118. * | 'pos' => 0, # Is the zero-based position this node has in the parent's 'childNodes'-list.
  119. * | 'contextPos' => 1, # Is the one-based position this node has by counting the siblings tags (tags with same name)
  120. * | 'xpath' => '' # Is the abs. XPath to this node.
  121. * | 'generated_id'=> '' # The id returned for this node by generate-id() (attribute and text nodes not supported)
  122. * |
  123. * | - The NodeIndex
  124. * | -------------
  125. * | Every node in the tree has an absolute XPath. E.g '/AAA[1]/BBB[2]' the $nodeIndex is a hash array
  126. * | to all the nodes in the node-tree. The key used is the absolute XPath (a string).
  127. * |
  128. * +------------------------------------------------------------------------------------------------------+
  129. * | License:
  130. * | --------
  131. * | The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License");
  132. * | you may not use this file except in compliance with the License. You may obtain a copy of the
  133. * | License at http://www.mozilla.org/MPL/
  134. * |
  135. * | Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY
  136. * | OF ANY KIND, either express or implied. See the License for the specific language governing
  137. * | rights and limitations under the License.
  138. * |
  139. * | The Original Code is <phpXML/>.
  140. * |
  141. * | The Initial Developer of the Original Code is Michael P. Mehl. Portions created by Michael
  142. * | P. Mehl are Copyright (C) 2001 Michael P. Mehl. All Rights Reserved.
  143. * |
  144. * | Contributor(s): N.Swinson / S.Blum / D.Allen
  145. * |
  146. * | Alternatively, the contents of this file may be used under the terms of either of the GNU
  147. * | General Public License Version 2 or later (the "GPL"), or the GNU Lesser General Public
  148. * | License Version 2.1 or later (the "LGPL"), in which case the provisions of the GPL or the
  149. * | LGPL License are applicable instead of those above. If you wish to allow use of your version
  150. * | of this file only under the terms of the GPL or the LGPL License and not to allow others to
  151. * | use your version of this file under the MPL, indicate your decision by deleting the
  152. * | provisions above and replace them with the notice and other provisions required by the
  153. * | GPL or the LGPL License. If you do not delete the provisions above, a recipient may use
  154. * | your version of this file under either the MPL, the GPL or the LGPL License.
  155. * |
  156. * +======================================================================================================+
  157. *
  158. * @author S.Blum / N.Swinson / D.Allen / (P.Mehl)
  159. * @link http://sourceforge.net/projects/phpxpath/
  160. * @version 3.5
  161. * @CVS $Id: XPath.class.php,v 1.9 2005/11/16 17:26:05 bigmichi1 Exp $
  162. */
  163. // Include guard, protects file being included twice
  164. $ConstantName = 'INCLUDED_'.strtoupper(__FILE__);
  165. if (defined($ConstantName)) return;
  166. define($ConstantName,1, TRUE);
  167. /************************************************************************************************
  168. * ===============================================================================================
  169. * X P a t h B a s e - Class
  170. * ===============================================================================================
  171. ************************************************************************************************/
  172. class XPathBase {
  173. var $_lastError;
  174. // As debugging of the xml parse is spread across several functions, we need to make this a member.
  175. var $bDebugXmlParse = FALSE;
  176. // do we want to do profiling?
  177. var $bClassProfiling = FALSE;
  178. // Used to help navigate through the begin/end debug calls
  179. var $iDebugNextLinkNumber = 1;
  180. var $aDebugOpenLinks = array();
  181. var $aDebugFunctions = array(
  182. //'_evaluatePrimaryExpr',
  183. //'_evaluateExpr',
  184. //'_evaluateStep',
  185. //'_checkPredicates',
  186. //'_evaluateFunction',
  187. //'_evaluateOperator',
  188. //'_evaluatePathExpr',
  189. );
  190. /**
  191. * Constructor
  192. */
  193. function XPathBase() {
  194. # $this->bDebugXmlParse = TRUE;
  195. $this->properties['verboseLevel'] = 1; // 0=silent, 1 and above produce verbose output (an echo to screen).
  196. if (!isSet($_ENV)) { // Note: $_ENV introduced in 4.1.0. In earlier versions, use $HTTP_ENV_VARS.
  197. $_ENV = $GLOBALS['HTTP_ENV_VARS'];
  198. }
  199. // Windows 95/98 do not support file locking. Detecting OS (Operation System) and setting the
  200. // properties['OS_supports_flock'] to FALSE if win 95/98 is detected.
  201. // This will surpress the file locking error reported from win 98 users when exportToFile() is called.
  202. // May have to add more OS's to the list in future (Macs?).
  203. // ### Note that it's only the FAT and NFS file systems that are really a problem. NTFS and
  204. // the latest php libs do support flock()
  205. $_ENV['OS'] = isSet($_ENV['OS']) ? $_ENV['OS'] : 'Unknown OS';
  206. switch ($_ENV['OS']) {
  207. case 'Windows_95':
  208. case 'Windows_98':
  209. case 'Unknown OS':
  210. // should catch Mac OS X compatible environment
  211. if (!empty($_SERVER['SERVER_SOFTWARE'])
  212. && preg_match('/Darwin/',$_SERVER['SERVER_SOFTWARE'])) {
  213. // fall-through
  214. } else {
  215. $this->properties['OS_supports_flock'] = FALSE;
  216. break;
  217. }
  218. default:
  219. $this->properties['OS_supports_flock'] = TRUE;
  220. }
  221. }
  222. /**
  223. * Resets the object so it's able to take a new xml sting/file
  224. *
  225. * Constructing objects is slow. If you can, reuse ones that you have used already
  226. * by using this reset() function.
  227. */
  228. function reset() {
  229. $this->_lastError = '';
  230. }
  231. //-----------------------------------------------------------------------------------------
  232. // XPathBase ------ Helpers ------
  233. //-----------------------------------------------------------------------------------------
  234. /**
  235. * This method checks the right amount and match of brackets
  236. *
  237. * @param $term (string) String in which is checked.
  238. * @return (bool) TRUE: OK / FALSE: KO
  239. */
  240. function _bracketsCheck($term) {
  241. $leng = strlen($term);
  242. $brackets = 0;
  243. $bracketMisscount = $bracketMissmatsh = FALSE;
  244. $stack = array();
  245. for ($i=0; $i<$leng; $i++) {
  246. switch ($term[$i]) {
  247. case '(' :
  248. case '[' :
  249. $stack[$brackets] = $term[$i];
  250. $brackets++;
  251. break;
  252. case ')':
  253. $brackets--;
  254. if ($brackets<0) {
  255. $bracketMisscount = TRUE;
  256. break 2;
  257. }
  258. if ($stack[$brackets] != '(') {
  259. $bracketMissmatsh = TRUE;
  260. break 2;
  261. }
  262. break;
  263. case ']' :
  264. $brackets--;
  265. if ($brackets<0) {
  266. $bracketMisscount = TRUE;
  267. break 2;
  268. }
  269. if ($stack[$brackets] != '[') {
  270. $bracketMissmatsh = TRUE;
  271. break 2;
  272. }
  273. break;
  274. }
  275. }
  276. // Check whether we had a valid number of brackets.
  277. if ($brackets != 0) $bracketMisscount = TRUE;
  278. if ($bracketMisscount || $bracketMissmatsh) {
  279. return FALSE;
  280. }
  281. return TRUE;
  282. }
  283. /**
  284. * Looks for a string within another string -- BUT the search-string must be located *outside* of any brackets.
  285. *
  286. * This method looks for a string within another string. Brackets in the
  287. * string the method is looking through will be respected, which means that
  288. * only if the string the method is looking for is located outside of
  289. * brackets, the search will be successful.
  290. *
  291. * @param $term (string) String in which the search shall take place.
  292. * @param $expression (string) String that should be searched.
  293. * @return (int) This method returns -1 if no string was found,
  294. * otherwise the offset at which the string was found.
  295. */
  296. function _searchString($term, $expression) {
  297. $bracketCounter = 0; // Record where we are in the brackets.
  298. $leng = strlen($term);
  299. $exprLeng = strlen($expression);
  300. for ($i=0; $i<$leng; $i++) {
  301. $char = $term[$i];
  302. if ($char=='(' || $char=='[') {
  303. $bracketCounter++;
  304. continue;
  305. }
  306. elseif ($char==')' || $char==']') {
  307. $bracketCounter--;
  308. }
  309. if ($bracketCounter == 0) {
  310. // Check whether we can find the expression at this index.
  311. if (substr($term, $i, $exprLeng) == $expression) return $i;
  312. }
  313. }
  314. // Nothing was found.
  315. return (-1);
  316. }
  317. /**
  318. * Split a string by a searator-string -- BUT the separator-string must be located *outside* of any brackets.
  319. *
  320. * Returns an array of strings, each of which is a substring of string formed
  321. * by splitting it on boundaries formed by the string separator.
  322. *
  323. * @param $separator (string) String that should be searched.
  324. * @param $term (string) String in which the search shall take place.
  325. * @return (array) see above
  326. */
  327. function _bracketExplode($separator, $term) {
  328. // Note that it doesn't make sense for $separator to itself contain (,),[ or ],
  329. // but as this is a private function we should be ok.
  330. $resultArr = array();
  331. $bracketCounter = 0; // Record where we are in the brackets.
  332. do { // BEGIN try block
  333. // Check if any separator is in the term
  334. $sepLeng = strlen($separator);
  335. if (strpos($term, $separator)===FALSE) { // no separator found so end now
  336. $resultArr[] = $term;
  337. break; // try-block
  338. }
  339. // Make a substitute separator out of 'unused chars'.
  340. $substituteSep = str_repeat(chr(2), $sepLeng);
  341. // Now determine the first bracket '(' or '['.
  342. $tmp1 = strpos($term, '(');
  343. $tmp2 = strpos($term, '[');
  344. if ($tmp1===FALSE) {
  345. $startAt = (int)$tmp2;
  346. } elseif ($tmp2===FALSE) {
  347. $startAt = (int)$tmp1;
  348. } else {
  349. $startAt = min($tmp1, $tmp2);
  350. }
  351. // Get prefix string part before the first bracket.
  352. $preStr = substr($term, 0, $startAt);
  353. // Substitute separator in prefix string.
  354. $preStr = str_replace($separator, $substituteSep, $preStr);
  355. // Now get the rest-string (postfix string)
  356. $postStr = substr($term, $startAt);
  357. // Go all the way through the rest-string.
  358. $strLeng = strlen($postStr);
  359. for ($i=0; $i < $strLeng; $i++) {
  360. $char = $postStr[$i];
  361. // Spot (,),[,] and modify our bracket counter. Note there is an
  362. // assumption here that you don't have a string(with[mis)matched]brackets.
  363. // This should be ok as the dodgy string will be detected elsewhere.
  364. if ($char=='(' || $char=='[') {
  365. $bracketCounter++;
  366. continue;
  367. }
  368. elseif ($char==')' || $char==']') {
  369. $bracketCounter--;
  370. }
  371. // If no brackets surround us check for separator
  372. if ($bracketCounter == 0) {
  373. // Check whether we can find the expression starting at this index.
  374. if ((substr($postStr, $i, $sepLeng) == $separator)) {
  375. // Substitute the found separator
  376. for ($j=0; $j<$sepLeng; $j++) {
  377. $postStr[$i+$j] = $substituteSep[$j];
  378. }
  379. }
  380. }
  381. }
  382. // Now explod using the substitute separator as key.
  383. $resultArr = explode($substituteSep, $preStr . $postStr);
  384. } while (FALSE); // End try block
  385. // Return the results that we found. May be a array with 1 entry.
  386. return $resultArr;
  387. }
  388. /**
  389. * Split a string at it's groups, ie bracketed expressions
  390. *
  391. * Returns an array of strings, when concatenated together would produce the original
  392. * string. ie a(b)cde(f)(g) would map to:
  393. * array ('a', '(b)', cde', '(f)', '(g)')
  394. *
  395. * @param $string (string) The string to process
  396. * @param $open (string) The substring for the open of a group
  397. * @param $close (string) The substring for the close of a group
  398. * @return (array) The parsed string, see above
  399. */
  400. function _getEndGroups($string, $open='[', $close=']') {
  401. // Note that it doesn't make sense for $separator to itself contain (,),[ or ],
  402. // but as this is a private function we should be ok.
  403. $resultArr = array();
  404. do { // BEGIN try block
  405. // Check if we have both an open and a close tag
  406. if (empty($open) and empty($close)) { // no separator found so end now
  407. $resultArr[] = $string;
  408. break; // try-block
  409. }
  410. if (empty($string)) {
  411. $resultArr[] = $string;
  412. break; // try-block
  413. }
  414. while (!empty($string)) {
  415. // Now determine the first bracket '(' or '['.
  416. $openPos = strpos($string, $open);
  417. $closePos = strpos($string, $close);
  418. if ($openPos===FALSE || $closePos===FALSE) {
  419. // Oh, no more groups to be found then. Quit
  420. $resultArr[] = $string;
  421. break;
  422. }
  423. // Sanity check
  424. if ($openPos > $closePos) {
  425. // Malformed string, dump the rest and quit.
  426. $resultArr[] = $string;
  427. break;
  428. }
  429. // Get prefix string part before the first bracket.
  430. $preStr = substr($string, 0, $openPos);
  431. // This is the first string that will go in our output
  432. if (!empty($preStr))
  433. $resultArr[] = $preStr;
  434. // Skip over what we've proceed, including the open char
  435. $string = substr($string, $openPos + 1 - strlen($string));
  436. // Find the next open char and adjust our close char
  437. //echo "close: $closePos\nopen: $openPos\n\n";
  438. $closePos -= $openPos + 1;
  439. $openPos = strpos($string, $open);
  440. //echo "close: $closePos\nopen: $openPos\n\n";
  441. // While we have found nesting...
  442. while ($openPos && $closePos && ($closePos > $openPos)) {
  443. // Find another close pos after the one we are looking at
  444. $closePos = strpos($string, $close, $closePos + 1);
  445. // And skip our open
  446. $openPos = strpos($string, $open, $openPos + 1);
  447. }
  448. //echo "close: $closePos\nopen: $openPos\n\n";
  449. // If we now have a close pos, then it's the end of the group.
  450. if ($closePos === FALSE) {
  451. // We didn't... so bail dumping what was left
  452. $resultArr[] = $open.$string;
  453. break;
  454. }
  455. // We did, so we can extract the group
  456. $resultArr[] = $open.substr($string, 0, $closePos + 1);
  457. // Skip what we have processed
  458. $string = substr($string, $closePos + 1);
  459. }
  460. } while (FALSE); // End try block
  461. // Return the results that we found. May be a array with 1 entry.
  462. return $resultArr;
  463. }
  464. /**
  465. * Retrieves a substring before a delimiter.
  466. *
  467. * This method retrieves everything from a string before a given delimiter,
  468. * not including the delimiter.
  469. *
  470. * @param $string (string) String, from which the substring should be extracted.
  471. * @param $delimiter (string) String containing the delimiter to use.
  472. * @return (string) Substring from the original string before the delimiter.
  473. * @see _afterstr()
  474. */
  475. function _prestr(&$string, $delimiter, $offset=0) {
  476. // Return the substring.
  477. $offset = ($offset<0) ? 0 : $offset;
  478. $pos = strpos($string, $delimiter, $offset);
  479. if ($pos===FALSE) return $string; else return substr($string, 0, $pos);
  480. }
  481. /**
  482. * Retrieves a substring after a delimiter.
  483. *
  484. * This method retrieves everything from a string after a given delimiter,
  485. * not including the delimiter.
  486. *
  487. * @param $string (string) String, from which the substring should be extracted.
  488. * @param $delimiter (string) String containing the delimiter to use.
  489. * @return (string) Substring from the original string after the delimiter.
  490. * @see _prestr()
  491. */
  492. function _afterstr($string, $delimiter, $offset=0) {
  493. $offset = ($offset<0) ? 0 : $offset;
  494. // Return the substring.
  495. return substr($string, strpos($string, $delimiter, $offset) + strlen($delimiter));
  496. }
  497. //-----------------------------------------------------------------------------------------
  498. // XPathBase ------ Debug Stuff ------
  499. //-----------------------------------------------------------------------------------------
  500. /**
  501. * Alter the verbose (error) level reporting.
  502. *
  503. * Pass an int. >0 to turn on, 0 to turn off. The higher the number, the
  504. * higher the level of verbosity. By default, the class has a verbose level
  505. * of 1.
  506. *
  507. * @param $levelOfVerbosity (int) default is 1 = on
  508. */
  509. function setVerbose($levelOfVerbosity = 1) {
  510. $level = -1;
  511. if ($levelOfVerbosity === TRUE) {
  512. $level = 1;
  513. } elseif ($levelOfVerbosity === FALSE) {
  514. $level = 0;
  515. } elseif (is_numeric($levelOfVerbosity)) {
  516. $level = $levelOfVerbosity;
  517. }
  518. if ($level >= 0) $this->properties['verboseLevel'] = $levelOfVerbosity;
  519. }
  520. /**
  521. * Returns the last occured error message.
  522. *
  523. * @access public
  524. * @return string (may be empty if there was no error at all)
  525. * @see _setLastError(), _lastError
  526. */
  527. function getLastError() {
  528. return $this->_lastError;
  529. }
  530. /**
  531. * Creates a textual error message and sets it.
  532. *
  533. * example: 'XPath error in THIS_FILE_NAME:LINE. Message: YOUR_MESSAGE';
  534. *
  535. * I don't think the message should include any markup because not everyone wants to debug
  536. * into the browser window.
  537. *
  538. * You should call _displayError() rather than _setLastError() if you would like the message,
  539. * dependant on their verbose settings, echoed to the screen.
  540. *
  541. * @param $message (string) a textual error message default is ''
  542. * @param $line (int) the line number where the error occured, use __LINE__
  543. * @see getLastError()
  544. */
  545. function _setLastError($message='', $line='-', $file='-') {
  546. $this->_lastError = 'XPath error in ' . basename($file) . ':' . $line . '. Message: ' . $message;
  547. }
  548. /**
  549. * Displays an error message.
  550. *
  551. * This method displays an error messages depending on the users verbose settings
  552. * and sets the last error message.
  553. *
  554. * If also possibly stops the execution of the script.
  555. * ### Terminate should not be allowed --fab. Should it?? N.S.
  556. *
  557. * @param $message (string) Error message to be displayed.
  558. * @param $lineNumber (int) line number given by __LINE__
  559. * @param $terminate (bool) (default TURE) End the execution of this script.
  560. */
  561. function _displayError($message, $lineNumber='-', $file='-', $terminate=TRUE) {
  562. // Display the error message.
  563. $err = '<b>XPath error in '.basename($file).':'.$lineNumber.'</b> '.$message."<br \>\n";
  564. $this->_setLastError($message, $lineNumber, $file);
  565. if (($this->properties['verboseLevel'] > 0) OR ($terminate)) echo $err;
  566. // End the execution of this script.
  567. if ($terminate) exit;
  568. }
  569. /**
  570. * Displays a diagnostic message
  571. *
  572. * This method displays an error messages
  573. *
  574. * @param $message (string) Error message to be displayed.
  575. * @param $lineNumber (int) line number given by __LINE__
  576. */
  577. function _displayMessage($message, $lineNumber='-', $file='-') {
  578. // Display the error message.
  579. $err = '<b>XPath message from '.basename($file).':'.$lineNumber.'</b> '.$message."<br \>\n";
  580. if ($this->properties['verboseLevel'] > 0) echo $err;
  581. }
  582. /**
  583. * Called to begin the debug run of a function.
  584. *
  585. * This method starts a <DIV><PRE> tag so that the entry to this function
  586. * is clear to the debugging user. Call _closeDebugFunction() at the
  587. * end of the function to create a clean box round the function call.
  588. *
  589. * @author Nigel Swinson <nigelswinson@users.sourceforge.net>
  590. * @author Sam Blum <bs_php@infeer.com>
  591. * @param $functionName (string) the name of the function we are beginning to debug
  592. * @param $bDebugFlag (bool) TRUE if we are to draw a call stack, FALSE otherwise
  593. * @return (array) the output from the microtime() function.
  594. * @see _closeDebugFunction()
  595. */
  596. function _beginDebugFunction($functionName, $bDebugFlag) {
  597. if ($bDebugFlag) {
  598. $fileName = basename(__FILE__);
  599. static $color = array('green','blue','red','lime','fuchsia', 'aqua');
  600. static $colIndex = -1;
  601. $colIndex++;
  602. echo '<div style="clear:both" align="left"> ';
  603. echo '<pre STYLE="border:solid thin '. $color[$colIndex % 6] . '; padding:5">';
  604. echo '<a style="float:right;margin:5px" name="'.$this->iDebugNextLinkNumber.'Open" href="#'.$this->iDebugNextLinkNumber.'Close">Function Close '.$this->iDebugNextLinkNumber.'</a>';
  605. echo "<STRONG>{$fileName} : {$functionName}</STRONG>";
  606. echo '<hr style="clear:both">';
  607. array_push($this->aDebugOpenLinks, $this->iDebugNextLinkNumber);
  608. $this->iDebugNextLinkNumber++;
  609. }
  610. if ($this->bClassProfiling)
  611. $this->_ProfBegin($FunctionName);
  612. return TRUE;
  613. }
  614. /**
  615. * Called to end the debug run of a function.
  616. *
  617. * This method ends a <DIV><PRE> block and reports the time since $aStartTime
  618. * is clear to the debugging user.
  619. *
  620. * @author Nigel Swinson <nigelswinson@users.sourceforge.net>
  621. * @param $functionName (string) the name of the function we are beginning to debug
  622. * @param $return_value (mixed) the return value from the function call that
  623. * we are debugging
  624. * @param $bDebugFlag (bool) TRUE if we are to draw a call stack, FALSE otherwise
  625. */
  626. function _closeDebugFunction($functionName, $returnValue = "", $bDebugFlag) {
  627. if ($bDebugFlag) {
  628. echo "<hr>";
  629. $iOpenLinkNumber = array_pop($this->aDebugOpenLinks);
  630. echo '<a style="float:right" name="'.$iOpenLinkNumber.'Close" href="#'.$iOpenLinkNumber.'Open">Function Open '.$iOpenLinkNumber.'</a>';
  631. if (isSet($returnValue)) {
  632. if (is_array($returnValue))
  633. echo "Return Value: ".print_r($returnValue)."\n";
  634. else if (is_numeric($returnValue))
  635. echo "Return Value: ".(string)$returnValue."\n";
  636. else if (is_bool($returnValue))
  637. echo "Return Value: ".($returnValue ? "TRUE" : "FALSE")."\n";
  638. else
  639. echo "Return Value: \"".htmlspecialchars($returnValue)."\"\n";
  640. }
  641. echo '<br style="clear:both">';
  642. echo " \n</pre></div>";
  643. }
  644. if ($this->bClassProfiling)
  645. $this->_ProfEnd($FunctionName);
  646. return TRUE;
  647. }
  648. /**
  649. * Profile begin call
  650. */
  651. function _ProfBegin($sonFuncName) {
  652. static $entryTmpl = array ( 'start' => array(),
  653. 'recursiveCount' => 0,
  654. 'totTime' => 0,
  655. 'callCount' => 0 );
  656. $now = explode(' ', microtime());
  657. if (empty($this->callStack)) {
  658. $fatherFuncName = '';
  659. }
  660. else {
  661. $fatherFuncName = $this->callStack[sizeOf($this->callStack)-1];
  662. $fatherEntry = &$this->profile[$fatherFuncName];
  663. }
  664. $this->callStack[] = $sonFuncName;
  665. if (!isSet($this->profile[$sonFuncName])) {
  666. $this->profile[$sonFuncName] = $entryTmpl;
  667. }
  668. $sonEntry = &$this->profile[$sonFuncName];
  669. $sonEntry['callCount']++;
  670. // if we call the t's the same function let the time run, otherwise sum up
  671. if ($fatherFuncName == $sonFuncName) {
  672. $sonEntry['recursiveCount']++;
  673. }
  674. if (!empty($fatherFuncName)) {
  675. $last = $fatherEntry['start'];
  676. $fatherEntry['totTime'] += round( (($now[1] - $last[1]) + ($now[0] - $last[0]))*10000 );
  677. $fatherEntry['start'] = 0;
  678. }
  679. $sonEntry['start'] = explode(' ', microtime());
  680. }
  681. /**
  682. * Profile end call
  683. */
  684. function _ProfEnd($sonFuncName) {
  685. $now = explode(' ', microtime());
  686. array_pop($this->callStack);
  687. if (empty($this->callStack)) {
  688. $fatherFuncName = '';
  689. }
  690. else {
  691. $fatherFuncName = $this->callStack[sizeOf($this->callStack)-1];
  692. $fatherEntry = &$this->profile[$fatherFuncName];
  693. }
  694. $sonEntry = &$this->profile[$sonFuncName];
  695. if (empty($sonEntry)) {
  696. echo "ERROR in profEnd(): '$funcNam' not in list. Seams it was never started ;o)";
  697. }
  698. $last = $sonEntry['start'];
  699. $sonEntry['totTime'] += round( (($now[1] - $last[1]) + ($now[0] - $last[0]))*10000 );
  700. $sonEntry['start'] = 0;
  701. if (!empty($fatherEntry)) $fatherEntry['start'] = explode(' ', microtime());
  702. }
  703. /**
  704. * Show profile gathered so far as HTML table
  705. */
  706. function _ProfileToHtml() {
  707. $sortArr = array();
  708. if (empty($this->profile)) return '';
  709. reset($this->profile);
  710. while (list($funcName) = each($this->profile)) {
  711. $sortArrKey[] = $this->profile[$funcName]['totTime'];
  712. $sortArrVal[] = $funcName;
  713. }
  714. //echo '<pre>';var_dump($sortArrVal);echo '</pre>';
  715. array_multisort ($sortArrKey, SORT_DESC, $sortArrVal );
  716. //echo '<pre>';var_dump($sortArrVal);echo '</pre>';
  717. $totTime = 0;
  718. $size = sizeOf($sortArrVal);
  719. for ($i=0; $i<$size; $i++) {
  720. $funcName = &$sortArrVal[$i];
  721. $totTime += $this->profile[$funcName]['totTime'];
  722. }
  723. $out = '<table border="1">';
  724. $out .='<tr align="center" bgcolor="#bcd6f1"><th>Function</th><th> % </th><th>Total [ms]</th><th># Call</th><th>[ms] per Call</th><th># Recursive</th></tr>';
  725. for ($i=0; $i<$size; $i++) {
  726. $funcName = &$sortArrVal[$i];
  727. $row = &$this->profile[$funcName];
  728. $procent = round($row['totTime']*100/$totTime);
  729. if ($procent>20) $bgc = '#ff8080';
  730. elseif ($procent>15) $bgc = '#ff9999';
  731. elseif ($procent>10) $bgc = '#ffcccc';
  732. elseif ($procent>5) $bgc = '#ffffcc';
  733. else $bgc = '#66ff99';
  734. $out .="<tr align='center' bgcolor='{$bgc}'>";
  735. $out .='<td>'. $funcName .'</td><td>'. $procent .'% '.'</td><td>'. $row['totTime']/10 .'</td><td>'. $row['callCount'] .'</td><td>'. round($row['totTime']/10/$row['callCount'],2) .'</td><td>'. $row['recursiveCount'].'</td>';
  736. $out .='</tr>';
  737. }
  738. $out .= '</table> Total Time [' . $totTime/10 .'ms]' ;
  739. echo $out;
  740. return TRUE;
  741. }
  742. /**
  743. * Echo an XPath context for diagnostic purposes
  744. *
  745. * @param $context (array) An XPath context
  746. */
  747. function _printContext($context) {
  748. echo "{$context['nodePath']}({$context['pos']}/{$context['size']})";
  749. }
  750. /**
  751. * This is a debug helper function. It dumps the node-tree as HTML
  752. *
  753. * *QUICK AND DIRTY*. Needs some polishing.
  754. *
  755. * @param $node (array) A node
  756. * @param $indent (string) (optional, default=''). For internal recursive calls.
  757. */
  758. function _treeDump($node, $indent = '') {
  759. $out = '';
  760. // Get rid of recursion
  761. $parentName = empty($node['parentNode']) ? "SUPER ROOT" : $node['parentNode']['name'];
  762. unset($node['parentNode']);
  763. $node['parentNode'] = $parentName ;
  764. $out .= "NODE[{$node['name']}]\n";
  765. foreach($node as $key => $val) {
  766. if ($key === 'childNodes') continue;
  767. if (is_Array($val)) {
  768. $out .= $indent . " [{$key}]\n" . arrayToStr($val, $indent . ' ');
  769. } else {
  770. $out .= $indent . " [{$key}] => '{$val}' \n";
  771. }
  772. }
  773. if (!empty($node['childNodes'])) {
  774. $out .= $indent . " ['childNodes'] (Size = ".sizeOf($node['childNodes']).")\n";
  775. foreach($node['childNodes'] as $key => $childNode) {
  776. $out .= $indent . " [$key] => " . $this->_treeDump($childNode, $indent . ' ') . "\n";
  777. }
  778. }
  779. if (empty($indent)) {
  780. return "<pre>" . htmlspecialchars($out) . "</pre>";
  781. }
  782. return $out;
  783. }
  784. } // END OF CLASS XPathBase
  785. /************************************************************************************************
  786. * ===============================================================================================
  787. * X P a t h E n g i n e - Class
  788. * ===============================================================================================
  789. ************************************************************************************************/
  790. class XPathEngine extends XPathBase {
  791. // List of supported XPath axes.
  792. // What a stupid idea from W3C to take axes name containing a '-' (dash)
  793. // NOTE: We replace the '-' with '_' to avoid the conflict with the minus operator.
  794. // We will then do the same on the users Xpath querys
  795. // -sibling => _sibling
  796. // -or- => _or_
  797. //
  798. // This array contains a list of all valid axes that can be evaluated in an
  799. // XPath query.
  800. var $axes = array ( 'ancestor', 'ancestor_or_self', 'attribute', 'child', 'descendant',
  801. 'descendant_or_self', 'following', 'following_sibling',
  802. 'namespace', 'parent', 'preceding', 'preceding_sibling', 'self'
  803. );
  804. // List of supported XPath functions.
  805. // What a stupid idea from W3C to take function name containing a '-' (dash)
  806. // NOTE: We replace the '-' with '_' to avoid the conflict with the minus operator.
  807. // We will then do the same on the users Xpath querys
  808. // starts-with => starts_with
  809. // substring-before => substring_before
  810. // substring-after => substring_after
  811. // string-length => string_length
  812. //
  813. // This array contains a list of all valid functions that can be evaluated
  814. // in an XPath query.
  815. var $functions = array ( 'last', 'position', 'count', 'id', 'name',
  816. 'string', 'concat', 'starts_with', 'contains', 'substring_before',
  817. 'substring_after', 'substring', 'string_length', 'normalize_space', 'translate',
  818. 'boolean', 'not', 'true', 'false', 'lang', 'number', 'sum', 'floor',
  819. 'ceiling', 'round', 'x_lower', 'x_upper', 'generate_id' );
  820. // List of supported XPath operators.
  821. //
  822. // This array contains a list of all valid operators that can be evaluated
  823. // in a predicate of an XPath query. The list is ordered by the
  824. // precedence of the operators (lowest precedence first).
  825. var $operators = array( ' or ', ' and ', '=', '!=', '<=', '<', '>=', '>',
  826. '+', '-', '*', ' div ', ' mod ', ' | ');
  827. // List of literals from the xPath string.
  828. var $axPathLiterals = array();
  829. // The index and tree that is created during the analysis of an XML source.
  830. var $nodeIndex = array();
  831. var $nodeRoot = array();
  832. var $emptyNode = array(
  833. 'name' => '', // The tag name. E.g. In <FOO bar="aaa"/> it would be 'FOO'
  834. 'attributes' => array(), // The attributes of the tag E.g. In <FOO bar="aaa"/> it would be array('bar'=>'aaa')
  835. 'childNodes' => array(), // Array of pointers to child nodes.
  836. 'textParts' => array(), // Array of text parts between the cilderen E.g. <FOO>aa<A>bb<B/>cc</A>dd</FOO> -> array('aa','bb','cc','dd')
  837. 'parentNode' => NULL, // Pointer to parent node or NULL if this node is the 'super root'
  838. //-- *!* Following vars are set by the indexer and is for optimisation only *!*
  839. 'depth' => 0, // The tag depth (or tree level) starting with the root tag at 0.
  840. 'pos' => 0, // Is the zero-based position this node has in the parents 'childNodes'-list.
  841. 'contextPos' => 1, // Is the one-based position this node has by counting the siblings tags (tags with same name)
  842. 'xpath' => '' // Is the abs. XPath to this node.
  843. );
  844. var $_indexIsDirty = FALSE;
  845. // These variable used during the parse XML source
  846. var $nodeStack = array(); // The elements that we have still to close.
  847. var $parseStackIndex = 0; // The current element of the nodeStack[] that we are adding to while
  848. // parsing an XML source. Corresponds to the depth of the xml node.
  849. // in our input data.
  850. var $parseOptions = array(); // Used to set the PHP's XML parser options (see xml_parser_set_option)
  851. var $parsedTextLocation = ''; // A reference to where we have to put char data collected during XML parsing
  852. var $parsInCData = 0 ; // Is >0 when we are inside a CDATA section.
  853. var $parseSkipWhiteCache = 0; // A cache of the skip whitespace parse option to speed up the parse.
  854. // This is the array of error strings, to keep consistency.
  855. var $errorStrings = array(
  856. 'AbsoluteXPathRequired' => "The supplied xPath '%s' does not *uniquely* describe a node in the xml document.",
  857. 'NoNodeMatch' => "The supplied xPath-query '%s' does not match *any* node in the xml document.",
  858. 'RootNodeAlreadyExists' => "An xml document may have only one root node."
  859. );
  860. /**
  861. * Constructor
  862. *
  863. * Optionally you may call this constructor with the XML-filename to parse and the
  864. * XML option vector. Each of the entries in the option vector will be passed to
  865. * xml_parser_set_option().
  866. *
  867. * A option vector sample:
  868. * $xmlOpt = array(XML_OPTION_CASE_FOLDING => FALSE,
  869. * XML_OPTION_SKIP_WHITE => TRUE);
  870. *
  871. * @param $userXmlOptions (array) (optional) Vector of (<optionID>=><value>,
  872. * <optionID>=><value>, ...). See PHP's
  873. * xml_parser_set_option() docu for a list of possible
  874. * options.
  875. * @see importFromFile(), importFromString(), setXmlOptions()
  876. */
  877. function XPathEngine($userXmlOptions=array()) {
  878. parent::XPathBase();
  879. // Default to not folding case
  880. $this->parseOptions[XML_OPTION_CASE_FOLDING] = FALSE;
  881. // And not skipping whitespace
  882. $this->parseOptions[XML_OPTION_SKIP_WHITE] = FALSE;
  883. // Now merge in the overrides.
  884. // Don't use PHP's array_merge!
  885. if (is_array($userXmlOptions)) {
  886. foreach($userXmlOptions as $key => $val) $this->parseOptions[$key] = $val;
  887. }
  888. }
  889. /**
  890. * Resets the object so it's able to take a new xml sting/file
  891. *
  892. * Constructing objects is slow. If you can, reuse ones that you have used already
  893. * by using this reset() function.
  894. */
  895. function reset() {
  896. parent::reset();
  897. $this->properties['xmlFile'] = '';
  898. $this->parseStackIndex = 0;
  899. $this->parsedTextLocation = '';
  900. $this->parsInCData = 0;
  901. $this->nodeIndex = array();
  902. $this->nodeRoot = array();
  903. $this->nodeStack = array();
  904. $this->aLiterals = array();
  905. $this->_indexIsDirty = FALSE;
  906. }
  907. //-----------------------------------------------------------------------------------------
  908. // XPathEngine ------ Get / Set Stuff ------
  909. //-----------------------------------------------------------------------------------------
  910. /**
  911. * Returns the property/ies you want.
  912. *
  913. * if $param is not given, all properties will be returned in a hash.
  914. *
  915. * @param $param (string) the property you want the value of, or NULL for all the properties
  916. * @return (mixed) string OR hash of all params, or NULL on an unknown parameter.
  917. */
  918. function getProperties($param=NULL) {
  919. $this->properties['hasContent'] = !empty($this->nodeRoot);
  920. $this->properties['caseFolding'] = $this->parseOptions[XML_OPTION_CASE_FOLDING];
  921. $this->properties['skipWhiteSpaces'] = $this->parseOptions[XML_OPTION_SKIP_WHITE];
  922. if (empty($param)) return $this->properties;
  923. if (isSet($this->properties[$param])) {
  924. return $this->properties[$param];
  925. } else {
  926. return NULL;
  927. }
  928. }
  929. /**
  930. * Set an xml_parser_set_option()
  931. *
  932. * @param $optionID (int) The option ID (e.g. XML_OPTION_SKIP_WHITE)
  933. * @param $value (int) The option value.
  934. * @see XML parser functions in PHP doc
  935. */
  936. function setXmlOption($optionID, $value) {
  937. if (!is_numeric($optionID)) return;
  938. $this->parseOptions[$optionID] = $value;
  939. }
  940. /**
  941. * Sets a number of xml_parser_set_option()s
  942. *
  943. * @param $userXmlOptions (array) An array of parser options.
  944. * @see setXmlOption
  945. */
  946. function setXmlOptions($userXmlOptions=array()) {
  947. if (!is_array($userXmlOptions)) return;
  948. foreach($userXmlOptions as $key => $val) {
  949. $this->setXmlOption($key, $val);
  950. }
  951. }
  952. /**
  953. * Alternative way to control whether case-folding is enabled for this XML parser.
  954. *
  955. * Short cut to setXmlOptions(XML_OPTION_CASE_FOLDING, TRUE/FALSE)
  956. *
  957. * When it comes to XML, case-folding simply means uppercasing all tag-
  958. * and attribute-names (NOT the content) if set to TRUE. Note if you
  959. * have this option set, then your XPath queries will also be case folded
  960. * for you.
  961. *
  962. * @param $onOff (bool) (default TRUE)
  963. * @see XML parser functions in PHP doc
  964. */
  965. function setCaseFolding($onOff=TRUE) {
  966. $this->parseOptions[XML_OPTION_CASE_FOLDING] = $onOff;
  967. }
  968. /**
  969. * Alternative way to control whether skip-white-spaces is enabled for this XML parser.
  970. *
  971. * Short cut to setXmlOptions(XML_OPTION_SKIP_WHITE, TRUE/FALSE)
  972. *
  973. * When it comes to XML, skip-white-spaces will trim the tag content.
  974. * An XML file with no whitespace will be faster to process, but will make
  975. * your data less human readable when you come to write it out.
  976. *
  977. * Running with this option on will slow the class down, so if you want to
  978. * speed up your XML, then run it through once skipping white-spaces, then
  979. * write out the new version of your XML without whitespace, then use the
  980. * new XML file with skip whitespaces turned off.
  981. *
  982. * @param $onOff (bool) (default TRUE)
  983. * @see XML parser functions in PHP doc
  984. */
  985. function setSkipWhiteSpaces($onOff=TRUE) {
  986. $this->parseOptions[XML_OPTION_SKIP_WHITE] = $onOff;
  987. }
  988. /**
  989. * Get the node defined by the $absoluteXPath.
  990. *
  991. * @param $absoluteXPath (string) (optional, default is 'super-root') xpath to the node.
  992. * @return (array) The node, or FALSE if the node wasn't found.
  993. */
  994. function &getNode($absoluteXPath='') {
  995. if ($absoluteXPath==='/') $absoluteXPath = '';
  996. if (!isSet($this->nodeIndex[$absoluteXPath])) return FALSE;
  997. if ($this->_indexIsDirty) $this->reindexNodeTree();
  998. return $this->nodeIndex[$absoluteXPath];
  999. }
  1000. /**
  1001. * Get a the content of a node text part or node attribute.
  1002. *
  1003. * If the absolute Xpath references an attribute (Xpath ends with @ or attribute::),
  1004. * then the text value of that node-attribute is returned.
  1005. * Otherwise the Xpath is referencing a text part of the node. This can be either a
  1006. * direct reference to a text part (Xpath ends with text()[<nr>]) or indirect reference
  1007. * (a simple abs. Xpath to a node).
  1008. * 1) Direct Reference (xpath ends with text()[<part-number>]):
  1009. * If the 'part-number' is omitted, the first text-part is assumed; starting by 1.
  1010. * Negative numbers are allowed, where -1 is the last text-part a.s.o.
  1011. * 2) Indirect Reference (a simple abs. Xpath to a node):
  1012. * Default is to return the *whole text*; that is the concated text-parts of the matching
  1013. * node. (NOTE that only in this case you'll only get a copy and changes to the returned
  1014. * value wounld have no effect). Optionally you may pass a parameter
  1015. * $textPartNr to define the text-part you want; starting by 1.
  1016. * Negative numbers are allowed, where -1 is the last text-part a.s.o.
  1017. *
  1018. * NOTE I : The returned value can be fetched by reference
  1019. * E.g. $text =& wholeText(). If you wish to modify the text.
  1020. * NOTE II: text-part numbers out of range will return FALSE
  1021. * SIDENOTE:The function name is a suggestion from W3C in the XPath specification level 3.
  1022. *
  1023. * @param $absoluteXPath (string) xpath to the node (See above).
  1024. * @param $textPartNr (int) If referring to a node, specifies which text part
  1025. * to query.
  1026. * @return (&string) A *reference* to the text if the node that the other
  1027. * parameters describe or FALSE if the node is not found.
  1028. */
  1029. function &wholeText($absoluteXPath, $textPartNr=NULL) {
  1030. $status = FALSE;
  1031. $text = NULL;
  1032. if ($this->_indexIsDirty) $this->reindexNodeTree();
  1033. do { // try-block
  1034. if (preg_match(";(.*)/(attribute::|@)([^/]*)$;U", $absoluteXPath, $matches)) {
  1035. $absoluteXPath = $matches[1];
  1036. $attribute = $matches[3];
  1037. if (!isSet($this->nodeIndex[$absoluteXPath]['attributes'][$attribute])) {
  1038. $this->_displayError("The $absoluteXPath/attribute::$attribute value isn't a node in this document.", __LINE__, __FILE__, FALSE);
  1039. break; // try-block
  1040. }
  1041. $text =& $this->nodeIndex[$absoluteXPath]['attributes'][$attribute];
  1042. $status = TRUE;
  1043. break; // try-block
  1044. }
  1045. // Xpath contains a 'text()'-function, thus goes right to a text node. If so interpret the Xpath.
  1046. if (preg_match(":(.*)/text\(\)(\[(.*)\])?$:U", $absoluteXPath, $matches)) {
  1047. $absoluteXPath = $matches[1];
  1048. if (!isSet($this->nodeIndex[$absoluteXPath])) {
  1049. $this->_displayError("The $absoluteXPath value isn't a node in this document.", __LINE__, __FILE__, FALSE);
  1050. break; // try-block
  1051. }
  1052. // Get the amount of the text parts in the node.
  1053. $textPartSize = sizeOf($this->nodeIndex[$absoluteXPath]['textParts']);
  1054. // default to the first text node if a text node was not specified
  1055. $textPartNr = isSet($matches[2]) ? substr($matches[2],1,-1) : 1;
  1056. // Support negative indexes like -1 === last a.s.o.
  1057. if ($textPartNr < 0) $textPartNr = $textPartSize + $textPartNr +1;
  1058. if (($textPartNr <= 0) OR ($textPartNr > $textPartSize)) {
  1059. $this->_displayError("The $absoluteXPath/text()[$textPartNr] value isn't a NODE in this document.", __LINE__, __FILE__, FALSE);
  1060. break; // try-block
  1061. }
  1062. $text =& $this->nodeIndex[$absoluteXPath]['textParts'][$textPartNr - 1];
  1063. $status = TRUE;
  1064. break; // try-block
  1065. }
  1066. // At this point we have been given an xpath with neither a 'text()' nor 'attribute::' axis at the end
  1067. // So we assume a get to text is wanted and use the optioanl fallback parameters $textPartNr
  1068. if (!isSet($this->nodeIndex[$absoluteXPath])) {
  1069. $this->_displayError("The $absoluteXPath value isn't a node in this document.", __LINE__, __FILE__, FALSE);
  1070. break; // try-block
  1071. }
  1072. // Get the amount of the text parts in the node.
  1073. $textPartSize

Large files files are truncated, but you can click here to view the full file