import { Token } from '../token' import { BS } from '../bs' import * as XmlLike from './xml-like' import { XmlToObjectParser, createObject } from './to-object' /** * Parsing XML Element * @public */ export namespace Parser { /** * Interface that must be implemented by a parser * @public */ export 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` */ export class PassThrough implements IParser { passThrough = true private constructor(readonly names: BS[], readonly parser: IParser) {} static on(path: string, parser: IParser) { return new PassThrough( path.split('/').map(tagName => BS.create(tagName)), parser ) } onChild(startTag: Token.StartTag) { const name = this.names[0] return ( name.equals(startTag.name) && (this.names.length > 1 ? new PassThrough(this.names.slice(1), this.parser) : this.parser) ) } } /** * Switches to the first parser amongst a list of possible parsers */ export class Switch implements IParser { private constructor(readonly then: IParser[] = []) {} static create() { return new Switch() } /** * Adds a parser to the list * @param parser-parser to be added */ case(parser: IParser) { this.then.push(parser) return this } /** * Returns the first parser matching the start tag * @param startTag-current start-tag */ onChild(startTag: Token.StartTag) { const matchingPassThrough: IParser[] = [] const childParsers = this.then for (let i = 0; i < childParsers.length; i++) { const childParser = childParsers[i] const matching = childParser.onChild && childParser.onChild(startTag) if (matching) { if (matching.passThrough) { matchingPassThrough.push(matching) } else { return matching } } } return matchingPassThrough.length > 0 && new Switch(matchingPassThrough) } } /** * Returns parser to generate a simple object representation of an xml element * * @beta */ export function getXmlToObjectParser(): IParser { const parser = new XmlToObjectParser() return { onStart: createObject, onEnd: (object: any) => object, onText: parser.onText, onChild: () => parser, } } /** Representation of an xml element */ export import XmlElement = XmlLike.XmlElement /** Returns a object representation based on XmlElement */ export import XmlElementParser = XmlLike.XmlElementParser }