diff --git a/modules/@angular/common/src/pipes/replace_pipe.ts b/modules/@angular/common/src/pipes/replace_pipe.ts index 24cfdc41b8..4ae89c1925 100644 --- a/modules/@angular/common/src/pipes/replace_pipe.ts +++ b/modules/@angular/common/src/pipes/replace_pipe.ts @@ -51,14 +51,14 @@ export class ReplacePipe implements PipeTransform { if (!this._supportedReplacement(replacement)) { throw new InvalidPipeArgumentException(ReplacePipe, replacement); } - // template fails with literal RegExp e.g /pattern/igm - // var rgx = pattern instanceof RegExp ? pattern : RegExpWrapper.create(pattern); if (isFunction(replacement)) { - var rgxPattern = isString(pattern) ? RegExpWrapper.create(pattern) : pattern; + const rgxPattern = isString(pattern) ? RegExpWrapper.create(pattern) : pattern; - return StringWrapper.replaceAllMapped(input, rgxPattern, replacement); + return StringWrapper.replaceAllMapped( + input, rgxPattern, <(m: string[]) => string>replacement); } + if (pattern instanceof RegExp) { // use the replaceAll variant return StringWrapper.replaceAll(input, pattern, replacement); diff --git a/modules/@angular/compiler/src/i18n/xmb_serializer.ts b/modules/@angular/compiler/src/i18n/xmb_serializer.ts index ba83ea7b9d..7fec593689 100644 --- a/modules/@angular/compiler/src/i18n/xmb_serializer.ts +++ b/modules/@angular/compiler/src/i18n/xmb_serializer.ts @@ -85,8 +85,9 @@ function _id(el: HtmlElementAst): string { } function _serializeMessage(m: Message): string { - let desc = isPresent(m.description) ? ` desc='${m.description}'` : ''; - return `${m.content}`; + 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 { @@ -95,3 +96,15 @@ function _expandPlaceholder(input: string): string { 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); +} diff --git a/modules/@angular/compiler/test/i18n/xmb_serializer_spec.ts b/modules/@angular/compiler/test/i18n/xmb_serializer_spec.ts index b78e0308c8..936505b875 100644 --- a/modules/@angular/compiler/test/i18n/xmb_serializer_spec.ts +++ b/modules/@angular/compiler/test/i18n/xmb_serializer_spec.ts @@ -10,16 +10,23 @@ export function main() { it('should return an empty message bundle for an empty list of messages', () => { expect(serializeXmb([])).toEqual(''); }); - it('should serializeXmb messages without desc', () => { - let m = new Message('content', 'meaning', null); + it('should serialize messages without desc nor meaning', () => { + let m = new Message('content', null, null); let expected = `content`; expect(serializeXmb([m])).toEqual(expected); }); - it('should serializeXmb messages with desc', () => { + it('should serialize messages with desc and meaning', () => { let m = new Message('content', 'meaning', 'description'); let expected = - `content`; + `content`; + expect(serializeXmb([m])).toEqual(expected); + }); + + it('should escape the desc and meaning', () => { + let m = new Message('content', '"\'&<>"\'&<>', '"\'&<>"\'&<>'); + let expected = + `content`; expect(serializeXmb([m])).toEqual(expected); }); }); diff --git a/modules/@angular/facade/src/lang.ts b/modules/@angular/facade/src/lang.ts index 5cb7ae6b46..67e5c1a03f 100644 --- a/modules/@angular/facade/src/lang.ts +++ b/modules/@angular/facade/src/lang.ts @@ -216,7 +216,7 @@ export class StringWrapper { return s.slice(from, to === null ? undefined : to); } - static replaceAllMapped(s: string, from: RegExp, cb: Function): string { + static replaceAllMapped(s: string, from: RegExp, cb: (m: string[]) => string): string { return s.replace(from, function(...matches: any[]) { // Remove offset & string from the result array matches.splice(-2, 2);