Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion cjs/interface/attr.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ class Attr extends Node {
if (emptyAttributes.has(name) && !value) {
return ignoreCase(this) ? name : `${name}=""`;
}
const escapedValue = (ignoreCase(this) ? value : escape(value)).replace(QUOTE, '"');
const rawValue = ignoreCase(this) ? value : escape(value);
const escapedValue = rawValue.includes('"') ? rawValue.replace(QUOTE, '"') : rawValue;
return `${name}="${escapedValue}"`;
}

Expand Down
50 changes: 27 additions & 23 deletions cjs/interface/element.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,35 +160,35 @@ class Element extends ParentNode {

// <contentRelated>
get innerText() {
const text = [];
let text = '';
let {[NEXT]: next, [END]: end} = this;
while (next !== end) {
if (next.nodeType === TEXT_NODE) {
text.push(next.textContent.replace(/\s+/g, ' '));
text += next.textContent.replace(/\s+/g, ' ');
} else if(
text.length && next[NEXT] != end &&
BLOCK_ELEMENTS.has(next.tagName)
) {
text.push('\n');
text += '\n';
}
next = next[NEXT];
}
return text.join('');
return text;
}

/**
* @returns {String}
*/
get textContent() {
const text = [];
let text = '';
let {[NEXT]: next, [END]: end} = this;
while (next !== end) {
const nodeType = next.nodeType;
if (nodeType === TEXT_NODE || nodeType === CDATA_SECTION_NODE)
text.push(next.textContent);
text += next.textContent;
next = next[NEXT];
}
return text.join('');
return text;
}

set textContent(text) {
Expand Down Expand Up @@ -435,62 +435,66 @@ class Element extends ParentNode {

// <custom>
toString() {
const out = [];
// the output grows by concatenation: one rope beats an array of thousands of pieces
let out = '';
const {[END]: end} = this;
let next = {[NEXT]: this};
let isOpened = false;
do {
next = next[NEXT];
switch (next.nodeType) {
case ATTRIBUTE_NODE: {
const attr = ' ' + next;
const attr = next.toString();
switch (attr) {
case ' id':
case ' class':
case ' style':
case 'id':
case 'class':
case 'style':
break;
default:
out.push(attr);
out += ' ' + attr;
}
break;
}
case NODE_END: {
const start = next[START];
if (isOpened) {
if ('ownerSVGElement' in start)
out.push(' />');
out += ' />';
else if (isVoid(start))
out.push(ignoreCase(start) ? '>' : ' />');
out += ignoreCase(start) ? '>' : ' />';
else
out.push(`></${start.localName}>`);
out += `></${start.localName}>`;
isOpened = false;
}
else
out.push(`</${start.localName}>`);
out += `</${start.localName}>`;
break;
}
case ELEMENT_NODE:
if (isOpened)
out.push('>');
out += '>';
if (next.toString !== this.toString) {
out.push(next.toString());
out += next.toString();
next = next[END];
isOpened = false;
}
else {
out.push(`<${next.localName}`);
out += `<${next.localName}`;
isOpened = true;
}
break;
case TEXT_NODE:
case COMMENT_NODE:
case CDATA_SECTION_NODE:
out.push((isOpened ? '>' : '') + next);
isOpened = false;
if (isOpened) {
out += '>';
isOpened = false;
}
out += next;
break;
}
} while (next !== end);
return out.join('');
return out;
}

toJSON() {
Expand Down
10 changes: 5 additions & 5 deletions cjs/interface/text.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,24 @@ class Text extends CharacterData {
}

get wholeText() {
const text = [];
let {previousSibling, nextSibling} = this;
let text = '';
while (previousSibling) {
if (previousSibling.nodeType === TEXT_NODE)
text.unshift(previousSibling[VALUE]);
text = previousSibling[VALUE] + text;
else
break;
previousSibling = previousSibling.previousSibling;
}
text.push(this[VALUE]);
text += this[VALUE];
while (nextSibling) {
if (nextSibling.nodeType === TEXT_NODE)
text.push(nextSibling[VALUE]);
text += nextSibling[VALUE];
else
break;
nextSibling = nextSibling.nextSibling;
}
return text.join('');
return text;
}

cloneNode() {
Expand Down
35 changes: 25 additions & 10 deletions cjs/mixin/parent-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ const {NodeList} = require('../interface/node-list.js');
const {moCallback} = require('../interface/mutation-observer.js');
const {connectedCallback} = require('../interface/custom-element-registry.js');

const {nextElementSibling} = require('./non-document-type-child-node.js');

const isNode = node => node instanceof Node;

const insert = (parentNode, child, nodes) => {
Expand Down Expand Up @@ -64,20 +62,37 @@ class ParentNode extends Node {

get childNodes() {
const childNodes = new NodeList;
let {firstChild} = this;
while (firstChild) {
childNodes.push(firstChild);
firstChild = nextSibling(firstChild);
// walk the list once: an element is followed by its whole subtree, so jump over it
const {[END]: end} = this;
let next = this[NEXT];
while (next !== end) {
switch (next.nodeType) {
case ATTRIBUTE_NODE:
next = next[NEXT];
break;
case ELEMENT_NODE:
childNodes.push(next);
next = next[END][NEXT];
break;
default:
childNodes.push(next);
next = next[NEXT];
}
}
return childNodes;
}

get children() {
const children = new NodeList;
let {firstElementChild} = this;
while (firstElementChild) {
children.push(firstElementChild);
firstElementChild = nextElementSibling(firstElementChild);
const {[END]: end} = this;
let next = this[NEXT];
while (next !== end) {
if (next.nodeType === ELEMENT_NODE) {
children.push(next);
next = next[END][NEXT];
}
else
next = next[NEXT];
}
return children;
}
Expand Down
4 changes: 3 additions & 1 deletion cjs/shared/text-escaper.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ const {replace} = '';

// escape
const ca = /[<>&\xA0]/g;
// same characters, without the global flag: `test` on a global regex moves lastIndex
const needsEscape = /[<>&\xA0]/;

const esca = {
'\xA0': '&#160;',
Expand All @@ -20,5 +22,5 @@ const pe = m => esca[m];
* the input type is unexpected, except for boolean and numbers,
* converted as string.
*/
const escape = es => replace.call(es, ca, pe);
const escape = es => needsEscape.test(es) ? replace.call(es, ca, pe) : es;
exports.escape = escape;
3 changes: 2 additions & 1 deletion esm/interface/attr.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ export class Attr extends Node {
if (emptyAttributes.has(name) && !value) {
return ignoreCase(this) ? name : `${name}=""`;
}
const escapedValue = (ignoreCase(this) ? value : escape(value)).replace(QUOTE, '&quot;');
const rawValue = ignoreCase(this) ? value : escape(value);
const escapedValue = rawValue.includes('"') ? rawValue.replace(QUOTE, '&quot;') : rawValue;
return `${name}="${escapedValue}"`;
}

Expand Down
50 changes: 27 additions & 23 deletions esm/interface/element.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,35 +162,35 @@ export class Element extends ParentNode {

// <contentRelated>
get innerText() {
const text = [];
let text = '';
let {[NEXT]: next, [END]: end} = this;
while (next !== end) {
if (next.nodeType === TEXT_NODE) {
text.push(next.textContent.replace(/\s+/g, ' '));
text += next.textContent.replace(/\s+/g, ' ');
} else if(
text.length && next[NEXT] != end &&
BLOCK_ELEMENTS.has(next.tagName)
) {
text.push('\n');
text += '\n';
}
next = next[NEXT];
}
return text.join('');
return text;
}

/**
* @returns {String}
*/
get textContent() {
const text = [];
let text = '';
let {[NEXT]: next, [END]: end} = this;
while (next !== end) {
const nodeType = next.nodeType;
if (nodeType === TEXT_NODE || nodeType === CDATA_SECTION_NODE)
text.push(next.textContent);
text += next.textContent;
next = next[NEXT];
}
return text.join('');
return text;
}

set textContent(text) {
Expand Down Expand Up @@ -437,62 +437,66 @@ export class Element extends ParentNode {

// <custom>
toString() {
const out = [];
// the output grows by concatenation: one rope beats an array of thousands of pieces
let out = '';
const {[END]: end} = this;
let next = {[NEXT]: this};
let isOpened = false;
do {
next = next[NEXT];
switch (next.nodeType) {
case ATTRIBUTE_NODE: {
const attr = ' ' + next;
const attr = next.toString();
switch (attr) {
case ' id':
case ' class':
case ' style':
case 'id':
case 'class':
case 'style':
break;
default:
out.push(attr);
out += ' ' + attr;
}
break;
}
case NODE_END: {
const start = next[START];
if (isOpened) {
if ('ownerSVGElement' in start)
out.push(' />');
out += ' />';
else if (isVoid(start))
out.push(ignoreCase(start) ? '>' : ' />');
out += ignoreCase(start) ? '>' : ' />';
else
out.push(`></${start.localName}>`);
out += `></${start.localName}>`;
isOpened = false;
}
else
out.push(`</${start.localName}>`);
out += `</${start.localName}>`;
break;
}
case ELEMENT_NODE:
if (isOpened)
out.push('>');
out += '>';
if (next.toString !== this.toString) {
out.push(next.toString());
out += next.toString();
next = next[END];
isOpened = false;
}
else {
out.push(`<${next.localName}`);
out += `<${next.localName}`;
isOpened = true;
}
break;
case TEXT_NODE:
case COMMENT_NODE:
case CDATA_SECTION_NODE:
out.push((isOpened ? '>' : '') + next);
isOpened = false;
if (isOpened) {
out += '>';
isOpened = false;
}
out += next;
break;
}
} while (next !== end);
return out.join('');
return out;
}

toJSON() {
Expand Down
Loading