Commit b3e6d445cbf73da2b00062c3f639c2453041ee41

Authored by Jay Berkenbilt
1 parent 3661f274

Tweak "AndGet" mutator functions again

Remove any ambiguity around whether old or new value is being
returned.
ChangeLog
1 2022-07-24 Jay Berkenbilt <ejb@ql.org> 1 2022-07-24 Jay Berkenbilt <ejb@ql.org>
2 2
  3 + * QPDFObjectHandle: for the methods insertItem, appendItem,
  4 + eraseItem, replaceKey, and removeKey, add a corresponding
  5 + "AndGetNew" and/or "AndGetOld" methods. The ones that end with
  6 + "AndGetNew" return the newly added item. The ones that end with
  7 + "AndGetOld" return the old value. The AndGetNew methods make it
  8 + possible to create a new object, add it to an array or dictionary,
  9 + and get a handle to it all in one line. The AndGetOld methods make
  10 + it easier to retrieve an old value when removing or replacing it.
  11 +
3 * Thanks to m-holger for doing significant cleanup of private APIs 12 * Thanks to m-holger for doing significant cleanup of private APIs
4 and internals around QPDFObjGen and for significantly improving 13 and internals around QPDFObjGen and for significantly improving
5 the performance of QPDFObjGen -- See #731. This includes a few 14 the performance of QPDFObjGen -- See #731. This includes a few
@@ -168,16 +177,6 @@ @@ -168,16 +177,6 @@
168 128-bit without AES) an error rather than a warning when 177 128-bit without AES) an error rather than a warning when
169 --allow-weak-crypto is not specified. Fixes #576. 178 --allow-weak-crypto is not specified. Fixes #576.
170 179
171 -2022-04-29 Jay Berkenbilt <ejb@ql.org>  
172 -  
173 - * QPDFObjectHandle: for the methods insertItem, appendItem,  
174 - eraseItem, replaceKey, and removeKey, add a corresponding "AndGet"  
175 - method (insertItemAndGet, appendItemAndGet, eraseItemAndGet,  
176 - replaceKeyAndGet, and removeKeyAndGet) that returns the newly  
177 - inserted, replaced, or removed item. This makes it possible to  
178 - create a new object, add it to an array or dictionary, and get a  
179 - handle to it all in one line.  
180 -  
181 2022-04-24 Jay Berkenbilt <ejb@ql.org> 180 2022-04-24 Jay Berkenbilt <ejb@ql.org>
182 181
183 * Bug fix: "removeAttachment" in the job JSON now takes an array 182 * Bug fix: "removeAttachment" in the job JSON now takes an array
@@ -8,29 +8,6 @@ Before Release: @@ -8,29 +8,6 @@ Before Release:
8 * Stay on top of https://github.com/pikepdf/pikepdf/pull/315 8 * Stay on top of https://github.com/pikepdf/pikepdf/pull/315
9 * Release qtest with updates to qtest-driver and copy back into qpdf 9 * Release qtest with updates to qtest-driver and copy back into qpdf
10 10
11 -Parent pointer idea:  
12 -  
13 -* Have replaceKey, removeKey, and eraseItem return the old values. The  
14 - comments will clarify the difference between these and the andGet  
15 - versions.  
16 -* Add std::weak_ptr<QPDFObject> parent to QPDFObject. When adding a  
17 - direct object to an array or dictionary, set its parent. When  
18 - removing it, clear the parent pointer.  
19 -* When a direct object that already has a parent is added to  
20 - something, it is a warning and will become an error in qpdf 12.  
21 - There needs to be unsafe add methods used by unsafeShallowCopy.  
22 - These will add but not modify the parent pointer.  
23 -  
24 -This allows an object to be moved from one object to another by  
25 -removing it, which returns the now orphaned object, and then inserting  
26 -it somewhere else. It also doesn't break the pattern of adding a  
27 -direct object to something and subsequently mutating it. It just  
28 -prevents the same object from being added to more than one thing.  
29 -  
30 -Note that arrays and dictionaries still need to contain  
31 -QPDFObjectHandle because of indirect objects. This only pertains to  
32 -direct objects, which are always "resolved" in QPDFObjectHandle.  
33 -  
34 Next: 11 Next:
35 * JSON v2 fixes 12 * JSON v2 fixes
36 13
@@ -70,6 +47,26 @@ Pending changes: @@ -70,6 +47,26 @@ Pending changes:
70 about the case of more than 65,536 pages. If the top node has more 47 about the case of more than 65,536 pages. If the top node has more
71 than 256 children, we'll live with it. 48 than 256 children, we'll live with it.
72 49
  50 +Parent pointer idea:
  51 +
  52 +* Add std::weak_ptr<QPDFObject> parent to QPDFObject. When adding a
  53 + direct object to an array or dictionary, set its parent. When
  54 + removing it, clear the parent pointer.
  55 +* When a direct object that already has a parent is added to
  56 + something, it is a warning and will become an error in qpdf 12.
  57 + There needs to be unsafe add methods used by unsafeShallowCopy.
  58 + These will add but not modify the parent pointer.
  59 +
  60 +This allows an object to be moved from one object to another by
  61 +removing it, which returns the now orphaned object, and then inserting
  62 +it somewhere else. It also doesn't break the pattern of adding a
  63 +direct object to something and subsequently mutating it. It just
  64 +prevents the same object from being added to more than one thing.
  65 +
  66 +Note that arrays and dictionaries still need to contain
  67 +QPDFObjectHandle because of indirect objects. This only pertains to
  68 +direct objects, which are always "resolved" in QPDFObjectHandle.
  69 +
73 Soon: Break ground on "Document-level work" 70 Soon: Break ground on "Document-level work"
74 71
75 72
include/qpdf/QPDFObjectHandle.hh
@@ -999,18 +999,15 @@ class QPDFObjectHandle @@ -999,18 +999,15 @@ class QPDFObjectHandle
999 999
1000 // Mutator methods. 1000 // Mutator methods.
1001 1001
1002 - // Since qpdf 11: when a mutator object returns QPDFObjectHandle&,  
1003 - // it is a reference to the object itself. This makes it possible  
1004 - // to use a fluent style. For example: 1002 + // Since qpdf 11: for mutators that may add or remove an item,
  1003 + // there are additional versions whose names contain "AndGet" that
  1004 + // return the added or removed item. For example:
1005 // 1005 //
1006 - // array.appendItem(i1).appendItem(i2);  
1007 - //  
1008 - // would append i1 and then i2 to the array. There are also items  
1009 - // that end with AndGet and return a QPDFObjectHandle. These  
1010 - // return the newly added object. For example:  
1011 - //  
1012 - // auto new_dict = dict.replaceKeyAndGet( 1006 + // auto new_dict = dict.replaceKeyAndGetNew(
1013 // "/New", QPDFObjectHandle::newDictionary()); 1007 // "/New", QPDFObjectHandle::newDictionary());
  1008 + //
  1009 + // auto old_value = dict.replaceKeyAndGetOld(
  1010 + // "/New", "(something)"_qpdf);
1014 1011
1015 // Recursively copy this object, making it direct. An exception is 1012 // Recursively copy this object, making it direct. An exception is
1016 // thrown if a loop is detected. With allow_streams true, keep 1013 // thrown if a loop is detected. With allow_streams true, keep
@@ -1036,20 +1033,20 @@ class QPDFObjectHandle @@ -1036,20 +1033,20 @@ class QPDFObjectHandle
1036 void insertItem(int at, QPDFObjectHandle const& item); 1033 void insertItem(int at, QPDFObjectHandle const& item);
1037 // Like insertItem but return the item that was inserted. 1034 // Like insertItem but return the item that was inserted.
1038 QPDF_DLL 1035 QPDF_DLL
1039 - QPDFObjectHandle insertItemAndGet(int at, QPDFObjectHandle const& item); 1036 + QPDFObjectHandle insertItemAndGetNew(int at, QPDFObjectHandle const& item);
1040 // Append an item to an array. 1037 // Append an item to an array.
1041 QPDF_DLL 1038 QPDF_DLL
1042 void appendItem(QPDFObjectHandle const& item); 1039 void appendItem(QPDFObjectHandle const& item);
1043 // Append an item, and return the newly added item. 1040 // Append an item, and return the newly added item.
1044 QPDF_DLL 1041 QPDF_DLL
1045 - QPDFObjectHandle appendItemAndGet(QPDFObjectHandle const& item); 1042 + QPDFObjectHandle appendItemAndGetNew(QPDFObjectHandle const& item);
1046 // Remove the item at that position, reducing the size of the 1043 // Remove the item at that position, reducing the size of the
1047 // array by one. 1044 // array by one.
1048 QPDF_DLL 1045 QPDF_DLL
1049 void eraseItem(int at); 1046 void eraseItem(int at);
1050 // Erase and item and return the item that was removed. 1047 // Erase and item and return the item that was removed.
1051 QPDF_DLL 1048 QPDF_DLL
1052 - QPDFObjectHandle eraseItemAndGet(int at); 1049 + QPDFObjectHandle eraseItemAndGetOld(int at);
1053 1050
1054 // Mutator methods for dictionary objects 1051 // Mutator methods for dictionary objects
1055 1052
@@ -1060,14 +1057,19 @@ class QPDFObjectHandle @@ -1060,14 +1057,19 @@ class QPDFObjectHandle
1060 // Replace value of key and return the value. 1057 // Replace value of key and return the value.
1061 QPDF_DLL 1058 QPDF_DLL
1062 QPDFObjectHandle 1059 QPDFObjectHandle
1063 - replaceKeyAndGet(std::string const& key, QPDFObjectHandle const& value); 1060 + replaceKeyAndGetNew(std::string const& key, QPDFObjectHandle const& value);
  1061 + // Replace value of key and return the old value, or null if the
  1062 + // key was previously not present.
  1063 + QPDF_DLL
  1064 + QPDFObjectHandle
  1065 + replaceKeyAndGetOld(std::string const& key, QPDFObjectHandle const& value);
1064 // Remove key, doing nothing if key does not exist. 1066 // Remove key, doing nothing if key does not exist.
1065 QPDF_DLL 1067 QPDF_DLL
1066 void removeKey(std::string const& key); 1068 void removeKey(std::string const& key);
1067 // Remove key and return the old value. If the old value didn't 1069 // Remove key and return the old value. If the old value didn't
1068 // exist, return a null object. 1070 // exist, return a null object.
1069 QPDF_DLL 1071 QPDF_DLL
1070 - QPDFObjectHandle removeKeyAndGet(std::string const& key); 1072 + QPDFObjectHandle removeKeyAndGetOld(std::string const& key);
1071 1073
1072 // ABI: Remove in qpdf 12 1074 // ABI: Remove in qpdf 12
1073 [[deprecated("use replaceKey -- it does the same thing")]] QPDF_DLL void 1075 [[deprecated("use replaceKey -- it does the same thing")]] QPDF_DLL void
libqpdf/QPDFAcroFormDocumentHelper.cc
@@ -40,7 +40,7 @@ QPDFAcroFormDocumentHelper::getOrCreateAcroForm() @@ -40,7 +40,7 @@ QPDFAcroFormDocumentHelper::getOrCreateAcroForm()
40 { 40 {
41 auto acroform = this->qpdf.getRoot().getKey("/AcroForm"); 41 auto acroform = this->qpdf.getRoot().getKey("/AcroForm");
42 if (!acroform.isDictionary()) { 42 if (!acroform.isDictionary()) {
43 - acroform = this->qpdf.getRoot().replaceKeyAndGet( 43 + acroform = this->qpdf.getRoot().replaceKeyAndGetNew(
44 "/AcroForm", 44 "/AcroForm",
45 this->qpdf.makeIndirectObject(QPDFObjectHandle::newDictionary())); 45 this->qpdf.makeIndirectObject(QPDFObjectHandle::newDictionary()));
46 } 46 }
@@ -53,8 +53,8 @@ QPDFAcroFormDocumentHelper::addFormField(QPDFFormFieldObjectHelper ff) @@ -53,8 +53,8 @@ QPDFAcroFormDocumentHelper::addFormField(QPDFFormFieldObjectHelper ff)
53 auto acroform = getOrCreateAcroForm(); 53 auto acroform = getOrCreateAcroForm();
54 auto fields = acroform.getKey("/Fields"); 54 auto fields = acroform.getKey("/Fields");
55 if (!fields.isArray()) { 55 if (!fields.isArray()) {
56 - fields =  
57 - acroform.replaceKeyAndGet("/Fields", QPDFObjectHandle::newArray()); 56 + fields = acroform.replaceKeyAndGetNew(
  57 + "/Fields", QPDFObjectHandle::newArray());
58 } 58 }
59 fields.appendItem(ff.getObjectHandle()); 59 fields.appendItem(ff.getObjectHandle());
60 std::set<QPDFObjGen> visited; 60 std::set<QPDFObjGen> visited;
@@ -854,7 +854,7 @@ QPDFAcroFormDocumentHelper::transformAnnotations( @@ -854,7 +854,7 @@ QPDFAcroFormDocumentHelper::transformAnnotations(
854 } 854 }
855 dr.makeResourcesIndirect(this->qpdf); 855 dr.makeResourcesIndirect(this->qpdf);
856 if (!dr.isIndirect()) { 856 if (!dr.isIndirect()) {
857 - dr = acroform.replaceKeyAndGet( 857 + dr = acroform.replaceKeyAndGetNew(
858 "/DR", this->qpdf.makeIndirectObject(dr)); 858 "/DR", this->qpdf.makeIndirectObject(dr));
859 } 859 }
860 // Merge the other document's /DR, creating a conflict 860 // Merge the other document's /DR, creating a conflict
@@ -1076,7 +1076,7 @@ QPDFAcroFormDocumentHelper::transformAnnotations( @@ -1076,7 +1076,7 @@ QPDFAcroFormDocumentHelper::transformAnnotations(
1076 auto apdict = ah.getAppearanceDictionary(); 1076 auto apdict = ah.getAppearanceDictionary();
1077 std::vector<QPDFObjectHandle> streams; 1077 std::vector<QPDFObjectHandle> streams;
1078 auto replace_stream = [](auto& dict, auto& key, auto& old) { 1078 auto replace_stream = [](auto& dict, auto& key, auto& old) {
1079 - return dict.replaceKeyAndGet(key, old.copyStream()); 1079 + return dict.replaceKeyAndGetNew(key, old.copyStream());
1080 }; 1080 };
1081 if (apdict.isDictionary()) { 1081 if (apdict.isDictionary()) {
1082 for (auto& ap: apdict.ditems()) { 1082 for (auto& ap: apdict.ditems()) {
libqpdf/QPDFEFStreamObjectHelper.cc
@@ -28,7 +28,7 @@ QPDFEFStreamObjectHelper::setParam( @@ -28,7 +28,7 @@ QPDFEFStreamObjectHelper::setParam(
28 { 28 {
29 auto params = this->oh.getDict().getKey("/Params"); 29 auto params = this->oh.getDict().getKey("/Params");
30 if (!params.isDictionary()) { 30 if (!params.isDictionary()) {
31 - params = this->oh.getDict().replaceKeyAndGet( 31 + params = this->oh.getDict().replaceKeyAndGetNew(
32 "/Params", QPDFObjectHandle::newDictionary()); 32 "/Params", QPDFObjectHandle::newDictionary());
33 } 33 }
34 params.replaceKey(pkey, pval); 34 params.replaceKey(pkey, pval);
libqpdf/QPDFEmbeddedFileDocumentHelper.cc
@@ -62,8 +62,8 @@ QPDFEmbeddedFileDocumentHelper::initEmbeddedFiles() @@ -62,8 +62,8 @@ QPDFEmbeddedFileDocumentHelper::initEmbeddedFiles()
62 auto root = qpdf.getRoot(); 62 auto root = qpdf.getRoot();
63 auto names = root.getKey("/Names"); 63 auto names = root.getKey("/Names");
64 if (!names.isDictionary()) { 64 if (!names.isDictionary()) {
65 - names =  
66 - root.replaceKeyAndGet("/Names", QPDFObjectHandle::newDictionary()); 65 + names = root.replaceKeyAndGetNew(
  66 + "/Names", QPDFObjectHandle::newDictionary());
67 } 67 }
68 auto embedded_files = names.getKey("/EmbeddedFiles"); 68 auto embedded_files = names.getKey("/EmbeddedFiles");
69 if (!embedded_files.isDictionary()) { 69 if (!embedded_files.isDictionary()) {
libqpdf/QPDFJob.cc
@@ -2098,7 +2098,7 @@ QPDFJob::doUnderOverlayForPage( @@ -2098,7 +2098,7 @@ QPDFJob::doUnderOverlayForPage(
2098 QPDFObjectHandle resources = dest_page.getAttribute("/Resources", true); 2098 QPDFObjectHandle resources = dest_page.getAttribute("/Resources", true);
2099 if (!resources.isDictionary()) { 2099 if (!resources.isDictionary()) {
2100 QTC::TC("qpdf", "QPDFJob overlay page with no resources"); 2100 QTC::TC("qpdf", "QPDFJob overlay page with no resources");
2101 - resources = dest_page.getObjectHandle().replaceKeyAndGet( 2101 + resources = dest_page.getObjectHandle().replaceKeyAndGetNew(
2102 "/Resources", QPDFObjectHandle::newDictionary()); 2102 "/Resources", QPDFObjectHandle::newDictionary());
2103 } 2103 }
2104 for (int from_pageno: pagenos[pageno]) { 2104 for (int from_pageno: pagenos[pageno]) {
libqpdf/QPDFObjectHandle.cc
@@ -915,7 +915,7 @@ QPDFObjectHandle::insertItem(int at, QPDFObjectHandle const&amp; item) @@ -915,7 +915,7 @@ QPDFObjectHandle::insertItem(int at, QPDFObjectHandle const&amp; item)
915 } 915 }
916 916
917 QPDFObjectHandle 917 QPDFObjectHandle
918 -QPDFObjectHandle::insertItemAndGet(int at, QPDFObjectHandle const& item) 918 +QPDFObjectHandle::insertItemAndGetNew(int at, QPDFObjectHandle const& item)
919 { 919 {
920 insertItem(at, item); 920 insertItem(at, item);
921 return item; 921 return item;
@@ -934,7 +934,7 @@ QPDFObjectHandle::appendItem(QPDFObjectHandle const&amp; item) @@ -934,7 +934,7 @@ QPDFObjectHandle::appendItem(QPDFObjectHandle const&amp; item)
934 } 934 }
935 935
936 QPDFObjectHandle 936 QPDFObjectHandle
937 -QPDFObjectHandle::appendItemAndGet(QPDFObjectHandle const& item) 937 +QPDFObjectHandle::appendItemAndGetNew(QPDFObjectHandle const& item)
938 { 938 {
939 appendItem(item); 939 appendItem(item);
940 return item; 940 return item;
@@ -957,7 +957,7 @@ QPDFObjectHandle::eraseItem(int at) @@ -957,7 +957,7 @@ QPDFObjectHandle::eraseItem(int at)
957 } 957 }
958 958
959 QPDFObjectHandle 959 QPDFObjectHandle
960 -QPDFObjectHandle::eraseItemAndGet(int at) 960 +QPDFObjectHandle::eraseItemAndGetOld(int at)
961 { 961 {
962 auto result = QPDFObjectHandle::newNull(); 962 auto result = QPDFObjectHandle::newNull();
963 if (isArray() && (at < getArrayNItems()) && (at >= 0)) { 963 if (isArray() && (at < getArrayNItems()) && (at >= 0)) {
@@ -1113,7 +1113,8 @@ QPDFObjectHandle::mergeResources( @@ -1113,7 +1113,8 @@ QPDFObjectHandle::mergeResources(
1113 // subdictionaries just to get this shallow copy 1113 // subdictionaries just to get this shallow copy
1114 // functionality. 1114 // functionality.
1115 QTC::TC("qpdf", "QPDFObjectHandle replace with copy"); 1115 QTC::TC("qpdf", "QPDFObjectHandle replace with copy");
1116 - this_val = replaceKeyAndGet(rtype, this_val.shallowCopy()); 1116 + this_val =
  1117 + replaceKeyAndGetNew(rtype, this_val.shallowCopy());
1117 } 1118 }
1118 std::map<QPDFObjGen, std::string> og_to_name; 1119 std::map<QPDFObjGen, std::string> og_to_name;
1119 std::set<std::string> rnames; 1120 std::set<std::string> rnames;
@@ -1242,13 +1243,22 @@ QPDFObjectHandle::replaceKey( @@ -1242,13 +1243,22 @@ QPDFObjectHandle::replaceKey(
1242 } 1243 }
1243 1244
1244 QPDFObjectHandle 1245 QPDFObjectHandle
1245 -QPDFObjectHandle::replaceKeyAndGet( 1246 +QPDFObjectHandle::replaceKeyAndGetNew(
1246 std::string const& key, QPDFObjectHandle const& value) 1247 std::string const& key, QPDFObjectHandle const& value)
1247 { 1248 {
1248 replaceKey(key, value); 1249 replaceKey(key, value);
1249 return value; 1250 return value;
1250 } 1251 }
1251 1252
  1253 +QPDFObjectHandle
  1254 +QPDFObjectHandle::replaceKeyAndGetOld(
  1255 + std::string const& key, QPDFObjectHandle const& value)
  1256 +{
  1257 + QPDFObjectHandle old = removeKeyAndGetOld(key);
  1258 + replaceKey(key, value);
  1259 + return old;
  1260 +}
  1261 +
1252 void 1262 void
1253 QPDFObjectHandle::removeKey(std::string const& key) 1263 QPDFObjectHandle::removeKey(std::string const& key)
1254 { 1264 {
@@ -1261,7 +1271,7 @@ QPDFObjectHandle::removeKey(std::string const&amp; key) @@ -1261,7 +1271,7 @@ QPDFObjectHandle::removeKey(std::string const&amp; key)
1261 } 1271 }
1262 1272
1263 QPDFObjectHandle 1273 QPDFObjectHandle
1264 -QPDFObjectHandle::removeKeyAndGet(std::string const& key) 1274 +QPDFObjectHandle::removeKeyAndGetOld(std::string const& key)
1265 { 1275 {
1266 auto result = QPDFObjectHandle::newNull(); 1276 auto result = QPDFObjectHandle::newNull();
1267 if (isDictionary()) { 1277 if (isDictionary()) {
libqpdf/QPDFPageObjectHelper.cc
@@ -595,7 +595,7 @@ QPDFPageObjectHelper::removeUnreferencedResourcesHelper( @@ -595,7 +595,7 @@ QPDFPageObjectHelper::removeUnreferencedResourcesHelper(
595 for (auto const& iter: to_filter) { 595 for (auto const& iter: to_filter) {
596 QPDFObjectHandle dict = resources.getKey(iter); 596 QPDFObjectHandle dict = resources.getKey(iter);
597 if (dict.isDictionary()) { 597 if (dict.isDictionary()) {
598 - dict = resources.replaceKeyAndGet(iter, dict.shallowCopy()); 598 + dict = resources.replaceKeyAndGetNew(iter, dict.shallowCopy());
599 rdicts.push_back(dict); 599 rdicts.push_back(dict);
600 auto keys = dict.getKeys(); 600 auto keys = dict.getKeys();
601 known_names.insert(keys.begin(), keys.end()); 601 known_names.insert(keys.begin(), keys.end());
@@ -1110,8 +1110,8 @@ QPDFPageObjectHelper::copyAnnotations( @@ -1110,8 +1110,8 @@ QPDFPageObjectHelper::copyAnnotations(
1110 afdh->addAndRenameFormFields(new_fields); 1110 afdh->addAndRenameFormFields(new_fields);
1111 auto annots = this->oh.getKey("/Annots"); 1111 auto annots = this->oh.getKey("/Annots");
1112 if (!annots.isArray()) { 1112 if (!annots.isArray()) {
1113 - annots =  
1114 - this->oh.replaceKeyAndGet("/Annots", QPDFObjectHandle::newArray()); 1113 + annots = this->oh.replaceKeyAndGetNew(
  1114 + "/Annots", QPDFObjectHandle::newArray());
1115 } 1115 }
1116 for (auto const& annot: new_annots) { 1116 for (auto const& annot: new_annots) {
1117 annots.appendItem(annot); 1117 annots.appendItem(annot);
libqpdf/QPDFWriter.cc
@@ -1571,7 +1571,7 @@ QPDFWriter::unparseObject( @@ -1571,7 +1571,7 @@ QPDFWriter::unparseObject(
1571 "qpdf", 1571 "qpdf",
1572 "QPDFWriter create Extensions", 1572 "QPDFWriter create Extensions",
1573 this->m->qdf_mode ? 0 : 1); 1573 this->m->qdf_mode ? 0 : 1);
1574 - extensions = object.replaceKeyAndGet( 1574 + extensions = object.replaceKeyAndGetNew(
1575 "/Extensions", QPDFObjectHandle::newDictionary()); 1575 "/Extensions", QPDFObjectHandle::newDictionary());
1576 } 1576 }
1577 } else if (!have_extensions_other) { 1577 } else if (!have_extensions_other) {
@@ -2277,7 +2277,7 @@ QPDFWriter::prepareFileForWrite() @@ -2277,7 +2277,7 @@ QPDFWriter::prepareFileForWrite()
2277 if (oh.isIndirect()) { 2277 if (oh.isIndirect()) {
2278 QTC::TC("qpdf", "QPDFWriter make Extensions direct"); 2278 QTC::TC("qpdf", "QPDFWriter make Extensions direct");
2279 extensions_indirect = true; 2279 extensions_indirect = true;
2280 - oh = root.replaceKeyAndGet(key, oh.shallowCopy()); 2280 + oh = root.replaceKeyAndGetNew(key, oh.shallowCopy());
2281 } 2281 }
2282 if (oh.hasKey("/ADBE")) { 2282 if (oh.hasKey("/ADBE")) {
2283 QPDFObjectHandle adbe = oh.getKey("/ADBE"); 2283 QPDFObjectHandle adbe = oh.getKey("/ADBE");
manual/release-notes.rst
@@ -163,9 +163,13 @@ For a detailed list of changes, please see the file @@ -163,9 +163,13 @@ For a detailed list of changes, please see the file
163 - See examples :file:`examples/qpdfjob-save-attachment.cc` and 163 - See examples :file:`examples/qpdfjob-save-attachment.cc` and
164 :file:`examples/qpdfjob-c-save-attachment.cc`. 164 :file:`examples/qpdfjob-c-save-attachment.cc`.
165 165
166 - - New methods ``insertItemAndGet``, ``appendItemAndGet``,  
167 - ``eraseItemAndGet``, ``replaceKeyAndGet``, and  
168 - ``removeKeyAndGet`` return the newly added or removed object. 166 + - In ``QPDFObjectHandle``, new methods ``insertItemAndGetNew``,
  167 + ``appendItemAndGetNew``, and ``replaceKeyAndGetNew`` return the
  168 + newly added item. New methods ``eraseItemAndGetOld``,
  169 + ``replaceKeyAndGetOld``, and ``removeKeyAndGetOld`` return the
  170 + item that was just removed or, in the case of
  171 + ``replaceKeyAndGetOld``, a ``null`` object if the object was not
  172 + previously there.
169 173
170 - Add new ``Pipeline`` methods to reduce the amount of casting that is 174 - Add new ``Pipeline`` methods to reduce the amount of casting that is
171 needed: 175 needed:
qpdf/test_driver.cc
@@ -1095,8 +1095,8 @@ test_27(QPDF&amp; pdf, char const* arg2) @@ -1095,8 +1095,8 @@ test_27(QPDF&amp; pdf, char const* arg2)
1095 QPDFObjectHandle s2 = QPDFObjectHandle::newStream(&oldpdf, "potato\n"); 1095 QPDFObjectHandle s2 = QPDFObjectHandle::newStream(&oldpdf, "potato\n");
1096 auto trailer = pdf.getTrailer(); 1096 auto trailer = pdf.getTrailer();
1097 trailer.replaceKey("/QTest", pdf.copyForeignObject(qtest)); 1097 trailer.replaceKey("/QTest", pdf.copyForeignObject(qtest));
1098 - auto qtest2 =  
1099 - trailer.replaceKeyAndGet("/QTest2", QPDFObjectHandle::newArray()); 1098 + auto qtest2 = trailer.replaceKeyAndGetNew(
  1099 + "/QTest2", QPDFObjectHandle::newArray());
1100 qtest2.appendItem(pdf.copyForeignObject(s1)); 1100 qtest2.appendItem(pdf.copyForeignObject(s1));
1101 qtest2.appendItem(pdf.copyForeignObject(s2)); 1101 qtest2.appendItem(pdf.copyForeignObject(s2));
1102 qtest2.appendItem(pdf.copyForeignObject(s3)); 1102 qtest2.appendItem(pdf.copyForeignObject(s3));
@@ -3165,15 +3165,23 @@ test_88(QPDF&amp; pdf, char const* arg2) @@ -3165,15 +3165,23 @@ test_88(QPDF&amp; pdf, char const* arg2)
3165 auto dict = QPDFObjectHandle::newDictionary(); 3165 auto dict = QPDFObjectHandle::newDictionary();
3166 dict.replaceKey("/One", QPDFObjectHandle::newInteger(1)); 3166 dict.replaceKey("/One", QPDFObjectHandle::newInteger(1));
3167 dict.replaceKey("/Two", QPDFObjectHandle::newInteger(2)); 3167 dict.replaceKey("/Two", QPDFObjectHandle::newInteger(2));
3168 - auto three = dict.replaceKeyAndGet("/Three", QPDFObjectHandle::newArray()); 3168 + auto three =
  3169 + dict.replaceKeyAndGetNew("/Three", QPDFObjectHandle::newArray());
3169 three.appendItem("(a)"_qpdf); 3170 three.appendItem("(a)"_qpdf);
3170 three.appendItem("(b)"_qpdf); 3171 three.appendItem("(b)"_qpdf);
3171 - auto newdict = three.appendItemAndGet(QPDFObjectHandle::newDictionary()); 3172 + auto newdict = three.appendItemAndGetNew(QPDFObjectHandle::newDictionary());
3172 newdict.replaceKey("/Z", "/Y"_qpdf); 3173 newdict.replaceKey("/Z", "/Y"_qpdf);
3173 newdict.replaceKey("/X", "/W"_qpdf); 3174 newdict.replaceKey("/X", "/W"_qpdf);
  3175 + dict.replaceKey("/Quack", "[1 2 3]"_qpdf);
  3176 + auto quack = dict.replaceKeyAndGetOld("/Quack", "/Moo"_qpdf);
  3177 + assert(quack.unparse() == "[ 1 2 3 ]");
  3178 + auto nothing =
  3179 + dict.replaceKeyAndGetOld("/NotThere", QPDFObjectHandle::newNull());
  3180 + assert(nothing.isNull());
3174 assert(dict.unparse() == R"( 3181 assert(dict.unparse() == R"(
3175 << 3182 <<
3176 /One 1 3183 /One 1
  3184 + /Quack /Moo
3177 /Two 2 3185 /Two 2
3178 /Three [ (a) (b) << /Z /Y /X /W >> ] 3186 /Three [ (a) (b) << /Z /Y /X /W >> ]
3179 >> 3187 >>
@@ -3184,7 +3192,7 @@ test_88(QPDF&amp; pdf, char const* arg2) @@ -3184,7 +3192,7 @@ test_88(QPDF&amp; pdf, char const* arg2)
3184 assert( 3192 assert(
3185 arr.unparse() == 3193 arr.unparse() ==
3186 "[ (00) (0) (a) (b) << /Z /Y /X /W >> ]"_qpdf.unparse()); 3194 "[ (00) (0) (a) (b) << /Z /Y /X /W >> ]"_qpdf.unparse());
3187 - auto new_dict = arr.insertItemAndGet(1, "<< /P /Q /R /S >>"_qpdf); 3195 + auto new_dict = arr.insertItemAndGetNew(1, "<< /P /Q /R /S >>"_qpdf);
3188 arr.eraseItem(2); 3196 arr.eraseItem(2);
3189 arr.eraseItem(0); 3197 arr.eraseItem(0);
3190 assert( 3198 assert(
@@ -3200,20 +3208,20 @@ test_88(QPDF&amp; pdf, char const* arg2) @@ -3200,20 +3208,20 @@ test_88(QPDF&amp; pdf, char const* arg2)
3200 assert( 3208 assert(
3201 arr.unparse() == 3209 arr.unparse() ==
3202 "[ << /P /Q /T /U >> (a) (b) << /Z /Y /X /W >> ]"_qpdf.unparse()); 3210 "[ << /P /Q /T /U >> (a) (b) << /Z /Y /X /W >> ]"_qpdf.unparse());
3203 - auto s = arr.eraseItemAndGet(1); 3211 + auto s = arr.eraseItemAndGetOld(1);
3204 assert(s.unparse() == "(a)"); 3212 assert(s.unparse() == "(a)");
3205 assert( 3213 assert(
3206 arr.unparse() == 3214 arr.unparse() ==
3207 "[ << /P /Q /T /U >> (b) << /Z /Y /X /W >> ]"_qpdf.unparse()); 3215 "[ << /P /Q /T /U >> (b) << /Z /Y /X /W >> ]"_qpdf.unparse());
3208 3216
3209 - assert(new_dict.removeKeyAndGet("/M").isNull());  
3210 - assert(new_dict.removeKeyAndGet("/P").unparse() == "/Q"); 3217 + assert(new_dict.removeKeyAndGetOld("/M").isNull());
  3218 + assert(new_dict.removeKeyAndGetOld("/P").unparse() == "/Q");
3211 assert(new_dict.unparse() == "<< /T /U >>"_qpdf.unparse()); 3219 assert(new_dict.unparse() == "<< /T /U >>"_qpdf.unparse());
3212 3220
3213 // Test errors 3221 // Test errors
3214 - auto arr2 = pdf.getRoot().replaceKeyAndGet("/QTest", "[1 2]"_qpdf); 3222 + auto arr2 = pdf.getRoot().replaceKeyAndGetNew("/QTest", "[1 2]"_qpdf);
3215 arr2.setObjectDescription(&pdf, "test array"); 3223 arr2.setObjectDescription(&pdf, "test array");
3216 - assert(arr2.eraseItemAndGet(50).isNull()); 3224 + assert(arr2.eraseItemAndGetOld(50).isNull());
3217 } 3225 }
3218 3226
3219 static void 3227 static void