1use ifc_model::{Entity, EntityId, Model, Value};
30
31pub mod slot {
33 pub const GLOBAL_ID: usize = 0;
35 pub const NAME: usize = 2;
37 pub const IDENTIFICATION: usize = 5;
39 pub const WORKING_TIMES: usize = 6;
41 pub const EXCEPTION_TIMES: usize = 7;
43 pub const PREDEFINED_TYPE: usize = 8;
45}
46
47pub mod work_time_slot {
49 pub const NAME: usize = 0;
51 pub const RECURRENCE_PATTERN: usize = 3;
53 pub const START: usize = 4;
55 pub const FINISH: usize = 5;
57}
58
59pub mod recurrence_slot {
61 pub const RECURRENCE_TYPE: usize = 0;
63 pub const DAY_COMPONENT: usize = 1;
65 pub const WEEKDAY_COMPONENT: usize = 2;
67 pub const MONTH_COMPONENT: usize = 3;
69 pub const POSITION: usize = 4;
71 pub const INTERVAL: usize = 5;
73 pub const OCCURRENCES: usize = 6;
75}
76
77#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79pub enum WorkTimeRole {
80 Working,
82 Exception,
84}
85
86#[derive(Debug, Clone, Copy, PartialEq, Eq)]
90pub enum RecurrenceType {
91 Daily,
93 Weekly,
95 MonthlyByDayOfMonth,
97 MonthlyByPosition,
99 ByDayCount,
101 ByWeekdayCount,
103 YearlyByDayOfMonth,
105 YearlyByPosition,
107}
108
109impl RecurrenceType {
110 fn parse(token: &str) -> Option<Self> {
111 Some(match token {
112 "DAILY" => Self::Daily,
113 "WEEKLY" => Self::Weekly,
114 "MONTHLY_BY_DAY_OF_MONTH" => Self::MonthlyByDayOfMonth,
115 "MONTHLY_BY_POSITION" => Self::MonthlyByPosition,
116 "BY_DAY_COUNT" => Self::ByDayCount,
117 "BY_WEEKDAY_COUNT" => Self::ByWeekdayCount,
118 "YEARLY_BY_DAY_OF_MONTH" => Self::YearlyByDayOfMonth,
119 "YEARLY_BY_POSITION" => Self::YearlyByPosition,
120 _ => return None,
121 })
122 }
123}
124
125#[derive(Debug, Clone, PartialEq, Eq)]
132pub struct Recurrence {
133 pub id: EntityId,
135 pub recurrence_type: Option<RecurrenceType>,
137 pub weekdays: Vec<i64>,
139 pub position: Option<i64>,
144 pub interval: Option<i64>,
146 pub occurrences: Option<i64>,
148}
149
150impl Recurrence {
151 #[must_use]
156 pub fn is_bounded(&self) -> bool {
157 self.occurrences.is_some()
158 }
159}
160
161#[derive(Debug, Clone, PartialEq)]
163pub struct WorkTime {
164 pub id: EntityId,
166 pub role: WorkTimeRole,
168 pub name: Option<String>,
170 pub start: Option<String>,
172 pub finish: Option<String>,
174 pub recurrence: Option<Recurrence>,
176}
177
178#[derive(Debug, Clone, Copy)]
180pub struct WorkCalendar<'m> {
181 id: EntityId,
182 entity: &'m Entity,
183}
184
185impl<'m> WorkCalendar<'m> {
186 #[must_use]
188 pub fn id(&self) -> EntityId {
189 self.id
190 }
191
192 #[must_use]
194 pub fn name(&self) -> Option<&'m str> {
195 self.entity.text(slot::NAME)
196 }
197
198 #[must_use]
200 pub fn identification(&self) -> Option<&'m str> {
201 self.entity.text(slot::IDENTIFICATION)
202 }
203
204 #[must_use]
206 pub fn predefined_type(&self) -> Option<&'m str> {
207 match self.entity.attribute(slot::PREDEFINED_TYPE)? {
208 Value::Enum(token) => Some(token),
209 _ => None,
210 }
211 }
212
213 #[must_use]
215 pub fn working_times(&self, model: &Model) -> Vec<WorkTime> {
216 self.times(model, slot::WORKING_TIMES, WorkTimeRole::Working)
217 }
218
219 #[must_use]
221 pub fn exception_times(&self, model: &Model) -> Vec<WorkTime> {
222 self.times(model, slot::EXCEPTION_TIMES, WorkTimeRole::Exception)
223 }
224
225 fn times(&self, model: &Model, slot: usize, role: WorkTimeRole) -> Vec<WorkTime> {
226 let mut refs = Vec::new();
227 if let Some(v) = self.entity.attribute(slot) {
228 v.for_each_ref(&mut |id| refs.push(id));
229 }
230 refs.into_iter()
231 .filter_map(|id| read_work_time(model, id, role))
232 .collect()
233 }
234}
235
236fn read_work_time(model: &Model, id: EntityId, role: WorkTimeRole) -> Option<WorkTime> {
237 let entity = model.get(id)?;
238 if !entity.type_name.eq_ignore_ascii_case("IFCWORKTIME") {
239 return None;
240 }
241 let recurrence = match entity.attribute(work_time_slot::RECURRENCE_PATTERN) {
242 Some(Value::Ref(pattern)) => read_recurrence(model, *pattern),
243 _ => None,
244 };
245 Some(WorkTime {
246 id,
247 role,
248 name: entity.text(work_time_slot::NAME).map(str::to_string),
249 start: entity.text(work_time_slot::START).map(str::to_string),
250 finish: entity.text(work_time_slot::FINISH).map(str::to_string),
251 recurrence,
252 })
253}
254
255fn read_recurrence(model: &Model, id: EntityId) -> Option<Recurrence> {
256 let entity = model.get(id)?;
257 if !entity
258 .type_name
259 .eq_ignore_ascii_case("IFCRECURRENCEPATTERN")
260 {
261 return None;
262 }
263 let recurrence_type = match entity.attribute(recurrence_slot::RECURRENCE_TYPE) {
264 Some(Value::Enum(token)) => RecurrenceType::parse(token),
265 _ => None,
266 };
267 let mut weekdays = Vec::new();
268 if let Some(Value::List(items)) = entity.attribute(recurrence_slot::WEEKDAY_COMPONENT) {
269 for item in items {
270 if let Some(day) = item.unwrap_typed().as_i64() {
271 weekdays.push(day);
272 }
273 }
274 }
275 Some(Recurrence {
276 id,
277 recurrence_type,
278 weekdays,
279 position: entity
280 .attribute(recurrence_slot::POSITION)
281 .and_then(|v| v.unwrap_typed().as_i64()),
282 interval: entity
283 .attribute(recurrence_slot::INTERVAL)
284 .and_then(|v| v.unwrap_typed().as_i64()),
285 occurrences: entity
286 .attribute(recurrence_slot::OCCURRENCES)
287 .and_then(|v| v.unwrap_typed().as_i64()),
288 })
289}
290
291#[must_use]
293pub fn work_calendars(model: &Model) -> Vec<WorkCalendar<'_>> {
294 model
295 .of_type("IFCWORKCALENDAR")
296 .map(|(id, entity)| WorkCalendar { id, entity })
297 .collect()
298}