import { BS } from './bs' type NsEntry = { srcName: BS | undefined; trgName: BS | undefined; uri: BS; uriString: string } // type NsEntry = [BS | undefined, BS | undefined, string | undefined, BS, string] export type NsResolverMap = { [nsName: string]: string } /** * Namespace resolver * * Representing the mapping of epxected namespaces and their URI's * * Created using an object whith namespaces as keys and URI's as values * * const nsResolver = NsResolver.create({svg: 'http://www.w3.org/2000/svg'}) */ export class NsResolver extends Array { /** creates an instance of `NsResolver` */ static create(nsResolverMap: NsResolverMap) { const nsResolver = new NsResolver() for (let nsName in nsResolverMap) { const uriString = nsResolverMap[nsName] const trgName = nsName ? BS.create(nsName) : undefined nsResolver.push({ srcName: trgName, trgName, uriString, uri: BS.create(uriString) }) } return nsResolver } /** * create a new resolver inheriting this one with possibly additional namespace mappings of the current XML files * * const nsResolver = NsResolver.create({ * abc: 'uri://abc', * lmn: 'uri://lmn', * '': 'uri://uvw', * }) * const nsResolverChild = nsResolver.forChildTag([ * { name: BS.create('abd'), uri: BS.create('uri://abc') }, * { name: undefined, uri: BS.create('uri://lmn') }, * { name: BS.create('uvw'), uri: BS.create('uri://uvw') }, * ]) * nsResolverChild.resolveTagName(BS.create('abd:tagName1')) === 'abc:tagName1' * nsResolverChild.resolveTagName(BS.create('tagName2')) === 'lmn:tagName2' * nsResolverChild.resolveTagName(BS.create('uvw:tagName3')) === 'tagName3' * */ forChildTag(ns: { name?: BS; uri: BS }[]) { if (!ns || ns.length === 0) return this const nsResolver = this.slice() as NsResolver ns.forEach(({ name: srcName, uri }) => { const entry = nsResolver.find(ns => ns.uri.equals(uri)) if (entry) { entry.srcName = srcName } else { nsResolver.push({ srcName, trgName: srcName, uri, uriString: uri.toString() }) } }) return nsResolver } /** resolve the namespace of a fully qualified tag name */ resolveTagName(name: BS) { const coIndex = name.indexOf(0x3a /*:*/) if (coIndex > -1) { return this.replaceNs(name, coIndex) } else { // maybe add ns const ns = this.find(ns => !ns.srcName) if (ns && ns.trgName) { const nsTarget = ns.trgName const mappedName = new BS(name.length + nsTarget.length + 1) mappedName.set(nsTarget) mappedName[nsTarget.length] = 0x3a /*:*/ mappedName.set(name.subarray(coIndex + 1), nsTarget.length + 1) return mappedName } else { return name } } } private replaceNs(name: BS, coIndex: number) { const ns = this.find(ns => ns.srcName?.isStartOf(name, coIndex)) if (ns && ns.srcName !== ns.trgName) { if (!ns.trgName) return name.subarray(coIndex + 1) if (ns.srcName && ns.srcName.length === ns.trgName.length) { ns.trgName.forEach((b, i) => (name[i] = b)) return name } else { const mappedName = new BS(name.length + ns.trgName.length - coIndex) mappedName.set(ns.trgName) mappedName.set(name.subarray(coIndex), ns.trgName.length) return mappedName } } else { return name } } /** resolve the namespace of a fully qualified attribute name */ resolveAtts(atts: { name: BS; value: BS }[]) { for (let i = 0; i < atts.length; i++) { const { name } = atts[i] const coIndex = name.indexOf(0x3a /*:*/) if (coIndex !== -1) { atts[i].name = this.replaceNs(name, coIndex) } } return atts } /** resolve the namespace set (attributes with 'xmlns' prefix) of a tag */ resolveNs(tagNs: { name?: BS; uri: BS }[]) { return tagNs.map(({ name, uri }) => { const ns = name ? this.find(({ srcName }) => srcName?.equals(name)) : this.find(({ srcName }) => !srcName) return ns ? { name: ns.trgName, uriString: ns.uriString, uri: ns.uri } : { name: name, uriString: uri.toString(), uri: uri } }) } }