8000 feat: add DOM level 4 child element properties by dmitri-gb · Pull Request #652 · xmldom/xmldom · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

feat: add DOM level 4 child element properties #652

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
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
130 changes: 127 additions & 3 deletions lib/dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -1623,10 +1623,14 @@ function _onRemoveAttribute(doc, el, newAttr, remove) {
}

/**
* Updates `parent.childNodes`, adjusting the indexed items and its `length`.
* Updates `parent.firstElementChild`, `parent.lastElementChild` and `parent.childNodes`,
* adjusting the indexed items and its `length`.
* Also updates `nextElementSibling` and `previousElementSibling` references of the child
* nodes.
* If `newChild` is provided and has no nextSibling, it will be appended.
* Otherwise, it's assumed that an item has been removed or inserted,
* and `parent.firstNode` and its `.nextSibling` to re-indexing all child nodes of `parent`.
* and `parent.firstNode` and its `.nextSibling` are used to re-index all child nodes of
* `parent`.
*
* @param {Document} doc
* The parent document of `el`.
Expand All @@ -1645,13 +1649,39 @@ function _onUpdateChild(doc, parent, newChild) {
if (newChild && !newChild.nextSibling) {
// if an item has been appended, we only need to update the last index and the length
childNodes[childNodes.length++] = newChild;
if (isElementNode(newChild)) {
newChild.previousElementSibling = parent.lastElementChild;
newChild.nextElementSibling = null;
parent.childElementCount++;
if (!parent.firstElementChild) {
parent.firstElementChild = newChild;
}
if (parent.lastElementChild) {
parent.lastElementChild.nextElementSibling = newChild;
}
parent.lastElementChild = newChild;
}
} else {
// otherwise we need to reindex all items,
// which can take a while when processing nodes with a lot of children
parent.childElementCount = 0;
parent.firstElementChild = parent.lastElementChild = null;
var child = parent.firstChild;
var i = 0;
while (child) {
childNodes[i++] = child;
if (isElementNode(child)) {
child.previousElementSibling = parent.lastElementChild;
child.nextElementSibling = null;
parent.childElementCount++;
8000 if (!parent.firstElementChild) {
parent.firstElementChild = child;
}
if (parent.lastElementChild) {
parent.lastElementChild.nextElementSibling = child;
}
parent.lastElementChild = child;
}
child = child.nextSibling;
}
childNodes.length = i;
Expand Down Expand Up @@ -1697,6 +1727,10 @@ function _removeChild(parentNode, child) {
child.parentNode = null;
child.previousSibling = null;
child.nextSibling = null;
if (isElementNode(child)) {
child.previousElementSibling = null;
child.nextElementSibling = null;
}
return child;
}

Expand Down Expand Up @@ -2056,7 +2090,7 @@ function _insertBefore(parent, node, child, _inDocumentAssertion) {
do {
newFirst.parentNode = parent;
} while (newFirst !== newLast && (newFirst = newFirst.nextSibling));
_onUpdateChild(parent.ownerDocument || parent, parent, node);
_onUpdateChild(parent.ownerDocument || parent, parent, node.nodeType === DOCUMENT_FRAGMENT_NODE ? null : node);
if (node.nodeType == DOCUMENT_FRAGMENT_NODE) {
node.firstChild = node.lastChild = null;
}
Expand All @@ -2082,6 +2116,30 @@ Document.prototype = {
*/
doctype: null,
documentElement: null,
/**
* The number of child elements of the current document.
*
* @type {number}
* @readonly
*/
childElementCount: 0,

/**
* The first child element of the current document.
*
* @type {Element | null}
* @readonly
*/
firstElementChild: null,

/**
* The last child element of the current document.
*
* @type {Element | null}
* @readonly
*/
lastElementChild: null,

_inc: 1,

insertBefore: function (newChild, refChild) {
Expand Down Expand Up @@ -2347,6 +2405,49 @@ Element.prototype = {
* @type {NamedNodeMap | null}
*/
attributes: null,

/**
* The number of child elements of this element.
*
* @type {number}
* @readonly
*/
childElementCount: 0,

/**
* An element's first child Element, or null if there are no child elements.
*
* @type {Element | null}
* @readonly
*/
firstElementChild: null,

/**
* An element's last child Element, or null if there are no child elements.
*
* @type {Element | null}
* @readonly
*/
lastElementChild: null,

/**
* The element immediately following the specified one in its parent's children list, or null
* if the specified element is the last one in the list.
*
* @type {Element | null}
* @readonly
*/
nextElementSibling: null,

/**
* The element immediately following the specified one in its parent's children list, or null
* if the specified element is the last one in the list.
*
* @type {Element | null}
* @readonly
*/
previousElementSibling: null,

getQualifiedName: function () {
return this.prefix ? this.prefix + ':' + this.localName : this.localName;
},
Expand Down Expand Up @@ -2671,6 +2772,29 @@ function DocumentFragment(symbol) {
}
DocumentFragment.prototype.nodeName = '#document-fragment';
DocumentFragment.prototype.nodeType = DOCUMENT_FRAGMENT_NODE;
/**
* The amount of child elements the `DocumentFragment` has.
*
* @type {number}
* @readonly
*/
DocumentFragment.prototype.childElementCount = 0;
/**
* The Element that is the first child of the `DocumentFragment` object, or null if there is
* none.
*
* @type {Element | null}
* @readonly
*/
DocumentFragment.prototype.firstElementChild = null;
/**
* The Element that is the last child of the `DocumentFragment` object, or null if there is
* none.
*
* @type {Element | null}
* @readonly
*/
DocumentFragment.prototype.lastElementChild = null;
_extends(DocumentFragment, Node);

function ProcessingInstruction(symbol) {
Expand Down
20 changes: 19 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ import { DOMParser } from '@xmldom/xmldom'
```javascript
serializeToString(node)
```
### DOM level2 method and attribute:
### DOM level 2 method and attribute:

* [Node](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247)

Expand Down Expand Up @@ -289,6 +289,24 @@ import { DOMParser } from '@xmldom/xmldom'
- `isDefaultNamespace(namespaceURI)`
- `lookupNamespaceURI(prefix)`

### DOM level 4 support:

* [Element](https://dom.spec.whatwg.org/#interface-element) (via `ParentNode` and `NonDocumentTypeChildNode` mixins)

attribute:
- `firstElementChild`
- `lastElementChild`
- `childElementCount`
- `previousElementSibling`
- `nextElementSibling`

* [Document](https://dom.spec.whatwg.org/#interface-document) and [DocumentFragment](https://dom.spec.whatwg.org/#interface-documentfragment) (via `ParentNode` mixin)

attribute:
- `firstElementChild`
- `lastElementChild`
- `childElementCount`

### DOM extension by xmldom

* [Node] Source position extension;
Expand Down
38 changes: 19 additions & 19 deletions test/dom/dom-implementation.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ describe('DOMImplementation', () => {
expect(root.prefix).toBe(null);
expect(root.localName).toBe(NAME);
expect(doc.documentElement).toBe(root);
expect(doc.childElementCount).toBe(1);
expect(doc.firstElementChild).toBe(root);
expect(doc.lastElementChild).toBe(root);
expect(doc.contentType).toBe(MIME_TYPE.XML_APPLICATION);
expect(doc.type).toBe('xml');
});
Expand All @@ -80,6 +83,9 @@ describe('DOMImplementation', () => {
expect(root.tagName).toBe(NAME);

expect(doc.documentElement).toBe(root);
expect(doc.childElementCount).toBe(1);
expect(doc.firstElementChild).toBe(root);
expect(doc.lastElementChild).toBe(root);
expect(doc.contentType).toBe(MIME_TYPE.XML_APPLICATION);
expect(doc.type).toBe('xml');
});
Expand All @@ -99,25 +105,9 @@ describe('DOMImplementation', () => {
expect(root.tagName).toBe(qualifiedName);

expect(doc.documentElement).toBe(root);
expect(doc.contentType).toBe(MIME_TYPE.XML_APPLICATION);
expect(doc.type).toBe('xml');
});

test('should create a Document with root element in a named namespace', () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this dropped?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was an exact duplicate of the test above.

const impl = new DOMImplementation();
const qualifiedName = `${PREFIX}:${NAME}`;
const doc = impl.createDocument(NS, qualifiedName);

const root = doc.childNodes.item(0);
expect(root).toBeInstanceOf(Element);
expect(root.ownerDocument).toBe(doc);
expect(root.namespaceURI).toBe(NS);
expect(root.prefix).toBe(PREFIX);
expect(root.localName).toBe(NAME);
expect(root.nodeName).toBe(qualifiedName);
expect(root.tagName).toBe(qualifiedName);

expect(doc.documentElement).toBe(root);
expect(doc.childElementCount).toBe(1);
expect(doc.firstElementChild).toBe(root);
expect(doc.lastElementChild).toBe(root);
expect(doc.contentType).toBe(MIME_TYPE.XML_APPLICATION);
expect(doc.type).toBe('xml');
});
Expand All @@ -142,6 +132,9 @@ describe('DOMImplementation', () => {
expect(root.tagName).toBe(qualifiedName);

expect(doc.documentElement).toBe(root);
expect(doc.childElementCount).toBe(1);
expect(doc.firstElementChild).toBe(root);
expect(doc.lastElementChild).toBe(root);
expect(doc.contentType).toBe(MIME_TYPE.XML_APPLICATION);
expect(doc.type).toBe('xml');
});
Expand Down Expand Up @@ -210,6 +203,9 @@ describe('DOMImplementation', () => {
expect(doc.doctype.ownerDocument).toBe(doc);
expect(doc.childNodes.item(0)).toBe(doc.doctype);
expect(doc.firstChild).toBe(doc.doctype);
expect(doc.childElementCount).toBe(1);
expect(doc.firstElementChild).toBe(doc.documentElement);
expect(doc.lastElementChild).toBe(doc.documentElement);

expect(doc.documentElement).not.toBeNull();
expect(doc.documentElement.localName).toBe('html');
Expand All @@ -219,10 +215,14 @@ describe('DOMImplementation', () => {
expect(htmlNode.firstChild).not.toBeNull();
expect(htmlNode.firstChild.nodeName).toBe('head');
expect(htmlNode.firstChild.childNodes).toHaveLength(0);
expect(htmlNode.firstElementChild).toBe(htmlNode.firstChild);
expect(htmlNode.firstElementChild.nextElementSibling).toBe(htmlNode.lastChild);

expect(htmlNode.lastChild).not.toBeNull();
expect(htmlNode.lastChild.nodeName).toBe('body');
expect(htmlNode.lastChild.childNodes).toHaveLength(0);
expect(htmlNode.lastElementChild).toBe(htmlNode.lastChild);
expect(htmlNode.lastElementChild.previousElementSibling).toBe(htmlNode.firstChild);
});
test('should create an HTML document with specified elements including an empty title', () => {
const impl = new DOMImplementation();
Expand Down
45 changes: 32 additions & 13 deletions test/dom/element.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ describe('documentElement', () => {
expect(doc.documentElement.toString()).toBe('<test>bye</test>');
});

test('appendElement and removeElement', () => {
const dom = new DOMParser().parseFromString(`<root><A/><B/><C/></root>`, MIME_TYPE.XML_TEXT);
test('appendChild and removeChild', () => {
const dom = new DOMParser().parseFromString(`<root><A/>x<B/>y<C/></root>`, MIME_TYPE.XML_TEXT);
Comment on lines -78 to +79
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might need to check out the code to understand this change. Could we keep the existing test and add a new one for the new cases?
Or does it cover all 4 funtions, in that case Iwould prefer the message to cobtain all of them.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It only covers the two functions -- appendChild and removeChild. The test simply had the names wrong.

const doc = dom.documentElement;
const arr = [];
while (doc.firstChild) {
Expand All @@ -85,26 +85,45 @@ describe('documentElement', () => {
expect(node.parentNode).toBeNull();
expect(node.previousSibling).toBeNull();
expect(node.nextSibling).toBeNull();
if (node.nodeType == Node.ELEMENT_NODE) {
expect(node.previousElementSibling).toBeNull();
expect(node.nextElementSibling).toBeNull();
}
expect(node.ownerDocument).toBe(dom);
expect(doc.firstChild).not.toBe(node);
const expectedLength = 3 - arr.length;
expect(doc.firstElementChild).not.toBe(node);
expect(doc.lastElementChild).not.toBe(node);
const expectedLength = 5 - arr.length;
expect(doc.childNodes).toHaveLength(expectedLength);
expect(doc.childNodes.item(expectedLength)).toBeNull();
}
expect(arr).toHaveLength(3);
while (arr.length) {
const node = arr.shift();
expect(arr).toHaveLength(5);
expect(doc.childElementCount).toBe(0);
expect(doc.firstElementChild).toBeNull();
expect(doc.lastElementChild).toBeNull();
for (let i = 0; i < arr.length; i++) {
const node = arr[i];
expect(doc.appendChild(node)).toBe(node);
expect(doc.childNodes).toHaveLength(i + 1);
expect(doc.childNodes.item(i)).toBe(node);
expect(node.parentNode).toBe(doc);
const expectedLength = 3 - arr.length;
expect(doc.childNodes).toHaveLength(expectedLength);
expect(doc.childNodes.item(expectedLength - 1)).toBe(node);
if (expectedLength > 1) {
expect(node.previousSibling).toBeInstanceOf(Element);
expect(node.previousSibling.nextSibling).toBe(node);
expect(node.previousSibling).toBe(i == 0 ? null : arr[i - 1]);
expect(node.nextSibling).toBe(null);
if (i > 0) expect(node.previousSibling.nextSibling).toBe(node);
if (node.nodeType == Node.ELEMENT_NODE) {
expect(doc.lastElementChild).toBe(node);
expect(node.previousElementSibling).toBe(i <= 1 ? null : arr[i - 2]);
expect(node.nextElementSibling).toBe(null);
if (i > 1) expect(node.previousElementSibling.nextElementSibling).toBe(node);
}
}
expect(doc.childNodes.toString()).toBe(`<A/><B/><C/>`);
expect(doc.childElementCount).toBe(3);
expect(doc.firstElementChild).toBe(arr[0]);
expect(doc.childNodes.toString()).toBe(`<A/>x<B/>y<C/>`);

doc.removeChild(doc.lastChild);
expect(doc.lastElementChild).toBe(arr[2]);
expect(doc.lastElementChild.nextElementSibling).toBeNull();
});

test('should throw DOMException when trying to append a doctype', () => {
Expand Down
Loading
Loading
0