// String methods based on https://github.com/cburgmer/xmlserializer/blob/master/xmlserializer.js export const removeInvalidXMLCharacters = (content: string) => // See http://www.w3.org/TR/xml/#NT-Char for valid XML 1.0 characters content.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, ''); export const serializeAttributeValue = (value: string) => value .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); export const serializeTextContent = (content: string) => content .replace(/&/g, '&') .replace(//g, '>'); export const serializeAttribute = (name: string, value: string) => name + '="' + serializeAttributeValue(value) + '"'; export const indent = (text: string, size: number) => text.split('\n').map(line => `${' '.repeat(size)}${line}`).join('\n') export enum XMLTagType { DocumentRoot = '#document', XMLDeclaration = '#declaration', Element = '#element', Text = '#text', Comment = '#comment', CData = '#cdata-section', } export abstract class BaseXMLTag { public abstract readonly tagType: XMLTagType; public abstract toString(): string; } export abstract class XMLTagWithAttributes extends BaseXMLTag { public attributes = new Map(); protected get attributeString() { let str = ''; for (const segment of this.attributes.entries().map(([k, v]) => serializeAttribute(k, v))) { if (str && str.length + segment.length > 60) str += `\n${indent(segment, 2)}`; else str += ` ${segment}`; } return str; } public attribute(name: string, value: string) { this.attributes.set(name, value); return this; } public getAttribute(name: string) { return this.attributes.get(name); } } export abstract class XMLTagWithChildrenAndAttributes extends XMLTagWithAttributes { public children: BaseXMLTag[] = []; protected get contentString() { return this.children.map(v => v.toString()).join('\n'); } public child(...children: BaseXMLTag[]) { this.children.push(...children); return this; } } export class XMLDocumentRoot extends XMLTagWithChildrenAndAttributes { public readonly tagType = XMLTagType.DocumentRoot as const; public toString(): string { return removeInvalidXMLCharacters(this.contentString); } } export class XMLDeclaration extends XMLTagWithChildrenAndAttributes { public readonly tagType = XMLTagType.XMLDeclaration as const; public toString(): string { return ``; } public version(version = "1.0") { return this.attribute("version", version) } public encoding(encoding = "UTF-8") { return this.attribute("encoding", encoding); } }; export class XMLElement extends XMLTagWithChildrenAndAttributes { public readonly tagType = XMLTagType.Element as const; public constructor(public tagName: string) { super() } public toString(): string { return `<${this.tagName}${this.attributeString}${this.children.length ? `> ${indent(this.contentString, 2)} ` : ' />'}` } } export class XMLRootElement extends XMLElement { /** * @param subns Leave as empty string for root namespace */ public xmlns(subns: string, value: string) { this.attribute(`xmlns${subns ? `:${subns}` : ''}`, value) return this; } } export class XMLText extends BaseXMLTag { public tagType = XMLTagType.Text as const; public constructor(public text: string) { super() } public toString(): string { return serializeTextContent(this.text); } } export class XMLComment extends BaseXMLTag { public tagType = XMLTagType.Comment as const; public constructor( public text: string, /** Set to 0 for no wrapping at all */ public wrapAt = 70, public addSpacesAroundCommentTags = true ) { super() } public toString(): string { let text = this.text; if (this.wrapAt) { const lines = text.split('\n') const newLines = [] as string[] if (lines.length === 1 && lines[0].length < this.wrapAt) { if (this.addSpacesAroundCommentTags) newLines[0] = ` ${text} ` } else for (let line of lines) while (line.length) { newLines.push(line.substring(0, this.wrapAt)) line = line.substring(this.wrapAt) } } else if (this.addSpacesAroundCommentTags) this.text = ` ${this.text} ` return ``; } } export class XMLCData extends BaseXMLTag { public tagType = XMLTagType.CData as const; public constructor(public content: string) { super() } public toString(): string { return `/gu, ']]]]>')}]]>`; } } export type XMLTag = XMLDocumentRoot | XMLDeclaration | XMLElement | XMLComment;