You are here

class FeedsExJmesPath in Feeds extensible parsers 7

Same name and namespace in other branches
  1. 7.2 src/FeedsExJmesPath.inc \FeedsExJmesPath

Parses JSON documents with JMESPath.

Hierarchy

Expanded class hierarchy of FeedsExJmesPath

2 string references to 'FeedsExJmesPath'
FeedsExJmesPath.test in src/Tests/FeedsExJmesPath.test
feeds_ex_feeds_plugins in ./feeds_ex.feeds.inc
Implements hook_feeds_plugins().

File

src/FeedsExJmesPath.inc, line 22
Contains FeedsExJmesPath.

View source
class FeedsExJmesPath extends FeedsExBase {

  /**
   * The JMESPath parser.
   *
   * This is an object with an __invoke() method.
   *
   * @var object
   *
   * @todo add interface so checking for an object with an __invoke() method
   * becomes explicit?
   */
  protected $runtime;

  /**
   * Returns the compilation directory.
   *
   * @return string
   *   The directory JmesPath uses to store generated code.
   */
  protected function getCompileDirectory() {

    // Look for a previous directory.
    $directory = variable_get('feeds_ex_jmespath_compile_dir');

    // The temp directory doesn't exist, or has moved.
    if (!$this
      ->validateCompileDirectory($directory)) {
      $directory = $this
        ->generateCompileDirectory();
      variable_set('feeds_ex_jmespath_compile_dir', $directory);

      // Creates the directory with the correct perms. We don't check the
      // return value since if it didn't work, there's nothing we can do. We
      // just fallback to the AstRuntime anyway.
      $this
        ->validateCompileDirectory($directory);
    }
    return $directory;
  }

  /**
   * Generates a directory path to store auto-generated PHP files.
   *
   * @return string
   *   A temp directory path.
   */
  protected function generateCompileDirectory() {

    // A random prefix to store the generated files.
    $prefix = drupal_base64_encode(drupal_random_bytes(40));
    return file_directory_temp() . '/' . $prefix . '_feeds_ex_jmespath_dir';
  }

  /**
   * Validates that a compile directory exists and is valid.
   *
   * @param string $directory
   *   A directory path.
   *
   * @return bool
   *   True if the directory exists and is writable, false if not.
   */
  protected function validateCompileDirectory($directory) {
    if (!$directory) {
      return FALSE;
    }
    return file_prepare_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
  }

  /**
   * Returns data from the input array that matches a JMESPath expression.
   *
   * @param string $expression
   *   JMESPath expression to evaluate.
   * @param mixed $data
   *   JSON-like data to search.
   *
   * @return mixed|null
   *   Returns the matching data or null.
   */
  protected function search($expression, $data) {
    if (!isset($this->runtime)) {
      $this->runtime = $this
        ->createRuntime($this
        ->getCompileDirectory());
    }

    // Stupid PHP.
    $runtime = $this->runtime;
    return $runtime($expression, $data);
  }

  /**
   * Creates a runtime object.
   *
   * Checks for different versions of JMESPath.php.
   *
   * @param string $directory
   *   The compile directory.
   *
   * @return object
   *   An invokable runtime object.
   */
  protected function createRuntime($directory) {

    // Version 2.
    if (class_exists('JmesPath\\AstRuntime')) {
      try {
        $runtime = new CompilerRuntime2($directory);
      } catch (RuntimeException $e) {
        $runtime = new AstRuntime2();
      }
    }
    elseif (class_exists('JmesPath\\Runtime\\AstRuntime')) {
      try {
        $runtime = new CompilerRuntime1(array(
          'dir' => $directory,
        ));
      } catch (RuntimeException $e) {
        $runtime = new AstRuntime1();
      }
      $runtime = new FeedsExJmesPathV1Wrapper($runtime);
    }
    else {
      throw new RuntimeException(t('JMESPath.php is not installed correctly.'));
    }
    return $runtime;
  }

  /**
   * {@inheritdoc}
   */
  protected function executeContext(FeedsSource $source, FeedsFetcherResult $fetcher_result) {
    $parsed = FeedsExJsonUtility::decodeJsonObject($this
      ->prepareRaw($fetcher_result));
    $parsed = $this
      ->search($this->config['context']['value'], $parsed);
    if (!is_array($parsed) && !is_object($parsed)) {
      throw new RuntimeException(t('The context expression must return an object or array.'));
    }

    // If an object is returned, consider it one item.
    if (is_object($parsed)) {
      return array(
        $parsed,
      );
    }
    $state = $source
      ->state(FEEDS_PARSE);
    if (!$state->total) {
      $state->total = count($parsed);
    }
    $start = (int) $state->pointer;
    $state->pointer = $start + $source->importer
      ->getLimit();
    return array_slice($parsed, $start, $source->importer
      ->getLimit());
  }

  /**
   * {@inheritdoc}
   */
  protected function cleanUp(FeedsSource $source, FeedsParserResult $result) {

    // @todo Verify if this is necessary. Not sure if the runtime keeps a
    // reference to the input data.
    unset($this->runtime);

    // Calculate progress.
    $state = $source
      ->state(FEEDS_PARSE);
    $state
      ->progress($state->total, $state->pointer);
  }

  /**
   * {@inheritdoc}
   */
  protected function executeSourceExpression($machine_name, $expression, $row) {
    try {
      $result = $this
        ->search($expression, $row);
    } catch (Exception $e) {

      // There was an error executing this expression, nothing we can do about
      // it.
      return;
    }
    if (is_object($result)) {
      $result = (array) $result;
    }
    if (!is_array($result)) {
      return $result;
    }
    $count = count($result);
    if ($count === 0) {
      return;
    }
    return count($result) === 1 ? reset($result) : array_values($result);
  }

  /**
   * {@inheritdoc}
   */
  protected function validateExpression(&$expression) {
    $expression = trim($expression);
    if (!strlen($expression)) {
      return;
    }
    try {
      $this
        ->search($expression, array());
    } catch (SyntaxErrorException $e) {

      // Remove newlines after nl2br() to make testing easier.
      return str_replace("\n", '', nl2br(check_plain(trim($e
        ->getMessage()))));
    } catch (Exception $e) {

      // This is a problem executing the query, which we don't worry about.
    }
  }

  /**
   * {@inheritdoc}
   */
  protected function startErrorHandling() {

    // Clear the json errors from previous parsing.
    json_decode('{}');
  }

  /**
   * {@inheritdoc}
   */
  protected function getErrors() {
    if (!function_exists('json_last_error')) {
      return array();
    }
    if (!($error = json_last_error())) {
      return array();
    }
    $message = array(
      'message' => FeedsExJsonUtility::translateError($error),
      'variables' => array(),
      'severity' => WATCHDOG_ERROR,
    );
    return array(
      $message,
    );
  }

  /**
   * {@inheritdoc}
   */
  protected function loadLibrary() {
    if (!FeedsExJsonUtility::jmesPathParserInstalled()) {
      throw new RuntimeException(t('The JMESPath library is not installed.'));
    }
  }

}

Members

Namesort descending Modifiers Type Description Overrides
FeedsExBase::$encoder protected property The encoder used to convert encodings.
FeedsExBase::$encoderClass protected property The class used as the text encoder. 1
FeedsExBase::$messenger protected property The object used to display messages to the user.
FeedsExBase::configDefaults public function 1
FeedsExBase::configForm public function 1
FeedsExBase::configFormTableColumn protected function Returns a form element for a specific column. 1
FeedsExBase::configFormTableHeader protected function Reuturns the list of table headers. 1
FeedsExBase::configFormValidate public function 1
FeedsExBase::debug protected function Renders our debug messages into a list.
FeedsExBase::executeSources protected function Executes the source expressions.
FeedsExBase::getEncoder public function Returns the encoder.
FeedsExBase::getFormHeader protected function Returns the configuration form table header.
FeedsExBase::getMappingSources public function
FeedsExBase::getMessenger public function Returns the messenger.
FeedsExBase::hasConfigForm public function
FeedsExBase::hasConfigurableContext protected function Returns whether or not this parser uses a context query. 2
FeedsExBase::hasSourceConfig public function
FeedsExBase::logErrors protected function Logs errors.
FeedsExBase::parse public function
FeedsExBase::parseItems protected function Performs the actual parsing. 2
FeedsExBase::prepareExpressions protected function Prepares the expressions for parsing.
FeedsExBase::prepareRaw protected function Prepares the raw string for parsing.
FeedsExBase::prepareVariables protected function Prepares the variable map used to substitution.
FeedsExBase::printErrors protected function Prints errors to the screen.
FeedsExBase::setEncoder public function Sets the encoder.
FeedsExBase::setMessenger public function Sets the messenger to be used to display messages.
FeedsExBase::setUp protected function Allows subclasses to prepare for parsing. 3
FeedsExBase::sourceDefaults public function
FeedsExBase::sourceForm public function
FeedsExBase::sourceFormValidate public function
FeedsExBase::sourceSave public function
FeedsExBase::stopErrorHandling protected function Stops internal error handling. 1
FeedsExJmesPath::$runtime protected property The JMESPath parser.
FeedsExJmesPath::cleanUp protected function Allows subclasses to cleanup after parsing. Overrides FeedsExBase::cleanUp 1
FeedsExJmesPath::createRuntime protected function Creates a runtime object.
FeedsExJmesPath::executeContext protected function Returns rows to be parsed. Overrides FeedsExBase::executeContext
FeedsExJmesPath::executeSourceExpression protected function Executes a single source expression. Overrides FeedsExBase::executeSourceExpression
FeedsExJmesPath::generateCompileDirectory protected function Generates a directory path to store auto-generated PHP files.
FeedsExJmesPath::getCompileDirectory protected function Returns the compilation directory.
FeedsExJmesPath::getErrors protected function Returns the errors after parsing. Overrides FeedsExBase::getErrors
FeedsExJmesPath::loadLibrary protected function Loads the necessary library. Overrides FeedsExBase::loadLibrary
FeedsExJmesPath::search protected function Returns data from the input array that matches a JMESPath expression.
FeedsExJmesPath::startErrorHandling protected function Starts internal error handling. Overrides FeedsExBase::startErrorHandling
FeedsExJmesPath::validateCompileDirectory protected function Validates that a compile directory exists and is valid.
FeedsExJmesPath::validateExpression protected function Validates an expression. Overrides FeedsExBase::validateExpression