Skip to content

Commit da7d34e

Browse files
authored
Merge pull request #11 from roblabla/colnames
Add COLNAMES option to give names to the columns
2 parents b5ab81b + f73381f commit da7d34e

File tree

4 files changed

+86
-2
lines changed

4 files changed

+86
-2
lines changed

src/options.rs

+26-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use nom::branch::alt;
22
use nom::bytes::complete::{escaped, tag, tag_no_case};
3-
use nom::character::complete::{alpha1, digit0, multispace0, multispace1, none_of};
3+
use nom::character::complete::{alpha1, digit0, digit1, multispace0, multispace1, none_of};
44
use nom::combinator::{map, recognize};
55
use nom::{IResult, Parser};
66
use nom::sequence::{delimited, preceded, separated_pair, terminated, tuple};
@@ -9,13 +9,15 @@ pub enum UsingOption {
99
File(String),
1010
Worksheet(String),
1111
Range(String),
12+
ColNames(String),
1213
}
1314

1415
pub fn parse_option(input: &str) -> IResult<&str, UsingOption> {
1516
parse_with_spaces(alt((
1617
parse_filename_option,
1718
parse_worksheet_option,
1819
parse_range_option,
20+
parse_colnames_option,
1921
))).parse(input)
2022
}
2123

@@ -49,6 +51,16 @@ fn parse_range_option(input: &str) -> IResult<&str, UsingOption> {
4951
|t: (&str, &str)| UsingOption::Range(t.1.to_string()))(input)
5052
}
5153

54+
fn parse_colnames_option(input: &str) -> IResult<&str, UsingOption> {
55+
let option = tag_no_case("COLNAMES");
56+
57+
let value = preceded(
58+
tag("'"), terminated(digit1, tag("'")));
59+
60+
map(separated_pair(option, multispace1, value),
61+
|t: (&str, &str)| UsingOption::ColNames(t.1.to_string()))(input)
62+
}
63+
5264
fn parse_with_spaces<'a, T>(parser: impl Parser<&'a str, T, nom::error::Error<&'a str>>)
5365
-> impl Parser<&'a str, T, nom::error::Error<&'a str>> {
5466
preceded(multispace0, terminated(parser, multispace0))
@@ -120,4 +132,17 @@ mod tests {
120132
_ => panic!("Expected range option")
121133
}
122134
}
135+
136+
#[test]
137+
fn parse_colnames_option_produces_colname() {
138+
let (output, option) = parse_colnames_option("COLNAMES '152'").unwrap();
139+
140+
assert_eq!(output, "");
141+
match option {
142+
UsingOption::ColNames(colname) => {
143+
assert_eq!(colname, "152");
144+
},
145+
_ => panic!("Expected colnames option")
146+
}
147+
}
123148
}

src/spreadsheet/manager.rs

+23-1
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ use calamine::{open_workbook_auto, DataType, Range, Reader, Sheets};
77
use std::fs::File;
88
use std::io::BufReader;
99
use std::path::Path;
10+
use std::str::FromStr;
1011

1112
pub struct DataManager {
1213
sheets: Sheets<BufReader<File>>,
1314
worksheet: String,
1415
range: Option<CellRange>,
16+
colnames_row: Option<u32>,
1517
}
1618

1719
pub enum DataManagerError {
@@ -49,9 +51,17 @@ impl DataManager {
4951
pub fn get_columns(&mut self) -> Vec<String> {
5052
let range = self.get_effective_range();
5153
if range.get_size().1 > 0 {
54+
let row_workspace_sheet = self.colnames_row
55+
.and_then(|v| Some((v, self.sheets.worksheet_range(self.worksheet.as_str()))))
56+
.and_then(|(row, sheet)| Some((row, sheet?.ok()?)));
5257
(range.start().unwrap().1..=range.end().unwrap().1)
5358
.into_iter()
54-
.map(|n| CellIndex::new(n + 1, 1).get_x_as_string())
59+
.map(|n| {
60+
row_workspace_sheet
61+
.as_ref()
62+
.and_then(|(row, sheet)| sheet.get_value((*row, n)).map(|v| v.to_string()))
63+
.unwrap_or_else(|| CellIndex::new(n + 1, 1).get_x_as_string())
64+
})
5565
.collect()
5666
} else {
5767
Vec::new()
@@ -70,6 +80,7 @@ pub struct DataManagerBuilder {
7080
file: Option<String>,
7181
worksheet: Option<String>,
7282
range: Option<CellRange>,
83+
colnames_row: Option<u32>,
7384
}
7485

7586
impl DataManagerBuilder {
@@ -91,6 +102,11 @@ impl DataManagerBuilder {
91102
UsingOption::Range(range) => {
92103
builder = builder.range(CellRange::try_parse(range.as_str()).unwrap());
93104
}
105+
UsingOption::ColNames(colnames) => {
106+
// We substract 1 to go from excel indexing (which starts at 1) to 0-based
107+
// indexing of the row.
108+
builder = builder.colnames_row(u32::from_str(colnames.as_str()).unwrap().saturating_sub(1));
109+
},
94110
}
95111
}
96112

@@ -112,6 +128,11 @@ impl DataManagerBuilder {
112128
self
113129
}
114130

131+
pub fn colnames_row(mut self, row: u32) -> Self {
132+
self.colnames_row = Some(row);
133+
self
134+
}
135+
115136
pub fn open(self) -> Result<DataManager, DataManagerError> {
116137
if let Some(file) = self.file {
117138
if let Some(worksheet) = self.worksheet {
@@ -120,6 +141,7 @@ impl DataManagerBuilder {
120141
sheets,
121142
worksheet,
122143
range: self.range,
144+
colnames_row: self.colnames_row,
123145
}),
124146
Err(err) => Err(DataManagerError::Calamine(err)),
125147
}

tests/abcdef_colnames.xlsx

5.25 KB
Binary file not shown.

tests/lib.rs

+37
Original file line numberDiff line numberDiff line change
@@ -141,3 +141,40 @@ fn test_abcdef_file_aggregate() {
141141
AbcdAgg { kind: "odd".to_string(), count: 3 },
142142
]);
143143
}
144+
145+
#[test]
146+
fn test_abcdef_file_with_colnames() {
147+
let connection = init_connection();
148+
connection.execute("\
149+
CREATE VIRTUAL TABLE test_data USING xlite(\
150+
FILENAME './tests/abcdef_colnames.xlsx',\
151+
WORKSHEET 'Sheet1',\
152+
RANGE 'A2:D7',
153+
COLNAMES '1'
154+
);\
155+
", params![]).unwrap();
156+
157+
let mut query = connection.prepare("\
158+
SELECT alpha, number, word, kind FROM test_data;\
159+
").unwrap();
160+
161+
let rows = query.query_map(params![], |row| Ok(Abcd {
162+
alpha: row.get(0).unwrap(),
163+
number: row.get(1).unwrap(),
164+
word: row.get(2).unwrap(),
165+
kind: row.get(3).unwrap(),
166+
})).unwrap();
167+
168+
let data = rows
169+
.map(|r| r.unwrap())
170+
.collect::<Vec<Abcd>>();
171+
172+
assert_eq!(data, vec![
173+
Abcd { alpha: "A".to_string(), number: 10.0, word: "ten".to_string(), kind: "even".to_string() },
174+
Abcd { alpha: "B".to_string(), number: 11.0, word: "eleven".to_string(), kind: "odd".to_string() },
175+
Abcd { alpha: "C".to_string(), number: 12.0, word: "twelve".to_string(), kind: "even".to_string() },
176+
Abcd { alpha: "D".to_string(), number: 13.0, word: "thirteen".to_string(), kind: "odd".to_string() },
177+
Abcd { alpha: "E".to_string(), number: 14.0, word: "fourteen".to_string(), kind: "even".to_string() },
178+
Abcd { alpha: "F".to_string(), number: 15.0, word: "fifteen".to_string(), kind: "odd".to_string() },
179+
]);
180+
}

0 commit comments

Comments
 (0)