WordPress/wp-includes/js/dist/block-serialization-default-parser.js

407 lines
13 KiB
JavaScript
Raw Normal View History

/******/ (function() { // webpackBootstrap
/******/ "use strict";
/******/ // The require scope
/******/ var __webpack_require__ = {};
/******/
/************************************************************************/
/******/ /* webpack/runtime/define property getters */
/******/ !function() {
/******/ // define getter functions for harmony exports
/******/ __webpack_require__.d = function(exports, definition) {
/******/ for(var key in definition) {
/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
/******/ }
/******/ }
/******/ };
/******/ }();
/******/
/******/ /* webpack/runtime/hasOwnProperty shorthand */
/******/ !function() {
/******/ __webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }
/******/ }();
/******/
/******/ /* webpack/runtime/make namespace object */
/******/ !function() {
/******/ // define __esModule on exports
/******/ __webpack_require__.r = function(exports) {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/ }();
/******/
/************************************************************************/
var __webpack_exports__ = {};
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "parse": function() { return /* binding */ parse; }
/* harmony export */ });
let document;
let offset;
let output;
let stack;
/**
* Matches block comment delimiters
*
* While most of this pattern is straightforward the attribute parsing
* incorporates a tricks to make sure we don't choke on specific input
*
* - since JavaScript has no possessive quantifier or atomic grouping
* we are emulating it with a trick
*
* we want a possessive quantifier or atomic group to prevent backtracking
* on the `}`s should we fail to match the remainder of the pattern
*
* we can emulate this with a positive lookahead and back reference
* (a++)*c === ((?=(a+))\1)*c
*
* let's examine an example:
* - /(a+)*c/.test('aaaaaaaaaaaaad') fails after over 49,000 steps
* - /(a++)*c/.test('aaaaaaaaaaaaad') fails after 85 steps
* - /(?>a+)*c/.test('aaaaaaaaaaaaad') fails after 126 steps
*
* this is because the possessive `++` and the atomic group `(?>)`
* tell the engine that all those `a`s belong together as a single group
* and so it won't split it up when stepping backwards to try and match
*
* if we use /((?=(a+))\1)*c/ then we get the same behavior as the atomic group
* or possessive and prevent the backtracking because the `a+` is matched but
* not captured. thus, we find the long string of `a`s and remember it, then
* reference it as a whole unit inside our pattern
*
Block Editor: Update the WordPress Packages to the ones used in the Gutenberg 6.5 release Updated Packages: In version 6.2: @wordpress/a11y@2.5.0 @wordpress/annotations@1.5.0 @wordpress/api-fetch@3.4.0 @wordpress/autop@2.4.0 @wordpress/babel-plugin-import-jsx-pragma@2.3.0 @wordpress/babel-plugin-makepot@3.2.0 @wordpress/babel-preset-default@4.4.0 @wordpress/blob@2.5.0 @wordpress/block-editor@3.0.0 @wordpress/block-library@2.7.0 @wordpress/block-serialization-default-parser@3.3.0 @wordpress/block-serialization-spec-parser@3.2.0 @wordpress/blocks@6.5.0 @wordpress/browserslist-config@2.6.0 @wordpress/components@8.1.0 @wordpress/compose@3.5.0 @wordpress/core-data@2.5.0 @wordpress/custom-templated-path-webpack-plugin@1.5.0 @wordpress/data-controls@1.1.0 @wordpress/data@4.7.0 @wordpress/date@3.4.0 @wordpress/dependency-extraction-webpack-plugin@1.1.0 @wordpress/deprecated@2.5.0 @wordpress/docgen@1.3.0 @wordpress/dom-ready@2.5.0 @wordpress/dom@2.4.0 @wordpress/e2e-test-utils@2.2.0 @wordpress/e2e-tests@1.4.0 @wordpress/edit-post@3.6.0 @wordpress/editor@9.5.0 @wordpress/element@2.6.0 @wordpress/escape-html@1.5.0 @wordpress/eslint-plugin@2.4.0 @wordpress/format-library@1.7.0 @wordpress/hooks@2.5.0 @wordpress/html-entities@2.5.0 @wordpress/i18n@3.6.0 @wordpress/is-shallow-equal@1.5.0 @wordpress/jest-console@3.2.0 @wordpress/jest-preset-default@4.3.0 @wordpress/jest-puppeteer-axe@1.2.0 @wordpress/keycodes@2.5.0 @wordpress/library-export-default-webpack-plugin@1.4.0 @wordpress/list-reusable-blocks@1.6.0 @wordpress/media-utils@1.0.0 @wordpress/notices@1.6.0 @wordpress/npm-package-json-lint-config@2.1.0 @wordpress/nux@3.5.0 @wordpress/plugins@2.5.0 @wordpress/postcss-themes@2.2.0 @wordpress/priority-queue@1.3.0 @wordpress/redux-routine@3.5.0 @wordpress/rich-text@3.5.0 @wordpress/scripts@3.4.0 @wordpress/server-side-render@1.1.0 @wordpress/shortcode@2.4.0 @wordpress/token-list@1.5.0 @wordpress/url@2.7.0 @wordpress/viewport@2.6.0 @wordpress/wordcount@2.5.0 In version 6.4: @wordpress/annotations@1.6.0 @wordpress/api-fetch@3.5.0 @wordpress/autop@2.5.0 @wordpress/babel-preset-default@4.5.0 @wordpress/block-editor@3.1.0 @wordpress/block-library@2.8.0 @wordpress/block-serialization-default-parser@3.4.0 @wordpress/block-serialization-spec-parser@3.3.0 @wordpress/blocks@6.6.0 @wordpress/components@8.2.0 @wordpress/compose@3.6.0 @wordpress/core-data@2.6.0 @wordpress/data-controls@1.2.0 @wordpress/data@4.8.0 @wordpress/date@3.5.0 @wordpress/dependency-extraction-webpack-plugin@1.2.0 @wordpress/deprecated@2.6.0 @wordpress/docgen@1.4.0 @wordpress/dom@2.5.0 @wordpress/e2e-test-utils@2.3.0 @wordpress/e2e-tests@1.5.0 @wordpress/edit-post@3.7.0 @wordpress/editor@9.6.0 @wordpress/element@2.7.0 @wordpress/eslint-plugin@3.0.0 @wordpress/format-library@1.8.0 @wordpress/hooks@2.6.0 @wordpress/is-shallow-equal@1.6.0 @wordpress/jest-console@3.3.0 @wordpress/jest-preset-default@5.0.0 @wordpress/jest-puppeteer-axe@1.3.0 @wordpress/keycodes@2.6.0 @wordpress/list-reusable-blocks@1.7.0 @wordpress/media-utils@1.1.0 @wordpress/notices@1.7.0 @wordpress/nux@3.6.0 @wordpress/plugins@2.6.0 @wordpress/project-management-automation@1.0.0 @wordpress/redux-routine@3.6.0 @wordpress/rich-text@3.6.0 @wordpress/scripts@4.0.0 @wordpress/server-side-render@1.2.0 @wordpress/token-list@1.6.0 @wordpress/viewport@2.7.0 @wordpress/wordcount@2.6.0 In version 6.5: @wordpress/annotations@1.7.0 @wordpress/api-fetch@3.6.0 @wordpress/babel-preset-default@4.6.0 @wordpress/block-directory@1.0.0 @wordpress/block-editor@3.2.0 @wordpress/block-library@2.9.0 @wordpress/blocks@6.7.0 @wordpress/components@8.3.0 @wordpress/compose@3.7.0 @wordpress/core-data@2.7.0 @wordpress/data-controls@1.3.0 @wordpress/data@4.9.0 @wordpress/dependency-extraction-webpack-plugin@2.0.0 @wordpress/e2e-test-utils@2.4.0 @wordpress/e2e-tests@1.7.0 @wordpress/edit-post@3.8.0 @wordpress/editor@9.7.0 @wordpress/element@2.8.0 @wordpress/eslint-plugin@3.1.0 @wordpress/format-library@1.9.0 @wordpress/list-reusable-blocks@1.8.0 @wordpress/media-utils@1.2.0 @wordpress/notices@1.8.0 @wordpress/nux@3.7.0 @wordpress/plugins@2.7.0 @wordpress/rich-text@3.7.0 @wordpress/scripts@5.0.0 @wordpress/server-side-render@1.3.0 @wordpress/url@2.8.0 @wordpress/viewport@2.8.0 Props youknowriad desrosj noisysocks pento jorgefilipecosta iseulde Fixes: #47843 Built from https://develop.svn.wordpress.org/trunk@46189 git-svn-id: http://core.svn.wordpress.org/trunk@46001 1a063a9b-81f0-0310-95a4-ce76da25c4cd
2019-09-19 17:19:18 +02:00
* @see http://instanceof.me/post/52245507631/regex-emulate-atomic-grouping-with-lookahead
* @see http://blog.stevenlevithan.com/archives/mimic-atomic-groups
* @see https://javascript.info/regexp-infinite-backtracking-problem
*
* once browsers reliably support atomic grouping or possessive
* quantifiers natively we should remove this trick and simplify
*
* @type {RegExp}
*
* @since 3.8.0
* @since 4.6.1 added optimization to prevent backtracking on attribute parsing
*/
const tokenizer = /<!--\s+(\/)?wp:([a-z][a-z0-9_-]*\/)?([a-z][a-z0-9_-]*)\s+({(?:(?=([^}]+|}+(?=})|(?!}\s+\/?-->)[^])*)\5|[^]*?)}\s+)?(\/)?-->/g;
function Block(blockName, attrs, innerBlocks, innerHTML, innerContent) {
return {
blockName,
attrs,
innerBlocks,
innerHTML,
innerContent
};
}
function Freeform(innerHTML) {
return Block(null, {}, [], innerHTML, [innerHTML]);
}
function Frame(block, tokenStart, tokenLength, prevOffset, leadingHtmlStart) {
return {
block,
tokenStart,
tokenLength,
prevOffset: prevOffset || tokenStart + tokenLength,
leadingHtmlStart
};
}
Block Editor: Update the WordPress Packages from Gutenberg 5.3 - @wordpress/a11y@2.2.0 - @wordpress/annotations@1.2.0 - @wordpress/api-fetch@3.1.0 - @wordpress/autop@2.2.0 - @wordpress/babel-plugin-import-jsx-pragma@2.1.0 - @wordpress/babel-preset-default@4.1.0 - @wordpress/blob@2.3.0 - @wordpress/block-editor@1.1.0 - @wordpress/block-library@2.4.0 - @wordpress/block-serialization-default-parser@3.1.0 - @wordpress/blocks@6.2.0 - @wordpress/components@7.2.0 - @wordpress/compose@3.2.0 - @wordpress/core-data@2.2.0 - @wordpress/data@4.4.0 - @wordpress/date@3.2.0 - @wordpress/deprecated@2.2.0 - @wordpress/docgen@1.1.0 - @wordpress/dom-ready@2.2.0 - @wordpress/dom@2.2.0 - @wordpress/e2e-test-utils@1.1.0 - @wordpress/e2e-tests@1.1.0 - @wordpress/edit-post@3.3.0 - @wordpress/edit-widgets@0.2.0 - @wordpress/editor@9.2.0 - @wordpress/element@2.3.0 - @wordpress/escape-html@1.2.0 - @wordpress/eslint-plugin@2.1.0 - @wordpress/format-library@1.4.0 - @wordpress/hooks@2.2.0 - @wordpress/html-entities@2.2.0 - @wordpress/i18n@3.3.0 - @wordpress/keycodes@2.2.0 - @wordpress/list-reusable-blocks@1.3.0 - @wordpress/notices@1.3.0 - @wordpress/nux@3.2.0 - @wordpress/plugins@2.2.0 - @wordpress/priority-queue@1.1.0 - @wordpress/redux-routine@3.2.0 - @wordpress/rich-text@3.2.0 - @wordpress/scripts@3.1.0 - @wordpress/shortcode@2.2.0 - @wordpress/url@2.5.0 - @wordpress/viewport@2.3.0 - @wordpress/wordcount@2.2.0 Built from https://develop.svn.wordpress.org/trunk@44960 git-svn-id: http://core.svn.wordpress.org/trunk@44791 1a063a9b-81f0-0310-95a4-ce76da25c4cd
2019-03-21 13:48:00 +01:00
/**
* Parser function, that converts input HTML into a block based structure.
*
* @param {string} doc The HTML document to parse.
*
* @example
* Input post:
* ```html
* <!-- wp:columns {"columns":3} -->
* <div class="wp-block-columns has-3-columns"><!-- wp:column -->
* <div class="wp-block-column"><!-- wp:paragraph -->
* <p>Left</p>
* <!-- /wp:paragraph --></div>
* <!-- /wp:column -->
*
* <!-- wp:column -->
* <div class="wp-block-column"><!-- wp:paragraph -->
* <p><strong>Middle</strong></p>
* <!-- /wp:paragraph --></div>
* <!-- /wp:column -->
*
* <!-- wp:column -->
* <div class="wp-block-column"></div>
* <!-- /wp:column --></div>
* <!-- /wp:columns -->
* ```
*
* Parsing code:
* ```js
* import { parse } from '@wordpress/block-serialization-default-parser';
*
* parse( post ) === [
* {
* blockName: "core/columns",
* attrs: {
* columns: 3
* },
* innerBlocks: [
* {
* blockName: "core/column",
* attrs: null,
* innerBlocks: [
* {
* blockName: "core/paragraph",
* attrs: null,
* innerBlocks: [],
* innerHTML: "\n<p>Left</p>\n"
* }
* ],
* innerHTML: '\n<div class="wp-block-column"></div>\n'
* },
* {
* blockName: "core/column",
* attrs: null,
* innerBlocks: [
* {
* blockName: "core/paragraph",
* attrs: null,
* innerBlocks: [],
* innerHTML: "\n<p><strong>Middle</strong></p>\n"
* }
* ],
* innerHTML: '\n<div class="wp-block-column"></div>\n'
* },
* {
* blockName: "core/column",
* attrs: null,
* innerBlocks: [],
* innerHTML: '\n<div class="wp-block-column"></div>\n'
* }
* ],
* innerHTML: '\n<div class="wp-block-columns has-3-columns">\n\n\n\n</div>\n'
* }
* ];
* ```
* @return {Array} A block-based representation of the input HTML.
*/
const parse = doc => {
document = doc;
offset = 0;
output = [];
stack = [];
tokenizer.lastIndex = 0;
do {// twiddle our thumbs
} while (proceed());
return output;
};
function proceed() {
const next = nextToken();
const [tokenType, blockName, attrs, startOffset, tokenLength] = next;
const stackDepth = stack.length; // we may have some HTML soup before the next block
const leadingHtmlStart = startOffset > offset ? offset : null;
switch (tokenType) {
case 'no-more-tokens':
// if not in a block then flush output
if (0 === stackDepth) {
addFreeform();
return false;
} // Otherwise we have a problem
// This is an error
// we have options
// - treat it all as freeform text
// - assume an implicit closer (easiest when not nesting)
// for the easy case we'll assume an implicit closer
if (1 === stackDepth) {
addBlockFromStack();
return false;
} // for the nested case where it's more difficult we'll
// have to assume that multiple closers are missing
// and so we'll collapse the whole stack piecewise
while (0 < stack.length) {
addBlockFromStack();
}
return false;
case 'void-block':
// easy case is if we stumbled upon a void block
// in the top-level of the document
if (0 === stackDepth) {
if (null !== leadingHtmlStart) {
output.push(Freeform(document.substr(leadingHtmlStart, startOffset - leadingHtmlStart)));
}
output.push(Block(blockName, attrs, [], '', []));
offset = startOffset + tokenLength;
return true;
} // otherwise we found an inner block
addInnerBlock(Block(blockName, attrs, [], '', []), startOffset, tokenLength);
offset = startOffset + tokenLength;
return true;
case 'block-opener':
// track all newly-opened blocks on the stack
stack.push(Frame(Block(blockName, attrs, [], '', []), startOffset, tokenLength, startOffset + tokenLength, leadingHtmlStart));
offset = startOffset + tokenLength;
return true;
case 'block-closer':
// if we're missing an opener we're in trouble
// This is an error
if (0 === stackDepth) {
// we have options
// - assume an implicit opener
// - assume _this_ is the opener
// - give up and close out the document
addFreeform();
return false;
} // if we're not nesting then this is easy - close the block
if (1 === stackDepth) {
addBlockFromStack(startOffset);
offset = startOffset + tokenLength;
return true;
} // otherwise we're nested and we have to close out the current
// block and add it as a innerBlock to the parent
const stackTop = stack.pop();
const html = document.substr(stackTop.prevOffset, startOffset - stackTop.prevOffset);
stackTop.block.innerHTML += html;
stackTop.block.innerContent.push(html);
stackTop.prevOffset = startOffset + tokenLength;
addInnerBlock(stackTop.block, stackTop.tokenStart, stackTop.tokenLength, startOffset + tokenLength);
offset = startOffset + tokenLength;
return true;
default:
// This is an error
addFreeform();
return false;
}
}
/**
* Parse JSON if valid, otherwise return null
*
* Note that JSON coming from the block comment
* delimiters is constrained to be an object
* and cannot be things like `true` or `null`
*
* @param {string} input JSON input string to parse
* @return {Object|null} parsed JSON if valid
*/
function parseJSON(input) {
try {
return JSON.parse(input);
} catch (e) {
return null;
}
}
function nextToken() {
// 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 JavaScript to see which one it was.
const matches = tokenizer.exec(document); // we have no more tokens
if (null === matches) {
return ['no-more-tokens'];
}
const startedAt = matches.index;
const [match, closerMatch, namespaceMatch, nameMatch, attrsMatch
/* internal/unused */
,, voidMatch] = matches;
const length = match.length;
const isCloser = !!closerMatch;
const isVoid = !!voidMatch;
const namespace = namespaceMatch || 'core/';
const name = namespace + nameMatch;
const hasAttrs = !!attrsMatch;
const attrs = hasAttrs ? parseJSON(attrsMatch) : {}; // This state isn't allowed
// This is an error
if (isCloser && (isVoid || hasAttrs)) {// we can ignore them since they don't hurt anything
// we may warn against this at some point or reject it
}
if (isVoid) {
return ['void-block', name, attrs, startedAt, length];
}
if (isCloser) {
return ['block-closer', name, null, startedAt, length];
}
return ['block-opener', name, attrs, startedAt, length];
}
function addFreeform(rawLength) {
const length = rawLength ? rawLength : document.length - offset;
if (0 === length) {
return;
}
output.push(Freeform(document.substr(offset, length)));
}
function addInnerBlock(block, tokenStart, tokenLength, lastOffset) {
const parent = stack[stack.length - 1];
parent.block.innerBlocks.push(block);
const html = document.substr(parent.prevOffset, tokenStart - parent.prevOffset);
if (html) {
parent.block.innerHTML += html;
parent.block.innerContent.push(html);
}
parent.block.innerContent.push(null);
parent.prevOffset = lastOffset ? lastOffset : tokenStart + tokenLength;
}
function addBlockFromStack(endOffset) {
const {
block,
leadingHtmlStart,
prevOffset,
tokenStart
} = stack.pop();
const html = endOffset ? document.substr(prevOffset, endOffset - prevOffset) : document.substr(prevOffset);
if (html) {
block.innerHTML += html;
block.innerContent.push(html);
}
if (null !== leadingHtmlStart) {
output.push(Freeform(document.substr(leadingHtmlStart, tokenStart - leadingHtmlStart)));
}
output.push(block);
}
(window.wp = window.wp || {}).blockSerializationDefaultParser = __webpack_exports__;
/******/ })()
;