excel_cli/json_export/
exporters.rs

1use anyhow::{Context, Result};
2use indexmap::IndexMap;
3use serde::Serialize;
4
5use std::fs::File;
6use std::io::Write;
7use std::path::Path;
8
9use crate::excel::{Sheet, Workbook};
10use crate::json_export::converters::process_cell_value;
11use crate::json_export::extractors::{extract_horizontal_headers, extract_vertical_headers};
12use crate::json_export::types::{HeaderDirection, OrderedSheetData};
13
14pub fn serialize_to_json<T: Serialize>(data: &T) -> Result<String> {
15    serde_json::to_string_pretty(data).context("Failed to serialize data to JSON")
16}
17
18fn write_json_to_file<T: Serialize>(data: &T, path: &Path) -> Result<()> {
19    let mut file =
20        File::create(path).with_context(|| format!("Failed to create file: {}", path.display()))?;
21
22    let json_string = serialize_to_json(data)?;
23
24    file.write_all(json_string.as_bytes())
25        .with_context(|| format!("Failed to write to file: {}", path.display()))?;
26
27    Ok(())
28}
29
30// Process a single sheet for all-sheets export
31pub fn process_sheet_for_json(
32    sheet: &Sheet,
33    direction: HeaderDirection,
34    header_count: usize,
35) -> Result<OrderedSheetData> {
36    match direction {
37        HeaderDirection::Horizontal => {
38            if header_count == 0 || header_count >= sheet.data.len() {
39                anyhow::bail!("Invalid header rows: {}", header_count);
40            }
41
42            let headers = extract_horizontal_headers(sheet, header_count)?;
43
44            let row_count = sheet.data.len().saturating_sub(header_count + 1);
45            let mut sheet_data = Vec::with_capacity(row_count);
46
47            let mut ordered_headers: Vec<(usize, &String)> = headers
48                .iter()
49                .map(|(col_idx, header)| (*col_idx, header))
50                .collect();
51            ordered_headers.sort_by_key(|(col_idx, _)| *col_idx);
52
53            // Process each data row
54            for row_idx in (header_count + 1)..sheet.data.len() {
55                let mut row_data = IndexMap::with_capacity(ordered_headers.len());
56
57                for (col_idx, header) in &ordered_headers {
58                    if row_idx < sheet.data.len() && *col_idx < sheet.data[row_idx].len() {
59                        let cell = &sheet.data[row_idx][*col_idx];
60
61                        if !header.is_empty() {
62                            let json_value = process_cell_value(cell);
63                            row_data.insert((*header).clone(), json_value);
64                        }
65                    }
66                }
67
68                if !row_data.is_empty() {
69                    sheet_data.push(row_data);
70                }
71            }
72
73            Ok(sheet_data)
74        }
75        HeaderDirection::Vertical => {
76            if header_count == 0 || header_count >= sheet.data[0].len() {
77                anyhow::bail!("Invalid header columns: {}", header_count);
78            }
79
80            let headers = extract_vertical_headers(sheet, header_count)?;
81
82            let col_count = sheet.data[0].len().saturating_sub(header_count + 1);
83            let mut sheet_data = Vec::with_capacity(col_count);
84
85            let mut ordered_headers: Vec<(usize, &String)> = headers
86                .iter()
87                .map(|(row_idx, header)| (*row_idx, header))
88                .collect();
89            ordered_headers.sort_by_key(|(row_idx, _)| *row_idx);
90
91            // Process each data column
92            for col_idx in (header_count + 1)..sheet.data[0].len() {
93                let mut obj = IndexMap::with_capacity(ordered_headers.len());
94
95                for (row_idx, header) in &ordered_headers {
96                    if *row_idx < sheet.data.len() && col_idx < sheet.data[*row_idx].len() {
97                        let cell = &sheet.data[*row_idx][col_idx];
98
99                        if !header.is_empty() {
100                            let json_value = process_cell_value(cell);
101                            obj.insert((*header).clone(), json_value);
102                        }
103                    }
104                }
105
106                if !obj.is_empty() {
107                    sheet_data.push(obj);
108                }
109            }
110
111            Ok(sheet_data)
112        }
113    }
114}
115
116// Export JSON file for a single sheet
117pub fn export_json(
118    sheet: &Sheet,
119    direction: HeaderDirection,
120    header_count: usize,
121    path: &Path,
122) -> Result<()> {
123    let sheet_data = process_sheet_for_json(sheet, direction, header_count)?;
124    write_json_to_file(&sheet_data, path)
125}
126
127pub fn generate_all_sheets_json(
128    workbook: &Workbook,
129    direction: HeaderDirection,
130    header_count: usize,
131) -> Result<IndexMap<String, OrderedSheetData>> {
132    let sheet_names = workbook.get_sheet_names();
133
134    let mut all_sheets = IndexMap::with_capacity(sheet_names.len());
135
136    let current_sheet_index = workbook.get_current_sheet_index();
137
138    // Process each sheet
139    for (index, sheet_name) in sheet_names.iter().enumerate() {
140        let sheet_data = if index == current_sheet_index {
141            process_sheet_for_json(workbook.get_current_sheet(), direction, header_count)?
142        } else {
143            // Need to switch sheets - create a clone and process
144            let mut wb_clone = workbook.clone();
145            wb_clone.switch_sheet(index)?;
146            process_sheet_for_json(wb_clone.get_current_sheet(), direction, header_count)?
147        };
148
149        all_sheets.insert(sheet_name.clone(), sheet_data);
150    }
151
152    Ok(all_sheets)
153}
154
155// Export all sheets to a single JSON file
156pub fn export_all_sheets_json(
157    workbook: &Workbook,
158    direction: HeaderDirection,
159    header_count: usize,
160    path: &Path,
161) -> Result<()> {
162    let all_sheets = generate_all_sheets_json(workbook, direction, header_count)?;
163
164    write_json_to_file(&all_sheets, path)
165}