Skip to content
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

Correctly handle attributes with dots in names #284

Merged
merged 1 commit into from
Dec 26, 2024
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
32 changes: 17 additions & 15 deletions src/nodes/html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,11 @@ export default class HTMLElement extends Node {
return 'null';
}

return JSON.stringify(attr.replace(/"/g, '"')).replace(/\\t/g, '\t').replace(/\\n/g, '\n').replace(/\\r/g, '\r').replace(/\\/g, '');
return JSON.stringify(attr.replace(/"/g, '"'))
.replace(/\\t/g, '\t')
.replace(/\\n/g, '\n')
.replace(/\\r/g, '\r')
.replace(/\\/g, '');
}

/**
Expand Down Expand Up @@ -373,11 +377,7 @@ export default class HTMLElement extends Node {
return child === this;
});
resetParent([this], null);
parent.childNodes = [
...parent.childNodes.slice(0, idx),
...resetParent(content, parent),
...parent.childNodes.slice(idx + 1),
];
parent.childNodes = [...parent.childNodes.slice(0, idx), ...resetParent(content, parent), ...parent.childNodes.slice(idx + 1)];
return this;
}

Expand Down Expand Up @@ -456,10 +456,12 @@ export default class HTMLElement extends Node {
this.childNodes.length = o;

// remove whitespace between attributes
const attrs = Object.keys( this.rawAttributes).map((key) => {
const val = this.rawAttributes[key];
return `${key}=${ JSON.stringify(val)}`;
}).join(' ');
const attrs = Object.keys(this.rawAttributes)
.map((key) => {
const val = this.rawAttributes[key];
return `${key}=${JSON.stringify(val)}`;
})
.join(' ');
this.rawAttrs = attrs;
delete this._rawAttrs;
return this;
Expand Down Expand Up @@ -565,7 +567,7 @@ export default class HTMLElement extends Node {
if (child.nodeType === NodeType.ELEMENT_NODE) {
if (child.id === id) {
return child;
};
}

// if children are existing push the current status to the stack and keep searching for elements in the level below
if (child.childNodes.length > 0) {
Expand Down Expand Up @@ -686,7 +688,7 @@ export default class HTMLElement extends Node {
}
const attrs = {} as RawAttributes;
if (this.rawAttrs) {
const re = /([a-zA-Z()[\]#@$.?:][a-zA-Z0-9-_:()[\]#]*)(?:\s*=\s*((?:'[^']*')|(?:"[^"]*")|\S+))?/g;
const re = /([a-zA-Z()\[\]#@$.?:][a-zA-Z0-9-._:()[\]#]*)(?:\s*=\s*((?:'[^']*')|(?:"[^"]*")|\S+))?/g;
let match: RegExpExecArray;
while ((match = re.exec(this.rawAttrs))) {
const key = match[1];
Expand Down Expand Up @@ -1023,7 +1025,7 @@ export interface Options {
* void tag serialisation, add a final slash <br/>
*/
closingSlash?: boolean;
}
};
}

const frameflag = 'documentfragmentcontainer';
Expand Down Expand Up @@ -1109,7 +1111,7 @@ export function base_parse(data: string, options = {} as Partial<Options>) {
if (!leadingSlash) {
/* Populate attributes */
const attrs = {} as Record<string, string>;
for (let attMatch; (attMatch = kAttributePattern.exec(attributes));) {
for (let attMatch; (attMatch = kAttributePattern.exec(attributes)); ) {
const { 1: key, 2: val } = attMatch;
const isQuoted = val[0] === `'` || val[0] === `"`;
attrs[key.toLowerCase()] = isQuoted ? val.slice(1, val.length - 1) : val;
Expand Down Expand Up @@ -1247,7 +1249,7 @@ export function parse(data: string, options = {} as Partial<Options>) {
* and removes nodes from any potential parent.
*/
function resolveInsertable(insertable: NodeInsertable[]): Node[] {
return insertable.map(val => {
return insertable.map((val) => {
if (typeof val === 'string') {
return new TextNode(val);
}
Expand Down
44 changes: 44 additions & 0 deletions test/tests/attributes-with-dots.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
const { parse } = require('@test/test-target');

describe('funky attributes', function () {
it('x-transition.duration.500ms', function () {
const root = parse('<div x-transition.duration.500ms></div>');
const div = root.firstChild;
div.getAttribute('x-transition.duration.500ms').should.eql('');
div.toString().should.eql('<div x-transition.duration.500ms></div>');
});

it('x-transition:enter.duration.500ms and x-transition:leave.duration.400ms', function () {
const root = parse('<div x-transition:enter.duration.500ms x-transition:leave.duration.400ms></div>');
const div = root.firstChild;
div.getAttribute('x-transition:enter.duration.500ms').should.eql('');
div.getAttribute('x-transition:leave.duration.400ms').should.eql('');
div.toString().should.eql('<div x-transition:enter.duration.500ms x-transition:leave.duration.400ms></div>');
});

it('@click="open = ! open"', function () {
const root = parse('<button @click="open = ! open">Toggle</button>');
const div = root.firstChild;
div.getAttribute('@click').should.eql('open = ! open');
div.toString().should.eql('<button @click="open = ! open">Toggle</button>');
});

it('a bunch of stuff at the same time', function () {
const root = parse(
'<div x-show="open" x-transition:enter="transition ease-out duration-300" x-transition:enter-start="opacity-0 scale-90" x-transition:enter-end="opacity-100 scale-100" x-transition:leave="transition ease-in duration-300" x-transition:leave-start="opacity-100 scale-100" x-transition:leave-end="opacity-0 scale-90">Hello 👋</div>'
);
const div = root.firstChild;

div.getAttribute('x-show').should.eql('open');
div.getAttribute('x-transition:enter').should.eql('transition ease-out duration-300');
div.getAttribute('x-transition:enter-start').should.eql('opacity-0 scale-90');
div.getAttribute('x-transition:enter-end').should.eql('opacity-100 scale-100');
div.getAttribute('x-transition:leave').should.eql('transition ease-in duration-300');
div.getAttribute('x-transition:leave-start').should.eql('opacity-100 scale-100');
div.getAttribute('x-transition:leave-end').should.eql('opacity-0 scale-90');

div.toString().should.eql(
'<div x-show="open" x-transition:enter="transition ease-out duration-300" x-transition:enter-start="opacity-0 scale-90" x-transition:enter-end="opacity-100 scale-100" x-transition:leave="transition ease-in duration-300" x-transition:leave-start="opacity-100 scale-100" x-transition:leave-end="opacity-0 scale-90">Hello 👋</div>'
);
});
});
Loading