UNPKG

fomod

Version:

A library for creating, parsing, editing, and validating XML-based Fomod installers, widely popularized in the Bethesda modding scene

84 lines (64 loc) 4.06 kB
import { ensureXmlDoctype } from "../../DomUtils"; import { TagName } from "../Enums"; import { FomodDocumentConfig } from "./FomodDocumentConfig"; import { InvalidityReport } from "./InvalidityReporting"; /** The foundation of a class that can be validated against a schema. * * This model is important because it allows for the parsing of unsafe documents while providing maximum type safety for generated documents. */ export abstract class Verifiable<TStrict extends boolean> { /** Checks if this item is compliant with the schema. Can be used both for type narrowing and input validation. */ abstract isValid(): this is Verifiable<true>; /** Query the reason why the given item is invalid. Returns `null` if the object is valid. * * This method tends to be very, very, very slow. If you are only interested in whether or not the object is valid, please use `isValid()` instead. * * Depending on how often you expect the object to be invalid, it may be faster to use `isValid()` as a fail-fast check before calling this method. */ abstract reasonForInvalidity(...tree: Omit<Verifiable<false>, 'isValid' | 'reasonForInvalidity'>[]): InvalidityReport | null; } export const ElementObjectMap = new WeakMap<Element, XmlRepresentation<boolean>>(); /** A representation of an XML element for use with various Fomod classes. helps to facilitate in-place editing of Fomod documents. * */ export abstract class XmlRepresentation<TStrict extends boolean> extends Verifiable<TStrict> { static readonly tagName: TagName|TagName[]; abstract readonly tagName: TagName; /** A [weak map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) of documents to this representation's associated element within that document. * * Without this map, in-place editing would be significantly more difficult. */ documentMap: WeakMap<Document, Element> = new WeakMap(); /** Assigns an element for a document. * * @throws If the tag name of the element does not match the tag name of this class. * @throws If an element has already been assigned for this document * @throws If the element has already been assigned to a different instance of any Fomod class. */ assignElement(element: Element) { const document = element.ownerDocument; ensureXmlDoctype(element.ownerDocument); if (element.tagName !== this.tagName && element.tagName !== this.tagName.toUpperCase()) throw new Error(`Cannot assign an element with tag name "${element.tagName}" to a class with tag name "${this.tagName}".`); if (this.documentMap.has(document)) throw new Error('Cannot assign an XmlRepresentation to an element from a given document more than once.'); if (ElementObjectMap.has(element)) throw new Error('Cannot assign an element that has already been assigned to a different instance of a Fomod class.'); this.documentMap.set(document, element); ElementObjectMap.set(element, this); } /** Returns the element assigned to the document. Will create a new one if it does not exist. */ getElementForDocument(document: Document): Element { ensureXmlDoctype(document); const existing = this.documentMap.get(document); if (existing) return existing; const newElement = document.createElement(this.tagName); this.documentMap.set(document, newElement); return newElement; } /** Preforms non-serializing operations to associate this object with a document. */ abstract associateWithDocument(document: Document): unknown; abstract decommission?(currentDocument?: Document): unknown; /** Generates an XML element from this object. */ abstract asElement(document: Document, config?: FomodDocumentConfig): Element; static parse(element: Element, config?: FomodDocumentConfig): XmlRepresentation<boolean> | null { throw new Error('This static method must be implemented by the child class!'); } }