import { type DataRef, LiteralRule, OrRule, type Rule, type RuleTestResponse, } from '@os-team/lexical-rules'; import { type Parser } from './Parser.js'; import MixedParser, { type Mixed } from './MixedParser.js'; import ChildrenParser, { type Children } from './ChildrenParser.js'; interface Empty { type: 'EMPTY'; } interface Any { type: 'ANY'; } export type ContentSpec = Empty | Any | Mixed | Children; /** * The content of an element. * See https://www.w3.org/TR/xml/#NT-contentspec */ class ContentSpecParser implements Parser { private readonly mixedParser: MixedParser; private readonly childrenParser: ChildrenParser; private readonly rule: Rule<'EMPTY' | 'ANY' | Mixed | Children>; public constructor() { const emptyRule = new LiteralRule('EMPTY'); // 'EMPTY' const anyRule = new LiteralRule('ANY'); // 'ANY' this.mixedParser = new MixedParser(); // Mixed this.childrenParser = new ChildrenParser(); // children this.rule = new OrRule([ emptyRule, anyRule, this.mixedParser, this.childrenParser, ]); // 'EMPTY' | 'ANY' | Mixed | children } public test(ref: DataRef, pos: number): RuleTestResponse { const [isValid, nextPos, res] = this.rule.test(ref, pos); if (!isValid || res === undefined) return [false, nextPos]; let contentSpec: ContentSpec; if (res === 'EMPTY') contentSpec = { type: 'EMPTY' }; else if (res === 'ANY') contentSpec = { type: 'ANY' }; else contentSpec = res; return [true, nextPos, contentSpec]; } public build(data: ContentSpec) { if (Array.isArray(data)) return this.childrenParser.build(data); if (data.type === 'EMPTY') return 'EMPTY'; if (data.type === 'ANY') return 'ANY'; if (data.type === 'MIXED') return this.mixedParser.build(data); return this.childrenParser.build(data); } } export default ContentSpecParser;