You are here

public function MigrateJSONReader::next in Migrate 7.2

Implementation of Iterator::next().

Populates currentElement (the object being retrieved) and currentId (that object's unique identifier) from the specified JSON file. Sets both to NULL at end-of-file. Handles properly-formed JSON, as well as some improper coding (specifically that generated in Ning exports).

Return value

void

1 call to MigrateJSONReader::next()
MigrateJSONReader::rewind in plugins/sources/json.inc
Implementation of Iterator::rewind().

File

plugins/sources/json.inc, line 265
Support for migration from JSON sources.

Class

MigrateJSONReader
An iterator over a JSON file. As is, this assumes that the file is structured as an array of objects,…

Code

public function next() {
  migrate_instrument_start('MigrateJSONReader::next');
  $this->currentElement = $this->currentId = NULL;

  // Open the file and position it if necessary
  if (!$this->fileHandle) {
    $this->fileHandle = fopen($this->url, 'r');
    if (!$this->fileHandle) {
      Migration::displayMessage(t('Could not open JSON file !url', array(
        '!url' => $this->url,
      )));
      return;
    }

    // We're expecting an array of characters, so the first character should be [.
    $char = $this
      ->getNonBlank();

    // Ning exports are wrapped in bogus (), so skip a leading (
    if ($char == '(') {
      $char = $this
        ->getNonBlank();
    }
    if ($char != '[') {
      Migration::displayMessage(t('!url is not a JSON file containing an array of objects', array(
        '!url' => $this->url,
      )));
      return;
    }
  }

  // We expect to be positioned either at an object (beginning with {) or
  // the end of the file (we should see a ] indicating this). Or, an
  // object-separating comma, to be skipped. Note that this treats
  // commas as optional between objects, which helps with processing
  // malformed JSON with missing commas (as in Ning exports).
  $c = $this
    ->getNonBlank();
  if ($c == ',') {
    $c = $this
      ->getNonBlank();
  }
  elseif ($c == ']') {
    $c = $this
      ->getNonBlank();
    if ($c != '{') {
      $c = NULL;
    }
  }

  // We expect to be at the first character of an object now.
  if ($c == '{') {

    // Start building a JSON string for this object.
    $json = $c;

    // Look for the closing }, ignoring brackets in strings, tracking nested
    // brackets. Watch out for escaped quotes, but also note that \\" is not
    // an escaped quote.
    $depth = 1;
    $in_string = FALSE;
    $in_escape = FALSE;
    while (($c = fgetc($this->fileHandle)) !== FALSE) {
      $json .= $c;
      if ($in_string) {

        // Quietly accept an escaped character
        if ($in_escape) {
          $in_escape = FALSE;
        }
        else {
          switch ($c) {

            // Unescaped " means end of string
            case '"':
              $in_string = FALSE;
              break;

            // Unescaped \\ means start of escape
            case '\\':
              $in_escape = TRUE;
              break;
          }
        }
      }
      else {

        // Outside of strings, recognize {} as depth changes, " as start of
        // string.
        switch ($c) {
          case '{':
            $depth++;
            break;
          case '}':
            $depth--;
            break;
          case '"':
            $in_string = TRUE;
            break;
        }

        // We've found our match, exit the loop.
        if ($depth < 1) {
          break;
        }
      }
    }

    // Turn the JSON string into an object.
    $this->currentElement = json_decode($json);
    $this->currentId = $this->currentElement->{$this->idField};
  }
  else {
    $this->currentElement = NULL;
    $this->currentId = NULL;
  }
  migrate_instrument_stop('MigrateJSONReader::next');
}