mirror of
https://github.com/titaniumnetwork-dev/Ultraviolet.git
synced 2025-05-15 20:40:01 -04:00
237 lines
No EOL
6.4 KiB
JavaScript
237 lines
No EOL
6.4 KiB
JavaScript
import EventEmitter from './events.js';
|
|
import { parse, parseFragment, serialize } from 'parse5';
|
|
|
|
class HTML extends EventEmitter {
|
|
constructor(ctx) {
|
|
super();
|
|
this.ctx = ctx;
|
|
this.rewriteUrl = ctx.rewriteUrl;
|
|
this.sourceUrl = ctx.sourceUrl;
|
|
};
|
|
rewrite(str, options = {}) {
|
|
if (!str) return str;
|
|
return this.recast(str, node => {
|
|
if (node.tagName) this.emit('element', node, 'rewrite');
|
|
if (node.attr) this.emit('attr', node, 'rewrite');
|
|
if (node.nodeName === '#text') this.emit('text', node, 'rewrite');
|
|
}, options)
|
|
};
|
|
source(str, options = {}) {
|
|
if (!str) return str;
|
|
return this.recast(str, node => {
|
|
if (node.tagName) this.emit('element', node, 'source');
|
|
if (node.attr) this.emit('attr', node, 'source');
|
|
if (node.nodeName === '#text') this.emit('text', node, 'source');
|
|
}, options)
|
|
};
|
|
recast(str, fn, options = {}) {
|
|
try {
|
|
const ast = (options.document ? parse : parseFragment)(new String(str).toString());
|
|
this.iterate(ast, fn, options);
|
|
return serialize(ast);
|
|
} catch(e) {
|
|
return str;
|
|
};
|
|
};
|
|
iterate(ast, fn, fnOptions) {
|
|
if (!ast) return ast;
|
|
|
|
if (ast.tagName) {
|
|
const element = new P5Element(ast, false, fnOptions);
|
|
fn(element);
|
|
if (ast.attrs) {
|
|
for (const attr of ast.attrs) {
|
|
if (!attr.skip) fn(new AttributeEvent(element, attr, fnOptions));
|
|
};
|
|
};
|
|
};
|
|
|
|
if (ast.childNodes) {
|
|
for (const child of ast.childNodes) {
|
|
if (!child.skip) this.iterate(child, fn, fnOptions);
|
|
};
|
|
};
|
|
|
|
if (ast.nodeName === '#text') {
|
|
fn(new TextEvent(ast, new P5Element(ast.parentNode), false, fnOptions));
|
|
};
|
|
|
|
return ast;
|
|
};
|
|
wrapSrcset(str, meta = this.ctx.meta) {
|
|
return str.split(',').map(src => {
|
|
const parts = src.trimStart().split(' ');
|
|
if (parts[0]) parts[0] = this.ctx.rewriteUrl(parts[0], meta);
|
|
return parts.join(' ');
|
|
}).join(', ');
|
|
};
|
|
unwrapSrcset(str, meta = this.ctx.meta) {
|
|
return str.split(',').map(src => {
|
|
const parts = src.trimStart().split(' ');
|
|
if (parts[0]) parts[0] = this.ctx.sourceUrl(parts[0], meta);
|
|
return parts.join(' ');
|
|
}).join(', ');
|
|
};
|
|
static parse = parse;
|
|
static parseFragment = parseFragment;
|
|
static serialize = serialize;
|
|
};
|
|
|
|
class P5Element extends EventEmitter {
|
|
constructor(node, stream = false, options = {}) {
|
|
super();
|
|
this.stream = stream;
|
|
this.node = node;
|
|
this.options = options;
|
|
};
|
|
setAttribute(name, value) {
|
|
for (const attr of this.attrs) {
|
|
if (attr.name === name) {
|
|
attr.value = value;
|
|
return true;
|
|
};
|
|
};
|
|
|
|
this.attrs.push(
|
|
{
|
|
name,
|
|
value,
|
|
}
|
|
);
|
|
};
|
|
getAttribute(name) {
|
|
const attr = this.attrs.find(attr => attr.name === name) || {};
|
|
return attr.value;
|
|
};
|
|
hasAttribute(name) {
|
|
return !!this.attrs.find(attr => attr.name === name);
|
|
};
|
|
removeAttribute(name) {
|
|
const i = this.attrs.findIndex(attr => attr.name === name);
|
|
if (typeof i !== 'undefined') this.attrs.splice(i, 1);
|
|
};
|
|
get tagName() {
|
|
return this.node.tagName;
|
|
};
|
|
set tagName(val) {
|
|
this.node.tagName = val;
|
|
};
|
|
get childNodes() {
|
|
return !this.stream ? this.node.childNodes : null;
|
|
};
|
|
get innerHTML() {
|
|
return !this.stream ? serialize(
|
|
{
|
|
nodeName: '#document-fragment',
|
|
childNodes: this.childNodes,
|
|
}
|
|
) : null;
|
|
};
|
|
set innerHTML(val) {
|
|
if (!this.stream) this.node.childNodes = parseFragment(val).childNodes;
|
|
};
|
|
get outerHTML() {
|
|
return !this.stream ? serialize(
|
|
{
|
|
nodeName: '#document-fragment',
|
|
childNodes: [ this ],
|
|
}
|
|
) : null;
|
|
};
|
|
set outerHTML(val) {
|
|
if (!this.stream) this.parentNode.childNodes.splice(this.parentNode.childNodes.findIndex(node => node === this.node), 1, ...parseFragment(val).childNodes);
|
|
};
|
|
get textContent() {
|
|
if (this.stream) return null;
|
|
|
|
let str = '';
|
|
iterate(this.node, node => {
|
|
if (node.nodeName === '#text') str += node.value;
|
|
});
|
|
|
|
return str;
|
|
};
|
|
set textContent(val) {
|
|
if (!this.stream) this.node.childNodes = [
|
|
{
|
|
nodeName: '#text',
|
|
value: val,
|
|
parentNode: this.node
|
|
}
|
|
];
|
|
};
|
|
get nodeName() {
|
|
return this.node.nodeName;
|
|
}
|
|
get parentNode() {
|
|
return this.node.parentNode ? new P5Element(this.node.parentNode) : null;
|
|
};
|
|
get attrs() {
|
|
return this.node.attrs;
|
|
}
|
|
get namespaceURI() {
|
|
return this.node.namespaceURI;
|
|
}
|
|
};
|
|
|
|
class AttributeEvent {
|
|
constructor(node, attr, options = {}) {
|
|
this.attr = attr;
|
|
this.attrs = node.attrs;
|
|
this.node = node;
|
|
this.options = options;
|
|
};
|
|
delete() {
|
|
const i = this.attrs.findIndex(attr => attr === this.attr);
|
|
|
|
this.attrs.splice(i, 1);
|
|
|
|
Object.defineProperty(this, 'deleted', {
|
|
get: () => true,
|
|
});
|
|
|
|
return true;
|
|
};
|
|
get name() {
|
|
return this.attr.name;
|
|
};
|
|
|
|
set name(val) {
|
|
this.attr.name = val;
|
|
};
|
|
get value() {
|
|
return this.attr.value;
|
|
};
|
|
|
|
set value(val) {
|
|
this.attr.value = val;
|
|
};
|
|
get deleted() {
|
|
return false;
|
|
};
|
|
};
|
|
|
|
class TextEvent {
|
|
constructor(node, element, stream = false, options = {}) {
|
|
this.stream = stream;
|
|
this.node = node;
|
|
this.element = element;
|
|
this.options = options;
|
|
};
|
|
get nodeName() {
|
|
return this.node.nodeName;
|
|
}
|
|
get parentNode() {
|
|
return this.element;
|
|
};
|
|
get value() {
|
|
return this.stream ? this.node.text : this.node.value;
|
|
};
|
|
set value(val) {
|
|
|
|
if (this.stream) this.node.text = val;
|
|
else this.node.value = val;
|
|
};
|
|
};
|
|
|
|
export default HTML; |