Registry / RepositoryTypeScriptRustPython
Rollpie
ReadmeFiles
Versions
Info
Download
0.1.199.9 KB2026-09-230.1.098.9 KB2026-09-14
Version
0.1.1
Copyright
Rollpie, Irohabook
Publisher
math
Published
2026-09-23
Size
99.9 KB
Downloads
15
Checksum
e691f02f331393c7b5c3406746caa7ed2636c7af24b1cef422bc957ed16ecd74
Dependencies
None

book.rs

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
//! ワークブック。xlsx と xls を開く
//!
//! ```no_run
//! use folio::book::Book;
//! use std::path::Path;
//!
//! let mut book = Book::open(Path::new("data.xlsx")).unwrap();
//!
//! for one in book.sheet() {
//!     println!("{}", one.name);
//! }
//! ```

use crate::cfb;
use crate::error::Error;
use crate::package::Package;
use crate::shared::Shared;
use crate::sheet;
use crate::style::Style;
use crate::xls;
use crate::xml::{Event, Reader};
use std::path::Path;
use std::sync::Arc;


/// ワークブックに載っているシート
#[derive(Debug, Clone)]
pub struct Sheet {
    /// 見出しに出る名前
    pub name: String,
    /// パートの名前
    pub part: String,
    /// 隠していないか
    pub visible: bool,
}


/// どちらの形式を開いたか
enum Origin {
    /// xlsx
    Package(Package),
    /// xls
    Record(xls::Book),
}


/// 読むために開いたワークブック
pub struct Book {
    source: Origin,
    sheet: Vec<Sheet>,
    shared: Arc<Shared>,
    style: Arc<Style>,
    epoch1904: bool,
}


impl Book {
    /// xlsx か xls を開く
    ///
    /// 先頭のバイトを見て、どちらの形式かを決める。読む側は形式を気にしなくてよい。
    pub fn open(path: &Path) -> Result<Book, Error> {
        if cfb::is_compound(path) {
            return open_record(path);
        }

        let mut package = Package::open(path)?;
        let main = package.main()?;
        let (listed, epoch1904) = read_workbook(&mut package, &main)?;
        let link = package.relationship(&main)?;
        let mut sheet = Vec::with_capacity(listed.len());

        for (name, id, visible) in listed {
            let Some(one) = link.iter().find(|one| one.id == id) else {
                return Err(Error::at(format!("シート {name} の指し先がありません"), &main));
            };

            sheet.push(Sheet {name, part: one.target.clone(), visible});
        }

        // 共有文字列と書式の在り処も、いま読んだ関係から引く。関係のパートを読み直さない
        let find = |kind: &str| link.iter().find(|one| one.is(kind) && !one.external).map(|one| one.target.clone());

        let shared = match find("sharedStrings") {
            Some(part) if package.has(&part) => {
                let source = package.stream(&part)?;
                Shared::read(source).map_err(|error| error.within(&part))?
            }
            _ => Shared::new(),
        };

        let style = match find("styles") {
            Some(part) if package.has(&part) => {
                let mut reader = package.reader(&part)?;
                Style::read(&mut reader).map_err(|error| error.within(&part))?
            }
            _ => Style::new(),
        };

        Ok(Book {
            source: Origin::Package(package),
            sheet,
            shared: Arc::new(shared),
            style: Arc::new(style),
            epoch1904,
        })
    }

    /// 載っているシート
    pub fn sheet(&self) -> &[Sheet] {
        &self.sheet
    }

    /// 書式の対応
    pub fn style(&self) -> &Style {
        &self.style
    }

    /// 共有文字列
    pub fn shared(&self) -> &Shared {
        &self.shared
    }

    /// 1904 年方式かどうか
    pub fn epoch1904(&self) -> bool {
        self.epoch1904
    }

    /// 包みそのもの。folio が読まないパートを自分で取り出すときに使う
    ///
    /// 旧形式には包みがないので None を返す。
    pub fn package(&mut self) -> Option<&mut Package> {
        match &mut self.source {
            Origin::Package(one) => Some(one),
            Origin::Record(_) => None,
        }
    }

    /// 旧形式かどうか
    pub fn is_record(&self) -> bool {
        matches!(self.source, Origin::Record(_))
    }

    /// シートを行ごとに読む
    pub fn read(&mut self, name: &str) -> Result<sheet::Reader, Error> {
        let Some(index) = self.sheet.iter().position(|one| one.name == name) else {
            return Err(Error::new(format!("シート {name} がありません")));
        };

        let shared = self.shared.clone();
        let style = self.style.clone();
        let epoch1904 = self.epoch1904;

        match &mut self.source {
            Origin::Package(package) => {
                let part = self.sheet[index].part.clone();
                let source = package.stream(&part)?;
                Ok(sheet::Reader::new(source, shared, style, epoch1904))
            }

            Origin::Record(book) => {
                let source = book.cursor(index)?;
                Ok(sheet::Reader::over(source, shared, style, epoch1904))
            }
        }
    }
}


/// xls を開く
fn open_record(path: &Path) -> Result<Book, Error> {
    let mut file = cfb::Compound::open(path)?;

    let name = if file.has("Workbook") {
        "Workbook"
    } else if file.has("Book") {
        "Book"
    } else {
        return Err(Error::new("xls の Workbook ストリームがありません"));
    };

    let data = file.read(name)?;
    let mut book = xls::Book::open(data)?;

    let sheet = book
        .sheet
        .iter()
        .map(|one| Sheet {
            name: one.name.clone(),
            part: String::new(),
            visible: one.visible,
        })
        .collect();

    let shared = std::mem::take(&mut book.shared);
    let style = std::mem::take(&mut book.style);
    let epoch1904 = book.epoch1904;

    Ok(Book {
        source: Origin::Record(book),
        sheet,
        shared: Arc::new(shared),
        style: Arc::new(style),
        epoch1904,
    })
}


/// workbook.xml から、シートの並びと日付の方式を読む
fn read_workbook(package: &mut Package, main: &str) -> Result<(Vec<(String, String, bool)>, bool), Error> {
    let data = package.read(main)?;
    let mut reader = Reader::over(data);
    let mut sheet = Vec::new();
    let mut epoch1904 = false;

    while let Some(event) = reader.next().map_err(|error| error.within(main))? {
        if event != Event::Open && event != Event::Empty {
            continue;
        }

        match reader.name() {
            "workbookPr" => {
                let value = reader.attribute("date1904").unwrap_or("0");
                epoch1904 = value == "1" || value == "true";
            }

            "sheet" => {
                let name = reader.attribute("name").unwrap_or("").to_string();
                let id = reader.attribute("id").unwrap_or("").to_string();
                let visible = reader.attribute("state").unwrap_or("visible") == "visible";
                sheet.push((name, id, visible));
            }

            _ => {}
        }
    }

    Ok((sheet, epoch1904))
}