jenkins-bot has submitted this change and it was merged. ( 
https://gerrit.wikimedia.org/r/389852 )

Change subject: Preserve original transclusion's parameter order
......................................................................


Preserve original transclusion's parameter order

* Parameters that were previously present aren't reordered.
  Any updates to params are made in place.

* Newly introduced parameters are sorted in between the original
  parameters at a place where they preserve templateData order
  partially.

* In the absence of templatedata, they will be serialized
  in the order in which they were added by VE into data-mw.

* Updated mocha tests.

Change-Id: I2b269d5bfb08efbe56b9293c2aa3e810fb1b29da
---
M lib/html2wt/WikitextSerializer.js
M tests/mocha/templatedata.js
M tests/mockAPI.js
3 files changed, 96 insertions(+), 53 deletions(-)

Approvals:
  jenkins-bot: Verified
  Arlolra: Looks good to me, approved



diff --git a/lib/html2wt/WikitextSerializer.js 
b/lib/html2wt/WikitextSerializer.js
index 2fc2a1a..ad503fc 100644
--- a/lib/html2wt/WikitextSerializer.js
+++ b/lib/html2wt/WikitextSerializer.js
@@ -413,6 +413,72 @@
        });
 };
 
+function createParamComparator(dpArgInfo, tplData, dataMwKeys) {
+       // Record order of parameters in new data-mw
+       var newOrder = new Map(Array.from(dataMwKeys).map(function(key, i) {
+               return [key, { order: i }];
+       }));
+       // Record order of parameters in original wikitext (from data-parsoid)
+       var origOrder = new Map(dpArgInfo.map(function(argInfo, i) {
+               return [argInfo.k, { order: i, dist: 0 }];
+       }));
+       // Record order of parameters in templatedata (if present)
+       var tplDataOrder = new Map();
+       var keys = [];
+       if (tplData && Array.isArray(tplData.paramOrder)) {
+               var params = tplData.params;
+               tplData.paramOrder.forEach(function(k, i) {
+                       tplDataOrder.set(k, { order: keys.length });
+                       keys.push(k);
+                       // Aliases have the same sort order as the main name.
+                       var aliases = params && params[k] && params[k].aliases;
+                       (aliases || []).forEach(function(a) {
+                               tplDataOrder.set(a, { order: keys.length });
+                               keys.push(a);
+                       });
+               });
+       }
+       // Find the closest "original parameter" for each templatedata 
parameter,
+       // so that newly-added parameters are placed near the parameters which
+       // templatedata says they should be adjacent to.
+       var nearestOrder = new Map(origOrder);
+       var reduceF = function(acc, val, i) {
+               if (origOrder.has(val)) {
+                       acc = origOrder.get(val);
+               }
+               if (!(nearestOrder.has(val) && nearestOrder.get(val).dist < 
acc.dist)) {
+                       nearestOrder.set(val, acc);
+               }
+               return { order: acc.order, dist: acc.dist + 1 };
+       };
+       // Find closest original parameter before the key.
+       keys.reduce(reduceF, { order: -1, dist: 2 * keys.length });
+       // Find closest original parameter after the key.
+       keys.reduceRight(reduceF, { order: origOrder.size, dist: keys.length });
+
+       // Helper function to return a large number if the given key isn't
+       // in the sort order map
+       var big = Math.max(nearestOrder.size, newOrder.size);
+       var defaultGet = function(map, key) {
+               return map.has(key) ? map.get(key).order : big;
+       };
+
+       return function cmp(a, b) {
+               // primary key is `nearestOrder` (nearest original parameter)
+               var aOrder = defaultGet(nearestOrder, a);
+               var bOrder = defaultGet(nearestOrder, b);
+               if (aOrder !== bOrder) { return aOrder - bOrder; }
+               // secondary key is templatedata order
+               aOrder = defaultGet(tplDataOrder, a);
+               bOrder = defaultGet(tplDataOrder, b);
+               if (aOrder !== bOrder) { return aOrder - bOrder; }
+               // tertiary key is original input order (makes sort stable)
+               aOrder = defaultGet(newOrder, a);
+               bOrder = defaultGet(newOrder, b);
+               return aOrder - bOrder;
+       };
+}
+
 // See 
https://github.com/wikimedia/mediawiki-extensions-TemplateData/blob/master/Specification.md
 // for the templatedata specification.
 WSP.serializePart = Promise.method(function(node, type, part, tplData) {
@@ -540,34 +606,8 @@
                        kvMap.set(k, { serializeAsNamed: serializeAsNamed, 
name: name, value: value });
                });
        }).then(function() {
-               var paramOrder = [];
-               if (tplData && Array.isArray(tplData.paramOrder)) {
-                       var params = tplData.params;
-                       // Aliases have the same sort order as the main name
-                       paramOrder = tplData.paramOrder.reduce(function(prev, 
next) {
-                               var aliases = params && params[next] && 
params[next].aliases;
-                               aliases = Array.isArray(aliases) ? aliases : [];
-                               return prev.concat(next, aliases);
-                       }, paramOrder);
-               }
-               var argOrder = [].concat(
-                       // 1. Use tpldata param order as the first guide
-                       //    Note that:
-                       //    (a) template arg names are case-sensitive
-                       //    (b) arg names in tplData.paramOrder already
-                       //        have their spaces trimmed
-                       paramOrder,
-
-                       // 2. Push keys from data-parsoid
-                       //    data-parsoid argInfo also present in data-mw
-                       //    will get processed
-                       dpArgInfo.map(function(argInfo) { return argInfo.k; }),
-
-                       // 3. Push keys from data-mw
-                       //    data-mw entry not present in data-parsoid argInfo
-                       //    will get processed
-                       tplKeysFromDataMw
-               );
+               var argOrder = Array.from(kvMap.keys())
+                       .sort(createParamComparator(dpArgInfo, tplData, 
kvMap.keys()));
 
                var argIndex = 1;
                var numericIndex = 1;
@@ -578,10 +618,6 @@
 
                return argOrder.reduce(function(argBuf, param) {
                        var kv = kvMap.get(param);
-                       if (!kv) {
-                               return argBuf;
-                       }
-
                        // Add nowiki escapes for the arg value, as required
                        var escapedValue = 
self.wteHandlers.escapeTplArgWT(kv.value, {
                                serializeAsNamed: kv.serializeAsNamed || param 
!== numericIndex.toString(),
@@ -599,10 +635,6 @@
                                // No WS trimming for positional args
                                argBuf.push({ dpKey: param, name: null, value: 
escapedValue.v });
                        }
-
-                       // Clear entry for param
-                       // Ensures that a param is serialized exactly once!
-                       kvMap.delete(param);
 
                        return argBuf;
                }, []);
diff --git a/tests/mocha/templatedata.js b/tests/mocha/templatedata.js
index 71c5a1d..5774d3d 100644
--- a/tests/mocha/templatedata.js
+++ b/tests/mocha/templatedata.js
@@ -83,14 +83,14 @@
                },
        },
 
-       // 3. flipped f1 & f2 in data-parsoid
+       // 3. flipped f1 & f2 in data-parsoid + newly added f0
        {
-               'name': 'Enforce param order',
-               'html': '<span about="#mwt1" typeof="mw:Transclusion" 
data-parsoid=' + "'" + '{"pi":[[{"k":"f2"},{"k":"f1"}]]}' + "' data-mw='" + 
'{"parts":[{"template":{"target":{"wt":"NoFormatWithParamOrder","href":"./Template:NoFormatWithParamOrder"},"params":{"f1":{"wt":"foo"},"f2":{"wt":"foo"}},"i":0}}]}'
 + "'" + '>foo</span>',
+               'name': 'Preserve original param order + smart insertion of new 
params',
+               'html': '<span about="#mwt1" typeof="mw:Transclusion" 
data-parsoid=' + "'" + '{"pi":[[{"k":"f2"},{"k":"f1"}]]}' + "' data-mw='" + 
'{"parts":[{"template":{"target":{"wt":"NoFormatWithParamOrder","href":"./Template:NoFormatWithParamOrder"},"params":{"f1":{"wt":"foo"},"f2":{"wt":"foo"},"f0":{"wt":"BOO"}},"i":0}}]}'
 + "'" + '>foo</span>',
                'wt': {
-                       'no_selser':   
'{{NoFormatWithParamOrder|f2=foo|f1=foo}}',
-                       'new_content': 
'{{NoFormatWithParamOrder|f1=foo|f2=foo}}',
-                       'edited':      
'{{NoFormatWithParamOrder|f1=BAR|f2=foo}}',
+                       'no_selser':   
'{{NoFormatWithParamOrder|f2=foo|f1=foo|f0=BOO}}',
+                       'new_content': 
'{{NoFormatWithParamOrder|f0=BOO|f1=foo|f2=foo}}',
+                       'edited':      
'{{NoFormatWithParamOrder|f2=foo|f0=BOO|f1=BAR}}', // Preserve partial 
templatedata order
                },
        },
 
@@ -134,7 +134,7 @@
                'wt': {
                        'no_selser':   '{{InlineTplWithParamOrder\n|f2 = 
foo\n|f1 = foo\n}}',
                        'new_content': 
'{{InlineTplWithParamOrder|f1=foo|f2=foo}}',
-                       'edited':      
'{{InlineTplWithParamOrder|f1=BAR|f2=foo}}',
+                       'edited':      
'{{InlineTplWithParamOrder|f2=foo|f1=BAR}}',
                },
        },
 
@@ -145,7 +145,7 @@
                'wt': {
                        'no_selser':   
'{{BlockTplWithParamOrder|f2=foo|f1=foo}}',
                        'new_content': '{{BlockTplWithParamOrder\n| f1 = foo\n| 
f2 = foo\n}}',
-                       'edited':      '{{BlockTplWithParamOrder\n| f1 = BAR\n| 
f2 = foo\n}}',
+                       'edited':      '{{BlockTplWithParamOrder\n| f2 = foo\n| 
f1 = BAR\n}}',
                },
        },
 
@@ -167,7 +167,7 @@
                'wt': {
                        'no_selser':   
'{{BlockTplWithParamOrder|f2=foo|f1=foo}}SOME TEXT{{InlineTplNoParamOrder\n|f2 
= foo\n|f1 = foo\n}}',
                        'new_content': '{{BlockTplWithParamOrder\n| f1 = foo\n| 
f2 = foo\n}}SOME TEXT{{InlineTplNoParamOrder|f1=foo|f2=foo}}',
-                       'edited':      '{{BlockTplWithParamOrder\n| f1 = BAR\n| 
f2 = foo\n}}SOME TEXT{{InlineTplNoParamOrder|f2=foo|f1=foo}}',
+                       'edited':      '{{BlockTplWithParamOrder\n| f2 = foo\n| 
f1 = BAR\n}}SOME TEXT{{InlineTplNoParamOrder|f2=foo|f1=foo}}',
                },
        },
 
@@ -181,7 +181,18 @@
                        'edited':      
'{{WithParamOrderAndAliases|f3=foo|f2=BAR}}',
                },
        },
-       // 12. Inline Formatted template 1
+       // 12. Alias sort order, with both original and alias params
+       // Even aliased parameters should appear in the original order by 
default.
+       {
+               'name': 'Enforce param order with aliases (aliases in original 
order)',
+               'html': '<span about="#mwt1" typeof="mw:Transclusion" 
data-parsoid=' + "'" + '{"pi":[[{"k":"f4"},{"k":"f3"},{"k":"f1"}]]}' + "' 
data-mw='" + 
'{"parts":[{"template":{"target":{"wt":"WithParamOrderAndAliases","href":"./Template:WithParamOrderAndAliases"},"params":{"f4":{"wt":"foo"},"f3":{"wt":"foo"},"f1":{"wt":"foo"}},"i":0}}]}'
 + "'" + '>foo</span>',
+               'wt': {
+                       'no_selser':   
'{{WithParamOrderAndAliases|f4=foo|f3=foo|f1=foo}}',
+                       'new_content': 
'{{WithParamOrderAndAliases|f1=foo|f4=foo|f3=foo}}',
+                       'edited':      
'{{WithParamOrderAndAliases|f4=BAR|f3=foo|f1=foo}}',
+               },
+       },
+       // 13. Inline Formatted template 1
        {
                'html': 'x <span about="#mwt1" typeof="mw:Transclusion" 
data-parsoid=' + "'" + '{"pi":[[{"k":"f1"},{"k":"x"}]]}' + "' data-mw='" + 
'{"parts":[{"template":{"target":{"wt":"InlineFormattedTpl_1","href":"./Template:InlineFormattedTpl_1"},"params":{"f1":{"wt":""},"x":{"wt":"foo"}},"i":0}}]}'
 + "'" + '>something</span> y',
                'wt': {
@@ -190,7 +201,7 @@
                        'edited':      'x {{InlineFormattedTpl_1|f1=|x=BAR}} y',
                },
        },
-       // 13. Inline Formatted template 2
+       // 14. Inline Formatted template 2
        {
                'html': 'x <span about="#mwt1" typeof="mw:Transclusion" 
data-parsoid=' + "'" + '{"pi":[[{"k":"f1"},{"k":"x"}]]}' + "' data-mw='" + 
'{"parts":[{"template":{"target":{"wt":"InlineFormattedTpl_2","href":"./Template:InlineFormattedTpl_2"},"params":{"f1":{"wt":""},"x":{"wt":"foo"}},"i":0}}]}'
 + "'" + '>something</span> y',
                'wt': {
@@ -199,7 +210,7 @@
                        'edited':      'x \n{{InlineFormattedTpl_2 | f1 =  | x 
= BAR}} y',
                },
        },
-       // 14. Inline Formatted template 3
+       // 15. Inline Formatted template 3
        {
                'html': 'x <span about="#mwt1" typeof="mw:Transclusion" 
data-parsoid=' + "'" + '{"pi":[[{"k":"f1"},{"k":"x"}]]}' + "' data-mw='" + 
'{"parts":[{"template":{"target":{"wt":"InlineFormattedTpl_3","href":"./Template:InlineFormattedTpl_3"},"params":{"f1":{"wt":""},"x":{"wt":"foo"}},"i":0}}]}'
 + "'" + '>something</span> y',
                'wt': {
@@ -208,7 +219,7 @@
                        'edited':      'x {{InlineFormattedTpl_3| f1    = | x   
  = BAR}} y',
                },
        },
-       // 15. Custom block formatting 1
+       // 16. Custom block formatting 1
        {
                'html': 'x<span about="#mwt1" typeof="mw:Transclusion" 
data-parsoid=' + "'" + '{"pi":[[{"k":"f1"},{"k":"f2"}]]}' + "' data-mw='" + 
'{"parts":[{"template":{"target":{"wt":"BlockFormattedTpl_1","href":"./Template:BlockFormattedTpl_1"},"params":{"f1":{"wt":""},"f2":{"wt":"foo"}},"i":0}}]}'
 + "'" + '>something</span>y',
                'wt': {
@@ -217,7 +228,7 @@
                        'edited':      'x{{BlockFormattedTpl_1\n| f1 = \n| f2 = 
BAR\n}}y', // normalized
                },
        },
-       // 16. Custom block formatting 2
+       // 17. Custom block formatting 2
        {
                'html': 'x<span about="#mwt1" typeof="mw:Transclusion" 
data-parsoid=' + "'" + '{"pi":[[{"k":"f1"},{"k":"f2"}]]}' + "' data-mw='" + 
'{"parts":[{"template":{"target":{"wt":"BlockFormattedTpl_2","href":"./Template:BlockFormattedTpl_2"},"params":{"f1":{"wt":""},"f2":{"wt":"foo"}},"i":0}}]}'
 + "'" + '>something</span>y',
                'wt': {
@@ -226,7 +237,7 @@
                        'edited':      'x\n{{BlockFormattedTpl_2\n| f1 = \n| f2 
= BAR\n}}\ny', // normalized
                },
        },
-       // 17. Custom block formatting 3
+       // 18. Custom block formatting 3
        {
                'html': 'x<span about="#mwt1" typeof="mw:Transclusion" 
data-parsoid=' + "'" + '{"pi":[[{"k":"f1"},{"k":"f2"}]]}' + "' data-mw='" + 
'{"parts":[{"template":{"target":{"wt":"BlockFormattedTpl_3","href":"./Template:BlockFormattedTpl_3"},"params":{"f1":{"wt":""},"f2":{"wt":"foo"}},"i":0}}]}'
 + "'" + '>something</span>y',
                'wt': {
diff --git a/tests/mockAPI.js b/tests/mockAPI.js
index 81c58e0..1e16673 100644
--- a/tests/mockAPI.js
+++ b/tests/mockAPI.js
@@ -269,7 +269,7 @@
 // paramOrder fields at this point, so keeping these lean.
 var templateData = {
        'Template:NoFormatWithParamOrder': {
-               'paramOrder': ['unused1', 'f1', 'unused2', 'f2', 'unused3'],
+               'paramOrder': ['f0', 'f1', 'unused2', 'f2', 'unused3'],
        },
        'Template:InlineTplNoParamOrder': {
                'format': 'inline',
@@ -287,7 +287,7 @@
        },
        'Template:WithParamOrderAndAliases': {
                'params': {
-                       'f1': { 'aliases': ['f3'] }
+                       'f1': { 'aliases': ['f4','f3'] }
                },
                'paramOrder': ['f1','f2'],
        },

-- 
To view, visit https://gerrit.wikimedia.org/r/389852
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I2b269d5bfb08efbe56b9293c2aa3e810fb1b29da
Gerrit-PatchSet: 9
Gerrit-Project: mediawiki/services/parsoid
Gerrit-Branch: master
Gerrit-Owner: Subramanya Sastry <[email protected]>
Gerrit-Reviewer: Arlolra <[email protected]>
Gerrit-Reviewer: C. Scott Ananian <[email protected]>
Gerrit-Reviewer: Deskana <[email protected]>
Gerrit-Reviewer: Jforrester <[email protected]>
Gerrit-Reviewer: Sbailey <[email protected]>
Gerrit-Reviewer: Subramanya Sastry <[email protected]>
Gerrit-Reviewer: Thiemo Mättig (WMDE) <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to