feat(i18n): implement xmb deserialization

This commit is contained in:
vsavkin 2016-03-27 18:30:32 -07:00 committed by Victor Savkin
parent 66cd84e0d5
commit d7e1175df0
2 changed files with 180 additions and 15 deletions

View File

@ -1,12 +1,95 @@
import {isPresent} from 'angular2/src/facade/lang';
import {isPresent, isBlank, RegExpWrapper} from 'angular2/src/facade/lang';
import {HtmlAst, HtmlElementAst} from 'angular2/src/compiler/html_ast';
import {Message, id} from './message';
import {HtmlParser} from 'angular2/src/compiler/html_parser';
import {ParseSourceSpan, ParseError} from 'angular2/src/compiler/parse_util';
let _PLACEHOLDER_REGEXP = RegExpWrapper.create(`\\<ph(\\s)+name=("(\\w)+")\\/\\>`);
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 `<message-bundle>${ms}</message-bundle>`;
}
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 = <HtmlElementAst>parsed.rootNodes[0]; // test this
let errors = [];
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) ||
(<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 = <HtmlElementAst>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 {
let desc = isPresent(m.description) ? ` desc='${m.description}'` : "";
return `<msg id='${id(m)}'${desc}>${m.content}</msg>`;
}
}
function _expandPlaceholder(input: string): string {
return RegExpWrapper.replaceAll(_PLACEHOLDER_REGEXP, input, (match) => {
let nameWithQuotes = match[2];
return `<ph name=${nameWithQuotes}></ph>`;
});
}

View File

@ -11,25 +11,107 @@ import {
xit
} from 'angular2/testing_internal';
import {HtmlAst} from 'angular2/src/compiler/html_ast';
import {Message, id} from 'angular2/src/i18n/message';
import {serializeXmb} from 'angular2/src/i18n/xmb_serializer';
import {serializeXmb, deserializeXmb} from 'angular2/src/i18n/xmb_serializer';
import {ParseSourceSpan, ParseError} from 'angular2/src/compiler/parse_util';
export function main() {
describe('Xmb Serialization', () => {
it("should return an empty message bundle for an empty list of messages",
() => { expect(serializeXmb([])).toEqual("<message-bundle></message-bundle>"); });
describe("Xmb", () => {
describe('Xmb Serialization', () => {
it("should return an empty message bundle for an empty list of messages",
() => { expect(serializeXmb([])).toEqual("<message-bundle></message-bundle>"); });
it("should serializeXmb messages without desc", () => {
let m = new Message("content", "meaning", null);
let expected = `<message-bundle><msg id='${id(m)}'>content</msg></message-bundle>`;
expect(serializeXmb([m])).toEqual(expected);
it("should serializeXmb messages without desc", () => {
let m = new Message("content", "meaning", null);
let expected = `<message-bundle><msg id='${id(m)}'>content</msg></message-bundle>`;
expect(serializeXmb([m])).toEqual(expected);
});
it("should serializeXmb messages with desc", () => {
let m = new Message("content", "meaning", "description");
let expected =
`<message-bundle><msg id='${id(m)}' desc='description'>content</msg></message-bundle>`;
expect(serializeXmb([m])).toEqual(expected);
});
});
it("should serializeXmb messages with desc", () => {
let m = new Message("content", "meaning", "description");
let expected =
`<message-bundle><msg id='${id(m)}' desc='description'>content</msg></message-bundle>`;
expect(serializeXmb([m])).toEqual(expected);
describe("Xmb Deserialization", () => {
it("should parse an empty bundle", () => {
let mb = "<message-bundle></message-bundle>";
expect(deserializeXmb(mb, "url").messages).toEqual({});
});
it("should parse an non-empty bundle", () => {
let mb = `
<message-bundle>
<msg id="id1" desc="description1">content1</msg>
<msg id="id2">content2</msg>
</message-bundle>
`;
let parsed = deserializeXmb(mb, "url").messages;
expect(_serialize(parsed["id1"])).toEqual("content1");
expect(_serialize(parsed["id2"])).toEqual("content2");
});
it("should error when cannot parse the content", () => {
let mb = `
<message-bundle>
<msg id="id1" desc="description1">content
</message-bundle>
`;
let res = deserializeXmb(mb, "url");
expect(_serializeErrors(res.errors)).toEqual(['Unexpected closing tag "message-bundle"']);
});
it("should error when cannot find the id attribute", () => {
let mb = `
<message-bundle>
<msg>content</msg>
</message-bundle>
`;
let res = deserializeXmb(mb, "url");
expect(_serializeErrors(res.errors)).toEqual(['"id" attribute is missing']);
});
it("should error on empty content", () => {
let mb = ``;
let res = deserializeXmb(mb, "url");
expect(_serializeErrors(res.errors)).toEqual(['Missing element "message-bundle"']);
});
it("should error on an invalid element", () => {
let mb = `
<message-bundle>
<invalid>content</invalid>
</message-bundle>
`;
let res = deserializeXmb(mb, "url");
expect(_serializeErrors(res.errors)).toEqual(['Unexpected element "invalid"']);
});
it("should expand 'ph' elements", () => {
let mb = `
<message-bundle>
<msg id="id1">a<ph name="i0"/></msg>
</message-bundle>
`;
let res = deserializeXmb(mb, "url").messages["id1"];
expect((<any>res[1]).name).toEqual("ph");
});
});
});
}
function _serialize(nodes: HtmlAst[]): string {
return (<any>nodes[0]).value;
}
function _serializeErrors(errors: ParseError[]): string[] {
return errors.map(e => e.msg);
}