1use heck::ToSnakeCase;
2use indexmap::IndexMap;
3use serde_json::Value;
4
5use crate::{Column, FieldNameMode};
6
7pub fn to_a1_col_key(index: usize) -> String {
8 let mut result = String::new();
9 let mut n = index as i32; while n >= 0 {
12 let remainder = (n % 26) as u8;
13 result.push((b'a' + remainder) as char);
14 n = (n / 26) - 1;
15 }
16 result.chars().rev().collect()
17}
18
19pub fn to_padded_col_key(prefix: &str, index: usize, num_cols: usize) -> String {
20 build_padded_col_key(prefix, false, index, num_cols)
21}
22
23pub fn to_padded_col_suffix(prefix: &str, index: usize, num_cols: usize) -> String {
24 build_padded_col_key(prefix, true, index, num_cols)
25}
26
27fn build_padded_col_key(prefix: &str, underscore: bool, index: usize, num_cols: usize) -> String {
28 let width = if num_cols < 100 {
29 2
30 } else if num_cols < 1000 {
31 3
32 } else if num_cols < 10000 {
33 4
34 } else {
35 5
36 };
37 let num = index + 1;
38 let separator = if underscore { "_" } else { "" };
39 format!("{}{}{:0width$}", prefix, separator, num, width = width)
40}
41
42pub fn to_c01_col_key(index: usize, num_cols: usize) -> String {
43 to_padded_col_key("c", index, num_cols)
44}
45
46pub fn to_head_key(index: usize, field_mode: &FieldNameMode, num_cols: usize) -> String {
47 if field_mode.use_c01() {
48 to_c01_col_key(index, num_cols)
49 } else {
50 to_a1_col_key(index)
51 }
52}
53
54pub fn to_head_key_default(index: usize) -> String {
55 to_c01_col_key(index, 1000)
56}
57
58pub fn build_header_keys(
60 first_row: &[String],
61 columns: &[Column],
62 field_mode: &FieldNameMode,
63) -> Vec<String> {
64 let mut h_index = 0;
65 let mut headers: Vec<String> = vec![];
66 let num_cols = first_row.len();
67 let keep_headers = field_mode.keep_headers();
68 for h_row in first_row.to_owned() {
69 let sn = h_row.to_snake_case();
70 let mut has_override = false;
71 if let Some(col) = columns.get(h_index) {
72 if let Some(k_str) = &col.key {
74 let h_key = if headers.contains(&k_str.to_string()) {
75 to_padded_col_suffix(k_str, h_index, num_cols)
76 } else {
77 k_str.to_string()
78 };
79 headers.push(h_key);
80 has_override = true;
81 }
82 }
83 if !has_override {
84 if keep_headers && sn.len() > 0 {
85 let sn_key = if headers.contains(&sn) {
86 to_padded_col_suffix(&sn, h_index, num_cols)
87 } else {
88 sn
89 };
90 headers.push(sn_key);
91 } else {
92 headers.push(to_head_key(h_index, field_mode, num_cols));
93 }
94 }
95 h_index += 1;
96 }
97 headers
98}
99
100pub fn build_a1_headers(first_row: &[String]) -> Vec<String> {
102 build_header_keys(first_row, &[], &FieldNameMode::A1)
103}
104
105pub fn build_c01_headers(first_row: &[String]) -> Vec<String> {
107 build_header_keys(first_row, &[], &FieldNameMode::NumPadded)
108}
109
110pub(crate) fn is_not_header_row(
112 row_map: &IndexMap<String, Value>,
113 row_index: usize,
114 headers: &[String],
115) -> bool {
116 if row_index > 0 {
117 return true;
118 }
119 let mut h_index = 0;
120 let mut num_matched: usize = 0;
121 for (_key, value) in row_map.iter() {
122 let ref_key = value.to_string().to_snake_case();
123 if let Some(hk) = headers.get(h_index) {
124 let sn = hk.to_snake_case();
125 if sn == ref_key || sn.len() == 0 {
126 num_matched += 1;
127 }
128 }
129 h_index += 1;
130 }
131 num_matched < headers.len()
132}
133
134#[cfg(test)]
135mod tests {
136
137 use crate::Format;
138
139 use super::*;
140
141 #[test]
142 fn test_cell_letters_1() {
143 assert_eq!(to_a1_col_key(26), "aa");
144 }
145
146 #[test]
147 fn test_cell_letters_2() {
148 assert_eq!(to_a1_col_key(701), "zz");
149 }
150
151 #[test]
152 fn test_cell_letters_3() {
153 assert_eq!(to_a1_col_key(702), "aaa");
154 }
155
156 #[test]
157 fn test_cell_letters_4() {
158 assert_eq!(to_c01_col_key(8, 60), "c09");
159 }
160
161 #[test]
162 fn test_cell_letters_5() {
163 assert_eq!(to_c01_col_key(20, 750), "c021");
164 }
165
166 #[test]
167 fn test_cell_letters_6() {
168 assert_eq!(to_c01_col_key(20, 2000), "c0021");
169 }
170
171 #[test]
172 fn test_first_row() {
173 let first_row = ["Viscosity", "Rating", "", ""].map(|s| s.to_string());
175 let cols = vec![
176 Column::from_key_ref_with_format(None, Format::Float, None, false, false),
177 Column::from_key_ref_with_format(
178 Some("points"),
179 Format::Decimal(3),
180 None,
181 false,
182 false,
183 ),
184 Column::from_key_ref_with_format(Some("adjusted"), Format::Float, None, false, false),
185 ];
186 let headers = build_header_keys(&first_row, &cols, &FieldNameMode::AutoA1);
187 assert_eq!(headers.get(0).unwrap(), "viscosity");
189 assert_eq!(headers.get(1).unwrap(), "points");
191 assert_eq!(headers.get(2).unwrap(), "adjusted");
193 assert_eq!(headers.get(3).unwrap(), "d");
195 }
196
197 #[test]
198 fn test_headers_a1_override() {
199 let first_row = ["Viscosity", "Rating", "Weighted", "Class"].map(|s| s.to_string());
201
202 let headers = build_a1_headers(&first_row);
203 assert_eq!(headers.get(0).unwrap(), "a");
205 assert_eq!(headers.get(3).unwrap(), "d");
207 }
208
209 #[test]
210 fn test_headers_c01_override() {
211 let first_row: Vec<String> = (0..200)
213 .map(|x| {
214 [
215 char::from_u32(65 + (x % 26)).unwrap_or('_').to_string(),
216 (x * 3 * 1).to_string(),
217 ]
218 .concat()
219 })
220 .collect();
221
222 let headers = build_c01_headers(&first_row);
223 assert_eq!(headers.get(0).unwrap(), "c001");
225 assert_eq!(headers.get(3).unwrap(), "c004");
227 }
228}