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
//! xlsx、docx、pptx と、その旧形式を開いて中身を出す
//!
//! xlsx はシート名を渡すとその中身を、渡さないとシートの一覧を出す。
//! docx は本文を、pptx はスライドごとの図形と文字を出す。
//! 行も段落も打ち切らずに全部出す。長いときは head などで絞る。
//!
//! cargo run --release --example show -- data.xlsx
//! cargo run --release --example show -- data.xlsx Sheet1
//! cargo run --release --example show -- file.docx
//! cargo run --release --example show -- deck.pptx
//! cargo run --release --example show -- old.xls Sheet1
use folio::Error;
use folio::book::Book;
use folio::cell::{Value, letter_of};
use folio::date;
use folio::document::{Block, Document};
use folio::presentation::Presentation;
use std::path::Path;
fn main() {
if let Err(error) = run() {
eprintln!("{error}");
std::process::exit(1);
}
}
fn run() -> Result<(), Error> {
let mut argument = std::env::args().skip(1);
let Some(path) = argument.next() else {
return Err(Error::new("xlsx、docx、pptx のいずれかのパスを渡してください"));
};
if path.ends_with(".docx") || path.ends_with(".doc") {
return show_document(Path::new(&path));
}
if path.ends_with(".pptx") || path.ends_with(".ppt") {
return show_presentation(Path::new(&path));
}
show_book(Path::new(&path), argument.next())
}
/// docx の本文を出す
fn show_document(path: &Path) -> Result<(), Error> {
let mut document = Document::open(path)?;
let mut body = document.read()?;
while let Some(block) = body.read()? {
match block {
Block::Paragraph(one) => {
let style = if one.style.is_empty() {String::new()} else {format!("[{}] ", one.style)};
println!("{style}{}", one.text().replace('\n', "\\n"));
}
Block::Table(one) => {
println!("表 {} 行 {} 列", one.row.len(), one.width());
for row in &one.row {
let cell: Vec<String> = row.cell.iter().map(|value| value.text().replace('\n', "\\n")).collect();
println!(" {}", cell.join(" | "));
}
}
}
}
Ok(())
}
/// pptx のスライドを出す
fn show_presentation(path: &Path) -> Result<(), Error> {
let mut file = Presentation::open(path)?;
for index in 0..file.count() {
println!("--- {} 枚目 ---", index + 1);
for shape in &file.read(index)?.shape {
let place = if shape.place.is_empty() {String::new()} else {format!(" [{}]", shape.place)};
println!("{}{place}", shape.name);
for line in &shape.paragraph {
let indent = " ".repeat(line.level as usize + 1);
println!("{indent}{}", line.text().replace('\n', "\\n"));
}
}
}
Ok(())
}
/// xlsx のシート、またはシートの中身を出す
fn show_book(path: &Path, want: Option<String>) -> Result<(), Error> {
let mut book = Book::open(path)?;
let Some(want) = want else {
for one in book.sheet() {
let state = if one.visible {""} else {" (隠し)"};
println!("{}{state}", one.name);
}
return Ok(());
};
let epoch1904 = book.epoch1904();
let mut sheet = book.read(&want)?;
let style = sheet.style();
while let Some(row) = sheet.read()? {
let mut line = format!("{:>6}|", row.number);
for cell in &row.cell {
let shown = match &cell.value {
Value::Empty => continue,
Value::Number(number) if style.is_date(cell.style) => {
let when = date::moment(*number, epoch1904);
format!(
"{}-{:02}-{:02} {:02}:{:02}:{:02}",
when.year, when.month, when.day, when.hour, when.minute, when.second
)
}
Value::Number(number) => format!("{number}"),
Value::Text(text) => text.replace('\n', "\\n"),
Value::Boolean(state) => format!("{state}"),
Value::Error(text) => text.clone(),
};
line.push_str(&format!(" {}={shown}", letter_of(cell.column)));
}
println!("{line}");
}
Ok(())
}