Skip to content

Commit

Permalink
Escape characters on xml attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
dt-jean-baptiste-lemee committed Aug 28, 2023
1 parent ee12989 commit 8085ee9
Show file tree
Hide file tree
Showing 10 changed files with 109 additions and 12 deletions.
3 changes: 2 additions & 1 deletion cjs/interface/document.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const {NodeList} = require('./node-list.js');
const {Range} = require('./range.js');
const {Text} = require('./text.js');
const {TreeWalker} = require('./tree-walker.js');
const {XMLAttr} = require('./xml-attr.js');

const query = (method, ownerDocument, selectors) => {
let {[NEXT]: next, [END]: end} = ownerDocument;
Expand Down Expand Up @@ -170,7 +171,7 @@ class Document extends NonElementParentNode {
return this[EVENT_TARGET];
}

createAttribute(name) { return new Attr(this, name); }
createAttribute(name) { return this[MIME].isXML ? new XMLAttr(this, name) : new Attr(this, name); }
createComment(textContent) { return new Comment(this, textContent); }
createDocumentFragment() { return new DocumentFragment(this); }
createDocumentType(name, publicId, systemId) { return new DocumentType(this, name, publicId, systemId); }
Expand Down
23 changes: 23 additions & 0 deletions cjs/interface/xml-attr.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use strict';
const {VALUE} = require('../shared/symbols.js');
const {emptyAttributes} = require('../shared/attributes.js');
const {escape} = require('../shared/text-escaper.js');
const {Attr} = require('./attr.js');

const QUOTE = /"/g;

/**
* @implements globalThis.Attr
*/
class XMLAttr extends Attr {
constructor(ownerDocument, name, value = '') {
super(ownerDocument, name, value);
}

toString() {
const {name, [VALUE]: value} = this;
return emptyAttributes.has(name) && !value ?
name : `${name}="${escape(value).replace(QUOTE, """)}"`;
}
}
exports.XMLAttr = XMLAttr
5 changes: 5 additions & 0 deletions cjs/shared/mime.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,31 @@ const Mime = {
'text/html': {
docType: '<!DOCTYPE html>',
ignoreCase: true,
isXML: false,
voidElements: /^(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)$/i
},
'image/svg+xml': {
docType: '<?xml version="1.0" encoding="utf-8"?>',
ignoreCase: false,
isXML: true,
voidElements
},
'text/xml': {
docType: '<?xml version="1.0" encoding="utf-8"?>',
ignoreCase: false,
isXML: true,
voidElements
},
'application/xml': {
docType: '<?xml version="1.0" encoding="utf-8"?>',
ignoreCase: false,
isXML: true,
voidElements
},
'application/xhtml+xml': {
docType: '<?xml version="1.0" encoding="utf-8"?>',
ignoreCase: false,
isXML: true,
voidElements
}
};
Expand Down
3 changes: 2 additions & 1 deletion esm/interface/document.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {NodeList} from './node-list.js';
import {Range} from './range.js';
import {Text} from './text.js';
import {TreeWalker} from './tree-walker.js';
import {XMLAttr} from './xml-attr.js';

const query = (method, ownerDocument, selectors) => {
let {[NEXT]: next, [END]: end} = ownerDocument;
Expand Down Expand Up @@ -170,7 +171,7 @@ export class Document extends NonElementParentNode {
return this[EVENT_TARGET];
}

createAttribute(name) { return new Attr(this, name); }
createAttribute(name) { return this[MIME].isXML ? new XMLAttr(this, name) : new Attr(this, name); }
createComment(textContent) { return new Comment(this, textContent); }
createDocumentFragment() { return new DocumentFragment(this); }
createDocumentType(name, publicId, systemId) { return new DocumentType(this, name, publicId, systemId); }
Expand Down
21 changes: 21 additions & 0 deletions esm/interface/xml-attr.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import {VALUE} from '../shared/symbols.js';
import {emptyAttributes} from '../shared/attributes.js';
import {escape} from '../shared/text-escaper.js';
import {Attr} from './attr.js';

const QUOTE = /"/g;

/**
* @implements globalThis.Attr
*/
export class XMLAttr extends Attr {
constructor(ownerDocument, name, value = '') {
super(ownerDocument, name, value);
}

toString() {
const {name, [VALUE]: value} = this;
return emptyAttributes.has(name) && !value ?
name : `${name}="${escape(value).replace(QUOTE, "&quot;")}"`;
}
}
5 changes: 5 additions & 0 deletions esm/shared/mime.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,31 @@ export const Mime = {
'text/html': {
docType: '<!DOCTYPE html>',
ignoreCase: true,
isXML: false,
voidElements: /^(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)$/i
},
'image/svg+xml': {
docType: '<?xml version="1.0" encoding="utf-8"?>',
ignoreCase: false,
isXML: true,
voidElements
},
'text/xml': {
docType: '<?xml version="1.0" encoding="utf-8"?>',
ignoreCase: false,
isXML: true,
voidElements
},
'application/xml': {
docType: '<?xml version="1.0" encoding="utf-8"?>',
ignoreCase: false,
isXML: true,
voidElements
},
'application/xhtml+xml': {
docType: '<?xml version="1.0" encoding="utf-8"?>',
ignoreCase: false,
isXML: true,
voidElements
}
};
22 changes: 15 additions & 7 deletions test/xml/document.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,28 @@ const assert = require('../assert.js').for('XMLDocument');

const {DOMParser} = global[Symbol.for('linkedom')];

const document = (new DOMParser).parseFromString('<root></root>', 'text/xml');
{
const document = (new DOMParser).parseFromString('<root></root>', 'text/xml');

assert(document.toString(), '<?xml version="1.0" encoding="utf-8"?><root />');
assert(document.toString(), '<?xml version="1.0" encoding="utf-8"?><root />');;

assert(document.documentElement.tagName, 'root');
assert(document.documentElement.nodeName, 'root');
assert(document.documentElement.tagName, 'root');
assert(document.documentElement.nodeName, 'root');


document.documentElement.innerHTML = `
document.documentElement.innerHTML = `
<Something>
<Element>Text</Element>
<Element>Text</Element>
</Something>
`.trim();

assert(document.querySelectorAll('Element').length, 2, 'case sesntivive 2');
assert(document.querySelectorAll('element').length, 0, 'case sesntivive 0');
assert(document.querySelectorAll('Element').length, 2, 'case sensitive 2');
assert(document.querySelectorAll('element').length, 0, 'case sensitive 0');
}

{
const document = (new DOMParser).parseFromString('<root checked attr="&amp;&lt;&gt;&quot;"></root>', 'text/xml');
assert(document.toString(), '<?xml version="1.0" encoding="utf-8"?><root checked attr="&amp;&lt;&gt;&quot;" />');
}

6 changes: 6 additions & 0 deletions types/esm/interface/xml-attr.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/**
* @implements globalThis.Attr
*/
export class XMLAttr extends Attr implements globalThis.Attr {
}
import { Attr } from "./attr.js";
5 changes: 5 additions & 0 deletions types/esm/shared/mime.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,37 @@ export const Mime: {
'text/html': {
docType: string;
ignoreCase: boolean;
isXML: boolean;
voidElements: RegExp;
};
'image/svg+xml': {
docType: string;
ignoreCase: boolean;
isXML: boolean;
voidElements: {
test: () => boolean;
};
};
'text/xml': {
docType: string;
ignoreCase: boolean;
isXML: boolean;
voidElements: {
test: () => boolean;
};
};
'application/xml': {
docType: string;
ignoreCase: boolean;
isXML: boolean;
voidElements: {
test: () => boolean;
};
};
'application/xhtml+xml': {
docType: string;
ignoreCase: boolean;
isXML: boolean;
voidElements: {
test: () => boolean;
};
Expand Down
28 changes: 25 additions & 3 deletions worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -4418,7 +4418,7 @@ let Node$1 = class Node extends DOMEventTarget {
}
};

const QUOTE = /"/g;
const QUOTE$1 = /"/g;

/**
* @implements globalThis.Attr
Expand Down Expand Up @@ -4451,7 +4451,7 @@ let Attr$1 = class Attr extends Node$1 {
toString() {
const {name, [VALUE]: value} = this;
return emptyAttributes.has(name) && !value ?
name : `${name}="${value.replace(QUOTE, '&quot;')}"`;
name : `${name}="${value.replace(QUOTE$1, '&quot;')}"`;
}

toJSON() {
Expand Down Expand Up @@ -11224,26 +11224,31 @@ const Mime = {
'text/html': {
docType: '<!DOCTYPE html>',
ignoreCase: true,
isXML: false,
voidElements: /^(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)$/i
},
'image/svg+xml': {
docType: '<?xml version="1.0" encoding="utf-8"?>',
ignoreCase: false,
isXML: true,
voidElements
},
'text/xml': {
docType: '<?xml version="1.0" encoding="utf-8"?>',
ignoreCase: false,
isXML: true,
voidElements
},
'application/xml': {
docType: '<?xml version="1.0" encoding="utf-8"?>',
ignoreCase: false,
isXML: true,
voidElements
},
'application/xhtml+xml': {
docType: '<?xml version="1.0" encoding="utf-8"?>',
ignoreCase: false,
isXML: true,
voidElements
}
};
Expand Down Expand Up @@ -11442,6 +11447,23 @@ class TreeWalker {
}
}

const QUOTE = /"/g;

/**
* @implements globalThis.Attr
*/
class XMLAttr extends Attr$1 {
constructor(ownerDocument, name, value = '') {
super(ownerDocument, name, value);
}

toString() {
const {name, [VALUE]: value} = this;
return emptyAttributes.has(name) && !value ?
name : `${name}="${escape(value).replace(QUOTE, "&quot;")}"`;
}
}

const query = (method, ownerDocument, selectors) => {
let {[NEXT]: next, [END]: end} = ownerDocument;
return method.call({ownerDocument, [NEXT]: next, [END]: end}, selectors);
Expand Down Expand Up @@ -11577,7 +11599,7 @@ let Document$1 = class Document extends NonElementParentNode {
return this[EVENT_TARGET];
}

createAttribute(name) { return new Attr$1(this, name); }
createAttribute(name) { return this[MIME].isXML ? new XMLAttr(this, name) : new Attr$1(this, name); }
createComment(textContent) { return new Comment$1(this, textContent); }
createDocumentFragment() { return new DocumentFragment$1(this); }
createDocumentType(name, publicId, systemId) { return new DocumentType$1(this, name, publicId, systemId); }
Expand Down

0 comments on commit 8085ee9

Please sign in to comment.