messageformat
Version:
Intl.MessageFormat / Unicode MessageFormat 2 parser, runtime and polyfill
123 lines (122 loc) • 3.81 kB
JavaScript
import { isValidUnquotedLiteral } from "../cst/names.js";
import { isLiteral, isPatternMessage, isSelectMessage, isVariableRef } from "./type-guards.js";
/**
* Stringify a {@link Message} using its syntax representation.
* Parsing and stringifying a message will not necessarily produce
* exactly the same syntax representation.
*
* @category Message Data Model
*/
export function stringifyMessage(msg) {
let res = '';
for (const decl of msg.declarations)
res += stringifyDeclaration(decl);
if (isPatternMessage(msg)) {
res += stringifyPattern(msg.pattern, !!res);
}
else if (isSelectMessage(msg)) {
res += '.match';
for (const sel of msg.selectors)
res += ' ' + stringifyVariableRef(sel);
for (const { keys, value } of msg.variants) {
res += '\n';
for (const key of keys) {
res += (isLiteral(key) ? stringifyLiteral(key) : '*') + ' ';
}
res += stringifyPattern(value, true);
}
}
return res;
}
function stringifyDeclaration(decl) {
switch (decl.type) {
case 'input':
return `.input ${stringifyExpression(decl.value)}\n`;
case 'local':
return `.local $${decl.name} = ${stringifyExpression(decl.value)}\n`;
}
// @ts-expect-error Guard against non-TS users with bad data
throw new Error(`Unsupported ${decl.type} declaration`);
}
function stringifyFunctionRef({ name, options }) {
let res = `:${name}`;
if (options) {
for (const [key, value] of options) {
res += ' ' + stringifyOption(key, value);
}
}
return res;
}
function stringifyMarkup({ kind, name, options, attributes }) {
let res = kind === 'close' ? '{/' : '{#';
res += name;
if (options) {
for (const [name, value] of options) {
res += ' ' + stringifyOption(name, value);
}
}
if (attributes) {
for (const [name, value] of attributes) {
res += ' ' + stringifyAttribute(name, value);
}
}
res += kind === 'standalone' ? ' /}' : '}';
return res;
}
function stringifyLiteral({ value }) {
if (isValidUnquotedLiteral(value))
return value;
const esc = value.replace(/\\/g, '\\\\').replace(/\|/g, '\\|');
return `|${esc}|`;
}
function stringifyOption(name, value) {
const valueStr = isVariableRef(value)
? stringifyVariableRef(value)
: stringifyLiteral(value);
return `${name}=${valueStr}`;
}
function stringifyAttribute(name, value) {
return value === true ? `@${name}` : `@${name}=${stringifyLiteral(value)}`;
}
function stringifyPattern(pattern, quoted) {
let res = '';
if (!quoted && typeof pattern[0] === 'string' && /^\s*\./.test(pattern[0])) {
quoted = true;
}
for (const el of pattern) {
if (typeof el === 'string')
res += el.replace(/[\\{}]/g, '\\$&');
else if (el.type === 'markup')
res += stringifyMarkup(el);
else
res += stringifyExpression(el);
}
return quoted ? `{{${res}}}` : res;
}
function stringifyExpression({ arg, attributes, functionRef }) {
let res;
switch (arg?.type) {
case 'literal':
res = stringifyLiteral(arg);
break;
case 'variable':
res = stringifyVariableRef(arg);
break;
default:
res = '';
}
if (functionRef) {
if (res)
res += ' ';
res += stringifyFunctionRef(functionRef);
}
if (attributes) {
for (const [name, value] of attributes) {
res += ' ' + stringifyAttribute(name, value);
}
}
return `{${res}}`;
}
function stringifyVariableRef(ref) {
return '$' + ref.name;
}