diff options
feat: a bit of XML as a treat
Diffstat (limited to 'src/lib/vendor/rss/xml.ts')
| -rw-r--r-- | src/lib/vendor/rss/xml.ts | 141 |
1 files changed, 141 insertions, 0 deletions
diff --git a/src/lib/vendor/rss/xml.ts b/src/lib/vendor/rss/xml.ts new file mode 100644 index 0000000..30618c0 --- /dev/null +++ b/src/lib/vendor/rss/xml.ts @@ -0,0 +1,141 @@ +// 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, '"') + .replace(/'/g, '''); + +export const serializeTextContent = (content: string) => content + .replace(/&/g, '&') + .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<string, string>(); + 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 `<?xml${this.attributeString} ?>`; + } + 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)} +</${this.tagName}>` : ' />'}` + } +} +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 `<!--${text + .replace(/-/g, '-')}-->`; + } +} +export class XMLCData extends BaseXMLTag { + public tagType = XMLTagType.CData as const; + public constructor(public content: string) { super() } + public toString(): string { + return `<![CDATA[${this.content.replace(/\]\]>/gu, ']]]]><![CDATA[>')}]]>`; + } +} +export type XMLTag = XMLDocumentRoot | XMLDeclaration | XMLElement | XMLComment; |