mediawiki.jqueryMsg: Support arbitrary expressions in plural forms
authorSanthosh Thottingal <santhosh.thottingal@gmail.com>
Sun, 28 Sep 2014 09:01:46 +0000 (14:31 +0530)
committerSanthosh Thottingal <santhosh.thottingal@gmail.com>
Wed, 1 Oct 2014 04:00:58 +0000 (09:30 +0530)
In I15c167f245 mw.jqueryMsg.htmlEmitter#plural was modified to
convert the plural params to flat html. This caused extlink params
as plural forms expanded as text node and escaped.

This patch improves the plural support by:
* Allowing valid parser expressions as plural form parameters. They wont
  be converted to html, parsed nodes will be preserved in plural
  calculation
* For explicit plural forms like n=expression, the expression can be any
  valid parser expression. They also will not be coverted to html,
  nodes will be preserved
* Move the splitting by equal sign logic for explicit plural forms to
  jqueryMsg, avoiding mediawiki.language#convertPlural to do
  parsing logc. Explicit plural forms, if any, are passed as a parameter
  to mediawiki.language#convertPlural

Includes test cases for all combinations of the above cases.

mediawiki.jqueryMsg.peg can be improved to support digit=expression as an
additional form for templateParam to better handle this. But it looks
it can potentially disturb other parser functions. Not attempted.

Bug: 70617
Change-Id: I3c3cbb6d3676903941c40b658032b4c543556e06

resources/src/mediawiki.language/mediawiki.language.js
resources/src/mediawiki/mediawiki.jqueryMsg.js
tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js

index d4f3c69..dceae11 100644 (file)
@@ -40,39 +40,18 @@ $.extend( mw.language, {
         *
         * @param {number} count Non-localized quantifier
         * @param {Array} forms List of plural forms
+        * @param {Object} [explicitPluralForms] List of explicit plural forms
         * @return {string} Correct form for quantifier in this language
         */
-       convertPlural: function ( count, forms ) {
+       convertPlural: function ( count, forms, explicitPluralForms ) {
                var pluralRules,
-                       formCount,
-                       form,
-                       index,
-                       equalsPosition,
                        pluralFormIndex = 0;
 
-               if ( !forms || forms.length === 0 ) {
-                       return '';
+               if ( explicitPluralForms && explicitPluralForms[count] ) {
+                       return explicitPluralForms[count];
                }
 
-               // Handle for explicit n= forms
-               for ( index = 0; index < forms.length; index++ ) {
-                       form = forms[index];
-                       if ( /^\d+=/.test( form ) ) {
-                               equalsPosition = form.indexOf( '=' );
-                               formCount = parseInt( form.slice( 0, equalsPosition ), 10 );
-                               if ( formCount === count ) {
-                                       return form.slice( equalsPosition + 1 );
-                               }
-                               forms[index] = undefined;
-                       }
-               }
-
-               // Remove explicit plural forms from the forms.
-               forms = $.map( forms, function ( form ) {
-                       return form;
-               } );
-
-               if ( forms.length === 0 ) {
+               if ( !forms || forms.length === 0 ) {
                        return '';
                }
 
index ad71b08..3eaa6d2 100644 (file)
                 * @return {string} selected pluralized form according to current language
                 */
                plural: function ( nodes ) {
-                       var forms, formIndex, node, count;
+                       var forms, firstChild, firstChildText,
+                               explicitPluralForms = {}, explicitPluralFormNumber, formIndex, form, count;
+
                        count = parseFloat( this.language.convertNumber( nodes[0], true ) );
                        forms = nodes.slice( 1 );
                        for ( formIndex = 0; formIndex < forms.length; formIndex++ ) {
-                               node = forms[formIndex];
-                               if ( node.jquery && node.hasClass( 'mediaWiki_htmlEmitter' )  ) {
-                                       // This is a nested node, already expanded.
-                                       forms[formIndex] = forms[formIndex].html();
+                               form = forms[formIndex];
+
+                               if ( form.jquery && form.hasClass( 'mediaWiki_htmlEmitter' ) ) {
+                                       // This is a nested node, may be an explicit plural form like 5=[$2 linktext]
+                                       firstChild = form.contents().get( 0 );
+                                       if ( firstChild && firstChild.nodeType === Node.TEXT_NODE ) {
+                                               firstChildText = firstChild.textContent;
+                                               if ( /^\d+=/.test( firstChildText ) ) {
+                                                       explicitPluralFormNumber = parseInt( firstChildText.split( /=/ )[0], 10 );
+                                                       // Use the digit part as key and rest of first text node and
+                                                       // rest of child nodes as value.
+                                                       firstChild.textContent = firstChildText.slice( firstChildText.indexOf( '=' ) + 1 );
+                                                       explicitPluralForms[explicitPluralFormNumber] = form;
+                                                       forms[formIndex] = undefined;
+                                               }
+                                       }
+                               } else if ( /^\d+=/.test( form ) ) {
+                                       // Simple explicit plural forms like 12=a dozen
+                                       explicitPluralFormNumber = parseInt( form.split( /=/ )[0], 10 );
+                                       explicitPluralForms[explicitPluralFormNumber] = form.slice( form.indexOf( '=' ) + 1 );
+                                       forms[formIndex] = undefined;
                                }
                        }
-                       return forms.length ? this.language.convertPlural( count, forms ) : '';
+
+                       // Remove explicit plural forms from the forms. They were set undefined in the above loop.
+                       forms = $.map( forms, function ( form ) {
+                               return form;
+                       } );
+
+                       return this.language.convertPlural( count, forms, explicitPluralForms );
                },
 
                /**
index 906fd27..ece5116 100644 (file)
@@ -54,7 +54,9 @@
 
                        'jquerymsg-test-version-entrypoints-index-php': '[https://www.mediawiki.org/wiki/Manual:index.php index.php]',
 
-                       'external-link-replace': 'Foo [$1 bar]'
+                       'external-link-replace': 'Foo [$1 bar]',
+                       'external-link-plural': 'Foo {{PLURAL:$1|is [$2 one]|are [$2 some]|2=[$2 two]|3=three|4=a=b|5=}} things.',
+                       'plural-only-explicit-forms': 'It is a {{PLURAL:$1|1=single|2=double}} room.'
                }
        } ) );
 
@@ -85,7 +87,7 @@
                        } );
        }
 
-       QUnit.test( 'Replace', 9, function ( assert ) {
+       QUnit.test( 'Replace', 16, function ( assert ) {
                mw.messages.set( 'simple', 'Foo $1 baz $2' );
 
                assert.equal( formatParse( 'simple' ), 'Foo $1 baz $2', 'Replacements with no substitutes' );
                        'Foo <a href="http://example.org/?x=y&amp;z">bar</a>',
                        'Href is not double-escaped in wikilink function'
                );
+               assert.equal(
+                       formatParse( 'external-link-plural', 1, 'http://example.org' ),
+                       'Foo is <a href="http://example.org">one</a> things.',
+                       'Link is expanded inside plural and is not escaped html'
+               );
+               assert.equal(
+                       formatParse( 'external-link-plural', 2, 'http://example.org' ),
+                       'Foo <a href=\"http://example.org\">two</a> things.',
+                       'Link is expanded inside an explicit plural form and is not escaped html'
+               );
+               assert.equal(
+                       formatParse( 'external-link-plural', 3 ),
+                       'Foo three things.',
+                       'A simple explicit plural form co-existing with complex explicit plural forms'
+               );
+               assert.equal(
+                       formatParse( 'external-link-plural', 4, 'http://example.org' ),
+                       'Foo a=b things.',
+                       'Only first equal sign is used as delimiter for explicit plural form. Repeated equal signs does not create issue'
+               );
+               assert.equal(
+                       formatParse( 'external-link-plural', 5, 'http://example.org' ),
+                       'Foo are <a href="http://example.org">some</a> things.',
+                       'Invalid explicit plural form. Plural fallback to the "other" plural form'
+               );
+               assert.equal(
+                       formatParse( 'external-link-plural', 6, 'http://example.org' ),
+                       'Foo are <a href="http://example.org">some</a> things.',
+                       'Plural fallback to the "other" plural form'
+               );
+               assert.equal(
+                       formatParse( 'plural-only-explicit-forms', 2 ),
+                       'It is a double room.',
+                       'Plural with explicit forms alone.'
+               );
        } );
 
        QUnit.test( 'Plural', 6, function ( assert ) {