2016-07-15 09:42:33 -07:00
|
|
|
/**
|
|
|
|
|
* @license
|
|
|
|
|
* Copyright Google Inc. All Rights Reserved.
|
|
|
|
|
*
|
|
|
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
|
|
|
* found in the LICENSE file at https://angular.io/license
|
|
|
|
|
*/
|
|
|
|
|
|
2016-08-01 12:19:09 -07:00
|
|
|
import * as html from '../../ml_parser/ast';
|
2016-10-28 19:53:42 -07:00
|
|
|
import {decimalDigest} from '../digest';
|
2016-07-21 13:56:58 -07:00
|
|
|
import * as i18n from '../i18n_ast';
|
2016-08-09 21:05:04 -07:00
|
|
|
import {MessageBundle} from '../message_bundle';
|
2016-07-15 09:42:33 -07:00
|
|
|
|
|
|
|
|
import {Serializer} from './serializer';
|
|
|
|
|
import * as xml from './xml_helper';
|
|
|
|
|
|
|
|
|
|
const _MESSAGES_TAG = 'messagebundle';
|
|
|
|
|
const _MESSAGE_TAG = 'msg';
|
|
|
|
|
const _PLACEHOLDER_TAG = 'ph';
|
|
|
|
|
const _EXEMPLE_TAG = 'ex';
|
|
|
|
|
|
2016-07-29 13:07:01 -07:00
|
|
|
const _DOCTYPE = `<!ELEMENT messagebundle (msg)*>
|
|
|
|
|
<!ATTLIST messagebundle class CDATA #IMPLIED>
|
|
|
|
|
|
|
|
|
|
<!ELEMENT msg (#PCDATA|ph|source)*>
|
|
|
|
|
<!ATTLIST msg id CDATA #IMPLIED>
|
|
|
|
|
<!ATTLIST msg seq CDATA #IMPLIED>
|
|
|
|
|
<!ATTLIST msg name CDATA #IMPLIED>
|
|
|
|
|
<!ATTLIST msg desc CDATA #IMPLIED>
|
|
|
|
|
<!ATTLIST msg meaning CDATA #IMPLIED>
|
|
|
|
|
<!ATTLIST msg obsolete (obsolete) #IMPLIED>
|
|
|
|
|
<!ATTLIST msg xml:space (default|preserve) "default">
|
|
|
|
|
<!ATTLIST msg is_hidden CDATA #IMPLIED>
|
|
|
|
|
|
|
|
|
|
<!ELEMENT source (#PCDATA)>
|
|
|
|
|
|
|
|
|
|
<!ELEMENT ph (#PCDATA|ex)*>
|
|
|
|
|
<!ATTLIST ph name CDATA #REQUIRED>
|
|
|
|
|
|
|
|
|
|
<!ELEMENT ex (#PCDATA)>`;
|
|
|
|
|
|
2016-07-21 13:56:58 -07:00
|
|
|
export class Xmb implements Serializer {
|
2016-10-28 19:53:42 -07:00
|
|
|
write(messages: i18n.Message[]): string {
|
2016-07-15 09:42:33 -07:00
|
|
|
const visitor = new _Visitor();
|
2016-11-01 18:02:29 -07:00
|
|
|
const visited: {[id: string]: boolean} = {};
|
|
|
|
|
let rootNode = new xml.Tag(_MESSAGES_TAG);
|
2016-07-15 09:42:33 -07:00
|
|
|
|
2016-10-28 19:53:42 -07:00
|
|
|
messages.forEach(message => {
|
2016-11-01 18:02:29 -07:00
|
|
|
const id = this.digest(message);
|
|
|
|
|
|
|
|
|
|
// deduplicate messages
|
|
|
|
|
if (visited[id]) return;
|
|
|
|
|
visited[id] = true;
|
|
|
|
|
|
|
|
|
|
const attrs: {[k: string]: string} = {id};
|
2016-07-15 09:42:33 -07:00
|
|
|
|
|
|
|
|
if (message.description) {
|
|
|
|
|
attrs['desc'] = message.description;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (message.meaning) {
|
|
|
|
|
attrs['meaning'] = message.meaning;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rootNode.children.push(
|
2016-09-30 14:52:12 -07:00
|
|
|
new xml.CR(2), new xml.Tag(_MESSAGE_TAG, attrs, visitor.serialize(message.nodes)));
|
2016-07-15 09:42:33 -07:00
|
|
|
});
|
|
|
|
|
|
2016-09-30 14:52:12 -07:00
|
|
|
rootNode.children.push(new xml.CR());
|
|
|
|
|
|
2016-07-15 09:42:33 -07:00
|
|
|
return xml.serialize([
|
2016-07-29 13:07:01 -07:00
|
|
|
new xml.Declaration({version: '1.0', encoding: 'UTF-8'}),
|
2016-09-30 14:52:12 -07:00
|
|
|
new xml.CR(),
|
2016-07-29 13:07:01 -07:00
|
|
|
new xml.Doctype(_MESSAGES_TAG, _DOCTYPE),
|
2016-09-30 14:52:12 -07:00
|
|
|
new xml.CR(),
|
2016-07-15 09:42:33 -07:00
|
|
|
rootNode,
|
2016-09-30 14:52:12 -07:00
|
|
|
new xml.CR(),
|
2016-07-15 09:42:33 -07:00
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-09 21:05:04 -07:00
|
|
|
load(content: string, url: string, messageBundle: MessageBundle): {[id: string]: html.Node[]} {
|
2016-07-21 13:56:58 -07:00
|
|
|
throw new Error('Unsupported');
|
|
|
|
|
}
|
2016-10-28 19:53:42 -07:00
|
|
|
|
|
|
|
|
digest(message: i18n.Message): string { return digest(message); }
|
2016-07-15 09:42:33 -07:00
|
|
|
}
|
|
|
|
|
|
2016-07-21 13:56:58 -07:00
|
|
|
class _Visitor implements i18n.Visitor {
|
|
|
|
|
visitText(text: i18n.Text, context?: any): xml.Node[] { return [new xml.Text(text.value)]; }
|
2016-07-15 09:42:33 -07:00
|
|
|
|
2016-07-21 13:56:58 -07:00
|
|
|
visitContainer(container: i18n.Container, context?: any): xml.Node[] {
|
2016-07-15 09:42:33 -07:00
|
|
|
const nodes: xml.Node[] = [];
|
2016-07-21 13:56:58 -07:00
|
|
|
container.children.forEach((node: i18n.Node) => nodes.push(...node.visit(this)));
|
2016-07-15 09:42:33 -07:00
|
|
|
return nodes;
|
|
|
|
|
}
|
|
|
|
|
|
2016-07-21 13:56:58 -07:00
|
|
|
visitIcu(icu: i18n.Icu, context?: any): xml.Node[] {
|
2016-07-15 09:42:33 -07:00
|
|
|
const nodes = [new xml.Text(`{${icu.expression}, ${icu.type}, `)];
|
|
|
|
|
|
2016-08-04 19:35:41 +02:00
|
|
|
Object.keys(icu.cases).forEach((c: string) => {
|
2016-08-11 14:49:53 -07:00
|
|
|
nodes.push(new xml.Text(`${c} {`), ...icu.cases[c].visit(this), new xml.Text(`} `));
|
2016-07-15 09:42:33 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
nodes.push(new xml.Text(`}`));
|
|
|
|
|
|
|
|
|
|
return nodes;
|
|
|
|
|
}
|
|
|
|
|
|
2016-07-21 13:56:58 -07:00
|
|
|
visitTagPlaceholder(ph: i18n.TagPlaceholder, context?: any): xml.Node[] {
|
2016-07-15 09:42:33 -07:00
|
|
|
const startEx = new xml.Tag(_EXEMPLE_TAG, {}, [new xml.Text(`<${ph.tag}>`)]);
|
|
|
|
|
const startTagPh = new xml.Tag(_PLACEHOLDER_TAG, {name: ph.startName}, [startEx]);
|
|
|
|
|
if (ph.isVoid) {
|
|
|
|
|
// void tags have no children nor closing tags
|
|
|
|
|
return [startTagPh];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const closeEx = new xml.Tag(_EXEMPLE_TAG, {}, [new xml.Text(`</${ph.tag}>`)]);
|
|
|
|
|
const closeTagPh = new xml.Tag(_PLACEHOLDER_TAG, {name: ph.closeName}, [closeEx]);
|
|
|
|
|
|
|
|
|
|
return [startTagPh, ...this.serialize(ph.children), closeTagPh];
|
|
|
|
|
}
|
|
|
|
|
|
2016-07-21 13:56:58 -07:00
|
|
|
visitPlaceholder(ph: i18n.Placeholder, context?: any): xml.Node[] {
|
2016-07-15 09:42:33 -07:00
|
|
|
return [new xml.Tag(_PLACEHOLDER_TAG, {name: ph.name})];
|
|
|
|
|
}
|
|
|
|
|
|
2016-07-21 13:56:58 -07:00
|
|
|
visitIcuPlaceholder(ph: i18n.IcuPlaceholder, context?: any): xml.Node[] {
|
2016-07-15 09:42:33 -07:00
|
|
|
return [new xml.Tag(_PLACEHOLDER_TAG, {name: ph.name})];
|
|
|
|
|
}
|
|
|
|
|
|
2016-07-21 13:56:58 -07:00
|
|
|
serialize(nodes: i18n.Node[]): xml.Node[] {
|
2016-10-31 17:26:54 -07:00
|
|
|
return [].concat(...nodes.map(node => node.visit(this)));
|
2016-07-15 09:42:33 -07:00
|
|
|
}
|
|
|
|
|
}
|
2016-10-28 19:53:42 -07:00
|
|
|
|
|
|
|
|
export function digest(message: i18n.Message): string {
|
|
|
|
|
return decimalDigest(message);
|
|
|
|
|
}
|