PluginProbe
Gutenberg / 12.6.0
Gutenberg v12.6.0
23.9.1 23.9.0 23.8.0 23.7.2 23.7.1 23.7.0 23.6.1 23.6.2 23.6.0 23.5.3 23.5.2 23.5.1 23.5.0 23.4.0 23.3.2 23.3.1 23.3.0 23.2.0 23.2.1 23.2.2 23.1.1 23.1.0 23.0.1 12.6.0 7.4.0 All 402 releases
gutenberg / build / block-serialization-default-parser / index.js

index.js in Gutenberg 12.6.0, at build/block-serialization-default-parser/index.js

407 lines 12.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /******/ (function() { // webpackBootstrap
2 /******/ "use strict";
3 /******/ // The require scope
4 /******/ var __webpack_require__ = {};
5 /******/
6 /************************************************************************/
7 /******/ /* webpack/runtime/define property getters */
8 /******/ !function() {
9 /******/ // define getter functions for harmony exports
10 /******/ __webpack_require__.d = function(exports, definition) {
11 /******/ for(var key in definition) {
12 /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
13 /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
14 /******/ }
15 /******/ }
16 /******/ };
17 /******/ }();
18 /******/
19 /******/ /* webpack/runtime/hasOwnProperty shorthand */
20 /******/ !function() {
21 /******/ __webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }
22 /******/ }();
23 /******/
24 /******/ /* webpack/runtime/make namespace object */
25 /******/ !function() {
26 /******/ // define __esModule on exports
27 /******/ __webpack_require__.r = function(exports) {
28 /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
29 /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
30 /******/ }
31 /******/ Object.defineProperty(exports, '__esModule', { value: true });
32 /******/ };
33 /******/ }();
34 /******/
35 /************************************************************************/
36 var __webpack_exports__ = {};
37 __webpack_require__.r(__webpack_exports__);
38 /* harmony export */ __webpack_require__.d(__webpack_exports__, {
39 /* harmony export */ "parse": function() { return /* binding */ parse; }
40 /* harmony export */ });
41 let document;
42 let offset;
43 let output;
44 let stack;
45 /**
46 * Matches block comment delimiters
47 *
48 * While most of this pattern is straightforward the attribute parsing
49 * incorporates a tricks to make sure we don't choke on specific input
50 *
51 * - since JavaScript has no possessive quantifier or atomic grouping
52 * we are emulating it with a trick
53 *
54 * we want a possessive quantifier or atomic group to prevent backtracking
55 * on the `}`s should we fail to match the remainder of the pattern
56 *
57 * we can emulate this with a positive lookahead and back reference
58 * (a++)*c === ((?=(a+))\1)*c
59 *
60 * let's examine an example:
61 * - /(a+)*c/.test('aaaaaaaaaaaaad') fails after over 49,000 steps
62 * - /(a++)*c/.test('aaaaaaaaaaaaad') fails after 85 steps
63 * - /(?>a+)*c/.test('aaaaaaaaaaaaad') fails after 126 steps
64 *
65 * this is because the possessive `++` and the atomic group `(?>)`
66 * tell the engine that all those `a`s belong together as a single group
67 * and so it won't split it up when stepping backwards to try and match
68 *
69 * if we use /((?=(a+))\1)*c/ then we get the same behavior as the atomic group
70 * or possessive and prevent the backtracking because the `a+` is matched but
71 * not captured. thus, we find the long string of `a`s and remember it, then
72 * reference it as a whole unit inside our pattern
73 *
74 * @see http://instanceof.me/post/52245507631/regex-emulate-atomic-grouping-with-lookahead
75 * @see http://blog.stevenlevithan.com/archives/mimic-atomic-groups
76 * @see https://javascript.info/regexp-infinite-backtracking-problem
77 *
78 * once browsers reliably support atomic grouping or possessive
79 * quantifiers natively we should remove this trick and simplify
80 *
81 * @type {RegExp}
82 *
83 * @since 3.8.0
84 * @since 4.6.1 added optimization to prevent backtracking on attribute parsing
85 */
86
87 const tokenizer = /<!--\s+(\/)?wp:([a-z][a-z0-9_-]*\/)?([a-z][a-z0-9_-]*)\s+({(?:(?=([^}]+|}+(?=})|(?!}\s+\/?-->)[^])*)\5|[^]*?)}\s+)?(\/)?-->/g;
88
89 function Block(blockName, attrs, innerBlocks, innerHTML, innerContent) {
90 return {
91 blockName,
92 attrs,
93 innerBlocks,
94 innerHTML,
95 innerContent
96 };
97 }
98
99 function Freeform(innerHTML) {
100 return Block(null, {}, [], innerHTML, [innerHTML]);
101 }
102
103 function Frame(block, tokenStart, tokenLength, prevOffset, leadingHtmlStart) {
104 return {
105 block,
106 tokenStart,
107 tokenLength,
108 prevOffset: prevOffset || tokenStart + tokenLength,
109 leadingHtmlStart
110 };
111 }
112 /**
113 * Parser function, that converts input HTML into a block based structure.
114 *
115 * @param {string} doc The HTML document to parse.
116 *
117 * @example
118 * Input post:
119 * ```html
120 * <!-- wp:columns {"columns":3} -->
121 * <div class="wp-block-columns has-3-columns"><!-- wp:column -->
122 * <div class="wp-block-column"><!-- wp:paragraph -->
123 * <p>Left</p>
124 * <!-- /wp:paragraph --></div>
125 * <!-- /wp:column -->
126 *
127 * <!-- wp:column -->
128 * <div class="wp-block-column"><!-- wp:paragraph -->
129 * <p><strong>Middle</strong></p>
130 * <!-- /wp:paragraph --></div>
131 * <!-- /wp:column -->
132 *
133 * <!-- wp:column -->
134 * <div class="wp-block-column"></div>
135 * <!-- /wp:column --></div>
136 * <!-- /wp:columns -->
137 * ```
138 *
139 * Parsing code:
140 * ```js
141 * import { parse } from '@wordpress/block-serialization-default-parser';
142 *
143 * parse( post ) === [
144 * {
145 * blockName: "core/columns",
146 * attrs: {
147 * columns: 3
148 * },
149 * innerBlocks: [
150 * {
151 * blockName: "core/column",
152 * attrs: null,
153 * innerBlocks: [
154 * {
155 * blockName: "core/paragraph",
156 * attrs: null,
157 * innerBlocks: [],
158 * innerHTML: "\n<p>Left</p>\n"
159 * }
160 * ],
161 * innerHTML: '\n<div class="wp-block-column"></div>\n'
162 * },
163 * {
164 * blockName: "core/column",
165 * attrs: null,
166 * innerBlocks: [
167 * {
168 * blockName: "core/paragraph",
169 * attrs: null,
170 * innerBlocks: [],
171 * innerHTML: "\n<p><strong>Middle</strong></p>\n"
172 * }
173 * ],
174 * innerHTML: '\n<div class="wp-block-column"></div>\n'
175 * },
176 * {
177 * blockName: "core/column",
178 * attrs: null,
179 * innerBlocks: [],
180 * innerHTML: '\n<div class="wp-block-column"></div>\n'
181 * }
182 * ],
183 * innerHTML: '\n<div class="wp-block-columns has-3-columns">\n\n\n\n</div>\n'
184 * }
185 * ];
186 * ```
187 * @return {Array} A block-based representation of the input HTML.
188 */
189
190
191 const parse = doc => {
192 document = doc;
193 offset = 0;
194 output = [];
195 stack = [];
196 tokenizer.lastIndex = 0;
197
198 do {// twiddle our thumbs
199 } while (proceed());
200
201 return output;
202 };
203
204 function proceed() {
205 const next = nextToken();
206 const [tokenType, blockName, attrs, startOffset, tokenLength] = next;
207 const stackDepth = stack.length; // we may have some HTML soup before the next block
208
209 const leadingHtmlStart = startOffset > offset ? offset : null;
210
211 switch (tokenType) {
212 case 'no-more-tokens':
213 // if not in a block then flush output
214 if (0 === stackDepth) {
215 addFreeform();
216 return false;
217 } // Otherwise we have a problem
218 // This is an error
219 // we have options
220 // - treat it all as freeform text
221 // - assume an implicit closer (easiest when not nesting)
222 // for the easy case we'll assume an implicit closer
223
224
225 if (1 === stackDepth) {
226 addBlockFromStack();
227 return false;
228 } // for the nested case where it's more difficult we'll
229 // have to assume that multiple closers are missing
230 // and so we'll collapse the whole stack piecewise
231
232
233 while (0 < stack.length) {
234 addBlockFromStack();
235 }
236
237 return false;
238
239 case 'void-block':
240 // easy case is if we stumbled upon a void block
241 // in the top-level of the document
242 if (0 === stackDepth) {
243 if (null !== leadingHtmlStart) {
244 output.push(Freeform(document.substr(leadingHtmlStart, startOffset - leadingHtmlStart)));
245 }
246
247 output.push(Block(blockName, attrs, [], '', []));
248 offset = startOffset + tokenLength;
249 return true;
250 } // otherwise we found an inner block
251
252
253 addInnerBlock(Block(blockName, attrs, [], '', []), startOffset, tokenLength);
254 offset = startOffset + tokenLength;
255 return true;
256
257 case 'block-opener':
258 // track all newly-opened blocks on the stack
259 stack.push(Frame(Block(blockName, attrs, [], '', []), startOffset, tokenLength, startOffset + tokenLength, leadingHtmlStart));
260 offset = startOffset + tokenLength;
261 return true;
262
263 case 'block-closer':
264 // if we're missing an opener we're in trouble
265 // This is an error
266 if (0 === stackDepth) {
267 // we have options
268 // - assume an implicit opener
269 // - assume _this_ is the opener
270 // - give up and close out the document
271 addFreeform();
272 return false;
273 } // if we're not nesting then this is easy - close the block
274
275
276 if (1 === stackDepth) {
277 addBlockFromStack(startOffset);
278 offset = startOffset + tokenLength;
279 return true;
280 } // otherwise we're nested and we have to close out the current
281 // block and add it as a innerBlock to the parent
282
283
284 const stackTop = stack.pop();
285 const html = document.substr(stackTop.prevOffset, startOffset - stackTop.prevOffset);
286 stackTop.block.innerHTML += html;
287 stackTop.block.innerContent.push(html);
288 stackTop.prevOffset = startOffset + tokenLength;
289 addInnerBlock(stackTop.block, stackTop.tokenStart, stackTop.tokenLength, startOffset + tokenLength);
290 offset = startOffset + tokenLength;
291 return true;
292
293 default:
294 // This is an error
295 addFreeform();
296 return false;
297 }
298 }
299 /**
300 * Parse JSON if valid, otherwise return null
301 *
302 * Note that JSON coming from the block comment
303 * delimiters is constrained to be an object
304 * and cannot be things like `true` or `null`
305 *
306 * @param {string} input JSON input string to parse
307 * @return {Object|null} parsed JSON if valid
308 */
309
310
311 function parseJSON(input) {
312 try {
313 return JSON.parse(input);
314 } catch (e) {
315 return null;
316 }
317 }
318
319 function nextToken() {
320 // aye the magic
321 // we're using a single RegExp to tokenize the block comment delimiters
322 // we're also using a trick here because the only difference between a
323 // block opener and a block closer is the leading `/` before `wp:` (and
324 // a closer has no attributes). we can trap them both and process the
325 // match back in JavaScript to see which one it was.
326 const matches = tokenizer.exec(document); // we have no more tokens
327
328 if (null === matches) {
329 return ['no-more-tokens'];
330 }
331
332 const startedAt = matches.index;
333 const [match, closerMatch, namespaceMatch, nameMatch, attrsMatch
334 /* internal/unused */
335 ,, voidMatch] = matches;
336 const length = match.length;
337 const isCloser = !!closerMatch;
338 const isVoid = !!voidMatch;
339 const namespace = namespaceMatch || 'core/';
340 const name = namespace + nameMatch;
341 const hasAttrs = !!attrsMatch;
342 const attrs = hasAttrs ? parseJSON(attrsMatch) : {}; // This state isn't allowed
343 // This is an error
344
345 if (isCloser && (isVoid || hasAttrs)) {// we can ignore them since they don't hurt anything
346 // we may warn against this at some point or reject it
347 }
348
349 if (isVoid) {
350 return ['void-block', name, attrs, startedAt, length];
351 }
352
353 if (isCloser) {
354 return ['block-closer', name, null, startedAt, length];
355 }
356
357 return ['block-opener', name, attrs, startedAt, length];
358 }
359
360 function addFreeform(rawLength) {
361 const length = rawLength ? rawLength : document.length - offset;
362
363 if (0 === length) {
364 return;
365 }
366
367 output.push(Freeform(document.substr(offset, length)));
368 }
369
370 function addInnerBlock(block, tokenStart, tokenLength, lastOffset) {
371 const parent = stack[stack.length - 1];
372 parent.block.innerBlocks.push(block);
373 const html = document.substr(parent.prevOffset, tokenStart - parent.prevOffset);
374
375 if (html) {
376 parent.block.innerHTML += html;
377 parent.block.innerContent.push(html);
378 }
379
380 parent.block.innerContent.push(null);
381 parent.prevOffset = lastOffset ? lastOffset : tokenStart + tokenLength;
382 }
383
384 function addBlockFromStack(endOffset) {
385 const {
386 block,
387 leadingHtmlStart,
388 prevOffset,
389 tokenStart
390 } = stack.pop();
391 const html = endOffset ? document.substr(prevOffset, endOffset - prevOffset) : document.substr(prevOffset);
392
393 if (html) {
394 block.innerHTML += html;
395 block.innerContent.push(html);
396 }
397
398 if (null !== leadingHtmlStart) {
399 output.push(Freeform(document.substr(leadingHtmlStart, tokenStart - leadingHtmlStart)));
400 }
401
402 output.push(block);
403 }
404 //# sourceMappingURL=index.js.map
405 (window.wp = window.wp || {}).blockSerializationDefaultParser = __webpack_exports__;
406 /******/ })()
407 ;