[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

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

   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  
 405  (window.wp = window.wp || {}).blockSerializationDefaultParser = __webpack_exports__;
 406  /******/ })()
 407  ;


Generated: Fri Apr 19 01:00:02 2024 Cross-referenced by PHPXref 0.7.1