Merge "StringUtils: Add a utility for checking if a string is a valid regex"
[lhc/web/wiklou.git] / resources / src / mediawiki.Title / Title.js
index 259febc..843f00f 100644 (file)
 
 /* Private members */
 
-var
+var toUpperMap,
        mwString = require( 'mediawiki.String' ),
 
-       toUpperMapping = require( './phpCharToUpper.json' ),
-
        namespaceIds = mw.config.get( 'wgNamespaceIds' ),
 
        /**
@@ -137,11 +135,11 @@ var
                '[^' + mw.config.get( 'wgLegalTitleChars' ) + ']' +
                // URL percent encoding sequences interfere with the ability
                // to round-trip titles -- you can't link to them consistently.
-               '|%[0-9A-Fa-f]{2}' +
+               '|%[\\dA-Fa-f]{2}' +
                // XML/HTML character references produce similar issues.
-               '|&[A-Za-z0-9\u0080-\uFFFF]+;' +
-               '|&#[0-9]+;' +
-               '|&#x[0-9A-Fa-f]+;'
+               '|&[\\dA-Za-z\u0080-\uFFFF]+;' +
+               '|&#\\d+;' +
+               '|&#x[\\dA-Fa-f]+;'
        ),
 
        // From MediaWikiTitleCodec::splitTitleString() in PHP
@@ -149,7 +147,7 @@ var
        rWhitespace = /[ _\u00A0\u1680\u180E\u2000-\u200A\u2028\u2029\u202F\u205F\u3000]+/g,
 
        // From MediaWikiTitleCodec::splitTitleString() in PHP
-       rUnicodeBidi = /[\u200E\u200F\u202A-\u202E]/g,
+       rUnicodeBidi = /[\u200E\u200F\u202A-\u202E]+/g,
 
        /**
         * Slightly modified from Flinfo. Credit goes to Lupo and Flominator.
@@ -173,13 +171,13 @@ var
                },
                // URL encoding (possibly)
                {
-                       pattern: /%([0-9A-Fa-f]{2})/g,
+                       pattern: /%([\dA-Fa-f]{2})/g,
                        replace: '% $1',
                        generalRule: true
                },
                // HTML-character-entities
                {
-                       pattern: /&(([A-Za-z0-9\x80-\xff]+|#[0-9]+|#x[0-9A-Fa-f]+);)/g,
+                       pattern: /&(([\dA-Za-z\x80-\xff]+|#\d+|#x[\dA-Fa-f]+);)/g,
                        replace: '& $1',
                        generalRule: true
                },
@@ -228,7 +226,7 @@ var
         * @return {Object|boolean}
         */
        parse = function ( title, defaultNamespace ) {
-               var namespace, m, id, i, fragment, ext;
+               var namespace, m, id, i, fragment;
 
                namespace = defaultNamespace === undefined ? NS_MAIN : defaultNamespace;
 
@@ -294,7 +292,7 @@ var
                }
 
                // Reject illegal characters
-               if ( title.match( rInvalid ) ) {
+               if ( rInvalid.test( title ) ) {
                        return false;
                }
 
@@ -336,21 +334,9 @@ var
                        return false;
                }
 
-               // For backwards-compatibility with old mw.Title, we separate the extension from the
-               // rest of the title.
-               i = title.lastIndexOf( '.' );
-               if ( i === -1 || title.length <= i + 1 ) {
-                       // Extensions are the non-empty segment after the last dot
-                       ext = null;
-               } else {
-                       ext = title.slice( i + 1 );
-                       title = title.slice( 0, i );
-               }
-
                return {
                        namespace: namespace,
                        title: title,
-                       ext: ext,
                        fragment: fragment
                };
        },
@@ -438,7 +424,6 @@ function Title( title, namespace ) {
 
        this.namespace = parsed.namespace;
        this.title = parsed.title;
-       this.ext = parsed.ext;
        this.fragment = parsed.fragment;
 }
 
@@ -465,7 +450,6 @@ Title.newFromText = function ( title, namespace ) {
        t = Object.create( Title.prototype );
        t.namespace = parsed.namespace;
        t.title = parsed.title;
-       t.ext = parsed.ext;
        t.fragment = parsed.fragment;
 
        return t;
@@ -600,7 +584,6 @@ Title.newFromUserInput = function ( title, defaultNamespaceOrOptions, options )
  * @return {mw.Title|null} A valid Title object or null if the title is invalid
  */
 Title.newFromFileName = function ( uncleanName ) {
-
        return Title.newFromUserInput( 'File:' + uncleanName, {
                forUploading: true
        } );
@@ -609,7 +592,7 @@ Title.newFromFileName = function ( uncleanName ) {
 /**
  * Get the file title from an image element
  *
- *     var title = mw.Title.newFromImg( $( 'img:first' ) );
+ *     var title = mw.Title.newFromImg( imageNode );
  *
  * @static
  * @param {HTMLElement|jQuery} img The image to use as a base
@@ -622,10 +605,10 @@ Title.newFromImg = function ( img ) {
                thumbPhpRegex = /thumb\.php/,
                regexes = [
                        // Thumbnails
-                       /\/[a-f0-9]\/[a-f0-9]{2}\/([^\s/]+)\/[^\s/]+-[^\s/]*$/,
+                       /\/[\da-f]\/[\da-f]{2}\/([^\s/]+)\/[^\s/]+-[^\s/]*$/,
 
                        // Full size images
-                       /\/[a-f0-9]\/[a-f0-9]{2}\/([^\s/]+)$/,
+                       /\/[\da-f]\/[\da-f]{2}\/([^\s/]+)$/,
 
                        // Thumbnails in non-hashed upload directories
                        /\/([^\s/]+)\/[^\s/]+-(?:\1|thumbnail)[^\s/]*$/,
@@ -638,9 +621,7 @@ Title.newFromImg = function ( img ) {
 
        src = img.jquery ? img[ 0 ].src : img.src;
 
-       matches = src.match( thumbPhpRegex );
-
-       if ( matches ) {
+       if ( thumbPhpRegex.test( src ) ) {
                return mw.Title.newFromText( 'File:' + mw.util.getParamValue( 'f', src ) );
        }
 
@@ -759,16 +740,16 @@ Title.exist = {
 Title.normalizeExtension = function ( extension ) {
        var
                lower = extension.toLowerCase(),
-               squish = {
+               normalizations = {
                        htm: 'html',
                        jpeg: 'jpg',
                        mpeg: 'mpg',
                        tiff: 'tif',
                        ogv: 'ogg'
                };
-       if ( Object.prototype.hasOwnProperty.call( squish, lower ) ) {
-               return squish[ lower ];
-       } else if ( /^[0-9a-z]+$/.test( lower ) ) {
+       if ( Object.hasOwnProperty.call( normalizations, lower ) ) {
+               return normalizations[ lower ];
+       } else if ( /^[\da-z]+$/.test( lower ) ) {
                return lower;
        } else {
                return '';
@@ -782,8 +763,15 @@ Title.normalizeExtension = function ( extension ) {
  * @return {string} Unicode character, in upper case, according to the same rules as in PHP
  */
 Title.phpCharToUpper = function ( chr ) {
-       var mapped = toUpperMapping[ chr ];
-       return mapped || chr.toUpperCase();
+       if ( !toUpperMap ) {
+               toUpperMap = require( './phpCharToUpper.json' );
+       }
+       if ( toUpperMap[ chr ] === '' ) {
+               // Optimisation: When the override is to keep the character unchanged,
+               // we use an empty string in JSON. This reduces the data by 50%.
+               return chr;
+       }
+       return toUpperMap[ chr ] || chr.toUpperCase();
 };
 
 /* Public members */
@@ -824,13 +812,11 @@ Title.prototype = {
         * @return {string}
         */
        getName: function () {
-               if (
-                       mw.config.get( 'wgCaseSensitiveNamespaces' ).indexOf( this.namespace ) !== -1 ||
-                       !this.title.length
-               ) {
-                       return this.title;
+               var ext = this.getExtension();
+               if ( ext === null ) {
+                       return this.getMain();
                }
-               return mw.Title.phpCharToUpper( this.title[ 0 ] ) + this.title.slice( 1 );
+               return this.getMain().slice( 0, -ext.length - 1 );
        },
 
        /**
@@ -852,7 +838,11 @@ Title.prototype = {
         * @return {string|null} Name extension or null if there is none
         */
        getExtension: function () {
-               return this.ext;
+               var lastDot = this.title.lastIndexOf( '.' );
+               if ( lastDot === -1 ) {
+                       return null;
+               }
+               return this.title.slice( lastDot + 1 ) || null;
        },
 
        /**
@@ -863,7 +853,8 @@ Title.prototype = {
         * @return {string}
         */
        getDotExtension: function () {
-               return this.ext === null ? '' : '.' + this.ext;
+               var ext = this.getExtension();
+               return ext === null ? '' : '.' + ext;
        },
 
        /**
@@ -874,7 +865,13 @@ Title.prototype = {
         * @return {string}
         */
        getMain: function () {
-               return this.getName() + this.getDotExtension();
+               if (
+                       mw.config.get( 'wgCaseSensitiveNamespaces' ).indexOf( this.namespace ) !== -1 ||
+                       !this.title.length
+               ) {
+                       return this.title;
+               }
+               return mw.Title.phpCharToUpper( this.title[ 0 ] ) + this.title.slice( 1 );
        },
 
        /**