8000 feat: Update `DOMImplementation` according to recent specs by karfau · Pull Request #210 · xmldom/xmldom · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

feat: Update DOMImplementation according to recent specs #210

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

Merged
merged 4 commits into from
Jun 9, 2021
Merged
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
4 changes: 2 additions & 2 deletions lib/conventions.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ var MIME_TYPE = freeze({
/**
* Helper method to check a mime type if it indicates an HTML document
*
* @param {string=} value
* @param {string} [value]
* @returns {boolean}
*
* @see https://www.iana.org/assignments/media-types/text/html IANA MimeType registration
Expand Down Expand Up @@ -109,7 +109,7 @@ var NAMESPACE = freeze({
/**
* Checks if `uri` equals `NAMESPACE.HTML`.
*
* @param {string=} uri
* @param {string} [uri]
*
* @see NAMESPACE.HTML
*/
Expand Down
120 changes: 88 additions & 32 deletions lib/dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -259,55 +259,108 @@ NamedNodeMap.prototype = {
return null;
}
};

/**
* @see http://www.w3.org/TR/REC-DOM-Level-1/level-one-core.html#ID-102161490
* The DOMImplementation interface represents an object providing methods
* which are not dependent on any particular document.
* Such an object is returned by the `Document.implementation` property.
*
* __The individual methods describe the differences compared to the specs.__
*
* @constructor
*
* @see https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation MDN
* @see https://www.w3.org/TR/REC-DOM-Level-1/level-one-core.html#ID-102161490 DOM Level 1 Core (Initial)
* @see https://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-102161490 DOM Level 2 Core
* @see https://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-102161490 DOM Level 3 Core
* @see https://dom.spec.whatwg.org/#domimplementation DOM Living Standard
*/
function DOMImplementation(/* Object */ features) {
this._features = {};
if (features) {
for (var feature in features) {
this._features = features[feature];
}
}
};
Comment on lines -265 to -272
Copy link
Member Author

Choose a reason for hiding this comment

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

The previous implementation left the definition of features and versions that are supported to the code that calls the constructor, which doesn't make any sense at all. This was one more reason why I decided to not keep or fix it, but to implement it as it is specified in the living standard.

function DOMImplementation() {
}

DOMImplementation.prototype = {
hasFeature: function(/* string */ feature, /* string */ version) {
var versions = this._features[feature.toLowerCase()];
if (versions && (!version || version in versions)) {
/**
* The DOMImplementation.hasFeature() method returns a Boolean flag indicating if a given feature is supported.
* The different implementations fairly diverged in what kind of features were reported.
* The latest version of the spec settled to force this method to always return true, where the functionality was accurate and in use.
*
* @deprecated It is deprecated and modern browsers return true in all cases.
*
* @param {string} feature
* @param {string} [version]
* @returns {boolean} always true
*
* @see https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation/hasFeature MDN
* @see https://www.w3.org/TR/REC-DOM-Level-1/level-one-core.html#ID-5CED94D7 DOM Level 1 Core
* @see https://dom.spec.whatwg.org/#dom-domimplementation-hasfeature DOM Living Standard
*/
hasFeature: function(feature, version) {
return true;
} else {
return false;
}
},
// Introduced in DOM Level 2:
createDocument:function(namespaceURI, qualifiedName, doctype){// raises:INVALID_CHARACTER_ERR,NAMESPACE_ERR,WRONG_DOCUMENT_ERR
/**
* Creates an XML Document object of the specified type with its document element.
*
* __It behaves slightly different from the description in the living standard__:
* - There is no interface/class `XMLDocument`, it returns a `Document` instance.
* - `contentType`, `encoding`, `mode`, `origin`, `url` fields are currently not declared.
* - this implementation is not validating names or qualified names
* (when parsing XML strings, the SAX parser takes care of that)
*
* @param {string|null} namespaceURI
* @param {string} qualifiedName
* @param {DocumentType=null} doctype
* @returns {Document}
*
* @see https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation/createDocument MDN
* @see https://www.w3.org/TR/DOM-Level-2-Core/core.html#Level-2-Core-DOM-createDocument DOM Level 2 Core (initial)
* @see https://dom.spec.whatwg.org/#dom-domimplementation-createdocument DOM Level 2 Core
*
* @see https://dom.spec.whatwg.org/#validate-and-extract DOM: Validate and extract
* @see https://www.w3.org/TR/xml/#NT-NameStartChar XML Spec: Names
* @see https://www.w3.org/TR/xml-names/#ns-qualnames XML Namespaces: Qualified names
*/
createDocument: function(namespaceURI, qualifiedName, doctype){
var doc = new Document();
doc.implementation = this;
doc.childNodes = new NodeList();
doc.doctype = doctype;
if(doctype){
doc.doctype = doctype || null;
if (doctype){
doc.appendChild(doctype);
}
if(qualifiedName){
var root = doc.createElementNS(namespaceURI,qualifiedName);
if (qualifiedName){
var root = doc.createElementNS(namespaceURI, qualifiedName);
doc.appendChild(root);
}
return doc;
},
// Introduced in DOM Level 2:
createDocumentType:function(qualifiedName, publicId, systemId){// raises:INVALID_CHARACTER_ERR,NAMESPACE_ERR
/**
* Returns a doctype, with the given `qualifiedName`, `publicId`, and `systemId`.
*
* __This behavior is slightly different from the in the specs__:
* - this implementation is not validating names or qualified names
* (when parsing XML strings, the SAX parser takes care of that)
*
* @param {string} qualifiedName
* @param {string} [publicId]
* @param {string} [systemId]
* @returns {DocumentType} which can either be used with `DOMImplementation.createDocument` upon document creation
* or can be put into the document via methods like `Node.insertBefore()` or `Node.replaceChild()`
*
* @see https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation/createDocumentType MDN
* @see https://www.w3.org/TR/DOM-Level-2-Core/core.html#Level-2-Core-DOM-createDocType DOM Level 2 Core
* @see https://dom.spec.whatwg.org/#dom-domimplementation-createdocumenttype DOM Living Standard
*
* @see https://dom.spec.whatwg.org/#validate-and-extract DOM: Validate and extract
* @see https://www.w3.org/TR/xml/#NT-NameStartChar XML Spec: Names
* @see https://www.w3.org/TR/xml-names/#ns-qualnames XML Namespaces: Qualified names
*/
createDocumentType: function(qualifiedName, publicI 8000 d, systemId){
var node = new DocumentType();
node.name = qualifiedName;
node.nodeName = qualifiedName;
node.publicId = publicId;
node.systemId = systemId;
// Introduced in DOM Level 2:
//readonly attribute DOMString internalSubset;

//TODO:..
// readonly attribute NamedNodeMap entities;
// readonly attribute NamedNodeMap notations;
node.publicId = publicId || '';
node.systemId = systemId || '';

return node;
}
};
Expand Down Expand Up @@ -1290,8 +1343,11 @@ try{
}

//if(typeof require == 'function'){
exports.Node = Node;
exports.DocumentType = DocumentType;
exports.DOMException = DOMException;
exports.DOMImplementation = DOMImplementation;
exports.Element = Element;
exports.Node = Node;
exports.NodeList = NodeList;
exports.XMLSerializer = XMLSerializer;
//}
165 changes: 165 additions & 0 deletions test/dom/dom-implementation.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
'use strict'

const {
DocumentType,
DOMImplementation,
Element,
Node,
NodeList,
} = require('../../lib/dom')
Copy link
Member

Choose a reason for hiding this comment

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

I would favor that we export and use these from lib/index.js, as I proposed in PR #233.

Copy link
Member Author
@karfau karfau May 29, 2021

Choose a reason for hiding this comment

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

Does it mean you want to land #233 first? Since lib/index.js doesn't exist on master yet.

It currently already has a conflict and I raised two questions there.
If you think it's important to solve this before anything else, let's focus on that issue.
If not I would prefer to leave this as is in this PR, land it and continue to solve that issue in the related PR.


const NAME = 'NAME'
const PREFIX = 'PREFIX'
const NS = 'NS'

describe('DOMImplementation', () => {
describe('hasFeature (deprecated)', () => {
it.each(['', '0', 'feature'])(
"should return true when called with ('%s')",
(f) => {
expect(new DOMImplementation().hasFeature(f)).toBe(true)
}
)
it.each([
['', ''],
['0', '1'],
['feature', ''],
['feature', '1'],
])("should return true when called with ('%s', '%s')", (f, v) => {
expect(new DOMImplementation().hasFeature(f, v)).toBe(true)
})
})

describe('createDocument', () => {
it('should create a Document with basic mandatory arguments', () => {
const impl = new DOMImplementation()
const doc = impl.createDocument(null, '')

expect(doc.nodeType).toBe(Node.DOCUMENT_NODE)
expect(doc.implementation).toBe(impl)
expect(doc.doctype).toBe(null)
expect(doc.childNodes).toBeInstanceOf(NodeList)
expect(doc.documentElement).toBe(null)
})

it('should create a Document with only a doc type', () => {
const impl = new DOMImplementation()
const doctype = impl.createDocumentType('test')
const doc = impl.createDocument(null, '', doctype)

expect(doc.doctype).toBe(doctype)
expect(doctype.ownerDocument).toBe(doc)
expect(doc.childNodes.item(0)).toBe(doctype)
})

it('should create a Document with root element without a namespace', () => {
const impl = new DOMImplementation()
const doc = impl.createDocument(null, NAME)

const root = doc.childNodes.item(0)
expect(root).toBeInstanceOf(Element)
expect(root.ownerDocument).toBe(doc)
expect(root.namespaceURI).toBe(null)
expect(root.nodeName).toBe(NAME)
expect(root.tagName).toBe(NAME)
expect(root.prefix).toBe(null)
expect(root.localName).toBe(NAME)
expect(doc.documentElement).toBe(root)
})

it('should create a Document with root element in a default namespace', () => {
const impl = new DOMImplementation()
const doc = impl.createDocument(NS, NAME)

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

expect(doc.documentElement).toBe(root)
})

it('should create a Document with root element in a named namespace', () => {
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)
})

it('should create a Document with root element in a named namespace', () => {
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)
})

it('should create a Document with namespaced root element and doctype', () => {
const impl = new DOMImplementation()
const qualifiedName = `${PREFIX}:${NAME}`
const doctype = impl.createDocumentType('test')
const doc = impl.createDocument(NS, qualifiedName, doctype)

expect(doc.doctype).toBe(doctype)
expect(doctype.ownerDocument).toBe(doc)
expect(doc.childNodes.item(0)).toBe(doctype)

const root = doc.childNodes.item(1)
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)
})
})

describe('createDocumentType', () => {
it('should create a DocumentType with only a name', () => {
const impl = new DOMImplementation()
const doctype = impl.createDocumentType(NAME)

expect(doctype).toBeInstanceOf(Node)
expect(doctype).toBeInstanceOf(DocumentType)
expect(doctype.nodeType).toBe(Node.DOCUMENT_TYPE_NODE)
expect(doctype.name).toBe(NAME)
expect(doctype.publicId).toBe('')
expect(doctype.systemId).toBe('')
})

it('should create a DocumentType with name, publicId and systemId', () => {
const impl = new DOMImplementation()
const doctype = impl.createDocumentType(NAME, '"PUBLIC"', '"SYSTEM"')

expect(doctype.name).toBe(NAME)
expect(doctype.publicId).toBe('"PUBLIC"')
expect(doctype.systemId).toBe('"SYSTEM"')
})
})
})
0