I have a project where I read an Excel file and serialize it to JSON. (By the way thank you very much to the peoples doing the calamine
crate and certainly serde
will be next).
I am at a point where I want to parse multiples lines into one object (cells for two items are as follow):
ItemType A ID | 42 | |||
Name | Foo | |||
Property A | Bar | |||
Property Z | Bar | |||
ItemType B ID | 43 | |||
Name | Baz | |||
Property B | Toto | Titi | Tata | |
/* Next item, there is a full list */ |
I wanted to have a function that parse the catalog and detect which type of element is there (A or B) and call the corresponding struct's new method. In this method I need the current line (to get the id) and an
I just learned about the while let Some(row) = rows.next() {}
syntax and it looked exactly what I wanted.
Except that I cannot pass rows
to a function so that function is able to call rows.next()
itself. Or at least I did not find a way to do it that please the compiler.
pub struct Catalog {
range: Range<DataType>,
}
pub fn parser(&self) {
let mut rows = self.range.rows();
while let Some(row) = rows.next() {
let label = match &row[0] {
DataType::String(str) => str,
_ => "Not a string",
};
if label == "ItemTypeA" {
// if I do rows.next() here, the compiler is happy
// if I call it inside of ItemTypeA::new I cannot find any way to make the compiler happy
items::Item::ItemTypeA(items::ItemTypeA::new(&row[1], rows));
} else if label == "ItemTypeB" {
items::Item::ItemTypeB(items::ItemTypeB::new(&row[1], rows));
} else {
eprintln!("Skipping unknown item type: {}", label);
}
}
}
pub mod items {
use calamine::{DataType, Rows};
pub struct ItemTypeA {
id: u64
name: String
propertyA: String
propertyZ: String
}
impl ItemTypeA {
// Which types should be written there ?
pub fn new(row: &DataType, rows: &Rows<DataType>) -> Self {
dbg!(rows.next()) // trigger a compile time error.
ItemTypeA {}
}
}
// impl the same for ItemTypeB
}
Is it normal and I just forgot something ? Or did not read the book thoroughly enough ?
Is there any better way to do it ? My workaround is creating a temporary struct in the if/else branches but I do not really like it as it split the parsing. And the parse function may grow exponentially if we add items types:
if label == "ItemTypeA" {
let item = items::ItemTypeB {
id: vec![row[1].clone()],
name: vec![], // Do rows.next() and pass it in here
propertyB: vec![], // Do rows.next() and pass it in here
};
// Then call new with the
let item = items::Item::ItemTypeB(items::ItemTypeB::new(item));