2016-08-15 22:21:40 -07:00

103 lines
2.8 KiB
TypeScript

/**
* @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
*/
export interface IVisitor {
visitTag(tag: Tag): any;
visitText(text: Text): any;
visitDeclaration(decl: Declaration): any;
visitDoctype(doctype: Doctype): any;
}
class _Visitor implements IVisitor {
visitTag(tag: Tag): string {
const strAttrs = this._serializeAttributes(tag.attrs);
if (tag.children.length == 0) {
return `<${tag.name}${strAttrs}/>`;
}
const strChildren = tag.children.map(node => node.visit(this));
return `<${tag.name}${strAttrs}>${strChildren.join('')}</${tag.name}>`;
}
visitText(text: Text): string { return text.value; }
visitDeclaration(decl: Declaration): string {
return `<?xml${this._serializeAttributes(decl.attrs)} ?>`;
}
private _serializeAttributes(attrs: {[k: string]: string}) {
const strAttrs = Object.keys(attrs).map((name: string) => `${name}="${attrs[name]}"`).join(' ');
return strAttrs.length > 0 ? ' ' + strAttrs : '';
}
visitDoctype(doctype: Doctype): any {
return `<!DOCTYPE ${doctype.rootTag} [\n${doctype.dtd}\n]>`;
}
}
const _visitor = new _Visitor();
export function serialize(nodes: Node[]): string {
return nodes.map((node: Node): string => node.visit(_visitor)).join('');
}
export interface Node { visit(visitor: IVisitor): any; }
export class Declaration implements Node {
public attrs: {[k: string]: string} = {};
constructor(unescapedAttrs: {[k: string]: string}) {
Object.keys(unescapedAttrs).forEach((k: string) => {
this.attrs[k] = _escapeXml(unescapedAttrs[k]);
});
}
visit(visitor: IVisitor): any { return visitor.visitDeclaration(this); }
}
export class Doctype implements Node {
constructor(public rootTag: string, public dtd: string){};
visit(visitor: IVisitor): any { return visitor.visitDoctype(this); }
}
export class Tag implements Node {
public attrs: {[k: string]: string} = {};
constructor(
public name: string, unescapedAttrs: {[k: string]: string} = {},
public children: Node[] = []) {
Object.keys(unescapedAttrs).forEach((k: string) => {
this.attrs[k] = _escapeXml(unescapedAttrs[k]);
});
}
visit(visitor: IVisitor): any { return visitor.visitTag(this); }
}
export class Text implements Node {
value: string;
constructor(unescapedValue: string) { this.value = _escapeXml(unescapedValue); };
visit(visitor: IVisitor): any { return visitor.visitText(this); }
}
const _ESCAPED_CHARS: [RegExp, string][] = [
[/&/g, '&amp;'],
[/"/g, '&quot;'],
[/'/g, '&apos;'],
[/</g, '&lt;'],
[/>/g, '&gt;'],
];
function _escapeXml(text: string): string {
return _ESCAPED_CHARS.reduce(
(text: string, entry: [RegExp, string]) => text.replace(entry[0], entry[1]), text);
}