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