license interface Options { selfClosing ? : boolean ; preserveWhitespace ? : boolean ; } export class XmlFile { private output = '' ; private indent = '' ; private elements : string [ ] = [ ] ; private preservingWhitespace = false ; toString ( ) { return this . output ; } startTag ( name , attributes = { } , { selfClosing = false , preserveWhitespace } = { } ) { if ( ! this . preservingWhitespace ) { this . output += this . indent ; } this . output += `<${ name }` ; Object . keys ( attributes ) . forEach ( attrName => { if ( attributes [ attrName ] ) { this . output += ` ${ attrName }="${ escapeXml ( attributes [ attrName ] ) }"` ; } } ) ; if ( selfClosing ) { this . output += '/>' ; } else { this . output += '>' ; this . elements . push ( name ) ; this . incIndent ( ) ; } if ( preserveWhitespace !== undefined ) { this . preservingWhitespace = preserveWhitespace ; } if ( ! this . preservingWhitespace ) { this . output += `\n` ; } return this ; } endTag ( name , { preserveWhitespace } = { } ) { const expectedTag = this . elements . pop ( ) ; if ( expectedTag !== name ) { throw new Error ( `Unexpected closing tag: "${ name }", expected: "${ expectedTag }"` ) ; } this . decIndent ( ) ; if ( ! this . preservingWhitespace ) { this . output += this . indent ; } this . output += `</${ name }>` ; if ( preserveWhitespace !== undefined ) { this . preservingWhitespace = preserveWhitespace ; } if ( ! this . preservingWhitespace ) { this . output += `\n` ; } return this ; } text ( str ) { this . output += escapeXml ( str ) ; return this ; } startPreserveWhitespace ( ) { this . preservingWhitespace = true ; } stopPreserveWhitespace ( ) { this . preservingWhitespace = false ; } private incIndent ( ) { this . indent = this . indent + '  ' ; } private decIndent ( ) { this . indent = this . indent . slice ( 0 , - 2 ) ; } } const _ESCAPED_CHARS = [ [ /&/g , '&amp;' ] , [ /"/g , '&quot;' ] , [ /'/g , '&apos;' ] , [ /</g , '&lt;' ] , [ />/g , '&gt;' ] ] ; function escapeXml ( text ) { return _ESCAPED_CHARS . reduce ( ( t , entry ) => t . replace ( entry [ 0 ] , entry [ 1 ] ) , text ) ; }