Skip to content

Commit d7e1175

Browse files
committed
feat(i18n): implement xmb deserialization
1 parent 66cd84e commit d7e1175

File tree

2 files changed

+181
-16
lines changed

2 files changed

+181
-16
lines changed
Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,95 @@
1-
import {isPresent} from 'angular2/src/facade/lang';
1+
import {isPresent, isBlank, RegExpWrapper} from 'angular2/src/facade/lang';
2+
import {HtmlAst, HtmlElementAst} from 'angular2/src/compiler/html_ast';
23
import {Message, id} from './message';
4+
import {HtmlParser} from 'angular2/src/compiler/html_parser';
5+
import {ParseSourceSpan, ParseError} from 'angular2/src/compiler/parse_util';
6+
7+
let _PLACEHOLDER_REGEXP = RegExpWrapper.create(`\\<ph(\\s)+name=("(\\w)+")\\/\\>`);
8+
const _ID_ATTR = "id";
9+
const _MSG_ELEMENT = "msg";
10+
const _BUNDLE_ELEMENT = "message-bundle";
311

412
export function serializeXmb(messages: Message[]): string {
513
let ms = messages.map((m) => _serializeMessage(m)).join("");
614
return `<message-bundle>${ms}</message-bundle>`;
715
}
816

17+
export class XmbDeserializationResult {
18+
constructor(public content: string, public messages: {[key: string]: HtmlAst[]},
19+
public errors: ParseError[]) {}
20+
}
21+
22+
export class XmbDeserializationError extends ParseError {
23+
constructor(span: ParseSourceSpan, msg: string) { super(span, msg); }
24+
}
25+
26+
export function deserializeXmb(content: string, url: string): XmbDeserializationResult {
27+
let parser = new HtmlParser();
28+
let normalizedContent = _expandPlaceholder(content.trim());
29+
let parsed = parser.parse(normalizedContent, url);
30+
31+
if (parsed.errors.length > 0) {
32+
return new XmbDeserializationResult(null, {}, parsed.errors);
33+
}
34+
35+
if (_checkRootElement(parsed.rootNodes)) {
36+
return new XmbDeserializationResult(
37+
null, {}, [new XmbDeserializationError(null, `Missing element "${_BUNDLE_ELEMENT}"`)]);
38+
}
39+
40+
let bundleEl = <HtmlElementAst>parsed.rootNodes[0]; // test this
41+
let errors = [];
42+
let messages: {[key: string]: HtmlAst[]} = {};
43+
44+
_createMessages(bundleEl.children, messages, errors);
45+
46+
return (errors.length == 0) ?
47+
new XmbDeserializationResult(normalizedContent, messages, []) :
48+
new XmbDeserializationResult(null, <{[key: string]: HtmlAst[]}>{}, errors);
49+
}
50+
51+
function _checkRootElement(nodes: HtmlAst[]): boolean {
52+
return nodes.length < 1 || !(nodes[0] instanceof HtmlElementAst) ||
53+
(<HtmlElementAst>nodes[0]).name != _BUNDLE_ELEMENT;
54+
}
55+
56+
function _createMessages(nodes: HtmlAst[], messages: {[key: string]: HtmlAst[]},
57+
errors: ParseError[]): void {
58+
nodes.forEach((item) => {
59+
if (item instanceof HtmlElementAst) {
60+
let msg = <HtmlElementAst>item;
61+
62+
if (msg.name != _MSG_ELEMENT) {
63+
errors.push(
64+
new XmbDeserializationError(item.sourceSpan, `Unexpected element "${msg.name}"`));
65+
return;
66+
}
67+
68+
let id = _id(msg);
69+
if (isBlank(id)) {
70+
errors.push(
71+
new XmbDeserializationError(item.sourceSpan, `"${_ID_ATTR}" attribute is missing`));
72+
return;
73+
}
74+
75+
messages[id] = msg.children;
76+
}
77+
});
78+
}
79+
80+
function _id(el: HtmlElementAst): string {
81+
let ids = el.attrs.filter(a => a.name == _ID_ATTR);
82+
return ids.length > 0 ? ids[0].value : null;
83+
}
84+
985
function _serializeMessage(m: Message): string {
1086
let desc = isPresent(m.description) ? ` desc='${m.description}'` : "";
1187
return `<msg id='${id(m)}'${desc}>${m.content}</msg>`;
12-
}
88+
}
89+
90+
function _expandPlaceholder(input: string): string {
91+
return RegExpWrapper.replaceAll(_PLACEHOLDER_REGEXP, input, (match) => {
92+
let nameWithQuotes = match[2];
93+
return `<ph name=${nameWithQuotes}></ph>`;
94+
});
95+
}

modules/angular2/test/i18n/xmb_serializer_spec.ts

Lines changed: 96 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,25 +11,107 @@ import {
1111
xit
1212
} from 'angular2/testing_internal';
1313

14+
import {HtmlAst} from 'angular2/src/compiler/html_ast';
1415
import {Message, id} from 'angular2/src/i18n/message';
15-
import {serializeXmb} from 'angular2/src/i18n/xmb_serializer';
16+
import {serializeXmb, deserializeXmb} from 'angular2/src/i18n/xmb_serializer';
17+
import {ParseSourceSpan, ParseError} from 'angular2/src/compiler/parse_util';
1618

1719
export function main() {
18-
describe('Xmb Serialization', () => {
19-
it("should return an empty message bundle for an empty list of messages",
20-
() => { expect(serializeXmb([])).toEqual("<message-bundle></message-bundle>"); });
21-
22-
it("should serializeXmb messages without desc", () => {
23-
let m = new Message("content", "meaning", null);
24-
let expected = `<message-bundle><msg id='${id(m)}'>content</msg></message-bundle>`;
25-
expect(serializeXmb([m])).toEqual(expected);
20+
describe("Xmb", () => {
21+
describe('Xmb Serialization', () => {
22+
it("should return an empty message bundle for an empty list of messages",
23+
() => { expect(serializeXmb([])).toEqual("<message-bundle></message-bundle>"); });
24+
25+
it("should serializeXmb messages without desc", () => {
26+
let m = new Message("content", "meaning", null);
27+
let expected = `<message-bundle><msg id='${id(m)}'>content</msg></message-bundle>`;
28+
expect(serializeXmb([m])).toEqual(expected);
29+
});
30+
31+
it("should serializeXmb messages with desc", () => {
32+
let m = new Message("content", "meaning", "description");
33+
let expected =
34+
`<message-bundle><msg id='${id(m)}' desc='description'>content</msg></message-bundle>`;
35+
expect(serializeXmb([m])).toEqual(expected);
36+
});
2637
});
2738

28-
it("should serializeXmb messages with desc", () => {
29-
let m = new Message("content", "meaning", "description");
30-
let expected =
31-
`<message-bundle><msg id='${id(m)}' desc='description'>content</msg></message-bundle>`;
32-
expect(serializeXmb([m])).toEqual(expected);
39+
describe("Xmb Deserialization", () => {
40+
it("should parse an empty bundle", () => {
41+
let mb = "<message-bundle></message-bundle>";
42+
expect(deserializeXmb(mb, "url").messages).toEqual({});
43+
});
44+
45+
it("should parse an non-empty bundle", () => {
46+
let mb = `
47+
<message-bundle>
48+
<msg id="id1" desc="description1">content1</msg>
49+
<msg id="id2">content2</msg>
50+
</message-bundle>
51+
`;
52+
53+
let parsed = deserializeXmb(mb, "url").messages;
54+
expect(_serialize(parsed["id1"])).toEqual("content1");
55+
expect(_serialize(parsed["id2"])).toEqual("content2");
56+
});
57+
58+
it("should error when cannot parse the content", () => {
59+
let mb = `
60+
<message-bundle>
61+
<msg id="id1" desc="description1">content
62+
</message-bundle>
63+
`;
64+
65+
let res = deserializeXmb(mb, "url");
66+
expect(_serializeErrors(res.errors)).toEqual(['Unexpected closing tag "message-bundle"']);
67+
});
68+
69+
it("should error when cannot find the id attribute", () => {
70+
let mb = `
71+
<message-bundle>
72+
<msg>content</msg>
73+
</message-bundle>
74+
`;
75+
76+
let res = deserializeXmb(mb, "url");
77+
expect(_serializeErrors(res.errors)).toEqual(['"id" attribute is missing']);
78+
});
79+
80+
it("should error on empty content", () => {
81+
let mb = ``;
82+
let res = deserializeXmb(mb, "url");
83+
expect(_serializeErrors(res.errors)).toEqual(['Missing element "message-bundle"']);
84+
});
85+
86+
it("should error on an invalid element", () => {
87+
let mb = `
88+
<message-bundle>
89+
<invalid>content</invalid>
90+
</message-bundle>
91+
`;
92+
93+
let res = deserializeXmb(mb, "url");
94+
expect(_serializeErrors(res.errors)).toEqual(['Unexpected element "invalid"']);
95+
});
96+
97+
it("should expand 'ph' elements", () => {
98+
let mb = `
99+
<message-bundle>
100+
<msg id="id1">a<ph name="i0"/></msg>
101+
</message-bundle>
102+
`;
103+
104+
let res = deserializeXmb(mb, "url").messages["id1"];
105+
expect((<any>res[1]).name).toEqual("ph");
106+
});
33107
});
34108
});
35109
}
110+
111+
function _serialize(nodes: HtmlAst[]): string {
112+
return (<any>nodes[0]).value;
113+
}
114+
115+
function _serializeErrors(errors: ParseError[]): string[] {
116+
return errors.map(e => e.msg);
117+
}

0 commit comments

Comments
 (0)