/**
* Serializes language variant markup, like `-{ ... }-`.
* @module
*/
"use strict";
var Consts = require('../config/WikitextConstants.js').WikitextConstants;
var DOMDataUtils = require('../utils/DOMDataUtils.js').DOMDataUtils;
var Promise = require('../utils/promise.js');
var Util = require('../utils/Util.js').Util;
var LanguageVariantText = require('./ConstrainedText.js').LanguageVariantText;
var expandSpArray = function(a) {
var result = [];
if (Array.isArray(a)) {
a.forEach(function(el) {
if (typeof (el) === 'number') {
for (var i = 0; i < el; i++) {
result.push('');
}
} else {
result.push(el);
}
});
}
return result;
};
/**
* @function
* @param {Node} node
* @return {Promise}
*/
var languageVariantHandler = Promise.async(function *(state, node) {
var dataMWV = DOMDataUtils.getJSONAttribute(node, 'data-mw-variant', {});
var dp = DOMDataUtils.getDataParsoid(node);
var flSp = expandSpArray(dp.flSp);
var textSp = expandSpArray(dp.tSp);
var trailingSemi = false;
var text;
var flags;
var originalFlags = (dp.fl || []).reduce(function(m, k, idx) {
if (!m.has(k)) { m.set(k, idx); }
return m;
}, new Map());
var result = '$E|'; // "error" flag
// Backwards-compatibility: `bidir` => `twoway` ; `unidir` => `oneway`
if (dataMWV.bidir) {
dataMWV.twoway = dataMWV.bidir;
delete dataMWV.bidir;
}
if (dataMWV.unidir) {
dataMWV.oneway = dataMWV.undir;
delete dataMWV.unidir;
}
flags = Object.keys(dataMWV).reduce(function(f, k) {
if (Consts.LCNameMap.has(k)) {
f.add(Consts.LCNameMap.get(k));
}
return f;
}, new Set());
var maybeDeleteFlag = function(f) {
if (!originalFlags.has(f)) { flags.delete(f); }
};
// Tweak flag set to account for implicitly-enabled flags.
if (node.tagName !== 'META') {
flags.add('$S');
}
if (!flags.has('$S') && !flags.has('T') && dataMWV.filter === undefined) {
flags.add('H');
}
if (flags.size === 1 && flags.has('$S')) {
maybeDeleteFlag('$S');
} else if (flags.has('D')) {
// Weird: Only way to hide a 'describe' rule is to write -{D;A|...}-
if (flags.has('$S')) {
if (flags.has('A')) {
flags.add('H');
}
flags.delete('A');
} else {
flags.add('A');
flags.delete('H');
}
} else if (flags.has('T')) {
if (flags.has('A') && !flags.has('$S')) {
flags.delete('A');
flags.add('H');
}
} else if (flags.has('A')) {
if (flags.has('$S')) {
maybeDeleteFlag('$S');
} else if (flags.has('H')) {
maybeDeleteFlag('A');
}
} else if (flags.has('R')) {
maybeDeleteFlag('$S');
} else if (flags.has('-')) {
maybeDeleteFlag('H');
}
// Helper function: serialize a DOM string; returns a Promise
var ser = function(t, opts) {
var options = Object.assign({
env: state.env,
onSOL: false
}, opts || {});
return state.serializer.serializeHTML(options, t);
};
// Helper function: protect characters not allowed in language names.
var protectLang = function(l) {
if (/^[a-z][-a-zA-Z]+$/.test(l)) { return l; }
return '<nowiki>' + Util.escapeWtEntities(l) + '</nowiki>';
};
// Helper function: combine the three parts of the -{ }- string
var combine = function(flagStr, bodyStr, useTrailingSemi) {
if (flagStr || /\|/.test(bodyStr)) { flagStr += '|'; }
if (useTrailingSemi !== false) { bodyStr += ';' + useTrailingSemi; }
return flagStr + bodyStr;
};
// Canonicalize combinations of flags.
var sortedFlags = function(flags, noFilter, protectFunc) {
var s = Array.from(flags).filter(function(f) {
// Filter out internal-use-only flags
if (noFilter) { return true; }
return !/^[$]/.test(f);
}).sort(function(a, b) {
var ai = originalFlags.has(a) ? originalFlags.get(a) : -1;
var bi = originalFlags.has(b) ? originalFlags.get(b) : -1;
return ai - bi;
}).map(function(f) {
// Reinsert the original whitespace around the flag (if any)
var i = originalFlags.get(f);
var p = protectFunc ? protectFunc(f) : f;
if (i !== undefined && (2 * i + 1) < flSp.length) {
return flSp[2 * i] + p + flSp[2 * i + 1];
}
return p;
}).join(';');
if (2 * originalFlags.size + 1 === flSp.length) {
if (flSp.length > 1 || s.length) { s += ';'; }
s += flSp[2 * originalFlags.size];
}
return s;
};
if (dataMWV.filter && dataMWV.filter.l) {
// "Restrict possible variants to a limited set"
text = yield ser(dataMWV.filter.t, { protect: /\}-/ });
console.assert(flags.size === 0);
result = combine(
sortedFlags(dataMWV.filter.l, true, protectLang),
text,
false /* no trailing semi */);
} else if (dataMWV.disabled || dataMWV.name) {
// "Raw" / protect contents from language converter
text = yield ser((dataMWV.disabled || dataMWV.name).t, { protect: /\}-/ });
if (!/[:;|]/.test(text)) {
maybeDeleteFlag('R');
}
result = combine(sortedFlags(flags), text, false);
} else if (Array.isArray(dataMWV.twoway)) {
// Two-way rules (most common)
if (textSp.length % 3 === 1) {
trailingSemi = textSp[textSp.length - 1];
}
var b = (dataMWV.twoway[0] && dataMWV.twoway[0].l === '*') ?
dataMWV.twoway.slice(0, 1) :
dataMWV.twoway;
text = (yield Promise.all(b.map(Promise.async(function *(rule, idx) {
var text = yield ser(rule.t, { protect: /;|\}-/ });
if (rule.l === '*') {
trailingSemi = false;
return text;
}
var ws = (3 * idx + 2 < textSp.length) ?
textSp.slice(3 * idx, 3 * (idx + 1)) :
[ (idx > 0) ? ' ' : '', '', '' ];
return ws[0] + protectLang(rule.l) + ws[1] + ':' + ws[2] + text;
})))).join(';');
// suppress output of default flag ('S')
maybeDeleteFlag('$S');
result = combine(sortedFlags(flags), text, trailingSemi);
} else if (Array.isArray(dataMWV.oneway)) {
// One-way rules (uncommon)
if (textSp.length % 4 === 1) {
trailingSemi = textSp[textSp.length - 1];
}
text = (yield Promise.all(dataMWV.oneway.map(Promise.async(function *(rule, idx) {
var from = yield ser(rule.f, { protect: /:|;|=>|\}-/ });
var to = yield ser(rule.t, { protect: /;|\}-/ });
var ws = (4 * idx + 3 < textSp.length) ?
textSp.slice(4 * idx, 4 * (idx + 1)) :
[ '', '', '', '' ];
return ws[0] + from + '=>' + ws[1] + protectLang(rule.l) +
ws[2] + ':' + ws[3] + to;
})))).join(';');
result = combine(sortedFlags(flags), text, trailingSemi);
}
state.emitChunk(new LanguageVariantText('-{' + result + '}-', node), node);
});
if (typeof module === 'object') {
module.exports.languageVariantHandler = languageVariantHandler;
}