home *** CD-ROM | disk | FTP | other *** search
/ HTML Examples / WP.iso / wordpress / wp-includes / js / shortcode.js < prev    next >
Encoding:
JavaScript  |  2017-09-08  |  10.3 KB  |  359 lines

  1. // Utility functions for parsing and handling shortcodes in JavaScript.
  2.  
  3. /**
  4.  * Ensure the global `wp` object exists.
  5.  *
  6.  * @namespace wp
  7.  */
  8. window.wp = window.wp || {};
  9.  
  10. (function(){
  11.     wp.shortcode = {
  12.         // ### Find the next matching shortcode
  13.         //
  14.         // Given a shortcode `tag`, a block of `text`, and an optional starting
  15.         // `index`, returns the next matching shortcode or `undefined`.
  16.         //
  17.         // Shortcodes are formatted as an object that contains the match
  18.         // `content`, the matching `index`, and the parsed `shortcode` object.
  19.         next: function( tag, text, index ) {
  20.             var re = wp.shortcode.regexp( tag ),
  21.                 match, result;
  22.  
  23.             re.lastIndex = index || 0;
  24.             match = re.exec( text );
  25.  
  26.             if ( ! match ) {
  27.                 return;
  28.             }
  29.  
  30.             // If we matched an escaped shortcode, try again.
  31.             if ( '[' === match[1] && ']' === match[7] ) {
  32.                 return wp.shortcode.next( tag, text, re.lastIndex );
  33.             }
  34.  
  35.             result = {
  36.                 index:     match.index,
  37.                 content:   match[0],
  38.                 shortcode: wp.shortcode.fromMatch( match )
  39.             };
  40.  
  41.             // If we matched a leading `[`, strip it from the match
  42.             // and increment the index accordingly.
  43.             if ( match[1] ) {
  44.                 result.content = result.content.slice( 1 );
  45.                 result.index++;
  46.             }
  47.  
  48.             // If we matched a trailing `]`, strip it from the match.
  49.             if ( match[7] ) {
  50.                 result.content = result.content.slice( 0, -1 );
  51.             }
  52.  
  53.             return result;
  54.         },
  55.  
  56.         // ### Replace matching shortcodes in a block of text
  57.         //
  58.         // Accepts a shortcode `tag`, content `text` to scan, and a `callback`
  59.         // to process the shortcode matches and return a replacement string.
  60.         // Returns the `text` with all shortcodes replaced.
  61.         //
  62.         // Shortcode matches are objects that contain the shortcode `tag`,
  63.         // a shortcode `attrs` object, the `content` between shortcode tags,
  64.         // and a boolean flag to indicate if the match was a `single` tag.
  65.         replace: function( tag, text, callback ) {
  66.             return text.replace( wp.shortcode.regexp( tag ), function( match, left, tag, attrs, slash, content, closing, right ) {
  67.                 // If both extra brackets exist, the shortcode has been
  68.                 // properly escaped.
  69.                 if ( left === '[' && right === ']' ) {
  70.                     return match;
  71.                 }
  72.  
  73.                 // Create the match object and pass it through the callback.
  74.                 var result = callback( wp.shortcode.fromMatch( arguments ) );
  75.  
  76.                 // Make sure to return any of the extra brackets if they
  77.                 // weren't used to escape the shortcode.
  78.                 return result ? left + result + right : match;
  79.             });
  80.         },
  81.  
  82.         // ### Generate a string from shortcode parameters
  83.         //
  84.         // Creates a `wp.shortcode` instance and returns a string.
  85.         //
  86.         // Accepts the same `options` as the `wp.shortcode()` constructor,
  87.         // containing a `tag` string, a string or object of `attrs`, a boolean
  88.         // indicating whether to format the shortcode using a `single` tag, and a
  89.         // `content` string.
  90.         string: function( options ) {
  91.             return new wp.shortcode( options ).string();
  92.         },
  93.  
  94.         // ### Generate a RegExp to identify a shortcode
  95.         //
  96.         // The base regex is functionally equivalent to the one found in
  97.         // `get_shortcode_regex()` in `wp-includes/shortcodes.php`.
  98.         //
  99.         // Capture groups:
  100.         //
  101.         // 1. An extra `[` to allow for escaping shortcodes with double `[[]]`
  102.         // 2. The shortcode name
  103.         // 3. The shortcode argument list
  104.         // 4. The self closing `/`
  105.         // 5. The content of a shortcode when it wraps some content.
  106.         // 6. The closing tag.
  107.         // 7. An extra `]` to allow for escaping shortcodes with double `[[]]`
  108.         regexp: _.memoize( function( tag ) {
  109.             return new RegExp( '\\[(\\[?)(' + tag + ')(?![\\w-])([^\\]\\/]*(?:\\/(?!\\])[^\\]\\/]*)*?)(?:(\\/)\\]|\\](?:([^\\[]*(?:\\[(?!\\/\\2\\])[^\\[]*)*)(\\[\\/\\2\\]))?)(\\]?)', 'g' );
  110.         }),
  111.  
  112.  
  113.         // ### Parse shortcode attributes
  114.         //
  115.         // Shortcodes accept many types of attributes. These can chiefly be
  116.         // divided into named and numeric attributes:
  117.         //
  118.         // Named attributes are assigned on a key/value basis, while numeric
  119.         // attributes are treated as an array.
  120.         //
  121.         // Named attributes can be formatted as either `name="value"`,
  122.         // `name='value'`, or `name=value`. Numeric attributes can be formatted
  123.         // as `"value"` or just `value`.
  124.         attrs: _.memoize( function( text ) {
  125.             var named   = {},
  126.                 numeric = [],
  127.                 pattern, match;
  128.  
  129.             // This regular expression is reused from `shortcode_parse_atts()`
  130.             // in `wp-includes/shortcodes.php`.
  131.             //
  132.             // Capture groups:
  133.             //
  134.             // 1. An attribute name, that corresponds to...
  135.             // 2. a value in double quotes.
  136.             // 3. An attribute name, that corresponds to...
  137.             // 4. a value in single quotes.
  138.             // 5. An attribute name, that corresponds to...
  139.             // 6. an unquoted value.
  140.             // 7. A numeric attribute in double quotes.
  141.             // 8. A numeric attribute in single quotes.
  142.             // 9. An unquoted numeric attribute.
  143.             pattern = /([\w-]+)\s*=\s*"([^"]*)"(?:\s|$)|([\w-]+)\s*=\s*'([^']*)'(?:\s|$)|([\w-]+)\s*=\s*([^\s'"]+)(?:\s|$)|"([^"]*)"(?:\s|$)|'([^']*)'(?:\s|$)|(\S+)(?:\s|$)/g;
  144.  
  145.             // Map zero-width spaces to actual spaces.
  146.             text = text.replace( /[\u00a0\u200b]/g, ' ' );
  147.  
  148.             // Match and normalize attributes.
  149.             while ( (match = pattern.exec( text )) ) {
  150.                 if ( match[1] ) {
  151.                     named[ match[1].toLowerCase() ] = match[2];
  152.                 } else if ( match[3] ) {
  153.                     named[ match[3].toLowerCase() ] = match[4];
  154.                 } else if ( match[5] ) {
  155.                     named[ match[5].toLowerCase() ] = match[6];
  156.                 } else if ( match[7] ) {
  157.                     numeric.push( match[7] );
  158.                 } else if ( match[8] ) {
  159.                     numeric.push( match[8] );
  160.                 } else if ( match[9] ) {
  161.                     numeric.push( match[9] );
  162.                 }
  163.             }
  164.  
  165.             return {
  166.                 named:   named,
  167.                 numeric: numeric
  168.             };
  169.         }),
  170.  
  171.         // ### Generate a Shortcode Object from a RegExp match
  172.         // Accepts a `match` object from calling `regexp.exec()` on a `RegExp`
  173.         // generated by `wp.shortcode.regexp()`. `match` can also be set to the
  174.         // `arguments` from a callback passed to `regexp.replace()`.
  175.         fromMatch: function( match ) {
  176.             var type;
  177.  
  178.             if ( match[4] ) {
  179.                 type = 'self-closing';
  180.             } else if ( match[6] ) {
  181.                 type = 'closed';
  182.             } else {
  183.                 type = 'single';
  184.             }
  185.  
  186.             return new wp.shortcode({
  187.                 tag:     match[2],
  188.                 attrs:   match[3],
  189.                 type:    type,
  190.                 content: match[5]
  191.             });
  192.         }
  193.     };
  194.  
  195.  
  196.     // Shortcode Objects
  197.     // -----------------
  198.     //
  199.     // Shortcode objects are generated automatically when using the main
  200.     // `wp.shortcode` methods: `next()`, `replace()`, and `string()`.
  201.     //
  202.     // To access a raw representation of a shortcode, pass an `options` object,
  203.     // containing a `tag` string, a string or object of `attrs`, a string
  204.     // indicating the `type` of the shortcode ('single', 'self-closing', or
  205.     // 'closed'), and a `content` string.
  206.     wp.shortcode = _.extend( function( options ) {
  207.         _.extend( this, _.pick( options || {}, 'tag', 'attrs', 'type', 'content' ) );
  208.  
  209.         var attrs = this.attrs;
  210.  
  211.         // Ensure we have a correctly formatted `attrs` object.
  212.         this.attrs = {
  213.             named:   {},
  214.             numeric: []
  215.         };
  216.  
  217.         if ( ! attrs ) {
  218.             return;
  219.         }
  220.  
  221.         // Parse a string of attributes.
  222.         if ( _.isString( attrs ) ) {
  223.             this.attrs = wp.shortcode.attrs( attrs );
  224.  
  225.         // Identify a correctly formatted `attrs` object.
  226.         } else if ( _.isEqual( _.keys( attrs ), [ 'named', 'numeric' ] ) ) {
  227.             this.attrs = attrs;
  228.  
  229.         // Handle a flat object of attributes.
  230.         } else {
  231.             _.each( options.attrs, function( value, key ) {
  232.                 this.set( key, value );
  233.             }, this );
  234.         }
  235.     }, wp.shortcode );
  236.  
  237.     _.extend( wp.shortcode.prototype, {
  238.         // ### Get a shortcode attribute
  239.         //
  240.         // Automatically detects whether `attr` is named or numeric and routes
  241.         // it accordingly.
  242.         get: function( attr ) {
  243.             return this.attrs[ _.isNumber( attr ) ? 'numeric' : 'named' ][ attr ];
  244.         },
  245.  
  246.         // ### Set a shortcode attribute
  247.         //
  248.         // Automatically detects whether `attr` is named or numeric and routes
  249.         // it accordingly.
  250.         set: function( attr, value ) {
  251.             this.attrs[ _.isNumber( attr ) ? 'numeric' : 'named' ][ attr ] = value;
  252.             return this;
  253.         },
  254.  
  255.         // ### Transform the shortcode match into a string
  256.         string: function() {
  257.             var text    = '[' + this.tag;
  258.  
  259.             _.each( this.attrs.numeric, function( value ) {
  260.                 if ( /\s/.test( value ) ) {
  261.                     text += ' "' + value + '"';
  262.                 } else {
  263.                     text += ' ' + value;
  264.                 }
  265.             });
  266.  
  267.             _.each( this.attrs.named, function( value, name ) {
  268.                 text += ' ' + name + '="' + value + '"';
  269.             });
  270.  
  271.             // If the tag is marked as `single` or `self-closing`, close the
  272.             // tag and ignore any additional content.
  273.             if ( 'single' === this.type ) {
  274.                 return text + ']';
  275.             } else if ( 'self-closing' === this.type ) {
  276.                 return text + ' /]';
  277.             }
  278.  
  279.             // Complete the opening tag.
  280.             text += ']';
  281.  
  282.             if ( this.content ) {
  283.                 text += this.content;
  284.             }
  285.  
  286.             // Add the closing tag.
  287.             return text + '[/' + this.tag + ']';
  288.         }
  289.     });
  290. }());
  291.  
  292. // HTML utility functions
  293. // ----------------------
  294. //
  295. // Experimental. These functions may change or be removed in the future.
  296. (function(){
  297.     wp.html = _.extend( wp.html || {}, {
  298.         // ### Parse HTML attributes.
  299.         //
  300.         // Converts `content` to a set of parsed HTML attributes.
  301.         // Utilizes `wp.shortcode.attrs( content )`, which is a valid superset of
  302.         // the HTML attribute specification. Reformats the attributes into an
  303.         // object that contains the `attrs` with `key:value` mapping, and a record
  304.         // of the attributes that were entered using `empty` attribute syntax (i.e.
  305.         // with no value).
  306.         attrs: function( content ) {
  307.             var result, attrs;
  308.  
  309.             // If `content` ends in a slash, strip it.
  310.             if ( '/' === content[ content.length - 1 ] ) {
  311.                 content = content.slice( 0, -1 );
  312.             }
  313.  
  314.             result = wp.shortcode.attrs( content );
  315.             attrs  = result.named;
  316.  
  317.             _.each( result.numeric, function( key ) {
  318.                 if ( /\s/.test( key ) ) {
  319.                     return;
  320.                 }
  321.  
  322.                 attrs[ key ] = '';
  323.             });
  324.  
  325.             return attrs;
  326.         },
  327.  
  328.         // ### Convert an HTML-representation of an object to a string.
  329.         string: function( options ) {
  330.             var text = '<' + options.tag,
  331.                 content = options.content || '';
  332.  
  333.             _.each( options.attrs, function( value, attr ) {
  334.                 text += ' ' + attr;
  335.  
  336.                 // Convert boolean values to strings.
  337.                 if ( _.isBoolean( value ) ) {
  338.                     value = value ? 'true' : 'false';
  339.                 }
  340.  
  341.                 text += '="' + value + '"';
  342.             });
  343.  
  344.             // Return the result if it is a self-closing tag.
  345.             if ( options.single ) {
  346.                 return text + ' />';
  347.             }
  348.  
  349.             // Complete the opening tag.
  350.             text += '>';
  351.  
  352.             // If `content` is an object, recursively call this function.
  353.             text += _.isObject( content ) ? wp.html.string( content ) : content;
  354.  
  355.             return text + '</' + options.tag + '>';
  356.         }
  357.     });
  358. }());
  359.