You are here

public function BlockParser::nextToken in Gutenberg 8.2

Scans the document from where we last left off.

And finds the next valid token to parse if it exists.

@since 3.8.0 @since 4.6.1 fixed a bug in attribute parsing which caused catastrophic backtracking on invalid block comments @internal

Return value

array The type of the find: kind of find, block information, attributes.

1 call to BlockParser::nextToken()
BlockParser::proceed in src/Parser/BlockParser.php
Processes the next token from the input document.

File

src/Parser/BlockParser.php, line 263

Class

BlockParser
Class BlockParser.

Namespace

Drupal\gutenberg\Parser

Code

public function nextToken() {
  $matches = NULL;

  /*
   * aye the magic
   * we're using a single RegExp to tokenize the block comment delimiters
   * we're also using a trick here because the only difference between a
   * block opener and a block closer is the leading `/` before `wp:` (and
   * a closer has no attributes). we can trap them both and process the
   * match back in PHP to see which one it was.
   */
  $has_match = preg_match('/<!--\\s+(?P<closer>\\/)?wp:(?P<namespace>[a-z][a-z0-9_-]*\\/)?(?P<name>[a-z][a-z0-9_-]*)\\s+(?P<attrs>{(?:(?:[^}]+|}+(?=})|(?!}\\s+\\/?-->).)*+)?}\\s+)?(?P<void>\\/)?-->/s', $this->document, $matches, PREG_OFFSET_CAPTURE, $this->offset);

  // If we get here we probably have catastrophic backtracking or
  // out-of-memory in the PCRE.
  if (FALSE === $has_match) {
    return [
      'no-more-tokens',
      NULL,
      NULL,
      NULL,
      NULL,
    ];
  }

  // We have no more tokens.
  if (0 === $has_match) {
    return [
      'no-more-tokens',
      NULL,
      NULL,
      NULL,
      NULL,
    ];
  }
  list($match, $started_at) = $matches[0];
  $length = strlen($match);
  $is_closer = isset($matches['closer']) && -1 !== $matches['closer'][1];
  $is_void = isset($matches['void']) && -1 !== $matches['void'][1];
  $namespace = $matches['namespace'];
  $namespace = isset($namespace) && -1 !== $namespace[1] ? $namespace[0] : 'core/';
  $name = $namespace . $matches['name'][0];
  $has_attrs = isset($matches['attrs']) && -1 !== $matches['attrs'][1];

  /*
   * Fun fact! It's not trivial in PHP to create "an empty associative array"
   * since all arrays are associative arrays. If we use `array()` we get a
   * JSON `[]`
   */
  $attrs = $has_attrs ? json_decode($matches['attrs'][0], TRUE) : $this->emptyAttrs;

  /*
   * This state isn't allowed
   * This is an error
   */
  if ($is_closer && ($is_void || $has_attrs)) {

    // We can ignore them since they don't hurt anything.
  }
  if ($is_void) {
    return [
      'void-block',
      $name,
      $attrs,
      $started_at,
      $length,
    ];
  }
  if ($is_closer) {
    return [
      'block-closer',
      $name,
      NULL,
      $started_at,
      $length,
    ];
  }
  return [
    'block-opener',
    $name,
    $attrs,
    $started_at,
    $length,
  ];
}