mshio/lib.rs
1//! Parser for Gmsh mesh files using the MSH file format version 4.1
2//!
3//! The library supports parsing ASCII and binary encoded MSH files adhering to the MSH file format
4//! version 4.1 as specified in the [Gmsh documention](http://gmsh.info/doc/texinfo/gmsh.html#MSH-file-format).
5//!
6//! ```
7//! use std::error::Error;
8//! use std::fs;
9//!
10//! fn main() -> Result<(), Box<dyn Error>> {
11//! // Try to read and parse a MSH file
12//! let msh_bytes = fs::read("tests/data/sphere_coarse.msh")?;
13//! let parser_result = mshio::parse_msh_bytes(msh_bytes.as_slice());
14//!
15//! // Note that the a parser error cannot be propagated directly using the ?-operator, as it
16//! // contains a reference into the u8 slice where the error occurred.
17//! let msh = parser_result.map_err(|e| format!("Error while parsing:\n{}", e))?;
18//! assert_eq!(msh.total_element_count(), 891);
19//!
20//! Ok(())
21//! }
22//! ```
23//!
24//! If parsing was successful, the [`parse_msh_bytes`](fn.parse_msh_bytes.html) function returns a
25//! [`MshFile`](mshfile/struct.MshFile.html) instance. The structure of `MshFile` closely mirrors
26//! the MSH format specification. For example the `MeshData` associated to a `MshFile` may contain an
27//! optional [`Elements`](mshfile/struct.Elements.html) section. This `Elements` section can contain
28//! an arbitray number of [`ElementBlock`](mshfile/struct.ElementBlock.html) instances, where each
29//! `ElementBlock` only contains elements of the same type and dimension.
30//!
31//! Currently, only the following sections of MSH files are actually parsed: `Entities`, `Nodes`,
32//! `Elements`. All other sections are silently ignored, if they follow the pattern of being
33//! delimited by `$SectionName` and `$EndSectionName` (in accordance to the MSH format specification).
34//!
35//! Note that the actual values are not checked for consistency beyond what is defined in the MSH format specification.
36//! This means, that a parsed element may refer to node indices that are not present in the node section (if the MSH file already contains
37//! such an inconsistency). In the future, utility functions may be added to check this.
38//!
39//! Although the `MshFile` struct and all related structs are generic over their value types,
40//! the `parse_msh_bytes` function enforces the usage of `u64`, `i32` and `f64` as output value types
41//! corresponding to the MSH input value types `size_t`, `int` and `double`
42//! (of course `size_t` values will still be parsed as having the size specified in the file header).
43//! We did not encounter MSH files using different types (e.g. 64 bit integers or 32 bit floats) and therefore cannot test it.
44//! In addition, the MSH format specification does not specify the size of the float and integer types.
45//! If the user desires narrowing conversions, they should be performed manually after parsing the file.
46//!
47//! Note that when loading collections of elements/nodes and other entities, the parser checks if
48//! the number of these objects can be represented in the system's `usize` type. If this is not the
49//! case it returns an error as they cannot be stored in a `Vec` in this case.
50//!
51
52use std::convert::{TryFrom, TryInto};
53
54use nom::bytes::complete::tag;
55use nom::character::complete::{alpha0, char};
56use nom::combinator::peek;
57use nom::sequence::{delimited, preceded, terminated};
58use nom::IResult;
59
60/// Error handling components of the parser
61#[allow(unused)]
62pub mod error;
63/// Contains all types that are used to represent the structure of parsed MSH files
64///
65/// The central type is [`MshFile`](struct.MshFile.html) which contains the whole structure of the
66/// parsed mesh.
67pub mod mshfile;
68/// Parser utility functions used by this MSH parser (may be private in the future)
69pub mod parsers;
70
71/// Error type returned by the MSH parser if parsing fails without panic
72pub use error::MshParserError;
73/// Re-exports all types that are used to represent the structure of an MSH file
74pub use mshfile::*;
75
76use crate::error::{make_error, MapMshError, MshParserErrorKind};
77use error::{always_error, context};
78use parsers::{br, take_sp};
79use parsers::{
80 parse_element_section, parse_entity_section, parse_header_section, parse_node_section,
81};
82
83// TODO: Error instead of panic on num_parser construction if size of the data type is not supported
84// TODO: Reconsider naming of the MshUsizeT etc. and num parser trait names, make them consistent
85// TODO: Doc strings for the new num_parser trait interface
86
87// TODO: Make section parsers generic over data types (i.e. don't mandate f64, u64, i32)
88// TODO: Unify element and node section parsing
89// (e.g. a single section parser, then per section type one header and one content parser)
90// TODO: Unify entity parsing (currently, point parsers and the curve/surface/volume parsers are separate)
91
92// TODO: Implement parser for physical groups
93// TODO: Log in the MeshData struct which unknown sections were ignored
94// TODO: Add more .context() calls/more specialized errors
95// TODO: Replace remaining unimplemented!/expect calls with errors
96
97// TODO: Test the float values parsed from a binary MSH file
98// TODO: Add tests of errors in node section
99// TODO: Add tests of errors in entity section
100// TODO: Add tests that try to parse a mesh with u64 indices to u32
101
102/// Try to parse a MshFile from a slice of bytes
103///
104/// The input can be the content of an ASCII or binary encoded MSH file of file format version 4.1.
105impl<'a> TryFrom<&'a [u8]> for MshFile<u64, i32, f64> {
106 type Error = MshParserError<&'a [u8]>;
107
108 fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
109 match private_parse_msh_bytes(value) {
110 Ok((_, file)) => Ok(file),
111 Err(e) => Err(e.into()),
112 }
113 }
114}
115
116/// Try to parse a [`MshFile`](mshfile/struct.MshFile.html) from a slice of bytes
117///
118/// The input can be the content of an ASCII or binary encoded MSH file of file format version 4.1.
119pub fn parse_msh_bytes<'a>(
120 input: &'a [u8],
121) -> Result<MshFile<u64, i32, f64>, MshParserError<&'a [u8]>> {
122 input.try_into()
123}
124
125fn private_parse_msh_bytes<'a>(
126 input: &'a [u8],
127) -> IResult<&'a [u8], MshFile<u64, i32, f64>, MshParserError<&'a [u8]>> {
128 let (input, (header, parsers)) = context(
129 "MSH file header section",
130 parsers::parse_delimited_block(
131 terminated(tag("$MeshFormat"), br),
132 terminated(tag("$EndMeshFormat"), br),
133 context("MSH format header content", parse_header_section),
134 ),
135 )(input)?;
136
137 // Closure to detect a line with a section start tag
138 let section_detected = |start_tag, input| {
139 peek::<_, _, (), _>(delimited(take_sp, tag(start_tag), br))(input).is_ok()
140 };
141
142 // Macro to apply a parser to a section delimited by start and end tags
143 macro_rules! parse_section {
144 ($start_tag:expr, $end_tag:expr, $parser:expr, $input:expr) => {{
145 delimited(
146 delimited(take_sp, tag($start_tag), br),
147 $parser,
148 delimited(take_sp, tag($end_tag), take_sp),
149 )($input)
150 }};
151 }
152
153 let mut entity_sections = Vec::new();
154 let mut node_sections = Vec::new();
155 let mut element_sections = Vec::new();
156
157 let mut input = input;
158
159 // Loop over all sections of the mesh file
160 while !parsers::eof::<_, ()>(input).is_ok() {
161 // Check for entity section
162 if section_detected("$Entities", input) {
163 let (input_, entities) = parse_section!(
164 "$Entities",
165 "$EndEntities",
166 |i| context("entity section", parse_entity_section(&parsers))(i),
167 input
168 )?;
169
170 entity_sections.push(entities);
171 input = input_;
172 }
173 // Check for node section
174 else if section_detected("$Nodes", input) {
175 let (input_, nodes) = parse_section!(
176 "$Nodes",
177 "$EndNodes",
178 |i| context("node section", parse_node_section(&parsers))(i),
179 input
180 )?;
181
182 node_sections.push(nodes);
183 input = input_;
184 }
185 // Check for elements section
186 else if section_detected("$Elements", input) {
187 let (input_, elements) = parse_section!(
188 "$Elements",
189 "$EndElements",
190 |i| context("element section", parse_element_section(&parsers))(i),
191 input
192 )?;
193
194 element_sections.push(elements);
195 input = input_;
196 }
197 // Check for unknown section (gets ignored)
198 else if let Ok((input_, section_header)) =
199 peek::<_, _, (), _>(preceded(take_sp, delimited(char('$'), alpha0, br)))(input)
200 {
201 let section_header = String::from_utf8_lossy(section_header);
202 let section_start_tag = format!("${}", section_header);
203 let section_end_tag = format!("$End{}", section_header);
204
205 let (input_, _) = parsers::delimited_block(
206 delimited(take_sp, tag(§ion_start_tag[..]), br),
207 delimited(take_sp, tag(§ion_end_tag[..]), take_sp),
208 )(input_)?;
209
210 input = input_;
211 }
212 // Check for invalid lines
213 else {
214 return always_error(MshParserErrorKind::InvalidSectionHeader)(input);
215 }
216 }
217
218 // TODO: Replace the unimplemented! calls with errors
219
220 let entities = match entity_sections.len() {
221 1 => Some(entity_sections.remove(0)),
222 0 => None,
223 _ => {
224 return Err(make_error(input, MshParserErrorKind::Unimplemented)
225 .with_context(input, "Multiple entity sections found in the MSH file, this cannot be handled at the moment."))
226 }
227 };
228
229 let nodes = match node_sections.len() {
230 1 => Some(node_sections.remove(0)),
231 0 => None,
232 _ => return Err(make_error(input, MshParserErrorKind::Unimplemented)
233 .with_context(input, "Multiple node sections found in the MSH file, this cannot be handled at the moment.")),
234 };
235
236 let elements = match element_sections.len() {
237 1 => Some(element_sections.remove(0)),
238 0 => None,
239 _ => return Err(make_error(input, MshParserErrorKind::Unimplemented)
240 .with_context(input, "Multiple element sections found in the MSH file, this cannot be handled at the moment.")),
241 };
242
243 Ok((
244 input,
245 MshFile {
246 header,
247 data: MshData {
248 entities,
249 nodes,
250 elements,
251 },
252 },
253 ))
254}