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