1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
//! JSON で書き出す
//!
//! ほかの言語からシートを読むための形である。行を 1 つずつ JSON にして、改行で区切って並べる (JSON Lines)。
//!
//! 行は `{"number":6,"cell":[{"column":1,"letter":"A","value":"01"}]}` の形になる。
//! `cell` には、値か数式のあるセルだけが列の順に並ぶ。
//!
//! セルの値は JSON の型で `value` に入る。文字は文字列、数は数、真偽は真偽である。
//! 表示形式が日付の数は、数のまま `value` に入れ、`date` に日時を足す。
//! エラーは `value` の代わりに `error` を持つ。数式のあるセルには `formula` を足す。
//!
//! シートの一覧は `{"name":"Sheet1","visible":true}` の形で、1 枚を 1 行にする。
use crate::book::Sheet;
use crate::cell::{Cell, Row, Value, letter_of};
use crate::date;
use crate::style::Style;
/// 文字を JSON の文字列にして足す
///
/// 引用符、逆斜線、制御文字を逃がす。それ以外の文字はそのまま入れる。
pub fn text_into(text: &str, into: &mut String) {
into.push('"');
for value in text.chars() {
match value {
'"' => into.push_str("\\\""),
'\\' => into.push_str("\\\\"),
'\n' => into.push_str("\\n"),
'\r' => into.push_str("\\r"),
'\t' => into.push_str("\\t"),
_ => {
if (value as u32) < 0x20 {
into.push_str(&format!("\\u{:04x}", value as u32));
} else {
into.push(value);
}
}
}
}
into.push('"');
}
/// 数を JSON の数にして足す
///
/// 整数は小数点を付けずに書く。無限と非数は JSON に書けないので null にする。
pub fn number_into(number: f64, into: &mut String) {
if number.is_finite() {
into.push_str(&number.to_string());
} else {
into.push_str("null");
}
}
/// セル 1 つを JSON にして足す
///
/// 日付かどうかは書式で決まるので、シートの書式と、日付の数が 1904 年起点かどうかを渡す。
pub fn cell_into(cell: &Cell, style: &Style, epoch1904: bool, into: &mut String) {
into.push_str(&format!("{{\"column\":{},\"letter\":\"{}\"", cell.column, letter_of(cell.column)));
match &cell.value {
Value::Empty => {}
Value::Number(number) => {
into.push_str(",\"value\":");
number_into(*number, into);
if number.is_finite() && style.is_date(cell.style) {
let when = date::moment(*number, epoch1904);
into.push_str(&format!(
",\"date\":\"{:04}-{:02}-{:02}T{:02}:{:02}:{:02}\"",
when.year, when.month, when.day, when.hour, when.minute, when.second
));
}
}
Value::Text(text) => {
into.push_str(",\"value\":");
text_into(text, into);
}
Value::Boolean(state) => into.push_str(&format!(",\"value\":{state}")),
Value::Error(text) => {
into.push_str(",\"error\":");
text_into(text, into);
}
}
if !cell.formula.is_empty() {
into.push_str(",\"formula\":");
text_into(&cell.formula, into);
}
into.push('}');
}
/// 行 1 つを JSON にして足す。値も数式もないセルは出さない
pub fn row_into(row: &Row, style: &Style, epoch1904: bool, into: &mut String) {
into.push_str(&format!("{{\"number\":{},\"cell\":[", row.number));
let mut first = true;
for cell in &row.cell {
if cell.value.is_empty() && cell.formula.is_empty() {
continue;
}
if !first {
into.push(',');
}
cell_into(cell, style, epoch1904, into);
first = false;
}
into.push_str("]}");
}
/// 行 1 つを JSON にする
pub fn row(row: &Row, style: &Style, epoch1904: bool) -> String {
let mut result = String::new();
row_into(row, style, epoch1904, &mut result);
result
}
/// シート 1 枚の名前と、隠していないかを JSON にして足す
pub fn sheet_into(one: &Sheet, into: &mut String) {
into.push_str("{\"name\":");
text_into(&one.name, into);
into.push_str(&format!(",\"visible\":{}}}", one.visible));
}
/// シート 1 枚の名前と、隠していないかを JSON にする
pub fn sheet(one: &Sheet) -> String {
let mut result = String::new();
sheet_into(one, &mut result);
result
}