You are here

class JmesPathParser in Feeds extensible parsers 8

Defines a JSON parser using JMESPath.

Plugin annotation


@FeedsParser(
  id = "jmespath",
  title = @Translation("JSON JMESPath"),
  description = @Translation("Parse JSON with JMESPath.")
)

Hierarchy

Expanded class hierarchy of JmesPathParser

1 file declares its use of JmesPathParser
JmesPathParserTest.php in tests/src/Unit/Feeds/Parser/JmesPathParserTest.php

File

src/Feeds/Parser/JmesPathParser.php, line 24

Namespace

Drupal\feeds_ex\Feeds\Parser
View source
class JmesPathParser extends JsonParserBase {

  /**
   * 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;

  /**
   * A factory to generate JMESPath runtime objects.
   *
   * @var \Drupal\feeds_ex\JmesRuntimeFactoryInterface
   */
  protected $runtimeFactory;

  /**
   * {@inheritdoc}
   */
  public function __construct(array $configuration, $plugin_id, array $plugin_definition, JsonUtility $utility) {
    parent::__construct($configuration, $plugin_id, $plugin_definition, $utility);

    // Set default factory.
    $this->runtimeFactory = new JmesRuntimeFactory();
  }

  /**
   * Sets the factory to use for creating JMESPath Runtime objects.
   *
   * This is useful in unit tests.
   *
   * @param \Drupal\feeds_ex\JmesRuntimeFactoryInterface $factory
   *   The factory to use.
   */
  public function setRuntimeFactory(JmesRuntimeFactoryInterface $factory) {
    $this->runtimeFactory = $factory;
  }

  /**
   * 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->runtimeFactory
        ->createRuntime();
    }

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

  /**
   * {@inheritdoc}
   */
  protected function executeContext(FeedInterface $feed, FetcherResultInterface $fetcher_result, StateInterface $state) {
    $parsed = $this->utility
      ->decodeJsonObject($this
      ->prepareRaw($fetcher_result));
    $parsed = $this
      ->search($this->configuration['context']['value'], $parsed);
    if (!is_array($parsed) && !is_object($parsed)) {
      throw new \RuntimeException($this
        ->t('The context expression must return an object or array.'));
    }

    // If an object is returned, consider it one item.
    if (is_object($parsed)) {
      return [
        $parsed,
      ];
    }
    if (!$state->total) {
      $state->total = count($parsed);
    }
    $start = (int) $state->pointer;
    $state->pointer = $start + $this->configuration['line_limit'];
    return array_slice($parsed, $start, $this->configuration['line_limit']);
  }

  /**
   * {@inheritdoc}
   */
  protected function cleanUp(FeedInterface $feed, ParserResultInterface $result, StateInterface $state) {

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

    // Calculate progress.
    $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, transform it to a runtime
      // exception, so that it gets properly catched by Feeds.
      throw new \RuntimeException($e
        ->getMessage());
    }
    if (is_object($result)) {
      $result = (array) $result;
    }
    if (!is_array($result)) {
      return $result;
    }
    $count = count($result);
    if ($count === 0) {
      return;
    }

    // Return a single value if there's only one value.
    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, []);
    } catch (SyntaxErrorException $e) {
      return $this
        ->formatSyntaxError($e
        ->getMessage());
    } catch (\RuntimeException $e) {
      if (strpos($e
        ->getMessage(), 'Argument 0') === 0) {

        // Ignore 'Argument 0 errors'.
        return;
      }

      // In all other cases, rethrow the exception.
      throw $e;
    }
  }

  /**
   * Formats a syntax error message with HTML.
   *
   * A syntax error message can be for example:
   * @code
   * items[].join(`__`,[title,description)
   *                                     ^
   * @endcode
   *
   * @param string $message
   *   The message to format.
   *
   * @return string
   *   The HTML formatted message.
   */
  protected function formatSyntaxError($message) {
    $message = trim($message);
    $message = nl2br($message);

    // Spaces in the error message can be used to point to a specific
    // character in the line above.
    $message = str_replace('  ', '  ', $message);

    // Remove newlines to make testing easier.
    $message = str_replace("\n", '', $message);

    // Return the message between <pre>-tags so that the error message can be
    // displayed correctly. Else the double spaces may not get displayed.
    return '<pre>' . $message . '</pre>';
  }

  /**
   * {@inheritdoc}
   */
  protected function getErrors() {
    if (!function_exists('json_last_error')) {
      return [];
    }
    if (!($error = json_last_error())) {
      return [];
    }
    $message = [
      'message' => $this->utility
        ->translateError($error),
      'variables' => [],
      'severity' => RfcLogLevel::ERROR,
    ];
    return [
      $message,
    ];
  }

  /**
   * {@inheritdoc}
   */
  protected function loadLibrary() {
    if (!class_exists('JmesPath\\AstRuntime')) {
      throw new \RuntimeException($this
        ->t('The JMESPath library is not installed.'));
    }
  }

}

Members

Namesort descending Modifiers Type Description Overrides
DependencySerializationTrait::$_entityStorages protected property An array of entity type IDs keyed by the property name of their storages.
DependencySerializationTrait::$_serviceIds protected property An array of service IDs keyed by property name used for serialization.
DependencySerializationTrait::__sleep public function 1
DependencySerializationTrait::__wakeup public function 2
DependencyTrait::$dependencies protected property The object's dependencies.
DependencyTrait::addDependencies protected function Adds multiple dependencies.
DependencyTrait::addDependency protected function Adds a dependency.
JmesPathParser::$runtime protected property The JMESPath parser.
JmesPathParser::$runtimeFactory protected property A factory to generate JMESPath runtime objects.
JmesPathParser::cleanUp protected function Allows subclasses to cleanup after parsing. Overrides ParserBase::cleanUp 1
JmesPathParser::executeContext protected function Returns rows to be parsed. Overrides ParserBase::executeContext
JmesPathParser::executeSourceExpression protected function Executes a single source expression. Overrides ParserBase::executeSourceExpression
JmesPathParser::formatSyntaxError protected function Formats a syntax error message with HTML.
JmesPathParser::getErrors protected function Returns the errors after parsing. Overrides ParserBase::getErrors
JmesPathParser::loadLibrary protected function Loads the necessary library. Overrides ParserBase::loadLibrary
JmesPathParser::search protected function Returns data from the input array that matches a JMESPath expression.
JmesPathParser::setRuntimeFactory public function Sets the factory to use for creating JMESPath Runtime objects.
JmesPathParser::validateExpression protected function Validates an expression. Overrides ParserBase::validateExpression
JmesPathParser::__construct public function Constructs a JsonParserBase object. Overrides JsonParserBase::__construct
JsonParserBase::$utility protected property The JSON helper class.
JsonParserBase::create public static function Creates an instance of the plugin. Overrides ContainerFactoryPluginInterface::create
JsonParserBase::startErrorHandling protected function Starts internal error handling. Overrides ParserBase::startErrorHandling
MessengerTrait::$messenger protected property The messenger. 29
MessengerTrait::messenger public function Gets the messenger. 29
MessengerTrait::setMessenger public function Sets the messenger.
ParserBase::$encoder protected property The encoder used to convert encodings.
ParserBase::$encoderClass protected property The class used as the text encoder. 1
ParserBase::$feedsExMessenger protected property The messenger, for compatibility with Drupal 8.5.
ParserBase::$htmlTags protected static property The default list of HTML tags allowed by Xss::filter().
ParserBase::buildConfigurationForm public function Form constructor. Overrides PluginFormInterface::buildConfigurationForm 1
ParserBase::buildFeedForm public function
ParserBase::configFormTableColumn protected function Returns a form element for a specific column. 1
ParserBase::configFormTableHeader protected function Returns the list of table headers. 1
ParserBase::configFormValidate public function 1
ParserBase::configSourceDescription protected function Returns the description for single source. 1
ParserBase::configSourceLabel protected function Returns the label for single source. Overrides ParserBase::configSourceLabel 1
ParserBase::debug protected function Renders our debug messages into a list.
ParserBase::defaultConfiguration public function Gets default configuration for this plugin. Overrides PluginBase::defaultConfiguration 1
ParserBase::executeSources protected function Executes the source expressions.
ParserBase::getEncoder public function Returns the encoder.
ParserBase::getFormHeader protected function Returns the configuration form table header.
ParserBase::getMappingSources public function Declare the possible mapping sources that this parser produces. Overrides ParserInterface::getMappingSources
ParserBase::getMessenger public function Gets the messenger.
ParserBase::hasConfigForm public function 1
ParserBase::hasConfigurableContext protected function Returns whether or not this parser uses a context query. 2
ParserBase::hasSourceConfig public function
ParserBase::mappingFormAlter public function Alter mapping form. Overrides ParserBase::mappingFormAlter
ParserBase::mappingFormSubmit public function Submit handler for the mapping form. Overrides ParserBase::mappingFormSubmit
ParserBase::mappingFormValidate public function Validate handler for the mapping form. Overrides ParserBase::mappingFormValidate
ParserBase::parse public function Parses content returned by fetcher. Overrides ParserInterface::parse
ParserBase::parseItems protected function Performs the actual parsing. 2
ParserBase::prepareExpressions protected function Prepares the expressions for parsing.
ParserBase::prepareRaw protected function Prepares the raw string for parsing.
ParserBase::prepareVariables protected function Prepares the variable map used to substitution.
ParserBase::printErrors protected function Prints errors to the screen.
ParserBase::setEncoder public function Sets the encoder.
ParserBase::setFeedsExMessenger public function Sets the messenger.
ParserBase::setUp protected function Allows subclasses to prepare for parsing. 3
ParserBase::sourceDefaults public function
ParserBase::sourceFormValidate public function
ParserBase::sourceSave public function
ParserBase::stopErrorHandling protected function Stops internal error handling. 1
ParserBase::submitConfigurationForm public function Form submission handler. Overrides PluginFormInterface::submitConfigurationForm
ParserBase::validateConfigurationForm public function Form validation handler. Overrides PluginFormInterface::validateConfigurationForm
ParserBase::_buildConfigurationForm public function Builds configuration form for the parser settings.
PluginBase::$configuration protected property Configuration information passed into the plugin. 1
PluginBase::$feedType protected property The importer this plugin is working for.
PluginBase::$linkGenerator protected property The link generator.
PluginBase::$pluginDefinition protected property The plugin implementation definition. 1
PluginBase::$pluginId protected property The plugin_id.
PluginBase::$urlGenerator protected property The url generator.
PluginBase::calculateDependencies public function Calculates dependencies for the configured plugin. Overrides DependentPluginInterface::calculateDependencies 2
PluginBase::container private function Returns the service container.
PluginBase::defaultFeedConfiguration public function Returns default feed configuration. Overrides FeedsPluginInterface::defaultFeedConfiguration 3
PluginBase::DERIVATIVE_SEPARATOR constant A string which is used to separate base plugin IDs from the derivative ID.
PluginBase::getBaseId public function Gets the base_plugin_id of the plugin instance. Overrides DerivativeInspectionInterface::getBaseId
PluginBase::getConfiguration public function Gets this plugin's configuration. Overrides ConfigurableInterface::getConfiguration
PluginBase::getDerivativeId public function Gets the derivative_id of the plugin instance. Overrides DerivativeInspectionInterface::getDerivativeId
PluginBase::getPluginDefinition public function Gets the definition of the plugin implementation. Overrides PluginInspectionInterface::getPluginDefinition 3
PluginBase::getPluginId public function Gets the plugin_id of the plugin instance. Overrides PluginInspectionInterface::getPluginId
PluginBase::isConfigurable public function Determines if the plugin is configurable.
PluginBase::l protected function Renders a link to a route given a route name and its parameters.
PluginBase::linkGenerator protected function Returns the link generator service.
PluginBase::onFeedDeleteMultiple public function A feed is being deleted. 3
PluginBase::onFeedSave public function A feed is being saved.
PluginBase::onFeedTypeDelete public function The feed type is being deleted. 1
PluginBase::onFeedTypeSave public function The feed type is being saved. 1
PluginBase::pluginType public function Returns the type of plugin. Overrides FeedsPluginInterface::pluginType
PluginBase::setConfiguration public function Sets the configuration for this plugin instance. Overrides ConfigurableInterface::setConfiguration 1
PluginBase::url protected function Generates a URL or path for a specific route based on the given parameters.
PluginBase::urlGenerator protected function Returns the URL generator service.
StringTranslationTrait::$stringTranslation protected property The string translation service. 1
StringTranslationTrait::formatPlural protected function Formats a string containing a count of items.
StringTranslationTrait::getNumberOfPlurals protected function Returns the number of plurals supported by a given language.
StringTranslationTrait::getStringTranslation protected function Gets the string translation service.
StringTranslationTrait::setStringTranslation public function Sets the string translation service to use. 2
StringTranslationTrait::t protected function Translates a string to the current language or to a given language.