Something dumb to do for dumb reasons. This isn't a good idea, or well implemented. But I learned a few things, and thought I'd share.
//These types parse XML. With some caveats. // - depends on what you mean by parsing. // - practical applications. // // Bugs: // - Will parse malformed documents, particularly root elements. // - Serialization is not really namespace aware. // - API does not align with DOM API. interface Namespaces extends Record<string, string> { } type ALPHA = 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z'; type Alpha = ALPHA | Uppercase<ALPHA>; type NUMBS = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'; type VALID_CHARS = ALPHA | Uppercase<ALPHA> | '_'; type Special = '_' | '-' | '.' | ':'; type Trim<T> = T extends (` ${infer V}` | `${infer V} ` | `${infer V}\n`) ? Trim<V> : T; type isIn<T, Group> = T extends `${infer First}${infer Rest}` ? First extends Group ? Rest extends '' ? true : isIn<Rest, Group> : false : false; type isTagValid<T> = T extends `${infer First}${infer Rest}` ? isIn<First, Alpha | '_'> extends true ? Rest extends '' ? true : isIn<Rest, Alpha | NUMBS | Special> : false : false; type ParseTag<T, Ret extends string = ''> = T extends `${infer First}${infer Rest}` ? isTagValid<`${Ret}${First}`> extends true ? ParseTag<Rest, `${Ret}${First}`> : [Ret, T] : [Ret, T]; type StartTag<T> = T extends `<${infer STag}` ? ParseTag<STag> : never; type isEmpty<T, True = true, False = false> = [T] extends [never | undefined | ''] ? True : 'length' extends keyof T ? T['length'] extends 0 ? True : False : False; type isValidAttrName<T> = T extends `${infer First}${infer Rest}` ? isIn<First, Alpha> extends true ? isIn<Rest, Alpha | Special> : false : false; type _ParseAttrName<T, Ret extends string> = T extends `${infer F}${infer Rest}` ? F extends (Alpha | Special) ? _ParseAttrName<Rest, `${Ret}${F}`> : [Ret, T] : [Ret, T]; type ParseAttrName<T> = T extends `${infer First}${infer Rest}` ? First extends Alpha ? _ParseAttrName<Rest, First> : [T] : [T]; type Quote<T> = T extends `"${string}` ? '"' : T extends `'${string}` ? "'" : never; type _EndQ<T extends string, Q extends string = '"', Ret extends string = ''> = T extends `${infer L}\\${Q}${infer R}` ? _EndQ<R, Q, `${Ret}${L}${Q}`> : T extends `${infer V}${Q}${infer Cont}` ? [`${Ret}${V}`, Cont] : [`${Ret}${T}`, '']; type _ParseQ<T extends string, Q extends string = '"', E extends string = Q> = T extends `${Q}${infer Rest}` ? _EndQ<Rest, E> : ['', T]; type ParseQ<T extends string> = Quote<T> extends string ? _ParseQ<T, Quote<T>> : ['', T]; type ParseComment<T extends string> = _ParseQ<T, '<!--', '-->'>; type ParseAttrValue<T extends string> = T extends `${number}${string}` ? T extends `${infer Value}${infer NRest}` ? [Value, NRest] : [T, ''] : ParseQ<T>; /** * Parses attributes into objects returns [object, string]. Wish their was a way to define the types of a type. */ type _ParseAttributes<T, Ret extends {} = {}> = ParseAttrName<Trim<T>> extends [infer Attr, infer Rest] ? Attr extends string ? Rest extends `=${infer ARest}` ? ParseAttrValue<ARest> extends [infer QValue, infer QRest] ? _ParseAttributes<QRest, AddKey<Ret, Attr, QValue>> : _ParseAttributes<Rest, Ret> : _ParseAttributes<Rest, AddKey<Ret, Attr, true>> : _ParseAttributes<Rest, Ret> : [Ret, T]; type ParseAttributes<T, Ns extends Namespaces = {}> = _ParseAttributes<T> extends [infer Attr, infer Rest] ? FilterNS<Attr, Ns> extends [infer IAttr, infer INs] ? INs extends Namespaces ? [INs, AttrToNS<IAttr, INs, Def<Ns, 'xmlns', ''>>, Rest] : [Ns, {}, Rest] : [Ns, {}, Rest] : [Ns, {}, T]; type FilterNsKey<T> = T extends `xmlns:${infer Prefix}` ? [Prefix] : T extends 'xmlns' ? [T] : never; type _FilterNS<T, Attr extends Record<string, string> = {}, Ns extends Namespaces = {}> = T extends [infer K, infer V, ...infer Rest] ? K extends PropertyKey ? FilterNsKey<K> extends [infer NsP] ? NsP extends string ? _FilterNS<Rest, Attr, AddKey<Ns, NsP, V>> : _FilterNS<Rest, AddKey<Attr, K, V>, Ns> : [Attr, Ns] : [Attr, Ns] : [Attr, Ns]; //makes an attributes object and removes the namespace stuff, and adds the ns to the output. type FilterNS<T, Ns extends Namespaces> = _FilterNS<ToTuple<T>, {}, Ns>; type _AttrToNS<Attr, Ns extends Namespaces, XMLNS extends string, Ret = {}> = Attr extends [infer Key, infer Value, ...infer Rest] ? Key extends `${infer Prefix}:${infer UPKey}` ? _AttrToNS<Rest, Ns, XMLNS, AddKey<Ret, `${Prefix extends keyof Ns ? Ns[Prefix] : XMLNS}:${UPKey}`, Value>> : _AttrToNS<Rest, Ns, XMLNS, Key extends string ? AddKey<Ret, `${XMLNS}:${Key}`, Value> : Ret> : Ret; type AttrToNS<Attr, Ns extends Namespaces, XMLNS extends string> = _AttrToNS<ToTuple<Attr>, Ns, XMLNS> type TagToNS<T, Ns extends Namespaces> = T extends `${infer Prefix}:${infer Tag}` ? Prefix extends keyof Ns ? [Ns[Prefix], Tag] : [Ns['xmlns'], T] : [Ns['xmlns'], T]; //Adds a key to a type, if the type has the key already it overwrites it. type AddKey<T extends {}, K extends PropertyKey, V> = { [k in K]: V } & (K extends keyof T ? Omit<T, K> : T ); //If a key is a keyof T than return that value otherwise return the V type Def<T, K, V> = K extends keyof T ? T[K] : V; type XmlElement = { tagName: string; xmlns: string; attributes?: {}; children?: XmlNode[]; namespaces?: Namespaces; }; type XmlCData = { cdata: string }; type XmlComment = { comment: string }; type XmlNode = XmlElement | XmlComment | XmlCData | string; type FilterEmpty<T extends unknown[]> = T extends [infer First, ...infer Rest] ? [First] extends ['' | undefined | never | null] ? FilterEmpty<Rest> : [First, ...FilterEmpty<Rest>] : T; //This does the heavy lifting... Parsing comments. type _Xml<T extends string, NS extends Namespaces = { xmlns: '' }, XmlNodes extends XmlNode[] = [] > = //Empty string short circuits. T extends '' ? XmlNodes : //Parse comments T extends `<!--${string}` ? ParseComment<T> extends [infer Comment, infer CommentRest] ? CommentRest extends string ? Comment extends '' ? Xml<CommentRest, NS> : [ {comment:Comment}, ...Xml<CommentRest, NS>] : XmlNodes : XmlNodes : //Parse CData T extends `<![CDATA[${infer CData}]]>${infer CDataRest}` ? [{cdata:CData}, ...Xml<CDataRest, NS>] : //Parse tags T extends `<${string}` ? StartTag<T> extends [infer STag, infer Rest] ? TagToNS<STag, NS> extends [infer XMLNS, infer Tag] ? STag extends string ? Tag extends string ? ParseAttributes<Rest, NS> extends [infer ANS, infer Attr, infer ARest] ? ANS extends Namespaces ? Trim<ARest> extends `/>${infer Continue}` ? [{ tagName: STag, xmlns: XMLNS, namespaces: ANS, attributes: Attr }, ...Xml<Continue, ANS>] : Trim<ARest> extends `>${infer Content}</${STag}>${infer Continue}` ? Content extends `${infer ContentText}<${infer ContentXml}` ? [{ tagName: STag, xmlns: XMLNS, attributes: Attr, namespaces: ANS, children: isEmpty<ContentText, Xml<`<${ContentXml}`, ANS>, [ContentText, ...Xml<`<${ContentXml}`, ANS>]> }, ...Xml<Continue, ANS>] : [{ tagName: STag, xmlns: XMLNS, attributes: Attr, namespaces: ANS, children: [Content] }, ...Xml<Continue, ANS>] : ARest extends string ? Xml<ARest, ANS> : XmlNodes : XmlNodes : XmlNodes : XmlNodes : XmlNodes: XmlNodes: XmlNodes: //So we always start with '<' but here we reset it so we can handle text nodes T extends `${infer Content}<${infer Rest}` ? Content extends '' ? Xml<Rest, NS> : [{ content: Content}, ...Xml<Rest,NS>] : //Here we handle just text. T extends string ? [T] : XmlNodes; type Xml<T extends string, NS extends Namespaces = { xmlns: 'xml' }> = FilterEmpty<_Xml<T, NS>>; //From here down is Serialization code -- funny its longer than the parsing code. I really which there was a better way. //ToTuple was swiped from https://stackoverflow.com/questions/53058150/convert-an-interface-to-a-tuple-in-typescript and https://stackoverflow.com/questions/52855145/typescript-object-type-to-array-type-tuple type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never type LastOf<T> = UnionToIntersection<T extends any ? () => T : never> extends () => (infer R) ? R : never // TS4.1+ type ToTuple<R, T extends keyof R = keyof R, L = LastOf<T>> = [T] extends [never] ? [] : [...ToTuple<R, Exclude<T, L>>, L, R[L & keyof R]]; //end swipe type _AttrToStr<T extends readonly any[]> = T extends [infer Key, infer Value, ...infer Rest] ? Key extends string ? Value extends `${infer First}${infer VRest}` ? First extends NUMBS ? ` ${Key}=${First}${VRest}${_AttrToStr<Rest>}` : ` ${Key}="${Value}"${_AttrToStr<Rest>}` : Value extends string ? ` ${Key}="${Value}"${_AttrToStr<Rest>}` : Value extends true ? ` ${Key}${_AttrToStr<Rest>}` : Value extends false ? ` ${_AttrToStr<Rest>}` : Value extends number ? ` ${Key}=${Value}${_AttrToStr<Rest>}` : '' : '' : ''; type AttrToStr<T> = _AttrToStr<ToTuple<T>> extends never ? '' : _AttrToStr<ToTuple<T>>; type CloseTag<Tag extends string, Content extends string> = Content extends ('' | never | undefined) ? '/>' : `>${Content}</${Tag}>` //Fix Serialization so that it outputs namespaces correctly. type Serialize<T> = T extends [infer First, ...infer Rest] ? First extends XmlComment ? `<!--${First['comment']}-->${Serialize<Rest>}` : First extends XmlCData ? `<![CDATA[${First['cdata']}]]>${Serialize<Rest>}` : First extends XmlElement ? `<${First['tagName']}${AttrToStr<First['attributes']>}${CloseTag<First['tagName'], Serialize<First['children']>>}${Serialize<Rest>}` : First extends string ? `${First}${Serialize<Rest>}` : '' : ''; //Tests type PpNs0 = Xml<`<p xmlns:b="namespace-b" xmlns='new-ns' attr="1"><b:stuff b:what="1">s</b:stuff></p>`, { xmlns: 'stuff' }>; type CM0 = Xml<'<!-- comment -->'>; type Pp0 = Xml<`<br/>`, { xmlns: 'stuff' }>; type PpS = Serialize<Pp0>; type PC0 = Xml<`<br><![CDATA[hello]]>blag</br>`, { xmlns: 'stuff' }>; type PC0ST = Serialize<PC0>; type XmlNS0 = Xml<`<a:br/>`, { xmlns: 'stuff', a: 'aspace' }>; type XmlNS1 = Xml<`<a:br xmlns='def' brattr=2><p attr=1>is def</p></a:br>`, { xmlns: 'stuff', a: 'aspace' }>; type SNS1 = Serialize<XmlNS1>; type Pp0_ = Xml<`<br />`>; type Pp0C = Xml<`<br><!-- hello --></br>`>; type Pp0CS = Serialize<Pp0C>; type Pp01 = Xml<`<br class="foo" />`>; type Pp01_ = Serialize<Pp01>; type P2p01 = Xml<`<br-1 class="foo" />`>; type Pp1 = Xml<`<hello>what</hello>Rest`>; type Pp1_ = Serialize<Pp1>; type Pp2 = Xml<`<hello></hello>Rest`>; type Pp3 = Xml<`<hello>what</hello>`>; type Pp4 = Xml<`<hello class='name'>what</hello>`>; type Pp5 = Xml<`<hello>what<br/></hello>`>; type PPTT = `<hello>what<p>deep</p>more</hello>`; type Pp6 = Xml<PPTT>; type Pp6_ = Serialize<Pp6>; type BR = Xml<'<br/>'>; type BR0 = Xml<'<br class="what"/>'>; type BR1 = Xml<'<br></br>'>; type BR2 = Xml<'<br class="stuff"></br>'>; type DIV1 = Xml<'<div>hello</div>'>; type DIV2 = Xml<'<div>hello<br/>foo</div>'>; type XX1 = Xml<'<div>hello<br/></div>'>; type P1 = Xml<`<div class='stuff'>hello</div>`>; type P2 = Xml<`<div><span>he</span><why></why></div>`>; type X0 = Xml<`<div/><span/>`>; type X1 = Xml<`<div class='super' value="1"/>`>; type X2 = Xml<`<div class='super' value="1"></div>`>; type X3 = Xml<`<div class='super' value="1"><span>hello<br/></span></div>`>; //Other Tests type T1 = isTagValid<'tag'>; type T2 = isTagValid<'1tag'>;//false type T3 = isTagValid<'tag1'>; type T4 = isTagValid<'T'>; type T5 = isTagValid<'_T_'>; type T6 = isTagValid<''>; type T7 = isTagValid<'T%'>;//false type T8 = isTagValid<'T.1'>; type T9 = isTagValid<'hello:world'>; type TPA1 = ParseAttributes<'hello="world"'>; type TPA1_ = _ParseAttributes<'hello="world"'>; type TPA2 = ParseAttributes<`hello="world" goodbye='lo"nliness'`>; type TPA3 = ParseAttributes<'hello="world" goodbye'>; type TPA4 = ParseAttributes<'value=1 more>'>; type TPA5 = ParseAttributes<'more>'>; type TPA6 = ParseAttributes<'>helo'>; type T_PA1 = ParseAttrValue<'1 more'>; type T_PA2 = ParseAttrValue<'"what" more do you want'>; type T_PA3 = ParseAttrValue<'what more do you want'>; type PT1 = StartTag<'<hello/>world'>; type PT1_1 = StartTag<'<hello />world'>; type PT2 = StartTag<'<foo.bar rest/>'>; type PT3 = StartTag<'foo.bar rest'>; type TParseQ1 = ParseQ<`"hello"world`>; type TParseQ2 = ParseQ<`'hello'world`>; type TParseQ3 = ParseQ<`'hel\\'lo\\'wo'rld`>; type TQuote1 = Quote<`"hello`>; type TQuote2 = Quote<`'hello`>; type TQuote3 = Quote<'hello'>; type PA0 = ParseAttrName<'foo'>; type PA1 = ParseAttrName<'foo=bar'>; type PA2 = ParseAttrName<'f'>; type PA3 = ParseAttrName<'f-v=1'>; type PA4 = ParseAttrName<'f._-v=1'>; type IVA1 = isValidAttrName<'hello'>; type IVA2 = isValidAttrName<'hello='>; type A2NS1 = AttrToNS<{ a: '1', 'b:attr': 'b 2' }, { xmlns: 'not-default', 'b': 'namespace-b' }, 'default'>; type iSe1 = isEmpty<''>; type iSe2 = isEmpty<[]>; type iSe2_ = isEmpty<never>; type iSe3 = isEmpty<undefined, 'nope'>; type iSe4 = isEmpty<[1], true, 'one'>; type iSe5 = isEmpty<[], true, 'one'>; type TP1 = ParseComment<`<!-- comment -->there`>; type FNs1 = FilterNsKey<'xmlns:stuff'>; type FilterNSA1 = FilterNS<{ xmlns: 'xml-ns', stuff: 'ss', 'xmlns:a': 'a' }, { 'c': 'c-namespace' }>; type AddKey1 = AddKey<{ a: 1, b: 2 }, 'b', 3>; type A2S1 = AttrToStr<{ b: `1`, c: true, d: 'what' }>; type A2S2 = AttrToStr<{}>;