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;