Commit e80fad86e95af978ada2a6cc5c4b209a1f65f7c0

Authored by Jay Berkenbilt
1 parent ff73d71e

Add new QPDFObjectHandle methods for more fluent programming

ChangeLog
  1 +2022-04-29 Jay Berkenbilt <ejb@ql.org>
  2 +
  3 + * QPDFObjectHandle: for the methods insertItem, appendItem,
  4 + eraseItem, replaceKey, and removeKey, have the methods return a
  5 + reference to the original object, making a fluent interface to
  6 + initializing or modifying QPDFObjectHandle possible. Also, for
  7 + each one, add a corresponding "AndGet" method (insertItemAndGet,
  8 + appendItemAndGet, eraseItemAndGet, replaceKeyAndGet, and
  9 + removeKeyAndGet) that returns the newly inserted, replaced, or
  10 + removed item. This makes it possible to create a new object, add
  11 + it to an array or dictionary, and get a handle to it all in one
  12 + line.
  13 +
1 14 2022-04-24 Jay Berkenbilt <ejb@ql.org>
2 15  
3 16 * Bug fix: "removeAttachment" in the job JSON now takes an array
... ...
... ... @@ -474,13 +474,6 @@ This is a list of changes to make next time there is an ABI change.
474 474 Comments appear in the code prefixed by "ABI". Always Search for ABI
475 475 in source and header files to find items not listed here.
476 476  
477   -* Having QPDFObjectHandle setters return Class& to allow for
478   - use of fluent interfaces. This includes array and dictionary
479   - mutators.
480   - newDictionary().replaceKey("/X", "1"_qpdf).replaceKey("/Y", "(asdf)"_qpdf);
481   -* Add replaceKeyAndGet, appendItemAndGet, setArrayItemAndGet,
482   - insertItemAndGet that return the new item so you can say
483   - auto oh = dict.replaceKeyAndGet("/Key", QPDFObjectHandle::newSomething());
484 477 * Add getKeyOrInsert("/X", oh) that returns the existing value or adds
485 478 oh as the new value and returns it.
486 479 * Add default values to the getters, like getIntValue(default_value).
... ...
include/qpdf/QPDFObjectHandle.hh
... ... @@ -990,7 +990,20 @@ class QPDFObjectHandle
990 990 QPDF_DLL
991 991 QPDFObjectHandle copyStream();
992 992  
993   - // Mutator methods. Use with caution.
  993 + // Mutator methods.
  994 +
  995 + // Since qpdf 11: when a mutator object returns QPDFObjectHandle&,
  996 + // it is a reference to the object itself. This makes it possible
  997 + // to use a fluent style. For example:
  998 + //
  999 + // array.appendItem(i1).appendItem(i2);
  1000 + //
  1001 + // would append i1 and then i2 to the array. There are also items
  1002 + // that end with AndGet and return a QPDFObjectHandle. These
  1003 + // return the newly added object. For example:
  1004 + //
  1005 + // auto new_dict = dict.replaceKeyAndGet(
  1006 + // "/New", QPDFObjectHandle::newDictionary());
994 1007  
995 1008 // Recursively copy this object, making it direct. An exception is
996 1009 // thrown if a loop is detected. With allow_streams true, keep
... ... @@ -1010,31 +1023,54 @@ class QPDFObjectHandle
1010 1023 QPDF_DLL
1011 1024 void setArrayFromVector(std::vector<QPDFObjectHandle> const& items);
1012 1025 // Insert an item before the item at the given position ("at") so
1013   - // that it has that position after insertion. If "at" is equal to
1014   - // the size of the array, insert the item at the end.
  1026 + // that it has that position after insertion. If "at" is equal to
  1027 + // the size of the array, insert the item at the end. Return a
  1028 + // reference to the array (not the new item).
  1029 + QPDF_DLL
  1030 + QPDFObjectHandle& insertItem(int at, QPDFObjectHandle const& item);
  1031 + // Like insertItem but return the item that was inserted.
1015 1032 QPDF_DLL
1016   - void insertItem(int at, QPDFObjectHandle const& item);
  1033 + QPDFObjectHandle insertItemAndGet(int at, QPDFObjectHandle const& item);
  1034 + // Append an item, and return a reference to the original array
  1035 + // (not the new item).
1017 1036 QPDF_DLL
1018   - void appendItem(QPDFObjectHandle const& item);
  1037 + QPDFObjectHandle& appendItem(QPDFObjectHandle const& item);
  1038 + // Append an item, and return the newly added item.
  1039 + QPDF_DLL
  1040 + QPDFObjectHandle appendItemAndGet(QPDFObjectHandle const& item);
1019 1041 // Remove the item at that position, reducing the size of the
1020   - // array by one.
  1042 + // array by one. Return a reference the original array (not the
  1043 + // item that was removed).
  1044 + QPDF_DLL
  1045 + QPDFObjectHandle& eraseItem(int at);
  1046 + // Erase and item and return the item that was removed.
1021 1047 QPDF_DLL
1022   - void eraseItem(int at);
  1048 + QPDFObjectHandle eraseItemAndGet(int at);
1023 1049  
1024 1050 // Mutator methods for dictionary objects
1025 1051  
1026 1052 // Replace value of key, adding it if it does not exist. If value
1027   - // is null, remove the key.
  1053 + // is null, remove the key. Return a reference to the original
  1054 + // dictionary (not the new item).
  1055 + QPDF_DLL
  1056 + QPDFObjectHandle&
  1057 + replaceKey(std::string const& key, QPDFObjectHandle const& value);
  1058 + // Replace value of key and return the value.
1028 1059 QPDF_DLL
1029   - void replaceKey(std::string const& key, QPDFObjectHandle const& value);
1030   - // Remove key, doing nothing if key does not exist
  1060 + QPDFObjectHandle
  1061 + replaceKeyAndGet(std::string const& key, QPDFObjectHandle const& value);
  1062 + // Remove key, doing nothing if key does not exist. Return the
  1063 + // original dictionary (not the removed item).
1031 1064 QPDF_DLL
1032   - void removeKey(std::string const& key);
  1065 + QPDFObjectHandle& removeKey(std::string const& key);
  1066 + // Remove key and return the old value. If the old value didn't
  1067 + // exist, return a null object.
  1068 + QPDF_DLL
  1069 + QPDFObjectHandle removeKeyAndGet(std::string const& key);
1033 1070  
1034 1071 // ABI: Remove in qpdf 12
1035   - [[deprecated("use replaceKey -- it does the same thing")]]
1036   - QPDF_DLL
1037   - void replaceOrRemoveKey(std::string const& key, QPDFObjectHandle const&);
  1072 + [[deprecated("use replaceKey -- it does the same thing")]] QPDF_DLL void
  1073 + replaceOrRemoveKey(std::string const& key, QPDFObjectHandle const&);
1038 1074  
1039 1075 // Methods for stream objects
1040 1076 QPDF_DLL
... ...
libqpdf/QPDFObjectHandle.cc
... ... @@ -959,7 +959,7 @@ QPDFObjectHandle::setArrayFromVector(std::vector&lt;QPDFObjectHandle&gt; const&amp; items)
959 959 }
960 960 }
961 961  
962   -void
  962 +QPDFObjectHandle&
963 963 QPDFObjectHandle::insertItem(int at, QPDFObjectHandle const& item)
964 964 {
965 965 if (isArray()) {
... ... @@ -968,9 +968,17 @@ QPDFObjectHandle::insertItem(int at, QPDFObjectHandle const&amp; item)
968 968 typeWarning("array", "ignoring attempt to insert item");
969 969 QTC::TC("qpdf", "QPDFObjectHandle array ignoring insert item");
970 970 }
  971 + return *this;
971 972 }
972 973  
973   -void
  974 +QPDFObjectHandle
  975 +QPDFObjectHandle::insertItemAndGet(int at, QPDFObjectHandle const& item)
  976 +{
  977 + insertItem(at, item);
  978 + return item;
  979 +}
  980 +
  981 +QPDFObjectHandle&
974 982 QPDFObjectHandle::appendItem(QPDFObjectHandle const& item)
975 983 {
976 984 if (isArray()) {
... ... @@ -980,9 +988,17 @@ QPDFObjectHandle::appendItem(QPDFObjectHandle const&amp; item)
980 988 typeWarning("array", "ignoring attempt to append item");
981 989 QTC::TC("qpdf", "QPDFObjectHandle array ignoring append item");
982 990 }
  991 + return *this;
983 992 }
984 993  
985   -void
  994 +QPDFObjectHandle
  995 +QPDFObjectHandle::appendItemAndGet(QPDFObjectHandle const& item)
  996 +{
  997 + appendItem(item);
  998 + return item;
  999 +}
  1000 +
  1001 +QPDFObjectHandle&
986 1002 QPDFObjectHandle::eraseItem(int at)
987 1003 {
988 1004 if (isArray() && (at < getArrayNItems()) && (at >= 0)) {
... ... @@ -996,6 +1012,18 @@ QPDFObjectHandle::eraseItem(int at)
996 1012 QTC::TC("qpdf", "QPDFObjectHandle array ignoring erase item");
997 1013 }
998 1014 }
  1015 + return *this;
  1016 +}
  1017 +
  1018 +QPDFObjectHandle
  1019 +QPDFObjectHandle::eraseItemAndGet(int at)
  1020 +{
  1021 + auto result = QPDFObjectHandle::newNull();
  1022 + if (isArray() && (at < getArrayNItems()) && (at >= 0)) {
  1023 + result = getArrayItem(at);
  1024 + }
  1025 + eraseItem(at);
  1026 + return result;
999 1027 }
1000 1028  
1001 1029 // Dictionary accessors
... ... @@ -1267,7 +1295,7 @@ QPDFObjectHandle::getOwningQPDF()
1267 1295  
1268 1296 // Dictionary mutators
1269 1297  
1270   -void
  1298 +QPDFObjectHandle&
1271 1299 QPDFObjectHandle::replaceKey(
1272 1300 std::string const& key, QPDFObjectHandle const& value)
1273 1301 {
... ... @@ -1278,9 +1306,18 @@ QPDFObjectHandle::replaceKey(
1278 1306 typeWarning("dictionary", "ignoring key replacement request");
1279 1307 QTC::TC("qpdf", "QPDFObjectHandle dictionary ignoring replaceKey");
1280 1308 }
  1309 + return *this;
1281 1310 }
1282 1311  
1283   -void
  1312 +QPDFObjectHandle
  1313 +QPDFObjectHandle::replaceKeyAndGet(
  1314 + std::string const& key, QPDFObjectHandle const& value)
  1315 +{
  1316 + replaceKey(key, value);
  1317 + return value;
  1318 +}
  1319 +
  1320 +QPDFObjectHandle&
1284 1321 QPDFObjectHandle::removeKey(std::string const& key)
1285 1322 {
1286 1323 if (isDictionary()) {
... ... @@ -1289,6 +1326,18 @@ QPDFObjectHandle::removeKey(std::string const&amp; key)
1289 1326 typeWarning("dictionary", "ignoring key removal request");
1290 1327 QTC::TC("qpdf", "QPDFObjectHandle dictionary ignoring removeKey");
1291 1328 }
  1329 + return *this;
  1330 +}
  1331 +
  1332 +QPDFObjectHandle
  1333 +QPDFObjectHandle::removeKeyAndGet(std::string const& key)
  1334 +{
  1335 + auto result = QPDFObjectHandle::newNull();
  1336 + if (isDictionary()) {
  1337 + result = getKey(key);
  1338 + }
  1339 + removeKey(key);
  1340 + return result;
1292 1341 }
1293 1342  
1294 1343 void
... ...
manual/release-notes.rst
... ... @@ -73,6 +73,15 @@ For a detailed list of changes, please see the file
73 73 ``QPDFNumberTreeObjectHelper`` constructors that don't take a
74 74 ``QPDF&`` argument.
75 75  
  76 + - Library Enhancements
  77 +
  78 + - Support for more fluent programming with ``QPDFObjectHandle``.
  79 + The methods ``insertItem``, ``appendItem``, ``eraseItem``,
  80 + ``replaceKey``, and ``removeKey`` all return a reference to the
  81 + object being modified. New methods ``insertItemAndGet``,
  82 + ``appendItemAndGet``, ``eraseItemAndGet``, ``replaceKeyAndGet``,
  83 + and ``removeKeyAndGet`` return the newly added or removed object.
  84 +
76 85 - Other changes
77 86  
78 87 - A new chapter on contributing to qpdf has been added to the
... ...
qpdf/qtest/qpdf.test
... ... @@ -1440,13 +1440,16 @@ foreach (my $i = 1; $i &lt;= 3; ++$i)
1440 1440  
1441 1441 show_ntests();
1442 1442 # ----------
1443   -$td->notify("--- Dictionary keys ---");
1444   -$n_tests += 1;
  1443 +$td->notify("--- Miscellaneous QPDFObjectHandle API ---");
  1444 +$n_tests += 2;
1445 1445  
1446 1446 $td->runtest("dictionary keys",
1447 1447 {$td->COMMAND => "test_driver 87 - -"},
1448   - {$td->STRING => "test 87 done\n",
1449   - $td->EXIT_STATUS => 0},
  1448 + {$td->STRING => "test 87 done\n", $td->EXIT_STATUS => 0},
  1449 + $td->NORMALIZE_NEWLINES);
  1450 +$td->runtest("fluent interfaces",
  1451 + {$td->COMMAND => "test_driver 88 minimal.pdf -"},
  1452 + {$td->FILE => "test88.out", $td->EXIT_STATUS => 0},
1450 1453 $td->NORMALIZE_NEWLINES);
1451 1454  
1452 1455 show_ntests();
... ...
qpdf/qtest/qpdf/test88.out 0 → 100644
  1 +WARNING: test array: ignoring attempt to erase out of bounds array item
  2 +test 88 done
... ...
qpdf/test_driver.cc
... ... @@ -3178,6 +3178,63 @@ test_87(QPDF&amp; pdf, char const* arg2)
3178 3178 assert(dict.getJSON().unparse() == "{\n \"/A\": 2\n}");
3179 3179 }
3180 3180  
  3181 +static void
  3182 +test_88(QPDF& pdf, char const* arg2)
  3183 +{
  3184 + // Exercise fluent QPDFObjectHandle mutators and similar methods
  3185 + // added for qpdf 11.
  3186 + auto dict = QPDFObjectHandle::newDictionary()
  3187 + .replaceKey("/One", QPDFObjectHandle::newInteger(1))
  3188 + .replaceKey("/Two", QPDFObjectHandle::newInteger(2));
  3189 + dict.replaceKeyAndGet("/Three", QPDFObjectHandle::newArray())
  3190 + .appendItem("(a)"_qpdf)
  3191 + .appendItem("(b)"_qpdf)
  3192 + .appendItemAndGet(QPDFObjectHandle::newDictionary())
  3193 + .replaceKey("/Z", "/Y"_qpdf)
  3194 + .replaceKey("/X", "/W"_qpdf);
  3195 + assert(dict.unparse() == R"(
  3196 + <<
  3197 + /One 1
  3198 + /Two 2
  3199 + /Three [ (a) (b) << /Z /Y /X /W >> ]
  3200 + >>
  3201 + )"_qpdf.unparse());
  3202 + auto arr = dict.getKey("/Three")
  3203 + .insertItem(0, QPDFObjectHandle::newString("0"))
  3204 + .insertItem(0, QPDFObjectHandle::newString("00"));
  3205 + assert(
  3206 + arr.unparse() ==
  3207 + "[ (00) (0) (a) (b) << /Z /Y /X /W >> ]"_qpdf.unparse());
  3208 + auto new_dict = arr.insertItemAndGet(1, "<< /P /Q /R /S >>"_qpdf);
  3209 + arr.eraseItem(2).eraseItem(0);
  3210 + assert(
  3211 + arr.unparse() ==
  3212 + "[ << /P /Q /R /S >> (a) (b) << /Z /Y /X /W >> ]"_qpdf.unparse());
  3213 +
  3214 + // new_dict shares internals with the one in the array. It has
  3215 + // always been this way, and there is code that relies on this
  3216 + // behavior. Maybe it would be different if I could start over
  3217 + // again...
  3218 + new_dict.removeKey("/R").replaceKey("/T", "/U"_qpdf);
  3219 + assert(
  3220 + arr.unparse() ==
  3221 + "[ << /P /Q /T /U >> (a) (b) << /Z /Y /X /W >> ]"_qpdf.unparse());
  3222 + auto s = arr.eraseItemAndGet(1);
  3223 + assert(s.unparse() == "(a)");
  3224 + assert(
  3225 + arr.unparse() ==
  3226 + "[ << /P /Q /T /U >> (b) << /Z /Y /X /W >> ]"_qpdf.unparse());
  3227 +
  3228 + assert(new_dict.removeKeyAndGet("/M").isNull());
  3229 + assert(new_dict.removeKeyAndGet("/P").unparse() == "/Q");
  3230 + assert(new_dict.unparse() == "<< /T /U >>"_qpdf.unparse());
  3231 +
  3232 + // Test errors
  3233 + auto arr2 = pdf.getRoot().replaceKeyAndGet("/QTest", "[1 2]"_qpdf);
  3234 + arr2.setObjectDescription(&pdf, "test array");
  3235 + assert(arr2.eraseItemAndGet(50).isNull());
  3236 +}
  3237 +
3181 3238 void
3182 3239 runtest(int n, char const* filename1, char const* arg2)
3183 3240 {
... ... @@ -3280,7 +3337,7 @@ runtest(int n, char const* filename1, char const* arg2)
3280 3337 {76, test_76}, {77, test_77}, {78, test_78}, {79, test_79},
3281 3338 {80, test_80}, {81, test_81}, {82, test_82}, {83, test_83},
3282 3339 {84, test_84}, {85, test_85}, {86, test_86}, {87, test_87},
3283   - };
  3340 + {88, test_88}};
3284 3341  
3285 3342 auto fn = test_functions.find(n);
3286 3343 if (fn == test_functions.end()) {
... ...