pub fn parse_with(
input: &[u8],
options: ParseOptions,
) -> Result<ParseOutcome, StepError>Expand description
Parses a physical file under explicit ParseOptions.
With OnMalformed::Skip an unparsable data record is reported as a
Diagnostic and the parser resynchronizes on the next record or section
end, so a consumer can load a damaged file and still show exactly what was
dropped. Header structure stays strict under every policy: mandatory header
records are file-level invariants, not recoverable payload.
ยงErrors
Returns StepError for the wrong physical-file marker, for any header or
section-structure defect, and for data defects when the policy is
OnMalformed::Abort.
Examples found in repository?
examples/recovery_probe.rs (line 12)
3fn main() {
4 let path = std::env::args().nth(1).expect("path");
5 let bytes = std::fs::read(&path).expect("read");
6
7 match openbim_step::parse(&bytes) {
8 Ok(_) => println!("strict: OK (unexpected)"),
9 Err(error) => println!("strict: {error}"),
10 }
11
12 let outcome = openbim_step::parse_with(&bytes, openbim_step::ParseOptions::lenient())
13 .expect("lenient parse");
14 println!(
15 "lenient: {} records, {} diagnostics",
16 outcome.exchange.data.records.len(),
17 outcome.diagnostics.len()
18 );
19 for diagnostic in &outcome.diagnostics {
20 println!(" {diagnostic}");
21 let span = diagnostic.span();
22 println!(
23 " dropped bytes: {:?}",
24 String::from_utf8_lossy(&bytes[span.start..span.end])
25 );
26 }
27 let boundaries = outcome
28 .exchange
29 .data
30 .records
31 .iter()
32 .filter(|record| {
33 record
34 .as_simple()
35 .is_some_and(|simple| simple.name == "IFCRELSPACEBOUNDARY")
36 })
37 .count();
38 println!("IfcRelSpaceBoundary: {boundaries}");
39 println!("schema: {:?}", outcome.exchange.header.standard().schema);
40}More examples
examples/recovery_property.rs (lines 46-49)
18fn main() {
19 let base = "ISO-10303-21;\nHEADER;\nFILE_DESCRIPTION((''),'2;1');\n\
20 FILE_NAME('n','t',(''),(''),'p','o','a');\nFILE_SCHEMA(('IFC4'));\nENDSEC;\nDATA;\n\
21 #1= IFCPERSON($,$,'a;b',$,$,$,$,$);\n\
22 #2= IFCPIXELTEXTURE(1,1,8,(\"00AA;BB\"));\n\
23 #3= IFCORGANIZATION($,'o''x',$,$,$); /* c;c */\n\
24 #4= IFCDOOR('real');\nENDSEC;\nEND-ISO-10303-21;\n";
25
26 let mut state = 0x5eed_1234_u64;
27 let mut recovered = 0usize;
28 let mut fabricated = 0usize;
29 let mut span_faults = 0usize;
30
31 for _ in 0..20_000 {
32 let mut bytes = base.as_bytes().to_vec();
33 // Corrupt 1-3 bytes anywhere in the DATA region.
34 let edits = 1 + (lcg(&mut state) % 3) as usize;
35 for _ in 0..edits {
36 let at = 150 + (lcg(&mut state) as usize % (bytes.len() - 150));
37 match lcg(&mut state) % 3 {
38 0 => bytes[at] = b'@',
39 1 => {
40 bytes.remove(at);
41 }
42 _ => bytes[at] = (lcg(&mut state) % 128) as u8,
43 }
44 }
45
46 let Ok(outcome) = parse_with(
47 &bytes,
48 ParseOptions::default().on_malformed_record(OnMalformed::Skip),
49 ) else {
50 continue;
51 };
52 recovered += 1;
53
54 for diagnostic in &outcome.diagnostics {
55 let span = diagnostic.span();
56 if span.start > span.end || span.end > bytes.len() {
57 span_faults += 1;
58 }
59 }
60
61 // Every kept record must appear verbatim in the surviving source: its
62 // `#id=` must exist outside any diagnostic-covered range.
63 for record in &outcome.exchange.data.records {
64 let needle = format!("#{}=", record.id.as_str());
65 let found = String::from_utf8_lossy(&bytes).contains(&needle)
66 || String::from_utf8_lossy(&bytes).contains(&format!("#{} =", record.id.as_str()));
67 if !found {
68 fabricated += 1;
69 eprintln!(
70 "FABRICATED {} in:\n{}",
71 needle,
72 String::from_utf8_lossy(&bytes)
73 );
74 }
75 }
76 }
77
78 println!("recovered={recovered} fabricated={fabricated} span_faults={span_faults}");
79 assert_eq!(fabricated, 0, "recovery invented records");
80 assert_eq!(span_faults, 0, "diagnostic span out of bounds");
81 println!("PROPERTY PROBE OK");
82}