messageformat
Version:
Intl.MessageFormat / Unicode MessageFormat 2 parser, runtime and polyfill
116 lines (115 loc) • 3.84 kB
JavaScript
/**
* Stringify a message CST.
* Does not perform any error checking or validation.
*/
export function stringifyCST(cst) {
let str = '';
if (cst.declarations) {
for (const decl of cst.declarations) {
switch (decl.type) {
case 'input': {
const kw = decl.keyword.value;
const val = stringifyExpression(decl.value);
str += `${kw} ${val}\n`;
break;
}
case 'local': {
const kw = decl.keyword.value;
const tgt = stringifyValue(decl.target);
const eq = decl.equals?.value ?? '=';
const val = stringifyExpression(decl.value);
str += `${kw} ${tgt} ${eq} ${val}\n`;
break;
}
case 'junk':
str += decl.source + '\n';
break;
}
}
}
if (cst.type === 'select') {
str += cst.match.value;
for (const sel of cst.selectors)
str += ' ' + stringifyValue(sel);
for (const { keys, value } of cst.variants) {
str += '\n';
for (const key of keys)
str += stringifyValue(key) + ' ';
str += stringifyPattern(value, true);
}
}
else {
str += stringifyPattern(cst.pattern, cst.type !== 'simple' || !!cst.declarations);
}
return str;
}
function stringifyPattern({ body, braces }, braced) {
let str = braced ? (braces?.[0]?.value ?? '{{') : '';
for (const el of body) {
str +=
el.type === 'text'
? el.value.replace(/[\\{}]/g, '\\$&')
: stringifyExpression(el);
}
if (braced)
str += braces?.[1]?.value ?? '}}';
return str;
}
function stringifyExpression(exp) {
if (exp?.type !== 'expression')
return exp?.source ?? '';
const { braces, arg, functionRef, markup, attributes } = exp;
let str = braces[0]?.value ?? '{';
if (markup) {
str += markup.open.value + stringifyIdentifier(markup.name);
if (markup.type === 'markup') {
for (const opt of markup.options)
str += stringifyOption(opt);
if (markup.close)
str += ' ' + markup.close.value;
}
}
else {
if (arg) {
str += stringifyValue(arg);
if (functionRef)
str += ' ';
}
switch (functionRef?.type) {
case 'function':
str += functionRef.open.value + stringifyIdentifier(functionRef.name);
for (const opt of functionRef.options)
str += stringifyOption(opt);
break;
case 'junk':
str += functionRef.source;
break;
}
}
for (const { name, equals, value } of attributes) {
str += ' @' + stringifyIdentifier(name);
if (value)
str += (equals?.value ?? '=') + stringifyValue(value);
}
return str + (braces[1]?.value ?? '}');
}
const stringifyOption = ({ name, equals, value }) => ' ' +
stringifyIdentifier(name) +
(equals?.value ?? '=') +
stringifyValue(value);
const stringifyIdentifier = (id) => id.map(part => part.value).join('');
function stringifyValue(value) {
switch (value.type) {
case 'variable':
return value.open.value + value.name;
case 'literal':
if (!value.quoted)
return value.value;
return ((value.open?.value ?? '|') +
value.value.replace(/[\\|]/g, '\\$&') +
(value.close?.value ?? '|'));
case '*':
return '*';
}
return value.source ?? '';
}