Projet

Général

Profil

Paste
Télécharger (9,54 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / feeds_jsonpath_parser / FeedsJSONPathParser.inc @ 87dbc3bf

1
<?php
2
// $Id: FeedsJSONPathParser.inc,v 1.1.2.4.2.4 2011/02/05 19:28:01 twistor Exp $
3

    
4
/**
5
 * @file
6
 *
7
 * Provides the Class for Feeds JSONPath Parser.
8
 */
9

    
10
/**
11
 * Base class for the HTML and XML parsers.
12
 */
13
class FeedsJSONPathParser extends FeedsParser {
14

    
15
  /**
16
   * Implementation of FeedsParser::parse().
17
   */
18
  public function parse(FeedsSource $source, FeedsFetcherResult $fetcher_result) {
19
    $mappings = $source->importer->processor->config['mappings'];
20
    $mappings = $this->filterMappings($mappings);
21
    $source_config = $source->getConfigFor($this);
22
    // Allow config inheritance.
23
    if (empty($source_config)) {
24
      $source_config = $this->config;
25
    }
26
    $this->debug = array_keys(array_filter($source_config['debug']['options']));
27

    
28
    $raw = trim($fetcher_result->getRaw());
29
    $result = new FeedsParserResult();
30
    // Set link so we can set the result link attribute.
31
    $fetcher_config = $source->getConfigFor($source->importer->fetcher);
32
    $result->link = $fetcher_config['source'];
33

    
34
    $array = json_decode($raw, TRUE);
35

    
36
    // Support JSON lines format.
37
    if (!is_array($array)) {
38
      $raw = preg_replace('/}\s*{/', '},{', $raw);
39
      $raw = '[' . $raw . ']';
40
      $array = json_decode($raw, TRUE);
41
    }
42

    
43
    if (is_array($array)) {
44
      require_once 'jsonpath-0.8.1.php';
45

    
46
      $all_items = $this->jsonPath($array, $source_config['context']);
47
      $this->debug($all_items, 'context');
48
      unset($array);
49

    
50
      foreach ($all_items as $item) {
51
        $parsed_item = $variables = array();
52
        foreach ($source_config['sources'] as $source => $query) {
53
          $parsed = $this->parseSourceElement($item, $query, $source);
54
          // Avoid null values.
55
          if (isset($parsed)) {
56
            // Variable sunstitution can't handle arrays.
57
            if (!is_array($parsed)) {
58
              $variables['{' . $mappings[$source] . '}'] = $parsed;
59
            }
60
            else {
61
              $variables['{' . $mappings[$source] . '}'] = '';
62
            }
63
            $parsed_item[$source] = $parsed;
64
          }
65
        }
66
        $result->items[] = $parsed_item;
67
      }
68
    }
69
    else {
70
      throw new Exception(t('There was an error decoding the JSON document.'));
71
    }
72
    return $result;
73
  }
74

    
75
  /**
76
   * Utilizes the jsonPath function from jsonpath-0.8.1.php
77
   *
78
   * jsonPath returns false if the expression returns zero results and that will
79
   * mess up our for loops, so return an empty array instead.
80
   *
81
   * @todo
82
   *   Firgure out error handling.
83
   * @param $array
84
   *   The input array to parse
85
   * @$expression
86
   *   The JSONPath expression.
87
   * @return array
88
   *   Returns an array that is the output of jsonPath
89
   */
90
  private function jsonPath($array, $expression) {
91
    $result = jsonPath($array, $expression);
92
    return ($result === FALSE) ? array() : $result;
93
  }
94

    
95
  /**
96
   * Parses one item from the context array.
97
   *
98
   * @param $item
99
   *   A PHP array.
100
   * @param $query
101
   *   A JSONPath query.
102
   * @return array
103
   *   An array containing the results of the query.
104
   */
105
  protected function parseSourceElement($item, $query, $source) {
106
    if (empty($query)) {
107
      return;
108
    }
109
    $results = $this->jsonPath($item, $query);
110
    $this->debug($results, $source);
111
    unset($item);
112

    
113
    /**
114
     * If there is one result, return it directly.  If there are no results,
115
     * return. Otherwise return the results.
116
     */
117
    if (count($results) === 1) {
118
      return $results[0];
119
    }
120
    if (count($results) === 0) {
121
      return;
122
    }
123
    return $results;
124
  }
125

    
126
  /**
127
   * Source form.
128
   */
129
  public function sourceForm($source_config) {
130
    $form = array();
131

    
132
    if (empty($source_config)) {
133
      $source_config = $this->config;
134
    }
135
    // Add extensions that might get importerd.
136
    $fetcher = feeds_importer($this->id)->fetcher;
137
    if (isset($fetcher->config['allowed_extensions'])) {
138
      if (strpos($fetcher->config['allowed_extensions'], 'json') === FALSE) {
139
        $fetcher->config['allowed_extensions'] .= ' json';
140
      }
141
    }
142
    $mappings_ = feeds_importer($this->id)->processor->config['mappings'];
143
    $uniques = $mappings = array();
144

    
145
    foreach ($mappings_ as $mapping) {
146
      if (strpos($mapping['source'], 'jsonpath_parser:') === 0) {
147
        $mappings[$mapping['source']] = $mapping['target'];
148
        if ($mapping['unique']) {
149
          $uniques[] = $mapping['target'];
150
        }
151
      }
152
    }
153
    $form['jsonpath'] = array(
154
      '#type' => 'fieldset',
155
      '#title' => t('JSONPath Parser Settings'),
156
      '#collapsible' => TRUE,
157
      '#collapsed' => TRUE,
158
      '#tree' => TRUE,
159
    );
160
    if (empty($mappings)) {
161
      $form['jsonpath']['error_message']['#markup'] = t('FeedsJSONPathParser: No mappings were defined.');
162
      return $form;
163
    }
164
    $form['jsonpath']['context'] = array(
165
      '#type' => 'textfield',
166
      '#title' => t('Context'),
167
      '#required' => TRUE,
168
      '#description' => t('This is the base query, all other queries will execute in this context.'),
169
      '#default_value' => isset($source_config['context']) ? $source_config['context'] : '',
170
      '#maxlength' => 1024,
171
      '#size' => 80,
172
    );
173
    $form['jsonpath']['sources'] = array(
174
      '#type' => 'fieldset',
175
    );
176
    if (!empty($uniques)) {
177
      $items = array(
178
        format_plural(count($uniques),
179
          t('Field <strong>!column</strong> is mandatory and considered unique: only one item per !column value will be created.',
180
            array('!column' => implode(', ', $uniques))),
181
          t('Fields <strong>!columns</strong> are mandatory and values in these columns are considered unique: only one entry per value in one of these columns will be created.',
182
            array('!columns' => implode(', ', $uniques)))),
183
      );
184
      $form['jsonpath']['sources']['help']['#markup'] = '<div class="help">' . theme('item_list', array('items' => $items)) . '</div>';
185
    }
186
    $variables = array();
187
    foreach ($mappings as $source => $target) {
188
      $form['jsonpath']['sources'][$source] = array(
189
        '#type' => 'textfield',
190
        '#title' => $target,
191
        '#description' => t('The JSONPath expression to execute.'),
192
        '#default_value' => isset($source_config['sources'][$source]) ? $source_config['sources'][$source] : '',
193
        '#maxlength' => 1024,
194
        '#size' => 80,
195
      );
196
      if (!empty($variables)) {
197
        $form['jsonpath']['sources'][$source]['#description'] .= '<br>' . t('The variables '. implode(', ', $variables). ' are availliable for replacement.');
198
      }
199
      $variables[] = '{' . $target . '}';
200
    }
201
    $form['jsonpath']['debug'] = array(
202
      '#type' => 'fieldset',
203
      '#title' => t('Debug'),
204
      '#collapsible' => TRUE,
205
      '#collapsed' => TRUE,
206
    );
207
    $form['jsonpath']['debug']['options'] = array(
208
      '#type' => 'checkboxes',
209
      '#title' => t('Debug query'),
210
      '#options' => array_merge(array('context' => 'context'), $mappings),
211
      '#default_value' => isset($source_config['debug']['options']) ? $source_config['debug']['options'] : array(),
212
    );
213
    return $form;
214
  }
215

    
216
  /**
217
   * Override parent::configForm().
218
   */
219
  public function configForm(&$form_state) {
220
    $form = $this->sourceForm($this->config);
221
    $form['jsonpath']['context']['#required'] = FALSE;
222
    $form['jsonpath']['#collapsed'] = FALSE;
223
    return $form;
224
  }
225

    
226
  /**
227
  * Override parent::getMappingSources().
228
  */
229
  public function getMappingSources() {
230
    $mappings = $this->filterMappings(feeds_importer($this->id)->processor->config['mappings']);
231
    $next = 0;
232
    if (!empty($mappings)) {
233
      $last_mapping = end(array_keys($mappings));
234
      $next = explode(':', $last_mapping);
235
      $next = $next[1] + 1;
236
    }
237
    return array(
238
      'jsonpath_parser:' . $next => array(
239
        'name' => t('JSONPath Expression'),
240
        'description' => t('Allows you to configure n JSONPath expression that will populate this field.'),
241
      ),
242
    ) + parent::getMappingSources();
243
  }
244

    
245
  public function sourceDefaults() {
246
    return array();
247
  }
248

    
249
  /**
250
   * Define defaults.
251
   */
252
  public function configDefaults() {
253
    return array(
254
      'context' => '',
255
      'sources' => array(),
256
      'debug' => array(),
257
    );
258
  }
259

    
260
  /**
261
   * Override parent::sourceFormValidate().
262
   *
263
   * If the values of this source are the same as the base config we set them to
264
   * blank to that the values will be inherited from the importer defaults.
265
   *
266
   * @param &$values
267
   *   The values from the form to validate, passed by reference.
268
   */
269
  public function sourceFormValidate(&$values) {
270
    $values = $values['jsonpath'];
271
    asort($values);
272
    asort($this->config);
273
    if ($values === $this->config) {
274
      $values = array();
275
      return;
276
    }
277
    $this->configFormValidate($values);
278
  }
279

    
280
  /**
281
   * Override parent::sourceFormValidate().
282
   */
283
  public function configFormValidate(&$values) {
284
    if (isset($values['jsonpath'])) {
285
      $values = $values['jsonpath'];
286
    }
287
    $values['context'] = trim($values['context']);
288
    foreach ($values['sources'] as &$source) {
289
      $source = trim($source);
290
    }
291
  }
292

    
293
  /**
294
   * Filters mappings, returning the ones that belong to us.
295
   */
296
  private function filterMappings($mappings) {
297
    $our_mappings = array();
298
    foreach ($mappings as $mapping) {
299
      if (strpos($mapping['source'], 'jsonpath_parser:') === 0) {
300
        $our_mappings[$mapping['source']] = $mapping['target'];
301
      }
302
    }
303
    return $our_mappings;
304
  }
305

    
306
  private function debug($item, $source) {
307
    if (in_array($source, $this->debug)) {
308
      $o = '<ul>';
309
      foreach ($item as $i) {
310
        $o .= '<li>' . check_plain(var_export($i, TRUE)) . '</li>';
311
      }
312
      $o .= '</ul>';
313
      drupal_set_message($source . ':' . $o);
314
    }
315
  }
316
}