@ifc-lite/cli 0.28.0 → 0.29.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/commands/analyze.d.ts.map +1 -1
  2. package/dist/commands/analyze.js +9 -2
  3. package/dist/commands/analyze.js.map +1 -1
  4. package/dist/commands/bcf.js +1 -1
  5. package/dist/commands/bcf.js.map +1 -1
  6. package/dist/commands/diff-test-helpers.d.ts +7 -4
  7. package/dist/commands/diff-test-helpers.d.ts.map +1 -1
  8. package/dist/commands/diff-test-helpers.js +7 -4
  9. package/dist/commands/diff-test-helpers.js.map +1 -1
  10. package/dist/commands/export-rust-formats.d.ts +11 -0
  11. package/dist/commands/export-rust-formats.d.ts.map +1 -0
  12. package/dist/commands/export-rust-formats.js +185 -0
  13. package/dist/commands/export-rust-formats.js.map +1 -0
  14. package/dist/commands/export.d.ts +9 -0
  15. package/dist/commands/export.d.ts.map +1 -1
  16. package/dist/commands/export.js +36 -148
  17. package/dist/commands/export.js.map +1 -1
  18. package/dist/commands/extract-entities.d.ts +23 -18
  19. package/dist/commands/extract-entities.d.ts.map +1 -1
  20. package/dist/commands/extract-entities.js +86 -133
  21. package/dist/commands/extract-entities.js.map +1 -1
  22. package/dist/commands/info.d.ts.map +1 -1
  23. package/dist/commands/info.js +56 -0
  24. package/dist/commands/info.js.map +1 -1
  25. package/dist/commands/mutate-step-record.d.ts +33 -0
  26. package/dist/commands/mutate-step-record.d.ts.map +1 -0
  27. package/dist/commands/mutate-step-record.js +267 -0
  28. package/dist/commands/mutate-step-record.js.map +1 -0
  29. package/dist/commands/mutate.d.ts +2 -13
  30. package/dist/commands/mutate.d.ts.map +1 -1
  31. package/dist/commands/mutate.js +4 -105
  32. package/dist/commands/mutate.js.map +1 -1
  33. package/dist/commands/query-output.d.ts.map +1 -1
  34. package/dist/commands/query-output.js +33 -6
  35. package/dist/commands/query-output.js.map +1 -1
  36. package/dist/commands/spatial-ancestors.d.ts +26 -0
  37. package/dist/commands/spatial-ancestors.d.ts.map +1 -0
  38. package/dist/commands/spatial-ancestors.js +102 -0
  39. package/dist/commands/spatial-ancestors.js.map +1 -0
  40. package/dist/commands/step-args.d.ts +140 -0
  41. package/dist/commands/step-args.d.ts.map +1 -0
  42. package/dist/commands/step-args.js +333 -0
  43. package/dist/commands/step-args.js.map +1 -0
  44. package/dist/commands/storey-selection.d.ts +6 -0
  45. package/dist/commands/storey-selection.d.ts.map +1 -0
  46. package/dist/commands/storey-selection.js +90 -0
  47. package/dist/commands/storey-selection.js.map +1 -0
  48. package/dist/commands/subset-relations.d.ts +201 -0
  49. package/dist/commands/subset-relations.d.ts.map +1 -0
  50. package/dist/commands/subset-relations.js +277 -0
  51. package/dist/commands/subset-relations.js.map +1 -0
  52. package/dist/commands/where-filter.d.ts +1 -0
  53. package/dist/commands/where-filter.d.ts.map +1 -1
  54. package/dist/commands/where-filter.js +7 -3
  55. package/dist/commands/where-filter.js.map +1 -1
  56. package/dist/index.js +1 -1
  57. package/package.json +19 -19
@@ -0,0 +1,333 @@
1
+ /* This Source Code Form is subject to the terms of the Mozilla Public
2
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
+ /**
5
+ * Reading a STEP record's top-level argument list out of its text, for a
6
+ * caller that then edits one slot BY INDEX.
7
+ *
8
+ * The failure this exists to prevent is silent. A scanner that loses track of
9
+ * quote state or paren depth still produces parts; they are just not the
10
+ * record's arguments any more, because the commas it swallowed took every
11
+ * following slot with them. The write lands on whatever the mis-scan
12
+ * accumulated and the caller reports a success that did not happen
13
+ * (LTplus-AG/ifc-lite#2470, #4125 for the `mutate` instance). So this returns
14
+ * null rather than parts it does not believe in.
15
+ *
16
+ * The mis-scan nothing structural notices is an undoubled apostrophe, what an
17
+ * authoring tool emits when it forgets to double one. The scan closes the
18
+ * string at it and reopens on the next quote, so the text between the two is
19
+ * read inside-out and every comma in it is swallowed. TWO of them leave quote
20
+ * parity EVEN and paren depth at ZERO, so the scan ends clean on text that
21
+ * split wrong: `'guid',$,'John's wall',$,$,$,$,'A's',.NOTDEFINED.` is nine
22
+ * attributes read as four, and writing slot 2 deletes attributes 3 to 7.
23
+ *
24
+ * What always shows is a token ending where a token cannot end: the phantom
25
+ * terminator leaves the rest of the content where a separator belongs
26
+ * (`'John's wall'` closes after `John`, leaving a bare `s`). So this validates
27
+ * the list as a grammar rather than counting characters. An argument is ONE
28
+ * token, after a token only whitespace and then `,` or `)` may follow, and that
29
+ * (In the STEP examples below, `*\/` is a JavaScript escape so the example does not
30
+ * terminate this comment block. The backslash is not in the STEP text, and STEP
31
+ * gives `\` its own meaning, so strip it before copying an example into a test.)
32
+ *
33
+ * holds AT EVERY DEPTH. Applied to the top level alone it misses a phantom that
34
+ * swallows a `)`, a `(` and the comma between them, which leaves the depth
35
+ * balanced and each surviving part passing a token check on its own:
36
+ * `IFCLABEL('a's'),$,IFCLABEL('b's'),$` is four attributes read as two. Any
37
+ * two top-level slots that each hold a string inside parens can do it, and real
38
+ * entities have that shape (`IfcPerson`'s MiddleNames / PrefixTitles,
39
+ * `IfcPropertyTableValue`'s DefiningValues / DefinedValues).
40
+ *
41
+ * What the rule cannot see is a corruption whose bytes are themselves a valid
42
+ * argument list: a stray apostrophe at the END of a string's content emits a
43
+ * doubled quote by accident, so `''','''` is two arguments to its author and
44
+ * one to anyone reading the text. A generated sweep accepted 814 of 80,035 corruptions
45
+ * with a split other than the author's, down from 1,066 before the rule reached into
46
+ * lists, and every survivor it looked at had that shape; separating them needs the
47
+ * schema, not the text. That harness is NOT committed, so those figures record one run
48
+ * and cannot be reproduced from this tree.
49
+ *
50
+ * A STEP block comment is refused rather than understood, because `mutate` must
51
+ * not rewrite a record it cannot read: `/` breaks a bare run, so a part
52
+ * carrying a comment outside a string is never one token, whatever the comment
53
+ * CONTAINS. Whitespace in the comment is not what gives it away:
54
+ * `$,/*renamed*\/,$` has none and was read as three slots for two attributes,
55
+ * the same phantom-slot shift as the rest of #4125 (see {@link isTokenBreak}
56
+ * for why breaking on the OPENER is enough).
57
+ *
58
+ * Several readers in this repo DO skip comments; diverging from them is
59
+ * deliberate. The nearest is `validate.ts` in this very package, which skips
60
+ * `/* ... *\/` while counting top-level attribute indices; it returns indices
61
+ * rather than parts, so it never has to put the bytes back. `source-header.ts`'s
62
+ * `splitTopLevel` is closer still, since it returns PARTS, but it drops the
63
+ * comment bytes, so it cannot satisfy the round-trip contract below either.
64
+ * And `packages/parser`'s
65
+ * `entity-extractor.ts` reads comments at every depth through
66
+ * `StepTextScan.skipLexicalAt`, and says so as policy: one comment-skip rule for
67
+ * decoded STEP text rather than a fourth hand-rolled copy. The difference is what
68
+ * the two produce. That one extracts VALUES and collapses a comment to a space;
69
+ * this one must satisfy `parts.join(',') === input` byte for byte, because its
70
+ * caller rewrites the user's file. Skipping a comment under that contract would
71
+ * silently delete it from their file, and keeping it inside the part means the
72
+ * part is not one token. Doing both needs a richer return type than `string[]`,
73
+ * which is a different module. (`STEP_TRIVIA` itself is legal anywhere whitespace
74
+ * is. Its call sites are what place it, and they all put it immediately before a
75
+ * `\(`.)
76
+ *
77
+ * What this module does NOT cover, so its refusal is not read as more than it is:
78
+ * `mutate` finds records with a line regex that allows only whitespace before the
79
+ * `(`, so a comment after the class keyword is skipped BEFORE reaching this code,
80
+ * and the run reports success having changed nothing; and `lastIndexOf(')')` can
81
+ * slice into a legal trailing comment. Both are #4163, one on each SIDE of the
82
+ * argument list (before the `(`, after the `)`), and both are the same
83
+ * line-versus-record shape as the wrapped-record case #4163 also covers.
84
+ *
85
+ * `packages/export/src/step-argument-parser.ts` is the nearest copy, and this is
86
+ * deliberately its near-twin rather than an import: `@ifc-lite/export`'s
87
+ * `exports` map exposes only `.`, so reaching it would mean adding a published
88
+ * export (and an `api-surface` entry) to a v4.0.0 package to fix a CLI bug.
89
+ * #4125 declined consolidating them, with its own measurement of why: the
90
+ * splitters in this repo have different CONTRACTS, so a shared function needs a
91
+ * mode flag or a richer return type, which is a new abstraction rather than a
92
+ * de-duplication. What they could share is the REFUSE side, and for TWO of the
93
+ * three it is pinned rather than described: the export twin and the Rust
94
+ * `split_top_level_args` are both held to the vectors in
95
+ * `rust/export/tests/fixtures/step_refuse_vectors.json`, by
96
+ * `packages/export/src/step-refuse.parity.test.ts` and by
97
+ * `rust/export/src/step_slot_tests.rs`'s
98
+ * `refuses_every_shared_cross_language_vector`.
99
+ *
100
+ * THIS splitter is NOT held to those vectors, and would not pass them today. `"`
101
+ * is absent from `isTokenBreak` below, so a binary literal is consumed as a bare
102
+ * run and two of the shared vectors are ACCEPTED here: `'g',"0F` splits as two
103
+ * parts with the unterminated literal intact, and `'g',"01,23"` splits into
104
+ * THREE parts for two attributes, because the comma inside the literal is read
105
+ * as a separator. The consumer is a live by-index writer — `mutate-step-record.ts`
106
+ * does `args[attrIdx] = …` and then `args.join(',')` — so that is the #4125
107
+ * failure still open in this file, not a hypothetical one. Adding the character
108
+ * is a behaviour change that would also refuse legitimate binary literals such
109
+ * as `"0F"`, which needs corpus verification rather than a one-character edit;
110
+ * LTplus-AG/ifc-lite#4200 tracks both the fix and the parity test that would
111
+ * have caught it.
112
+ *
113
+ * They diverge on the ACCEPT side, deliberately: this one refuses any slot
114
+ * carrying a `/* ... *\/` comment, while the export twin keeps the comment's
115
+ * bytes inside the slot and accepts it. That divergence is each caller's
116
+ * choice, so no fixture pins it.
117
+ *
118
+ * The export twin's `splitTopLevelStepArguments` used to have the three
119
+ * structural checks and no per-part check at all; #4173 gave it one
120
+ * (`isWellFormedStepSlot`), so the two now agree that a part must be one
121
+ * well-formed value.
122
+ */
123
+ /**
124
+ * Split a STEP argument list on top-level commas, or return null when the text
125
+ * cannot be scanned as one. `input` is the text BETWEEN a record's outermost
126
+ * parentheses; the module header says why null rather than parts.
127
+ *
128
+ * Nothing is trimmed and nothing is normalised, so for input this accepts
129
+ * `parts.join(',')` reproduces `input` byte for byte and a caller that replaces
130
+ * one part leaves every other byte of the record alone. Rejected: a quote still
131
+ * open at the end, a paren depth that does not return to zero, a depth that
132
+ * ever goes NEGATIVE (a stray closing paren balanced by a later opening one),
133
+ * and a part that is not one token at every depth (see {@link isLoneStepToken}).
134
+ *
135
+ * An EMPTY top-level slot (`a,,b`, or a trailing comma) is NOT rejected: it is
136
+ * invalid STEP that costs no alignment, since an empty argument is one part
137
+ * exactly as the entity parser counts it, so every index still names the
138
+ * attribute it is meant to. An empty INPUT is not an empty slot: `#1=IFCFOO();`
139
+ * has no arguments, so it splits to `[]` and any slot request then fails the
140
+ * caller's bounds check.
141
+ */
142
+ export function splitTopLevelStepArgs(input) {
143
+ if (input === '')
144
+ return [];
145
+ const parts = [];
146
+ let current = '';
147
+ let depth = 0;
148
+ let inString = false;
149
+ for (let i = 0; i < input.length; i++) {
150
+ const char = input[i];
151
+ if (char === "'") {
152
+ current += char;
153
+ // A doubled quote INSIDE a string is an escaped apostrophe. Outside one
154
+ // the first quote opens a string and the second is read on its own next
155
+ // pass, so `''` there is the empty string rather than an escape.
156
+ if (inString && input[i + 1] === "'") {
157
+ current += input[i + 1];
158
+ i++;
159
+ continue;
160
+ }
161
+ inString = !inString;
162
+ continue;
163
+ }
164
+ if (!inString) {
165
+ if (char === '(') {
166
+ depth++;
167
+ }
168
+ else if (char === ')') {
169
+ depth--;
170
+ if (depth < 0)
171
+ return null;
172
+ }
173
+ else if (char === ',' && depth === 0) {
174
+ parts.push(current);
175
+ current = '';
176
+ continue;
177
+ }
178
+ }
179
+ current += char;
180
+ }
181
+ if (inString || depth !== 0)
182
+ return null;
183
+ parts.push(current);
184
+ // These three are the contract this shares with
185
+ // `packages/export/src/step-argument-parser.ts`, which has them too (plus its
186
+ // own per-part check since #4173). That parity, not speed, is why they are
187
+ // kept: they add no coverage at all, and deleting any one of them, or all
188
+ // three, fails no test (measured). Only the `depth < 0` check above is an
189
+ // early exit; these two run after the whole scan and save only a walk over
190
+ // the parts.
191
+ return parts.every(isLoneStepToken) ? parts : null;
192
+ }
193
+ /**
194
+ * Is `part` exactly ONE STEP argument, all the way down? Surrounding whitespace
195
+ * is ignored, because a record may carry it and this must not refuse a record
196
+ * it could rewrite. An empty part passes, as an empty slot is accepted above.
197
+ */
198
+ function isLoneStepToken(part) {
199
+ const text = part.trim();
200
+ return text === '' || skipToken(text, 0) === text.length;
201
+ }
202
+ /**
203
+ * Index just past the one token starting at `from`, or -1 when the text there
204
+ * is not one token. The forms, per ISO 10303-21: a string (`'...'`, `''`
205
+ * escaping an apostrophe), a list (`(...)`), or a bare run (`$`, `*`, `#123`, a
206
+ * number, an enumeration `.T.`, a binary `"0F"`) optionally applied to a list
207
+ * (`IFCINTEGER(3)`).
208
+ *
209
+ * Deliberately loose about what a bare run CONTAINS and strict only about where
210
+ * it may end: this is not a STEP validator and must not become one. A list's
211
+ * elements are held to the same rule, so a fragment left by a phantom string
212
+ * terminator is caught wherever it lands. Nesting is carried in `depth` rather
213
+ * than by recursing, so a pathologically nested record (this text comes from a
214
+ * file) refuses instead of overflowing the stack.
215
+ */
216
+ function skipToken(text, from) {
217
+ let i = from;
218
+ let depth = 0;
219
+ let expectToken = true;
220
+ for (;;) {
221
+ i += countWhitespace(text, i);
222
+ const char = text[i];
223
+ if (expectToken) {
224
+ // A list element may be empty (`(1,,2)`), as a top-level slot may be.
225
+ if (depth > 0 && (char === ',' || char === ')')) {
226
+ expectToken = false;
227
+ continue;
228
+ }
229
+ if (char === "'") {
230
+ i = skipString(text, i);
231
+ if (i < 0)
232
+ return -1;
233
+ }
234
+ else if (char === '(') {
235
+ depth++;
236
+ i++;
237
+ continue; // the list's first element is still a token to read
238
+ }
239
+ else {
240
+ const start = i;
241
+ while (i < text.length && !isTokenBreak(text[i]))
242
+ i++;
243
+ // Nothing consumed: the end of the text, or a separator where a token
244
+ // belongs.
245
+ if (i === start)
246
+ return -1;
247
+ const afterKeyword = i + countWhitespace(text, i);
248
+ if (text[afterKeyword] === '(') {
249
+ depth++;
250
+ i = afterKeyword + 1;
251
+ continue;
252
+ }
253
+ }
254
+ expectToken = false;
255
+ continue;
256
+ }
257
+ // A token just closed, so only `,` or `)` may follow. At depth zero the
258
+ // token was the whole argument.
259
+ if (depth === 0)
260
+ return i;
261
+ if (char === ')') {
262
+ depth--;
263
+ i++;
264
+ continue;
265
+ }
266
+ if (char !== ',')
267
+ return -1;
268
+ i++;
269
+ expectToken = true;
270
+ }
271
+ }
272
+ /** Index just past the string starting at `from`, or -1 if it never closes. */
273
+ function skipString(text, from) {
274
+ for (let i = from + 1; i < text.length; i++) {
275
+ if (text[i] !== "'")
276
+ continue;
277
+ if (text[i + 1] === "'")
278
+ i++;
279
+ else
280
+ return i + 1;
281
+ }
282
+ return -1;
283
+ }
284
+ /**
285
+ * Can this character only begin or separate another token?
286
+ *
287
+ * `/` is in the set for the STEP block comment, which opens with `/*`. Breaking
288
+ * on the OPENER is enough, and is why there is no comment state machine here: a
289
+ * comment can only sit where a token or a separator belongs, and either way the
290
+ * `/` ends the bare run short of the part's end, so `isLoneStepToken` refuses
291
+ * the part however the comment is written. `*` is deliberately NOT in the set,
292
+ * because it is the derived-attribute marker and a token in its own right, so
293
+ * breaking on it would refuse `$,$,*`.
294
+ */
295
+ function isTokenBreak(char) {
296
+ return (char === "'" ||
297
+ char === '(' ||
298
+ char === ')' ||
299
+ char === ',' ||
300
+ char === '/' ||
301
+ char === ' ' ||
302
+ char === '\t' ||
303
+ char === '\n' ||
304
+ char === '\r' ||
305
+ char === '\x0b' ||
306
+ char === '\x0c');
307
+ }
308
+ /**
309
+ * How many whitespace characters run from `from`.
310
+ *
311
+ * All six characters every NAMED STEP whitespace set in this repo agrees on
312
+ * (`is_step_space`, `isSpaceByte`, `isAsciiSpace`, `STEP_TRIVIA`): ` \t\n\r\x0b\x0c`.
313
+ * This used to be space and tab only, safe only because the caller split on
314
+ * newlines before this ever ran; #4163's multi-line work now feeds this a raw
315
+ * multi-line `argsText`, so `\n` and `\r` (and `\x0b`/`\x0c`, in the set for the
316
+ * same reason) had to join both this and {@link isTokenBreak} or a bare run
317
+ * spans a line break silently and two tokens separated by one come back as ONE
318
+ * part (measured, before this widening: `'$\n$'` split to `["$\n$"]`, where
319
+ * `'$ $'` and `'$\t$'` were already refused).
320
+ */
321
+ function countWhitespace(text, from) {
322
+ let n = 0;
323
+ while (from + n < text.length &&
324
+ (text[from + n] === ' ' ||
325
+ text[from + n] === '\t' ||
326
+ text[from + n] === '\n' ||
327
+ text[from + n] === '\r' ||
328
+ text[from + n] === '\x0b' ||
329
+ text[from + n] === '\x0c'))
330
+ n++;
331
+ return n;
332
+ }
333
+ //# sourceMappingURL=step-args.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"step-args.js","sourceRoot":"","sources":["../../src/commands/step-args.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { ParsedStep } from './extract-entities.js';
2
+ /** Every product whose ObjectPlacement chains up through `storeyPlacementId`. */
3
+ export declare function productsUnderPlacement(storeyPlacementId: number, parsed: ParsedStep): Set<number>;
4
+ /** Resolve a --storey selector (GUID / name / expressId) to its placement id. */
5
+ export declare function resolveStoreyPlacement(token: string, parsed: ParsedStep): number;
6
+ //# sourceMappingURL=storey-selection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storey-selection.d.ts","sourceRoot":"","sources":["../../src/commands/storey-selection.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAaxD,iFAAiF;AACjF,wBAAgB,sBAAsB,CAAC,iBAAiB,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,GAAG,CAAC,MAAM,CAAC,CA2BjG;AAED,iFAAiF;AACjF,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,MAAM,CAgChF"}
@@ -0,0 +1,90 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+ /**
5
+ * `extract-entities`' `--storey` selector: resolve a storey token (GUID /
6
+ * name / expressId) to the set of products whose ObjectPlacement chains up
7
+ * through that storey's placement. Split out of `extract-entities.ts` to
8
+ * keep that file under its module-size budget.
9
+ */
10
+ import { refsOutsideStrings } from './subset-relations.js';
11
+ /** Map each IfcLocalPlacement to its parent placement (or null when top-level). */
12
+ function placementParents(parsed) {
13
+ const parents = new Map();
14
+ for (const inst of parsed.instances.values()) {
15
+ if (inst.type !== 'IFCLOCALPLACEMENT')
16
+ continue;
17
+ const pm = /^\s*(#\d+|\$)/.exec(inst.body);
18
+ parents.set(inst.id, pm && pm[1].startsWith('#') ? parseInt(pm[1].slice(1), 10) : null);
19
+ }
20
+ return parents;
21
+ }
22
+ /** Every product whose ObjectPlacement chains up through `storeyPlacementId`. */
23
+ export function productsUnderPlacement(storeyPlacementId, parsed) {
24
+ const parents = placementParents(parsed);
25
+ const under = new Set();
26
+ for (const pid of parents.keys()) {
27
+ let cur = pid;
28
+ let guard = 0;
29
+ while (cur != null && guard++ < 128) {
30
+ if (cur === storeyPlacementId) {
31
+ under.add(pid);
32
+ break;
33
+ }
34
+ cur = parents.get(cur) ?? null;
35
+ }
36
+ }
37
+ // Products referencing a selected placement. `refsOutsideStrings`, not a
38
+ // raw REF_RE scan: free text (e.g. `'... see also #40'`) could otherwise
39
+ // seed a product into the wrong storey's extraction.
40
+ const seeds = new Set();
41
+ for (const inst of parsed.instances.values()) {
42
+ for (const ref of refsOutsideStrings(inst.body)) {
43
+ if (under.has(ref)) {
44
+ seeds.add(inst.id);
45
+ break;
46
+ }
47
+ }
48
+ }
49
+ return seeds;
50
+ }
51
+ /** Resolve a --storey selector (GUID / name / expressId) to its placement id. */
52
+ export function resolveStoreyPlacement(token, parsed) {
53
+ let storeyId;
54
+ const t = token.trim();
55
+ if (/^#?\d+$/.test(t)) {
56
+ storeyId = parseInt(t.replace('#', ''), 10);
57
+ }
58
+ else if (parsed.guidToId.has(t)) {
59
+ storeyId = parsed.guidToId.get(t);
60
+ }
61
+ else {
62
+ // match by name (2nd-to-last-ish quoted arg); scan storeys for a Name match
63
+ for (const inst of parsed.instances.values()) {
64
+ if (inst.type !== 'IFCBUILDINGSTOREY')
65
+ continue;
66
+ if (inst.body.includes(`'${t}'`)) {
67
+ storeyId = inst.id;
68
+ break;
69
+ }
70
+ }
71
+ }
72
+ if (storeyId === undefined)
73
+ throw new Error(`Storey not found: ${token}`);
74
+ const storey = parsed.instances.get(storeyId);
75
+ if (!storey || storey.type !== 'IFCBUILDINGSTOREY') {
76
+ throw new Error(`#${storeyId} is ${storey?.type ?? 'missing'}, not an IfcBuildingStorey`);
77
+ }
78
+ // IfcBuildingStorey ObjectPlacement is attribute 6 (after Guid, Owner, Name,
79
+ // Description, ObjectType) — the last #ref before LongName/Elevation. Grab the
80
+ // placement ref: the storey references exactly one IfcLocalPlacement.
81
+ // `refsOutsideStrings`, not a raw REF_RE scan: the storey's own Name/
82
+ // Description can contain a `#id`-shaped substring (e.g. `'duplicate of
83
+ // #99'`) naming an unrelated IfcLocalPlacement.
84
+ const refs = refsOutsideStrings(storey.body);
85
+ const placementId = refs.find((r) => parsed.instances.get(r)?.type === 'IFCLOCALPLACEMENT');
86
+ if (placementId === undefined)
87
+ throw new Error(`Storey #${storeyId} has no IfcLocalPlacement`);
88
+ return placementId;
89
+ }
90
+ //# sourceMappingURL=storey-selection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storey-selection.js","sourceRoot":"","sources":["../../src/commands/storey-selection.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,201 @@
1
+ /**
2
+ * Spatial-structure relations for an extracted subset: which ones join it, and
3
+ * what text each one emits.
4
+ *
5
+ * `IfcRelContainedInSpatialStructure` is one-to-MANY, and a real exporter
6
+ * writes exactly ONE of them per storey, naming every product in that storey.
7
+ * An all-or-nothing rule (keep the relation only when every id it mentions is
8
+ * kept) therefore drops containment on ANY strict subset of a real model: the
9
+ * extracted products land outside the spatial tree and a viewer shows the
10
+ * storey with nothing under it. So the `RelatedElements` SET is rewritten down
11
+ * to the kept members instead of the relation being dropped.
12
+ *
13
+ * The no-dangling-reference invariant is unchanged: every `#id` in an emitted
14
+ * record is an id the subset keeps, and since #4128 the caller's forward
15
+ * closure keeps only ids the file DEFINES, so kept implies defined. That covers
16
+ * the ids this module CHOOSES; it does not cover the references inside a kept
17
+ * record, which are emitted verbatim, so a source file that already dangles
18
+ * still dangles. That is the rest of the rules:
19
+ * - the RELATING object (`RelatingStructure` / `RelatingObject`, the spatial
20
+ * parent) is a hard requirement. A containment with no parent is
21
+ * meaningless, and it would dangle. A KNOWN residual gap follows from that,
22
+ * and it is not this module's to close: `buildSubset` force-keeps only
23
+ * `IfcProject` / `IfcSite` / `IfcBuilding` / `IfcBuildingStorey`, so a
24
+ * product contained in an `IfcSpace` or `IfcSpatialZone` still has an
25
+ * unkept parent and still loses containment. Widening that type list is the
26
+ * wrong close, because it force-keeps every space AND its forward closure in
27
+ * every extraction; the right one is a BACKWARD closure from each kept
28
+ * product up its containment and aggregation edges, which keeps exactly the
29
+ * seeds' ancestors and needs no type list at all. Either way it is a change
30
+ * to how `buildSubset` seeds, not to the rule here. Filed as #4124.
31
+ * - an empty intersection drops the relation.
32
+ * - every other non-set reference must be kept too. That is attribute 1,
33
+ * `OwnerHistory`; usually it already is, because each kept product's own
34
+ * body names the same shared `IfcOwnerHistory` and the products' forward
35
+ * closure keeps it. An exporter that writes a PER-RELATIONSHIP
36
+ * `IfcOwnerHistory` referenced by nothing else hits this rule every time,
37
+ * and dropping there was the orphaned-storey symptom again (#4126). This
38
+ * function still takes no `parsed`, so it cannot close over such a
39
+ * reference; it REPORTS it in {@link SpatialRelationPlan.blockedOn} and the
40
+ * caller, which does have `parsed`, decides whether to keep it and replan.
41
+ * Purity is intact: `blockedOn` is a finding, not a mutation.
42
+ *
43
+ * A KNOWN gap: {@link keepWhole}, the fallback for a record this module could
44
+ * not read as its six attributes, still drops on the same private
45
+ * `OwnerHistory` and reports nothing. That is deliberate. Its references are
46
+ * read off the raw body with no established string boundaries, so a `#6` it
47
+ * names may be text rather than a reference, and forward-closing over it would
48
+ * re-create the hash-in-a-Name bug the scanner exists to avoid.
49
+ *
50
+ * A relation that loses NO member re-emits its source line verbatim, so an
51
+ * extraction that happened to keep every member does not churn.
52
+ *
53
+ * ## What this module re-implements, and why
54
+ *
55
+ * Three things here already exist in `@ifc-lite/export`, and all three are
56
+ * copied for ONE reason: that package's `exports` map exposes only `.`, and its
57
+ * `index.ts` re-exports none of them, so reaching any of them would mean adding
58
+ * a published export (and an `api-surface` entry) to a v4.0.0 package in order
59
+ * to fix a CLI bug.
60
+ *
61
+ * - `filterHiddenRefsFromRelationshipLine` (`reference-collector.ts`) is the
62
+ * same job, done better: it filters EVERY parenthesised attribute of ANY
63
+ * `IFCREL*` line against an `isExcluded` predicate, so it needs no slot
64
+ * table at all, and it carries an edge case this module has no equivalent
65
+ * of (`IfcRelConnectsStructuralMember`'s optional trailing placement).
66
+ * Adopting it would delete most of this file. The export barrier above is
67
+ * the whole reason it is not adopted here, and nothing else: fed only
68
+ * today's three types it would change no behaviour, because the CALLER
69
+ * picks which lines it sees. Widening the type set is the separate decision,
70
+ * and that is the one that changes which relations survive an extraction.
71
+ * Consolidation follow-up: #4125.
72
+ * - `splitTopLevelStepArguments` (`step-argument-parser.ts`) and
73
+ * `skipStepComment` (`step-comment-skip.ts`): see {@link splitTopLevelArgs}.
74
+ * - `STRUCTURE_RELATIONS` (`merged-empty-containers.ts`): three lines, see
75
+ * below.
76
+ */
77
+ /**
78
+ * One parsed STEP record, as `extract-entities.ts`'s `parseStep` produces it.
79
+ * Declared HERE and imported there, rather than the other way round, so the
80
+ * dependency runs one way: `extract-entities.ts` imports this module, never
81
+ * the reverse.
82
+ */
83
+ export interface StepRecord {
84
+ id: number;
85
+ type: string;
86
+ /** Argument text between the outermost parentheses. */
87
+ body: string;
88
+ /** The verbatim `#id= TYPE(...);` text. */
89
+ full: string;
90
+ }
91
+ /**
92
+ * The assembled subset: the ids to emit, plus the record text to emit for the
93
+ * relations whose related-objects SET was filtered. An id absent from
94
+ * `rewritten` emits its source line unchanged.
95
+ */
96
+ export interface Subset {
97
+ keep: Set<number>;
98
+ rewritten: ReadonlyMap<number, string>;
99
+ }
100
+ /** What {@link planSpatialRelations} found: relations to add, and their text. */
101
+ export interface SpatialRelationPlan {
102
+ /** Relation ids that join the subset. */
103
+ add: number[];
104
+ /** Relation id → rewritten record text, for the ones that lost a member. */
105
+ rewritten: Map<number, string>;
106
+ /**
107
+ * One entry per relation this plan dropped for ONE reason alone: unkept non-SET references,
108
+ * in practice a relation-private `OwnerHistory`. Its relating parent is kept and its member
109
+ * intersection is non-empty, so keeping EVERY id in the entry is all that stands between it
110
+ * and surviving. Grouped per relation, because keeping only SOME of one relation's blockers
111
+ * leaves it dropped and those ids orphaned (#4150). A caller with the parsed model closes
112
+ * over a group and replans (#4126). Every other drop is final and reports nothing here.
113
+ */
114
+ blockedOn: number[][];
115
+ }
116
+ /**
117
+ * Spatial-structure relations, as `[relatingAttributeIndex, relatedAttributeIndex]`.
118
+ * `IfcRelAggregates` names the whole (`RelatingObject`) first; the two containment
119
+ * relations name the parts (`RelatedElements`) first. Same table as `STRUCTURE_RELATIONS`
120
+ * in `@ifc-lite/export`'s `merged-empty-containers.ts`, which reads the same three
121
+ * records, copied rather than imported because that module is internal to
122
+ * `@ifc-lite/export` and exporting it would widen a published API surface for a
123
+ * three-line constant.
124
+ *
125
+ * `IfcRelReferencedInSpatialStructure` is here for the same reason the other two are:
126
+ * same shape (one relating parent, one related SET), same one-per-storey authoring,
127
+ * same claim in the command's own docs that the output "parses and renders on its
128
+ * own". It used to be missing entirely, so a referenced-but-not-contained product was
129
+ * always orphaned.
130
+ */
131
+ export declare const STRUCTURE_RELATIONS: Record<string, [number, number]>;
132
+ /**
133
+ * All three are `GlobalId, OwnerHistory, Name, Description` plus the relating/related
134
+ * pair: exactly 6 attributes in every schema that defines them. A record that does not
135
+ * split into 6 was mis-scanned (or is not the entity the type name claims), so it falls
136
+ * back to keep-whole-or-drop-whole rather than having a slot index written into
137
+ * whatever it did split into.
138
+ *
139
+ * A relation type with a different attribute COUNT (`IfcRelAssignsToGroup` has 7)
140
+ * therefore cannot simply be added as a row above: it would fail this check on every
141
+ * record and fall silently back to keep-whole-or-drop-whole, which is the bug this
142
+ * module exists to fix. Such a type needs the count moved into the table value first,
143
+ * or the whole table derived from the schema registry, which returns 7 for that entity
144
+ * and would make row four safe rather than forbidden. Filed as #4123.
145
+ */
146
+ export declare const STRUCTURE_RELATION_ATTRS = 6;
147
+ /** A single `#id` and nothing else: an object reference in one slot. */
148
+ export declare const SINGLE_REF_RE: RegExp;
149
+ /**
150
+ * Decide every spatial-structure relation against a kept-id set.
151
+ *
152
+ * Pure in both arguments: adding relation ids to `keep` afterwards cannot
153
+ * change any verdict, because a relation of these types references products,
154
+ * spatial containers and an OwnerHistory, never another relation.
155
+ */
156
+ export declare function planSpatialRelations(instances: Iterable<StepRecord>, keep: ReadonlySet<number>): SpatialRelationPlan;
157
+ /**
158
+ * The `#id` object references in one argument, ignoring any that sit inside a
159
+ * STEP string literal.
160
+ *
161
+ * A relation's `Name` and `Description` are free TEXT, and free text contains
162
+ * `#`; `parseStep` tokenizes rather than regexes precisely because Revit
163
+ * writes names like that. Read raw, the `#99` in `'Level #99 contents'` looks
164
+ * like a reference to entity 99, and one unkept lookalike drops the WHOLE
165
+ * relation: the orphaned-storey bug this module exists to fix, re-created for
166
+ * any model that names a relation that way.
167
+ *
168
+ * Correct only on an argument whose split VALIDATED, so the quote boundaries
169
+ * are the record's own. {@link keepWhole} deliberately does NOT use this. It
170
+ * runs when the split FAILED, and also when it succeeded but the record is not
171
+ * the six-attribute shape this module rewrites, so its boundaries are not
172
+ * established in general. There, over-counting references (drop the record) is
173
+ * the safe error, while under-counting (emit a dangling `#id`) is not.
174
+ *
175
+ * Exported for `extract-entities.ts`'s `forwardClosure`, which has the same
176
+ * hazard scanning a whole record body for its reference closure: nothing here
177
+ * assumes `arg` is one split ARGUMENT rather than a whole body, since a comma
178
+ * or `)` outside a string does not affect the in-string/out-of-string state
179
+ * this walks.
180
+ */
181
+ export declare function refsOutsideStrings(arg: string): number[];
182
+ /**
183
+ * Split a STEP argument list on top-level commas, respecting nested parens,
184
+ * quoted strings, doubled-quote escapes, and `/* ... *\/` comments (#4227).
185
+ * Returns null on an unterminated string, an unbalanced paren depth, or a
186
+ * depth that ever goes negative -- {@link relationLine} writes a slot by
187
+ * index (#2470), so a mis-scanned list must not hand back parts that look
188
+ * like success. A comma/paren/quote inside a comment is skipped with it (see
189
+ * {@link skipStepComment}): unskipped, its comma shifts every later slot
190
+ * boundary and `relationLine` either mis-splices a member or falls back to
191
+ * {@link keepWhole}, dropping a relation that should have survived -- the
192
+ * orphaned-storey symptom (#4126) this module exists to fix, by a different
193
+ * route. An EMPTY INTERIOR slot (`a,,b`) is one part, not rejected.
194
+ *
195
+ * Near-twin of `@ifc-lite/export`'s `splitTopLevelStepArguments` (see the
196
+ * module header). Two differences: each part is `trim`med (because
197
+ * {@link SINGLE_REF_RE} is anchored), and a TRAILING empty slot (`a,b,`)
198
+ * yields 2 parts, not 3.
199
+ */
200
+ export declare function splitTopLevelArgs(text: string): string[] | null;
201
+ //# sourceMappingURL=subset-relations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subset-relations.d.ts","sourceRoot":"","sources":["../../src/commands/subset-relations.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2EG;AAEH;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAC;IACb,2CAA2C;IAC3C,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;GAIG;AACH,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAClB,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACxC;AAED,iFAAiF;AACjF,MAAM,WAAW,mBAAmB;IAClC,yCAAyC;IACzC,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,4EAA4E;IAC5E,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B;;;;;;;OAOG;IACH,SAAS,EAAE,MAAM,EAAE,EAAE,CAAC;CACvB;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAIhE,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAE1C,wEAAwE;AACxE,eAAO,MAAM,aAAa,QAAa,CAAC;AAExC;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,QAAQ,CAAC,UAAU,CAAC,EAC/B,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,GACxB,mBAAmB,CAarB;AA8DD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAsBxD;AAgDD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAuC/D"}