feat(ExtractorMerger): allow nested implicit tags

This commit is contained in:
Victor Berchet 2016-08-05 14:14:31 -07:00 committed by Alex Rickabaugh
parent 01bca41168
commit 1b04d70626
2 changed files with 33 additions and 20 deletions

View File

@ -54,8 +54,9 @@ enum _VisitorMode {
*/ */
class _Visitor implements html.Visitor { class _Visitor implements html.Visitor {
// <el i18n>...</el> // <el i18n>...</el>
private _inI18nNode = false; private _inI18nNode: boolean;
private _depth: number = 0; private _depth: number;
private _inImplicitNode: boolean;
// <!--i18n-->...<!--/i18n--> // <!--i18n-->...<!--/i18n-->
private _blockMeaningAndDesc: string; private _blockMeaningAndDesc: string;
@ -64,7 +65,7 @@ class _Visitor implements html.Visitor {
private _inI18nBlock: boolean; private _inI18nBlock: boolean;
// {<icu message>} // {<icu message>}
private _inIcu = false; private _inIcu: boolean;
private _msgCountAtSectionStart: number; private _msgCountAtSectionStart: number;
private _errors: I18nError[]; private _errors: I18nError[];
@ -204,12 +205,15 @@ class _Visitor implements html.Visitor {
this._mayBeAddBlockChildren(el); this._mayBeAddBlockChildren(el);
this._depth++; this._depth++;
const wasInI18nNode = this._inI18nNode; const wasInI18nNode = this._inI18nNode;
const wasInImplicitNode = this._inImplicitNode;
let childNodes: html.Node[]; let childNodes: html.Node[];
// Extract only top level nodes with the (implicit) "i18n" attribute if not in a block or an ICU // Extract only top level nodes with the (implicit) "i18n" attribute if not in a block or an ICU
// message // message
const i18nAttr = _getI18nAttr(el); const i18nAttr = _getI18nAttr(el);
const isImplicitI18n = this._implicitTags.some((tag: string): boolean => el.name === tag); const isImplicit = this._implicitTags.some((tag: string): boolean => el.name === tag);
const isTopLevelImplicit = !wasInImplicitNode && isImplicit;
this._inImplicitNode = this._inImplicitNode || isImplicit;
if (!this._isInTranslatableSection && !this._inIcu) { if (!this._isInTranslatableSection && !this._inIcu) {
if (i18nAttr) { if (i18nAttr) {
@ -217,7 +221,7 @@ class _Visitor implements html.Visitor {
this._inI18nNode = true; this._inI18nNode = true;
const message = this._addMessage(el.children, i18nAttr.value); const message = this._addMessage(el.children, i18nAttr.value);
childNodes = this._translateMessage(el, message); childNodes = this._translateMessage(el, message);
} else if (isImplicitI18n) { } else if (isTopLevelImplicit) {
// implicit translation // implicit translation
this._inI18nNode = true; this._inI18nNode = true;
const message = this._addMessage(el.children); const message = this._addMessage(el.children);
@ -225,7 +229,7 @@ class _Visitor implements html.Visitor {
} }
if (this._mode == _VisitorMode.Extract) { if (this._mode == _VisitorMode.Extract) {
const isTranslatable = i18nAttr || isImplicitI18n; const isTranslatable = i18nAttr || isTopLevelImplicit;
if (isTranslatable) { if (isTranslatable) {
this._openTranslatableSection(el); this._openTranslatableSection(el);
} }
@ -235,7 +239,7 @@ class _Visitor implements html.Visitor {
} }
} }
if (this._mode === _VisitorMode.Merge && !i18nAttr && !isImplicitI18n) { if (this._mode === _VisitorMode.Merge && !i18nAttr && !isTopLevelImplicit) {
childNodes = []; childNodes = [];
el.children.forEach(child => { el.children.forEach(child => {
const visited = child.visit(this, context); const visited = child.visit(this, context);
@ -247,8 +251,7 @@ class _Visitor implements html.Visitor {
}); });
} }
} else { } else {
if (i18nAttr || isImplicitI18n) { if (i18nAttr || isTopLevelImplicit) {
// TODO(vicb): we should probably allow nested implicit element (ie <div>)
this._reportError( this._reportError(
el, 'Could not mark an element as translatable inside a translatable section'); el, 'Could not mark an element as translatable inside a translatable section');
} }
@ -276,6 +279,7 @@ class _Visitor implements html.Visitor {
this._depth--; this._depth--;
this._inI18nNode = wasInI18nNode; this._inI18nNode = wasInI18nNode;
this._inImplicitNode = wasInImplicitNode;
if (this._mode === _VisitorMode.Merge) { if (this._mode === _VisitorMode.Merge) {
// There are no childNodes in translatable sections - those nodes will be replace anyway // There are no childNodes in translatable sections - those nodes will be replace anyway
@ -299,6 +303,7 @@ class _Visitor implements html.Visitor {
this._msgCountAtSectionStart = void 0; this._msgCountAtSectionStart = void 0;
this._errors = []; this._errors = [];
this._messages = []; this._messages = [];
this._inImplicitNode = false;
this._createI18nMessage = createI18nMessageFactory(interpolationConfig); this._createI18nMessage = createI18nMessageFactory(interpolationConfig);
} }

View File

@ -213,6 +213,17 @@ export function main() {
[['bold'], '', ''], [['bold'], '', ''],
]); ]);
}); });
it('should allow nested implicit elements', () => {
let result: any[];
expect(() => {result = extract('<div>outer<div>inner</div></div>', ['div'])}).not.toThrow();
expect(result).toEqual([
[['outer', '<ph tag name="START_TAG_DIV">inner</ph name="CLOSE_TAG_DIV">'], '', ''],
]);
});
}); });
describe('implicit attributes', () => { describe('implicit attributes', () => {
@ -288,12 +299,6 @@ export function main() {
}); });
describe('implicit elements', () => { describe('implicit elements', () => {
it('should report nested implicit elements', () => {
expect(extractErrors(`<p><b></b></p>`, ['p', 'b'])).toEqual([
['Could not mark an element as translatable inside a translatable section', '<b>'],
]);
});
it('should report implicit element in translatable element', () => { it('should report implicit element in translatable element', () => {
expect(extractErrors(`<p i18n><b></b></p>`, ['b'])).toEqual([ expect(extractErrors(`<p i18n><b></b></p>`, ['b'])).toEqual([
['Could not mark an element as translatable inside a translatable section', '<b>'], ['Could not mark an element as translatable inside a translatable section', '<b>'],
@ -358,7 +363,7 @@ function parseHtml(html: string): html.Node[] {
const htmlParser = new HtmlParser(); const htmlParser = new HtmlParser();
const parseResult = htmlParser.parse(html, 'extractor spec', true); const parseResult = htmlParser.parse(html, 'extractor spec', true);
if (parseResult.errors.length > 1) { if (parseResult.errors.length > 1) {
throw Error(`unexpected parse errors: ${parseResult.errors.join('\n')}`); throw new Error(`unexpected parse errors: ${parseResult.errors.join('\n')}`);
} }
return parseResult.rootNodes; return parseResult.rootNodes;
} }
@ -390,13 +395,16 @@ function fakeTranslate(
function extract( function extract(
html: string, implicitTags: string[] = [], html: string, implicitTags: string[] = [],
implicitAttrs: {[k: string]: string[]} = {}): [string[], string, string][] { implicitAttrs: {[k: string]: string[]} = {}): [string[], string, string][] {
const messages = const result =
extractMessages(parseHtml(html), DEFAULT_INTERPOLATION_CONFIG, implicitTags, implicitAttrs) extractMessages(parseHtml(html), DEFAULT_INTERPOLATION_CONFIG, implicitTags, implicitAttrs);
.messages;
if (result.errors.length > 0) {
throw new Error(`unexpected errors: ${result.errors.join('\n')}`);
}
// clang-format off // clang-format off
// https://github.com/angular/clang-format/issues/35 // https://github.com/angular/clang-format/issues/35
return messages.map( return result.messages.map(
message => [serializeI18nNodes(message.nodes), message.meaning, message.description, ]) as [string[], string, string][]; message => [serializeI18nNodes(message.nodes), message.meaning, message.description, ]) as [string[], string, string][];
// clang-format on // clang-format on
} }