- 656
work.remove(part); - 657
work.remove(&rels_part_name(part)); - 658
work.remove_override(part)?; - 659
} - 660
for origin in &origins { - 661
work.remove(&origin.target); - 662
work.remove(&rels_part_name(&origin.target)); - 663
work.remove_override(&origin.target)?; - 664
work.remove_relationship("", &origin.id)?; - 665
} - 666
Ok(signatures.len().max(usize::from(!origins.is_empty()))) - 667
} - 668
- 669
/// The written package, opened and its main part read when a check needs - 670
/// them. - 671
struct Written<'a> { - 672
bytes: &'a [u8], - 673
limits: Limits, - 674
package: Option<Package<Cursor<Vec<u8>>>>, - 675
main: Option<(String, Vec<u8>)>, - 676
} - 677
- 678
impl Written<'_> { - 679
fn package(&mut self) -> Result<&mut Package<Cursor<Vec<u8>>>, String> { - 680
if self.package.is_none() { - 681
let package = Package::open(Cursor::new(self.bytes.to_vec()), self.limits) - 682
.map_err(|error| error.to_string())?; - 683
self.package = Some(package); - 684
} - 685
self.package - 686
.as_mut() - 687
.ok_or_else(|| "the written package could not be opened".to_string()) - 688
} - 689
- 690
fn main(&mut self) -> Result<(&str, &[u8]), String> { - 691
if self.main.is_none() { - 692
let package = self.package()?; - 693
let name = package.main_part().to_string(); - 694
let bytes = package - 695
.read_part(&name) - 696
.map_err(|error| error.to_string())?; - 697
self.main = Some((name, bytes)); - 698
} - 699
match &self.main { - 700
Some((name, bytes)) => Ok((name.as_str(), bytes.as_slice())), - 701
None => Err("the main part could not be read".into()), - 702
} - 703
} - 704
} - 705
- 706
/// Word paragraphs in a main part, as anchors count them. - 707
fn count_paragraphs(bytes: &[u8], part: &str, limits: &Limits) -> Result<usize, EditError> { - 708
Ok(Tree::parse(bytes, part, limits)? - 709
.descendants(0, "p") - 710
.count()) - 711
} - 712
- 713
/// `p@N` → N. - 714
fn ordinal(anchor: &str) -> Option<usize> { - 715
anchor.strip_prefix("p@")?.split('/').next()?.parse().ok() - 716
} - 717
- 718
/// The Word paragraphs an op names. - 719
fn paragraph_references(op: &OfficeOp) -> Vec<&str> { - 720
match op { - 721
OfficeOp::ReplaceParagraphText { anchor, .. } | OfficeOp::DeleteParagraph { anchor } => { - 722
vec![anchor.as_str()] - 723
} - 724
OfficeOp::AddParagraph { after, .. } | OfficeOp::AddTable { after, .. } => { - 725
after.as_deref().into_iter().collect() - 726
} - 727
_ => Vec::new(), - 728
} - 729
} - 730
- 731
/// Whether op `later` names a paragraph that removing the paragraph op - 732
/// `earlier` deletes renumbers. In a new document (written clean) a - 733
/// deleted paragraph is removed, so the `p@` paragraphs after a removed - 734
/// `p@N` move up one; `p:` anchors never move. - 735
pub fn renumbered_by(earlier: &OfficeOp, later: &OfficeOp, context: &EditContext) -> bool { - 736
let OfficeOp::DeleteParagraph { anchor } = earlier else { - 737
return false; - 738
}; - 739
let Some(removed) = ordinal(anchor) else { - 740
return false; - 741
}; - 742
!context.tracked - 743
&& paragraph_references(later) - 744
.iter() - 745
.filter_map(|reference| ordinal(reference)) - 746
.any(|named| named >= removed) - 747
} - 748
- 749
/// Refuses ops written against one read that removing a `p@` paragraph - 750
/// earlier in the same call would renumber (see [`renumbered_by`]): they - 751
/// would land on the wrong paragraph. Checked where one call's ops are - 752
/// applied, never on a replay of several calls, whose later ops were - 753
/// written against the renumbered draft. - 754
pub fn check_renumbering(ops: &[OfficeOp], context: &EditContext) -> Result<(), EditError> { - 755
for (earlier, op) in ops.iter().enumerate() { - 756
if let Some(later) = - 757
(earlier + 1..ops.len()).find(|later| renumbered_by(op, &ops[*later], context)) - 758
{ - 759
return Err(EditError { - 760
op: Some((later, ops[later].name())), - 761
message: format!( - 762
"it names a paragraph that removing {} (op {}) renumbers in this new document; put paragraph deletions after the ops that name later paragraphs, or read the draft again and use its anchors", - 763
paragraph_references(op) - 764
.first() - 765
.copied() - 766
.unwrap_or_default(), - 767
earlier + 1 - 768
), - 769
}); - 770
} - 771
} - 772
Ok(()) - 773
} - 774
- 775
fn check(document: &Document, written: &mut Written<'_>, expect: &Expect) -> Result<(), String> { - 776
// A paragraph in a Word table cell is not a unit of its own: it is read - 777
// as part of its row, under its own anchor. - 778
let unit = |anchor: &str| -> Option<&str> { - 779
document - 780
.units - 781
.iter() - 782
.find(|unit| unit.anchor == anchor) - 783
.map(|unit| unit.text.as_str()) - 784
.or_else(|| { - 785
document - 786
.units - 787
.iter() - 788
.flat_map(|unit| unit.row_cells.iter().flatten()) - 789
.find(|(paragraph, _)| paragraph == anchor) - 790
.map(|(_, text)| text.as_str()) - 791
}) - 792
}; - 793
match expect { - 794
Expect::UnitContains { anchor, needles } => { - 795
let text = unit(anchor).ok_or_else(|| format!("{anchor} is missing"))?; - 796
match needles - 797
.iter() - 798
.find(|needle| !text.contains(needle.as_str())) - 799
{ - 800
Some(missing) => Err(format!("{anchor} does not contain {missing:?}")), - 801
None => Ok(()), - 802
} - 803
} - 804
Expect::UnitDeleted { anchor } => match unit(anchor) { - 805
None => Ok(()), - 806
Some(text) => { - 807
let mut rest = text; - 808
while let Some(start) = rest.find("[deleted by ") { - 809
if !rest[..start].trim().is_empty() { - 810
return Err(format!("{anchor} still has undeleted text")); - 811
} - 812
let Some(end) = rest[start..].find(']') else { - 813
break; - 814
}; - 815
rest = &rest[start + end + 1..]; - 816
} - 817
if rest.trim().is_empty() { - 818
Ok(()) - 819
} else { - 820
Err(format!("{anchor} still has undeleted text")) - 821
} - 822
} - 823
}, - 824
Expect::AnyUnitContains { prefix, needle } => { - 825
if document.units.iter().any(|unit| { - 826
unit.anchor.starts_with(prefix.as_str()) && unit.text.contains(needle.as_str()) - 827
}) { - 828
Ok(()) - 829
} else { - 830
Err(format!("no {prefix}… unit contains {needle:?}")) - 831
} - 832
} - 833
Expect::Absent { anchor } => match unit(anchor) { - 834
None => Ok(()), - 835
Some(_) => Err(format!("{anchor} is still present")), - 836
}, - 837
Expect::SlideOrder(order) => { - 838
let actual: Vec<&str> = document - 839
.sections - 840
.iter() - 841
.map(|section| section.anchor.as_str()) - 842
.collect(); - 843
if actual == order.iter().map(String::as_str).collect::<Vec<_>>() { - 844
Ok(()) - 845
} else { - 846
Err(format!("slide order is {actual:?}, expected {order:?}")) - 847
} - 848
} - 849
Expect::Section(anchor) => { - 850
if document - 851
.sections - 852
.iter() - 853
.any(|section| section.anchor == *anchor) - 854
{ - 855
Ok(()) - 856
} else { - 857
Err(format!("no section {anchor}")) - 858
} - 859
} - 860
Expect::NoSection(anchor) => { - 861
if document - 862
.sections - 863
.iter() - 864
.any(|section| section.anchor == *anchor) - 865
{ - 866
Err(format!("section {anchor} is still present")) - 867
} else { - 868
Ok(()) - 869
} - 870
} - 871
Expect::Title(title) => { - 872
if document.title.as_deref() == Some(title.as_str()) { - 873
Ok(()) - 874
} else { - 875
Err(format!("title is {:?}", document.title)) - 876
} - 877
} - 878
Expect::CellFormat { - 879
sheet, - 880
cells, - 881
format, - 882
} => sheet::check_format(written.package()?, sheet, cells, format), - 883
Expect::ColumnWidths { sheet, widths } => { - 884
sheet::check_widths(written.package()?, sheet, widths) - 885
} - 886
Expect::ParagraphDelta(_) => Ok(()), - 887
Expect::Paragraph { - 888
anchor, - 889
author, - 890
tracked, - 891
accepted, - 892
rejected, - 893
fixed, - 894
} => { - 895
let limits = written.limits; - 896
let (name, bytes) = written.main()?; - 897
let views = word::paragraph_views(name, bytes, &limits, anchor, author, *tracked)?; - 898
if textdiff::fold_text(&views.accepted) != *accepted { - 899
return Err(format!( - 900
"{anchor} reads {:?} with the changes accepted, not the text asked for", - 901
views.accepted - 902
)); - 903
} - 904
if !*tracked && textdiff::fold_text(&views.rejected) != *accepted { - 905
return Err(format!( - 906
"{anchor} was to be written clean, and reads {:?} with its changes rejected", - 907
views.rejected - 908
)); - 909
} - 910
if *tracked && views.rejected != *rejected { - 911
return Err(format!( - 912
"{anchor} reads {:?} with the changes rejected, not what it read before", - 913
views.rejected - 914
)); - 915
} - 916
if views.fixed != *fixed { - 917
return Err(format!( - 918
"{anchor} no longer holds the content the edit had to keep ({} before, {} after)", - 919
fixed.join(", "), - 920
views.fixed.join(", ") - 921
)); - 922
} - 923
Ok(()) - 924
} - 925
} - 926
} - 927
- 928
fn set_title<R: std::io::Read + std::io::Seek>( - 929
work: &mut Work<'_, R>, - 930
title: &str, - 931
) -> Result<Outcome, EditError> { - 932
let Some(part) = work.related("", "core-properties")? else { - 933
return fail("this file has no core properties part to hold a title"); - 934
}; - 935
let bytes = work.get(&part)?; - 936
let tree = Tree::parse(&bytes, &part, work.limits())?; - 937
let text = crate::splice::escape_text(title); - 938
let mut splice = Splice::default(); - 939
match tree.descendants(0, "title").next() { - 940
Some(node) => { - 941
let node = &tree.nodes[node]; - 942
if node.is_empty_element() { - 943
let name = node.element.name.clone(); - 944
splice.replace(node.span.clone(), format!("<{name}>{text}</{name}>")); - 945
} else { - 946
splice.replace(node.inner.clone(), text); - 947
} - 948
} - 949
None => { - 950
let Some(prefix) = tree.prefix_for("http://purl.org/dc/elements/1.1/") else { - 951
return fail("the core properties part does not declare the Dublin Core namespace"); - 952
}; - 953
splice.insert( - 954
tree.root().inner.end, - 955
format!("<{prefix}title>{text}</{prefix}title>"), - 956
); - 957
} - 958
} - 959
work.put(&part, splice.apply(&bytes, &part)?); - 960
Ok(Outcome { - 961
summary: format!("title set to {title:?}"), - 962
expect: vec![Expect::Title(title.to_string())], - 963
created: Vec::new(), - 964
}) - 965
} - 966
- 967
/// Parts as the ops leave them. `None` means removed. - 968
pub(crate) struct Work<'a, R: std::io::Read + std::io::Seek> { - 969
package: &'a mut Package<R>, - 970
parts: BTreeMap<String, Option<Vec<u8>>>, - 971
} - 972
- 973
impl<'a, R: std::io::Read + std::io::Seek> Work<'a, R> { - 974
fn new(package: &'a mut Package<R>) -> Self { - 975
Self { - 976
package, - 977
parts: BTreeMap::new(), - 978
} - 979
} - 980
- 981
pub fn limits(&self) -> &Limits { - 982
self.package.limits() - 983
} - 984
- 985
pub fn main_part(&self) -> String { - 986
self.package.main_part().to_string() - 987
} - 988
- 989
pub fn strict(&self) -> bool { - 990
self.package.conformance() == crate::Conformance::Strict - 991
} - 992
- 993
fn entry(&self, name: &str) -> Option<&Option<Vec<u8>>> { - 994
let key = part_key(name); - 995
self.parts - 996
.iter() - 997
.find(|(existing, _)| part_key(existing) == key) - 998
.map(|(_, value)| value) - 999
} - 1000
- 1001
pub fn exists(&self, name: &str) -> bool { - 1002
match self.entry(name) { - 1003
Some(value) => value.is_some(), - 1004
None => self.package.has_part(name), - 1005
} - 1006
} - 1007
- 1008
pub fn get(&mut self, name: &str) -> Result<Vec<u8>, EditError> { - 1009
match self.entry(name) { - 1010
Some(Some(bytes)) => Ok(bytes.clone()), - 1011
Some(None) => fail(format!("part {name} was removed by an earlier op")), - 1012
None => Ok(self.package.read_part(name)?), - 1013
} - 1014
} - 1015
- 1016
pub fn put(&mut self, name: &str, bytes: Vec<u8>) { - 1017
let key = part_key(name); - 1018
self.parts.retain(|existing, _| part_key(existing) != key); - 1019
self.parts - 1020
.insert(name.trim_start_matches('/').to_string(), Some(bytes)); - 1021
} - 1022
- 1023
pub fn remove(&mut self, name: &str) { - 1024
let key = part_key(name); - 1025
self.parts.retain(|existing, _| part_key(existing) != key); - 1026
self.parts - 1027
.insert(name.trim_start_matches('/').to_string(), None); - 1028
} - 1029
- 1030
/// Current relationships of `source`, read from its relationships part - 1031
/// as the ops have left it. - 1032
pub fn relationships(&mut self, source: &str) -> Result<Vec<Relationship>, EditError> { - 1033
let rels = rels_part_name(source); - 1034
if !self.exists(&rels) { - 1035
return Ok(Vec::new()); - 1036
} - 1037
let bytes = self.get(&rels)?; - 1038
Ok(parse_relationships(&bytes, &rels, source, self.limits())?) - 1039
} - 1040
- 1041
pub fn related(&mut self, source: &str, short_kind: &str) -> Result<Option<String>, EditError> { - 1042
Ok(self - 1043
.relationships(source)? - 1044
.into_iter() - 1045
.find(|relationship| !relationship.external && relationship.short_kind() == short_kind) - 1046
.map(|relationship| relationship.target)) - 1047
} - 1048
- 1049
pub fn by_id(&mut self, source: &str, id: &str) -> Result<Option<String>, EditError> { - 1050
Ok(self - 1051
.relationships(source)? - 1052
.into_iter() - 1053
.find(|relationship| !relationship.external && relationship.id == id) - 1054
.map(|relationship| relationship.target)) - 1055
} - 1056
- 1057
/// Adds a relationship from `source` to the part `target` and returns - 1058
/// its new id. - 1059
pub fn add_relationship( - 1060
&mut self, - 1061
source: &str, - 1062
kind: &str, - 1063
target: &str, - 1064
) -> Result<String, EditError> { - 1065
let rels = rels_part_name(source); - 1066
let existing = self.relationships(source)?; - 1067
let mut number = existing.len() + 1; - 1068
let id = loop { - 1069
let candidate = format!("rId{number}"); - 1070
if !existing - 1071
.iter() - 1072
.any(|relationship| relationship.id == candidate) - 1073
{ - 1074
break candidate; - 1075
} - 1076
number += 1; - 1077
}; - 1078
let line = format!( - 1079
r#"<Relationship Id="{id}" Type="{}" Target="{}"/>"#, - 1080
escape_attr(kind), - 1081
escape_attr(&relative_target(source, target)) - 1082
); - 1083
if self.exists(&rels) { - 1084
let bytes = self.get(&rels)?; - 1085
let tree = Tree::parse(&bytes, &rels, self.limits())?; - 1086
let mut splice = Splice::default(); - 1087
let root = tree.root(); - 1088
if root.is_empty_element() { - 1089
let name = root.element.name.clone(); - 1090
let open = String::from_utf8_lossy(&bytes[root.span.start..root.span.end - 2]) - 1091
.into_owned(); - 1092
splice.replace(root.span.clone(), format!("{open}>{line}</{name}>")); - 1093
} else { - 1094
splice.insert(root.inner.end, line); - 1095
} - 1096
self.put(&rels, splice.apply(&bytes, &rels)?); - 1097
} else { - 1098
self.put( - 1099
&rels, - 1100
format!( - 1101
r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?> - 1102
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">{line}</Relationships>"# - 1103
) - 1104
.into_bytes(), - 1105
); - 1106
self.add_default_rels_type()?; - 1107
} - 1108
Ok(id) - 1109
} - 1110
- 1111
pub fn remove_relationship(&mut self, source: &str, id: &str) -> Result<(), EditError> { - 1112
let rels = rels_part_name(source); - 1113
if !self.exists(&rels) { - 1114
return Ok(()); - 1115
} - 1116
let bytes = self.get(&rels)?; - 1117
let tree = Tree::parse(&bytes, &rels, self.limits())?; - 1118
let mut splice = Splice::default(); - 1119
for index in tree.descendants(0, "Relationship") { - 1120
if tree.nodes[index].element.attr("Id") == Some(id) { - 1121
splice.replace(tree.nodes[index].span.clone(), ""); - 1122
} - 1123
} - 1124
self.put(&rels, splice.apply(&bytes, &rels)?); - 1125
Ok(()) - 1126
} - 1127
- 1128
/// Adds or replaces the content-type override for `part`. - 1129
pub fn set_override(&mut self, part: &str, content_type: &str) -> Result<(), EditError> { - 1130
self.remove_override(part)?; - 1131
let name = "[Content_Types].xml"; - 1132
let bytes = self.get(name)?; - 1133
let tree = Tree::parse(&bytes, name, self.limits())?; - 1134
let mut splice = Splice::default(); - 1135
splice.insert( - 1136
tree.root().inner.end, - 1137
format!( - 1138
r#"<Override PartName="/{}" ContentType="{}"/>"#, - 1139
escape_attr(part.trim_start_matches('/')), - 1140
escape_attr(content_type) - 1141
), - 1142
); - 1143
self.put(name, splice.apply(&bytes, name)?); - 1144
Ok(()) - 1145
} - 1146
- 1147
pub fn remove_override(&mut self, part: &str) -> Result<(), EditError> { - 1148
let name = "[Content_Types].xml"; - 1149
let bytes = self.get(name)?; - 1150
let tree = Tree::parse(&bytes, name, self.limits())?; - 1151
let key = part_key(part); - 1152
let mut splice = Splice::default(); - 1153
for index in tree.descendants(0, "Override") { - 1154
if tree.nodes[index] - 1155
.element - 1156
.attr("PartName") - 1157
.is_some_and(|name| part_key(name) == key) - 1158
{ - 1159
splice.replace(tree.nodes[index].span.clone(), ""); - 1160
} - 1161
} - 1162
if !splice.is_empty() { - 1163
self.put(name, splice.apply(&bytes, name)?); - 1164
} - 1165
Ok(()) - 1166
} - 1167
- 1168
fn add_default_rels_type(&mut self) -> Result<(), EditError> { - 1169
let name = "[Content_Types].xml"; - 1170
let bytes = self.get(name)?; - 1171
let tree = Tree::parse(&bytes, name, self.limits())?; - 1172
let has = tree.descendants(0, "Default").any(|index| { - 1173
tree.nodes[index] - 1174
.element - 1175
.attr("Extension") - 1176
.is_some_and(|extension| extension.eq_ignore_ascii_case("rels")) - 1177
}); - 1178
if !has { - 1179
let mut splice = Splice::default(); - 1180
splice.insert( - 1181
tree.root().inner.end, - 1182
r#"<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>"#, - 1183
); - 1184
self.put(name, splice.apply(&bytes, name)?); - 1185
} - 1186
Ok(()) - 1187
} - 1188
- 1189
/// Part names that currently exist, including ones added by ops. - 1190
pub fn part_names(&self) -> Vec<String> { - 1191
let mut names: Vec<String> = self - 1192
.package - 1193
.part_names() - 1194
.filter(|name| self.entry(name).is_none()) - 1195
.map(str::to_string) - 1196
.collect(); - 1197
names.extend( - 1198
self.parts - 1199
.iter() - 1200
.filter(|(_, value)| value.is_some()) - 1201
.map(|(name, _)| name.clone()), - 1202
); - 1203
names - 1204
} - 1205
- 1206
pub fn content_type(&mut self, part: &str) -> Result<Option<String>, EditError> { - 1207
let name = "[Content_Types].xml"; - 1208
let bytes = self.get(name)?; - 1209
let tree = Tree::parse(&bytes, name, self.limits())?; - 1210
let key = part_key(part); - 1211
for index in tree.descendants(0, "Override") { - 1212
let element = &tree.nodes[index].element; - 1213
if element - 1214
.attr("PartName") - 1215
.is_some_and(|name| part_key(name) == key) - 1216
{ - 1217
return Ok(element.attr("ContentType").map(str::to_string)); - 1218
} - 1219
} - 1220
let extension = key - 1221
.rsplit_once('.') - 1222
.map(|(_, extension)| extension.to_string()); - 1223
for index in tree.descendants(0, "Default") { - 1224
let element = &tree.nodes[index].element; - 1225
if element - 1226
.attr("Extension") - 1227
.zip(extension.as_deref()) - 1228
.is_some_and(|(have, want)| have.eq_ignore_ascii_case(want)) - 1229
{ - 1230
return Ok(element.attr("ContentType").map(str::to_string)); - 1231
} - 1232
} - 1233
Ok(None) - 1234
} - 1235
} - 1236
- 1237
/// `target` written relative to `source`'s directory, as relationship - 1238
/// parts conventionally do (`ppt/slides/slide1.xml` → `../slideLayouts/x`). - 1239
pub(crate) fn relative_target(source: &str, target: &str) -> String { - 1240
let base: Vec<&str> = match source.rsplit_once('/') { - 1241
Some((directory, _)) => directory.split('/').collect(), - 1242
None => Vec::new(), - 1243
}; - 1244
let target_parts: Vec<&str> = target.trim_start_matches('/').split('/').collect(); - 1245
let common = base - 1246
.iter() - 1247
.zip(&target_parts) - 1248
.take_while(|(left, right)| left == right) - 1249
.count(); - 1250
let mut path: Vec<String> = - 1251
std::iter::repeat_n("..".to_string(), base.len() - common).collect(); - 1252
path.extend(target_parts[common..].iter().map(|part| part.to_string())); - 1253
path.join("/") - 1254
} - 1255
- 1256
/// The lowest `prefix{n}{suffix}` part name not in use. - 1257
pub(crate) fn free_part_name<R: std::io::Read + std::io::Seek>( - 1258
work: &Work<'_, R>, - 1259
prefix: &str, - 1260
suffix: &str, - 1261
) -> String { - 1262
let names: Vec<String> = work - 1263
.part_names() - 1264
.iter() - 1265
.map(|name| part_key(name)) - 1266
.collect(); - 1267
(1..) - 1268
.map(|number| format!("{prefix}{number}{suffix}")) - 1269
.find(|candidate| !names.contains(&part_key(candidate))) - 1270
.unwrap_or_else(|| format!("{prefix}x{suffix}")) - 1271
} - 1272
- 1273
#[cfg(test)] - 1274
mod tests { - 1275
use super::*; - 1276
- 1277
#[test] - 1278
fn relative_targets() { - 1279
assert_eq!( - 1280
relative_target("ppt/slides/slide1.xml", "ppt/slideLayouts/slideLayout2.xml"), - 1281
"../slideLayouts/slideLayout2.xml" - 1282
); - 1283
assert_eq!( - 1284
relative_target("ppt/presentation.xml", "ppt/slides/slide3.xml"), - 1285
"slides/slide3.xml" - 1286
); - 1287
assert_eq!( - 1288
relative_target("", "docProps/core.xml"), - 1289
"docProps/core.xml" - 1290
); - 1291
assert_eq!( - 1292
relative_target("xl/workbook.xml", "xl/worksheets/sheet2.xml"), - 1293
"worksheets/sheet2.xml" - 1294
); - 1295
} - 1296
} - 1297
Indexing the workspace…
Vakyartha documentation is discovering safe artifacts, anchors, and source references.