import { AndRule, type DataRef, LiteralRule, RepetitionRule, type Rule, type RuleTestResponse, } from '@os-team/lexical-rules'; import type { Parser } from './Parser.js'; import SRule from '../rules/SRule.js'; import NameRule from '../rules/NameRule.js'; import AttributeParser, { type Attribute } from './AttributeParser.js'; export interface EmptyElemTag { type: 'EMPTY_TAG'; name: string; attrs?: Record; } /** * The tag for an empty element. * See https://www.w3.org/TR/xml/#NT-EmptyElemTag */ class EmptyElemTagParser implements Parser { private readonly attributeParser: AttributeParser; private readonly rule: Rule< [string, string, Array<[undefined, Attribute]>, undefined, string] >; public constructor() { const prefixRule = new LiteralRule('<'); // '<' const nameRule = new NameRule(); // Name const sRule = new SRule('+'); // S this.attributeParser = new AttributeParser(); // Attribute const sAndAttributeRule = new AndRule([sRule, this.attributeParser]); // S Attribute const anySAndAttributeRule = new RepetitionRule(sAndAttributeRule, 0); // (S Attribute)* const anySRule = new SRule('*'); // S? const suffixRule = new LiteralRule('/>'); // '/>' this.rule = new AndRule([ prefixRule, nameRule, anySAndAttributeRule, anySRule, suffixRule, ]); // '<' Name (S Attribute)* S? '/>' } public test(ref: DataRef, pos: number): RuleTestResponse { const [isValid, nextPos, res] = this.rule.test(ref, pos); if (!isValid || res === undefined) return [false, nextPos]; const [, name, rawAttributes] = res; const emptyElemTag: EmptyElemTag = { type: 'EMPTY_TAG', name }; if (rawAttributes.length > 0) { emptyElemTag.attrs = rawAttributes.reduce( (acc, [, [key, value]]) => ({ ...acc, [key]: value }), {} ); } return [true, nextPos, emptyElemTag]; } public build(data: EmptyElemTag) { const attrs = Object.entries(data.attrs || {}) .map((item) => ` ${this.attributeParser.build(item)}`) .join(''); return `<${data.name}${attrs}/>`; } } export default EmptyElemTagParser;