pub struct EntityScanner<'a> { /* private fields */ }Expand description
Fast entity scanner over raw IFC bytes without full parsing. O(n) performance for finding entities by type Uses memchr for SIMD-accelerated byte searching
Implementations§
Source§impl<'a> EntityScanner<'a>
impl<'a> EntityScanner<'a>
Sourcepub fn skipped_oversized_ids(&self) -> usize
pub fn skipped_oversized_ids(&self) -> usize
How many records this scanner has skipped because their instance name
does not fit u32 (issue #3395).
ISO 10303-21 puts no upper bound on #<digits>, but every express-id
column in this workspace is u32 (ColumnarIndex::ids,
MeshData::express_id, the wasm express_ids buffers), so a wider id
cannot be represented — it used to wrap, making #4294967297
indistinguishable from #1. The record is dropped instead, and this
counter is the other half of that guard: callers report it rather than
letting the model come back quietly short.
Sourcepub fn skipped_oversized_id_starts(&self) -> &[usize]
pub fn skipped_oversized_id_starts(&self) -> &[usize]
The line_start byte offset of every record this scanner refused,
strictly increasing.
A whole-file scan only needs the count above. A SHARDED scan needs the
offsets, and the difference is not cosmetic: shard i > 0 starts at an
arbitrary byte, so it can begin inside a quoted value and parse a
string literal such as '…#4294967297=IFCWALL(…' as a record — and
refuse it. That refusal is an artefact of where the shard started, not
a record the file declares, so a count alone would let a file with
NOTHING oversized in it be reported as incomplete. The offset lets the
stitch keep only the refusals inside the byte region it actually
retained from that shard (issue #3395/#3430).
Sourcepub fn malformed_record_start(&self) -> Option<usize>
pub fn malformed_record_start(&self) -> Option<usize>
The first record this scan dropped for having no terminator of its own,
or None. See Self::malformed_record_starts.
Sourcepub fn malformed_record_starts(&self) -> &[usize]
pub fn malformed_record_starts(&self) -> &[usize]
The line_start of EVERY record this scan dropped for having no
terminator of its own, strictly increasing.
A REPORT, not a stop signal: a record merely missing its ; is dropped
and the scan CONTINUES past the ) closing its parameter list (#4179).
Ask position, not this, whether a scan finished.
A Vec for the reason
skipped_oversized_id_starts is
one, which recovery made load-bearing: a SHARDED scan can now hit a
speculative drop inside a quoted value AND a real one after it, and
keeping only the first made the stitch’s “inside my retained region”
filter test the speculative offset and discard the real report with it.
Source§impl<'a> EntityScanner<'a>
impl<'a> EntityScanner<'a>
Sourcepub fn new<T>(content: &'a T) -> Self
pub fn new<T>(content: &'a T) -> Self
Create a new scanner.
Positions past the STEP HEADER section when one is present so that a
stray # inside a header string (e.g. a CATIA FILE_NAME like
'…\X0\2#.ifc') can’t be mistaken for an entity start and corrupt
quote-parity for the rest of the file (issue #654).
Sourcepub fn new_at<T>(content: &'a T, position: usize) -> Self
pub fn new_at<T>(content: &'a T, position: usize) -> Self
Create a scanner positioned at a specific byte offset.
Used by the sharded-scan pre-pass: each shard scans the full file
(so byte offsets returned are GLOBAL, not relative to the shard’s
range) but starts walking at its assigned start offset. Callers are
expected to rewind position to a known entity boundary (typically
the byte after a ;\n terminator) before calling next_entity.
Does NOT auto-skip the HEADER section — that’s the caller’s responsibility, since shards expect the exact offset they were given.
Sourcepub fn position(&self) -> usize
pub fn position(&self) -> usize
Current byte offset of the scanner (start of the next entity to scan).
Sourcepub fn next_entity(&mut self) -> Option<(u32, &'a str, usize, usize)>
pub fn next_entity(&mut self) -> Option<(u32, &'a str, usize, usize)>
Next (entity_id, keyword, line_start, line_end). The keyword is the RAW slice as the file wrote it
(IfcWall stays IfcWall); STEP keyword case is not significant, so compare it with super::keyword_eq, never ==.
Sourcepub fn find_by_type(&mut self, target_type: &str) -> Vec<(u32, usize, usize)>
pub fn find_by_type(&mut self, target_type: &str) -> Vec<(u32, usize, usize)>
Find all entities of a specific type
Sourcepub fn count_by_type(&mut self) -> FxHashMap<String, usize>
pub fn count_by_type(&mut self) -> FxHashMap<String, usize>
Count entities by type
Sourcepub fn count(&mut self) -> usize
pub fn count(&mut self) -> usize
Count the entities remaining from the scanner’s current position, without allocating anything per entity.
Unlike count_by_type (which builds a per-keyword
map) or build_entity_index (which retains a
span per entity, ~20 B each), this walks the byte stream and increments a
single counter: O(scan) time, O(1) memory. It is the cheap primitive
for a downstream entity-count DoS guard on a file too large to index
(issue #1517). Advances the scanner to the end of the data section.