Projet

Général

Profil

Paste
Télécharger (5,52 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / feeds_xpathparser / FeedsXPathParserDOMXPath.inc @ 70a4c29b

1
<?php
2

    
3
/**
4
 * @file
5
 * Provides a custom version of DOMXPath for use with feeds_xpathparser.
6
 */
7

    
8
/**
9
 * Wraps DOMXPath providing enhanced debugging and special namespace handling.
10
 */
11
class FeedsXPathParserDOMXPath extends DOMXPath {
12

    
13
  /**
14
   * The DOMDocument to parse.
15
   *
16
   * @var DOMDocument
17
   */
18
  protected $doc;
19

    
20
  /**
21
   * Configuration array.
22
   *
23
   * @var array
24
   */
25
  protected $config = array();
26

    
27
  /**
28
   * Modified query cache.
29
   *
30
   * @var arrray
31
   */
32
  protected $modifiedQueries = array();
33

    
34
  /**
35
   * The namespaces in the document.
36
   *
37
   * @var arrray
38
   */
39
  protected $namepsaces = array();
40

    
41
  /**
42
   * The most recent error from parsing.
43
   *
44
   * @var stdClass
45
   */
46
  protected $error;
47

    
48
  /**
49
   * Constructs a FeedsXPathParserDOMXPath object.
50
   *
51
   * @param DOMDocument $doc
52
   *   The DOMDocument that we're operating on.
53
   */
54
  public function __construct(DOMDocument $doc) {
55

    
56
    $simple = simplexml_import_dom($doc);
57

    
58
    // An empty DOMDocument will make $simple NULL.
59
    if ($simple !== NULL) {
60
      $this->namespaces = $simple->getNamespaces(TRUE);
61
    }
62
    $this->doc = $doc;
63

    
64
    parent::__construct($doc);
65
  }
66

    
67
  /**
68
   * Sets the extended configuration.
69
   *
70
   * @param array $config
71
   *   The config array.
72
   */
73
  public function setConfig(array $config) {
74
    $this->config = $config;
75
  }
76

    
77
  /**
78
   * Renders our debug messages into a list.
79
   *
80
   * @param mixed $data
81
   *   The result of an XPath query. Either a scalar or a DOMNodeList.
82
   * @param string $source
83
   *   The source key that produced this query.
84
   *
85
   * @todo Use theme_item_list().
86
   */
87
  protected function debug($data, $source) {
88
    $output = "$source : <ul>";
89

    
90
    if ($data instanceof DOMNodeList) {
91
      foreach ($data as $node) {
92
        $output .= '<li>' . check_plain($this->doc->saveXML($node)) . '</li>';
93
      }
94
    }
95
    else {
96
      $output .= '<li>' . check_plain($data) . '</li>';
97
    }
98
    $output .= '</ul>';
99

    
100
    drupal_set_message($output);
101
  }
102

    
103
  /**
104
   * Executes an XPath query with namespace support.
105
   *
106
   * @param string $query
107
   *   An XPath query.
108
   * @param DOMNode $context
109
   *   The current context of the XPath query.
110
   * @param string $source
111
   *   The source key for this query.
112
   *
113
   * @return array
114
   *   An array containing the results of the query.
115
   */
116
  public function namespacedQuery($query, $context, $source) {
117
    $this->addDefaultNamespace($query);
118

    
119
    $results = $this->executeQuery($query, $context);
120

    
121
    if (in_array($source, $this->config['debug'])) {
122
      $this->debug($results, $source);
123
    }
124

    
125
    if (is_object($this->error) && $this->config['errors']) {
126

    
127
      if ($this->error->level == LIBXML_ERR_ERROR) {
128
        drupal_set_message(
129
          t('There was an error during the XPath query: %query.<br />Libxml returned the message: %message, with the error code: %code.', array(
130
            '%query' => $query,
131
            '%message' => trim($this->error->message),
132
            '%code' => $this->error->code,
133
          )),
134
          'error',
135
          FALSE);
136
      }
137
      elseif ($this->error->level == LIBXML_ERR_WARNING) {
138
        drupal_set_message(
139
          t('There was an error during the XPath query: %query.<br />Libxml returned the message: %message, with the error code: %code.', array(
140
            '%query' => $query,
141
            '%message' => trim($this->error->message),
142
            '%code' => $this->error->code,
143
          )),
144
          'warning',
145
          FALSE);
146
      }
147
    }
148

    
149
    // DOMXPath::evaluate() and DOMXPath::query() will return FALSE on error or
150
    // if the value is FALSE. We check error result and return NULL in case
151
    // of error.
152
    if (is_object($this->error) && $this->error->level == LIBXML_ERR_ERROR) {
153
      return NULL;
154
    }
155

    
156
    return $results;
157
  }
158

    
159
  /**
160
   * Normalizes XPath queries, adding the default namespace.
161
   *
162
   * @param string $query
163
   *   An XPath query string
164
   */
165
  protected function addDefaultNamespace(&$query) {
166
    foreach ($this->namespaces as $prefix => $namespace) {
167
      if ($prefix === '') {
168
        $this->registerNamespace('__default__', $namespace);
169

    
170
        // Replace all the elements without prefix by the default prefix.
171
        if (!isset($this->modifiedQueries[$query])) {
172
          $parser = new FeedsXPathParserQueryParser($query);
173
          $mod_query = $parser->getQuery();
174
          $this->modifiedQueries[$query] = $mod_query;
175
          $query = $mod_query;
176
        }
177
        else {
178
          $query = $this->modifiedQueries[$query];
179
        }
180
      }
181
      else {
182
        $this->registerNamespace($prefix, $namespace);
183
      }
184
    }
185
  }
186

    
187
  /**
188
   * Performs a XPath query.
189
   *
190
   * Here we set libxml_use_internal_errors() to TRUE because depending on the
191
   * libxml version, $xml->xpath() might return FALSE or an empty array() when
192
   * a query doesn't match.
193
   *
194
   * @param string $query
195
   *   The XPath query string.
196
   * @param DOMNode $context
197
   *   (optional) A context object. Defaults to NULL.
198
   *
199
   * @return mixed
200
   *   The result of the XPath query.
201
   */
202
  protected function executeQuery($query, DOMNode $context = NULL) {
203
    $use_errors = libxml_use_internal_errors(TRUE);
204

    
205
    // Perfom XPath query.
206
    // So, grrr. FALSE is returned when there is an error. However, FALSE is
207
    // also a valid return value from DOMXPath::evaluate(). Ex: '1 = 2'
208
    if ($context) {
209
      $results = $this->evaluate($query, $context);
210
    }
211
    else {
212
      $results = $this->query($query);
213
    }
214

    
215
    $this->error = libxml_get_last_error();
216
    libxml_clear_errors();
217
    libxml_use_internal_errors($use_errors);
218
    return $results;
219
  }
220

    
221
}