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