diff options
author | Andrew Nacin <nacin@git.wordpress.org> | 2013-08-07 05:25:25 +0000 |
---|---|---|
committer | Andrew Nacin <nacin@git.wordpress.org> | 2013-08-07 05:25:25 +0000 |
commit | b43712e0f79a9f5bea52217e06155e2f471c490c (patch) | |
tree | 23d331e7b2f29689571f03cfa5f9b7b3b6cafab8 /src/wp-includes/js/shortcode.js | |
parent | 5bbd08e1d17079a2e852517351f7405884a156b3 (diff) | |
download | wordpress-b43712e0f79a9f5bea52217e06155e2f471c490c.tar.gz wordpress-b43712e0f79a9f5bea52217e06155e2f471c490c.zip |
New develop.svn.wordpress.org repository based on the old core.svn repository.
* All WordPress files move to a src/ directory.
* New task runner (Grunt), configured to copy a built WordPress to build/.
* svn:ignore and .gitignore for Gruntfile.js, wp-config.php, and node.js.
* Remove Akismet external from develop.svn. Still exists in core.svn.
* Drop minified files from src/. The build process will now generate these.
props koop.
see #24976.
and see http://wp.me/p2AvED-1AI.
git-svn-id: https://develop.svn.wordpress.org/trunk@25001 602fd350-edb4-49c9-b593-d223f7449a82
Diffstat (limited to 'src/wp-includes/js/shortcode.js')
-rw-r--r-- | src/wp-includes/js/shortcode.js | 342 |
1 files changed, 342 insertions, 0 deletions
diff --git a/src/wp-includes/js/shortcode.js b/src/wp-includes/js/shortcode.js new file mode 100644 index 0000000000..0f83a445bc --- /dev/null +++ b/src/wp-includes/js/shortcode.js @@ -0,0 +1,342 @@ +// Utility functions for parsing and handling shortcodes in Javascript. + +// Ensure the global `wp` object exists. +window.wp = window.wp || {}; + +(function(){ + wp.shortcode = { + // ### Find the next matching shortcode + // + // Given a shortcode `tag`, a block of `text`, and an optional starting + // `index`, returns the next matching shortcode or `undefined`. + // + // Shortcodes are formatted as an object that contains the match + // `content`, the matching `index`, and the parsed `shortcode` object. + next: function( tag, text, index ) { + var re = wp.shortcode.regexp( tag ), + match, result; + + re.lastIndex = index || 0; + match = re.exec( text ); + + if ( ! match ) + return; + + // If we matched an escaped shortcode, try again. + if ( match[1] === '[' && match[7] === ']' ) + return wp.shortcode.next( tag, text, re.lastIndex ); + + result = { + index: match.index, + content: match[0], + shortcode: wp.shortcode.fromMatch( match ) + }; + + // If we matched a leading `[`, strip it from the match + // and increment the index accordingly. + if ( match[1] ) { + result.match = result.match.slice( 1 ); + result.index++; + } + + // If we matched a trailing `]`, strip it from the match. + if ( match[7] ) + result.match = result.match.slice( 0, -1 ); + + return result; + }, + + // ### Replace matching shortcodes in a block of text + // + // Accepts a shortcode `tag`, content `text` to scan, and a `callback` + // to process the shortcode matches and return a replacement string. + // Returns the `text` with all shortcodes replaced. + // + // Shortcode matches are objects that contain the shortcode `tag`, + // a shortcode `attrs` object, the `content` between shortcode tags, + // and a boolean flag to indicate if the match was a `single` tag. + replace: function( tag, text, callback ) { + return text.replace( wp.shortcode.regexp( tag ), function( match, left, tag, attrs, slash, content, closing, right, offset ) { + // If both extra brackets exist, the shortcode has been + // properly escaped. + if ( left === '[' && right === ']' ) + return match; + + // Create the match object and pass it through the callback. + var result = callback( wp.shortcode.fromMatch( arguments ) ); + + // Make sure to return any of the extra brackets if they + // weren't used to escape the shortcode. + return result ? left + result + right : match; + }); + }, + + // ### Generate a string from shortcode parameters + // + // Creates a `wp.shortcode` instance and returns a string. + // + // Accepts the same `options` as the `wp.shortcode()` constructor, + // containing a `tag` string, a string or object of `attrs`, a boolean + // indicating whether to format the shortcode using a `single` tag, and a + // `content` string. + string: function( options ) { + return new wp.shortcode( options ).string(); + }, + + // ### Generate a RegExp to identify a shortcode + // + // The base regex is functionally equivalent to the one found in + // `get_shortcode_regex()` in `wp-includes/shortcodes.php`. + // + // Capture groups: + // + // 1. An extra `[` to allow for escaping shortcodes with double `[[]]` + // 2. The shortcode name + // 3. The shortcode argument list + // 4. The self closing `/` + // 5. The content of a shortcode when it wraps some content. + // 6. The closing tag. + // 7. An extra `]` to allow for escaping shortcodes with double `[[]]` + regexp: _.memoize( function( tag ) { + return new RegExp( '\\[(\\[?)(' + tag + ')(?![\\w-])([^\\]\\/]*(?:\\/(?!\\])[^\\]\\/]*)*?)(?:(\\/)\\]|\\](?:([^\\[]*(?:\\[(?!\\/\\2\\])[^\\[]*)*)(\\[\\/\\2\\]))?)(\\]?)', 'g' ); + }), + + + // ### Parse shortcode attributes + // + // Shortcodes accept many types of attributes. These can chiefly be + // divided into named and numeric attributes: + // + // Named attributes are assigned on a key/value basis, while numeric + // attributes are treated as an array. + // + // Named attributes can be formatted as either `name="value"`, + // `name='value'`, or `name=value`. Numeric attributes can be formatted + // as `"value"` or just `value`. + attrs: _.memoize( function( text ) { + var named = {}, + numeric = [], + pattern, match; + + // This regular expression is reused from `shortcode_parse_atts()` + // in `wp-includes/shortcodes.php`. + // + // Capture groups: + // + // 1. An attribute name, that corresponds to... + // 2. a value in double quotes. + // 3. An attribute name, that corresponds to... + // 4. a value in single quotes. + // 5. An attribute name, that corresponds to... + // 6. an unquoted value. + // 7. A numeric attribute in double quotes. + // 8. An unquoted numeric attribute. + pattern = /(\w+)\s*=\s*"([^"]*)"(?:\s|$)|(\w+)\s*=\s*\'([^\']*)\'(?:\s|$)|(\w+)\s*=\s*([^\s\'"]+)(?:\s|$)|"([^"]*)"(?:\s|$)|(\S+)(?:\s|$)/g; + + // Map zero-width spaces to actual spaces. + text = text.replace( /[\u00a0\u200b]/g, ' ' ); + + // Match and normalize attributes. + while ( (match = pattern.exec( text )) ) { + if ( match[1] ) { + named[ match[1].toLowerCase() ] = match[2]; + } else if ( match[3] ) { + named[ match[3].toLowerCase() ] = match[4]; + } else if ( match[5] ) { + named[ match[5].toLowerCase() ] = match[6]; + } else if ( match[7] ) { + numeric.push( match[7] ); + } else if ( match[8] ) { + numeric.push( match[8] ); + } + } + + return { + named: named, + numeric: numeric + }; + }), + + // ### Generate a Shortcode Object from a RegExp match + // Accepts a `match` object from calling `regexp.exec()` on a `RegExp` + // generated by `wp.shortcode.regexp()`. `match` can also be set to the + // `arguments` from a callback passed to `regexp.replace()`. + fromMatch: function( match ) { + var type; + + if ( match[4] ) + type = 'self-closing'; + else if ( match[6] ) + type = 'closed'; + else + type = 'single'; + + return new wp.shortcode({ + tag: match[2], + attrs: match[3], + type: type, + content: match[5] + }); + } + }; + + + // Shortcode Objects + // ----------------- + // + // Shortcode objects are generated automatically when using the main + // `wp.shortcode` methods: `next()`, `replace()`, and `string()`. + // + // To access a raw representation of a shortcode, pass an `options` object, + // containing a `tag` string, a string or object of `attrs`, a string + // indicating the `type` of the shortcode ('single', 'self-closing', or + // 'closed'), and a `content` string. + wp.shortcode = _.extend( function( options ) { + _.extend( this, _.pick( options || {}, 'tag', 'attrs', 'type', 'content' ) ); + + var attrs = this.attrs; + + // Ensure we have a correctly formatted `attrs` object. + this.attrs = { + named: {}, + numeric: [] + }; + + if ( ! attrs ) + return; + + // Parse a string of attributes. + if ( _.isString( attrs ) ) { + this.attrs = wp.shortcode.attrs( attrs ); + + // Identify a correctly formatted `attrs` object. + } else if ( _.isEqual( _.keys( attrs ), [ 'named', 'numeric' ] ) ) { + this.attrs = attrs; + + // Handle a flat object of attributes. + } else { + _.each( options.attrs, function( value, key ) { + this.set( key, value ); + }, this ); + } + }, wp.shortcode ); + + _.extend( wp.shortcode.prototype, { + // ### Get a shortcode attribute + // + // Automatically detects whether `attr` is named or numeric and routes + // it accordingly. + get: function( attr ) { + return this.attrs[ _.isNumber( attr ) ? 'numeric' : 'named' ][ attr ]; + }, + + // ### Set a shortcode attribute + // + // Automatically detects whether `attr` is named or numeric and routes + // it accordingly. + set: function( attr, value ) { + this.attrs[ _.isNumber( attr ) ? 'numeric' : 'named' ][ attr ] = value; + return this; + }, + + // ### Transform the shortcode match into a string + string: function() { + var text = '[' + this.tag; + + _.each( this.attrs.numeric, function( value ) { + if ( /\s/.test( value ) ) + text += ' "' + value + '"'; + else + text += ' ' + value; + }); + + _.each( this.attrs.named, function( value, name ) { + text += ' ' + name + '="' + value + '"'; + }); + + // If the tag is marked as `single` or `self-closing`, close the + // tag and ignore any additional content. + if ( 'single' === this.type ) + return text + ']'; + else if ( 'self-closing' === this.type ) + return text + ' /]'; + + // Complete the opening tag. + text += ']'; + + if ( this.content ) + text += this.content; + + // Add the closing tag. + return text + '[/' + this.tag + ']'; + } + }); +}()); + +// HTML utility functions +// ---------------------- +// +// Experimental. These functions may change or be removed in the future. +(function(){ + wp.html = _.extend( wp.html || {}, { + // ### Parse HTML attributes. + // + // Converts `content` to a set of parsed HTML attributes. + // Utilizes `wp.shortcode.attrs( content )`, which is a valid superset of + // the HTML attribute specification. Reformats the attributes into an + // object that contains the `attrs` with `key:value` mapping, and a record + // of the attributes that were entered using `empty` attribute syntax (i.e. + // with no value). + attrs: function( content ) { + var result, attrs; + + // If `content` ends in a slash, strip it. + if ( '/' === content[ content.length - 1 ] ) + content = content.slice( 0, -1 ); + + result = wp.shortcode.attrs( content ); + attrs = result.named; + + _.each( result.numeric, function( key ) { + if ( /\s/.test( key ) ) + return; + + attrs[ key ] = ''; + }); + + return attrs; + }, + + // ### Convert an HTML-representation of an object to a string. + string: function( options ) { + var text = '<' + options.tag, + content = options.content || ''; + + _.each( options.attrs, function( value, attr ) { + text += ' ' + attr; + + // Use empty attribute notation where possible. + if ( '' === value ) + return; + + // Convert boolean values to strings. + if ( _.isBoolean( value ) ) + value = value ? 'true' : 'false'; + + text += '="' + value + '"'; + }); + + // Return the result if it is a self-closing tag. + if ( options.single ) + return text + ' />'; + + // Complete the opening tag. + text += '>'; + + // If `content` is an object, recursively call this function. + text += _.isObject( content ) ? wp.html.string( content ) : content; + + return text + '</' + options.tag + '>'; + } + }); +}());
\ No newline at end of file |