import { OrRule, type Rule, type RuleTestResponse, } from '@os-team/lexical-rules'; import type { DataWithOptionsRef, Parser } from './Parser.js'; import type { Content } from './ContentParser.js'; import EmptyElemTagParser, { type EmptyElemTag } from './EmptyElemTagParser.js'; import TagParser, { type Tag } from './TagParser.js'; export interface ContentWithAttributes { '#content': Content; '#attrs': Record; } export type ContentItem = Content | ContentWithAttributes; export interface Element { [key: string]: ContentItem | ContentItem[]; } /** * The element. * See https://www.w3.org/TR/xml/#NT-element */ class ElementParser implements Parser { private readonly emptyElemTagParser: EmptyElemTagParser; private readonly tagParser: TagParser; private readonly rule: Rule; public constructor() { this.emptyElemTagParser = new EmptyElemTagParser(); // EmptyElemTag this.tagParser = new TagParser(); // STag content ETag this.rule = new OrRule([this.emptyElemTagParser, this.tagParser]); // EmptyElemTag | STag content ETag } public test(ref: DataWithOptionsRef, pos: number): RuleTestResponse { const { tagName = (name) => name, include = '' } = ref.options || {}; const [isValid, nextPos, res] = this.rule.test(ref, pos); if (!isValid || res === undefined) return [false, nextPos]; const { name, content, attrs } = res.type === 'TAG' ? res : { ...res, content: '' }; const element: Element = { [tagName(name)]: attrs && ['ATTRIBUTES', 'ALL'].includes(include) ? { '#content': content, '#attrs': attrs } : content, }; return [true, nextPos, element]; } public build(data: Element) { return Object.entries(data).reduce((acc, [name, value]) => { if (Array.isArray(value)) { throw new Error('The root element must not be an array'); } const { content, attrs } = typeof value === 'object' && value !== null && value['#attrs'] ? { content: value['#content'], attrs: value['#attrs'] as Record, } : { content: value as Content, attrs: undefined }; const tag = content === '' ? this.emptyElemTagParser.build({ type: 'EMPTY_TAG', name, attrs }) : this.tagParser.build({ type: 'TAG', name, content, attrs }); return `${acc}${tag}`; }, ''); } } export default ElementParser;