aboutsummaryrefslogtreecommitdiffstats
path: root/src/lib/vendor/rss/xml.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/vendor/rss/xml.ts')
-rw-r--r--src/lib/vendor/rss/xml.ts141
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, '&lt;')
+ .replace(/>/g, '&gt;')
+ .replace(/"/g, '&quot;')
+ .replace(/'/g, '&apos;');
+
+export const serializeTextContent = (content: string) => content
+ .replace(/&/g, '&amp;')
+ .replace(/</g, '&lt;')
+ .replace(/>/g, '&gt;');
+
+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, '&#45;')}-->`;
+ }
+}
+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;