Skip to content

Commit

Permalink
Working filter and row lock
Browse files Browse the repository at this point in the history
  • Loading branch information
mpatnode committed Oct 29, 2024
1 parent f2ca357 commit 6cd14fa
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 12 deletions.
14 changes: 13 additions & 1 deletion cli-excel-rs/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ fn cli() -> Command {
Command::new("csv")
.about("Convert a csv file to xlsx")
.arg(arg!(--in <FILE> "csv file to convert"))
.arg(arg!(--out <FILE> "xlsx output file name")),
.arg(arg!(--out <FILE> "xlsx output file name"))
.arg(arg!(--filter "Freeze the top row and add auto-filters")),
)
}

Expand All @@ -25,6 +26,8 @@ fn main() {
let input = sub_matches.get_one::<String>("in").expect("required");
let out = sub_matches.get_one::<String>("out").expect("required");

let apply_filter = sub_matches.get_flag("filter");

let mut f = File::open(input).expect("input csv file not found");
let mut data: Vec<u8> = Vec::new();

Expand All @@ -34,6 +37,15 @@ fn main() {
let mut workbook = WorkBook::new(Cursor::new(output_buffer));
let mut worksheet = workbook.get_worksheet(String::from("Sheet 1"));

// Apply filters first if requested
if apply_filter {
worksheet.freeze_top_row();
worksheet.add_auto_filter();
}

// Initialize the sheet before writing any rows
worksheet.init_sheet().expect("Failed to initialize worksheet");

let mut reader = bytes_to_csv(data.as_slice());
let headers = get_headers(&mut reader);

Expand Down
84 changes: 73 additions & 11 deletions crates/excel-rs-xlsx/src/sheet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ pub struct Sheet<'a, W: Write + Seek> {
// pub id: u16,
// pub is_closed: bool,
col_num_to_letter: Vec<Vec<u8>>,
current_row_num: u32
current_row_num: u32,
has_auto_filter: bool,
sheet_data_started: bool, // Add this to track if we've started sheetData
freeze_top_row: bool, // Add this to track if we should freeze the top row
}


impl<'a, W: Write + Seek> Sheet<'a, W> {
pub fn new(name: String, id: u16, writer: &'a mut ZipWriter<W>) -> Self {
let options = SimpleFileOptions::default()
Expand All @@ -27,21 +29,56 @@ impl<'a, W: Write + Seek> Sheet<'a, W> {
.start_file(format!("xl/worksheets/sheet{}.xml", id), options)
.ok();

// Writes Sheet Header
writer.write(b"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<worksheet xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\">\n<sheetData>\n").ok();

writer.write(b"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n\
<worksheet xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\" \
xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\">\n").ok();

Sheet {
sheet_buf: writer,
// id,
_name: name,
// is_closed: false,
col_num_to_letter: Vec::with_capacity(64),
current_row_num: 0
current_row_num: 1,
has_auto_filter: false,
sheet_data_started: false,
freeze_top_row: false,
}
}

// Public method to set the freeze flag
pub fn freeze_top_row(&mut self) {
self.freeze_top_row = true;
}

// Private method to write the sheetViews XML
fn write_sheet_views(&mut self) -> Result<()> {
if self.sheet_data_started {
return Ok(()); // Can't write sheetViews after sheetData has started
}

self.sheet_buf.write(b"<sheetViews>\n\
<sheetView tabSelected=\"1\" workbookViewId=\"0\" zoomScale=\"100\">\n\
<pane ySplit=\"1\" xSplit=\"0\" topLeftCell=\"A2\" activePane=\"bottomLeft\" state=\"frozen\" />\n\
<selection pane=\"topLeft\" />\n\
<selection pane=\"bottomLeft\" activeCell=\"A2\" sqref=\"A2\" />\n\
</sheetView>\n\
</sheetViews>\n")?;

self.sheet_data_started = true;

Ok(())
}

// New public method to initialize the sheet
pub fn init_sheet(&mut self) -> Result<()> {
// Write sheetViews if requested
if self.freeze_top_row {
self.write_sheet_views()?;
}
// Write sheetData start tag
self.sheet_buf.write(b"<sheetData>\n")?;
Ok(())
}

// TOOD: Use ShortVec over Vec for cell ID
pub fn write_row(&mut self, data: Vec<&[u8]>) -> Result<()> {
let mut final_vec = Vec::with_capacity(512 * data.len());

Expand Down Expand Up @@ -74,9 +111,10 @@ impl<'a, W: Write + Seek> Sheet<'a, W> {
col += 1;
}

final_vec.write(b"</row>")?;
final_vec.write(b"</row>\n")?;

self.sheet_buf.write(&final_vec)?;
self.current_row_num += 1;

Ok(())
}
Expand Down Expand Up @@ -115,16 +153,40 @@ impl<'a, W: Write + Seek> Sheet<'a, W> {
}

pub fn close(&mut self) -> Result<()> {
self.sheet_buf.write(b"\n</sheetData>\n</worksheet>\n")?;
// Close sheetData
self.sheet_buf.write(b"</sheetData>\n")?;

// Write autoFilter if requested
if self.has_auto_filter {
let num_columns = self.col_num_to_letter.len();
if num_columns > 0 {
let last_col_letter = self.col_to_letter(num_columns - 1);
let auto_filter_range = format!("A1:{}1", String::from_utf8_lossy(last_col_letter));
self.sheet_buf.write(format!("<autoFilter ref=\"{}\"/>\n", auto_filter_range).as_bytes())?;
}
}

// Close worksheet
self.sheet_buf.write(b"</worksheet>")?;
Ok(())
}

pub fn add_auto_filter(&mut self) {
self.has_auto_filter = true;
}

fn num_to_bytes(&self, n: u32) -> ([u8; 9], usize) {
// Convert from number to string manually
let mut row_in_chars_arr: [u8; 9] = [0; 9];
let mut row = n;
let mut char_pos = 8;
let mut digits = 0;

if row == 0 {
row_in_chars_arr[8] = b'0';
return (row_in_chars_arr, 1);
}

while row > 0 {
row_in_chars_arr[char_pos] = b'0' + (row % 10) as u8;
row = row / 10;
Expand Down

0 comments on commit 6cd14fa

Please sign in to comment.