- 1001
let mut alignment_attributes = element.attributes.clone(); - 1002
alignment_attributes.retain(|(key, _)| key != "wrapText"); - 1003
if wrap { - 1004
alignment_attributes.push(("wrapText".into(), "1".into())); - 1005
} - 1006
children.push_str(&start_tag(&element.name, &alignment_attributes, true)); - 1007
set_attribute(&mut attributes, "applyAlignment", "1".into()); - 1008
} - 1009
(Some(true), None) => { - 1010
children.push_str(&format!(r#"<{s}alignment wrapText="1"/>"#)); - 1011
set_attribute(&mut attributes, "applyAlignment", "1".into()); - 1012
} - 1013
(Some(false), None) => {} - 1014
(None, Some(alignment)) => children.push_str(&self.slice(alignment)), - 1015
(None, None) => {} - 1016
} - 1017
for child in self.tree.nodes[xf].children.clone() { - 1018
if self.tree.nodes[child].local() != "alignment" { - 1019
children.push_str(&self.slice(child)); - 1020
} - 1021
} - 1022
let name = self.tree.nodes[xf].element.name.clone(); - 1023
let xml = if children.is_empty() { - 1024
start_tag(&name, &attributes, true) - 1025
} else { - 1026
format!( - 1027
"{}{children}</{name}>", - 1028
start_tag(&name, &attributes, false) - 1029
) - 1030
}; - 1031
Ok(find_or_add( - 1032
self.xfs.iter().map(|xf| self.slice(*xf)).collect(), - 1033
&mut self.new_xfs, - 1034
xml, - 1035
)) - 1036
} - 1037
- 1038
fn write<R2: Read + Seek>(self, work: &mut Work<'_, R2>) -> Result<(), EditError> { - 1039
if self.new_fonts.is_empty() - 1040
&& self.new_fills.is_empty() - 1041
&& self.new_formats.is_empty() - 1042
&& self.new_xfs.is_empty() - 1043
{ - 1044
return Ok(()); - 1045
} - 1046
let s = &self.s; - 1047
let mut splice = Splice::default(); - 1048
let list = |local: &str| self.tree.children(0, local).next(); - 1049
extend_list( - 1050
&mut splice, - 1051
&self.tree, - 1052
list("fonts"), - 1053
&self.new_fonts, - 1054
self.fonts.len(), - 1055
)?; - 1056
extend_list( - 1057
&mut splice, - 1058
&self.tree, - 1059
list("fills"), - 1060
&self.new_fills, - 1061
self.fills.len(), - 1062
)?; - 1063
extend_list( - 1064
&mut splice, - 1065
&self.tree, - 1066
list("cellXfs"), - 1067
&self.new_xfs, - 1068
self.xfs.len(), - 1069
)?; - 1070
if !self.new_formats.is_empty() { - 1071
let items: Vec<String> = self - 1072
.new_formats - 1073
.iter() - 1074
.map(|(id, code)| { - 1075
format!( - 1076
r#"<{s}numFmt numFmtId="{id}" formatCode="{}"/>"#, - 1077
escape_attr(code) - 1078
) - 1079
}) - 1080
.collect(); - 1081
match list("numFmts") { - 1082
Some(formats) => extend_list( - 1083
&mut splice, - 1084
&self.tree, - 1085
Some(formats), - 1086
&items, - 1087
self.custom.len(), - 1088
)?, - 1089
None => { - 1090
let Some(fonts) = list("fonts") else { - 1091
return fail("the styles part has no fonts list"); - 1092
}; - 1093
splice.insert( - 1094
self.tree.nodes[fonts].span.start, - 1095
format!( - 1096
r#"<{s}numFmts count="{}">{}</{s}numFmts>"#, - 1097
items.len(), - 1098
items.concat() - 1099
), - 1100
); - 1101
} - 1102
} - 1103
} - 1104
work.put(&self.part, splice.apply(&self.bytes, &self.part)?); - 1105
Ok(()) - 1106
} - 1107
} - 1108
- 1109
/// The index of `xml` among `existing` then `added`, adding it when new. - 1110
fn find_or_add(existing: Vec<String>, added: &mut Vec<String>, xml: String) -> usize { - 1111
if let Some(index) = existing.iter().position(|known| *known == xml) { - 1112
return index; - 1113
} - 1114
match added.iter().position(|known| *known == xml) { - 1115
Some(index) => existing.len() + index, - 1116
None => { - 1117
added.push(xml); - 1118
existing.len() + added.len() - 1 - 1119
} - 1120
} - 1121
} - 1122
- 1123
/// Appends `items` to a styles list and sets its `count`. - 1124
fn extend_list( - 1125
splice: &mut Splice, - 1126
tree: &Tree, - 1127
list: Option<usize>, - 1128
items: &[String], - 1129
existing: usize, - 1130
) -> Result<(), EditError> { - 1131
if items.is_empty() { - 1132
return Ok(()); - 1133
} - 1134
let Some(list) = list else { - 1135
return fail("the styles part lacks a list this op extends"); - 1136
}; - 1137
let node = &tree.nodes[list]; - 1138
let mut attributes = node.element.attributes.clone(); - 1139
set_attribute( - 1140
&mut attributes, - 1141
"count", - 1142
(existing + items.len()).to_string(), - 1143
); - 1144
if node.is_empty_element() { - 1145
splice.replace( - 1146
node.span.clone(), - 1147
format!( - 1148
"{}{}</{}>", - 1149
start_tag(&node.element.name, &attributes, false), - 1150
items.concat(), - 1151
node.element.name - 1152
), - 1153
); - 1154
} else { - 1155
splice.replace( - 1156
node.span.start..node.inner.start, - 1157
start_tag(&node.element.name, &attributes, false), - 1158
); - 1159
splice.insert(node.inner.end, items.concat()); - 1160
} - 1161
Ok(()) - 1162
} - 1163
- 1164
pub(super) fn format_cells<R2: Read + Seek>( - 1165
work: &mut Work<'_, R2>, - 1166
sheet: &str, - 1167
range: &str, - 1168
format: &Format, - 1169
) -> Result<Outcome, EditError> { - 1170
if format.is_empty() { - 1171
return fail("give at least one of bold, italic, number_format, fill or wrap"); - 1172
} - 1173
let format = Format { - 1174
fill: format.fill.as_deref().map(fill_colour).transpose()?, - 1175
number_format: match format.number_format.as_deref().map(str::trim) { - 1176
Some(code) - 1177
if code.is_empty() - 1178
|| code.chars().count() > 255 - 1179
|| code.chars().any(char::is_control) => - 1180
{ - 1181
return fail(format!( - 1182
"number_format {code:?} is not an Excel format code such as \"#,##0.00\" or \"0%\"" - 1183
)); - 1184
} - 1185
code => code.map(str::to_string), - 1186
}, - 1187
..format.clone() - 1188
}; - 1189
let (first_column, first_row, last_column, last_row) = cell_range(range)?; - 1190
let count = u64::from(last_column - first_column + 1) * u64::from(last_row - first_row + 1); - 1191
if count > MAX_FORMAT_CELLS { - 1192
return fail(format!( - 1193
"{range} is {count} cells; format at most {MAX_FORMAT_CELLS} in one op" - 1194
)); - 1195
} - 1196
let (part, name) = sheet_part(work, sheet)?; - 1197
let bytes = work.get(&part)?; - 1198
let tree = Tree::parse(&bytes, &part, work.limits())?; - 1199
let s = prefix(&tree)?; - 1200
refuse_protected_sheet(&tree, &name)?; - 1201
let Some(data) = tree.descendants(0, "sheetData").next() else { - 1202
return fail(format!("sheet {name:?} has no sheetData")); - 1203
}; - 1204
let mut rows: BTreeMap<u32, usize> = BTreeMap::new(); - 1205
for row in tree.children(data, "row") { - 1206
let Some(number) = tree.nodes[row] - 1207
.element - 1208
.attr("r") - 1209
.and_then(|r| r.parse::<u32>().ok()) - 1210
else { - 1211
return fail(format!( - 1212
"sheet {name:?} has a row without a number, which this op does not edit" - 1213
)); - 1214
}; - 1215
rows.insert(number, row); - 1216
} - 1217
let mut row_cells: BTreeMap<u32, BTreeMap<u32, usize>> = BTreeMap::new(); - 1218
for (number, row) in rows.range(first_row..=last_row) { - 1219
let mut cells = BTreeMap::new(); - 1220
for cell in tree.children(*row, "c") { - 1221
let Some((column, _)) = tree.nodes[cell] - 1222
.element - 1223
.attr("r") - 1224
.and_then(|reference| address(reference).ok()) - 1225
else { - 1226
return fail(format!("row {number} has a cell without an address")); - 1227
}; - 1228
cells.insert(column, cell); - 1229
} - 1230
row_cells.insert(*number, cells); - 1231
} - 1232
let style_of = |row: u32, column: u32| -> usize { - 1233
row_cells - 1234
.get(&row) - 1235
.and_then(|cells| cells.get(&column)) - 1236
.and_then(|cell| tree.nodes[*cell].element.attr("s")) - 1237
.and_then(|style| style.parse().ok()) - 1238
.unwrap_or(0) - 1239
}; - 1240
let mut styles = Styles::load(work)?; - 1241
let mut derived: BTreeMap<usize, usize> = BTreeMap::new(); - 1242
for row in first_row..=last_row { - 1243
for column in first_column..=last_column { - 1244
let old = style_of(row, column); - 1245
if let std::collections::btree_map::Entry::Vacant(entry) = derived.entry(old) { - 1246
entry.insert(styles.derive(old, &format)?); - 1247
} - 1248
} - 1249
} - 1250
styles.write(work)?; - 1251
let new_style = - 1252
|row: u32, column: u32| derived.get(&style_of(row, column)).copied().unwrap_or(0); - 1253
let empty_cell = |row: u32, column: u32| { - 1254
format!( - 1255
r#"<{s}c r="{}{row}" s="{}"/>"#, - 1256
column_name(column), - 1257
new_style(row, column) - 1258
) - 1259
}; - 1260
let mut splice = Splice::default(); - 1261
let mut new_rows: Vec<(u32, String)> = Vec::new(); - 1262
for row in first_row..=last_row { - 1263
let Some(row_index) = rows.get(&row) else { - 1264
let cells: String = (first_column..=last_column) - 1265
.map(|column| empty_cell(row, column)) - 1266
.collect(); - 1267
new_rows.push((row, format!(r#"<{s}row r="{row}">{cells}</{s}row>"#))); - 1268
continue; - 1269
}; - 1270
let row_node = &tree.nodes[*row_index]; - 1271
let mut attributes = row_node.element.attributes.clone(); - 1272
attributes.retain(|(key, _)| key != "spans"); - 1273
if row_node.is_empty_element() { - 1274
let cells: String = (first_column..=last_column) - 1275
.map(|column| empty_cell(row, column)) - 1276
.collect(); - 1277
splice.replace( - 1278
row_node.span.clone(), - 1279
format!( - 1280
"{}{cells}</{}>", - 1281
start_tag(&row_node.element.name, &attributes, false), - 1282
row_node.element.name - 1283
), - 1284
); - 1285
continue; - 1286
} - 1287
let cells = row_cells.get(&row).cloned().unwrap_or_default(); - 1288
let mut inserted = false; - 1289
for column in first_column..=last_column { - 1290
match cells.get(&column) { - 1291
Some(cell) => { - 1292
let node = &tree.nodes[*cell]; - 1293
let mut cell_attributes = node.element.attributes.clone(); - 1294
set_attribute( - 1295
&mut cell_attributes, - 1296
"s", - 1297
new_style(row, column).to_string(), - 1298
); - 1299
let end = if node.is_empty_element() { - 1300
node.span.end - 1301
} else { - 1302
node.inner.start - 1303
}; - 1304
splice.replace( - 1305
node.span.start..end, - 1306
start_tag( - 1307
&node.element.name, - 1308
&cell_attributes, - 1309
node.is_empty_element(), - 1310
), - 1311
); - 1312
} - 1313
None => { - 1314
let at = cells - 1315
.range(column + 1..) - 1316
.next() - 1317
.map(|(_, cell)| tree.nodes[*cell].span.start) - 1318
.unwrap_or(row_node.inner.end); - 1319
splice.insert(at, empty_cell(row, column)); - 1320
inserted = true; - 1321
} - 1322
} - 1323
} - 1324
if inserted && attributes.len() != row_node.element.attributes.len() { - 1325
splice.replace( - 1326
row_node.span.start..row_node.inner.start, - 1327
start_tag(&row_node.element.name, &attributes, false), - 1328
); - 1329
} - 1330
} - 1331
let data_node = &tree.nodes[data]; - 1332
if data_node.is_empty_element() { - 1333
let inner: String = new_rows.iter().map(|(_, xml)| xml.as_str()).collect(); - 1334
splice.replace( - 1335
data_node.span.clone(), - 1336
format!("<{0}>{inner}</{0}>", data_node.element.name), - 1337
); - 1338
} else { - 1339
for (number, xml) in new_rows { - 1340
let at = rows - 1341
.range(number + 1..) - 1342
.next() - 1343
.map(|(_, row)| tree.nodes[*row].span.start) - 1344
.unwrap_or(data_node.inner.end); - 1345
splice.insert(at, xml); - 1346
} - 1347
} - 1348
if let Some((range, tag)) = - 1349
widened_dimension(&tree, &[(first_column, first_row), (last_column, last_row)]) - 1350
{ - 1351
splice.replace(range, tag); - 1352
} - 1353
work.put(&part, splice.apply(&bytes, &part)?); - 1354
let cells: Vec<String> = (first_row..=last_row) - 1355
.flat_map(|row| { - 1356
(first_column..=last_column).map(move |column| format!("{}{row}", column_name(column))) - 1357
}) - 1358
.collect(); - 1359
Ok(Outcome { - 1360
summary: format!( - 1361
"{} cell(s) on {name} formatted: {}", - 1362
cells.len(), - 1363
format.describe() - 1364
), - 1365
expect: vec![Expect::CellFormat { - 1366
sheet: name, - 1367
cells, - 1368
format, - 1369
}], - 1370
created: Vec::new(), - 1371
}) - 1372
} - 1373
- 1374
/// The sheet's `dimension` start tag widened to cover `cells`, when the - 1375
/// sheet has one. - 1376
fn widened_dimension( - 1377
tree: &Tree, - 1378
cells: &[(u32, u32)], - 1379
) -> Option<(std::ops::Range<usize>, String)> { - 1380
let dimension = tree.descendants(0, "dimension").next()?; - 1381
let node = &tree.nodes[dimension]; - 1382
let mut bounds: Vec<(u32, u32)> = node - 1383
.element - 1384
.attr("ref") - 1385
.map(|reference| { - 1386
reference - 1387
.split(':') - 1388
.filter_map(|end| address(end).ok()) - 1389
.collect() - 1390
}) - 1391
.unwrap_or_default(); - 1392
bounds.extend_from_slice(cells); - 1393
let (min_column, max_column) = ( - 1394
bounds.iter().map(|b| b.0).min().unwrap_or(1), - 1395
bounds.iter().map(|b| b.0).max().unwrap_or(1), - 1396
); - 1397
let (min_row, max_row) = ( - 1398
bounds.iter().map(|b| b.1).min().unwrap_or(1), - 1399
bounds.iter().map(|b| b.1).max().unwrap_or(1), - 1400
); - 1401
let mut attributes = node.element.attributes.clone(); - 1402
attributes.retain(|(key, _)| key != "ref"); - 1403
attributes.push(( - 1404
"ref".into(), - 1405
format!( - 1406
"{}{min_row}:{}{max_row}", - 1407
column_name(min_column), - 1408
column_name(max_column) - 1409
), - 1410
)); - 1411
let end = if node.is_empty_element() { - 1412
node.span.end - 1413
} else { - 1414
node.inner.start - 1415
}; - 1416
Some(( - 1417
node.span.start..end, - 1418
start_tag(&node.element.name, &attributes, node.is_empty_element()), - 1419
)) - 1420
} - 1421
- 1422
/// A `col` entry: first and last column, and its attributes. - 1423
type ColumnRange = (u32, u32, Vec<(String, String)>); - 1424
- 1425
pub(super) fn set_column_widths<R2: Read + Seek>( - 1426
work: &mut Work<'_, R2>, - 1427
sheet: &str, - 1428
widths: &BTreeMap<String, f64>, - 1429
) -> Result<Outcome, EditError> { - 1430
if widths.is_empty() { - 1431
return fail( - 1432
"widths is empty; give column letters and widths in characters, e.g. {\"A\": 30, \"B\": 12}", - 1433
); - 1434
} - 1435
let mut wanted: BTreeMap<u32, f64> = BTreeMap::new(); - 1436
for (letters, width) in widths { - 1437
let (column, _) = address(&format!( - 1438
"{}1", - 1439
letters.trim().trim_end_matches(char::is_numeric) - 1440
)) - 1441
.map_err(|_| EditError { - 1442
op: None, - 1443
message: format!("{letters:?} is not a column letter like A or BC"), - 1444
})?; - 1445
if !width.is_finite() || *width <= 0.0 || *width > 255.0 { - 1446
return fail(format!( - 1447
"column {letters}: {width} is not a width between 0 and 255 characters" - 1448
)); - 1449
} - 1450
wanted.insert(column, *width); - 1451
} - 1452
let (part, name) = sheet_part(work, sheet)?; - 1453
let bytes = work.get(&part)?; - 1454
let tree = Tree::parse(&bytes, &part, work.limits())?; - 1455
let s = prefix(&tree)?; - 1456
refuse_protected_sheet(&tree, &name)?; - 1457
let Some(data) = tree.descendants(0, "sheetData").next() else { - 1458
return fail(format!("sheet {name:?} has no sheetData")); - 1459
}; - 1460
let columns = tree.children(0, "cols").next(); - 1461
let mut ranges: Vec<ColumnRange> = Vec::new(); - 1462
if let Some(columns) = columns { - 1463
for column in tree.children(columns, "col") { - 1464
let element = &tree.nodes[column].element; - 1465
let (Some(min), Some(max)) = ( - 1466
element - 1467
.attr("min") - 1468
.and_then(|value| value.parse::<u32>().ok()), - 1469
element - 1470
.attr("max") - 1471
.and_then(|value| value.parse::<u32>().ok()), - 1472
) else { - 1473
return fail(format!( - 1474
"sheet {name:?} has a column entry without a range, which this op does not edit" - 1475
)); - 1476
}; - 1477
ranges.push((min, max, element.attributes.clone())); - 1478
} - 1479
} - 1480
for (column, width) in &wanted { - 1481
let mut next = Vec::with_capacity(ranges.len() + 2); - 1482
let mut covered = false; - 1483
for (min, max, attributes) in ranges { - 1484
if min <= *column && *column <= max { - 1485
covered = true; - 1486
if min < *column { - 1487
next.push((min, column - 1, attributes.clone())); - 1488
} - 1489
let mut single = attributes.clone(); - 1490
set_attribute(&mut single, "width", width.to_string()); - 1491
set_attribute(&mut single, "customWidth", "1".into()); - 1492
next.push((*column, *column, single)); - 1493
if *column < max { - 1494
next.push((column + 1, max, attributes)); - 1495
} - 1496
} else { - 1497
next.push((min, max, attributes)); - 1498
} - 1499
} - 1500
if !covered { - 1501
next.push(( - 1502
*column, - 1503
*column, - 1504
vec![ - 1505
("width".into(), width.to_string()), - 1506
("customWidth".into(), "1".into()), - 1507
], - 1508
)); - 1509
} - 1510
ranges = next; - 1511
} - 1512
ranges.sort_by_key(|(min, _, _)| *min); - 1513
let content: String = ranges - 1514
.into_iter() - 1515
.map(|(min, max, mut attributes)| { - 1516
attributes.retain(|(key, _)| key != "min" && key != "max"); - 1517
attributes.insert(0, ("max".into(), max.to_string())); - 1518
attributes.insert(0, ("min".into(), min.to_string())); - 1519
start_tag(&format!("{s}col"), &attributes, true) - 1520
}) - 1521
.collect(); - 1522
let element = format!("<{s}cols>{content}</{s}cols>"); - 1523
let mut splice = Splice::default(); - 1524
match columns { - 1525
Some(columns) => splice.replace(tree.nodes[columns].span.clone(), element), - 1526
None => splice.insert(tree.nodes[data].span.start, element), - 1527
} - 1528
work.put(&part, splice.apply(&bytes, &part)?); - 1529
let letters: BTreeMap<String, f64> = wanted - 1530
.iter() - 1531
.map(|(column, width)| (column_name(*column), *width)) - 1532
.collect(); - 1533
Ok(Outcome { - 1534
summary: format!( - 1535
"column widths set on {name}: {}", - 1536
letters - 1537
.iter() - 1538
.map(|(letter, width)| format!("{letter} {width}")) - 1539
.collect::<Vec<_>>() - 1540
.join(", ") - 1541
), - 1542
expect: vec![Expect::ColumnWidths { - 1543
sheet: name, - 1544
widths: letters, - 1545
}], - 1546
created: Vec::new(), - 1547
}) - 1548
} - 1549
- 1550
// ---- postconditions read from the written package ------------------------- - 1551
- 1552
/// The part of the sheet named `sheet` in a written workbook. - 1553
fn written_sheet( - 1554
package: &mut crate::Package<std::io::Cursor<Vec<u8>>>, - 1555
sheet: &str, - 1556
) -> Result<String, String> { - 1557
let main = package.main_part().to_string(); - 1558
let bytes = package - 1559
.read_part(&main) - 1560
.map_err(|error| error.to_string())?; - 1561
let tree = Tree::parse(&bytes, &main, package.limits()).map_err(|error| error.to_string())?; - 1562
let id = tree - 1563
.descendants(0, "sheet") - 1564
.find(|node| { - 1565
tree.nodes[*node] - 1566
.element - 1567
.attr("name") - 1568
.is_some_and(|name| name.eq_ignore_ascii_case(sheet)) - 1569
}) - 1570
.and_then(|node| { - 1571
tree.nodes[node] - 1572
.element - 1573
.attr_prefixed("id") - 1574
.map(str::to_string) - 1575
}) - 1576
.ok_or_else(|| format!("no sheet {sheet:?}"))?; - 1577
package - 1578
.part_by_relationship_id(&main, &id) - 1579
.map_err(|error| error.to_string())? - 1580
.ok_or_else(|| format!("sheet {sheet:?} points at no part")) - 1581
} - 1582
- 1583
pub(super) fn check_format( - 1584
package: &mut crate::Package<std::io::Cursor<Vec<u8>>>, - 1585
sheet: &str, - 1586
cells: &[String], - 1587
format: &Format, - 1588
) -> Result<(), String> { - 1589
let part = written_sheet(package, sheet)?; - 1590
let limits = *package.limits(); - 1591
let bytes = package - 1592
.read_part(&part) - 1593
.map_err(|error| error.to_string())?; - 1594
let tree = Tree::parse(&bytes, &part, &limits).map_err(|error| error.to_string())?; - 1595
let main = package.main_part().to_string(); - 1596
let styles_part = package - 1597
.related_part(&main, "styles") - 1598
.map_err(|error| error.to_string())? - 1599
.ok_or("the workbook has no styles part")?; - 1600
let styles_bytes = package - 1601
.read_part(&styles_part) - 1602
.map_err(|error| error.to_string())?; - 1603
let styles = - 1604
Tree::parse(&styles_bytes, &styles_part, &limits).map_err(|error| error.to_string())?; - 1605
let list = |local: &str, item: &str| -> Vec<usize> { - 1606
styles - 1607
.children(0, local) - 1608
.next() - 1609
.map(|list| styles.children(list, item).collect()) - 1610
.unwrap_or_default() - 1611
}; - 1612
let (fonts, fills, xfs) = ( - 1613
list("fonts", "font"), - 1614
list("fills", "fill"), - 1615
list("cellXfs", "xf"), - 1616
); - 1617
let custom: BTreeMap<u32, String> = list("numFmts", "numFmt") - 1618
.into_iter() - 1619
.filter_map(|node| { - 1620
let element = &styles.nodes[node].element; - 1621
Some(( - 1622
element.attr("numFmtId")?.parse().ok()?, - 1623
element.attr("formatCode")?.to_string(), - 1624
)) - 1625
}) - 1626
.collect(); - 1627
let style_of: std::collections::HashMap<String, usize> = tree - 1628
.descendants(0, "c") - 1629
.filter_map(|cell| { - 1630
let element = &tree.nodes[cell].element; - 1631
Some(( - 1632
element.attr("r")?.to_ascii_uppercase(), - 1633
element.attr("s").and_then(|s| s.parse().ok()).unwrap_or(0), - 1634
)) - 1635
}) - 1636
.collect(); - 1637
let on = |node: usize, local: &str| { - 1638
styles.children(node, local).next().is_some_and(|child| { - 1639
!matches!(styles.nodes[child].element.attr("val"), Some("0" | "false")) - 1640
}) - 1641
}; - 1642
let number = |node: usize, attribute: &str| -> usize { - 1643
styles.nodes[node] - 1644
.element - 1645
.attr(attribute) - 1646
.and_then(|value| value.parse().ok()) - 1647
.unwrap_or(0) - 1648
}; - 1649
for cell in cells { - 1650
let style = *style_of - 1651
.get(&cell.to_ascii_uppercase()) - 1652
.ok_or_else(|| format!("{cell} is missing"))?; - 1653
let xf = *xfs - 1654
.get(style) - 1655
.ok_or_else(|| format!("{cell} names cell format {style}, which does not exist"))?; - 1656
if format.bold.is_some() || format.italic.is_some() { - 1657
let font = *fonts - 1658
.get(number(xf, "fontId")) - 1659
.ok_or_else(|| format!("{cell}'s font does not exist"))?; - 1660
if let Some(bold) = format.bold - 1661
&& on(font, "b") != bold - 1662
{ - 1663
return Err(format!( - 1664
"{cell} is not {}bold", - 1665
if bold { "" } else { "un" } - 1666
)); - 1667
} - 1668
if let Some(italic) = format.italic - 1669
&& on(font, "i") != italic - 1670
{ - 1671
return Err(format!( - 1672
"{cell} is not {}italic", - 1673
if italic { "" } else { "non-" } - 1674
)); - 1675
} - 1676
} - 1677
if let Some(code) = &format.number_format { - 1678
let id = u32::try_from(number(xf, "numFmtId")).unwrap_or(0); - 1679
let actual = BUILTIN_FORMATS - 1680
.iter() - 1681
.find(|(known, _)| *known == id) - 1682
.map(|(_, code)| code.to_string()) - 1683
.or_else(|| custom.get(&id).cloned()); - 1684
if actual.as_deref() != Some(code.as_str()) { - 1685
return Err(format!("{cell} has number format {actual:?}, not {code:?}")); - 1686
} - 1687
} - 1688
if let Some(fill) = &format.fill { - 1689
let colour = fills - 1690
.get(number(xf, "fillId")) - 1691
.and_then(|node| styles.descendants(*node, "fgColor").next()) - 1692
.and_then(|node| styles.nodes[node].element.attr("rgb")) - 1693
.map(str::to_ascii_uppercase); - 1694
if colour.as_deref() != Some(format!("FF{fill}").as_str()) { - 1695
return Err(format!("{cell} is filled {colour:?}, not #{fill}")); - 1696
} - 1697
} - 1698
if let Some(wrap) = format.wrap { - 1699
let wrapped = styles - 1700
.children(xf, "alignment") - 1701
.next() - 1702
.and_then(|node| styles.nodes[node].element.attr("wrapText")) - 1703
.is_some_and(|value| value == "1" || value == "true"); - 1704
if wrapped != wrap { - 1705
return Err(format!( - 1706
"{cell} is {}wrapped", - 1707
if wrapped { "" } else { "not " } - 1708
)); - 1709
} - 1710
} - 1711
} - 1712
Ok(()) - 1713
} - 1714
- 1715
pub(super) fn check_widths( - 1716
package: &mut crate::Package<std::io::Cursor<Vec<u8>>>, - 1717
sheet: &str, - 1718
widths: &BTreeMap<String, f64>, - 1719
) -> Result<(), String> { - 1720
let part = written_sheet(package, sheet)?; - 1721
let limits = *package.limits(); - 1722
let bytes = package - 1723
.read_part(&part) - 1724
.map_err(|error| error.to_string())?; - 1725
let tree = Tree::parse(&bytes, &part, &limits).map_err(|error| error.to_string())?; - 1726
let columns: Vec<(u32, u32, Option<f64>)> = tree - 1727
.descendants(0, "col") - 1728
.filter_map(|node| { - 1729
let element = &tree.nodes[node].element; - 1730
Some(( - 1731
element.attr("min")?.parse().ok()?, - 1732
element.attr("max")?.parse().ok()?, - 1733
element.attr("width").and_then(|width| width.parse().ok()), - 1734
)) - 1735
}) - 1736
.collect(); - 1737
for (letter, want) in widths { - 1738
let (column, _) = address(&format!("{letter}1")).map_err(|error| error.message)?; - 1739
let width = columns - 1740
.iter() - 1741
.find(|(min, max, _)| *min <= column && column <= *max) - 1742
.and_then(|(_, _, width)| *width) - 1743
.ok_or_else(|| format!("column {letter} has no width"))?; - 1744
if (width - want).abs() > 0.001 { - 1745
return Err(format!("column {letter} is {width} wide, not {want}")); - 1746
} - 1747
} - 1748
Ok(()) - 1749
} - 1750
- 1751
#[cfg(test)] - 1752
mod tests { - 1753
use super::names_sheet; - 1754
- 1755
#[test] - 1756
fn a_sheet_is_named_by_references_never_by_namespace_prefixes() { - 1757
assert!(names_sheet("<f>Sheet1!A1*2</f>", "Sheet1")); - 1758
assert!( - 1759
names_sheet("<f>SUM(sheet1!A1:A3)</f>", "Sheet1"), - 1760
"case-insensitive" - 1761
); - 1762
assert!(names_sheet("<f>'My sheet'!B2</f>", "My sheet")); - 1763
assert!(names_sheet("<f>'My sheet'!B2</f>", "My sheet")); - 1764
assert!(names_sheet("<f>SUM(Sheet1:Sheet3!A1)</f>", "Sheet1")); - 1765
assert!(names_sheet("<f>SUM(Sheet1:Sheet3!A1)</f>", "Sheet3")); - 1766
assert!(names_sheet( - 1767
r#"<worksheetSource sheet="Data" ref="A1:B4"/>"#, - 1768
"Data" - 1769
)); - 1770
assert!(!names_sheet("<f>MySheet1!A1</f>", "Sheet1")); - 1771
assert!( - 1772
!names_sheet(r#"<sheet name="r" r:id="rId1"/>"#, "r"), - 1773
"a namespace prefix is not a reference" - 1774
); - 1775
assert!(!names_sheet("<t>Sheet1</t>", "Sheet1")); - 1776
} - 1777
} - 1778
Indexing the workspace…
Vakyartha documentation is discovering safe artifacts, anchors, and source references.