Skip to main content

netcdf_reader/nc4/
attributes.rs

1//! Filter and convert HDF5 attributes to NetCDF-4 attributes.
2//!
3//! NetCDF-4 uses several internal HDF5 attributes that should not be exposed
4//! to users:
5//! - `_NCProperties`: file provenance metadata
6//! - `_Netcdf4Dimid`: dimension ID assignment
7//! - `_Netcdf4Coordinates`: coordinate variable tracking
8//! - `DIMENSION_LIST`: dimension-to-variable references
9//! - `REFERENCE_LIST`: variable-to-dimension back-references
10//! - `CLASS`: dimension scale marker
11//! - `NAME`: dimension scale name
12
13use hdf5_reader::group::Group;
14use hdf5_reader::messages::datatype::Datatype;
15
16use crate::error::Result;
17use crate::types::{NcAttrValue, NcAttribute};
18
19/// Internal attribute names that should be hidden from the NetCDF user API.
20const INTERNAL_ATTRIBUTES: &[&str] = &[
21    "_NCProperties",
22    "_Netcdf4Dimid",
23    "_Netcdf4Coordinates",
24    "_nc3_strict",
25    "DIMENSION_LIST",
26    "REFERENCE_LIST",
27    "CLASS",
28    "NAME",
29];
30
31/// Returns true if the given attribute name is an internal NetCDF-4/HDF5 attribute
32/// that should not be exposed to users.
33pub fn is_internal_attribute(name: &str) -> bool {
34    INTERNAL_ATTRIBUTES.contains(&name)
35}
36
37/// Extract user-visible attributes from an HDF5 group, filtering out
38/// internal NetCDF-4 attributes.
39pub fn extract_group_attributes(group: &Group<'_>) -> Result<Vec<NcAttribute>> {
40    let hdf5_attrs = match group.attributes() {
41        Ok(a) => a,
42        Err(_) => return Ok(Vec::new()),
43    };
44
45    let mut nc_attrs = Vec::new();
46    for attr in &hdf5_attrs {
47        if is_internal_attribute(&attr.name) {
48            continue;
49        }
50        if let Some(nc_attr) = convert_attribute(attr) {
51            nc_attrs.push(nc_attr);
52        }
53    }
54    Ok(nc_attrs)
55}
56
57/// Extract user-visible attributes from an HDF5 dataset, filtering out
58/// internal attributes.
59pub fn extract_variable_attributes(dataset: &hdf5_reader::Dataset<'_>) -> Result<Vec<NcAttribute>> {
60    let hdf5_attrs = dataset.attributes();
61    let mut nc_attrs = Vec::new();
62    for attr in &hdf5_attrs {
63        if is_internal_attribute(&attr.name) {
64            continue;
65        }
66        if let Some(nc_attr) = convert_attribute(attr) {
67            nc_attrs.push(nc_attr);
68        }
69    }
70    Ok(nc_attrs)
71}
72
73/// Convert an HDF5 attribute to a NetCDF attribute.
74fn convert_attribute(attr: &hdf5_reader::Attribute) -> Option<NcAttribute> {
75    let value = convert_attribute_value(attr)?;
76    Some(NcAttribute {
77        name: attr.name.clone(),
78        value,
79    })
80}
81
82/// Convert an HDF5 attribute's value to an NcAttrValue.
83fn convert_attribute_value(attr: &hdf5_reader::Attribute) -> Option<NcAttrValue> {
84    match &attr.datatype {
85        Datatype::FixedPoint { size, signed, .. } => match (size, signed) {
86            (1, true) => attr.read_1d::<i8>().ok().map(NcAttrValue::Bytes),
87            (1, false) => attr.read_1d::<u8>().ok().map(NcAttrValue::UBytes),
88            (2, true) => attr.read_1d::<i16>().ok().map(NcAttrValue::Shorts),
89            (2, false) => attr.read_1d::<u16>().ok().map(NcAttrValue::UShorts),
90            (4, true) => attr.read_1d::<i32>().ok().map(NcAttrValue::Ints),
91            (4, false) => attr.read_1d::<u32>().ok().map(NcAttrValue::UInts),
92            (8, true) => attr.read_1d::<i64>().ok().map(NcAttrValue::Int64s),
93            (8, false) => attr.read_1d::<u64>().ok().map(NcAttrValue::UInt64s),
94            _ => None,
95        },
96        Datatype::FloatingPoint { size, .. } => match size {
97            4 => attr.read_1d::<f32>().ok().map(NcAttrValue::Floats),
98            8 => attr.read_1d::<f64>().ok().map(NcAttrValue::Doubles),
99            _ => None,
100        },
101        Datatype::String { .. } => {
102            // Try reading as a single string first, then as array
103            if attr.num_elements() == 1 {
104                attr.read_string().ok().map(NcAttrValue::Chars)
105            } else {
106                attr.read_strings().ok().map(NcAttrValue::Strings)
107            }
108        }
109        Datatype::VarLen { base }
110            if attr.num_elements() == 1
111                && matches!(
112                    base.as_ref(),
113                    Datatype::FixedPoint {
114                        size: 1,
115                        signed: false,
116                        ..
117                    }
118                ) =>
119        {
120            attr.read_string().ok().map(NcAttrValue::Chars)
121        }
122        _ => None,
123    }
124}