From 6c5f63b4b992c8214bfca56e56e4920fa48acaf5 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Wed, 3 May 2017 15:33:05 +0100 Subject: [PATCH 1/3] Parser: Use an HTML like attribute syntax for comment attributes --- blocks/api/parser.js | 22 +++++++++++----------- blocks/api/post.pegjs | 4 ++-- blocks/api/test/parser.js | 3 ++- post-content.js | 2 +- 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/blocks/api/parser.js b/blocks/api/parser.js index 648f0bc363c673..dcfb18b2e88e21 100644 --- a/blocks/api/parser.js +++ b/blocks/api/parser.js @@ -99,7 +99,7 @@ export function parseWithTinyMCE( content ) { // In : // Out : content = content.replace( - //g, + //g, function( match, closingSlash, slug, attributes ) { if ( closingSlash ) { return ''; @@ -166,15 +166,15 @@ export function parseWithTinyMCE( content ) { }, {} ); // Retrieve the block attributes from the original delimiters - const blockAttributes = unescape( nodeAttributes.attributes || '' ) - .split( /\s+/ ) - .reduce( ( memo, attrString ) => { - const pieces = attrString.match( /^([a-z0-9_-]+):(.*)$/ ); - if ( pieces ) { - memo[ pieces[ 1 ] ] = pieces[ 2 ]; - } - return memo; - }, {} ); + const attributesMatcher = /([a-z0-9_-]+)="([^"]*)"/g; + const blockAttributes = {}; + let match; + do { + match = attributesMatcher.exec( unescape( nodeAttributes.attributes || '' ) ); + if ( match ) { + blockAttributes[ match[ 1 ] ] = match[ 2 ]; + } + } while ( !! match ); // Try to create the block const block = createBlockWithFallback( @@ -221,4 +221,4 @@ export function parseWithGrammar( content ) { }, [] ); } -export default parseWithTinyMCE; +export default parseWithGrammar; diff --git a/blocks/api/post.pegjs b/blocks/api/post.pegjs index 886137c830cf8e..696ecde0dfe30e 100644 --- a/blocks/api/post.pegjs +++ b/blocks/api/post.pegjs @@ -49,7 +49,7 @@ WP_Block_Attribute_List }, {} ) } WP_Block_Attribute - = name:WP_Block_Attribute_Name ":" value:WP_Block_Attribute_Value + = name:WP_Block_Attribute_Name '="' value:WP_Block_Attribute_Value '"' { return { name: name, value: value }; } WP_Block_Attribute_Name @@ -68,7 +68,7 @@ ASCII_AlphaNumeric / Special_Chars WP_Block_Attribute_Value_Char - = [^ \t\r\n] + = [^"] ASCII_Letter = [a-zA-Z] diff --git a/blocks/api/test/parser.js b/blocks/api/test/parser.js index f752390139fb1a..58d8b9b08b5879 100644 --- a/blocks/api/test/parser.js +++ b/blocks/api/test/parser.js @@ -150,7 +150,7 @@ describe( 'block parser', () => { } ); const parsed = parse( - '' + + '' + 'Brisket' + '' ); @@ -160,6 +160,7 @@ describe( 'block parser', () => { expect( parsed[ 0 ].attributes ).to.eql( { content: 'Brisket', smoked: 'yes', + url: 'http://google.com' } ); expect( parsed[ 0 ].uid ).to.be.a( 'string' ); } ); diff --git a/post-content.js b/post-content.js index bbadbcddb7b804..dc4371e22288be 100644 --- a/post-content.js +++ b/post-content.js @@ -41,7 +41,7 @@ window._wpGutenbergPost = { '', '', - '', + '', '', '', ].join( '' ), From ef3e4e13e59d42841bbeeb6011d02c3cf6b90fe3 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Thu, 4 May 2017 12:56:22 +0100 Subject: [PATCH 2/3] Parser: improve HTML attributes parsing --- blocks/api/parser.js | 8 ++++---- blocks/api/post.pegjs | 43 ++++++++++++++++++++++++++----------------- 2 files changed, 30 insertions(+), 21 deletions(-) diff --git a/blocks/api/parser.js b/blocks/api/parser.js index dcfb18b2e88e21..fa1a1ad201e49e 100644 --- a/blocks/api/parser.js +++ b/blocks/api/parser.js @@ -99,7 +99,7 @@ export function parseWithTinyMCE( content ) { // In : // Out : content = content.replace( - //g, + //g, function( match, closingSlash, slug, attributes ) { if ( closingSlash ) { return ''; @@ -166,13 +166,13 @@ export function parseWithTinyMCE( content ) { }, {} ); // Retrieve the block attributes from the original delimiters - const attributesMatcher = /([a-z0-9_-]+)="([^"]*)"/g; + const attributesMatcher = /([a-z0-9_-]+)(="([^"]*)")?/g; const blockAttributes = {}; let match; do { match = attributesMatcher.exec( unescape( nodeAttributes.attributes || '' ) ); if ( match ) { - blockAttributes[ match[ 1 ] ] = match[ 2 ]; + blockAttributes[ match[ 1 ] ] = !! match[ 2 ] ? match[ 3 ] : true; } } while ( !! match ); @@ -221,4 +221,4 @@ export function parseWithGrammar( content ) { }, [] ); } -export default parseWithGrammar; +export default parseWithTinyMCE; diff --git a/blocks/api/post.pegjs b/blocks/api/post.pegjs index 696ecde0dfe30e..f841d51e1a895a 100644 --- a/blocks/api/post.pegjs +++ b/blocks/api/post.pegjs @@ -26,7 +26,7 @@ WP_Block_Html } WP_Block_Start - = "" + = "" { return { blockType: blockType, attrs: attrs @@ -41,22 +41,34 @@ WP_Block_End WP_Block_Type = $(ASCII_Letter WP_Block_Type_Char*) -WP_Block_Attribute_List - = as:(_+ attr:WP_Block_Attribute { return attr })* - { return as.reduce( function( attrs, pair ) { - attrs[ pair.name ] = pair.value; - return attrs; - }, {} ) } +HTML_Attribute_List + = as:(_+ a:HTML_Attribute_Item { return a })* + { return as.reduce( ( attrs, [ name, value ] ) => Object.assign( + attrs, + { [ name ]: value } + ), {} ) } -WP_Block_Attribute - = name:WP_Block_Attribute_Name '="' value:WP_Block_Attribute_Value '"' - { return { name: name, value: value }; } +HTML_Attribute_Item + = HTML_Attribute_Quoted + / HTML_Attribute_Unquoted + / HTML_Attribute_Empty -WP_Block_Attribute_Name - = $(ASCII_Letter ASCII_AlphaNumeric*) +HTML_Attribute_Empty + = name:HTML_Attribute_Name + { return [ name, true ] } -WP_Block_Attribute_Value - = $(ASCII_Letter WP_Block_Attribute_Value_Char*) +HTML_Attribute_Unquoted + = name:HTML_Attribute_Name _* "=" _* value:$([a-zA-Z0-9]+) + { return [ name, value ] } + +HTML_Attribute_Quoted + = name:HTML_Attribute_Name _* "=" _* '"' value:$((!'"' .)*) '"' + { return [ name, value ] } + / name:HTML_Attribute_Name _* "=" _* "'" value:$((!"'" .)*) "'" + { return [ name, value ] } + +HTML_Attribute_Name + = $([a-zA-Z0-9:.]+) WP_Block_Type_Char = ASCII_AlphaNumeric @@ -67,9 +79,6 @@ ASCII_AlphaNumeric / ASCII_Digit / Special_Chars -WP_Block_Attribute_Value_Char - = [^"] - ASCII_Letter = [a-zA-Z] From 6331eb35f8cb585b21e2e70f837712b6ffc7dd64 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Thu, 4 May 2017 12:56:35 +0100 Subject: [PATCH 3/3] Parser: Test both parsing strategies --- blocks/api/post.pegjs | 12 ++- blocks/api/test/parser.js | 211 ++++++++++++++++++++------------------ 2 files changed, 118 insertions(+), 105 deletions(-) diff --git a/blocks/api/post.pegjs b/blocks/api/post.pegjs index f841d51e1a895a..1c7f9b56d6a6c2 100644 --- a/blocks/api/post.pegjs +++ b/blocks/api/post.pegjs @@ -43,10 +43,14 @@ WP_Block_Type HTML_Attribute_List = as:(_+ a:HTML_Attribute_Item { return a })* - { return as.reduce( ( attrs, [ name, value ] ) => Object.assign( - attrs, - { [ name ]: value } - ), {} ) } + { return as.reduce( function( attrs, currentAttribute ) { + var currentAttrs = {}; + currentAttrs[ currentAttribute[ 0 ] ] = currentAttribute[ 1 ]; + return Object.assign( + attrs, + currentAttrs + ); + }, {} ) } HTML_Attribute_Item = HTML_Attribute_Quoted diff --git a/blocks/api/test/parser.js b/blocks/api/test/parser.js index 58d8b9b08b5879..99671d43cbf150 100644 --- a/blocks/api/test/parser.js +++ b/blocks/api/test/parser.js @@ -11,7 +11,8 @@ import { getBlockAttributes, parseBlockAttributes, createBlockWithFallback, - default as parse, + parseWithGrammar, + parseWithTinyMCE } from '../parser'; import { registerBlock, @@ -139,107 +140,115 @@ describe( 'block parser', () => { } ); describe( 'parse()', () => { - it( 'should parse the post content, including block attributes', () => { - registerBlock( 'core/test-block', { - // Currently this is the only way to test block content parsing? - attributes: function( rawContent ) { - return { - content: rawContent, - }; - } - } ); - - const parsed = parse( - '' + - 'Brisket' + - '' - ); - - expect( parsed ).to.have.lengthOf( 1 ); - expect( parsed[ 0 ].blockType ).to.equal( 'core/test-block' ); - expect( parsed[ 0 ].attributes ).to.eql( { - content: 'Brisket', - smoked: 'yes', - url: 'http://google.com' - } ); - expect( parsed[ 0 ].uid ).to.be.a( 'string' ); - } ); - - it( 'should parse the post content, ignoring unknown blocks', () => { - registerBlock( 'core/test-block', { - attributes: function( rawContent ) { - return { - content: rawContent + ' & Chicken' - }; - } - } ); - - const parsed = parse( - 'Ribs' + - '

Broccoli

' + - 'Ribs' - ); - - expect( parsed ).to.have.lengthOf( 1 ); - expect( parsed[ 0 ].blockType ).to.equal( 'core/test-block' ); - expect( parsed[ 0 ].attributes ).to.eql( { - content: 'Ribs & Chicken' - } ); - expect( parsed[ 0 ].uid ).to.be.a( 'string' ); - } ); - - it( 'should parse the post content, using unknown block handler', () => { - registerBlock( 'core/test-block', {} ); - registerBlock( 'core/unknown-block', {} ); - - setUnknownTypeHandler( 'core/unknown-block' ); - - const parsed = parse( - 'Ribs' + - '

Broccoli

' + - 'Ribs' - ); - - expect( parsed ).to.have.lengthOf( 3 ); - expect( parsed.map( ( { blockType } ) => blockType ) ).to.eql( [ - 'core/test-block', - 'core/unknown-block', - 'core/unknown-block', - ] ); - } ); - - it( 'should parse the post content, including raw HTML at each end', () => { - registerBlock( 'core/test-block', {} ); - registerBlock( 'core/unknown-block', { - // Currently this is the only way to test block content parsing? - attributes: function( rawContent ) { - return { - content: rawContent, - }; - } + const parsers = { parseWithTinyMCE, parseWithGrammar }; + Object.keys( parsers ).forEach( ( parser ) => { + const parse = parsers[ parser ]; + describe( parser, () => { + it( 'should parse the post content, including block attributes', () => { + registerBlock( 'core/test-block', { + // Currently this is the only way to test block content parsing? + attributes: function( rawContent ) { + return { + content: rawContent, + }; + } + } ); + + const parsed = parse( + '' + + 'Brisket' + + '' + ); + + expect( parsed ).to.have.lengthOf( 1 ); + expect( parsed[ 0 ].blockType ).to.equal( 'core/test-block' ); + expect( parsed[ 0 ].attributes ).to.eql( { + content: 'Brisket', + smoked: 'yes', + url: 'http://google.com', + chicken: 'ribs & \'wings\'', + checked: true + } ); + expect( parsed[ 0 ].uid ).to.be.a( 'string' ); + } ); + + it( 'should parse the post content, ignoring unknown blocks', () => { + registerBlock( 'core/test-block', { + attributes: function( rawContent ) { + return { + content: rawContent + ' & Chicken' + }; + } + } ); + + const parsed = parse( + 'Ribs' + + '

Broccoli

' + + 'Ribs' + ); + + expect( parsed ).to.have.lengthOf( 1 ); + expect( parsed[ 0 ].blockType ).to.equal( 'core/test-block' ); + expect( parsed[ 0 ].attributes ).to.eql( { + content: 'Ribs & Chicken' + } ); + expect( parsed[ 0 ].uid ).to.be.a( 'string' ); + } ); + + it( 'should parse the post content, using unknown block handler', () => { + registerBlock( 'core/test-block', {} ); + registerBlock( 'core/unknown-block', {} ); + + setUnknownTypeHandler( 'core/unknown-block' ); + + const parsed = parse( + 'Ribs' + + '

Broccoli

' + + 'Ribs' + ); + + expect( parsed ).to.have.lengthOf( 3 ); + expect( parsed.map( ( { blockType } ) => blockType ) ).to.eql( [ + 'core/test-block', + 'core/unknown-block', + 'core/unknown-block', + ] ); + } ); + + it( 'should parse the post content, including raw HTML at each end', () => { + registerBlock( 'core/test-block', {} ); + registerBlock( 'core/unknown-block', { + // Currently this is the only way to test block content parsing? + attributes: function( rawContent ) { + return { + content: rawContent, + }; + } + } ); + + setUnknownTypeHandler( 'core/unknown-block' ); + + const parsed = parse( + '

Cauliflower

' + + 'Ribs' + + '

Broccoli

' + + 'Ribs' + + '

Romanesco

' + ); + + expect( parsed ).to.have.lengthOf( 5 ); + expect( parsed.map( ( { blockType } ) => blockType ) ).to.eql( [ + 'core/unknown-block', + 'core/test-block', + 'core/unknown-block', + 'core/test-block', + 'core/unknown-block', + ] ); + expect( parsed[ 0 ].attributes.content ).to.eql( '

Cauliflower

' ); + expect( parsed[ 2 ].attributes.content ).to.eql( '

Broccoli

' ); + expect( parsed[ 4 ].attributes.content ).to.eql( '

Romanesco

' ); + } ); } ); - - setUnknownTypeHandler( 'core/unknown-block' ); - - const parsed = parse( - '

Cauliflower

' + - 'Ribs' + - '

Broccoli

' + - 'Ribs' + - '

Romanesco

' - ); - - expect( parsed ).to.have.lengthOf( 5 ); - expect( parsed.map( ( { blockType } ) => blockType ) ).to.eql( [ - 'core/unknown-block', - 'core/test-block', - 'core/unknown-block', - 'core/test-block', - 'core/unknown-block', - ] ); - expect( parsed[ 0 ].attributes.content ).to.eql( '

Cauliflower

' ); - expect( parsed[ 2 ].attributes.content ).to.eql( '

Broccoli

' ); - expect( parsed[ 4 ].attributes.content ).to.eql( '

Romanesco

' ); } ); } ); } );