UNPKG

messageformat

Version:

Intl.MessageFormat / Unicode MessageFormat 2 parser, runtime and polyfill

161 lines (160 loc) 5.14 kB
import { parseNameValue, parseUnquotedLiteralValue } from "./names.js"; // Text ::= (TextChar | TextEscape)+ // TextChar ::= AnyChar - ('{' | '}' | Esc) // AnyChar ::= [#x0-#x10FFFF] // Esc ::= '\' // TextEscape ::= Esc Esc | Esc '{' | Esc '}' export function parseText(ctx, start) { let value = ''; let pos = start; let i = start; loop: for (; i < ctx.source.length; ++i) { switch (ctx.source[i]) { case '\\': { const esc = parseEscape(ctx, i); if (esc) { value += ctx.source.substring(pos, i) + esc.value; i += esc.length; pos = i + 1; } break; } case '{': case '}': break loop; case '\n': if (ctx.resource) { const nl = i; let next = ctx.source[i + 1]; while (next === ' ' || next === '\t') { i += 1; next = ctx.source[i + 1]; } if (i > nl) { value += ctx.source.substring(pos, nl + 1); pos = i + 1; } } break; } } value += ctx.source.substring(pos, i); return { type: 'text', start, end: i, value }; } export function parseLiteral(ctx, start, required) { if (ctx.source[start] === '|') return parseQuotedLiteral(ctx, start); const value = parseUnquotedLiteralValue(ctx.source, start); if (!value) { if (required) ctx.onError('empty-token', start, start); else return undefined; } const end = start + value.length; return { type: 'literal', quoted: false, start, end, value }; } function parseQuotedLiteral(ctx, start) { let value = ''; let pos = start + 1; const open = { start, end: pos, value: '|' }; for (let i = pos; i < ctx.source.length; ++i) { switch (ctx.source[i]) { case '\\': { const esc = parseEscape(ctx, i); if (esc) { value += ctx.source.substring(pos, i) + esc.value; i += esc.length; pos = i + 1; } break; } case '|': value += ctx.source.substring(pos, i); return { type: 'literal', quoted: true, start, end: i + 1, open, value, close: { start: i, end: i + 1, value: '|' } }; case '\n': if (ctx.resource) { const nl = i; let next = ctx.source[i + 1]; while (next === ' ' || next === '\t') { i += 1; next = ctx.source[i + 1]; } if (i > nl) { value += ctx.source.substring(pos, nl + 1); pos = i + 1; } } break; } } value += ctx.source.substring(pos); ctx.onError('missing-syntax', ctx.source.length, '|'); return { type: 'literal', quoted: true, start, end: ctx.source.length, open, value, close: undefined }; } export function parseVariable(ctx, start) { const pos = start + 1; const open = { start, end: pos, value: '$' }; const name = parseNameValue(ctx.source, pos); if (!name) { ctx.onError('empty-token', pos, pos + 1); return { type: 'variable', start, end: pos, open, name: '' }; } return { type: 'variable', start, end: name.end, open, name: name.value }; } function parseEscape(ctx, start) { const raw = ctx.source[start + 1]; if ('\\{|}'.includes(raw)) return { value: raw, length: 1 }; if (ctx.resource) { let hexLen = 0; switch (raw) { case '\t': case ' ': return { value: raw, length: 1 }; case 'n': return { value: '\n', length: 1 }; case 'r': return { value: '\r', length: 1 }; case 't': return { value: '\t', length: 1 }; case 'u': hexLen = 4; break; case 'U': hexLen = 6; break; case 'x': hexLen = 2; break; } if (hexLen > 0) { const h0 = start + 2; const raw = ctx.source.substring(h0, h0 + hexLen); if (raw.length === hexLen && /^[0-9A-Fa-f]+$/.test(raw)) { return { value: String.fromCharCode(parseInt(raw, 16)), length: 1 + hexLen }; } } } ctx.onError('bad-escape', start, start + 2); return null; }