diff --git a/Cargo.toml b/Cargo.toml index 2fa6fa7..a45c6d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,15 +1,13 @@ [package] name = "tdms" description = "A LabView TDMS file parser written in Rust" -version = "0.1.5" +version = "0.1.6" edition = "2021" license = "MIT" repository = "https://github.com/DnOberon/tdms" # no need to include the test data directory in the crate, in fact that makes it so we can't publish exclude = ["/data"] -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] indexmap = "1.8.2" thiserror = "1.0" diff --git a/README.md b/README.md index b0f1d90..b8e3b4b 100644 --- a/README.md +++ b/README.md @@ -27,13 +27,25 @@ fn main() { Err(e) => panic!("{:?}", e), }; - // TDMS files record their data in segments - each can potentially contain metadata and/or raw - // data - for segment in file.segments { - println!("{:?}", segment.metadata) + // fetch groups + let groups = file.groups(); + + for group in groups { + // fetch an IndexSet of the group's channels + let channels = file.channels(&group); + + for channel in channels { + // once you know the channel's full path (group + channel) you can ask for the full + // channel object. This contains all the segments that contain this data channel + // eventually this will also implement Iterator, allowing you to iterate through + // the channels raw data, currently planned + match file.channel(&group, &channel) { + Ok(c) => c, + Err(e) => panic!("{:?}", e), + }; + } } } - ``` ## Contributing diff --git a/examples/segments.rs b/examples/segments.rs index f9a38a6..9f731c8 100644 --- a/examples/segments.rs +++ b/examples/segments.rs @@ -11,9 +11,22 @@ fn main() { Err(e) => panic!("{:?}", e), }; - // TDMS files record their data in segments - each can potentially contain metadata and/or raw - // data - for segment in file.segments { - println!("{:?}", segment.metadata) + // fetch groups + let groups = file.groups(); + + for group in groups { + // fetch an IndexSet of the group's channels + let channels = file.channels(&group); + + for channel in channels { + // once you know the channel's full path (group + channel) you can ask for the full + // channel object. This contains all the segments that contain this data channel + // eventually this will also implement Iterator, allowing you to iterate through + // the channels raw data, currently planned + match file.channel(&group, &channel) { + Ok(c) => c, + Err(e) => panic!("{:?}", e), + }; + } } } diff --git a/src/channel.rs b/src/channel.rs index df62aa3..c8517cb 100644 --- a/src/channel.rs +++ b/src/channel.rs @@ -1,18 +1,21 @@ -use crate::segment::{DAQmxDataIndex, Metadata, RawDataIndex}; +use crate::segment::{ChannelPath, DAQmxDataIndex, GroupPath, RawDataIndex}; use crate::{General, Segment, TdmsError}; #[derive(Debug, Clone)] pub struct Channel<'a> { - path: String, + group_path: GroupPath, + path: ChannelPath, segments: Vec<&'a Segment>, bytes_read: u64, current_segment: &'a Segment, - raw_data_index: &'a Option, - daqmx_data_index: &'a Option, } impl<'a> Channel<'a> { - pub fn new(segments: Vec<&'a Segment>, path: String) -> Result { + pub fn new( + segments: Vec<&'a Segment>, + group_path: String, + path: String, + ) -> Result { if segments.len() <= 0 { return Err(General(String::from( "no segments provided for channel creation", @@ -21,26 +24,12 @@ impl<'a> Channel<'a> { let current_segment = segments[0]; - let mut raw_data_index: &Option = &None; - let mut daqmx_data_index: &Option = &None; - - match ¤t_segment.metadata { - None => {} - Some(metadata) => { - for object in &metadata.objects { - raw_data_index = &object.raw_data_index; - daqmx_data_index = &object.daqmx_data_index - } - } - } - return Ok(Channel { + group_path, path, segments, bytes_read: 0, current_segment, - raw_data_index, - daqmx_data_index, }); } } diff --git a/src/lib.rs b/src/lib.rs index d931988..bb6e605 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,8 @@ //! A Rust library for reading LabVIEW TDMS files. //! //! More information about the TDMS file format can be found here: +use indexmap::IndexSet; +use std::collections::HashSet; use std::fs; use std::fs::File; use std::io::{BufReader, Read, Seek, SeekFrom}; @@ -8,6 +10,7 @@ use std::path::Path; pub mod error; use crate::channel::Channel; +use crate::segment::ChannelPath; use crate::TdmsError::{ General, InvalidDAQmxDataIndex, InvalidSegment, StringConversionError, UnknownDataType, }; @@ -51,14 +54,84 @@ impl TDMSFile { return Ok(TDMSFile { segments, reader }); } - pub fn channel(&self, path: String) -> Result { + /// groups returns all possible groups throughout the file + pub fn groups(&self) -> Vec { + let mut map: HashSet = HashSet::new(); + + for segment in &self.segments { + for (group, _) in &segment.groups { + map.insert(String::from(group)); + } + } + + return Vec::from_iter(map); + } + + pub fn channels(&self, group_path: &str) -> Vec { + let mut map: HashSet = HashSet::new(); + + for segment in &self.segments { + let channel_map = match segment.groups.get(group_path) { + Some(m) => m, + None => &None, + }; + + let channel_map = match channel_map { + None => continue, + Some(m) => m, + }; + + for channel in channel_map { + map.insert(String::from(channel)); + } + } + + return Vec::from_iter(map); + } + + pub fn channel(&self, group_path: &str, path: &str) -> Result { let mut vec: Vec<&Segment> = vec![]; + let mut channel_in_segment: bool = false; - // TODO: actually iterate through and build a set of segments that match for segment in &self.segments { - vec.push(&segment) + match segment.groups.get(group_path) { + None => { + if !segment.has_new_obj_list() && channel_in_segment { + vec.push(&segment) + } else { + channel_in_segment = false + } + } + Some(channels) => match channels { + None => { + if !segment.has_new_obj_list() && channel_in_segment { + vec.push(&segment) + } else { + channel_in_segment = false + } + } + Some(channels) => { + let channel = channels.get(path); + + match channel { + None => { + if !segment.has_new_obj_list() && channel_in_segment { + vec.push(&segment) + } else { + channel_in_segment = false + } + } + Some(_) => { + vec.push(&segment); + channel_in_segment = true; + } + } + } + }, + } } - return Channel::new(vec, path); + + return Channel::new(vec, group_path.to_string(), path.to_string()); } } diff --git a/src/segment.rs b/src/segment.rs index 959039e..9cbe8dc 100644 --- a/src/segment.rs +++ b/src/segment.rs @@ -90,23 +90,23 @@ impl Segment { let paths: Vec<&str> = path.split("/").collect(); if paths.len() >= 2 && paths[1] != "" { - if !groups.contains_key(rem_first_and_last(paths[1])) { - let _ = groups.insert(rem_first_and_last(paths[1]).to_string(), None); + if !groups.contains_key(rem_quotes(paths[1])) { + let _ = groups.insert(rem_quotes(paths[1]).to_string(), None); } } if paths.len() >= 3 && paths[2] != "" { - let map = groups.get_mut(rem_first_and_last(paths[1])); + let map = groups.get_mut(rem_quotes(paths[1])); match map { Some(map) => match map { Some(map) => { - map.insert(rem_first_and_last(paths[2]).to_string()); + map.insert(rem_quotes(paths[2]).to_string()); } None => { let _ = groups.insert( - rem_first_and_last(paths[1]).to_string(), - Some(indexset! {rem_first_and_last(paths[2]).to_string()}), + rem_quotes(paths[1]).to_string(), + Some(indexset! {rem_quotes(paths[2]).to_string()}), ); } }, @@ -156,15 +156,15 @@ impl Segment { /// `all_data_reader` returns a Take containing the raw data for the segment. This function assumes /// that the reader passed in is the ORIGINAL reader, or another instance thereof. pub fn all_data_reader(&mut self, mut r: R) -> Result, TdmsError> { - match r.seek(SeekFrom::Start( + return match r.seek(SeekFrom::Start( self.start_pos + self.lead_in.raw_data_offset, )) { Ok(_) => { let take = r.take(self.lead_in.next_segment_offset - self.lead_in.raw_data_offset); - return Ok(take); + Ok(take) } - Err(e) => return Err(ReadError(e)), - } + Err(e) => Err(ReadError(e)), + }; } /// this function is not accurate unless the lead in portion of the segment has been read @@ -594,11 +594,30 @@ impl MetadataProperty { } } -fn rem_first_and_last(value: &str) -> &str { - let mut chars = value.chars(); - chars.next(); - chars.next_back(); - chars.as_str() +fn rem_quotes(value: &str) -> &str { + let mut original = value.chars(); + let mut chars = value.clone().chars().peekable(); + + match chars.peek() { + None => (), + Some(first) => { + if first.to_string() == "'" { + original.next(); + } + } + } + + let mut reversed = chars.rev().peekable(); + match reversed.peek() { + None => (), + Some(last) => { + if last.to_string() == "'" { + original.next_back(); + } + } + } + + original.as_str() } #[macro_export] diff --git a/src/tests.rs b/src/tests.rs index 133f417..fe45e05 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,5 +1,5 @@ use crate::segment::Segment; -use crate::TDMSFile; +use crate::{Channel, TDMSFile, TdmsError}; use std::fs::File; use std::path::Path; @@ -100,3 +100,27 @@ fn can_read_all_segments_raw() { assert_eq!(file.segments.len(), 3); } + +#[test] +fn can_read_groups_channels() { + let file = match TDMSFile::from_path(Path::new("data/standard.tdms"), false) { + Ok(f) => f, + Err(e) => panic!("{:?}", e), + }; + + let groups = file.groups(); + assert!(groups.len() > 0); + + for group in groups { + let channels = file.channels(&group); + + for channel in channels { + match file.channel(&group, &channel) { + Ok(c) => c, + Err(e) => panic!("{:?}", e), + }; + } + } + + assert_eq!(file.segments.len(), 2); +}