- 839
} - 840
_ => {} - 841
} - 842
Ok(()) - 843
})?; - 844
let shared = match package.related_part(&main, "sharedStrings")? { - 845
Some(part) => { - 846
let bytes = package.read_part(&part)?; - 847
shared_strings(&bytes, &part, &limits)? - 848
} - 849
None => Vec::new(), - 850
}; - 851
- 852
let mut units = Vec::new(); - 853
let mut sections = Vec::new(); - 854
let mut tables = Vec::new(); - 855
let mut total_rows = 0usize; - 856
let mut formulas = 0usize; - 857
let sheet_count = sheets.len(); - 858
for (name, state, relationship) in sheets { - 859
let quoted = quote_sheet(&name); - 860
let anchor = format!("{quoted}!"); - 861
let mut labels = Vec::new(); - 862
match state.as_deref() { - 863
Some("hidden") => labels.push("hidden sheet".to_string()), - 864
Some("veryHidden") => labels.push("very hidden sheet".to_string()), - 865
_ => {} - 866
} - 867
let start = units.len(); - 868
let Some(part) = package.part_by_relationship_id(&main, &relationship)? else { - 869
continue; - 870
}; - 871
let is_worksheet = package - 872
.content_type(&part) - 873
.is_some_and(|content_type| content_type.contains("worksheet")); - 874
if !is_worksheet { - 875
sections.push(Section { - 876
anchor, - 877
title: format!("{name} (chart or dialog sheet, not read)"), - 878
level: 1, - 879
units: start..start, - 880
}); - 881
continue; - 882
} - 883
let bytes = package.read_part(&part)?; - 884
let rows = sheet_rows( - 885
&bytes, - 886
&part, - 887
&limits, - 888
&shared, - 889
recalculate_on_open, - 890
&mut formulas, - 891
)?; - 892
let used: std::collections::BTreeSet<u32> = rows - 893
.iter() - 894
.flat_map(|row| row.cells.iter().map(|(column, _)| *column)) - 895
.collect(); - 896
let omitted_columns = used.len().saturating_sub(MAX_TABLE_COLUMNS); - 897
let shown: Vec<u32> = used.into_iter().take(MAX_TABLE_COLUMNS).collect(); - 898
let slot_of: HashMap<u32, usize> = shown - 899
.iter() - 900
.enumerate() - 901
.map(|(slot, column)| (*column, slot)) - 902
.collect(); - 903
let mut table_rows = vec![ - 904
std::iter::once(String::new()) - 905
.chain(shown.iter().copied().map(column_name)) - 906
.collect::<Vec<_>>(), - 907
]; - 908
for row in &rows { - 909
total_rows += 1; - 910
let mut dense = vec![String::new(); shown.len()]; - 911
for (column, value) in &row.cells { - 912
if let Some(slot) = slot_of.get(column) { - 913
dense[*slot] = value.clone(); - 914
} - 915
} - 916
let first = row.cells.first().map(|(column, _)| *column).unwrap_or(1); - 917
let last = row.cells.last().map(|(column, _)| *column).unwrap_or(1); - 918
let mut row_labels = labels.clone(); - 919
if row.hidden { - 920
row_labels.push("hidden row".into()); - 921
} - 922
units.push(Unit { - 923
anchor: format!( - 924
"{quoted}!{}{}:{}{}", - 925
column_name(first), - 926
row.number, - 927
column_name(last), - 928
row.number - 929
), - 930
kind: UnitKind::SheetRow, - 931
level: 0, - 932
text: row - 933
.cells - 934
.iter() - 935
.map(|(column, value)| { - 936
format!("{}{}: {value}", column_name(*column), row.number) - 937
}) - 938
.collect::<Vec<_>>() - 939
.join(" | "), - 940
labels: row_labels, - 941
cells: row - 942
.cells - 943
.iter() - 944
.map(|(column, value)| { - 945
( - 946
format!("{}{}", column_name(*column), row.number), - 947
value.clone(), - 948
) - 949
}) - 950
.collect(), - 951
row_cells: Vec::new(), - 952
}); - 953
table_rows.push( - 954
std::iter::once(row.number.to_string()) - 955
.chain(dense) - 956
.collect(), - 957
); - 958
} - 959
sections.push(Section { - 960
anchor: anchor.clone(), - 961
title: name.clone(), - 962
level: 1, - 963
units: start..units.len(), - 964
}); - 965
tables.push(Table { - 966
anchor, - 967
title: name, - 968
rows: table_rows, - 969
labels, - 970
omitted_columns, - 971
}); - 972
} - 973
document.stats = vec![ - 974
("sheets".into(), sheet_count), - 975
("rows".into(), total_rows), - 976
("formulas".into(), formulas), - 977
("defined names".into(), defined_names.len()), - 978
]; - 979
for (name, reference) in defined_names { - 980
units.push(Unit { - 981
anchor: name.clone(), - 982
kind: UnitKind::DefinedName, - 983
level: 0, - 984
text: format!("defined name {name} = {reference}"), - 985
labels: Vec::new(), - 986
cells: Vec::new(), - 987
row_cells: Vec::new(), - 988
}); - 989
} - 990
document.units = units; - 991
document.sections = sections; - 992
document.tables = tables; - 993
document.not_read = vec![ - 994
"number formats (values are shown as stored)", - 995
"charts, pivot tables and cell comments", - 996
"recalculation (a formula shows the value cached in the file, which may be stale)", - 997
]; - 998
Ok(()) - 999
} - 1000
- 1001
struct SheetRow { - 1002
number: u32, - 1003
hidden: bool, - 1004
cells: Vec<(u32, String)>, - 1005
} - 1006
- 1007
#[derive(Default)] - 1008
struct CellState { - 1009
column: u32, - 1010
kind: Option<String>, - 1011
value: String, - 1012
formula: Option<String>, - 1013
} - 1014
- 1015
fn sheet_rows( - 1016
bytes: &[u8], - 1017
part: &str, - 1018
limits: &Limits, - 1019
shared: &[String], - 1020
recalculate_on_open: bool, - 1021
formulas: &mut usize, - 1022
) -> Result<Vec<SheetRow>, Error> { - 1023
let mut rows: Vec<SheetRow> = Vec::new(); - 1024
let mut cell: Option<CellState> = None; - 1025
let mut in_value = false; - 1026
let mut in_formula = false; - 1027
let mut in_inline = false; - 1028
let mut next_row = 1u32; - 1029
let mut next_column = 1u32; - 1030
xml::walk(bytes, part, limits, |event| { - 1031
match event { - 1032
XmlEvent::Open(element) => match element.local() { - 1033
"row" => { - 1034
let number = element - 1035
.attr("r") - 1036
.and_then(|value| value.parse::<u32>().ok()) - 1037
.unwrap_or(next_row); - 1038
next_row = number.saturating_add(1); - 1039
next_column = 1; - 1040
rows.push(SheetRow { - 1041
number, - 1042
hidden: element - 1043
.attr("hidden") - 1044
.is_some_and(|value| value == "1" || value == "true"), - 1045
cells: Vec::new(), - 1046
}); - 1047
} - 1048
"c" => { - 1049
let column = element.attr("r").and_then(column_of).unwrap_or(next_column); - 1050
next_column = column.saturating_add(1); - 1051
cell = Some(CellState { - 1052
column, - 1053
kind: element.attr("t").map(str::to_string), - 1054
..CellState::default() - 1055
}); - 1056
} - 1057
"v" => in_value = true, - 1058
"f" => { - 1059
in_formula = true; - 1060
if let Some(cell) = cell.as_mut() { - 1061
cell.formula.get_or_insert_with(String::new); - 1062
} - 1063
} - 1064
"is" => in_inline = true, - 1065
_ => {} - 1066
}, - 1067
XmlEvent::Text(text) => { - 1068
if let Some(cell) = cell.as_mut() { - 1069
if in_formula { - 1070
if let Some(formula) = cell.formula.as_mut() { - 1071
formula.push_str(&text); - 1072
} - 1073
} else if in_value || in_inline { - 1074
cell.value.push_str(&text); - 1075
} - 1076
} - 1077
} - 1078
XmlEvent::Close(name) => match xml::local_name(&name) { - 1079
"v" => in_value = false, - 1080
"f" => in_formula = false, - 1081
"is" => in_inline = false, - 1082
"c" => { - 1083
if let Some(state) = cell.take() { - 1084
let shown = match state.kind.as_deref() { - 1085
Some("s") => state - 1086
.value - 1087
.trim() - 1088
.parse::<usize>() - 1089
.ok() - 1090
.and_then(|index| shared.get(index).cloned()) - 1091
.unwrap_or_default(), - 1092
Some("b") if state.value.trim() == "1" => "TRUE".into(), - 1093
Some("b") => "FALSE".into(), - 1094
Some("e") => format!("#ERROR {}", state.value.trim()), - 1095
_ => state.value.clone(), - 1096
}; - 1097
let rendered = match state.formula { - 1098
Some(formula) => { - 1099
*formulas += 1; - 1100
let cached = match (shown.is_empty(), recalculate_on_open) { - 1101
(true, _) => "[not calculated yet]".to_string(), - 1102
(false, true) => { - 1103
format!("[cached: {shown}, stale until recalculated]") - 1104
} - 1105
(false, false) => format!("[cached: {shown}]"), - 1106
}; - 1107
if formula.trim().is_empty() { - 1108
format!("(shared formula) {cached}") - 1109
} else { - 1110
format!("={} {cached}", formula.trim()) - 1111
} - 1112
} - 1113
None => shown, - 1114
}; - 1115
if !rendered.is_empty() - 1116
&& let Some(row) = rows.last_mut() - 1117
{ - 1118
row.cells.push((state.column, rendered)); - 1119
} - 1120
} - 1121
} - 1122
_ => {} - 1123
}, - 1124
} - 1125
Ok(()) - 1126
})?; - 1127
rows.retain(|row| !row.cells.is_empty()); - 1128
Ok(rows) - 1129
} - 1130
- 1131
fn shared_strings(bytes: &[u8], part: &str, limits: &Limits) -> Result<Vec<String>, Error> { - 1132
let mut strings = Vec::new(); - 1133
let mut current = String::new(); - 1134
let mut in_text = false; - 1135
let mut phonetic = 0usize; - 1136
xml::walk(bytes, part, limits, |event| { - 1137
match event { - 1138
XmlEvent::Open(element) => match element.local() { - 1139
"si" => current.clear(), - 1140
"rPh" => phonetic += 1, - 1141
"t" if phonetic == 0 => in_text = true, - 1142
_ => {} - 1143
}, - 1144
XmlEvent::Text(text) if in_text => current.push_str(&text), - 1145
XmlEvent::Close(name) => match xml::local_name(&name) { - 1146
"t" => in_text = false, - 1147
"rPh" => phonetic = phonetic.saturating_sub(1), - 1148
"si" => strings.push(std::mem::take(&mut current)), - 1149
_ => {} - 1150
}, - 1151
_ => {} - 1152
} - 1153
Ok(()) - 1154
})?; - 1155
Ok(strings) - 1156
} - 1157
- 1158
fn column_of(reference: &str) -> Option<u32> { - 1159
let letters: String = reference - 1160
.chars() - 1161
.take_while(|character| character.is_ascii_alphabetic()) - 1162
.collect(); - 1163
if letters.is_empty() || letters.len() > 3 { - 1164
return None; - 1165
} - 1166
letters.chars().try_fold(0u32, |total, character| { - 1167
Some(total * 26 + (character.to_ascii_uppercase() as u32 - 'A' as u32 + 1)) - 1168
}) - 1169
} - 1170
- 1171
/// `1` → `A`, `27` → `AA`. - 1172
pub fn column_name(mut column: u32) -> String { - 1173
let mut name = Vec::new(); - 1174
while column > 0 { - 1175
let remainder = (column - 1) % 26; - 1176
name.push(char::from(b'A' + remainder as u8)); - 1177
column = (column - 1) / 26; - 1178
} - 1179
name.iter().rev().collect() - 1180
} - 1181
- 1182
fn quote_sheet(name: &str) -> String { - 1183
if name - 1184
.chars() - 1185
.all(|character| character.is_alphanumeric() || character == '_' || character == '.') - 1186
{ - 1187
name.to_string() - 1188
} else { - 1189
format!("'{}'", name.replace('\'', "''")) - 1190
} - 1191
} - 1192
- 1193
// ---- PowerPoint ------------------------------------------------------------ - 1194
- 1195
#[derive(Default, Clone)] - 1196
struct Shape { - 1197
id: String, - 1198
name: String, - 1199
hidden: bool, - 1200
placeholder: Option<String>, - 1201
offset: Option<(i64, i64)>, - 1202
extent: Option<(i64, i64)>, - 1203
paragraphs: Vec<String>, - 1204
table: Vec<Vec<String>>, - 1205
} - 1206
- 1207
fn powerpoint<R: Read + Seek>( - 1208
package: &mut Package<R>, - 1209
document: &mut Document, - 1210
) -> Result<(), Error> { - 1211
let main = package.main_part().to_string(); - 1212
let limits = *package.limits(); - 1213
let bytes = package.read_part(&main)?; - 1214
let mut slide_ids: Vec<(String, String)> = Vec::new(); - 1215
let mut size: Option<(i64, i64)> = None; - 1216
xml::walk(&bytes, &main, &limits, |event| { - 1217
if let XmlEvent::Open(element) = event { - 1218
match element.local() { - 1219
"sldId" => { - 1220
if let (Some(id), Some(relationship)) = - 1221
(element.attr_unprefixed("id"), element.attr_prefixed("id")) - 1222
{ - 1223
slide_ids.push((id.to_string(), relationship.to_string())); - 1224
} - 1225
} - 1226
"sldSz" => { - 1227
size = element - 1228
.attr("cx") - 1229
.and_then(|cx| cx.parse().ok()) - 1230
.zip(element.attr("cy").and_then(|cy| cy.parse().ok())); - 1231
} - 1232
_ => {} - 1233
} - 1234
} - 1235
Ok(()) - 1236
})?; - 1237
- 1238
let mut units = Vec::new(); - 1239
let mut sections = Vec::new(); - 1240
let mut tables = Vec::new(); - 1241
let mut hidden_slides = 0usize; - 1242
let mut with_notes = 0usize; - 1243
for (index, (slide_id, relationship)) in slide_ids.iter().enumerate() { - 1244
let number = index + 1; - 1245
let Some(part) = package.part_by_relationship_id(&main, relationship)? else { - 1246
continue; - 1247
}; - 1248
let bytes = package.read_part(&part)?; - 1249
let (hidden, shapes) = slide_shapes(&bytes, &part, &limits)?; - 1250
let anchor = format!("slide:{slide_id}"); - 1251
let title = shapes - 1252
.iter() - 1253
.find(|shape| matches!(shape.placeholder.as_deref(), Some("title" | "ctrTitle"))) - 1254
.map(|shape| shape.paragraphs.join(" ")) - 1255
.filter(|title| !title.trim().is_empty()) - 1256
.unwrap_or_else(|| format!("Slide {number}")); - 1257
let start = units.len(); - 1258
let mut slide_labels = Vec::new(); - 1259
if hidden { - 1260
hidden_slides += 1; - 1261
slide_labels.push("hidden slide".to_string()); - 1262
} - 1263
units.push(Unit { - 1264
anchor: anchor.clone(), - 1265
kind: UnitKind::Slide, - 1266
level: 1, - 1267
text: format!("Slide {number}: {title}"), - 1268
labels: slide_labels.clone(), - 1269
cells: Vec::new(), - 1270
row_cells: Vec::new(), - 1271
}); - 1272
for shape in &shapes { - 1273
let mut labels = slide_labels.clone(); - 1274
if shape.hidden { - 1275
labels.push("hidden shape".into()); - 1276
} - 1277
if let (Some((x, y)), Some((width, height)), Some((slide_width, slide_height))) = - 1278
(shape.offset, shape.extent, size) - 1279
&& (x >= slide_width || y >= slide_height || x + width <= 0 || y + height <= 0) - 1280
{ - 1281
labels.push("off-slide, not visible when presented".into()); - 1282
} - 1283
let text = shape.paragraphs.join(PARAGRAPH_BREAK); - 1284
if !text.trim().is_empty() { - 1285
units.push(Unit { - 1286
anchor: format!("{anchor}/shape:{}", shape.id), - 1287
kind: UnitKind::Shape, - 1288
level: 0, - 1289
text, - 1290
labels: labels.clone(), - 1291
cells: Vec::new(), - 1292
row_cells: Vec::new(), - 1293
}); - 1294
} - 1295
if !shape.table.is_empty() { - 1296
tables.push(Table { - 1297
anchor: format!("{anchor}/shape:{}", shape.id), - 1298
title: format!("Slide {number} · {}", shape.name), - 1299
rows: shape.table.clone(), - 1300
labels, - 1301
omitted_columns: 0, - 1302
}); - 1303
} - 1304
} - 1305
if let Some(notes_part) = package.related_part(&part, "notesSlide")? { - 1306
let bytes = package.read_part(¬es_part)?; - 1307
let (_, note_shapes) = slide_shapes(&bytes, ¬es_part, &limits)?; - 1308
let notes: Vec<String> = note_shapes - 1309
.iter() - 1310
.filter(|shape| shape.placeholder.as_deref() == Some("body")) - 1311
.map(|shape| shape.paragraphs.join(PARAGRAPH_BREAK)) - 1312
.filter(|text| !text.trim().is_empty()) - 1313
.collect(); - 1314
if !notes.is_empty() { - 1315
with_notes += 1; - 1316
units.push(Unit { - 1317
anchor: format!("{anchor}/notes"), - 1318
kind: UnitKind::Notes, - 1319
level: 0, - 1320
text: notes.join(PARAGRAPH_BREAK), - 1321
labels: vec!["speaker notes".into()], - 1322
cells: Vec::new(), - 1323
row_cells: Vec::new(), - 1324
}); - 1325
} - 1326
} - 1327
sections.push(Section { - 1328
anchor, - 1329
title: format!("Slide {number}: {title}"), - 1330
level: 1, - 1331
units: start..units.len(), - 1332
}); - 1333
} - 1334
document.stats = vec![ - 1335
("slides".into(), slide_ids.len()), - 1336
("hidden slides".into(), hidden_slides), - 1337
("slides with notes".into(), with_notes), - 1338
("tables".into(), tables.len()), - 1339
]; - 1340
document.units = units; - 1341
document.sections = sections; - 1342
document.tables = tables; - 1343
document.not_read = vec![ - 1344
"comments", - 1345
"chart data and SmartArt text", - 1346
"text inherited from layouts and masters", - 1347
"off-slide checks for shapes inside groups", - 1348
]; - 1349
Ok(()) - 1350
} - 1351
- 1352
fn slide_shapes(bytes: &[u8], part: &str, limits: &Limits) -> Result<(bool, Vec<Shape>), Error> { - 1353
let mut hidden_slide = false; - 1354
let mut shapes: Vec<Shape> = Vec::new(); - 1355
let mut stack: Vec<Shape> = Vec::new(); - 1356
let mut group_depth = 0usize; - 1357
let mut paragraph: Option<String> = None; - 1358
let mut in_text = false; - 1359
let mut row: Option<Vec<String>> = None; - 1360
let mut cell: Option<String> = None; - 1361
xml::walk(bytes, part, limits, |event| { - 1362
match event { - 1363
XmlEvent::Open(element) => match element.local() { - 1364
"sld" => { - 1365
hidden_slide = element - 1366
.attr("show") - 1367
.is_some_and(|value| value == "0" || value == "false") - 1368
} - 1369
"grpSp" => group_depth += 1, - 1370
"sp" | "pic" | "graphicFrame" | "cxnSp" => stack.push(Shape::default()), - 1371
"cNvPr" => { - 1372
if let Some(shape) = stack.last_mut() - 1373
&& shape.id.is_empty() - 1374
{ - 1375
shape.id = element.attr("id").unwrap_or_default().to_string(); - 1376
shape.name = element.attr("name").unwrap_or_default().to_string(); - 1377
shape.hidden = element - 1378
.attr("hidden") - 1379
.is_some_and(|value| value == "1" || value == "true"); - 1380
} - 1381
} - 1382
"ph" => { - 1383
if let Some(shape) = stack.last_mut() { - 1384
shape.placeholder = - 1385
Some(element.attr("type").unwrap_or("body").to_string()); - 1386
} - 1387
} - 1388
"off" if group_depth == 0 => { - 1389
if let Some(shape) = stack.last_mut() - 1390
&& shape.offset.is_none() - 1391
{ - 1392
shape.offset = element - 1393
.attr("x") - 1394
.and_then(|x| x.parse().ok()) - 1395
.zip(element.attr("y").and_then(|y| y.parse().ok())); - 1396
} - 1397
} - 1398
"ext" if group_depth == 0 => { - 1399
if let Some(shape) = stack.last_mut() - 1400
&& shape.extent.is_none() - 1401
&& element.attr("cx").is_some() - 1402
{ - 1403
shape.extent = element - 1404
.attr("cx") - 1405
.and_then(|x| x.parse().ok()) - 1406
.zip(element.attr("cy").and_then(|y| y.parse().ok())); - 1407
} - 1408
} - 1409
"p" => paragraph = Some(String::new()), - 1410
"t" => in_text = true, - 1411
"br" => { - 1412
if let Some(paragraph) = paragraph.as_mut() { - 1413
paragraph.push(' '); - 1414
} - 1415
} - 1416
"tr" => row = Some(Vec::new()), - 1417
"tc" => cell = Some(String::new()), - 1418
_ => {} - 1419
}, - 1420
XmlEvent::Text(text) if in_text => { - 1421
if let Some(paragraph) = paragraph.as_mut() { - 1422
paragraph.push_str(&text); - 1423
} - 1424
} - 1425
XmlEvent::Close(name) => match xml::local_name(&name) { - 1426
"t" => in_text = false, - 1427
"p" => { - 1428
if let Some(text) = paragraph.take() { - 1429
let text = text.trim().to_string(); - 1430
if let Some(cell) = cell.as_mut() { - 1431
if !cell.is_empty() && !text.is_empty() { - 1432
cell.push(' '); - 1433
} - 1434
cell.push_str(&text); - 1435
} else if !text.is_empty() - 1436
&& let Some(shape) = stack.last_mut() - 1437
{ - 1438
shape.paragraphs.push(text); - 1439
} - 1440
} - 1441
} - 1442
"tc" => { - 1443
if let (Some(text), Some(row)) = (cell.take(), row.as_mut()) { - 1444
row.push(text); - 1445
} - 1446
} - 1447
"tr" => { - 1448
if let (Some(cells), Some(shape)) = (row.take(), stack.last_mut()) { - 1449
shape.table.push(cells); - 1450
} - 1451
} - 1452
"sp" | "pic" | "graphicFrame" | "cxnSp" => { - 1453
if let Some(shape) = stack.pop() { - 1454
shapes.push(shape); - 1455
} - 1456
} - 1457
"grpSp" => group_depth = group_depth.saturating_sub(1), - 1458
_ => {} - 1459
}, - 1460
_ => {} - 1461
} - 1462
Ok(()) - 1463
})?; - 1464
Ok((hidden_slide, shapes)) - 1465
} - 1466
- 1467
// ---- Visio ----------------------------------------------------------------- - 1468
- 1469
struct VisioPage { - 1470
id: String, - 1471
name: String, - 1472
background: bool, - 1473
relationship: Option<String>, - 1474
} - 1475
- 1476
fn visio<R: Read + Seek>(package: &mut Package<R>, document: &mut Document) -> Result<(), Error> { - 1477
let main = package.main_part().to_string(); - 1478
let limits = *package.limits(); - 1479
let Some(pages_part) = package.related_part(&main, "pages")? else { - 1480
document.not_read = vec!["pages (the drawing has no pages part)"]; - 1481
return Ok(()); - 1482
}; - 1483
let bytes = package.read_part(&pages_part)?; - 1484
let mut pages: Vec<VisioPage> = Vec::new(); - 1485
xml::walk(&bytes, &pages_part, &limits, |event| { - 1486
if let XmlEvent::Open(element) = event { - 1487
match element.local() { - 1488
"Page" => pages.push(VisioPage { - 1489
id: element.attr("ID").unwrap_or_default().to_string(), - 1490
name: element - 1491
.attr("Name") - 1492
.or_else(|| element.attr("NameU")) - 1493
.unwrap_or_default() - 1494
.to_string(), - 1495
background: element.attr("Background").is_some_and(|value| value == "1"), - 1496
relationship: None, - 1497
}), - 1498
"Rel" => { - 1499
if let (Some(page), Some(id)) = (pages.last_mut(), element.attr_prefixed("id")) - 1500
{ - 1501
page.relationship = Some(id.to_string()); - 1502
} - 1503
} - 1504
_ => {} - 1505
} - 1506
} - 1507
Ok(()) - 1508
})?; - 1509
let mut units = Vec::new(); - 1510
let mut sections = Vec::new(); - 1511
let mut shape_count = 0usize; - 1512
for page in pages { - 1513
let anchor = format!("page:{}", page.id); - 1514
let start = units.len(); - 1515
let labels = if page.background { - 1516
vec!["background page".to_string()] - 1517
} else { - 1518
Vec::new() - 1519
}; - 1520
units.push(Unit { - 1521
anchor: anchor.clone(), - 1522
kind: UnitKind::Page, - 1523
level: 1, - 1524
text: format!("Page: {}", page.name), - 1525
labels: labels.clone(), - 1526
cells: Vec::new(), - 1527
row_cells: Vec::new(), - 1528
}); - 1529
if let Some(relationship) = page.relationship - 1530
&& let Some(part) = package.part_by_relationship_id(&pages_part, &relationship)? - 1531
{ - 1532
let bytes = package.read_part(&part)?; - 1533
for (shape_id, shape_name, text) in visio_shapes(&bytes, &part, &limits)? { - 1534
shape_count += 1; - 1535
if text.is_empty() { - 1536
continue; - 1537
} - 1538
units.push(Unit { - 1539
anchor: format!("{anchor}/shape:{shape_id}"), - 1540
kind: UnitKind::Shape, - 1541
level: 0, - 1542
text: if shape_name.is_empty() { - 1543
text - 1544
} else { - 1545
format!("{shape_name}: {text}") - 1546
}, - 1547
labels: labels.clone(), - 1548
cells: Vec::new(), - 1549
row_cells: Vec::new(), - 1550
}); - 1551
} - 1552
} - 1553
sections.push(Section { - 1554
anchor, - 1555
title: page.name, - 1556
level: 1, - 1557
units: start..units.len(), - 1558
}); - 1559
} - 1560
document.stats = vec![ - 1561
("pages".into(), sections.len()), - 1562
("shapes".into(), shape_count), - 1563
]; - 1564
document.units = units; - 1565
document.sections = sections; - 1566
document.not_read = vec![ - 1567
"text and geometry inherited from masters", - 1568
"shape data (properties) and connectors", - 1569
]; - 1570
Ok(()) - 1571
} - 1572
- 1573
fn visio_shapes( - 1574
bytes: &[u8], - 1575
part: &str, - 1576
limits: &Limits, - 1577
) -> Result<Vec<(String, String, String)>, Error> { - 1578
let mut shapes = Vec::new(); - 1579
let mut stack: Vec<(String, String, String)> = Vec::new(); - 1580
let mut in_text = 0usize; - 1581
xml::walk(bytes, part, limits, |event| { - 1582
match event { - 1583
XmlEvent::Open(element) => match element.local() { - 1584
"Shape" => stack.push(( - 1585
element.attr("ID").unwrap_or_default().to_string(), - 1586
element - 1587
.attr("Name") - 1588
.or_else(|| element.attr("NameU")) - 1589
.unwrap_or_default() - 1590
.to_string(), - 1591
String::new(), - 1592
)), - 1593
"Text" => in_text += 1, - 1594
_ => {} - 1595
}, - 1596
XmlEvent::Text(text) if in_text > 0 => { - 1597
if let Some(shape) = stack.last_mut() { - 1598
shape.2.push_str(&text); - 1599
} - 1600
} - 1601
XmlEvent::Close(name) => match xml::local_name(&name) { - 1602
"Text" => in_text = in_text.saturating_sub(1), - 1603
"Shape" => { - 1604
if let Some((id, name, text)) = stack.pop() { - 1605
let text = text.split_whitespace().collect::<Vec<_>>().join(" "); - 1606
shapes.push((id, name, text)); - 1607
} - 1608
} - 1609
_ => {} - 1610
}, - 1611
_ => {} - 1612
} - 1613
Ok(()) - 1614
})?; - 1615
Ok(shapes) - 1616
} - 1617
- 1618
#[cfg(test)] - 1619
mod tests { - 1620
use super::*; - 1621
- 1622
#[test] - 1623
fn column_names_round_trip() { - 1624
for (column, name) in [(1, "A"), (26, "Z"), (27, "AA"), (52, "AZ"), (703, "AAA")] { - 1625
assert_eq!(column_name(column), name); - 1626
assert_eq!(column_of(&format!("{name}12")), Some(column)); - 1627
} - 1628
} - 1629
- 1630
#[test] - 1631
fn sheet_names_are_quoted_when_needed() { - 1632
assert_eq!(quote_sheet("Budget"), "Budget"); - 1633
assert_eq!(quote_sheet("Q1 plan"), "'Q1 plan'"); - 1634
assert_eq!(quote_sheet("Bob's"), "'Bob''s'"); - 1635
} - 1636
} - 1637
Indexing the workspace…
Vakyartha documentation is discovering safe artifacts, anchors, and source references.