rsshub
Version:
Make RSS Great Again!
178 lines (167 loc) • 5.05 kB
text/typescript
const text_tag = {
LINE_BREAK: 0,
INLINE_CODE: 1,
LEGACY_PUBLISHING_EMPHASIS: 2,
LIST_ITEM: 3,
LIST: 4,
STRIKETHROUGH: 5,
SUBSCRIPT: 6,
SUPERSCRIPT: 7,
UNDERLINE: 8,
BOLD: 9,
ITALIC: 10,
PARAGRAPH: 11,
};
class TreeNode {
constructor(attr) {
this.attr = attr;
this.start = attr.start;
this.end = attr.start + attr.length;
this.children = [];
}
}
class Ucs2Text {
constructor(text) {
if (Array.isArray(text)) {
this.string = null;
this.codePoints = text;
} else {
this.string = text;
const k = text.length;
const d = [];
let l,
e,
m = 0;
while (m < k) {
l = text.charCodeAt(m++);
if (l >= 55296 && l <= 56319 && m < k) {
e = text.charCodeAt(m++);
if (56320 === (64512 & e)) {
d.push(((1023 & l) << 10) + (1023 & e) + 65536);
} else {
d.push(l);
m--;
}
} else {
d.push(l);
}
}
this.codePoints = d;
}
this.length = this.codePoints.length;
}
substring(start, end) {
let _len = this.length,
_start = start,
_end = end;
if (_end === 0) {
return new Ucs2Text('');
}
if (((Number.isNaN(_start) || 0 > _start) && (_start = 0), (Number.isNaN(_end) || 0 > _end) && (_end = _len), _start > _len && (_start = _len), _end > _len && (_end = _len), _end < _start)) {
_len = [_start, _end];
_end = _len[0];
_start = _len[1];
}
return new Ucs2Text(_start === _end ? '' : this.codePoints.slice(_start, _end));
}
slice(a, b) {
return this.substring(a, b).toString();
}
toString() {
return null === this.string
? this.codePoints
.map((a) => {
let b = '';
return (65535 < a && ((b += String.fromCharCode((((a -= 65536) >>> 10) & 1023) | 55296)), (a = 1023 & (56320 | a))), b + String.fromCharCode(a));
})
.join('')
: this.string;
}
}
const renderSingle = (node) => {
const style = node?.attr?.detailData?.style;
switch (style) {
case 'BOLD':
return `<b>${node.text}</b>`;
case 'LINE_BREAK':
return `<br>`;
case 'LIST_ITEM':
return `<li>${node.text}</li>`;
case 'LIST':
return `<ul>${node.text}</ul>`;
case 'INLINE_CODE':
return `<code>${node.text}</code>`;
case 'LEGACY_PUBLISHING_EMPHASIS':
return `<em>${node.text}</em>`;
case 'STRIKETHROUGH':
return `<s>${node.text}</s>`;
case 'SUBSCRIPT':
return `<sub>${node.text}</sub>`;
case 'SUPERSCRIPT':
return `<sup>${node.text}</sup>`;
case 'UNDERLINE':
return `<u>${node.text}</u>`;
case 'ITALIC':
return `<i>${node.text}</i>`;
case 'PARAGRAPH':
return `<p>${node.text}</p>`;
default:
return node.text || node;
}
};
const parseAttr = (description) => {
const { attributes, text } = description;
const attrs = [...attributes];
attrs.sort((b, d) => {
const f = b.start + b.length,
e = d.start + d.length;
return f === e ? text_tag[b.detailData.style] - text_tag[d.detailData.style] : f - e;
});
const n = [];
for (const q of attrs) {
const p = new TreeNode(q);
const w = q.start;
while (0 < n.length && n.at(-1).attr.start >= w) {
p.children.push(n.pop());
}
p.children.reverse();
n.push(p);
}
const m = new Ucs2Text(text);
const render = (node) => {
// DFS render
if (node.children.length === 0) {
node.text = m.slice(node.start, node.end);
return renderSingle(node);
}
const res = [];
let s = node.start;
for (const child of node.children) {
if (s < child.start) {
res.push(m.slice(s, child.start)); // added text before 1st child node
}
s = child.end;
res.push(render(child));
}
if (s < node.end) {
res.push(m.slice(s, node.end)); // added text after the last child node
}
node.text = res.join('');
return renderSingle(node);
};
const q = [];
let p = 0;
for (const e of n) {
// BFS render
if (e.start > p) {
q.push(m.slice(p, e.start));
}
p = e.end;
q.push(render(e));
}
if (p < m.length) {
q.push(m.slice(p));
}
return q.join('');
};
export { parseAttr };