1use crate::escape;
7use crate::lexer::{Lexer, Token};
8use crate::{Exchange, HeaderRecord, Parameter, Record};
9use std::io::{self, Write};
10
11pub fn write<S: AsRef<str>, W: Write + ?Sized>(
18 exchange: &Exchange<S>,
19 output: &mut W,
20) -> io::Result<()> {
21 validate_header(&exchange.header.records)?;
22 writeln!(output, "ISO-10303-21;")?;
23 writeln!(output, "HEADER;")?;
24 for record in &exchange.header.records {
25 write_identifier(record.name.as_ref(), output)?;
26 write!(output, "(")?;
27 write_parameters(&record.parameters, output, 0)?;
28 writeln!(output, ");")?;
29 }
30 writeln!(output, "ENDSEC;")?;
31 writeln!(output, "DATA;")?;
32 for instance in &exchange.data.records {
33 write!(output, "#{}=", instance.id.as_str())?;
34 match instance.records.as_slice() {
35 [] => return Err(invalid("data instance must contain a record")),
36 [record] => write_record(record, output)?,
37 records => {
38 write!(output, "(")?;
39 for record in records {
40 write_record(record, output)?;
41 }
42 write!(output, ")")?;
43 }
44 }
45 writeln!(output, ";")?;
46 }
47 writeln!(output, "ENDSEC;")?;
48 writeln!(output, "END-ISO-10303-21;")
49}
50
51pub fn write_to_string<S: AsRef<str>>(exchange: &Exchange<S>) -> io::Result<String> {
59 let mut bytes = Vec::new();
60 write(exchange, &mut bytes)?;
61 String::from_utf8(bytes).map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))
62}
63
64fn invalid(detail: &'static str) -> io::Error {
65 io::Error::new(io::ErrorKind::InvalidInput, detail)
66}
67
68fn validate_header<S: AsRef<str>>(records: &[HeaderRecord<S>]) -> io::Result<()> {
69 const REQUIRED: [&str; 3] = ["FILE_DESCRIPTION", "FILE_NAME", "FILE_SCHEMA"];
70 if records.len() < REQUIRED.len() {
71 return Err(invalid("missing mandatory STEP header record"));
72 }
73 for (record, required) in records.iter().zip(REQUIRED) {
74 if !record.name.as_ref().eq_ignore_ascii_case(required) {
75 return Err(invalid("mandatory STEP header records are out of order"));
76 }
77 }
78 if records[REQUIRED.len()..].iter().any(|record| {
79 REQUIRED
80 .iter()
81 .any(|required| record.name.as_ref().eq_ignore_ascii_case(required))
82 }) {
83 return Err(invalid("duplicate mandatory STEP header record"));
84 }
85 Ok(())
86}
87
88fn require_identifier(value: &str) -> io::Result<()> {
89 let keyword = value.strip_prefix('!').unwrap_or(value);
90 let mut bytes = keyword.bytes();
91 let valid = bytes
92 .next()
93 .is_some_and(|byte| byte.is_ascii_alphabetic() || byte == b'_')
94 && bytes.all(|byte| byte.is_ascii_alphanumeric() || byte == b'_');
95 if valid {
96 Ok(())
97 } else {
98 Err(invalid("invalid STEP identifier"))
99 }
100}
101
102fn write_identifier<W: Write + ?Sized>(value: &str, output: &mut W) -> io::Result<()> {
103 require_identifier(value)?;
104 output.write_all(value.to_ascii_uppercase().as_bytes())
105}
106
107fn write_enumeration<W: Write + ?Sized>(value: &str, output: &mut W) -> io::Result<()> {
108 if value.starts_with('!') {
109 return Err(invalid("invalid STEP enumeration"));
110 }
111 write_identifier(value, output)
112}
113
114fn require_number(value: &str, real: bool) -> io::Result<()> {
115 let mut lexer = Lexer::new(value.as_bytes());
116 let valid = lexer.next().is_some_and(|token| {
117 token.is_ok_and(|token| {
118 token.span.start == 0
119 && token.span.end == value.len()
120 && if real {
121 matches!(token.value, Token::Real(_))
122 } else {
123 matches!(token.value, Token::Integer(_))
124 }
125 })
126 }) && lexer.next().is_none();
127 if valid {
128 Ok(())
129 } else {
130 Err(invalid("invalid STEP number"))
131 }
132}
133
134fn require_binary(value: &str) -> io::Result<()> {
135 let bytes = value.as_bytes();
136 if matches!(bytes.first(), Some(b'0'..=b'3')) && bytes[1..].iter().all(u8::is_ascii_hexdigit) {
137 Ok(())
138 } else {
139 Err(invalid("invalid STEP binary"))
140 }
141}
142
143fn write_record<S: AsRef<str>, W: Write + ?Sized>(
144 record: &Record<S>,
145 output: &mut W,
146) -> io::Result<()> {
147 write_identifier(record.name.as_ref(), output)?;
148 write!(output, "(")?;
149 write_parameters(&record.parameters, output, 0)?;
150 write!(output, ")")
151}
152
153fn write_parameters<S: AsRef<str>, W: Write + ?Sized>(
154 parameters: &[Parameter<S>],
155 output: &mut W,
156 depth: usize,
157) -> io::Result<()> {
158 for (index, parameter) in parameters.iter().enumerate() {
159 if index != 0 {
160 write!(output, ",")?;
161 }
162 write_parameter_at(parameter, output, depth)?;
163 }
164 Ok(())
165}
166
167pub fn write_parameter<S: AsRef<str>, W: Write + ?Sized>(
174 parameter: &Parameter<S>,
175 output: &mut W,
176) -> io::Result<()> {
177 write_parameter_at(parameter, output, 0)
178}
179
180fn write_parameter_at<S: AsRef<str>, W: Write + ?Sized>(
181 parameter: &Parameter<S>,
182 output: &mut W,
183 depth: usize,
184) -> io::Result<()> {
185 if depth > crate::MAX_PARAMETER_NESTING {
186 return Err(io::Error::new(
187 io::ErrorKind::InvalidInput,
188 "parameter nesting limit exceeded",
189 ));
190 }
191 match parameter {
192 Parameter::Null => write!(output, "$"),
193 Parameter::Derived => write!(output, "*"),
194 Parameter::Bool(true) => write!(output, ".T."),
195 Parameter::Bool(false) => write!(output, ".F."),
196 Parameter::LogicalUnknown => write!(output, ".U."),
197 Parameter::Integer(value) => {
198 require_number(value.as_ref(), false)?;
199 write!(output, "{}", value.as_ref())
200 }
201 Parameter::Real(value) => {
202 require_number(value.as_ref(), true)?;
203 write!(output, "{}", value.as_ref())
204 }
205 Parameter::Text(text) => write!(output, "'{}'", escape::encode(text.as_ref())),
206 Parameter::Binary(binary) => {
207 require_binary(binary.as_ref())?;
208 write!(output, "\"{}\"", binary.as_ref())
209 }
210 Parameter::Enum(value) => {
211 write!(output, ".")?;
212 write_enumeration(value.as_ref(), output)?;
213 write!(output, ".")
214 }
215 Parameter::Ref(id) => write!(output, "#{id_value}", id_value = id.as_str()),
216 Parameter::List(items) => {
217 write!(output, "(")?;
218 write_parameters(items, output, depth + 1)?;
219 write!(output, ")")
220 }
221 Parameter::Typed { type_name, value } => {
222 write_identifier(type_name.as_ref(), output)?;
223 write!(output, "(")?;
224 write_parameter_at(value, output, depth + 1)?;
225 write!(output, ")")
226 }
227 }
228}