2
1
mirror of https://github.com/qpdf/qpdf.git synced 2024-06-03 19:00:51 +00:00

Tweak "AndGet" mutator functions again

Remove any ambiguity around whether old or new value is being
returned.
This commit is contained in:
Jay Berkenbilt 2022-07-24 15:42:23 -04:00
parent 3661f2749a
commit b3e6d445cb
12 changed files with 101 additions and 81 deletions

View File

@ -1,5 +1,14 @@
2022-07-24 Jay Berkenbilt <ejb@ql.org> 2022-07-24 Jay Berkenbilt <ejb@ql.org>
* QPDFObjectHandle: for the methods insertItem, appendItem,
eraseItem, replaceKey, and removeKey, add a corresponding
"AndGetNew" and/or "AndGetOld" methods. The ones that end with
"AndGetNew" return the newly added item. The ones that end with
"AndGetOld" return the old value. The AndGetNew methods make it
possible to create a new object, add it to an array or dictionary,
and get a handle to it all in one line. The AndGetOld methods make
it easier to retrieve an old value when removing or replacing it.
* Thanks to m-holger for doing significant cleanup of private APIs * Thanks to m-holger for doing significant cleanup of private APIs
and internals around QPDFObjGen and for significantly improving and internals around QPDFObjGen and for significantly improving
the performance of QPDFObjGen -- See #731. This includes a few the performance of QPDFObjGen -- See #731. This includes a few
@ -168,16 +177,6 @@
128-bit without AES) an error rather than a warning when 128-bit without AES) an error rather than a warning when
--allow-weak-crypto is not specified. Fixes #576. --allow-weak-crypto is not specified. Fixes #576.
2022-04-29 Jay Berkenbilt <ejb@ql.org>
* QPDFObjectHandle: for the methods insertItem, appendItem,
eraseItem, replaceKey, and removeKey, add a corresponding "AndGet"
method (insertItemAndGet, appendItemAndGet, eraseItemAndGet,
replaceKeyAndGet, and removeKeyAndGet) that returns the newly
inserted, replaced, or removed item. This makes it possible to
create a new object, add it to an array or dictionary, and get a
handle to it all in one line.
2022-04-24 Jay Berkenbilt <ejb@ql.org> 2022-04-24 Jay Berkenbilt <ejb@ql.org>
* Bug fix: "removeAttachment" in the job JSON now takes an array * Bug fix: "removeAttachment" in the job JSON now takes an array

43
TODO
View File

@ -8,29 +8,6 @@ Before Release:
* Stay on top of https://github.com/pikepdf/pikepdf/pull/315 * Stay on top of https://github.com/pikepdf/pikepdf/pull/315
* Release qtest with updates to qtest-driver and copy back into qpdf * Release qtest with updates to qtest-driver and copy back into qpdf
Parent pointer idea:
* Have replaceKey, removeKey, and eraseItem return the old values. The
comments will clarify the difference between these and the andGet
versions.
* Add std::weak_ptr<QPDFObject> parent to QPDFObject. When adding a
direct object to an array or dictionary, set its parent. When
removing it, clear the parent pointer.
* When a direct object that already has a parent is added to
something, it is a warning and will become an error in qpdf 12.
There needs to be unsafe add methods used by unsafeShallowCopy.
These will add but not modify the parent pointer.
This allows an object to be moved from one object to another by
removing it, which returns the now orphaned object, and then inserting
it somewhere else. It also doesn't break the pattern of adding a
direct object to something and subsequently mutating it. It just
prevents the same object from being added to more than one thing.
Note that arrays and dictionaries still need to contain
QPDFObjectHandle because of indirect objects. This only pertains to
direct objects, which are always "resolved" in QPDFObjectHandle.
Next: Next:
* JSON v2 fixes * JSON v2 fixes
@ -70,6 +47,26 @@ Pending changes:
about the case of more than 65,536 pages. If the top node has more about the case of more than 65,536 pages. If the top node has more
than 256 children, we'll live with it. than 256 children, we'll live with it.
Parent pointer idea:
* Add std::weak_ptr<QPDFObject> parent to QPDFObject. When adding a
direct object to an array or dictionary, set its parent. When
removing it, clear the parent pointer.
* When a direct object that already has a parent is added to
something, it is a warning and will become an error in qpdf 12.
There needs to be unsafe add methods used by unsafeShallowCopy.
These will add but not modify the parent pointer.
This allows an object to be moved from one object to another by
removing it, which returns the now orphaned object, and then inserting
it somewhere else. It also doesn't break the pattern of adding a
direct object to something and subsequently mutating it. It just
prevents the same object from being added to more than one thing.
Note that arrays and dictionaries still need to contain
QPDFObjectHandle because of indirect objects. This only pertains to
direct objects, which are always "resolved" in QPDFObjectHandle.
Soon: Break ground on "Document-level work" Soon: Break ground on "Document-level work"

View File

@ -999,18 +999,15 @@ class QPDFObjectHandle
// Mutator methods. // Mutator methods.
// Since qpdf 11: when a mutator object returns QPDFObjectHandle&, // Since qpdf 11: for mutators that may add or remove an item,
// it is a reference to the object itself. This makes it possible // there are additional versions whose names contain "AndGet" that
// to use a fluent style. For example: // return the added or removed item. For example:
// //
// array.appendItem(i1).appendItem(i2); // auto new_dict = dict.replaceKeyAndGetNew(
//
// would append i1 and then i2 to the array. There are also items
// that end with AndGet and return a QPDFObjectHandle. These
// return the newly added object. For example:
//
// auto new_dict = dict.replaceKeyAndGet(
// "/New", QPDFObjectHandle::newDictionary()); // "/New", QPDFObjectHandle::newDictionary());
//
// auto old_value = dict.replaceKeyAndGetOld(
// "/New", "(something)"_qpdf);
// Recursively copy this object, making it direct. An exception is // Recursively copy this object, making it direct. An exception is
// thrown if a loop is detected. With allow_streams true, keep // thrown if a loop is detected. With allow_streams true, keep
@ -1036,20 +1033,20 @@ class QPDFObjectHandle
void insertItem(int at, QPDFObjectHandle const& item); void insertItem(int at, QPDFObjectHandle const& item);
// Like insertItem but return the item that was inserted. // Like insertItem but return the item that was inserted.
QPDF_DLL QPDF_DLL
QPDFObjectHandle insertItemAndGet(int at, QPDFObjectHandle const& item); QPDFObjectHandle insertItemAndGetNew(int at, QPDFObjectHandle const& item);
// Append an item to an array. // Append an item to an array.
QPDF_DLL QPDF_DLL
void appendItem(QPDFObjectHandle const& item); void appendItem(QPDFObjectHandle const& item);
// Append an item, and return the newly added item. // Append an item, and return the newly added item.
QPDF_DLL QPDF_DLL
QPDFObjectHandle appendItemAndGet(QPDFObjectHandle const& item); QPDFObjectHandle appendItemAndGetNew(QPDFObjectHandle const& item);
// Remove the item at that position, reducing the size of the // Remove the item at that position, reducing the size of the
// array by one. // array by one.
QPDF_DLL QPDF_DLL
void eraseItem(int at); void eraseItem(int at);
// Erase and item and return the item that was removed. // Erase and item and return the item that was removed.
QPDF_DLL QPDF_DLL
QPDFObjectHandle eraseItemAndGet(int at); QPDFObjectHandle eraseItemAndGetOld(int at);
// Mutator methods for dictionary objects // Mutator methods for dictionary objects
@ -1060,14 +1057,19 @@ class QPDFObjectHandle
// Replace value of key and return the value. // Replace value of key and return the value.
QPDF_DLL QPDF_DLL
QPDFObjectHandle QPDFObjectHandle
replaceKeyAndGet(std::string const& key, QPDFObjectHandle const& value); replaceKeyAndGetNew(std::string const& key, QPDFObjectHandle const& value);
// Replace value of key and return the old value, or null if the
// key was previously not present.
QPDF_DLL
QPDFObjectHandle
replaceKeyAndGetOld(std::string const& key, QPDFObjectHandle const& value);
// Remove key, doing nothing if key does not exist. // Remove key, doing nothing if key does not exist.
QPDF_DLL QPDF_DLL
void removeKey(std::string const& key); void removeKey(std::string const& key);
// Remove key and return the old value. If the old value didn't // Remove key and return the old value. If the old value didn't
// exist, return a null object. // exist, return a null object.
QPDF_DLL QPDF_DLL
QPDFObjectHandle removeKeyAndGet(std::string const& key); QPDFObjectHandle removeKeyAndGetOld(std::string const& key);
// ABI: Remove in qpdf 12 // ABI: Remove in qpdf 12
[[deprecated("use replaceKey -- it does the same thing")]] QPDF_DLL void [[deprecated("use replaceKey -- it does the same thing")]] QPDF_DLL void

View File

@ -40,7 +40,7 @@ QPDFAcroFormDocumentHelper::getOrCreateAcroForm()
{ {
auto acroform = this->qpdf.getRoot().getKey("/AcroForm"); auto acroform = this->qpdf.getRoot().getKey("/AcroForm");
if (!acroform.isDictionary()) { if (!acroform.isDictionary()) {
acroform = this->qpdf.getRoot().replaceKeyAndGet( acroform = this->qpdf.getRoot().replaceKeyAndGetNew(
"/AcroForm", "/AcroForm",
this->qpdf.makeIndirectObject(QPDFObjectHandle::newDictionary())); this->qpdf.makeIndirectObject(QPDFObjectHandle::newDictionary()));
} }
@ -53,8 +53,8 @@ QPDFAcroFormDocumentHelper::addFormField(QPDFFormFieldObjectHelper ff)
auto acroform = getOrCreateAcroForm(); auto acroform = getOrCreateAcroForm();
auto fields = acroform.getKey("/Fields"); auto fields = acroform.getKey("/Fields");
if (!fields.isArray()) { if (!fields.isArray()) {
fields = fields = acroform.replaceKeyAndGetNew(
acroform.replaceKeyAndGet("/Fields", QPDFObjectHandle::newArray()); "/Fields", QPDFObjectHandle::newArray());
} }
fields.appendItem(ff.getObjectHandle()); fields.appendItem(ff.getObjectHandle());
std::set<QPDFObjGen> visited; std::set<QPDFObjGen> visited;
@ -854,7 +854,7 @@ QPDFAcroFormDocumentHelper::transformAnnotations(
} }
dr.makeResourcesIndirect(this->qpdf); dr.makeResourcesIndirect(this->qpdf);
if (!dr.isIndirect()) { if (!dr.isIndirect()) {
dr = acroform.replaceKeyAndGet( dr = acroform.replaceKeyAndGetNew(
"/DR", this->qpdf.makeIndirectObject(dr)); "/DR", this->qpdf.makeIndirectObject(dr));
} }
// Merge the other document's /DR, creating a conflict // Merge the other document's /DR, creating a conflict
@ -1076,7 +1076,7 @@ QPDFAcroFormDocumentHelper::transformAnnotations(
auto apdict = ah.getAppearanceDictionary(); auto apdict = ah.getAppearanceDictionary();
std::vector<QPDFObjectHandle> streams; std::vector<QPDFObjectHandle> streams;
auto replace_stream = [](auto& dict, auto& key, auto& old) { auto replace_stream = [](auto& dict, auto& key, auto& old) {
return dict.replaceKeyAndGet(key, old.copyStream()); return dict.replaceKeyAndGetNew(key, old.copyStream());
}; };
if (apdict.isDictionary()) { if (apdict.isDictionary()) {
for (auto& ap: apdict.ditems()) { for (auto& ap: apdict.ditems()) {

View File

@ -28,7 +28,7 @@ QPDFEFStreamObjectHelper::setParam(
{ {
auto params = this->oh.getDict().getKey("/Params"); auto params = this->oh.getDict().getKey("/Params");
if (!params.isDictionary()) { if (!params.isDictionary()) {
params = this->oh.getDict().replaceKeyAndGet( params = this->oh.getDict().replaceKeyAndGetNew(
"/Params", QPDFObjectHandle::newDictionary()); "/Params", QPDFObjectHandle::newDictionary());
} }
params.replaceKey(pkey, pval); params.replaceKey(pkey, pval);

View File

@ -62,8 +62,8 @@ QPDFEmbeddedFileDocumentHelper::initEmbeddedFiles()
auto root = qpdf.getRoot(); auto root = qpdf.getRoot();
auto names = root.getKey("/Names"); auto names = root.getKey("/Names");
if (!names.isDictionary()) { if (!names.isDictionary()) {
names = names = root.replaceKeyAndGetNew(
root.replaceKeyAndGet("/Names", QPDFObjectHandle::newDictionary()); "/Names", QPDFObjectHandle::newDictionary());
} }
auto embedded_files = names.getKey("/EmbeddedFiles"); auto embedded_files = names.getKey("/EmbeddedFiles");
if (!embedded_files.isDictionary()) { if (!embedded_files.isDictionary()) {

View File

@ -2098,7 +2098,7 @@ QPDFJob::doUnderOverlayForPage(
QPDFObjectHandle resources = dest_page.getAttribute("/Resources", true); QPDFObjectHandle resources = dest_page.getAttribute("/Resources", true);
if (!resources.isDictionary()) { if (!resources.isDictionary()) {
QTC::TC("qpdf", "QPDFJob overlay page with no resources"); QTC::TC("qpdf", "QPDFJob overlay page with no resources");
resources = dest_page.getObjectHandle().replaceKeyAndGet( resources = dest_page.getObjectHandle().replaceKeyAndGetNew(
"/Resources", QPDFObjectHandle::newDictionary()); "/Resources", QPDFObjectHandle::newDictionary());
} }
for (int from_pageno: pagenos[pageno]) { for (int from_pageno: pagenos[pageno]) {

View File

@ -915,7 +915,7 @@ QPDFObjectHandle::insertItem(int at, QPDFObjectHandle const& item)
} }
QPDFObjectHandle QPDFObjectHandle
QPDFObjectHandle::insertItemAndGet(int at, QPDFObjectHandle const& item) QPDFObjectHandle::insertItemAndGetNew(int at, QPDFObjectHandle const& item)
{ {
insertItem(at, item); insertItem(at, item);
return item; return item;
@ -934,7 +934,7 @@ QPDFObjectHandle::appendItem(QPDFObjectHandle const& item)
} }
QPDFObjectHandle QPDFObjectHandle
QPDFObjectHandle::appendItemAndGet(QPDFObjectHandle const& item) QPDFObjectHandle::appendItemAndGetNew(QPDFObjectHandle const& item)
{ {
appendItem(item); appendItem(item);
return item; return item;
@ -957,7 +957,7 @@ QPDFObjectHandle::eraseItem(int at)
} }
QPDFObjectHandle QPDFObjectHandle
QPDFObjectHandle::eraseItemAndGet(int at) QPDFObjectHandle::eraseItemAndGetOld(int at)
{ {
auto result = QPDFObjectHandle::newNull(); auto result = QPDFObjectHandle::newNull();
if (isArray() && (at < getArrayNItems()) && (at >= 0)) { if (isArray() && (at < getArrayNItems()) && (at >= 0)) {
@ -1113,7 +1113,8 @@ QPDFObjectHandle::mergeResources(
// subdictionaries just to get this shallow copy // subdictionaries just to get this shallow copy
// functionality. // functionality.
QTC::TC("qpdf", "QPDFObjectHandle replace with copy"); QTC::TC("qpdf", "QPDFObjectHandle replace with copy");
this_val = replaceKeyAndGet(rtype, this_val.shallowCopy()); this_val =
replaceKeyAndGetNew(rtype, this_val.shallowCopy());
} }
std::map<QPDFObjGen, std::string> og_to_name; std::map<QPDFObjGen, std::string> og_to_name;
std::set<std::string> rnames; std::set<std::string> rnames;
@ -1242,13 +1243,22 @@ QPDFObjectHandle::replaceKey(
} }
QPDFObjectHandle QPDFObjectHandle
QPDFObjectHandle::replaceKeyAndGet( QPDFObjectHandle::replaceKeyAndGetNew(
std::string const& key, QPDFObjectHandle const& value) std::string const& key, QPDFObjectHandle const& value)
{ {
replaceKey(key, value); replaceKey(key, value);
return value; return value;
} }
QPDFObjectHandle
QPDFObjectHandle::replaceKeyAndGetOld(
std::string const& key, QPDFObjectHandle const& value)
{
QPDFObjectHandle old = removeKeyAndGetOld(key);
replaceKey(key, value);
return old;
}
void void
QPDFObjectHandle::removeKey(std::string const& key) QPDFObjectHandle::removeKey(std::string const& key)
{ {
@ -1261,7 +1271,7 @@ QPDFObjectHandle::removeKey(std::string const& key)
} }
QPDFObjectHandle QPDFObjectHandle
QPDFObjectHandle::removeKeyAndGet(std::string const& key) QPDFObjectHandle::removeKeyAndGetOld(std::string const& key)
{ {
auto result = QPDFObjectHandle::newNull(); auto result = QPDFObjectHandle::newNull();
if (isDictionary()) { if (isDictionary()) {

View File

@ -595,7 +595,7 @@ QPDFPageObjectHelper::removeUnreferencedResourcesHelper(
for (auto const& iter: to_filter) { for (auto const& iter: to_filter) {
QPDFObjectHandle dict = resources.getKey(iter); QPDFObjectHandle dict = resources.getKey(iter);
if (dict.isDictionary()) { if (dict.isDictionary()) {
dict = resources.replaceKeyAndGet(iter, dict.shallowCopy()); dict = resources.replaceKeyAndGetNew(iter, dict.shallowCopy());
rdicts.push_back(dict); rdicts.push_back(dict);
auto keys = dict.getKeys(); auto keys = dict.getKeys();
known_names.insert(keys.begin(), keys.end()); known_names.insert(keys.begin(), keys.end());
@ -1110,8 +1110,8 @@ QPDFPageObjectHelper::copyAnnotations(
afdh->addAndRenameFormFields(new_fields); afdh->addAndRenameFormFields(new_fields);
auto annots = this->oh.getKey("/Annots"); auto annots = this->oh.getKey("/Annots");
if (!annots.isArray()) { if (!annots.isArray()) {
annots = annots = this->oh.replaceKeyAndGetNew(
this->oh.replaceKeyAndGet("/Annots", QPDFObjectHandle::newArray()); "/Annots", QPDFObjectHandle::newArray());
} }
for (auto const& annot: new_annots) { for (auto const& annot: new_annots) {
annots.appendItem(annot); annots.appendItem(annot);

View File

@ -1571,7 +1571,7 @@ QPDFWriter::unparseObject(
"qpdf", "qpdf",
"QPDFWriter create Extensions", "QPDFWriter create Extensions",
this->m->qdf_mode ? 0 : 1); this->m->qdf_mode ? 0 : 1);
extensions = object.replaceKeyAndGet( extensions = object.replaceKeyAndGetNew(
"/Extensions", QPDFObjectHandle::newDictionary()); "/Extensions", QPDFObjectHandle::newDictionary());
} }
} else if (!have_extensions_other) { } else if (!have_extensions_other) {
@ -2277,7 +2277,7 @@ QPDFWriter::prepareFileForWrite()
if (oh.isIndirect()) { if (oh.isIndirect()) {
QTC::TC("qpdf", "QPDFWriter make Extensions direct"); QTC::TC("qpdf", "QPDFWriter make Extensions direct");
extensions_indirect = true; extensions_indirect = true;
oh = root.replaceKeyAndGet(key, oh.shallowCopy()); oh = root.replaceKeyAndGetNew(key, oh.shallowCopy());
} }
if (oh.hasKey("/ADBE")) { if (oh.hasKey("/ADBE")) {
QPDFObjectHandle adbe = oh.getKey("/ADBE"); QPDFObjectHandle adbe = oh.getKey("/ADBE");

View File

@ -163,9 +163,13 @@ For a detailed list of changes, please see the file
- See examples :file:`examples/qpdfjob-save-attachment.cc` and - See examples :file:`examples/qpdfjob-save-attachment.cc` and
:file:`examples/qpdfjob-c-save-attachment.cc`. :file:`examples/qpdfjob-c-save-attachment.cc`.
- New methods ``insertItemAndGet``, ``appendItemAndGet``, - In ``QPDFObjectHandle``, new methods ``insertItemAndGetNew``,
``eraseItemAndGet``, ``replaceKeyAndGet``, and ``appendItemAndGetNew``, and ``replaceKeyAndGetNew`` return the
``removeKeyAndGet`` return the newly added or removed object. newly added item. New methods ``eraseItemAndGetOld``,
``replaceKeyAndGetOld``, and ``removeKeyAndGetOld`` return the
item that was just removed or, in the case of
``replaceKeyAndGetOld``, a ``null`` object if the object was not
previously there.
- Add new ``Pipeline`` methods to reduce the amount of casting that is - Add new ``Pipeline`` methods to reduce the amount of casting that is
needed: needed:

View File

@ -1095,8 +1095,8 @@ test_27(QPDF& pdf, char const* arg2)
QPDFObjectHandle s2 = QPDFObjectHandle::newStream(&oldpdf, "potato\n"); QPDFObjectHandle s2 = QPDFObjectHandle::newStream(&oldpdf, "potato\n");
auto trailer = pdf.getTrailer(); auto trailer = pdf.getTrailer();
trailer.replaceKey("/QTest", pdf.copyForeignObject(qtest)); trailer.replaceKey("/QTest", pdf.copyForeignObject(qtest));
auto qtest2 = auto qtest2 = trailer.replaceKeyAndGetNew(
trailer.replaceKeyAndGet("/QTest2", QPDFObjectHandle::newArray()); "/QTest2", QPDFObjectHandle::newArray());
qtest2.appendItem(pdf.copyForeignObject(s1)); qtest2.appendItem(pdf.copyForeignObject(s1));
qtest2.appendItem(pdf.copyForeignObject(s2)); qtest2.appendItem(pdf.copyForeignObject(s2));
qtest2.appendItem(pdf.copyForeignObject(s3)); qtest2.appendItem(pdf.copyForeignObject(s3));
@ -3165,15 +3165,23 @@ test_88(QPDF& pdf, char const* arg2)
auto dict = QPDFObjectHandle::newDictionary(); auto dict = QPDFObjectHandle::newDictionary();
dict.replaceKey("/One", QPDFObjectHandle::newInteger(1)); dict.replaceKey("/One", QPDFObjectHandle::newInteger(1));
dict.replaceKey("/Two", QPDFObjectHandle::newInteger(2)); dict.replaceKey("/Two", QPDFObjectHandle::newInteger(2));
auto three = dict.replaceKeyAndGet("/Three", QPDFObjectHandle::newArray()); auto three =
dict.replaceKeyAndGetNew("/Three", QPDFObjectHandle::newArray());
three.appendItem("(a)"_qpdf); three.appendItem("(a)"_qpdf);
three.appendItem("(b)"_qpdf); three.appendItem("(b)"_qpdf);
auto newdict = three.appendItemAndGet(QPDFObjectHandle::newDictionary()); auto newdict = three.appendItemAndGetNew(QPDFObjectHandle::newDictionary());
newdict.replaceKey("/Z", "/Y"_qpdf); newdict.replaceKey("/Z", "/Y"_qpdf);
newdict.replaceKey("/X", "/W"_qpdf); newdict.replaceKey("/X", "/W"_qpdf);
dict.replaceKey("/Quack", "[1 2 3]"_qpdf);
auto quack = dict.replaceKeyAndGetOld("/Quack", "/Moo"_qpdf);
assert(quack.unparse() == "[ 1 2 3 ]");
auto nothing =
dict.replaceKeyAndGetOld("/NotThere", QPDFObjectHandle::newNull());
assert(nothing.isNull());
assert(dict.unparse() == R"( assert(dict.unparse() == R"(
<< <<
/One 1 /One 1
/Quack /Moo
/Two 2 /Two 2
/Three [ (a) (b) << /Z /Y /X /W >> ] /Three [ (a) (b) << /Z /Y /X /W >> ]
>> >>
@ -3184,7 +3192,7 @@ test_88(QPDF& pdf, char const* arg2)
assert( assert(
arr.unparse() == arr.unparse() ==
"[ (00) (0) (a) (b) << /Z /Y /X /W >> ]"_qpdf.unparse()); "[ (00) (0) (a) (b) << /Z /Y /X /W >> ]"_qpdf.unparse());
auto new_dict = arr.insertItemAndGet(1, "<< /P /Q /R /S >>"_qpdf); auto new_dict = arr.insertItemAndGetNew(1, "<< /P /Q /R /S >>"_qpdf);
arr.eraseItem(2); arr.eraseItem(2);
arr.eraseItem(0); arr.eraseItem(0);
assert( assert(
@ -3200,20 +3208,20 @@ test_88(QPDF& pdf, char const* arg2)
assert( assert(
arr.unparse() == arr.unparse() ==
"[ << /P /Q /T /U >> (a) (b) << /Z /Y /X /W >> ]"_qpdf.unparse()); "[ << /P /Q /T /U >> (a) (b) << /Z /Y /X /W >> ]"_qpdf.unparse());
auto s = arr.eraseItemAndGet(1); auto s = arr.eraseItemAndGetOld(1);
assert(s.unparse() == "(a)"); assert(s.unparse() == "(a)");
assert( assert(
arr.unparse() == arr.unparse() ==
"[ << /P /Q /T /U >> (b) << /Z /Y /X /W >> ]"_qpdf.unparse()); "[ << /P /Q /T /U >> (b) << /Z /Y /X /W >> ]"_qpdf.unparse());
assert(new_dict.removeKeyAndGet("/M").isNull()); assert(new_dict.removeKeyAndGetOld("/M").isNull());
assert(new_dict.removeKeyAndGet("/P").unparse() == "/Q"); assert(new_dict.removeKeyAndGetOld("/P").unparse() == "/Q");
assert(new_dict.unparse() == "<< /T /U >>"_qpdf.unparse()); assert(new_dict.unparse() == "<< /T /U >>"_qpdf.unparse());
// Test errors // Test errors
auto arr2 = pdf.getRoot().replaceKeyAndGet("/QTest", "[1 2]"_qpdf); auto arr2 = pdf.getRoot().replaceKeyAndGetNew("/QTest", "[1 2]"_qpdf);
arr2.setObjectDescription(&pdf, "test array"); arr2.setObjectDescription(&pdf, "test array");
assert(arr2.eraseItemAndGet(50).isNull()); assert(arr2.eraseItemAndGetOld(50).isNull());
} }
static void static void