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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
//! 共有文字列
//!
//! xlsx は、セルに入る文字をシートに直接は書かず、`xl/sharedStrings.xml` に一度だけ置いて、
//! セルからは番号で指す。同じ語が何度も出る表では、これでファイルがかなり小さくなる。
//!
//! # 置き方
//!
//! 文字列ごとに String を持つと、1000 万語で 1000 万個の入れ物ができる。
//! ここでは全部を 1 本の String につなぎ、始まりの位置だけを並べて持つ。
//! 1 語あたり 4 バイトで済み、入れ物を作る手間もかからない。
//!
//! `_xHHHH_` の形で書かれた文字は、読み込むときに戻しておく。
//! セルから引くたびに戻すと、同じ語を何度も調べることになるためである。
//!
//! # バイトのまま走査する
//!
//! 語は数十万になることもあるので、汎用の XML の読み取りは通さず、`scan` モジュールの道具で
//! バイトのまま走査する。`<si>` の中身は、シートの `<is>` と同じ形である。
use crate::error::Error;
use crate::scan::{self, Feed, Mark, find, local};
use crate::zip;
/// 前もって取っておく語の数の上限。uniqueCount が壊れていても、大きく取りすぎないように
const RESERVE_LIMIT: usize = 1 << 24;
/// 共有文字列の表
pub struct Shared {
/// 文字を全部つないだもの
text: String,
/// 語ごとの始まり。末尾に text の長さを置くので、要素数は語数 + 1
start: Vec<u32>,
}
impl Shared {
/// 空の表
pub fn new() -> Shared {
Shared {text: String::new(), start: vec![0]}
}
/// sharedStrings.xml を読む
///
/// `<si>` が 1 語である。`<r>` で区切られた飾りつきの語は、つないで 1 語にする。
/// `<rPh>` はふりがななので、中の `<t>` は語に入れない。
pub fn read(source: zip::Reader) -> Result<Shared, Error> {
let mut result = Shared::new();
let mut feed = Feed::new(source);
loop {
match result.scan(feed.data())? {
Some(used) => feed.advance(used),
None => {
feed.finish()?;
return Ok(result);
}
}
if !feed.fill()? {
if !feed.blank() {
return Err(Error::new("共有文字列が途中で終わっています"));
}
return Ok(result);
}
}
}
/// 受け取ったバイトの中の、そろっている語を全部読む
///
/// 読み終えて捨ててよいバイト数を返す。`</sst>` に着いたら None
fn scan(&mut self, data: &[u8]) -> Result<Option<usize>, Error> {
let mut at = 0;
loop {
let Some(open) = find(data, at, b'<') else {
return Ok(Some(data.len()));
};
match scan::mark(data, open) {
Mark::More => return Ok(Some(open)),
Mark::Skip(next) | Mark::Data(_, _, next) => at = next,
Mark::Close(next, from, to) => {
if local(&data[from..to]) == b"sst" {
return Ok(None);
}
at = next;
}
Mark::Open(bracket, from, to) => match local(&data[from..to]) {
b"si" => {
if data[bracket - 1] == b'/' {
self.settle();
at = bracket + 1;
continue;
}
let begin = self.text.len();
match scan::rich(data, bracket + 1, b"si", &mut self.text)? {
Some(next) => {
self.settle();
at = next;
}
None => {
self.text.truncate(begin);
return Ok(Some(open));
}
}
}
b"sst" => {
// 語の数が書いてあれば、先に場所を取っておく
let stop = if data[bracket - 1] == b'/' {bracket - 1} else {bracket};
let mut cursor = to;
while let Some((key, value, next)) = scan::attribute(data, cursor, stop)? {
if key == b"uniqueCount" {
let count = crate::sheet::unsigned(value).unwrap_or(0) as usize;
self.start.reserve(count.min(RESERVE_LIMIT));
}
cursor = next;
}
at = bracket + 1;
}
_ => match scan::skip(data, bracket, from, to) {
Some(next) => at = next,
None => return Ok(Some(open)),
},
},
}
}
}
/// 入っている語の数
pub fn count(&self) -> usize {
self.start.len() - 1
}
/// 番号で引く。範囲の外なら空
pub fn get(&self, index: u32) -> &str {
let index = index as usize;
if index + 1 >= self.start.len() {
return "";
}
&self.text[self.start[index] as usize..self.start[index + 1] as usize]
}
/// 語を 1 つ足す
///
/// 旧形式の共有文字列は番号で指されているので、同じ語が重なっていてもまとめない。
pub fn push(&mut self, value: &str) {
self.text.push_str(value);
self.start.push(self.text.len() as u32);
}
/// 読み込んでいる語を締める。`_xHHHH_` の形があれば、ここで戻す
fn settle(&mut self) {
let begin = self.start[self.start.len() - 1] as usize;
if self.text[begin..].contains("_x") {
let value = decode(&self.text[begin..]);
self.text.truncate(begin);
self.text.push_str(&value);
}
self.start.push(self.text.len() as u32);
}
}
impl Default for Shared {
fn default() -> Shared {
Shared::new()
}
}
/// `_xHHHH_` の形を文字に戻す
///
/// XML に置けない文字を、Excel はこの形で書く。左から順に見るので、
/// 文字として `_x0041_` と書きたいときに Excel が出す `_x005F_x0041_` も、正しく戻る。
pub fn decode(text: &str) -> String {
let mut result = String::with_capacity(text.len());
decode_into(text, &mut result);
result
}
/// `_xHHHH_` の形を文字に戻して足す
pub fn decode_into(text: &str, into: &mut String) {
if !text.contains("_x") {
into.push_str(text);
return;
}
let byte = text.as_bytes();
let mut cursor = 0;
while cursor < byte.len() {
if byte[cursor] == b'_' && cursor + 7 <= byte.len() && byte[cursor + 1] == b'x' && byte[cursor + 6] == b'_' {
let digit = &text[cursor + 2..cursor + 6];
if let Ok(number) = u32::from_str_radix(digit, 16) {
if let Some(value) = char::from_u32(number) {
into.push(value);
cursor += 7;
continue;
}
}
}
let step = step_of(byte[cursor]);
into.push_str(&text[cursor..cursor + step]);
cursor += step;
}
}
/// その位置から始まる文字のバイト数
fn step_of(first: u8) -> usize {
match first {
0x00..=0x7f => 1,
0xc0..=0xdf => 2,
0xe0..=0xef => 3,
_ => 4,
}
}