import {RegExpWrapper, isBlank, isPresent} from '../facade/lang'; import {HtmlAst, HtmlElementAst} from '../html_ast'; import {HtmlParser} from '../html_parser'; import {ParseError, ParseSourceSpan} from '../parse_util'; import {Message, id} from './message'; let _PLACEHOLDER_REGEXP = RegExpWrapper.create(`\\`); const _ID_ATTR = 'id'; const _MSG_ELEMENT = 'msg'; const _BUNDLE_ELEMENT = 'message-bundle'; export function serializeXmb(messages: Message[]): string { let ms = messages.map((m) => _serializeMessage(m)).join(''); return `${ms}`; } export class XmbDeserializationResult { constructor( public content: string, public messages: {[key: string]: HtmlAst[]}, public errors: ParseError[]) {} } export class XmbDeserializationError extends ParseError { constructor(span: ParseSourceSpan, msg: string) { super(span, msg); } } export function deserializeXmb(content: string, url: string): XmbDeserializationResult { let parser = new HtmlParser(); let normalizedContent = _expandPlaceholder(content.trim()); let parsed = parser.parse(normalizedContent, url); if (parsed.errors.length > 0) { return new XmbDeserializationResult(null, {}, parsed.errors); } if (_checkRootElement(parsed.rootNodes)) { return new XmbDeserializationResult( null, {}, [new XmbDeserializationError(null, `Missing element "${_BUNDLE_ELEMENT}"`)]); } let bundleEl = parsed.rootNodes[0]; // test this let errors: ParseError[] = []; let messages: {[key: string]: HtmlAst[]} = {}; _createMessages(bundleEl.children, messages, errors); return (errors.length == 0) ? new XmbDeserializationResult(normalizedContent, messages, []) : new XmbDeserializationResult(null, <{[key: string]: HtmlAst[]}>{}, errors); } function _checkRootElement(nodes: HtmlAst[]): boolean { return nodes.length < 1 || !(nodes[0] instanceof HtmlElementAst) || (nodes[0]).name != _BUNDLE_ELEMENT; } function _createMessages( nodes: HtmlAst[], messages: {[key: string]: HtmlAst[]}, errors: ParseError[]): void { nodes.forEach((item) => { if (item instanceof HtmlElementAst) { let msg = item; if (msg.name != _MSG_ELEMENT) { errors.push( new XmbDeserializationError(item.sourceSpan, `Unexpected element "${msg.name}"`)); return; } let id = _id(msg); if (isBlank(id)) { errors.push( new XmbDeserializationError(item.sourceSpan, `"${_ID_ATTR}" attribute is missing`)); return; } messages[id] = msg.children; } }); } function _id(el: HtmlElementAst): string { let ids = el.attrs.filter(a => a.name == _ID_ATTR); return ids.length > 0 ? ids[0].value : null; } function _serializeMessage(m: Message): string { const desc = isPresent(m.description) ? ` desc='${_escapeXml(m.description)}'` : ''; const meaning = isPresent(m.meaning) ? ` meaning='${_escapeXml(m.meaning)}'` : ''; return `${m.content}`; } function _expandPlaceholder(input: string): string { return RegExpWrapper.replaceAll(_PLACEHOLDER_REGEXP, input, (match: string[]) => { let nameWithQuotes = match[2]; return ``; }); } const _XML_ESCAPED_CHARS: [RegExp, string][] = [ [/&/g, '&'], [/"/g, '"'], [/'/g, '''], [//g, '>'], ]; function _escapeXml(value: string): string { return _XML_ESCAPED_CHARS.reduce((value, escape) => value.replace(escape[0], escape[1]), value); }