Skip to content

Commit

Permalink
Parser: Normalize data types and fix default implementation (#10107)
Browse files Browse the repository at this point in the history
* Parser: Normalize data types and fix default implementation

Resolves #10041
Resolves #10047

A few inconsistencies have remained in the grammar specification
concerning freeform blocks and blocks without attributes in the
block delimiters. Freeform blocks were returned without block
names and blocks without attributes returned `null` instead of
an empty set of attributes.

Further, the default parser implementation (from #8083) was
returning an array of block objects instead of an array of
generic arrays. This resulted in mismatches in PHP of accessing
properties with `$block[ 'attrs' ]` syntax vs `$block->attrs`
syntax.

In this patch I've updatd the specification to remove all of
the type ambiguity and have updated the default parser to match
it. After this patch every block should be accessible as a normal
array in PHP and have all properties: `blockName`, `attrs`,
`innerBlocks`, and `innerHTML`. If no attributes are specified
then `attrs` will be an empty set (in JavaScript `{}` and in
PHP `array()`).
  • Loading branch information
dmsnell authored Oct 6, 2018
1 parent 86dad5f commit 49b67d3
Show file tree
Hide file tree
Showing 57 changed files with 266 additions and 112 deletions.
6 changes: 4 additions & 2 deletions lib/blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,10 @@ function gutenberg_parse_blocks( $content ) {
if ( ! has_blocks( $content ) ) {
return array(
array(
'attrs' => array(),
'innerHTML' => $content,
'blockName' => null,
'attrs' => array(),
'innerBlocks' => array(),
'innerHTML' => $content,
),
);
}
Expand Down
35 changes: 25 additions & 10 deletions lib/parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -259,26 +259,26 @@ private function peg_f1($pre, $bs, $post) { return peg_join_blocks( $pre, $bs, $
private function peg_f2($blockName, $a) { return $a; }
private function peg_f3($blockName, $attrs) {
return array(
'blockName' => $blockName,
'attrs' => $attrs,
'blockName' => $blockName,
'attrs' => isset( $attrs ) ? $attrs : array(),
'innerBlocks' => array(),
'innerHTML' => '',
'innerHTML' => '',
);
}
private function peg_f4($s, $children, $e) {
list( $innerHTML, $innerBlocks ) = peg_array_partition( $children, 'is_string' );

return array(
'blockName' => $s['blockName'],
'attrs' => $s['attrs'],
'blockName' => $s['blockName'],
'attrs' => $s['attrs'],
'innerBlocks' => $innerBlocks,
'innerHTML' => implode( '', $innerHTML ),
'innerHTML' => implode( '', $innerHTML ),
);
}
private function peg_f5($blockName, $attrs) {
return array(
'blockName' => $blockName,
'attrs' => $attrs,
'attrs' => isset( $attrs ) ? $attrs : array(),
);
}
private function peg_f6($blockName) {
Expand Down Expand Up @@ -1461,7 +1461,12 @@ function peg_join_blocks( $pre, $tokens, $post ) {
$blocks = array();

if ( ! empty( $pre ) ) {
$blocks[] = array( 'attrs' => array(), 'innerHTML' => $pre );
$blocks[] = array(
'blockName' => null,
'attrs' => array(),
'innerBlocks' => array(),
'innerHTML' => $pre
);
}

foreach ( $tokens as $token ) {
Expand All @@ -1470,12 +1475,22 @@ function peg_join_blocks( $pre, $tokens, $post ) {
$blocks[] = $token;

if ( ! empty( $html ) ) {
$blocks[] = array( 'attrs' => array(), 'innerHTML' => $html );
$blocks[] = array(
'blockName' => null,
'attrs' => array(),
'innerBlocks' => array(),
'innerHTML' => $html
);
}
}

if ( ! empty( $post ) ) {
$blocks[] = array( 'attrs' => array(), 'innerHTML' => $post );
$blocks[] = array(
'blockName' => null,
'attrs' => array(),
'innerBlocks' => array(),
'innerHTML' => $post
);
}

return $blocks;
Expand Down
60 changes: 36 additions & 24 deletions packages/block-serialization-default-parser/parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ function __construct( $block, $token_start, $token_length, $prev_offset = null,
* Parses a document and constructs a list of parsed block objects
*
* @since 3.8.0
* @since 4.0.0 returns arrays not objects, all attributes are arrays
*/
class WP_Block_Parser {
/**
Expand Down Expand Up @@ -244,17 +245,14 @@ function proceed() {
*/
if ( 0 === $stack_depth ) {
if ( isset( $leading_html_start ) ) {
$this->output[] = array(
'attrs' => array(),
'innerHTML' => substr(
$this->document,
$leading_html_start,
$start_offset - $leading_html_start
),
);
$this->output[] = (array) self::freeform( substr(
$this->document,
$leading_html_start,
$start_offset - $leading_html_start
) );
}

$this->output[] = new WP_Block_Parser_Block( $block_name, $attrs, array(), '' );
$this->output[] = (array) new WP_Block_Parser_Block( $block_name, $attrs, array(), '' );
$this->offset = $start_offset + $token_length;
return true;
}
Expand Down Expand Up @@ -370,7 +368,14 @@ function next_token() {
$namespace = ( isset( $namespace ) && -1 !== $namespace[ 1 ] ) ? $namespace[ 0 ] : 'core/';
$name = $namespace . $matches[ 'name' ][ 0 ];
$has_attrs = isset( $matches[ 'attrs' ] ) && -1 !== $matches[ 'attrs' ][ 1 ];
$attrs = $has_attrs ? json_decode( $matches[ 'attrs' ][ 0 ] ) : null;

/*
* 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 ], /* as-associative */ true )
: json_decode( '{}', /* don't ask why, just verify in PHP */ false );

/*
* This state isn't allowed
Expand All @@ -391,6 +396,19 @@ function next_token() {
return array( 'block-opener', $name, $attrs, $started_at, $length );
}

/**
* Returns a new block object for freeform HTML
*
* @internal
* @since 3.9.0
*
* @param string $innerHTML HTML content of block
* @return WP_Block_Parser_Block freeform block object
*/
static function freeform( $innerHTML ) {
return new WP_Block_Parser_Block( null, array(), array(), $innerHTML );
}

/**
* Pushes a length of text from the input document
* to the output list as a freeform block
Expand All @@ -406,10 +424,7 @@ function add_freeform( $length = null ) {
return;
}

$this->output[] = array(
'attrs' => new stdClass(),
'innerHTML' => substr( $this->document, $this->offset, $length ),
);
$this->output[] = (array) self::freeform( substr( $this->document, $this->offset, $length ) );
}

/**
Expand All @@ -423,7 +438,7 @@ function add_freeform( $length = null ) {
* @param int $token_length byte length of entire block from start of opening token to end of closing token
* @param int|null $last_offset last byte offset into document if continuing form earlier output
*/
function add_inner_block(WP_Block_Parser_Block $block, $token_start, $token_length, $last_offset = null ) {
function add_inner_block( WP_Block_Parser_Block $block, $token_start, $token_length, $last_offset = null ) {
$parent = $this->stack[ count( $this->stack ) - 1 ];
$parent->block->innerBlocks[] = $block;
$parent->block->innerHTML .= substr( $this->document, $parent->prev_offset, $token_start - $parent->prev_offset );
Expand All @@ -446,16 +461,13 @@ function add_block_from_stack( $end_offset = null ) {
: substr( $this->document, $prev_offset );

if ( isset( $stack_top->leading_html_start ) ) {
$this->output[] = array(
'attrs' => array(),
'innerHTML' => substr(
$this->document,
$stack_top->leading_html_start,
$stack_top->token_start - $stack_top->leading_html_start
),
);
$this->output[] = (array) self::freeform( substr(
$this->document,
$stack_top->leading_html_start,
$stack_top->token_start - $stack_top->leading_html_start
) );
}

$this->output[] = $stack_top->block;
$this->output[] = (array) $stack_top->block;
}
}
25 changes: 8 additions & 17 deletions packages/block-serialization-default-parser/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ function Block( blockName, attrs, innerBlocks, innerHTML ) {
};
}

function Freeform( innerHTML ) {
return Block( null, {}, [], innerHTML );
}

function Frame( block, tokenStart, tokenLength, prevOffset, leadingHtmlStart ) {
return {
block,
Expand Down Expand Up @@ -78,10 +82,7 @@ function proceed() {
// in the top-level of the document
if ( 0 === stackDepth ) {
if ( null !== leadingHtmlStart ) {
output.push( {
attrs: {},
innerHTML: document.substr( leadingHtmlStart, startOffset - leadingHtmlStart ),
} );
output.push( Freeform( document.substr( leadingHtmlStart, startOffset - leadingHtmlStart ) ) );
}
output.push( Block( blockName, attrs, [], '' ) );
offset = startOffset + tokenLength;
Expand Down Expand Up @@ -196,7 +197,7 @@ function nextToken() {
const namespace = namespaceMatch || 'core/';
const name = namespace + nameMatch;
const hasAttrs = !! attrsMatch;
const attrs = hasAttrs ? parseJSON( attrsMatch ) : null;
const attrs = hasAttrs ? parseJSON( attrsMatch ) : {};

// This state isn't allowed
// This is an error
Expand All @@ -223,14 +224,7 @@ function addFreeform( rawLength ) {
return;
}

// why is this not a Frame? it's because the current grammar
// specifies an object that's different. we can update the
// specification and change here if we want to but for now we
// want this parser to be spec-compliant
output.push( {
attrs: {},
innerHTML: document.substr( offset, length ),
} );
output.push( Freeform( document.substr( offset, length ) ) );
}

function addInnerBlock( block, tokenStart, tokenLength, lastOffset ) {
Expand All @@ -253,10 +247,7 @@ function addBlockFromStack( endOffset ) {
}

if ( null !== leadingHtmlStart ) {
output.push( {
attrs: {},
innerHTML: document.substr( leadingHtmlStart, tokenStart - leadingHtmlStart ),
} );
output.push( Freeform( document.substr( leadingHtmlStart, tokenStart - leadingHtmlStart ) ) );
}

output.push( block );
Expand Down
43 changes: 30 additions & 13 deletions packages/block-serialization-spec-parser/grammar.pegjs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,12 @@ if ( ! function_exists( 'peg_join_blocks' ) ) {
$blocks = array();
if ( ! empty( $pre ) ) {
$blocks[] = array( 'attrs' => array(), 'innerHTML' => $pre );
$blocks[] = array(
'blockName' => null,
'attrs' => array(),
'innerBlocks' => array(),
'innerHTML' => $pre
);
}
foreach ( $tokens as $token ) {
Expand All @@ -80,12 +85,22 @@ if ( ! function_exists( 'peg_join_blocks' ) ) {
$blocks[] = $token;
if ( ! empty( $html ) ) {
$blocks[] = array( 'attrs' => array(), 'innerHTML' => $html );
$blocks[] = array(
'blockName' => null,
'attrs' => array(),
'innerBlocks' => array(),
'innerHTML' => $html
);
}
}
if ( ! empty( $post ) ) {
$blocks[] = array( 'attrs' => array(), 'innerHTML' => $post );
$blocks[] = array(
'blockName' => null,
'attrs' => array(),
'innerBlocks' => array(),
'innerHTML' => $post
);
}
return $blocks;
Expand All @@ -96,8 +111,10 @@ if ( ! function_exists( 'peg_join_blocks' ) ) {

function freeform( s ) {
return s.length && {
blockName: null,
attrs: {},
innerHTML: s
innerBlocks: [],
innerHTML: s,
};
}

Expand Down Expand Up @@ -180,16 +197,16 @@ Block_Void
{
/** <?php
return array(
'blockName' => $blockName,
'attrs' => $attrs,
'blockName' => $blockName,
'attrs' => isset( $attrs ) ? $attrs : array(),
'innerBlocks' => array(),
'innerHTML' => '',
'innerHTML' => '',
);
?> **/

return {
blockName: blockName,
attrs: attrs,
attrs: attrs || {},
innerBlocks: [],
innerHTML: ''
};
Expand All @@ -202,10 +219,10 @@ Block_Balanced
list( $innerHTML, $innerBlocks ) = peg_array_partition( $children, 'is_string' );
return array(
'blockName' => $s['blockName'],
'attrs' => $s['attrs'],
'blockName' => $s['blockName'],
'attrs' => $s['attrs'],
'innerBlocks' => $innerBlocks,
'innerHTML' => implode( '', $innerHTML ),
'innerHTML' => implode( '', $innerHTML ),
);
?> **/

Expand All @@ -230,13 +247,13 @@ Block_Start
/** <?php
return array(
'blockName' => $blockName,
'attrs' => $attrs,
'attrs' => isset( $attrs ) ? $attrs : array(),
);
?> **/

return {
blockName: blockName,
attrs: attrs
attrs: attrs || {}
};
}

Expand Down
Loading

0 comments on commit 49b67d3

Please sign in to comment.