step_io/header.rs
1//! [`FileHeader`] — the Part 21 HEADER section, shared by reading and writing.
2//!
3//! Every STEP file opens with three HEADER records. `FILE_DESCRIPTION` says
4//! what the file contains; `FILE_NAME` records where it came from — file
5//! name, timestamp, authors, organizations, the STEP processor, and the
6//! originating CAD system; `FILE_SCHEMA` names the schema. [`FileHeader`]
7//! models all three and lives on the [`StepModel`]: reading surfaces the
8//! source file's header as [`StepModel::header`], and authoring fills it
9//! through [`StepBuilder::header`](crate::StepBuilder::header) for the
10//! written file.
11
12use crate::generated::model::StepModel;
13use crate::parser::{Attribute, RawEntity, SchemaId};
14
15/// The Part 21 HEADER section (`FILE_DESCRIPTION` + `FILE_NAME` +
16/// `FILE_SCHEMA`). Reading decodes it from the source file; the builder
17/// fills it from user input plus its automatic timestamp and preprocessor
18/// stamp. The default is the customary all-empty header.
19#[derive(Debug, Clone, Default)]
20pub struct FileHeader {
21 /// `FILE_DESCRIPTION.description`; multiple entries read as one joined
22 /// string.
23 pub description: String,
24 /// `FILE_NAME.name`.
25 pub file_name: String,
26 /// `FILE_NAME.time_stamp`.
27 pub time_stamp: String,
28 /// `FILE_NAME.author`; empty renders as the customary `('')`.
29 pub authors: Vec<String>,
30 /// `FILE_NAME.organization`; empty renders as the customary `('')`.
31 pub organizations: Vec<String>,
32 /// `FILE_NAME.preprocessor_version` — the STEP processor or library
33 /// that produced the file.
34 pub preprocessor_version: String,
35 /// `FILE_NAME.originating_system` — the CAD application the file came
36 /// from.
37 pub originating_system: String,
38 /// `FILE_NAME.authorisation`.
39 pub authorisation: String,
40 /// `FILE_SCHEMA` — the identified schema (AP family, edition, stage)
41 /// plus the raw strings. The source schema on a read model; the AP242
42 /// identity on an authored one.
43 pub schema: SchemaId,
44}
45
46impl StepModel {
47 /// The file's HEADER section. On a model returned by
48 /// [`read`](crate::read) this is the source file's header, the
49 /// identified [`schema`](FileHeader::schema) included.
50 #[must_use]
51 pub fn header(&self) -> &FileHeader {
52 &self.header
53 }
54}
55
56/// Decode the raw HEADER entities into a [`FileHeader`]. Positional per Part
57/// 21: `FILE_NAME(name, time_stamp, author, organization,
58/// preprocessor_version, originating_system, authorisation)` and
59/// `FILE_DESCRIPTION(description, implementation_level)`. Lenient on
60/// non-standard input — a missing or off-kind field reads as empty.
61pub(crate) fn from_raw(raw: &[RawEntity], schema: SchemaId) -> FileHeader {
62 let mut h = FileHeader {
63 schema,
64 ..FileHeader::default()
65 };
66 for ent in raw {
67 let RawEntity::Simple {
68 name, attributes, ..
69 } = ent
70 else {
71 continue;
72 };
73 let at = |i: usize| attributes.get(i);
74 match name.as_str() {
75 "FILE_NAME" => {
76 h.file_name = attr_str(at(0));
77 h.time_stamp = attr_str(at(1));
78 h.authors = attr_list(at(2));
79 h.organizations = attr_list(at(3));
80 h.preprocessor_version = attr_str(at(4));
81 h.originating_system = attr_str(at(5));
82 h.authorisation = attr_str(at(6));
83 }
84 "FILE_DESCRIPTION" => {
85 // The description list customarily holds one entry; join any
86 // extras rather than dropping them.
87 h.description = attr_list(at(0)).join(", ");
88 }
89 _ => {}
90 }
91 }
92 h
93}
94
95/// A HEADER string attribute; anything else reads as empty.
96fn attr_str(a: Option<&Attribute>) -> String {
97 match a {
98 Some(Attribute::String(s)) => s.clone(),
99 _ => String::new(),
100 }
101}
102
103/// A HEADER string-list attribute; empty entries are dropped (the customary
104/// empty list renders as `('')`).
105fn attr_list(a: Option<&Attribute>) -> Vec<String> {
106 match a {
107 Some(Attribute::List(items)) => items
108 .iter()
109 .filter_map(|it| match it {
110 Attribute::String(s) if !s.is_empty() => Some(s.clone()),
111 _ => None,
112 })
113 .collect(),
114 _ => Vec::new(),
115 }
116}