aboutsummaryrefslogtreecommitdiffstats
path: root/src/lib/vendor/rss/xml.ts
blob: 8a0d2c4fd3fc0606c81c3bc1cc555330c2bdd5ad (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
// 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: XMLTag[] = [];
  protected get contentString() {
    return this.children.map(v => v.toString()).join('\n');
  }
  public child(...children: XMLTag[]) {
    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() }
  protected findElementChild(query: (element: XMLElement) => boolean) {
    return this.children.find(el => el.tagType === XMLTagType.Element && query(el)) as XMLElement
  }
  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(