/// declare module "bs-raw" { interface TextDecoder { decode(input?: any): string; } interface TextEncoder { encode(input?: string): Uint8Array; } /** * Tiny buffer class for UTF8 strings. * @internal * * It is a subclass of Uint8Array with the added capacity to encode/decode text from/into string and therefore requires to be initialised with BS.setupEncodeDecoder() as such: * * ```javascript * // Encode & Decode using Buffer class (nodejs) * BS.setupEncodeDecoder( * { * encode(s) { * return BS.create(Buffer.from(s || '')) * }, * }, * { * decode(bs) { * return Buffer.from(bs).toString() * }, * } * ) * ``` * * or * * ```javascript * // Encode & Decode using TextEncoder and TextDecoder class (nodejs) * // import { TextEncoder, TextDecoder } from 'util' // required in nodejs * BS.setupEncodeDecoder(new TextEncoder(), new TextDecoder()) * ``` * * @remarks * * Use `BS.create()` to create an instance. * `slice()` and `subarray()` will return a BS instance. * */ export class BS extends Uint8Array { /** * Test whether this byte string is the start of another one for a given length * * const abc = BS.create('abc') * const abcdef = BS.create('abcdef') * abc.isStartOf(abcdef, 3) === true * abc.isStartOf(abcdef, 4) === false */ isStartOf(bs: Uint8Array, length: number): boolean; /** * Test strick equality * * const abc1 = BS.create('abc') * const abc2 = BS.create('abc') * abc1 !== abc2 * abc1.equals(abc2) === true */ equals(bs: Uint8Array): boolean; /** creates an istance from a string or a Uint8Array */ static create: (input: string | Uint8Array) => BS; /** * Extend a byte string with another one * * const abc = BS.create('abc') * const def = BS.create('def') * const abcdef = abc.append(def) * abcdef.toString() === 'abcdef' */ append(tail: BS): BS; /** converts to string */ toString(start?: number, length?: number): string; /** @internal */ static encode(_input?: string): BS; /** @internal */ static decode(_input?: BS): string; /** Defines encoder and decoder - must be called before use */ static setupEncodeDecoder(textEncoder: TextEncoder, textDecoder: TextDecoder): void; /** @internal peeks the first few chars for debugging */ get _(): string; /** @internal singleton reffering to an empty array (used for initialisation of internal variables) */ static get EMPTY(): BS; } /** @internal */ export interface BS { slice(start?: number, end?: number): BS; subarray(start?: number, end?: number): BS; } } declare module "bs-browser" { /** Setting up text decoder for node */ import { BS } from "bs-raw"; export { BS }; } declare module "bs" { import { BS } from "bs-raw"; /** @public */ export { BS }; } declare module "token" { import { BS } from "bs"; export type Attribute = { name: BS; value: BS; }; export type Ns = { name?: BS; uriString: string; uri: BS; }; /** * Defininition of the tokens emmited by the parser * @public */ export namespace Token { /** @internal */ type Token = Token.EndTag | Token.StartTag | Token.Text | Token.CDATA | Token.Comment; /** * Represent a start-tag, ie, `` * @public */ class StartTag { readonly name: BS; constructor(name: BS, atts: Attribute[], ns: Ns[], selfclosing?: true); /** @internal */ readonly atts?: { name: BS; value: BS; }[]; /** @internal */ readonly ns?: Ns[]; /** self-closing start-tag, like `` */ readonly selfClosing?: true; /** * namespace URI * * with ``, aStartTag.namespaceUri === '//uri/x' */ get namespaceUri(): string | undefined; /** * with ``, aStartTag.tagName === 'x:a' */ get tagName(): string; /** * with ``, aStartTag.localName === 'a' */ get localName(): string; /** * with ``, aStartTag.getAttribute('att') === 'value' */ getAttribute(name: string): string | undefined; /** @internal */ getAttributeFQN(fqName: BS): string | undefined; /** * with ``, aStartTag.getAttributeNS('//uri/x', 'att') === 'value' */ getAttributeNS(nsUri: string, localName: string): string | undefined; /** @internal */ get length(): number; /** @internal */ get bs(): BS; /** return this tag as a string (extra space-like character ommited) */ toString(): string; } /** * Represent a CDATA node, ie, `` * @public */ class CDATA { readonly content: BS; constructor(content: BS); /** return text as a string */ get textContent(): string; /** return this tag as a string */ toString(): string; } /** * Represent a Text node * @public */ class Text { readonly content: BS; constructor(content: BS); /** return text as a string, decoding xml entities */ toString(): string; /** return text as a string */ get textContent(): string; private decode; /** Supported entities (in addition to hex and dec codepoints) * * @remarks * * Default entities: `&`, `>`, `<`, `"` and `'` */ static entities: { c: string; bs: number[]; }[]; } /** * Represent a comment node, ie, `` * @public */ class Comment { readonly content: BS; constructor(content: BS); toString(): string; /** return comment as a string */ get textContent(): string; } /** * Represent a end-tag, ie `` * @public */ class EndTag { readonly name: BS; constructor(name: BS); toString(): string; } } } declare module "ns-resolver" { import { BS } from "bs"; type NsEntry = { srcName: BS | undefined; trgName: BS | undefined; uri: BS; uriString: 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): 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; }[]): NsResolver; /** resolve the namespace of a fully qualified tag name */ resolveTagName(name: BS): BS; private replaceNs; /** resolve the namespace of a fully qualified attribute name */ resolveAtts(atts: { name: BS; value: BS; }[]): { name: BS; value: BS; }[]; /** resolve the namespace set (attributes with 'xmlns' prefix) of a tag */ resolveNs(tagNs: { name?: BS; uri: BS; }[]): { name: BS | undefined; uriString: string; uri: BS; }[]; } } declare module "tokenizer" { import { BS } from "bs"; import { Token } from "token"; import { NsResolverMap } from "ns-resolver"; /** * Produces tokens while scanning the input stream. * @public * * @remarks * * Will emit (using `nextToken()`) any of StartTag, EndTag, Text, Comment, CDATA or undefined when the stream chunk is exhauseted (the last full tag was scanned). * On the next write, scanning resumes taking into account any uncomplete tag from the previous chunk. * * @example * Writing the xml in 2 chunks: * * ```javascript * const tokenizer = new Tokenizer() * tokenizer.write('someinn') * while ((token = tokenizer.nextToken())) { * // do something * } * tokenizer.write('ertext') * while ((token = tokenizer.nextToken())) { * // do some more * } * ``` * */ export class Tokenizer { private at; private bs; /** carry over partial names or content to next chunk */ private tail; /** tag name of pending start or end tag */ private name; /** name of pending attribute */ private attName; /** list of pending attributes */ private atts; /** list of pending name spaces */ private ns; /** state of tokenizer of previous chunk */ private state; private seenLength; /** namespace resolver */ private nsResolver?; private nsResolverStack; private parentNames; private skippingLevel; private skippingEndTag; constructor(nsResolverMap?: NsResolverMap); /** * Write some bytes to be scanned. * * @param bs - byte sequence as an instance of `BS`, `Buffer` or `string` */ write(bs: BS | string | Buffer | Uint8Array): void; get exhausted(): boolean; /** * Gets the next token * * return `undefined` when fully scanned or last tag was incomplete, waiting for more. */ nextToken(): Token.EndTag | Token.StartTag | Token.Text | Token.CDATA | Token.Comment | undefined; /** * When called, the tokenizer will skip decoding the child nodes of the last tag. * But scanning will resume on its end-tag. This end-tag will not be emitted when called with `true` (`tokenizer.skipChildNodes(true)`) * This is a performance optimization as there's no need to decode text, attribute values... * * @example * Skipping nodes * * ```javascript * tokenizer.write('someinnertext') * const aStart = tokenizer.nextToken() * const bStart = tokenizer.nextToken() * bStart.getAttribute('att') === 'value' * tokenizer.skipChildNodes() * const bEnd = tokenizer.nextToken() * bEnd.toString() === '' * ``` */ skipChildNodes(skippingEndTag?: boolean): void; /** Capuring a name */ private getName; /** Capuring a sequence */ private capture; /** Capuring content ending with a given byte */ private startingContentCapture; /** detecting the possible end of content */ private mayBeEndingContentCapture; /** detecting the actual end of content */ private hasContentCaputreEnded; /** returns the catpure (value) */ private flushCapture; /** returns the content of comment or CDATA */ private flushCaptureContent; /** if skipping this token */ private get isSkipping(); /** creates a StartTag */ private startTag; /** creates an EndTag */ private endTag; /** skipping spaces, ie after names */ private skipSpaceLike; /** testing 'XMLNS' or 'xmlns' sequence */ private isAtXmlns; /** testing 'xmlns' sequence */ private isAt_xmlns; /** testing 'XMLNS' sequence */ private isAt_XMLNS; } } declare module "parser/xml-like" { import { Token } from "token"; import { Parser } from "parser/index"; /** * Representation of an xml element * @beta * */ export class XmlElement { readonly startTag: Token.StartTag; constructor(startTag: Token.StartTag); /** * {@inheritDoc Token.StartTag.getAttribute} */ getAttribute(name: string): string | undefined; /** see {@link Token.StartTag.tagName} */ get tagName(): string; /** Children */ children: (XmlElement | Token.Text | Token.CDATA)[]; /** string as xml */ toString(): string; } /** * Parser producing a representation of an xml element see class `XmlElement` * @beta */ export const XmlElementParser: () => Parser.IParser; } declare module "parser/to-object" { import { Parser } from "parser/index"; import { Token } from "token"; export const createObject: (startTag: Token.StartTag) => any; /** * Simplified, opiniated, representation of an xml element * * Produce an object where attributes are properties (string) of the object and * children element are arrays of objects. * * @remarks * - Fully qualified names are used for property names. * - All texts are collected in property `#text` */ export class XmlToObjectParser implements Parser.IParser { onStart(startTag: Token.StartTag, parentCtx: any): void; onText(text: Token.Text | Token.CDATA, ctx: any): void; onChild(): this; } } declare module "parser/index" { import { Token } from "token"; import { BS } from "bs"; import * as XmlLike from "parser/xml-like"; /** * Parsing XML Element * @public */ export namespace Parser { /** * Interface that must be implemented by a parser * @public */ interface IParser { /** * Called on start-tag of a child element. * * Receiving the start-tag opened and a parser if different than the parent parser. If returns `false`, the child element is skipped. */ onChild?: (startTag: Token.StartTag) => IParser | false; /** * Called on end-tag. * * Receiving the context of the current element being closed and the context of its parent */ onEnd?: (ctx: any, parentCtx: any) => any; /** * Called on start-tag. * * Receiving the context of the current element being opened and the context of its parent */ onStart?: (startTag: Token.StartTag, parentCtx: any) => any; /** * Called on text or CDATA. * * Receiving the text token (`text.textContent`) and the context of its parent */ onText?: (text: Token.Text | Token.CDATA, ctx: any) => void; /** when `true`, child nodes are ignored */ skipChildNodes?: boolean; /** @internal */ passThrough?: boolean; } /** * Helps with intermediate nodes of a path like `a/b/c`. * * When calling docParser.on(`a/b/c`, myParser), `c` will be associated with the target parser but `a` and `b` will be associatiated with `PassThrough` */ class PassThrough implements IParser { readonly names: BS[]; readonly parser: IParser; passThrough: boolean; private constructor(); static on(path: string, parser: IParser): PassThrough; onChild(startTag: Token.StartTag): false | IParser | PassThrough; } /** * Switches to the first parser amongst a list of possible parsers */ class Switch implements IParser { readonly then: IParser[]; private constructor(); static create(): Switch; /** * Adds a parser to the list * @param parser-parser to be added */ case(parser: IParser): this; /** * Returns the first parser matching the start tag * @param startTag-current start-tag */ onChild(startTag: Token.StartTag): false | IParser | Switch; } /** * Returns parser to generate a simple object representation of an xml element * * @beta */ function getXmlToObjectParser(): IParser; /** Representation of an xml element */ export import XmlElement = XmlLike.XmlElement; /** Returns a object representation based on XmlElement */ export import XmlElementParser = XmlLike.XmlElementParser; } } declare module "index" { /** * @packageDocumentation * A simple js parser for XML for nodejs and the browser. * * It will handle smoothly line-breaks, spaces, chunks (from stream), namespaces... * * It is fast and optimized for Buffer or Uint8Array of encoded strings (utf8) but will take strings too. */ export { Tokenizer } from "tokenizer"; export { DocumentParser } from "document-parser"; export { Parser } from "parser/index"; export { Token } from "token"; } declare module "document-parser" { import { Tokenizer } from "index"; import { BS } from "bs"; import { Parser } from "parser/index"; /** * Defines a document parser. * @public */ export class DocumentParser { private tokenizer; private openElement?; constructor(tokenizer: Tokenizer); /** * Defines how a set of nodes should be parsed * @public * @param path - a node name of a path to a node (similar to file path, this is not a xpath) * @param parser - a parser to be applied */ on(path: string, parser: Parser.IParser): Parser.IParser; /** * Defines, as for `on()` how the whole document should be parsed * @param parser - parser to be applied */ onRoot(parser: Parser.IParser): void; /** * Writes xml chunks to the parser * @remarks * Before writing a new chunk the current one should be exhausted (all tokens have been processed, this means {@link DocumentParser.next} has returned `undefined`) * @param chunk- byte sequence (utf8) */ write(chunk: BS | string | Buffer | Uint8Array): void; /** * Getting the next element */ next(): any; private resolveTag; } }