Commit e80fad86e95af978ada2a6cc5c4b209a1f65f7c0
1 parent
ff73d71e
Add new QPDFObjectHandle methods for more fluent programming
Showing
8 changed files
with
193 additions
and
31 deletions
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 | ... | ... |
TODO
| ... | ... | @@ -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<QPDFObjectHandle> const& 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& 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& 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& 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 <= 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
qpdf/test_driver.cc
| ... | ... | @@ -3178,6 +3178,63 @@ test_87(QPDF& 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()) { | ... | ... |