import type { Schema } from '@idfkit/schemas'; import { IdfCollection } from './collection.js'; import { IdfObject, type FieldValues, type ObjectOwner, type StoredValue } from './object.js'; import { ReferenceGraph } from './references.js'; import type { Region } from './syntax/region.js'; import { type WriteIdfOptions } from './write/idf.js'; import type { AnyTypeMap, ObjectOf, TypeNameOf, UntypedMap, ValuesOf } from './typemap.js'; /** * An EnergyPlus model. * * Holds collections keyed by object type, a live reference graph, and the * schema for one specific EnergyPlus version. Every document is bound to a * version at construction; there is no version-agnostic mode, because field * order and reference lists genuinely differ between releases. * * The optional `M` parameter attaches a generated type map, which makes field * access statically checked without changing anything at runtime. See * `typemap.ts`. */ export declare class IdfDocument implements ObjectOwner { #private; readonly schema: Schema; constructor(schema: Schema); /** EnergyPlus version this model targets, e.g. `"26.1.0"`. */ get version(): string; get references(): ReferenceGraph; /** * The text this document was read from, when it was read with `preserveFormatting`. * * `undefined` after an ordinary read, which is how a consumer answers whether a write will * preserve: a save button that has to say "this will reformat your file" needs this and nothing * more. The anchoring behind it stays internal, because `scanIdf` already exports the layer. */ get rawText(): string | undefined; /** Object type names present in this document, in insertion order. */ types(): string[]; /** Total object count across all types. */ get size(): number; /** * Every object of one type. * * The type name is matched case-insensitively, because EnergyPlus matches it * that way: `all('zone')`, `all('ZONE')` and `all('Zone')` are one * collection, whatever casing the source file used. Every type-name-keyed * entry point resolves identically, so `get`, `require`, `has` and `remove` * cannot disagree with this one. * * When the document carries a generated type map, the argument completes * among that version's type names and the result is narrowed to the matching * field interface. Unknown names still work and simply stay untyped, which is * what version-generic code needs. * * ## Why an unknown type name returns empty rather than throwing * * `all('Zoen')` returns an empty collection. Throwing would catch the typo, * and it was weighed and rejected. `has()` must answer `false` rather than * throw, and an `all()` that throws where `has()` does not is two rules for * one question. The version-generic contract above depends on an unknown name * being answerable: code written against no particular release asks for types * that exist in some versions and not others, and that is not an error. And * `idfkit`, the Python library this API is unified with, cannot throw here at * all, because a document may carry no schema and then nothing distinguishes * a typo from a valid type; a rule that holds only when a schema happens to * be loaded is not one rule. * * The typo is caught on the paths that write, which can afford to be strict: * `add()` throws for a type in no schema, so does `attach()`, and the IDF and * epJSON parsers reject one. To ask the question directly, call * `document.schema.has(name)`. * * What this must never do, and no longer does, is *store* the empty * collection it hands back. A read that mutates the document is wrong on its * own terms: probing five misspelled names used to leave five junk keys in * `types()`, visible to every later iteration and to `toJSON`. The collection * returned for an absent type is detached from the document. * * `idfkit` resolves and declines to store on exactly the same terms, in * `IDFDocument.__getitem__`. */ all>(type: K): IdfCollection>; /** One object by type and name. */ get>(type: K, name: string): (IdfObject & ObjectOf) | undefined; /** One object by type and name, throwing if absent. */ require>(type: K, name: string): IdfObject & ObjectOf; /** Whether any object of this type exists. */ has(type: string): boolean; /** * Create an object and attach it to the document. * * Anonymous types (`Version`, `Timestep`) take `null` for the name and get a * synthetic key that never appears in output. */ add>(type: K, name: string | null, values?: ValuesOf): IdfObject & ObjectOf; /** * Untyped object creation, for the parsers. * * They work from runtime strings, so the compiler cannot check them against * the type map. Same reasoning as the in-package collection accessor: one * deliberate seam rather than casts scattered through the parse loop. */ addRaw(type: string, name: string | null, values?: FieldValues): IdfObject; /** * Attach an existing detached object, e.g. one produced by `clone()`. * * Repeats the checks `addRaw` makes rather than trusting the object. An * object carries its own schema definition, so one cloned out of a document * on a different EnergyPlus version would otherwise be written using that * version's field order under this document's `Version` header, which * mis-maps every field on reload instead of failing. */ attach(obj: IdfObject): IdfObject; /** Remove an object. Does not touch objects that referenced it. */ remove(obj: IdfObject): boolean; /** * Rename an object and rewrite every reference to it. * * Equivalent to assigning `obj.name`. */ rename(obj: IdfObject, next: string): void; /** Every object in the document, grouped by type in insertion order. */ objects(): Generator; /** * Every object a preserving write will write afresh rather than reproduce. * * Empty for a document read with `preserveFormatting` and not edited since. Every object for a * document read without it, because there is nothing to reproduce. * * `rawText` answers whether a write will preserve at all. This answers how many objects it will * REWRITE, and it is the part a consumer cannot work out for itself: a rename clears the record on * every object that referred to the renamed one, so counting from your own edit log reports one * where the answer is nine. * * **It is not "everything that will differ", and a removal is the case that separates the two.** * An object removed from the document is no longer in it to be yielded, so this can return nothing * for a write that changes the file. A consumer treating an empty result as "the file is * unchanged" would be wrong on every removal. To ask whether the file will differ at all, compare * the write with `rawText`; ask this for how much of it is being written afresh. * * A generator, so listing what is about to be reformatted is as easy as counting it: * * ```ts * const changed = [...document.changedObjects()]; * if (changed.length > 0) warn(`Saving will rewrite ${changed.length} objects.`); * ``` */ changedObjects(): Generator; /** * Where an object's characters sit in {@link rawText}, or `undefined` if they sit nowhere. * * `undefined` for an object added since the read, for a document read without preservation, and * for one read from the object notation, which has no statements to point at and preserves * all-or-nothing. * * This is what makes {@link changedObjects} usable. Turning an edit into the smallest possible * change to a file takes three things: WHICH objects will be rewritten, WHAT text each becomes, * and WHERE the old one was. Without the third a consumer has to write the whole file and diff * it, which is the work `changedObjects` exists to avoid. * * The range is where the object WAS, and stays answerable after it changes. That is the case it * is for: the objects worth locating are the ones being rewritten. * * ```ts * for (const obj of document.changedObjects()) { * const at = document.regionOf(obj); * if (at === undefined) continue; // added since the read; there is no old text to replace * edits.push({ range: at, newText: replacementFor(obj) }); * } * ``` * * **Where the replacement text comes from is not settled by this method, and `writeObject` is * not the answer.** A preserving write hands that function the author's own per-field comments, * which are internal, so calling it with options built by hand produces text that differs from * what {@link writeIdf} would have produced for the same object: the author's units and notes * come back as generated labels. A consumer that needs the two to agree has to take the whole * file from `writeIdf`. This method locates the edit; producing its text for a single object is * a gap that is open, and it is recorded rather than papered over. * * The end of the range is the WRITER's, which is not always the semicolon: a comment on the * terminator's own line is that statement's last field's comment and a preserving write replaces * it. A range that stopped at the semicolon would leave it behind, describing a field that had * just moved. * * Offsets, not a line and column: `Region` carries the conversion, and a consumer that wants one * has the text to compute it from, while going the other way costs a scan. */ regionOf(obj: IdfObject): Region | undefined; /** * One object, rendered exactly as a preserving write would render it. * * The text that belongs in the range {@link regionOf} returns, so the two compose into an edit * that leaves the file byte for byte where {@link writeIdf} would have left it. `undefined` for * an object the retained source does not hold, which is the same set `regionOf` declines. * * ```ts * for (const obj of document.changedObjects()) { * const at = document.regionOf(obj); * const text = document.renderObject(obj); * if (at === undefined || text === undefined) continue; // added since the read * edits.push({ range: at, newText: text }); * } * ``` * * `writeObject` is not this, and that is the reason this exists. A preserving write hands that * function the author's own per-field annotations, which are internal, so calling it with options * built by hand comes back with the author's units and notes as generated labels: `!- North Axis * {deg}` becomes `!- North Axis`. That is a unit lost from an engineering model by an editor * asked to save a file, and no doc comment is a good enough guard against it. * * `fieldComments` is the ONLY option, because it is the only one a preserving write honours. * `indent`, `commentColumn`, `ordering` and `versionFirst` are refused by {@link writeIdf} * alongside `preserveFormatting`, and `comments: false` and `compressed` defeat preservation and * send the whole document down the formatting path instead. Accepting any of them here would let * a caller render one object on terms the surrounding file was not written on, which is the exact * divergence this method exists to prevent. * * No trailing line break: the range this fills ends at the terminator, or at the comment on that * line, and the break after it is the first character of what separates one object from the next, * which a preserving write leaves in place. */ renderObject(obj: IdfObject, options?: Pick): string | undefined; /** Reference targets that no object provides. */ danglingReferences(): ReturnType; /** * epJSON representation. * * A type that declares no name field at all, `Version` or * `GlobalGeometryRules`, gets the `" N"` key EnergyPlus itself emits, * numbered from 1 in document order, which is what makes the output loadable * by the real engine. */ toJSON(): Record>>; /** * Whether an in-place edit to an extensible repeat has to be heard. * * Only while this document carries a retained source. Without one there is no touched record to * maintain and nothing a preserving write would consult, so the repeats stay plain objects and a * geometry consumer reads a coordinate at the cost it has always read one at. */ tracksExtensibleEdits(): boolean; onFieldChanged(obj: IdfObject, field: string, previous: unknown, next: unknown): void; onNameChanged(obj: IdfObject, previous: string, next: string): void; } //# sourceMappingURL=document.d.ts.map