[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/js/dist/ -> block-serialization-default-parser.js (source)

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


Generated : Tue Mar 19 08:20:01 2024 Cross-referenced by PHPXref