PageRenderTime 40ms CodeModel.GetById 8ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/php/XML/Transformer.php

https://bitbucket.org/adarshj/convenient_website
PHP | 797 lines | 366 code | 124 blank | 307 comment | 57 complexity | f1695166dc87a5cbde452b1d414267f2 MD5 | raw file
Possible License(s): Apache-2.0, MPL-2.0-no-copyleft-exception, LGPL-2.1, BSD-2-Clause, GPL-2.0, LGPL-3.0
  1. <?php
  2. //
  3. // +---------------------------------------------------------------------------+
  4. // | PEAR :: XML :: Transformer |
  5. // +---------------------------------------------------------------------------+
  6. // | Copyright (c) 2002-2004 Sebastian Bergmann <sb@sebastian-bergmann.de> and |
  7. // | Kristian Köhntopp <kris@koehntopp.de>. |
  8. // +---------------------------------------------------------------------------+
  9. // | This source file is subject to version 3.00 of the PHP License, |
  10. // | that is available at http://www.php.net/license/3_0.txt. |
  11. // | If you did not receive a copy of the PHP license and are unable to |
  12. // | obtain it through the world-wide-web, please send a note to |
  13. // | license@php.net so we can mail you a copy immediately. |
  14. // +---------------------------------------------------------------------------+
  15. //
  16. // $Id: Transformer.php,v 1.137 2004/11/20 08:23:51 sebastian Exp $
  17. //
  18. require_once 'XML/Transformer/CallbackRegistry.php';
  19. require_once 'XML/Util.php';
  20. /**
  21. * XML Transformations in PHP.
  22. *
  23. * With this class one can easily bind PHP functionality to XML tags,
  24. * thus transforming an XML input tree into another XML tree without
  25. * the need for XSLT.
  26. *
  27. * @author Sebastian Bergmann <sb@sebastian-bergmann.de>
  28. * @author Kristian Köhntopp <kris@koehntopp.de>
  29. * @copyright Copyright &copy; 2002-2004 Sebastian Bergmann <sb@sebastian-bergmann.de> and Kristian Köhntopp <kris@koehntopp.de>
  30. * @license http://www.php.net/license/3_0.txt The PHP License, Version 3.0
  31. * @category XML
  32. * @package XML_Transformer
  33. */
  34. class XML_Transformer {
  35. // {{{ Members
  36. /**
  37. * @var object
  38. * @access private
  39. */
  40. var $_callbackRegistry = NULL;
  41. /**
  42. * If TRUE, XML attribute and element names will be
  43. * case-folded.
  44. *
  45. * @var boolean
  46. * @access private
  47. * @see $_caseFoldingTo
  48. */
  49. var $_caseFolding = FALSE;
  50. /**
  51. * Can be set to either CASE_UPPER or CASE_LOWER
  52. * and sets the target case for the case-folding.
  53. *
  54. * @var integer
  55. * @access private
  56. * @see $_caseFolding
  57. */
  58. var $_caseFoldingTo = CASE_UPPER;
  59. /**
  60. * When set to TRUE empty XML tags (<foo></foo>) are
  61. * collapsed to their short-tag (<foo/>) equivalent.
  62. *
  63. * @var boolean
  64. * @access private
  65. */
  66. var $_collapseEmptyTags = FALSE;
  67. /**
  68. * Collapse mode
  69. *
  70. * @var int
  71. * @access private
  72. */
  73. var $_collapseEmptyTagsMode = XML_UTIL_COLLAPSE_ALL;
  74. /**
  75. * If TRUE, debugging information will be sent to
  76. * the error.log.
  77. *
  78. * @var boolean
  79. * @access private
  80. * @see $_debugFilter
  81. */
  82. var $_debug = FALSE;
  83. /**
  84. * If not empty, debugging information will only be generated
  85. * for XML elements whose names are in this array.
  86. *
  87. * @var array
  88. * @access private
  89. * @see $_debug
  90. */
  91. var $_debugFilter = array();
  92. /**
  93. * Specifies the target to which error messages and
  94. * debugging messages are sent.
  95. *
  96. * @var string
  97. * @access private
  98. * @see $_debug
  99. */
  100. var $_logTarget = 'error_log';
  101. /**
  102. * @var array
  103. * @access private
  104. */
  105. var $_attributesStack = array();
  106. /**
  107. * @var array
  108. * @access private
  109. */
  110. var $_cdataStack = array('');
  111. /**
  112. * @var array
  113. * @access private
  114. */
  115. var $_elementStack = array();
  116. /**
  117. * @var integer
  118. * @access private
  119. */
  120. var $_level = 0;
  121. /**
  122. * @var string
  123. * @access private
  124. */
  125. var $_lastProcessed = '';
  126. /**
  127. * @var boolean
  128. * @access public
  129. */
  130. var $_secondPassRequired = FALSE;
  131. /**
  132. * @var integer
  133. * @access private
  134. */
  135. var $_depth = 0;
  136. // }}}
  137. // {{{ function XML_Transformer($parameters = array())
  138. /**
  139. * Constructor.
  140. *
  141. * @param array
  142. * @access public
  143. */
  144. function XML_Transformer($parameters = array()) {
  145. // Parse parameters array.
  146. if (isset($parameters['debug'])) {
  147. $this->setDebug($parameters['debug']);
  148. }
  149. $this->_caseFolding = isset($parameters['caseFolding']) ? $parameters['caseFolding'] : FALSE;
  150. $this->_collapseEmptyTags = isset($parameters['collapseEmptyTags']) ? $parameters['collapseEmptyTags'] : FALSE;
  151. $this->_collapseEmptyTagsMode = isset($parameters['collapseEmptyTagsMode']) ? $parameters['collapseEmptyTagsMode'] : XML_UTIL_COLLAPSE_ALL;
  152. $this->_caseFoldingTo = isset($parameters['caseFoldingTo']) ? $parameters['caseFoldingTo'] : CASE_UPPER;
  153. $this->_lastProcessed = isset($parameters['lastProcessed']) ? $parameters['lastProcessed'] : '';
  154. $this->_logTarget = isset($parameters['logTarget']) ? $parameters['logTarget'] : 'error_log';
  155. $autoload = isset($parameters['autoload']) ? $parameters['autoload'] : FALSE;
  156. $overloadedNamespaces = isset($parameters['overloadedNamespaces']) ? $parameters['overloadedNamespaces'] : array();
  157. $recursiveOperation = isset($parameters['recursiveOperation']) ? $parameters['recursiveOperation'] : TRUE;
  158. // Initialize callback registry.
  159. if (!isset($parameters['callbackRegistry'])) {
  160. $this->_callbackRegistry = new XML_Transformer_CallbackRegistry($recursiveOperation);
  161. } else {
  162. $this->_callbackRegistry = &$parameters['callbackRegistry'];
  163. }
  164. foreach ($overloadedNamespaces as $namespacePrefix => $object) {
  165. $this->overloadNamespace(
  166. $namespacePrefix,
  167. $object
  168. );
  169. }
  170. if ($autoload !== FALSE) {
  171. $this->_autoload($autoload);
  172. }
  173. }
  174. // }}}
  175. // {{{ function canonicalize($target)
  176. /**
  177. * Canonicalizes a given attributes array or element name.
  178. *
  179. * @param mixed
  180. * @return mixed
  181. * @access public
  182. */
  183. function canonicalize($target) {
  184. if ($this->_caseFolding) {
  185. if (is_string($target)) {
  186. return ($this->_caseFoldingTo == CASE_UPPER) ? strtoupper($target) : strtolower($target);
  187. } else {
  188. return array_change_key_case(
  189. $target,
  190. $this->_caseFoldingTo
  191. );
  192. }
  193. }
  194. return $target;
  195. }
  196. // }}}
  197. // {{{ function overloadNamespace($namespacePrefix, &$object, $recursiveOperation = '')
  198. /**
  199. * Overloads an XML Namespace.
  200. *
  201. * @param string
  202. * @param object
  203. * @param boolean
  204. * @access public
  205. */
  206. function overloadNamespace($namespacePrefix, &$object, $recursiveOperation = '') {
  207. if (empty($namespacePrefix) ||
  208. $namespacePrefix == '&MAIN') {
  209. $namespacePrefix = '&MAIN';
  210. } else {
  211. $namespacePrefix = $this->canonicalize($namespacePrefix);
  212. }
  213. $result = $this->_callbackRegistry->overloadNamespace(
  214. $namespacePrefix,
  215. $object,
  216. $recursiveOperation
  217. );
  218. if ($result === TRUE) {
  219. if ($object->secondPassRequired) {
  220. $this->_secondPassRequired = TRUE;
  221. }
  222. // Call initObserver() on the object, if it exists.
  223. if (method_exists($object, 'initObserver')) {
  224. $object->initObserver(
  225. $namespacePrefix,
  226. $this
  227. );
  228. }
  229. } else {
  230. $this->sendMessage(
  231. $result,
  232. $this->_logTarget
  233. );
  234. }
  235. }
  236. // }}}
  237. // {{{ function unOverloadNamespace($namespacePrefix)
  238. /**
  239. * Reverts overloading of a given XML Namespace.
  240. *
  241. * @param string
  242. * @access public
  243. */
  244. function unOverloadNamespace($namespacePrefix) {
  245. $this->_callbackRegistry->unOverloadNamespace($namespacePrefix);
  246. }
  247. // }}}
  248. // {{{ function isOverloadedNamespace($namespacePrefix)
  249. /**
  250. * Returns TRUE if a given namespace is overloaded,
  251. * FALSE otherwise.
  252. *
  253. * @param string
  254. * @return boolean
  255. * @access public
  256. */
  257. function isOverloadedNamespace($namespacePrefix) {
  258. return $this->_callbackRegistry->isOverloadedNamespace(
  259. $this->canonicalize($namespacePrefix)
  260. );
  261. }
  262. // }}}
  263. // {{{ function sendMessage($message, $target = 'error_log')
  264. /**
  265. * Sends a message to a given target.
  266. *
  267. * @param string
  268. * @param string
  269. * @access public
  270. */
  271. function sendMessage($message, $target = 'error_log') {
  272. switch ($target) {
  273. case 'echo':
  274. case 'print': {
  275. print $message;
  276. }
  277. break;
  278. default: {
  279. error_log($message);
  280. }
  281. }
  282. }
  283. // }}}
  284. // {{{ function setCaseFolding($caseFolding)
  285. /**
  286. * Sets the XML parser's case-folding option.
  287. *
  288. * @param boolean
  289. * @param integer
  290. * @access public
  291. */
  292. function setCaseFolding($caseFolding, $caseFoldingTo = CASE_UPPER) {
  293. if (is_bool($caseFolding) &&
  294. ($caseFoldingTo == CASE_LOWER || $caseFoldingTo == CASE_UPPER)) {
  295. $this->_caseFolding = $caseFolding;
  296. $this->_caseFoldingTo = $caseFoldingTo;
  297. }
  298. }
  299. // }}}
  300. // {{{ function setCollapsingOfEmptyTags($collapseEmptyTags, $mode = XML_UTIL_COLLAPSE_ALL)
  301. /**
  302. * Sets the collapsing of empty tags.
  303. *
  304. * @param boolean
  305. * @param integer
  306. * @access public
  307. */
  308. function setCollapsingOfEmptyTags($collapseEmptyTags, $mode = XML_UTIL_COLLAPSE_ALL) {
  309. if (is_bool($collapseEmptyTags) &&
  310. ($mode == XML_UTIL_COLLAPSE_ALL || $mode == XML_UTIL_COLLAPSE_XHTML_ONLY)) {
  311. $this->_collapseEmptyTags = $collapseEmptyTags;
  312. $this->_collapseEmptyTagsMode = $mode;
  313. }
  314. }
  315. // }}}
  316. // {{{ function setDebug($debug)
  317. /**
  318. * Enables or disables debugging information.
  319. *
  320. * @param mixed
  321. * @access public
  322. */
  323. function setDebug($debug) {
  324. if (is_array($debug)) {
  325. $this->_debug = TRUE;
  326. $this->_debugFilter = array_flip($debug);
  327. }
  328. else if (is_bool($debug)) {
  329. $this->_debug = $debug;
  330. }
  331. }
  332. // }}}
  333. // {{{ function setLogTarget($logTarget)
  334. /**
  335. * Sets the target to which error messages and
  336. * debugging messages are sent.
  337. *
  338. * @param string
  339. * @access public
  340. */
  341. function setLogTarget($logTarget) {
  342. $this->_logTarget = $logTarget;
  343. }
  344. // }}}
  345. // {{{ function setRecursiveOperation($recursiveOperation)
  346. /**
  347. * Enables or disables the recursive operation.
  348. *
  349. * @param boolean
  350. * @access public
  351. */
  352. function setRecursiveOperation($recursiveOperation) {
  353. $this->_callbackRegistry->setRecursiveOperation($recursiveOperation);
  354. }
  355. // }}}
  356. // {{{ function stackdump()
  357. /**
  358. * Returns a stack dump as a debugging aid.
  359. *
  360. * @return string
  361. * @access public
  362. */
  363. function stackdump() {
  364. $stackdump = sprintf(
  365. "Stackdump (level: %s) follows:\n",
  366. $this->_level
  367. );
  368. for ($i = $this->_level; $i >= 0; $i--) {
  369. $stackdump .= sprintf(
  370. "level=%d\nelement=%s:%s\ncdata=%s\n\n",
  371. $i,
  372. isset($this->_elementStack[$i]) ? $this->_elementStack[$i] : '',
  373. isset($this->_attributesStack[$i]) ? XML_Util::attributesToString($this->_attributesStack[$i]) : '',
  374. isset($this->_cdataStack[$i]) ? $this->_cdataStack[$i] : ''
  375. );
  376. }
  377. return $stackdump;
  378. }
  379. // }}}
  380. // {{{ function transform($xml)
  381. /**
  382. * Transforms a given XML string using the registered
  383. * PHP callbacks for overloaded tags.
  384. *
  385. * @param string
  386. * @return string
  387. * @access public
  388. */
  389. function transform($xml) {
  390. // Do not process input when it contains no XML elements.
  391. if (strpos($xml, '<') === FALSE) {
  392. return $xml;
  393. }
  394. // Replace all occurrences of the '&' character that are not directly
  395. // followed by 'amp;' with the '&amp;' entity.
  396. $xml = preg_replace('/&(?!amp;)/i', '&amp;', $xml);
  397. // Create XML parser, set parser options.
  398. $parser = xml_parser_create();
  399. xml_set_object($parser, $this);
  400. xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, $this->_caseFolding);
  401. // Register SAX callbacks.
  402. xml_set_element_handler($parser, '_startElement', '_endElement');
  403. xml_set_character_data_handler($parser, '_characterData');
  404. xml_set_default_handler($parser, '_characterData');
  405. // Parse input.
  406. if (!xml_parse($parser, $xml, TRUE)) {
  407. $line = xml_get_current_line_number($parser);
  408. $errorMessage = sprintf(
  409. "Transformer: XML Error: %s at line %d:%d\n",
  410. xml_error_string(xml_get_error_code($parser)),
  411. $line,
  412. xml_get_current_column_number($parser)
  413. );
  414. $exml = preg_split('/\n/', $xml);
  415. $start = ($line - 3 > 0) ? $line - 3 : 0;
  416. $end = ($line + 3 < sizeof($exml)) ? $line + 3 : sizeof($exml);
  417. for ($i = $start; $i < $end; $i++) {
  418. $errorMessage .= sprintf(
  419. "line %d: %s\n",
  420. $i+1,
  421. $exml[$i]
  422. );
  423. }
  424. $this->sendMessage(
  425. $errorMessage . "\n" . $this->stackdump(),
  426. $this->_logTarget
  427. );
  428. return '';
  429. }
  430. $result = $this->_cdataStack[0];
  431. // Clean up.
  432. xml_parser_free($parser);
  433. $this->_attributesStack = array();
  434. $this->_cdataStack = array('');
  435. $this->_elementStack = array();
  436. $this->_level = 0;
  437. $this->_lastProcessed = '';
  438. // Perform second transformation pass, if required.
  439. $secondPassRequired = $this->_secondPassRequired;
  440. if ($secondPassRequired) {
  441. $this->_depth++;
  442. $this->_secondPassRequired = FALSE;
  443. $result = $this->transform($result);
  444. $this->_depth--;
  445. }
  446. if ($this->_collapseEmptyTags &&
  447. $this->_depth == 0) {
  448. $result = XML_Util::collapseEmptyTags(
  449. $result,
  450. $this->_collapseEmptyTagsMode
  451. );
  452. }
  453. $this->_secondPassRequired = $secondPassRequired;
  454. // Return result of the transformation.
  455. return $result;
  456. }
  457. // }}}
  458. // {{{ function _startElement($parser, $element, $attributes)
  459. /**
  460. * SAX callback for 'startElement' event.
  461. *
  462. * @param resource
  463. * @param string
  464. * @param array
  465. * @access private
  466. */
  467. function _startElement($parser, $element, $attributes) {
  468. $attributes = $this->canonicalize($attributes);
  469. $element = $this->canonicalize($element);
  470. $qElement = XML_Util::splitQualifiedName($element, '&MAIN');
  471. $process = $this->_lastProcessed != $element;
  472. // Push element's name and attributes onto the stack.
  473. $this->_level++;
  474. $this->_elementStack[$this->_level] = $element;
  475. $this->_attributesStack[$this->_level] = $attributes;
  476. if ($this->_checkDebug($element)) {
  477. $this->sendMessage(
  478. sprintf(
  479. 'startElement[%d]: %s %s',
  480. $this->_level,
  481. $element,
  482. XML_Util::attributesToString($attributes)
  483. )
  484. );
  485. }
  486. if ($process &&
  487. isset($this->_callbackRegistry->overloadedNamespaces[$qElement['namespace']]['active'])) {
  488. // The event is handled by a callback
  489. // that is registered for this namespace.
  490. $cdata = $this->_callbackRegistry->overloadedNamespaces[$qElement['namespace']]['object']->startElement(
  491. $qElement['localPart'],
  492. $attributes
  493. );
  494. } else {
  495. // No callback was registered for this element's
  496. // opening tag, copy it.
  497. $cdata = sprintf(
  498. '<%s%s>',
  499. $element,
  500. XML_Util::attributesToString($attributes)
  501. );
  502. }
  503. $this->_cdataStack[$this->_level] = $cdata;
  504. }
  505. // }}}
  506. // {{{ function _endElement($parser, $element)
  507. /**
  508. * SAX callback for 'endElement' event.
  509. *
  510. * @param resource
  511. * @param string
  512. * @access private
  513. */
  514. function _endElement($parser, $element) {
  515. $cdata = $this->_cdataStack[$this->_level];
  516. $element = $this->canonicalize($element);
  517. $qElement = XML_Util::splitQualifiedName($element, '&MAIN');
  518. $process = $this->_lastProcessed != $element;
  519. $recursion = FALSE;
  520. if ($process &&
  521. isset($this->_callbackRegistry->overloadedNamespaces[$qElement['namespace']]['active'])) {
  522. // The event is handled by a callback
  523. // that is registered for this namespace.
  524. $result = $this->_callbackRegistry->overloadedNamespaces[$qElement['namespace']]['object']->endElement(
  525. $qElement['localPart'],
  526. $cdata
  527. );
  528. if (is_array($result)) {
  529. $cdata = &$result[0];
  530. $reparse = $result[1];
  531. } else {
  532. $cdata = &$result;
  533. $reparse = TRUE;
  534. }
  535. $recursion = $reparse &&
  536. isset($this->_elementStack[$this->_level-1]) &&
  537. $this->_callbackRegistry->overloadedNamespaces[$qElement['namespace']]['recursiveOperation'];
  538. } else {
  539. // No callback was registered for this element's
  540. // closing tag, copy it.
  541. $cdata .= '</' . $element . '>';
  542. }
  543. if ($recursion) {
  544. // Recursively process this transformation's result.
  545. if ($this->_checkDebug('&RECURSE')) {
  546. $this->sendMessage(
  547. sprintf(
  548. 'start recursion[%d]: %s',
  549. $this->_level,
  550. $cdata
  551. )
  552. );
  553. }
  554. $transformer = new XML_Transformer(
  555. array(
  556. 'callbackRegistry' => &$this->_callbackRegistry,
  557. 'caseFolding' => $this->_caseFolding,
  558. 'caseFoldingTo' => $this->_caseFoldingTo,
  559. 'lastProcessed' => $element
  560. )
  561. );
  562. $cdata = substr($transformer->transform("<_>$cdata</_>"), 3, -4);
  563. if ($this->_checkDebug('&RECURSE')) {
  564. $this->sendMessage(
  565. sprintf(
  566. 'end recursion[%d]: %s',
  567. $this->_level,
  568. $cdata
  569. )
  570. );
  571. }
  572. }
  573. if ($this->_checkDebug($element)) {
  574. $this->sendMessage(
  575. sprintf(
  576. 'endElement[%d]: %s (with cdata=%s)',
  577. $this->_level,
  578. $element,
  579. $this->_cdataStack[$this->_level]
  580. )
  581. );
  582. }
  583. // Move result of this transformation step to
  584. // the parent's CDATA section.
  585. $this->_cdataStack[--$this->_level] .= $cdata;
  586. }
  587. // }}}
  588. // {{{ function _characterData($parser, $cdata)
  589. /**
  590. * SAX callback for 'characterData' event.
  591. *
  592. * @param resource
  593. * @param string
  594. * @access private
  595. */
  596. function _characterData($parser, $cdata) {
  597. if ($this->_checkDebug('&CDATA')) {
  598. $this->sendMessage(
  599. sprintf(
  600. 'cdata [%d]: %s + %s',
  601. $this->_level,
  602. $this->_cdataStack[$this->_level],
  603. $cdata
  604. )
  605. );
  606. }
  607. $this->_cdataStack[$this->_level] .= $cdata;
  608. }
  609. // }}}
  610. // {{{ function _autoload($namespaces)
  611. /**
  612. * Loads either all (TRUE) or a selection of namespace
  613. * handlers from XML/Transformer/Namespace/.
  614. *
  615. * @param mixed
  616. * @access private
  617. */
  618. function _autoload($namespaces) {
  619. $path = dirname(__FILE__) . '/Transformer/Namespace/';
  620. if ($namespaces === TRUE) {
  621. $namespaces = array();
  622. if ($dir = @opendir($path)) {
  623. while (($file = @readdir($dir)) !== FALSE) {
  624. if (strstr($file, '.php')) {
  625. $namespaces[] = $this->canonicalize(
  626. strtolower(
  627. substr($file, 0, -4)
  628. )
  629. );
  630. }
  631. }
  632. }
  633. }
  634. else if (is_string($namespaces)) {
  635. $namespaces = array($namespaces);
  636. }
  637. foreach ($namespaces as $namespace) {
  638. if (@include_once($path . $namespace . '.php')) {
  639. $className = 'XML_Transformer_Namespace_' . $namespace;
  640. $object = new $className;
  641. $this->overloadNamespace(
  642. !empty($object->defaultNamespacePrefix) ? $object->defaultNamespacePrefix : $namespace,
  643. $object
  644. );
  645. }
  646. }
  647. }
  648. // }}}
  649. // {{{ function _checkDebug($currentElement = '')
  650. /**
  651. * Checks whether a debug message should be printed
  652. * for the current event.
  653. *
  654. * @param string
  655. * @return boolean
  656. * @access private
  657. */
  658. function _checkDebug($currentElement = '') {
  659. if ($this->_debug &&
  660. (empty($this->_debugFilter) ||
  661. isset($this->_debugFilter[$currentElement]))) {
  662. return TRUE;
  663. } else {
  664. return FALSE;
  665. }
  666. }
  667. // }}}
  668. }
  669. /*
  670. * vim600: et sw=2 ts=2 fdm=marker
  671. * vim<600: et sw=2 ts=2
  672. */
  673. ?>