Skip to main content

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}