@ifc-lite/rules 0.2.0
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.
- package/LICENSE +373 -0
- package/README.md +45 -0
- package/dist/engine/rule-engine-chunk.d.ts +25 -0
- package/dist/engine/rule-engine-chunk.d.ts.map +1 -0
- package/dist/engine/rule-engine-chunk.js +43 -0
- package/dist/engine/rule-engine-chunk.js.map +1 -0
- package/dist/engine/rule-engine-compare.d.ts +17 -0
- package/dist/engine/rule-engine-compare.d.ts.map +1 -0
- package/dist/engine/rule-engine-compare.js +69 -0
- package/dist/engine/rule-engine-compare.js.map +1 -0
- package/dist/engine/rule-engine-requirements.d.ts +37 -0
- package/dist/engine/rule-engine-requirements.d.ts.map +1 -0
- package/dist/engine/rule-engine-requirements.js +293 -0
- package/dist/engine/rule-engine-requirements.js.map +1 -0
- package/dist/engine/rule-engine-sets.d.ts +46 -0
- package/dist/engine/rule-engine-sets.d.ts.map +1 -0
- package/dist/engine/rule-engine-sets.js +298 -0
- package/dist/engine/rule-engine-sets.js.map +1 -0
- package/dist/engine/rule-engine-unit.d.ts +23 -0
- package/dist/engine/rule-engine-unit.d.ts.map +1 -0
- package/dist/engine/rule-engine-unit.js +64 -0
- package/dist/engine/rule-engine-unit.js.map +1 -0
- package/dist/engine/rule-engine.d.ts +29 -0
- package/dist/engine/rule-engine.d.ts.map +1 -0
- package/dist/engine/rule-engine.js +244 -0
- package/dist/engine/rule-engine.js.map +1 -0
- package/dist/filter/effective-filter-fields.d.ts +13 -0
- package/dist/filter/effective-filter-fields.d.ts.map +1 -0
- package/dist/filter/effective-filter-fields.js +44 -0
- package/dist/filter/effective-filter-fields.js.map +1 -0
- package/dist/filter/entity-predefined-type.d.ts +24 -0
- package/dist/filter/entity-predefined-type.d.ts.map +1 -0
- package/dist/filter/entity-predefined-type.js +34 -0
- package/dist/filter/entity-predefined-type.js.map +1 -0
- package/dist/filter/filter-evaluate-groups.d.ts +36 -0
- package/dist/filter/filter-evaluate-groups.d.ts.map +1 -0
- package/dist/filter/filter-evaluate-groups.js +111 -0
- package/dist/filter/filter-evaluate-groups.js.map +1 -0
- package/dist/filter/filter-evaluate-model-tag.d.ts +22 -0
- package/dist/filter/filter-evaluate-model-tag.d.ts.map +1 -0
- package/dist/filter/filter-evaluate-model-tag.js +35 -0
- package/dist/filter/filter-evaluate-model-tag.js.map +1 -0
- package/dist/filter/filter-evaluate-mutations.d.ts +51 -0
- package/dist/filter/filter-evaluate-mutations.d.ts.map +1 -0
- package/dist/filter/filter-evaluate-mutations.js +111 -0
- package/dist/filter/filter-evaluate-mutations.js.map +1 -0
- package/dist/filter/filter-evaluate-yield.d.ts +9 -0
- package/dist/filter/filter-evaluate-yield.d.ts.map +1 -0
- package/dist/filter/filter-evaluate-yield.js +32 -0
- package/dist/filter/filter-evaluate-yield.js.map +1 -0
- package/dist/filter/filter-evaluate.d.ts +120 -0
- package/dist/filter/filter-evaluate.d.ts.map +1 -0
- package/dist/filter/filter-evaluate.js +408 -0
- package/dist/filter/filter-evaluate.js.map +1 -0
- package/dist/filter/filter-group-rule.d.ts +23 -0
- package/dist/filter/filter-group-rule.d.ts.map +1 -0
- package/dist/filter/filter-group-rule.js +69 -0
- package/dist/filter/filter-group-rule.js.map +1 -0
- package/dist/filter/filter-groups.d.ts +65 -0
- package/dist/filter/filter-groups.d.ts.map +1 -0
- package/dist/filter/filter-groups.js +136 -0
- package/dist/filter/filter-groups.js.map +1 -0
- package/dist/filter/filter-iteration-source.d.ts +26 -0
- package/dist/filter/filter-iteration-source.d.ts.map +1 -0
- package/dist/filter/filter-iteration-source.js +154 -0
- package/dist/filter/filter-iteration-source.js.map +1 -0
- package/dist/filter/filter-iteration.d.ts +5 -0
- package/dist/filter/filter-iteration.d.ts.map +1 -0
- package/dist/filter/filter-iteration.js +25 -0
- package/dist/filter/filter-iteration.js.map +1 -0
- package/dist/filter/filter-match.d.ts +114 -0
- package/dist/filter/filter-match.d.ts.map +1 -0
- package/dist/filter/filter-match.js +302 -0
- package/dist/filter/filter-match.js.map +1 -0
- package/dist/filter/filter-ops.d.ts +90 -0
- package/dist/filter/filter-ops.d.ts.map +1 -0
- package/dist/filter/filter-ops.js +202 -0
- package/dist/filter/filter-ops.js.map +1 -0
- package/dist/filter/filter-rule-guards.d.ts +4 -0
- package/dist/filter/filter-rule-guards.d.ts.map +1 -0
- package/dist/filter/filter-rule-guards.js +74 -0
- package/dist/filter/filter-rule-guards.js.map +1 -0
- package/dist/filter/filter-rules.d.ts +275 -0
- package/dist/filter/filter-rules.d.ts.map +1 -0
- package/dist/filter/filter-rules.js +68 -0
- package/dist/filter/filter-rules.js.map +1 -0
- package/dist/filter/lens-material-names.d.ts +13 -0
- package/dist/filter/lens-material-names.d.ts.map +1 -0
- package/dist/filter/lens-material-names.js +36 -0
- package/dist/filter/lens-material-names.js.map +1 -0
- package/dist/filter/measure-units.d.ts +29 -0
- package/dist/filter/measure-units.d.ts.map +1 -0
- package/dist/filter/measure-units.js +72 -0
- package/dist/filter/measure-units.js.map +1 -0
- package/dist/filter/model-tag.d.ts +65 -0
- package/dist/filter/model-tag.d.ts.map +1 -0
- package/dist/filter/model-tag.js +57 -0
- package/dist/filter/model-tag.js.map +1 -0
- package/dist/filter/read-measure-subject.d.ts +28 -0
- package/dist/filter/read-measure-subject.d.ts.map +1 -0
- package/dist/filter/read-measure-subject.js +116 -0
- package/dist/filter/read-measure-subject.js.map +1 -0
- package/dist/filter/read-subject.d.ts +72 -0
- package/dist/filter/read-subject.d.ts.map +1 -0
- package/dist/filter/read-subject.js +128 -0
- package/dist/filter/read-subject.js.map +1 -0
- package/dist/filter/subject-match.d.ts +27 -0
- package/dist/filter/subject-match.d.ts.map +1 -0
- package/dist/filter/subject-match.js +52 -0
- package/dist/filter/subject-match.js.map +1 -0
- package/dist/filter/subject-read-options.d.ts +33 -0
- package/dist/filter/subject-read-options.d.ts.map +1 -0
- package/dist/filter/subject-read-options.js +8 -0
- package/dist/filter/subject-read-options.js.map +1 -0
- package/dist/ids/ids-facet-to-rules.d.ts +20 -0
- package/dist/ids/ids-facet-to-rules.d.ts.map +1 -0
- package/dist/ids/ids-facet-to-rules.js +230 -0
- package/dist/ids/ids-facet-to-rules.js.map +1 -0
- package/dist/ids/ids-regex.d.ts +44 -0
- package/dist/ids/ids-regex.d.ts.map +1 -0
- package/dist/ids/ids-regex.js +334 -0
- package/dist/ids/ids-regex.js.map +1 -0
- package/dist/ids/ids-to-rule-set.d.ts +39 -0
- package/dist/ids/ids-to-rule-set.d.ts.map +1 -0
- package/dist/ids/ids-to-rule-set.js +103 -0
- package/dist/ids/ids-to-rule-set.js.map +1 -0
- package/dist/ids/ids-xml-writer.d.ts +11 -0
- package/dist/ids/ids-xml-writer.d.ts.map +1 -0
- package/dist/ids/ids-xml-writer.js +172 -0
- package/dist/ids/ids-xml-writer.js.map +1 -0
- package/dist/ids/rule-set-to-ids.d.ts +45 -0
- package/dist/ids/rule-set-to-ids.d.ts.map +1 -0
- package/dist/ids/rule-set-to-ids.js +140 -0
- package/dist/ids/rule-set-to-ids.js.map +1 -0
- package/dist/ids/rule-to-ids-facets.d.ts +52 -0
- package/dist/ids/rule-to-ids-facets.d.ts.map +1 -0
- package/dist/ids/rule-to-ids-facets.js +291 -0
- package/dist/ids/rule-to-ids-facets.js.map +1 -0
- package/dist/ids/stored-unit-scale.d.ts +15 -0
- package/dist/ids/stored-unit-scale.d.ts.map +1 -0
- package/dist/ids/stored-unit-scale.js +64 -0
- package/dist/ids/stored-unit-scale.js.map +1 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +46 -0
- package/dist/index.js.map +1 -0
- package/dist/rule-set/between-chip.d.ts +54 -0
- package/dist/rule-set/between-chip.d.ts.map +1 -0
- package/dist/rule-set/between-chip.js +95 -0
- package/dist/rule-set/between-chip.js.map +1 -0
- package/dist/rule-set/requirement-invariants.d.ts +25 -0
- package/dist/rule-set/requirement-invariants.d.ts.map +1 -0
- package/dist/rule-set/requirement-invariants.js +29 -0
- package/dist/rule-set/requirement-invariants.js.map +1 -0
- package/dist/rule-set/requirement-text.d.ts +20 -0
- package/dist/rule-set/requirement-text.d.ts.map +1 -0
- package/dist/rule-set/requirement-text.js +179 -0
- package/dist/rule-set/requirement-text.js.map +1 -0
- package/dist/rule-set/rule-set-io-requirement.d.ts +22 -0
- package/dist/rule-set/rule-set-io-requirement.d.ts.map +1 -0
- package/dist/rule-set/rule-set-io-requirement.js +140 -0
- package/dist/rule-set/rule-set-io-requirement.js.map +1 -0
- package/dist/rule-set/rule-set-io-shared.d.ts +24 -0
- package/dist/rule-set/rule-set-io-shared.d.ts.map +1 -0
- package/dist/rule-set/rule-set-io-shared.js +48 -0
- package/dist/rule-set/rule-set-io-shared.js.map +1 -0
- package/dist/rule-set/rule-set-io-subject.d.ts +10 -0
- package/dist/rule-set/rule-set-io-subject.d.ts.map +1 -0
- package/dist/rule-set/rule-set-io-subject.js +67 -0
- package/dist/rule-set/rule-set-io-subject.js.map +1 -0
- package/dist/rule-set/rule-set-io.d.ts +37 -0
- package/dist/rule-set/rule-set-io.d.ts.map +1 -0
- package/dist/rule-set/rule-set-io.js +142 -0
- package/dist/rule-set/rule-set-io.js.map +1 -0
- package/dist/rule-set/rule-set.d.ts +109 -0
- package/dist/rule-set/rule-set.d.ts.map +1 -0
- package/dist/rule-set/rule-set.js +5 -0
- package/dist/rule-set/rule-set.js.map +1 -0
- package/package.json +59 -0
|
@@ -0,0 +1,302 @@
|
|
|
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
|
+
import { flattenMaterials } from '@ifc-lite/ids';
|
|
5
|
+
import { collectSpatialAncestors } from '@ifc-lite/data';
|
|
6
|
+
import { valueOpMatches, numericOpMatches, matchStringAnyNone } from './filter-ops.js';
|
|
7
|
+
import { lensMaterialNames } from './lens-material-names.js';
|
|
8
|
+
import { parsePropertyValue } from '@ifc-lite/encoding';
|
|
9
|
+
import { compileNameMatcher, isNamePattern } from '@ifc-lite/lists';
|
|
10
|
+
/**
|
|
11
|
+
* Compare a rule's property-set / property name against a row's.
|
|
12
|
+
*
|
|
13
|
+
* `kind` is the rule saying what it holds (see {@link TextKind}). A `'regex'`
|
|
14
|
+
* name is a SOURCE and the whole string is the pattern, which is what makes
|
|
15
|
+
* the selector syntax's `/Pset_.*Common/.FireRating` reach `Pset_WallCommon`
|
|
16
|
+
* and `Pset_SlabCommon` in one rule; a `'literal'` name is compared as text
|
|
17
|
+
* even when it is spelled with slashes, which is what the grammar's quoting
|
|
18
|
+
* means and the only reason `"/Wall/".FireRating` can be asked for at all.
|
|
19
|
+
*
|
|
20
|
+
* A name with no declared kind was typed into a chip field, where the
|
|
21
|
+
* Lists-panel `/…/` convention (#1591) is the user's only way to say
|
|
22
|
+
* "pattern"; anything else there is the historical case-insensitive equality.
|
|
23
|
+
*
|
|
24
|
+
* `compileNameMatcher` throws when the regex body is syntactically valid but
|
|
25
|
+
* rejected by `@ifc-lite/regex-guard` as unsafe (catastrophic-backtracking
|
|
26
|
+
* shaped, or over the length cap) — see `filter-ops.ts`'s `regexOpMatches`
|
|
27
|
+
* docstring for the full contract. This function does not catch that; it
|
|
28
|
+
* propagates to `filter-evaluate.ts`'s caller, which must.
|
|
29
|
+
*/
|
|
30
|
+
export function nameMatches(rulePattern, rowName, kind) {
|
|
31
|
+
if (kind === 'regex')
|
|
32
|
+
return compileNameMatcher(`/${rulePattern}/`)(rowName);
|
|
33
|
+
if (kind === undefined && isNamePattern(rulePattern))
|
|
34
|
+
return compileNameMatcher(rulePattern)(rowName);
|
|
35
|
+
return rowName.toLowerCase() === rulePattern.toLowerCase();
|
|
36
|
+
}
|
|
37
|
+
export function flattenPsets(psets) {
|
|
38
|
+
const out = [];
|
|
39
|
+
for (const set of psets) {
|
|
40
|
+
for (const p of set.properties) {
|
|
41
|
+
out.push({
|
|
42
|
+
setName: set.name,
|
|
43
|
+
propertyName: p.name,
|
|
44
|
+
// Stringify everything — `valueOpMatches` re-parses numeric ops
|
|
45
|
+
// from this representation. Booleans render as "True"/"False" —
|
|
46
|
+
// the SAME capitalised display string the property table and the
|
|
47
|
+
// list engine (`@ifc-lite/encoding`'s `parsePropertyValue`) show —
|
|
48
|
+
// so a value picked from a chip/dropdown suggestion always matches
|
|
49
|
+
// what the user sees rendered elsewhere. `valueOpMatches`'s eq/ne
|
|
50
|
+
// are case-insensitive, so this doesn't change matching outcomes
|
|
51
|
+
// for either the search chips or free-typed "true"/"false".
|
|
52
|
+
value: stringifyValue(p.value),
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
export function flattenQtys(qtos) {
|
|
59
|
+
const out = [];
|
|
60
|
+
for (const set of qtos) {
|
|
61
|
+
for (const q of set.quantities) {
|
|
62
|
+
out.push({ setName: set.name, quantityName: q.name, value: q.value });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
export function stringifyValue(value) {
|
|
68
|
+
if (value === null || value === undefined)
|
|
69
|
+
return '';
|
|
70
|
+
// Match `parsePropertyValue`'s boolean rendering ("True"/"False") — the
|
|
71
|
+
// SAME function the property table and the list engine's display use —
|
|
72
|
+
// so discovered dropdown suggestions equal what's actually shown/matched
|
|
73
|
+
// elsewhere (#IsExternal true/false mismatch report).
|
|
74
|
+
if (typeof value === 'boolean')
|
|
75
|
+
return parsePropertyValue(value).displayValue;
|
|
76
|
+
if (typeof value === 'number')
|
|
77
|
+
return String(value);
|
|
78
|
+
return String(value);
|
|
79
|
+
}
|
|
80
|
+
export function matchPropertyRule(rule, rows) {
|
|
81
|
+
// isSet / isNotSet are presence checks against (setName, propertyName).
|
|
82
|
+
if (rule.op === 'isSet' || rule.op === 'isNotSet') {
|
|
83
|
+
const present = rows.some((r) => nameMatches(rule.setName, r.setName, rule.setNameKind) &&
|
|
84
|
+
nameMatches(rule.propertyName, r.propertyName, rule.propertyNameKind));
|
|
85
|
+
return rule.op === 'isSet' ? present : !present;
|
|
86
|
+
}
|
|
87
|
+
return rows.some((r) => nameMatches(rule.setName, r.setName, rule.setNameKind) &&
|
|
88
|
+
nameMatches(rule.propertyName, r.propertyName, rule.propertyNameKind) &&
|
|
89
|
+
valueOpMatches(rule.op, r.value, rule.value, rule.valueKind));
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Match an `attribute` rule (Description, ObjectType, Tag, LongName, any
|
|
93
|
+
* other schema-named attribute) against one entity's extracted attributes.
|
|
94
|
+
* Attribute NAME matching is case-insensitive, same as the IDS attribute
|
|
95
|
+
* facet this reuses the extraction from; the VALUE comparison reuses
|
|
96
|
+
* `valueOpMatches`, the same comparator `matchPropertyRule` uses, so a
|
|
97
|
+
* numeric attribute value compares the same way a numeric property does.
|
|
98
|
+
*/
|
|
99
|
+
export function matchAttributeRule(rule, attrs) {
|
|
100
|
+
const wanted = rule.name.toLowerCase();
|
|
101
|
+
const found = attrs.find((a) => a.name.toLowerCase() === wanted);
|
|
102
|
+
const stringified = found === undefined ? undefined : stringifyValue(found.value);
|
|
103
|
+
if (rule.op === 'isSet' || rule.op === 'isNotSet') {
|
|
104
|
+
const present = (stringified ?? '').length > 0;
|
|
105
|
+
return rule.op === 'isSet' ? present : !present;
|
|
106
|
+
}
|
|
107
|
+
if (stringified === undefined)
|
|
108
|
+
return false;
|
|
109
|
+
return valueOpMatches(rule.op, stringified, rule.value, rule.valueKind);
|
|
110
|
+
}
|
|
111
|
+
export function matchQuantityRule(rule, rows) {
|
|
112
|
+
return rows.some((r) => nameMatches(rule.setName, r.setName, rule.setNameKind) &&
|
|
113
|
+
nameMatches(rule.quantityName, r.quantityName, rule.quantityNameKind) &&
|
|
114
|
+
numericOpMatches(rule.op, r.value, rule.value));
|
|
115
|
+
}
|
|
116
|
+
// ── Storey lookup fallback ────────────────────────────────────────────────────
|
|
117
|
+
/**
|
|
118
|
+
* Storey id an element belongs to: its own direct containment
|
|
119
|
+
* (`elementToStorey`, set for an element the storey directly contains plus
|
|
120
|
+
* its `IfcRelAggregates`-aggregated parts) OR — one hop through a
|
|
121
|
+
* containing `IfcSpace`/`IfcSpatialZone` — the storey THAT space belongs
|
|
122
|
+
* to. A space is itself mapped to its storey in `elementToStorey`
|
|
123
|
+
* (`SpatialHierarchyBuilder` walks a storey's own spatial children), so
|
|
124
|
+
* this is a composition of two maps that already exist plus
|
|
125
|
+
* `getContainingSpace` (`@ifc-lite/data`'s `spatialLookups`), not a new
|
|
126
|
+
* traversal. The single home for "which storey" so the per-entity
|
|
127
|
+
* evaluator (`defaultStoreyName`/`storeyMatchesRefs`) and the bulk index
|
|
128
|
+
* prefilter (`unionByStorey`) cannot answer it two different ways.
|
|
129
|
+
*
|
|
130
|
+
* Reaches exactly one level down from the storey: an element inside a
|
|
131
|
+
* space nested inside another space (rather than directly under the
|
|
132
|
+
* storey) is not resolved — see `docs/guide/selector-syntax.md`.
|
|
133
|
+
*/
|
|
134
|
+
function storeyIdOf(hierarchy, expressId) {
|
|
135
|
+
const direct = hierarchy.elementToStorey.get(expressId);
|
|
136
|
+
if (direct !== undefined)
|
|
137
|
+
return direct;
|
|
138
|
+
// `getContainingSpace` is part of the `spatialLookups()` contract every
|
|
139
|
+
// real hierarchy carries, but several tests in this suite build a
|
|
140
|
+
// hand-rolled partial `spatialHierarchy` mock (`byStorey`/`elementToStorey`
|
|
141
|
+
// only) to exercise the ref/name-matching paths in isolation — guard
|
|
142
|
+
// rather than assume every field is present.
|
|
143
|
+
const spaceId = hierarchy.getContainingSpace?.(expressId);
|
|
144
|
+
if (spaceId == null)
|
|
145
|
+
return undefined;
|
|
146
|
+
return hierarchy.elementToStorey.get(spaceId);
|
|
147
|
+
}
|
|
148
|
+
/** Every element `bySpace` lists for a space belonging to `storeyId` — the
|
|
149
|
+
* bulk-prefilter twin of {@link storeyIdOf}'s one-hop space widening.
|
|
150
|
+
* `bySpace` is absent on the same partial test mocks {@link storeyIdOf}
|
|
151
|
+
* guards against. */
|
|
152
|
+
function spaceElementsOfStorey(hierarchy, storeyId) {
|
|
153
|
+
const out = [];
|
|
154
|
+
if (!hierarchy.bySpace)
|
|
155
|
+
return out;
|
|
156
|
+
for (const [spaceId, elements] of hierarchy.bySpace) {
|
|
157
|
+
if (hierarchy.elementToStorey.get(spaceId) !== storeyId)
|
|
158
|
+
continue;
|
|
159
|
+
for (const id of elements)
|
|
160
|
+
out.push(id);
|
|
161
|
+
}
|
|
162
|
+
return out;
|
|
163
|
+
}
|
|
164
|
+
export function defaultStoreyName(store, expressId) {
|
|
165
|
+
const hierarchy = store.spatialHierarchy;
|
|
166
|
+
if (!hierarchy)
|
|
167
|
+
return '';
|
|
168
|
+
const storeyId = storeyIdOf(hierarchy, expressId);
|
|
169
|
+
if (!storeyId)
|
|
170
|
+
return '';
|
|
171
|
+
return store.entities.getName(storeyId);
|
|
172
|
+
}
|
|
173
|
+
/** Does `expressId` (in `modelId`) sit in a storey ref'd by `rule.refs`?
|
|
174
|
+
* `IfcBuildingStorey.Name` isn't unique, so once a `StoreyRule` carries
|
|
175
|
+
* an exact (modelId, expressId) ref (mirrored from a HierarchyPanel
|
|
176
|
+
* click), matching bypasses Name entirely. Reaches through a containing
|
|
177
|
+
* space via {@link storeyIdOf}, same as the Name-matched path. */
|
|
178
|
+
export function storeyMatchesRefs(store, expressId, modelId, rule) {
|
|
179
|
+
const hierarchy = store.spatialHierarchy;
|
|
180
|
+
const storeyId = hierarchy ? storeyIdOf(hierarchy, expressId) : undefined;
|
|
181
|
+
return storeyId != null && !!rule.refs?.some((r) => r.modelId === modelId && r.expressId === storeyId);
|
|
182
|
+
}
|
|
183
|
+
/** Index-prefilter twin of {@link storeyMatchesRefs}/Name matching: the
|
|
184
|
+
* bucket of elements a `storey op:'in'` rule narrows to for `modelId`.
|
|
185
|
+
* Includes each matched storey's directly-contained elements AND every
|
|
186
|
+
* element `bySpace` lists for a space belonging to that storey, so this
|
|
187
|
+
* prefilter can never exclude a candidate the (also widened) per-entity
|
|
188
|
+
* check in `filter-evaluate.ts` would go on to match. */
|
|
189
|
+
export function unionByStorey(store, rule, modelId) {
|
|
190
|
+
const hierarchy = store.spatialHierarchy;
|
|
191
|
+
if (!hierarchy)
|
|
192
|
+
return null;
|
|
193
|
+
const out = [];
|
|
194
|
+
if (rule.refs) {
|
|
195
|
+
for (const ref of rule.refs) {
|
|
196
|
+
if (ref.modelId !== modelId)
|
|
197
|
+
continue;
|
|
198
|
+
const elements = hierarchy.byStorey.get(ref.expressId);
|
|
199
|
+
if (elements)
|
|
200
|
+
for (const id of elements)
|
|
201
|
+
out.push(id);
|
|
202
|
+
for (const id of spaceElementsOfStorey(hierarchy, ref.expressId))
|
|
203
|
+
out.push(id);
|
|
204
|
+
}
|
|
205
|
+
return out.length > 0 ? out : null;
|
|
206
|
+
}
|
|
207
|
+
const wanted = new Set(rule.values.map((n) => n.toLowerCase()));
|
|
208
|
+
for (const storeyId of hierarchy.byStorey.keys()) {
|
|
209
|
+
const name = store.entities.getName(storeyId);
|
|
210
|
+
if (!wanted.has(name.toLowerCase()))
|
|
211
|
+
continue;
|
|
212
|
+
const elements = hierarchy.byStorey.get(storeyId);
|
|
213
|
+
if (elements)
|
|
214
|
+
for (const id of elements)
|
|
215
|
+
out.push(id);
|
|
216
|
+
for (const id of spaceElementsOfStorey(hierarchy, storeyId))
|
|
217
|
+
out.push(id);
|
|
218
|
+
}
|
|
219
|
+
return out.length > 0 ? out : null;
|
|
220
|
+
}
|
|
221
|
+
// ── Material / classification / elevation resolution ─────────────────────────
|
|
222
|
+
/** Collect the *individual* material names an element exposes - each layer /
|
|
223
|
+
* constituent / profile material, or the single plain material - for the
|
|
224
|
+
* multi-valued `material` rule matcher and the material dropdown. Shares the
|
|
225
|
+
* #1366 lens collector, so filtering by material groups by the real materials
|
|
226
|
+
* rather than the layer-set / Revit family+type name that masked them. (#1462) */
|
|
227
|
+
export function materialNamesOf(info) {
|
|
228
|
+
return lensMaterialNames(info);
|
|
229
|
+
}
|
|
230
|
+
/** `materialNamesOf` plus every material Category (`material=` matches
|
|
231
|
+
* Name OR Category, #4094) — categories come from `flattenMaterials`
|
|
232
|
+
* (`@ifc-lite/ids`'s material-facet flattener), reused rather than a
|
|
233
|
+
* second layer/profile/constituent walk; only its `.category` field is
|
|
234
|
+
* used, since its `.name` field also surfaces a layer's own label, which
|
|
235
|
+
* `materialNamesOf` deliberately excludes (#1462). */
|
|
236
|
+
export function materialMatchCandidates(info) {
|
|
237
|
+
const categories = flattenMaterials(info)
|
|
238
|
+
.map((m) => m.category)
|
|
239
|
+
.filter((c) => !!c);
|
|
240
|
+
return [...new Set([...materialNamesOf(info), ...categories])];
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Match a classification rule against an element's classification refs.
|
|
244
|
+
* `system` (when set) scopes to one system; value ops match a ref's code
|
|
245
|
+
* (identification) OR name. Pushes both through even when `undefined`
|
|
246
|
+
* (absent) rather than filtering on truthy — `matchStringAnyNone` decides
|
|
247
|
+
* absent-vs-empty explicitly (#4930).
|
|
248
|
+
*/
|
|
249
|
+
export function matchClassificationRule(rule, refs) {
|
|
250
|
+
const sys = rule.system?.trim().toLowerCase();
|
|
251
|
+
const scoped = sys
|
|
252
|
+
? refs.filter((r) => (r.system ?? '').toLowerCase() === sys)
|
|
253
|
+
: refs;
|
|
254
|
+
if (rule.op === 'isSet')
|
|
255
|
+
return scoped.length > 0;
|
|
256
|
+
if (rule.op === 'isNotSet')
|
|
257
|
+
return scoped.length === 0;
|
|
258
|
+
// Value ops — match against identification (code) and name of each ref.
|
|
259
|
+
// `unresolved` (server-parsed, no source bytes — #3948) means UNKNOWN, not
|
|
260
|
+
// ABSENT: excluded from candidates entirely, same as before #4930, so it
|
|
261
|
+
// can't flip a negative op false->true. Only a RESOLVED ref's genuinely
|
|
262
|
+
// `$` identification/name contributes a real `undefined` candidate.
|
|
263
|
+
const candidates = [];
|
|
264
|
+
for (const r of scoped) {
|
|
265
|
+
if (r.unresolved)
|
|
266
|
+
continue;
|
|
267
|
+
candidates.push(r.identification);
|
|
268
|
+
candidates.push(r.name);
|
|
269
|
+
}
|
|
270
|
+
// rule.op is now eq | ne | contains | notContains — a StringOp subset.
|
|
271
|
+
return matchStringAnyNone(rule.op, candidates, rule.value, rule.valueKind);
|
|
272
|
+
}
|
|
273
|
+
/** Element elevation in metres, derived from its building storey's
|
|
274
|
+
* elevation. Returns null when the element isn't placed in the spatial
|
|
275
|
+
* hierarchy (so an elevation rule simply doesn't match it). */
|
|
276
|
+
export function elevationOf(store, expressId) {
|
|
277
|
+
const hierarchy = store.spatialHierarchy;
|
|
278
|
+
if (!hierarchy)
|
|
279
|
+
return null;
|
|
280
|
+
const storeyId = hierarchy.elementToStorey.get(expressId);
|
|
281
|
+
if (!storeyId)
|
|
282
|
+
return null;
|
|
283
|
+
const elev = hierarchy.storeyElevations.get(storeyId);
|
|
284
|
+
return typeof elev === 'number' ? elev : null;
|
|
285
|
+
}
|
|
286
|
+
// ── Parent (spatial ancestor) resolution ──────────────────────────────────────
|
|
287
|
+
/**
|
|
288
|
+
* `parent=Foo` (#4903) — does ANY ancestor of `expressId`, walking upward
|
|
289
|
+
* through spatial containment and aggregation to any depth via
|
|
290
|
+
* `collectSpatialAncestors` (`@ifc-lite/data`, the single shared resolver),
|
|
291
|
+
* have a Name the rule's op matches? Reuses `matchStringAnyNone`, the same
|
|
292
|
+
* multi-valued convention `material`/`classification` rules use: an element
|
|
293
|
+
* with NO ancestors — or none whose Name matches — satisfies neither a
|
|
294
|
+
* positive nor a negative op (#4659). `getNameOrUndefined`, not `getName`,
|
|
295
|
+
* so an ancestor with no Name at all is a real absent candidate (#4930).
|
|
296
|
+
*/
|
|
297
|
+
export function matchParentRule(rule, store, expressId) {
|
|
298
|
+
const ancestorNames = collectSpatialAncestors(store.relationships, expressId)
|
|
299
|
+
.map((id) => store.entities.getNameOrUndefined(id));
|
|
300
|
+
return matchStringAnyNone(rule.op, ancestorNames, rule.value, rule.valueKind);
|
|
301
|
+
}
|
|
302
|
+
//# sourceMappingURL=filter-match.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filter-match.js","sourceRoot":"","sources":["../../src/filter/filter-match.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { NumericOp, SetOp, StringOp, TextKind, ValueOp } from './filter-rules.js';
|
|
2
|
+
/**
|
|
3
|
+
* Trailing options accepted by every op-matching function below (#5138 PR 3).
|
|
4
|
+
* Omitted entirely = today's behaviour EXACTLY: search, saved filters and
|
|
5
|
+
* clash presets never pass this, so they fold case unconditionally (like
|
|
6
|
+
* `fold()` always did before this flag existed) and compare numbers with the
|
|
7
|
+
* historical absolute `1e-9` epsilon. Only the information-validation engine
|
|
8
|
+
* passes it, with `caseSensitive: rule.caseSensitive ?? true` (IDS parity,
|
|
9
|
+
* bSI #346) and `tolerance: rule.tolerance ?? 1e-6` (relative, bSI #418).
|
|
10
|
+
*/
|
|
11
|
+
export interface OpMatchOptions {
|
|
12
|
+
caseSensitive?: boolean;
|
|
13
|
+
tolerance?: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Regex matching for the `matches` / `notMatches` ops, shared by every rule
|
|
17
|
+
* kind that owns them.
|
|
18
|
+
*
|
|
19
|
+
* `kind` is the producer speaking (see {@link TextKind}). A declared operand is
|
|
20
|
+
* a regular-expression SOURCE and the WHOLE string is the pattern, slashes
|
|
21
|
+
* included — that is what makes the selector `Name=/\/tmp\//` look for `/tmp/`
|
|
22
|
+
* rather than `tmp`. An operand with no declared kind is free text under the
|
|
23
|
+
* Lists-panel convention (#1591): a `/body/flags` literal is a pattern, flags
|
|
24
|
+
* and all, and anything else is a bare source. That is the spelling the chip
|
|
25
|
+
* editors ask for, and there the slashes ARE the user's only way to say
|
|
26
|
+
* "pattern", so reading them is the grammar rather than a guess.
|
|
27
|
+
*
|
|
28
|
+
* Case-SENSITIVE, unlike every other op here: the selector grammar's `/…/` is
|
|
29
|
+
* a Python regular expression, and those do not fold case. Write JavaScript's
|
|
30
|
+
* equivalent of `(?i)` by adding an `i` flag to a full literal (`/wand/i`).
|
|
31
|
+
*
|
|
32
|
+
* An empty operand never matches; a MALFORMED literal (bad regex syntax)
|
|
33
|
+
* never matches either (`compileNameMatcher` logs it and falls back to an
|
|
34
|
+
* exact compare against the literal text, which no property value equals).
|
|
35
|
+
*
|
|
36
|
+
* An UNSAFE pattern — syntactically valid but rejected by
|
|
37
|
+
* `@ifc-lite/regex-guard` as catastrophic-backtracking-shaped or over the
|
|
38
|
+
* length cap — is different: `compileNameMatcher` THROWS a plain `Error`
|
|
39
|
+
* naming the pattern and reason, once, on the first row that reaches it.
|
|
40
|
+
* This function does not catch that throw; it propagates out of
|
|
41
|
+
* `stringOpMatches` / `matchStringAnyNone` / `valueOpMatches` and out of
|
|
42
|
+
* `filter-evaluate.ts`'s per-entity loop. Every current caller of the
|
|
43
|
+
* evaluator (`SearchModal.filter.tsx`'s `runFilter`, `resolveClashSetFilter`,
|
|
44
|
+
* `query-adapter.ts`'s `entitiesMatchingActiveFilter`) already wraps its call
|
|
45
|
+
* in a try/catch that turns this into a visible, recoverable error rather
|
|
46
|
+
* than an unhandled exception — see those call sites for how each one
|
|
47
|
+
* surfaces it. A new caller of `evaluateFilterRules` /
|
|
48
|
+
* `evaluateFilterRulesFederated` MUST do the same.
|
|
49
|
+
*/
|
|
50
|
+
export declare function regexOpMatches(candidate: string, value: string, kind: TextKind | undefined): boolean;
|
|
51
|
+
export declare function setOpMatches(op: SetOp, candidate: string, values: readonly string[], opts?: OpMatchOptions): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* `globalId` rule matching — exact, case-SENSITIVE, unlike every other
|
|
54
|
+
* `SetOp` dimension in this module. A GlobalId is a 22-character base64
|
|
55
|
+
* string (IFC's compressed GUID encoding), where upper/lower case is part of
|
|
56
|
+
* the identity: folding case would let two DIFFERENT elements' GlobalIds
|
|
57
|
+
* collide on an `in` match.
|
|
58
|
+
*/
|
|
59
|
+
export declare function globalIdOpMatches(op: SetOp, candidate: string, values: readonly string[]): boolean;
|
|
60
|
+
/**
|
|
61
|
+
* `candidate: string | undefined` — `undefined` means ABSENT (#4930), a real
|
|
62
|
+
* input, decided explicitly here: a positive op never matches it, its
|
|
63
|
+
* negation always does.
|
|
64
|
+
*/
|
|
65
|
+
export declare function stringOpMatches(op: StringOp, candidate: string | undefined, value: string, valueKind?: TextKind, opts?: OpMatchOptions): boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Match a StringOp against a *set* of candidates (materials, ancestors,
|
|
68
|
+
* classification refs). Positive ops match if ANY satisfies them; negative
|
|
69
|
+
* ops match only if NONE violates them; a `string | undefined` candidate is
|
|
70
|
+
* handled per-entry by `stringOpMatches` (#4930). An empty LIST stays
|
|
71
|
+
* distinct: "no candidates at all" never matches, negative ops included.
|
|
72
|
+
*/
|
|
73
|
+
export declare function matchStringAnyNone(op: StringOp, candidates: readonly (string | undefined)[], value: string, valueKind?: TextKind, opts?: OpMatchOptions): boolean;
|
|
74
|
+
/**
|
|
75
|
+
* `opts.tolerance` (relative, bSI #418 — e.g. `1e-6`) replaces the absolute
|
|
76
|
+
* `1e-9` epsilon below for `eq`/`ne` and widens `gte`/`lte` to accept a value
|
|
77
|
+
* within tolerance of the boundary; `gt`/`lt` stay strict (a tolerant strict
|
|
78
|
+
* inequality would let a value equal to the boundary pass both `gt` and its
|
|
79
|
+
* own negation's neighbour). Omitted `opts`/`tolerance` = today's absolute-
|
|
80
|
+
* epsilon behaviour, unchanged.
|
|
81
|
+
*/
|
|
82
|
+
export declare function numericOpMatches(op: NumericOp, candidate: number, value: number, opts?: OpMatchOptions): boolean;
|
|
83
|
+
/**
|
|
84
|
+
* Evaluate a Property ValueOp against the candidate's raw stringified
|
|
85
|
+
* value. `isSet`/`isNotSet` are presence checks and the property layer
|
|
86
|
+
* (filter-evaluate.ts) decides them before calling here — but we still
|
|
87
|
+
* accept them so the function is total.
|
|
88
|
+
*/
|
|
89
|
+
export declare function valueOpMatches(op: ValueOp, psetVal: string, ruleVal: string, valueKind?: TextKind, opts?: OpMatchOptions): boolean;
|
|
90
|
+
//# sourceMappingURL=filter-ops.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filter-ops.d.ts","sourceRoot":"","sources":["../../src/filter/filter-ops.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEvF;;;;;;;;GAQG;AACH,MAAM,WAAW,cAAc;IAC7B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAgBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,SAAS,GAAG,OAAO,CAIpG;AAED,wBAAgB,YAAY,CAC1B,EAAE,EAAE,KAAK,EACT,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,SAAS,MAAM,EAAE,EACzB,IAAI,CAAC,EAAE,cAAc,GACpB,OAAO,CAIT;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAGlG;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,EAAE,EAAE,QAAQ,EACZ,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,KAAK,EAAE,MAAM,EACb,SAAS,CAAC,EAAE,QAAQ,EACpB,IAAI,CAAC,EAAE,cAAc,GACpB,OAAO,CAyBT;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,EAAE,EAAE,QAAQ,EACZ,UAAU,EAAE,SAAS,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,EAC3C,KAAK,EAAE,MAAM,EACb,SAAS,CAAC,EAAE,QAAQ,EACpB,IAAI,CAAC,EAAE,cAAc,GACpB,OAAO,CAeT;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,OAAO,CAyBhH;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,EAAE,EAAE,OAAO,EACX,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,SAAS,CAAC,EAAE,QAAQ,EACpB,IAAI,CAAC,EAAE,cAAc,GACpB,OAAO,CAoBT"}
|
|
@@ -0,0 +1,202 @@
|
|
|
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
|
+
* Pure operator matching for the filter rule taxonomy (ported verbatim from
|
|
6
|
+
* the Tauri-side `filter.rs` engine).
|
|
7
|
+
*
|
|
8
|
+
* Split out of `filter-rules.ts` at the op seam so both stay under the module
|
|
9
|
+
* size cap: that file owns the rule SHAPES, their constructors and the JSON
|
|
10
|
+
* guards, this one owns what an operator MEANS. Nothing here reads a rule —
|
|
11
|
+
* each function takes an operator, a candidate and the rule's operand, which
|
|
12
|
+
* is what makes them testable without a model.
|
|
13
|
+
*/
|
|
14
|
+
import { compileNameMatcher, isNamePattern } from '@ifc-lite/lists';
|
|
15
|
+
/**
|
|
16
|
+
* Fold a candidate that may be undefined at runtime — crash-safety only
|
|
17
|
+
* (#1195) — to lower-case UNLESS `caseSensitive` is `true`. Does NOT decide
|
|
18
|
+
* what "no value" means to an operator: `undefined` (absent) vs `''`
|
|
19
|
+
* (explicitly empty) is a real distinction a caller must resolve before
|
|
20
|
+
* calling this — see `stringOpMatches` (#4930). `caseSensitive` omitted (the
|
|
21
|
+
* only path before #5138 PR 3) always folds, so this is byte-for-byte the
|
|
22
|
+
* historical `lower()` for every existing caller.
|
|
23
|
+
*/
|
|
24
|
+
function fold(s, caseSensitive) {
|
|
25
|
+
const v = s ?? '';
|
|
26
|
+
return caseSensitive ? v : v.toLowerCase();
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Regex matching for the `matches` / `notMatches` ops, shared by every rule
|
|
30
|
+
* kind that owns them.
|
|
31
|
+
*
|
|
32
|
+
* `kind` is the producer speaking (see {@link TextKind}). A declared operand is
|
|
33
|
+
* a regular-expression SOURCE and the WHOLE string is the pattern, slashes
|
|
34
|
+
* included — that is what makes the selector `Name=/\/tmp\//` look for `/tmp/`
|
|
35
|
+
* rather than `tmp`. An operand with no declared kind is free text under the
|
|
36
|
+
* Lists-panel convention (#1591): a `/body/flags` literal is a pattern, flags
|
|
37
|
+
* and all, and anything else is a bare source. That is the spelling the chip
|
|
38
|
+
* editors ask for, and there the slashes ARE the user's only way to say
|
|
39
|
+
* "pattern", so reading them is the grammar rather than a guess.
|
|
40
|
+
*
|
|
41
|
+
* Case-SENSITIVE, unlike every other op here: the selector grammar's `/…/` is
|
|
42
|
+
* a Python regular expression, and those do not fold case. Write JavaScript's
|
|
43
|
+
* equivalent of `(?i)` by adding an `i` flag to a full literal (`/wand/i`).
|
|
44
|
+
*
|
|
45
|
+
* An empty operand never matches; a MALFORMED literal (bad regex syntax)
|
|
46
|
+
* never matches either (`compileNameMatcher` logs it and falls back to an
|
|
47
|
+
* exact compare against the literal text, which no property value equals).
|
|
48
|
+
*
|
|
49
|
+
* An UNSAFE pattern — syntactically valid but rejected by
|
|
50
|
+
* `@ifc-lite/regex-guard` as catastrophic-backtracking-shaped or over the
|
|
51
|
+
* length cap — is different: `compileNameMatcher` THROWS a plain `Error`
|
|
52
|
+
* naming the pattern and reason, once, on the first row that reaches it.
|
|
53
|
+
* This function does not catch that throw; it propagates out of
|
|
54
|
+
* `stringOpMatches` / `matchStringAnyNone` / `valueOpMatches` and out of
|
|
55
|
+
* `filter-evaluate.ts`'s per-entity loop. Every current caller of the
|
|
56
|
+
* evaluator (`SearchModal.filter.tsx`'s `runFilter`, `resolveClashSetFilter`,
|
|
57
|
+
* `query-adapter.ts`'s `entitiesMatchingActiveFilter`) already wraps its call
|
|
58
|
+
* in a try/catch that turns this into a visible, recoverable error rather
|
|
59
|
+
* than an unhandled exception — see those call sites for how each one
|
|
60
|
+
* surfaces it. A new caller of `evaluateFilterRules` /
|
|
61
|
+
* `evaluateFilterRulesFederated` MUST do the same.
|
|
62
|
+
*/
|
|
63
|
+
export function regexOpMatches(candidate, value, kind) {
|
|
64
|
+
if (value.length === 0)
|
|
65
|
+
return false;
|
|
66
|
+
const literal = kind === undefined && isNamePattern(value) ? value : `/${value}/`;
|
|
67
|
+
return compileNameMatcher(literal)(candidate ?? '');
|
|
68
|
+
}
|
|
69
|
+
export function setOpMatches(op, candidate, values, opts) {
|
|
70
|
+
const c = fold(candidate, opts?.caseSensitive);
|
|
71
|
+
const hit = values.some((v) => fold(v, opts?.caseSensitive) === c);
|
|
72
|
+
return op === 'in' ? hit : !hit;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* `globalId` rule matching — exact, case-SENSITIVE, unlike every other
|
|
76
|
+
* `SetOp` dimension in this module. A GlobalId is a 22-character base64
|
|
77
|
+
* string (IFC's compressed GUID encoding), where upper/lower case is part of
|
|
78
|
+
* the identity: folding case would let two DIFFERENT elements' GlobalIds
|
|
79
|
+
* collide on an `in` match.
|
|
80
|
+
*/
|
|
81
|
+
export function globalIdOpMatches(op, candidate, values) {
|
|
82
|
+
const hit = values.includes(candidate);
|
|
83
|
+
return op === 'in' ? hit : !hit;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* `candidate: string | undefined` — `undefined` means ABSENT (#4930), a real
|
|
87
|
+
* input, decided explicitly here: a positive op never matches it, its
|
|
88
|
+
* negation always does.
|
|
89
|
+
*/
|
|
90
|
+
export function stringOpMatches(op, candidate, value, valueKind, opts) {
|
|
91
|
+
if (candidate === undefined) {
|
|
92
|
+
switch (op) {
|
|
93
|
+
case 'eq':
|
|
94
|
+
case 'contains':
|
|
95
|
+
case 'startsWith':
|
|
96
|
+
case 'matches':
|
|
97
|
+
return false;
|
|
98
|
+
case 'ne':
|
|
99
|
+
case 'notContains':
|
|
100
|
+
case 'notMatches':
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const a = fold(candidate, opts?.caseSensitive);
|
|
105
|
+
const b = fold(value, opts?.caseSensitive);
|
|
106
|
+
switch (op) {
|
|
107
|
+
case 'eq': return a === b;
|
|
108
|
+
case 'ne': return a !== b;
|
|
109
|
+
case 'contains': return a.includes(b);
|
|
110
|
+
case 'notContains': return !a.includes(b);
|
|
111
|
+
case 'startsWith': return a.startsWith(b);
|
|
112
|
+
case 'matches': return regexOpMatches(candidate, value, valueKind);
|
|
113
|
+
case 'notMatches': return !regexOpMatches(candidate, value, valueKind);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Match a StringOp against a *set* of candidates (materials, ancestors,
|
|
118
|
+
* classification refs). Positive ops match if ANY satisfies them; negative
|
|
119
|
+
* ops match only if NONE violates them; a `string | undefined` candidate is
|
|
120
|
+
* handled per-entry by `stringOpMatches` (#4930). An empty LIST stays
|
|
121
|
+
* distinct: "no candidates at all" never matches, negative ops included.
|
|
122
|
+
*/
|
|
123
|
+
export function matchStringAnyNone(op, candidates, value, valueKind, opts) {
|
|
124
|
+
if (candidates.length === 0)
|
|
125
|
+
return false;
|
|
126
|
+
switch (op) {
|
|
127
|
+
case 'eq':
|
|
128
|
+
case 'contains':
|
|
129
|
+
case 'startsWith':
|
|
130
|
+
case 'matches':
|
|
131
|
+
return candidates.some((c) => stringOpMatches(op, c, value, valueKind, opts));
|
|
132
|
+
case 'ne':
|
|
133
|
+
return candidates.every((c) => stringOpMatches('eq', c, value, undefined, opts) === false);
|
|
134
|
+
case 'notContains':
|
|
135
|
+
return candidates.every((c) => stringOpMatches('contains', c, value, undefined, opts) === false);
|
|
136
|
+
case 'notMatches':
|
|
137
|
+
return candidates.every((c) => stringOpMatches('matches', c, value, valueKind, opts) === false);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* `opts.tolerance` (relative, bSI #418 — e.g. `1e-6`) replaces the absolute
|
|
142
|
+
* `1e-9` epsilon below for `eq`/`ne` and widens `gte`/`lte` to accept a value
|
|
143
|
+
* within tolerance of the boundary; `gt`/`lt` stay strict (a tolerant strict
|
|
144
|
+
* inequality would let a value equal to the boundary pass both `gt` and its
|
|
145
|
+
* own negation's neighbour). Omitted `opts`/`tolerance` = today's absolute-
|
|
146
|
+
* epsilon behaviour, unchanged.
|
|
147
|
+
*/
|
|
148
|
+
export function numericOpMatches(op, candidate, value, opts) {
|
|
149
|
+
const tol = opts?.tolerance;
|
|
150
|
+
if (tol !== undefined) {
|
|
151
|
+
const eps = tol * Math.max(Math.abs(candidate), Math.abs(value), 1);
|
|
152
|
+
switch (op) {
|
|
153
|
+
case 'eq': return Math.abs(candidate - value) <= eps;
|
|
154
|
+
case 'ne': return Math.abs(candidate - value) > eps;
|
|
155
|
+
case 'gt': return candidate > value;
|
|
156
|
+
case 'gte': return candidate >= value - eps;
|
|
157
|
+
case 'lt': return candidate < value;
|
|
158
|
+
case 'lte': return candidate <= value + eps;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// The Rust side uses 1e-9 as the epsilon for eq/ne. Match it here for
|
|
162
|
+
// IDS-style parity — IFC quantities are stored as IFC4 IfcReal so the
|
|
163
|
+
// tolerance is large enough to absorb f32→f64 rounding from the parser.
|
|
164
|
+
const EPS = 1e-9;
|
|
165
|
+
switch (op) {
|
|
166
|
+
case 'eq': return Math.abs(candidate - value) < EPS;
|
|
167
|
+
case 'ne': return Math.abs(candidate - value) >= EPS;
|
|
168
|
+
case 'gt': return candidate > value;
|
|
169
|
+
case 'gte': return candidate >= value;
|
|
170
|
+
case 'lt': return candidate < value;
|
|
171
|
+
case 'lte': return candidate <= value;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Evaluate a Property ValueOp against the candidate's raw stringified
|
|
176
|
+
* value. `isSet`/`isNotSet` are presence checks and the property layer
|
|
177
|
+
* (filter-evaluate.ts) decides them before calling here — but we still
|
|
178
|
+
* accept them so the function is total.
|
|
179
|
+
*/
|
|
180
|
+
export function valueOpMatches(op, psetVal, ruleVal, valueKind, opts) {
|
|
181
|
+
switch (op) {
|
|
182
|
+
case 'isSet': return (psetVal ?? '').length > 0;
|
|
183
|
+
case 'isNotSet': return (psetVal ?? '').length === 0;
|
|
184
|
+
case 'eq': return fold(psetVal, opts?.caseSensitive) === fold(ruleVal, opts?.caseSensitive);
|
|
185
|
+
case 'ne': return fold(psetVal, opts?.caseSensitive) !== fold(ruleVal, opts?.caseSensitive);
|
|
186
|
+
case 'contains': return fold(psetVal, opts?.caseSensitive).includes(fold(ruleVal, opts?.caseSensitive));
|
|
187
|
+
case 'notContains': return !fold(psetVal, opts?.caseSensitive).includes(fold(ruleVal, opts?.caseSensitive));
|
|
188
|
+
case 'matches': return regexOpMatches(psetVal, ruleVal, valueKind);
|
|
189
|
+
case 'notMatches': return !regexOpMatches(psetVal, ruleVal, valueKind);
|
|
190
|
+
case 'gt':
|
|
191
|
+
case 'gte':
|
|
192
|
+
case 'lt':
|
|
193
|
+
case 'lte': {
|
|
194
|
+
const cv = Number.parseFloat(psetVal);
|
|
195
|
+
const rv = Number.parseFloat(ruleVal);
|
|
196
|
+
if (!Number.isFinite(cv) || !Number.isFinite(rv))
|
|
197
|
+
return false;
|
|
198
|
+
return numericOpMatches(op, cv, rv, opts);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
//# sourceMappingURL=filter-ops.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filter-ops.js","sourceRoot":"","sources":["../../src/filter/filter-ops.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filter-rule-guards.d.ts","sourceRoot":"","sources":["../../src/filter/filter-rule-guards.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAoB,UAAU,EAAY,MAAM,mBAAmB,CAAC;AAUhF,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,UAAU,CA6ChE;AASD,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,GAAG,UAAU,EAAE,CAG3D"}
|