rs_pcd/header/
parser.rs

1// Copyright 2025 bigpear0201
2
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6
7//     http://www.apache.org/licenses/LICENSE-2.0
8
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use super::PcdHeader;
16use crate::error::{PcdError, Result};
17use std::io::BufRead;
18
19pub fn parse_header<R: BufRead>(reader: &mut R) -> Result<PcdHeader> {
20    let mut header = PcdHeader::default();
21    let mut line_num = 0;
22
23    // Explicitly set viewpoint default to identity
24    header.viewpoint = [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0];
25
26    loop {
27        let mut line = String::new();
28        let bytes_read = reader.read_line(&mut line)?;
29        if bytes_read == 0 {
30            return Err(PcdError::InvalidHeader {
31                line: line_num,
32                msg: "Unexpected EOF before DATA section".to_string(),
33            });
34        }
35        line_num += 1;
36
37        let trimmed = line.trim();
38        if trimmed.is_empty() || trimmed.starts_with('#') {
39            continue;
40        }
41
42        let parts: Vec<&str> = trimmed.split_whitespace().collect();
43        if parts.is_empty() {
44            continue;
45        }
46
47        match parts[0] {
48            "VERSION" => {
49                header.version = parts.get(1).map(|&s| s.to_string()).unwrap_or_default();
50            }
51            "FIELDS" => {
52                header.fields = parts[1..].iter().map(|&s| s.to_string()).collect();
53            }
54            "SIZE" => {
55                header.sizes = parse_vec(&parts[1..], line_num, "SIZE")?;
56            }
57            "TYPE" => {
58                let type_chars: Result<Vec<char>> = parts[1..]
59                    .iter()
60                    .map(|s| {
61                        if s.len() != 1 {
62                            return Err(PcdError::InvalidHeader {
63                                line: line_num,
64                                msg: format!("Invalid TYPE: {}", s),
65                            });
66                        }
67                        Ok(s.chars().next().unwrap())
68                    })
69                    .collect();
70                header.types = type_chars?;
71            }
72            "COUNT" => {
73                header.counts = parse_vec(&parts[1..], line_num, "COUNT")?;
74            }
75            "WIDTH" => {
76                header.width = parse_single(parts.get(1), line_num, "WIDTH")?;
77            }
78            "HEIGHT" => {
79                header.height = parse_single(parts.get(1), line_num, "HEIGHT")?;
80            }
81            "VIEWPOINT" => {
82                let vp: Vec<f64> = parse_vec(&parts[1..], line_num, "VIEWPOINT")?;
83                if vp.len() == 7 {
84                    header.viewpoint.copy_from_slice(&vp);
85                } else {
86                    return Err(PcdError::InvalidHeader {
87                        line: line_num,
88                        msg: format!("VIEWPOINT expected 7 values, got {}", vp.len()),
89                    });
90                }
91            }
92            "POINTS" => {
93                header.points = parse_single(parts.get(1), line_num, "POINTS")?;
94            }
95            "DATA" => {
96                let fmt = parts.get(1).ok_or_else(|| PcdError::InvalidHeader {
97                    line: line_num,
98                    msg: "Missing DATA format".to_string(),
99                })?;
100                header.data = fmt.parse()?;
101
102                // Post-processing: Handle optional COUNT
103                if header.counts.is_empty() {
104                    header.counts = vec![1; header.fields.len()];
105                }
106
107                // Validate header consistency
108                validate_header(&header, line_num)?;
109
110                return Ok(header);
111            }
112            _ => {
113                // Ignore unknown fields as per spec? Or warn?
114                // For valid PCD, usually we shouldn't see random stuff, but tolerance is good.
115            }
116        }
117    }
118}
119
120fn parse_vec<T: std::str::FromStr>(parts: &[&str], line: usize, field: &str) -> Result<Vec<T>> {
121    parts
122        .iter()
123        .map(|s| {
124            s.parse::<T>().map_err(|_| PcdError::InvalidHeader {
125                line,
126                msg: format!("Invalid value for {}: {}", field, s),
127            })
128        })
129        .collect()
130}
131
132fn parse_single<T: std::str::FromStr>(part: Option<&&str>, line: usize, field: &str) -> Result<T> {
133    match part {
134        Some(s) => s.parse::<T>().map_err(|_| PcdError::InvalidHeader {
135            line,
136            msg: format!("Invalid token for {}: {}", field, s),
137        }),
138        None => Err(PcdError::InvalidHeader {
139            line,
140            msg: format!("Missing value for {}", field),
141        }),
142    }
143}
144
145fn validate_header(header: &PcdHeader, line: usize) -> Result<()> {
146    if header.fields.len() != header.sizes.len() {
147        return Err(PcdError::InvalidHeader {
148            line,
149            msg: format!(
150                "Mismatch in fields({}) and sizes({})",
151                header.fields.len(),
152                header.sizes.len()
153            ),
154        });
155    }
156    if header.fields.len() != header.types.len() {
157        return Err(PcdError::InvalidHeader {
158            line,
159            msg: format!(
160                "Mismatch in fields({}) and types({})",
161                header.fields.len(),
162                header.types.len()
163            ),
164        });
165    }
166    // Spec says COUNT is optional and defaults to 1.
167    if header.counts.is_empty() {
168        // If no COUNT line, fill with 1s
169        // header is mutable reference, but here we can't mutate easily if validation is separated.
170        // Actually, better to do this fixup before validation or allow mutation here?
171        // Let's assume validation is read-only but we can modify in the parser loop after DATA found.
172        // Wait, validate_header takes &PcdHeader. We cannot mutate.
173        // We should move this logic to the main parser loop before calling validate_header.
174        return Err(PcdError::InvalidHeader {
175            line,
176            msg: format!("Counts vector empty but fields present (logic error in parser)"),
177        });
178    } else if header.counts.len() != header.fields.len() {
179        return Err(PcdError::InvalidHeader {
180            line,
181            msg: format!(
182                "Mismatch in fields({}) and counts({})",
183                header.fields.len(),
184                header.counts.len()
185            ),
186        });
187    }
188
189    Ok(())
190}