diff --git a/src/api/attributes.spec.ts b/src/api/attributes.spec.ts index d9639b3da5..4cfce31eab 100644 --- a/src/api/attributes.spec.ts +++ b/src/api/attributes.spec.ts @@ -179,6 +179,15 @@ describe('$(...)', () => { expect($('.pear').attr('foo')).toBeUndefined(); expect($pear).toBeInstanceOf($); }); + + it("(bool) shouldn't treat boolean attributes differently in XML mode", () => { + const $xml = $.load(``, { + xml: true, + })('input'); + + expect($xml.attr('checked')).toBe('checked'); + expect($xml.attr('disabled')).toBe('yes'); + }); }); describe('.prop', () => { @@ -302,6 +311,15 @@ describe('$(...)', () => { '1TEXT2' ); }); + + it("(bool) shouldn't treat boolean attributes differently in XML mode", () => { + const $xml = $.load(``, { + xml: true, + })('input'); + + expect($xml.prop('checked')).toBe('checked'); + expect($xml.prop('disabled')).toBe('yes'); + }); }); describe('.data', () => { diff --git a/src/api/attributes.ts b/src/api/attributes.ts index d31b05a45b..d0de25277c 100644 --- a/src/api/attributes.ts +++ b/src/api/attributes.ts @@ -25,7 +25,7 @@ const primitives: Record = { const rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i; // Matches strings that look like JSON objects or arrays -const rbrace = /^(?:{[\w\W]*}|\[[\w\W]*])$/; +const rbrace = /^{[^]*}$|^\[[^]*]$/; /** * Gets a node's attribute. For boolean attributes, it will return the value's @@ -39,11 +39,20 @@ const rbrace = /^(?:{[\w\W]*}|\[[\w\W]*])$/; * @param name - Name of the attribute. * @returns The attribute's value. */ -function getAttr(elem: Node, name: undefined): Record; -function getAttr(elem: Node, name: string): string | undefined; function getAttr( elem: Node, - name: string | undefined + name: undefined, + xmlMode?: boolean +): Record; +function getAttr( + elem: Node, + name: string, + xmlMode?: boolean +): string | undefined; +function getAttr( + elem: Node, + name: string | undefined, + xmlMode?: boolean ): Record | string | undefined { if (!elem || !isTag(elem)) return undefined; @@ -56,7 +65,7 @@ function getAttr( if (hasOwn.call(elem.attribs, name)) { // Get the (decoded) attribute - return rboolean.test(name) ? name : elem.attribs[name]; + return !xmlMode && rboolean.test(name) ? name : elem.attribs[name]; } // Mimic the DOM and return text content as value for `option's` @@ -210,7 +219,9 @@ export function attr( }); } - return arguments.length > 1 ? this : getAttr(this[0], name as string); + return arguments.length > 1 + ? this + : getAttr(this[0], name as string, this.options.xmlMode); } /** @@ -224,16 +235,17 @@ export function attr( */ function getProp( el: Node | undefined, - name: string + name: string, + xmlMode?: boolean ): string | undefined | Element[keyof Element] { if (!el || !isTag(el)) return; return name in el ? // @ts-expect-error TS doesn't like us accessing the value directly here. el[name] - : rboolean.test(name) - ? getAttr(el, name) !== undefined - : getAttr(el, name); + : !xmlMode && rboolean.test(name) + ? getAttr(el, name, false) !== undefined + : getAttr(el, name, xmlMode); } /** @@ -244,12 +256,16 @@ function getProp( * @param name - The prop's name. * @param value - The prop's value. */ -function setProp(el: Element, name: string, value: unknown) { +function setProp(el: Element, name: string, value: unknown, xmlMode?: boolean) { if (name in el) { // @ts-expect-error Overriding value el[name] = value; } else { - setAttr(el, name, rboolean.test(name) ? (value ? '' : null) : `${value}`); + setAttr( + el, + name, + !xmlMode && rboolean.test(name) ? (value ? '' : null) : `${value}` + ); } } @@ -353,7 +369,7 @@ export function prop( return this.html(); default: - return getProp(this[0], name); + return getProp(this[0], name, this.options.xmlMode); } } @@ -363,7 +379,13 @@ export function prop( throw new Error('Bad combination of arguments.'); } return domEach(this, (el, i) => { - if (isTag(el)) setProp(el, name, value.call(el, i, getProp(el, name))); + if (isTag(el)) + setProp( + el, + name, + value.call(el, i, getProp(el, name, this.options.xmlMode)), + this.options.xmlMode + ); }); } @@ -373,10 +395,10 @@ export function prop( if (typeof name === 'object') { Object.keys(name).forEach((key) => { const val = name[key]; - setProp(el, key, val); + setProp(el, key, val, this.options.xmlMode); }); } else { - setProp(el, name, value); + setProp(el, name, value, this.options.xmlMode); } }); } @@ -810,8 +832,8 @@ export function addClass>( // If selected element isn't a tag, move on if (!isTag(el)) continue; - // If we don't already have classes - const className = getAttr(el, 'class'); + // If we don't already have classes — always set xmlMode to false here, as it doesn't matter for classes + const className = getAttr(el, 'class', false); if (!className) { setAttr(el, 'class', classNames.join(' ').trim());