excel_cli/json_export/
exporters.rs1use 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
30pub 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 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 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
116pub 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 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 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
155pub 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}