@xmldom/xmldom 0.9.0-beta.6 → 0.9.0-beta.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/dom.js CHANGED
@@ -65,6 +65,51 @@ function arrayIncludes(list) {
65
65
  };
66
66
  }
67
67
 
68
+ /**
69
+ * @param {string} qualifiedName
70
+ * @throws DOMException
71
+ * @see https://dom.spec.whatwg.org/#validate
72
+ */
73
+ function validateQualifiedName(qualifiedName) {
74
+ if (!conventions.QNAME.test(qualifiedName)) {
75
+ throw new DOMException(INVALID_CHARACTER_ERR, 'invalid character in qualified name "' + qualifiedName + '"');
76
+ }
77
+ }
78
+
79
+ /**
80
+ *
81
+ * @param {string | null} namespace
82
+ * @param {string} qualifiedName
83
+ *
84
+ * @returns {[namespace:string|null, prefix:string|null, localName:string]}
85
+ * @see https://dom.spec.whatwg.org/#validate-and-extract
86
+ */
87
+ function validateAndExtract(namespace, qualifiedName) {
88
+ validateQualifiedName(qualifiedName);
89
+ namespace = namespace || null;
90
+ /** @type {string | null} */
91
+ var prefix = null;
92
+ var localName = qualifiedName;
93
+ if (qualifiedName.indexOf(':') >= 0) {
94
+ var splitResult = qualifiedName.split(':');
95
+ prefix = splitResult[0];
96
+ localName = splitResult[1];
97
+ }
98
+ if (prefix !== null && namespace === null) {
99
+ throw new DOMException(NAMESPACE_ERR, 'prefix is non-null and namespace is null');
100
+ }
101
+ if (prefix === 'xml' && namespace !== conventions.NAMESPACE.XML) {
102
+ throw new DOMException(NAMESPACE_ERR, 'prefix is "xml" and namespace is not the XML namespace');
103
+ }
104
+ if ((prefix === 'xmlns' || qualifiedName === 'xmlns') && namespace !== conventions.NAMESPACE.XMLNS) {
105
+ throw new DOMException(NAMESPACE_ERR, 'either qualifiedName or prefix is "xmlns" and namespace is not the XMLNS namespace');
106
+ }
107
+ if (namespace === conventions.NAMESPACE.XMLNS && prefix !== 'xmlns' && qualifiedName !== 'xmlns') {
108
+ throw new DOMException(NAMESPACE_ERR, 'namespace is the XMLNS namespace and neither qualifiedName nor prefix is "xmlns"');
109
+ }
110
+ return [namespace, prefix, localName];
111
+ }
112
+
68
113
  function copy(src, dest) {
69
114
  for (var p in src) {
70
115
  if (Object.prototype.hasOwnProperty.call(src, p)) {
@@ -130,6 +175,58 @@ var INVALID_MODIFICATION_ERR = (ExceptionCode.INVALID_MODIFICATION_ERR = ((Excep
130
175
  var NAMESPACE_ERR = (ExceptionCode.NAMESPACE_ERR = ((ExceptionMessage[14] = 'Invalid namespace'), 14));
131
176
  var INVALID_ACCESS_ERR = (ExceptionCode.INVALID_ACCESS_ERR = ((ExceptionMessage[15] = 'Invalid access'), 15));
132
177
 
178
+ // compareDocumentPosition bitmask results
179
+ var DocumentPosition = {};
180
+ var DOCUMENT_POSITION_DISCONNECTED = (DocumentPosition.DOCUMENT_POSITION_DISCONNECTED = 1);
181
+ var DOCUMENT_POSITION_PRECEDING = (DocumentPosition.DOCUMENT_POSITION_PRECEDING = 2);
182
+ var DOCUMENT_POSITION_FOLLOWING = (DocumentPosition.DOCUMENT_POSITION_FOLLOWING = 4);
183
+ var DOCUMENT_POSITION_CONTAINS = (DocumentPosition.DOCUMENT_POSITION_CONTAINS = 8);
184
+ var DOCUMENT_POSITION_CONTAINED_BY = (DocumentPosition.DOCUMENT_POSITION_CONTAINED_BY = 16);
185
+ var DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC = (DocumentPosition.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC = 32);
186
+
187
+ //helper functions for compareDocumentPosition
188
+ /**
189
+ * Construct a parent chain for a node.
190
+ * @param {Node} node The start node.
191
+ * @return {Node[]} The parent chain.
192
+ */
193
+ function parentChain(node) {
194
+ var chain = [];
195
+ while (node.parentNode || node.ownerElement) {
196
+ node = node.parentNode || node.ownerElement;
197
+ chain.unshift(node);
198
+ }
199
+ return chain;
200
+ }
201
+
202
+ /**
203
+ * Find the common ancestor in two parent chains.
204
+ * @param {Node[]} a A parent chain.
205
+ * @param {Node[]} b A parent chain.
206
+ * @return {Node} The common ancestor if it exists.
207
+ */
208
+ function commonAncestor(a, b) {
209
+ if (b.length < a.length) return commonAncestor(b, a);
210
+ var c = null;
211
+ for (var n in a) {
212
+ if (a[n] !== b[n]) return c;
213
+ c = a[n];
214
+ }
215
+ return c;
216
+ }
217
+
218
+ /**
219
+ * Comparing unrelated nodes must be consistent, so we assign a guid to the
220
+ * compared docs for further reference.
221
+ * @param {Document} doc The document.
222
+ * @return {string} The document's guid.
223
+ */
224
+ function docGUID(doc) {
225
+ if (!doc.guid) doc.guid = Math.random();
226
+ return doc.guid;
227
+ }
228
+ //-- end of helper functions
229
+
133
230
  /**
134
231
  * DOM Level 2
135
232
  * Object DOMException
@@ -231,15 +328,22 @@ _extends(LiveNodeList, NodeList);
231
328
  * and does not imply that the DOM specifies an order to these Nodes.
232
329
  * NamedNodeMap objects in the DOM are live.
233
330
  * used for attributes or DocumentType entities
331
+ *
332
+ * This implementation only supports property indices, but does not support named properties,
333
+ * as specified in the living standard.
334
+ *
335
+ * @see https://dom.spec.whatwg.org/#interface-namednodemap
336
+ * @see https://webidl.spec.whatwg.org/#dfn-supported-property-names
234
337
  */
235
338
  function NamedNodeMap() {}
236
339
 
237
340
  function _findNodeIndex(list, node) {
238
- var i = list.length;
239
- while (i--) {
341
+ var i = 0;
342
+ while (i < list.length) {
240
343
  if (list[i] === node) {
241
344
  return i;
242
345
  }
346
+ i++;
243
347
  }
244
348
  }
245
349
 
@@ -247,7 +351,8 @@ function _addNamedNode(el, list, newAttr, oldAttr) {
247
351
  if (oldAttr) {
248
352
  list[_findNodeIndex(list, oldAttr)] = newAttr;
249
353
  } else {
250
- list[list.length++] = newAttr;
354
+ list[list.length] = newAttr;
355
+ list.length++;
251
356
  }
252
357
  if (el) {
253
358
  newAttr.ownerElement = el;
@@ -263,7 +368,7 @@ function _removeNamedNode(el, list, attr) {
263
368
  var i = _findNodeIndex(list, attr);
264
369
  if (i >= 0) {
265
370
  var lastIndex = list.length - 1;
266
- while (i < lastIndex) {
371
+ while (i <= lastIndex) {
267
372
  list[i] = list[++i];
268
373
  }
269
374
  list.length = lastIndex;
@@ -271,72 +376,127 @@ function _removeNamedNode(el, list, attr) {
271
376
  var doc = el.ownerDocument;
272
377
  if (doc) {
273
378
  _onRemoveAttribute(doc, el, attr);
274
- attr.ownerElement = null;
275
379
  }
380
+ attr.ownerElement = null;
276
381
  }
277
- } else {
278
- throw new DOMException(NOT_FOUND_ERR, new Error(el.tagName + '@' + attr));
279
382
  }
280
383
  }
281
384
  NamedNodeMap.prototype = {
282
385
  length: 0,
283
386
  item: NodeList.prototype.item,
284
- getNamedItem: function (key) {
285
- // if(key.indexOf(':')>0 || key == 'xmlns'){
286
- // return null;
287
- // }
288
- //console.log()
289
- var i = this.length;
290
- while (i--) {
387
+
388
+ /**
389
+ * get an attribute by name (lower case in case of HTML namespace and document)
390
+ *
391
+ * @param {string} localName
392
+ * @return {Attr | null}
393
+ *
394
+ * @see https://dom.spec.whatwg.org/#concept-element-attributes-get-by-name
395
+ */
396
+ getNamedItem: function (localName) {
397
+ if (this._ownerElement && this._ownerElement._isInHTMLDocumentAndNamespace()) {
398
+ localName = localName.toLowerCase();
399
+ }
400
+ var i = 0;
401
+ while (i < this.length) {
291
402
  var attr = this[i];
292
- //console.log(attr.nodeName,key)
293
- if (attr.nodeName == key) {
403
+ if (attr.nodeName === localName) {
294
404
  return attr;
295
405
  }
406
+ i++;
296
407
  }
408
+ return null;
297
409
  },
410
+
411
+ /**
412
+ * set an attribute
413
+ *
414
+ * @param {Attr} attr
415
+ * @return {Attr | null}
416
+ * @see https://dom.spec.whatwg.org/#concept-element-attributes-set
417
+ */
298
418
  setNamedItem: function (attr) {
299
419
  var el = attr.ownerElement;
300
- if (el && el != this._ownerElement) {
420
+ if (el && el !== this._ownerElement) {
301
421
  throw new DOMException(INUSE_ATTRIBUTE_ERR);
302
422
  }
303
- var oldAttr = this.getNamedItem(attr.nodeName);
423
+ var oldAttr = this.getNamedItemNS(attr.namespaceURI, attr.localName);
424
+ if (oldAttr === attr) {
425
+ return attr;
426
+ }
304
427
  _addNamedNode(this._ownerElement, this, attr, oldAttr);
305
428
  return oldAttr;
306
429
  },
307
- /* returns Node */
430
+
431
+ /**
432
+ * set an attribute
433
+ *
434
+ * @param {Attr} attr
435
+ * @return {Attr | null}
436
+ *
437
+ * @see https://dom.spec.whatwg.org/#concept-element-attributes-set
438
+ */
308
439
  setNamedItemNS: function (attr) {
309
- // raises: WRONG_DOCUMENT_ERR,NO_MODIFICATION_ALLOWED_ERR,INUSE_ATTRIBUTE_ERR
310
- var el = attr.ownerElement,
311
- oldAttr;
312
- if (el && el != this._ownerElement) {
313
- throw new DOMException(INUSE_ATTRIBUTE_ERR);
314
- }
315
- oldAttr = this.getNamedItemNS(attr.namespaceURI, attr.localName);
316
- _addNamedNode(this._ownerElement, this, attr, oldAttr);
317
- return oldAttr;
440
+ return this.setNamedItem(attr);
318
441
  },
319
442
 
320
- /* returns Node */
321
- removeNamedItem: function (key) {
322
- var attr = this.getNamedItem(key);
443
+ /**
444
+ * remove an attribute by name (lower case in case of HTML namespace and document)
445
+ *
446
+ * @param {string} localName
447
+ * @return {Attr | null}
448
+ *
449
+ * @see https://dom.spec.whatwg.org/#dom-namednodemap-removenameditem
450
+ * @see https://dom.spec.whatwg.org/#concept-element-attributes-remove-by-name
451
+ */
452
+ removeNamedItem: function (localName) {
453
+ var attr = this.getNamedItem(localName);
454
+ if (!attr) {
455
+ throw new DOMException(NOT_FOUND_ERR, localName);
456
+ }
323
457
  _removeNamedNode(this._ownerElement, this, attr);
324
458
  return attr;
325
- }, // raises: NOT_FOUND_ERR,NO_MODIFICATION_ALLOWED_ERR
459
+ },
326
460
 
327
- //for level2
461
+ /**
462
+ * remove an attribute by namespace and local name
463
+ *
464
+ * @param {string | null} namespaceURI
465
+ * @param {string} localName
466
+ * @return {Attr | null}
467
+ *
468
+ * @see https://dom.spec.whatwg.org/#dom-namednodemap-removenameditemns
469
+ * @see https://dom.spec.whatwg.org/#concept-element-attributes-remove-by-namespace
470
+ */
328
471
  removeNamedItemNS: function (namespaceURI, localName) {
329
472
  var attr = this.getNamedItemNS(namespaceURI, localName);
473
+ if (!attr) {
474
+ throw new DOMException(NOT_FOUND_ERR, namespaceURI ? namespaceURI + ' : ' + localName : localName);
475
+ }
330
476
  _removeNamedNode(this._ownerElement, this, attr);
331
477
  return attr;
332
478
  },
479
+
480
+ /**
481
+ * get an attribute by namespace and local name
482
+ *
483
+ * @param {string | null} namespaceURI
484
+ * @param {string} localName
485
+ * @return {Attr | null}
486
+ *
487
+ * @see https://dom.spec.whatwg.org/#concept-element-attributes-get-by-namespace
488
+ */
333
489
  getNamedItemNS: function (namespaceURI, localName) {
334
- var i = this.length;
335
- while (i--) {
490
+ if (!namespaceURI) {
491
+ namespaceURI = null;
492
+ }
493
+ var i = 0;
494
+ while (i < this.length) {
336
495
  var node = this[i];
337
- if (node.localName == localName && node.namespaceURI == namespaceURI) {
496
+ if (node.localName === localName && node.namespaceURI === namespaceURI) {
338
497
  return node;
339
498
  }
499
+ i++;
340
500
  }
341
501
  return null;
342
502
  },
@@ -426,8 +586,6 @@ DOMImplementation.prototype = {
426
586
  * Returns a doctype, with the given `qualifiedName`, `publicId`, and `systemId`.
427
587
  *
428
588
  * __This behavior is slightly different from the in the specs__:
429
- * - this implementation is not validating names or qualified names
430
- * (when parsing XML strings, the SAX parser takes care of that)
431
589
  * - `encoding`, `mode`, `origin`, `url` fields are currently not declared.
432
590
  *
433
591
  * @param {string} qualifiedName
@@ -439,12 +597,9 @@ DOMImplementation.prototype = {
439
597
  * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation/createDocumentType MDN
440
598
  * @see https://www.w3.org/TR/DOM-Level-2-Core/core.html#Level-2-Core-DOM-createDocType DOM Level 2 Core
441
599
  * @see https://dom.spec.whatwg.org/#dom-domimplementation-createdocumenttype DOM Living Standard
442
- *
443
- * @see https://dom.spec.whatwg.org/#validate-and-extract DOM: Validate and extract
444
- * @see https://www.w3.org/TR/xml/#NT-NameStartChar XML Spec: Names
445
- * @see https://www.w3.org/TR/xml-names/#ns-qualnames XML Namespaces: Qualified names
446
600
  */
447
601
  createDocumentType: function (qualifiedName, publicId, systemId) {
602
+ validateQualifiedName(qualifiedName);
448
603
  var node = new DocumentType();
449
604
  node.name = qualifiedName;
450
605
  node.nodeName = qualifiedName;
@@ -603,6 +758,62 @@ Node.prototype = {
603
758
  var prefix = this.lookupPrefix(namespaceURI);
604
759
  return prefix == null;
605
760
  },
761
+ // Introduced in DOM Level 3:
762
+ /**
763
+ * Compares the reference node with a node with regard to their position
764
+ * in the document and according to the document order.
765
+ *
766
+ * @param {Node} other The node to compare the reference node to.
767
+ * @return {number} Returns how the node is positioned relatively to the
768
+ * reference node according to the bitmask. 0 if reference node and
769
+ * given node are the same.
770
+ * @see https://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/core.html#Node3-compareDocumentPosition
771
+ */
772
+ compareDocumentPosition: function (other) {
773
+ if (this === other) return 0;
774
+ var node1 = other;
775
+ var node2 = this;
776
+ var attr1 = null;
777
+ var attr2 = null;
778
+ if (node1 instanceof Attr) {
779
+ attr1 = node1;
780
+ node1 = attr1.ownerElement;
781
+ }
782
+ if (node2 instanceof Attr) {
783
+ attr2 = node2;
784
+ node2 = attr2.ownerElement;
785
+ if (attr1 && node1 && node2 === node1) {
786
+ for (var i = 0, attr; (attr = node2.attributes[i]); i++) {
787
+ if (attr === attr1) return DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC + DOCUMENT_POSITION_PRECEDING;
788
+ if (attr === attr2) return DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC + DOCUMENT_POSITION_FOLLOWING;
789
+ }
790
+ }
791
+ }
792
+ if (!node1 || !node2 || node2.ownerDocument !== node1.ownerDocument) {
793
+ return (
794
+ DOCUMENT_POSITION_DISCONNECTED +
795
+ DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC +
796
+ (docGUID(node2.ownerDocument) > docGUID(node1.ownerDocument) ? DOCUMENT_POSITION_FOLLOWING : DOCUMENT_POSITION_PRECEDING)
797
+ );
798
+ }
799
+ var chain1 = parentChain(node1);
800
+ var chain2 = parentChain(node2);
801
+ if ((!attr1 && chain2.indexOf(node1) >= 0) || (attr2 && node1 === node2)) {
802
+ return DOCUMENT_POSITION_CONTAINS + DOCUMENT_POSITION_PRECEDING;
803
+ }
804
+ if ((!attr2 && chain1.indexOf(node2) >= 0) || (attr1 && node1 === node2)) {
805
+ return DOCUMENT_POSITION_CONTAINED_BY + DOCUMENT_POSITION_FOLLOWING;
806
+ }
807
+ var ca = commonAncestor(chain2, chain1);
808
+ for (var n in ca.childNodes) {
809
+ var child = ca.childNodes[n];
810
+ if (child === node2) return DOCUMENT_POSITION_FOLLOWING;
811
+ if (child === node1) return DOCUMENT_POSITION_PRECEDING;
812
+ if (chain2.indexOf(child) >= 0) return DOCUMENT_POSITION_FOLLOWING;
813
+ if (chain1.indexOf(child) >= 0) return DOCUMENT_POSITION_PRECEDING;
814
+ }
815
+ return 0;
816
+ },
606
817
  };
607
818
 
608
819
  function _xmlEncoder(c) {
@@ -613,6 +824,8 @@ function _xmlEncoder(c) {
613
824
 
614
825
  copy(NodeType, Node);
615
826
  copy(NodeType, Node.prototype);
827
+ copy(DocumentPosition, Node);
828
+ copy(DocumentPosition, Node.prototype);
616
829
 
617
830
  /**
618
831
  * @param callback return true for continue,false for break
@@ -739,22 +952,26 @@ function _onUpdateChild(doc, el, newChild) {
739
952
  * @private
740
953
  */
741
954
  function _removeChild(parentNode, child) {
742
- var previous = child.previousSibling;
743
- var next = child.nextSibling;
744
- if (previous) {
745
- previous.nextSibling = next;
955
+ if (parentNode !== child.parentNode) {
956
+ throw new DOMException(NOT_FOUND_ERR, "child's parent is not parent");
957
+ }
958
+ //var index = parentNode.childNodes.
959
+ var oldPreviousSibling = child.previousSibling;
960
+ var oldNextSibling = child.nextSibling;
961
+ if (oldPreviousSibling) {
962
+ oldPreviousSibling.nextSibling = oldNextSibling;
746
963
  } else {
747
- parentNode.firstChild = next;
964
+ parentNode.firstChild = oldNextSibling;
748
965
  }
749
- if (next) {
750
- next.previousSibling = previous;
966
+ if (oldNextSibling) {
967
+ oldNextSibling.previousSibling = oldPreviousSibling;
751
968
  } else {
752
- parentNode.lastChild = previous;
969
+ parentNode.lastChild = oldPreviousSibling;
753
970
  }
971
+ _onUpdateChild(parentNode.ownerDocument, parentNode);
754
972
  child.parentNode = null;
755
973
  child.previousSibling = null;
756
974
  child.nextSibling = null;
757
- _onUpdateChild(parentNode.ownerDocument, parentNode);
758
975
  return child;
759
976
  }
760
977
 
@@ -1132,10 +1349,11 @@ Document.prototype = {
1132
1349
  return newChild;
1133
1350
  },
1134
1351
  removeChild: function (oldChild) {
1135
- if (this.documentElement == oldChild) {
1352
+ var removed = _removeChild(this, oldChild);
1353
+ if (removed === this.documentElement) {
1136
1354
  this.documentElement = null;
1137
1355
  }
1138
- return _removeChild(this, oldChild);
1356
+ return removed;
1139
1357
  },
1140
1358
  replaceChild: function (newChild, oldChild) {
1141
1359
  //raises
@@ -1293,6 +1511,9 @@ Document.prototype = {
1293
1511
  * @see https://dom.spec.whatwg.org/#dom-document-createattribute
1294
1512
  */
1295
1513
  createAttribute: function (name) {
1514
+ if (!conventions.QNAME.test(name)) {
1515
+ throw new DOMException(INVALID_CHARACTER_ERR, 'invalid character in name "' + name + '"');
1516
+ }
1296
1517
  if (this.type === 'html') {
1297
1518
  name = name.toLowerCase();
1298
1519
  }
@@ -1315,40 +1536,31 @@ Document.prototype = {
1315
1536
  },
1316
1537
  // Introduced in DOM Level 2:
1317
1538
  createElementNS: function (namespaceURI, qualifiedName) {
1539
+ var validated = validateAndExtract(namespaceURI, qualifiedName);
1318
1540
  var node = new Element();
1319
- var pl = qualifiedName.split(':');
1320
1541
  var attrs = (node.attributes = new NamedNodeMap());
1321
1542
  node.childNodes = new NodeList();
1322
1543
  node.ownerDocument = this;
1323
1544
  node.nodeName = qualifiedName;
1324
1545
  node.tagName = qualifiedName;
1325
- node.namespaceURI = namespaceURI;
1326
- if (pl.length == 2) {
1327
- node.prefix = pl[0];
1328
- node.localName = pl[1];
1329
- } else {
1330
- //el.prefix = null;
1331
- node.localName = qualifiedName;
1332
- }
1546
+ node.namespaceURI = validated[0];
1547
+ node.prefix = validated[1];
1548
+ node.localName = validated[2];
1333
1549
  attrs._ownerElement = node;
1334
1550
  return node;
1335
1551
  },
1336
1552
  // Introduced in DOM Level 2:
1337
1553
  createAttributeNS: function (namespaceURI, qualifiedName) {
1554
+ var validated = validateAndExtract(namespaceURI, qualifiedName);
1338
1555
  var node = new Attr();
1339
1556
  var pl = qualifiedName.split(':');
1340
1557
  node.ownerDocument = this;
1341
1558
  node.nodeName = qualifiedName;
1342
1559
  node.name = qualifiedName;
1343
- node.namespaceURI = namespaceURI;
1344
1560
  node.specified = true;
1345
- if (pl.length == 2) {
1346
- node.prefix = pl[0];
1347
- node.localName = pl[1];
1348
- } else {
1349
- //el.prefix = null;
1350
- node.localName = qualifiedName;
1351
- }
1561
+ node.namespaceURI = validated[0];
1562
+ node.prefix = validated[1];
1563
+ node.localName = validated[2];
1352
1564
  return node;
1353
1565
  },
1354
1566
  };
@@ -1366,11 +1578,17 @@ Element.prototype = {
1366
1578
  return this.ownerDocument.type === 'html' && this.namespaceURI === NAMESPACE.HTML;
1367
1579
  },
1368
1580
  hasAttribute: function (name) {
1369
- return this.getAttributeNode(name) != null;
1581
+ return !!this.getAttributeNode(name);
1370
1582
  },
1583
+ /**
1584
+ * Returns element’s first attribute whose qualified name is `name`, and `null` if there is no such attribute.
1585
+ *
1586
+ * @param {string} name
1587
+ * @return {string | null}
1588
+ */
1371
1589
  getAttribute: function (name) {
1372
1590
  var attr = this.getAttributeNode(name);
1373
- return (attr && attr.value) || '';
1591
+ return attr ? attr.value : null;
1374
1592
  },
1375
1593
  getAttributeNode: function (name) {
1376
1594
  if (this._isInHTMLDocumentAndNamespace()) {
@@ -1378,13 +1596,24 @@ Element.prototype = {
1378
1596
  }
1379
1597
  return this.attributes.getNamedItem(name);
1380
1598
  },
1599
+ /**
1600
+ * Sets the value of element’s first attribute whose qualified name is qualifiedName to value.
1601
+ *
1602
+ * @param {string} name
1603
+ * @param {string} value
1604
+ */
1381
1605
  setAttribute: function (name, value) {
1382
1606
  if (this._isInHTMLDocumentAndNamespace()) {
1383
1607
  name = name.toLowerCase();
1384
1608
  }
1385
- var attr = this.ownerDocument._createAttribute(name);
1386
- attr.value = attr.nodeValue = '' + value;
1387
- this.setAttributeNode(attr);
1609
+ var attr = this.getAttributeNode(name);
1610
+ if (attr) {
1611
+ attr.value = attr.nodeValue = '' + value;
1612
+ } else {
1613
+ attr = this.ownerDocument._createAttribute(name);
1614
+ attr.value = attr.nodeValue = '' + value;
1615
+ this.setAttributeNode(attr);
1616
+ }
1388
1617
  },
1389
1618
  removeAttribute: function (name) {
1390
1619
  var attr = this.getAttributeNode(name);
@@ -1418,14 +1647,38 @@ Element.prototype = {
1418
1647
  hasAttributeNS: function (namespaceURI, localName) {
1419
1648
  return this.getAttributeNodeNS(namespaceURI, localName) != null;
1420
1649
  },
1650
+ /**
1651
+ * Returns element’s attribute whose namespace is `namespaceURI` and local name is `localName`,
1652
+ * or `null` if there is no such attribute.
1653
+ *
1654
+ * @param {string} namespaceURI
1655
+ * @param {string} localName
1656
+ * @return {string | null}
1657
+ */
1421
1658
  getAttributeNS: function (namespaceURI, localName) {
1422
1659
  var attr = this.getAttributeNodeNS(namespaceURI, localName);
1423
- return (attr && attr.value) || '';
1660
+ return attr ? attr.value : null;
1424
1661
  },
1662
+ /**
1663
+ * Sets the value of element’s attribute whose namespace is `namespaceURI` and local name is `localName` to value.
1664
+ *
1665
+ * @param {string} namespaceURI
1666
+ * @param {string} qualifiedName
1667
+ * @param {string} value
1668
+ *
1669
+ * @see https://dom.spec.whatwg.org/#dom-element-setattributens
1670
+ */
1425
1671
  setAttributeNS: function (namespaceURI, qualifiedName, value) {
1426
- var attr = this.ownerDocument.createAttributeNS(namespaceURI, qualifiedName);
1427
- attr.value = attr.nodeValue = '' + value;
1428
- this.setAttributeNode(attr);
1672
+ var validated = validateAndExtract(namespaceURI, qualifiedName);
1673
+ var localName = validated[2];
1674
+ var attr = this.getAttributeNodeNS(namespaceURI, localName);
1675
+ if (attr) {
1676
+ attr.value = attr.nodeValue = '' + value;
1677
+ } else {
1678
+ attr = this.ownerDocument.createAttributeNS(namespaceURI, qualifiedName);
1679
+ attr.value = attr.nodeValue = '' + value;
1680
+ this.setAttributeNode(attr);
1681
+ }
1429
1682
  },
1430
1683
  getAttributeNodeNS: function (namespaceURI, localName) {
1431
1684
  return this.attributes.getNamedItemNS(namespaceURI, localName);
@@ -1499,7 +1752,11 @@ Document.prototype.getElementsByTagName = Element.prototype.getElementsByTagName
1499
1752
  Document.prototype.getElementsByTagNameNS = Element.prototype.getElementsByTagNameNS;
1500
1753
 
1501
1754
  _extends(Element, Node);
1502
- function Attr() {}
1755
+ function Attr() {
1756
+ this.namespaceURI = null;
1757
+ this.prefix = null;
1758
+ this.ownerElement = null;
1759
+ }
1503
1760
  Attr.prototype.nodeType = ATTRIBUTE_NODE;
1504
1761
  _extends(Attr, Node);
1505
1762
 
@@ -1994,11 +2251,13 @@ try {
1994
2251
  //ie8
1995
2252
  }
1996
2253
 
2254
+ exports.Attr = Attr;
1997
2255
  exports.Document = Document;
1998
2256
  exports.DocumentType = DocumentType;
1999
2257
  exports.DOMException = DOMException;
2000
2258
  exports.DOMImplementation = DOMImplementation;
2001
2259
  exports.Element = Element;
2260
+ exports.NamedNodeMap = NamedNodeMap;
2002
2261
  exports.Node = Node;
2003
2262
  exports.NodeList = NodeList;
2004
2263
  exports.XMLSerializer = XMLSerializer;