FeedsXPathParserDOMXPath.inc 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. <?php
  2. /**
  3. * @file
  4. * Provides a custom version of DOMXPath for use with feeds_xpathparser.
  5. */
  6. class FeedsXPathParserDOMXPath extends DOMXPath {
  7. protected $config = array();
  8. protected $modifiedQueries = array();
  9. public function __construct(DOMDocument $doc) {
  10. $this->namespaces = array();
  11. $simple = simplexml_import_dom($doc);
  12. // An empty DOMDocument will make $simple NULL.
  13. if ($simple !== NULL) {
  14. $this->namespaces = $simple->getNamespaces(TRUE);
  15. }
  16. $this->doc = $doc;
  17. parent::__construct($doc);
  18. }
  19. public function setConfig(array $config) {
  20. $this->config = $config;
  21. }
  22. protected function debug($data, $source) {
  23. $output = "$source : <ul>";
  24. if ($data instanceof DOMNodeList) {
  25. foreach ($data as $node) {
  26. $output .= '<li>' . check_plain($this->doc->saveXML($node)) . '</li>';
  27. }
  28. }
  29. else {
  30. $output .= '<li>' . check_plain($data) . '</li>';
  31. }
  32. $output .= '</ul>';
  33. drupal_set_message($output);
  34. }
  35. /**
  36. * Executes an XPath query with namespace support.
  37. *
  38. * @param $xpath
  39. * The DOMXPath object.
  40. *
  41. * @param $query
  42. * An XPath query.
  43. *
  44. * @return array
  45. * An array containing the results of the query.
  46. */
  47. public function namespacedQuery($query, $context, $source) {
  48. $this->addDefaultNamespace($query);
  49. $results = $this->_query($query, $context);
  50. if (in_array($source, $this->config['debug'])) {
  51. $this->debug($results, $source);
  52. }
  53. if (is_object($this->error) && $this->config['errors']) {
  54. if ($this->error->level == LIBXML_ERR_ERROR) {
  55. drupal_set_message(
  56. t("There was an error during the XPath query: %query.<br />
  57. Libxml returned the message: %message, with the error code: %code.",
  58. array('%query' => $query,
  59. '%message' => trim($this->error->message),
  60. '%code' => $this->error->code)),
  61. 'error',
  62. FALSE);
  63. }
  64. elseif ($this->error->level == LIBXML_ERR_WARNING) {
  65. drupal_set_message(
  66. t("There was an error during the XPath query: %query.<br />
  67. Libxml returned the message: %message, with the error code: %code.",
  68. array('%query' => $query,
  69. '%message' => trim($this->error->message),
  70. '%code' => $this->error->code)),
  71. 'warning',
  72. FALSE);
  73. }
  74. }
  75. // DOMXPath::evaluate() and DOMXPath::query() will return FALSE on error or
  76. // if the value is false. We check error result and return NULL in case
  77. // of error.
  78. if (is_object($this->error) && $this->error->level == LIBXML_ERR_ERROR) {
  79. return NULL;
  80. }
  81. return $results;
  82. }
  83. /**
  84. * Normalizes XPath queries, adding the default namespace.
  85. *
  86. * @param $query
  87. * An XPath query string
  88. */
  89. protected function addDefaultNamespace(&$query) {
  90. foreach ($this->namespaces as $prefix => $namespace) {
  91. if ($prefix === '') {
  92. $this->registerNamespace('__default__', $namespace);
  93. // Replace all the elements without prefix by the default prefix.
  94. if (!isset($this->modifiedQueries[$query])) {
  95. $parser = new FeedsXPathParserQueryParser($query);
  96. $modQuery = $parser->getQuery();
  97. $this->modifiedQueries[$query] = $modQuery;
  98. $query = $modQuery;
  99. }
  100. else {
  101. $query = $this->modifiedQueries[$query];
  102. }
  103. }
  104. else {
  105. $this->registerNamespace($prefix, $namespace);
  106. }
  107. }
  108. }
  109. /**
  110. * Here we set libxml_use_internal_errors to TRUE because depending on the
  111. * libxml version, $xml->xpath() might return FALSE or an empty array() when
  112. * a query doesn't match.
  113. */
  114. protected function _query($query, $context = NULL) {
  115. $use_errors = libxml_use_internal_errors(TRUE);
  116. // Perfom XPath query.
  117. // So, grrr. FALSE is returned when there is an error. However, FALSE is
  118. // also a valid return value from DOMXPath::evaluate(). Ex: '1 = 2'
  119. if ($context) {
  120. $results = $this->evaluate($query, $context);
  121. }
  122. else {
  123. $results = $this->query($query);
  124. }
  125. $this->error = libxml_get_last_error();
  126. libxml_clear_errors();
  127. libxml_use_internal_errors($use_errors);
  128. return $results;
  129. }
  130. }