Commit de0b11fc4793213dc6156d34412580a6e4df0c48
1 parent
35e7859b
Add C++ iterator API around array and dictionary objects
Showing
5 changed files
with
427 additions
and
14 deletions
ChangeLog
| 1 | 1 | 2021-01-29 Jay Berkenbilt <ejb@ql.org> |
| 2 | 2 | |
| 3 | + * Add wrappers QPDFDictItems and QPDFArrayItems around | |
| 4 | + QPDFObjectHandle that provide a C++ iterator API, including C++11 | |
| 5 | + range-for iteration, over arrays and dictionaries. With this, you | |
| 6 | + can do | |
| 7 | + | |
| 8 | + for (auto i: QPDFDictItems(oh)) | |
| 9 | + { | |
| 10 | + // i.first is a string, i.second is a QPDFObjectHandle | |
| 11 | + } | |
| 12 | + for (auto i: QPDFArrayItems(oh)) | |
| 13 | + { | |
| 14 | + // i is a QPDFObjectHandle | |
| 15 | + } | |
| 16 | + | |
| 3 | 17 | * QPDFObjectHandle::is* methods to check type now return false on |
| 4 | 18 | uninitialized objects rather than crashing or throwing a logic |
| 5 | 19 | error. | ... | ... |
include/qpdf/QPDFObjectHandle.hh
| ... | ... | @@ -631,7 +631,8 @@ class QPDFObjectHandle |
| 631 | 631 | QPDF_DLL |
| 632 | 632 | std::string getInlineImageValue(); |
| 633 | 633 | |
| 634 | - // Methods for array objects; see also name and array objects | |
| 634 | + // Methods for array objects; see also name and array objects. See | |
| 635 | + // also QPDFArrayItems later in this file. | |
| 635 | 636 | QPDF_DLL |
| 636 | 637 | int getArrayNItems(); |
| 637 | 638 | QPDF_DLL |
| ... | ... | @@ -655,7 +656,8 @@ class QPDFObjectHandle |
| 655 | 656 | QPDF_DLL |
| 656 | 657 | Matrix getArrayAsMatrix(); |
| 657 | 658 | |
| 658 | - // Methods for dictionary objects | |
| 659 | + // Methods for dictionary objects. See also QPDFDictItems later in | |
| 660 | + // this file. | |
| 659 | 661 | QPDF_DLL |
| 660 | 662 | bool hasKey(std::string const&); |
| 661 | 663 | QPDF_DLL |
| ... | ... | @@ -1224,4 +1226,179 @@ class QPDFObjectHandle |
| 1224 | 1226 | bool reserved; |
| 1225 | 1227 | }; |
| 1226 | 1228 | |
| 1229 | +class QPDFDictItems | |
| 1230 | +{ | |
| 1231 | + // This class allows C++-style iteration, including range-for | |
| 1232 | + // iteration, around dictionaries. You can write | |
| 1233 | + | |
| 1234 | + // for (auto iter: QPDFDictItems(dictionary_obj)) | |
| 1235 | + // { | |
| 1236 | + // // iter.first is a string | |
| 1237 | + // // iter.second is a QPDFObjectHandle | |
| 1238 | + // } | |
| 1239 | + | |
| 1240 | + public: | |
| 1241 | + QPDF_DLL | |
| 1242 | + QPDFDictItems(QPDFObjectHandle& oh); | |
| 1243 | + | |
| 1244 | + class iterator: public std::iterator< | |
| 1245 | + std::bidirectional_iterator_tag, | |
| 1246 | + std::pair<std::string, QPDFObjectHandle>> | |
| 1247 | + { | |
| 1248 | + friend class QPDFDictItems; | |
| 1249 | + public: | |
| 1250 | + QPDF_DLL | |
| 1251 | + virtual ~iterator() = default; | |
| 1252 | + QPDF_DLL | |
| 1253 | + iterator& operator++(); | |
| 1254 | + QPDF_DLL | |
| 1255 | + iterator operator++(int) | |
| 1256 | + { | |
| 1257 | + iterator t = *this; | |
| 1258 | + ++(*this); | |
| 1259 | + return t; | |
| 1260 | + } | |
| 1261 | + QPDF_DLL | |
| 1262 | + iterator& operator--(); | |
| 1263 | + QPDF_DLL | |
| 1264 | + iterator operator--(int) | |
| 1265 | + { | |
| 1266 | + iterator t = *this; | |
| 1267 | + --(*this); | |
| 1268 | + return t; | |
| 1269 | + } | |
| 1270 | + QPDF_DLL | |
| 1271 | + reference operator*(); | |
| 1272 | + QPDF_DLL | |
| 1273 | + pointer operator->(); | |
| 1274 | + QPDF_DLL | |
| 1275 | + bool operator==(iterator const& other) const; | |
| 1276 | + QPDF_DLL | |
| 1277 | + bool operator!=(iterator const& other) const | |
| 1278 | + { | |
| 1279 | + return ! operator==(other); | |
| 1280 | + } | |
| 1281 | + | |
| 1282 | + private: | |
| 1283 | + iterator(QPDFObjectHandle& oh, bool for_begin); | |
| 1284 | + void updateIValue(); | |
| 1285 | + | |
| 1286 | + class Members | |
| 1287 | + { | |
| 1288 | + friend class QPDFDictItems::iterator; | |
| 1289 | + | |
| 1290 | + public: | |
| 1291 | + QPDF_DLL | |
| 1292 | + ~Members() = default; | |
| 1293 | + | |
| 1294 | + private: | |
| 1295 | + Members(QPDFObjectHandle& oh, bool for_begin); | |
| 1296 | + Members() = delete; | |
| 1297 | + Members(Members const&) = delete; | |
| 1298 | + | |
| 1299 | + QPDFObjectHandle& oh; | |
| 1300 | + std::set<std::string> keys; | |
| 1301 | + std::set<std::string>::iterator iter; | |
| 1302 | + bool is_end; | |
| 1303 | + }; | |
| 1304 | + PointerHolder<Members> m; | |
| 1305 | + value_type ivalue; | |
| 1306 | + }; | |
| 1307 | + | |
| 1308 | + QPDF_DLL | |
| 1309 | + iterator begin(); | |
| 1310 | + QPDF_DLL | |
| 1311 | + iterator end(); | |
| 1312 | + | |
| 1313 | + private: | |
| 1314 | + QPDFObjectHandle& oh; | |
| 1315 | +}; | |
| 1316 | + | |
| 1317 | +class QPDFArrayItems | |
| 1318 | +{ | |
| 1319 | + // This class allows C++-style iteration, including range-for | |
| 1320 | + // iteration, around arrays. You can write | |
| 1321 | + | |
| 1322 | + // for (auto iter: QPDFArrayItems(array_obj)) | |
| 1323 | + // { | |
| 1324 | + // // iter is a QPDFObjectHandle | |
| 1325 | + // } | |
| 1326 | + | |
| 1327 | + public: | |
| 1328 | + QPDF_DLL | |
| 1329 | + QPDFArrayItems(QPDFObjectHandle& oh); | |
| 1330 | + | |
| 1331 | + class iterator: public std::iterator< | |
| 1332 | + std::bidirectional_iterator_tag, | |
| 1333 | + QPDFObjectHandle> | |
| 1334 | + { | |
| 1335 | + friend class QPDFArrayItems; | |
| 1336 | + public: | |
| 1337 | + QPDF_DLL | |
| 1338 | + virtual ~iterator() = default; | |
| 1339 | + QPDF_DLL | |
| 1340 | + iterator& operator++(); | |
| 1341 | + QPDF_DLL | |
| 1342 | + iterator operator++(int) | |
| 1343 | + { | |
| 1344 | + iterator t = *this; | |
| 1345 | + ++(*this); | |
| 1346 | + return t; | |
| 1347 | + } | |
| 1348 | + QPDF_DLL | |
| 1349 | + iterator& operator--(); | |
| 1350 | + QPDF_DLL | |
| 1351 | + iterator operator--(int) | |
| 1352 | + { | |
| 1353 | + iterator t = *this; | |
| 1354 | + --(*this); | |
| 1355 | + return t; | |
| 1356 | + } | |
| 1357 | + QPDF_DLL | |
| 1358 | + reference operator*(); | |
| 1359 | + QPDF_DLL | |
| 1360 | + pointer operator->(); | |
| 1361 | + QPDF_DLL | |
| 1362 | + bool operator==(iterator const& other) const; | |
| 1363 | + QPDF_DLL | |
| 1364 | + bool operator!=(iterator const& other) const | |
| 1365 | + { | |
| 1366 | + return ! operator==(other); | |
| 1367 | + } | |
| 1368 | + | |
| 1369 | + private: | |
| 1370 | + iterator(QPDFObjectHandle& oh, bool for_begin); | |
| 1371 | + void updateIValue(); | |
| 1372 | + | |
| 1373 | + class Members | |
| 1374 | + { | |
| 1375 | + friend class QPDFArrayItems::iterator; | |
| 1376 | + | |
| 1377 | + public: | |
| 1378 | + QPDF_DLL | |
| 1379 | + ~Members() = default; | |
| 1380 | + | |
| 1381 | + private: | |
| 1382 | + Members(QPDFObjectHandle& oh, bool for_begin); | |
| 1383 | + Members() = delete; | |
| 1384 | + Members(Members const&) = delete; | |
| 1385 | + | |
| 1386 | + QPDFObjectHandle& oh; | |
| 1387 | + int item_number; | |
| 1388 | + bool is_end; | |
| 1389 | + }; | |
| 1390 | + PointerHolder<Members> m; | |
| 1391 | + value_type ivalue; | |
| 1392 | + }; | |
| 1393 | + | |
| 1394 | + QPDF_DLL | |
| 1395 | + iterator begin(); | |
| 1396 | + QPDF_DLL | |
| 1397 | + iterator end(); | |
| 1398 | + | |
| 1399 | + private: | |
| 1400 | + QPDFObjectHandle& oh; | |
| 1401 | +}; | |
| 1402 | + | |
| 1403 | + | |
| 1227 | 1404 | #endif // QPDFOBJECTHANDLE_HH | ... | ... |
libqpdf/QPDFObjectHandle.cc
| ... | ... | @@ -3079,3 +3079,180 @@ QPDFObjectHandle::warn(QPDF* qpdf, QPDFExc const& e) |
| 3079 | 3079 | throw e; |
| 3080 | 3080 | } |
| 3081 | 3081 | } |
| 3082 | + | |
| 3083 | +QPDFDictItems::QPDFDictItems(QPDFObjectHandle& oh) : | |
| 3084 | + oh(oh) | |
| 3085 | +{ | |
| 3086 | +} | |
| 3087 | + | |
| 3088 | +QPDFDictItems::iterator& | |
| 3089 | +QPDFDictItems::iterator::operator++() | |
| 3090 | +{ | |
| 3091 | + ++this->m->iter; | |
| 3092 | + updateIValue(); | |
| 3093 | + return *this; | |
| 3094 | +} | |
| 3095 | + | |
| 3096 | +QPDFDictItems::iterator& | |
| 3097 | +QPDFDictItems::iterator::operator--() | |
| 3098 | +{ | |
| 3099 | + --this->m->iter; | |
| 3100 | + updateIValue(); | |
| 3101 | + return *this; | |
| 3102 | +} | |
| 3103 | + | |
| 3104 | +QPDFDictItems::iterator::reference | |
| 3105 | +QPDFDictItems::iterator:: operator*() | |
| 3106 | +{ | |
| 3107 | + updateIValue(); | |
| 3108 | + return this->ivalue; | |
| 3109 | +} | |
| 3110 | + | |
| 3111 | +QPDFDictItems::iterator::pointer | |
| 3112 | +QPDFDictItems::iterator::operator->() | |
| 3113 | +{ | |
| 3114 | + updateIValue(); | |
| 3115 | + return &this->ivalue; | |
| 3116 | +} | |
| 3117 | + | |
| 3118 | +bool | |
| 3119 | +QPDFDictItems::iterator::operator==(iterator const& other) const | |
| 3120 | +{ | |
| 3121 | + if (this->m->is_end && other.m->is_end) | |
| 3122 | + { | |
| 3123 | + return true; | |
| 3124 | + } | |
| 3125 | + if (this->m->is_end || other.m->is_end) | |
| 3126 | + { | |
| 3127 | + return false; | |
| 3128 | + } | |
| 3129 | + return (this->ivalue.first == other.ivalue.first); | |
| 3130 | +} | |
| 3131 | + | |
| 3132 | +QPDFDictItems::iterator::iterator(QPDFObjectHandle& oh, bool for_begin) : | |
| 3133 | + m(new Members(oh, for_begin)) | |
| 3134 | +{ | |
| 3135 | + updateIValue(); | |
| 3136 | +} | |
| 3137 | + | |
| 3138 | +void | |
| 3139 | +QPDFDictItems::iterator::updateIValue() | |
| 3140 | +{ | |
| 3141 | + this->m->is_end = (this->m->iter == this->m->keys.end()); | |
| 3142 | + if (this->m->is_end) | |
| 3143 | + { | |
| 3144 | + this->ivalue.first = ""; | |
| 3145 | + this->ivalue.second = QPDFObjectHandle(); | |
| 3146 | + } | |
| 3147 | + else | |
| 3148 | + { | |
| 3149 | + this->ivalue.first = *(this->m->iter); | |
| 3150 | + this->ivalue.second = this->m->oh.getKey(this->ivalue.first); | |
| 3151 | + } | |
| 3152 | +} | |
| 3153 | + | |
| 3154 | +QPDFDictItems::iterator::Members::Members( | |
| 3155 | + QPDFObjectHandle& oh, bool for_begin) : | |
| 3156 | + oh(oh) | |
| 3157 | +{ | |
| 3158 | + this->keys = oh.getKeys(); | |
| 3159 | + this->iter = for_begin ? this->keys.begin() : this->keys.end(); | |
| 3160 | +} | |
| 3161 | + | |
| 3162 | +QPDFDictItems::iterator | |
| 3163 | +QPDFDictItems::begin() | |
| 3164 | +{ | |
| 3165 | + return iterator(oh, true); | |
| 3166 | +} | |
| 3167 | + | |
| 3168 | +QPDFDictItems::iterator | |
| 3169 | +QPDFDictItems::end() | |
| 3170 | +{ | |
| 3171 | + return iterator(oh, false); | |
| 3172 | +} | |
| 3173 | + | |
| 3174 | +QPDFArrayItems::QPDFArrayItems(QPDFObjectHandle& oh) : | |
| 3175 | + oh(oh) | |
| 3176 | +{ | |
| 3177 | +} | |
| 3178 | + | |
| 3179 | +QPDFArrayItems::iterator& | |
| 3180 | +QPDFArrayItems::iterator::operator++() | |
| 3181 | +{ | |
| 3182 | + if (! this->m->is_end) | |
| 3183 | + { | |
| 3184 | + ++this->m->item_number; | |
| 3185 | + updateIValue(); | |
| 3186 | + } | |
| 3187 | + return *this; | |
| 3188 | +} | |
| 3189 | + | |
| 3190 | +QPDFArrayItems::iterator& | |
| 3191 | +QPDFArrayItems::iterator::operator--() | |
| 3192 | +{ | |
| 3193 | + if (this->m->item_number > 0) | |
| 3194 | + { | |
| 3195 | + --this->m->item_number; | |
| 3196 | + updateIValue(); | |
| 3197 | + } | |
| 3198 | + return *this; | |
| 3199 | +} | |
| 3200 | + | |
| 3201 | +QPDFArrayItems::iterator::reference | |
| 3202 | +QPDFArrayItems::iterator:: operator*() | |
| 3203 | +{ | |
| 3204 | + updateIValue(); | |
| 3205 | + return this->ivalue; | |
| 3206 | +} | |
| 3207 | + | |
| 3208 | +QPDFArrayItems::iterator::pointer | |
| 3209 | +QPDFArrayItems::iterator::operator->() | |
| 3210 | +{ | |
| 3211 | + updateIValue(); | |
| 3212 | + return &this->ivalue; | |
| 3213 | +} | |
| 3214 | + | |
| 3215 | +bool | |
| 3216 | +QPDFArrayItems::iterator::operator==(iterator const& other) const | |
| 3217 | +{ | |
| 3218 | + return (this->m->item_number == other.m->item_number); | |
| 3219 | +} | |
| 3220 | + | |
| 3221 | +QPDFArrayItems::iterator::iterator(QPDFObjectHandle& oh, bool for_begin) : | |
| 3222 | + m(new Members(oh, for_begin)) | |
| 3223 | +{ | |
| 3224 | + updateIValue(); | |
| 3225 | +} | |
| 3226 | + | |
| 3227 | +void | |
| 3228 | +QPDFArrayItems::iterator::updateIValue() | |
| 3229 | +{ | |
| 3230 | + this->m->is_end = (this->m->item_number >= this->m->oh.getArrayNItems()); | |
| 3231 | + if (this->m->is_end) | |
| 3232 | + { | |
| 3233 | + this->ivalue = QPDFObjectHandle(); | |
| 3234 | + } | |
| 3235 | + else | |
| 3236 | + { | |
| 3237 | + this->ivalue = this->m->oh.getArrayItem(this->m->item_number); | |
| 3238 | + } | |
| 3239 | +} | |
| 3240 | + | |
| 3241 | +QPDFArrayItems::iterator::Members::Members( | |
| 3242 | + QPDFObjectHandle& oh, bool for_begin) : | |
| 3243 | + oh(oh) | |
| 3244 | +{ | |
| 3245 | + this->item_number = for_begin ? 0 : oh.getArrayNItems(); | |
| 3246 | +} | |
| 3247 | + | |
| 3248 | +QPDFArrayItems::iterator | |
| 3249 | +QPDFArrayItems::begin() | |
| 3250 | +{ | |
| 3251 | + return iterator(oh, true); | |
| 3252 | +} | |
| 3253 | + | |
| 3254 | +QPDFArrayItems::iterator | |
| 3255 | +QPDFArrayItems::end() | |
| 3256 | +{ | |
| 3257 | + return iterator(oh, false); | |
| 3258 | +} | ... | ... |
manual/qpdf-manual.xml
| ... | ... | @@ -4849,6 +4849,17 @@ print "\n"; |
| 4849 | 4849 | <itemizedlist> |
| 4850 | 4850 | <listitem> |
| 4851 | 4851 | <para> |
| 4852 | + Add <classname>QPDFDictItems</classname> and | |
| 4853 | + <classname>QPDFArrayItems</classname> wrappers around | |
| 4854 | + <classname>QPDFObjectHandle</classname>, allowing C++-style | |
| 4855 | + iteration, including range-for iteration, over dictionary | |
| 4856 | + and array QPDFObjectHandles. See comments in | |
| 4857 | + <filename>include/qpdf/QPDFObjectHandle.hh</filename> for | |
| 4858 | + details. | |
| 4859 | + </para> | |
| 4860 | + </listitem> | |
| 4861 | + <listitem> | |
| 4862 | + <para> | |
| 4852 | 4863 | Add <function>warn</function> to |
| 4853 | 4864 | <classname>QPDF</classname>'s public API. |
| 4854 | 4865 | </para> | ... | ... |
qpdf/test_driver.cc
| ... | ... | @@ -347,30 +347,29 @@ void runtest(int n, char const* filename1, char const* arg2) |
| 347 | 347 | else if (qtest.isArray()) |
| 348 | 348 | { |
| 349 | 349 | QTC::TC("qpdf", "main QTest array"); |
| 350 | - int nitems = qtest.getArrayNItems(); | |
| 351 | 350 | std::cout << "/QTest is an array with " |
| 352 | - << nitems << " items" << std::endl; | |
| 353 | - for (int i = 0; i < nitems; ++i) | |
| 351 | + << qtest.getArrayNItems() << " items" << std::endl; | |
| 352 | + int i = 0; | |
| 353 | + for (auto& iter: QPDFArrayItems(qtest)) | |
| 354 | 354 | { |
| 355 | 355 | QTC::TC("qpdf", "main QTest array indirect", |
| 356 | - qtest.getArrayItem(i).isIndirect() ? 1 : 0); | |
| 356 | + iter.isIndirect() ? 1 : 0); | |
| 357 | 357 | std::cout << " item " << i << " is " |
| 358 | - << (qtest.getArrayItem(i).isIndirect() ? "in" : "") | |
| 358 | + << (iter.isIndirect() ? "in" : "") | |
| 359 | 359 | << "direct" << std::endl; |
| 360 | + ++i; | |
| 360 | 361 | } |
| 361 | 362 | } |
| 362 | 363 | else if (qtest.isDictionary()) |
| 363 | 364 | { |
| 364 | 365 | QTC::TC("qpdf", "main QTest dictionary"); |
| 365 | 366 | std::cout << "/QTest is a dictionary" << std::endl; |
| 366 | - std::set<std::string> keys = qtest.getKeys(); | |
| 367 | - for (std::set<std::string>::iterator iter = keys.begin(); | |
| 368 | - iter != keys.end(); ++iter) | |
| 369 | - { | |
| 367 | + for (auto& iter: QPDFDictItems(qtest)) | |
| 368 | + { | |
| 370 | 369 | QTC::TC("qpdf", "main QTest dictionary indirect", |
| 371 | - (qtest.getKey(*iter).isIndirect() ? 1 : 0)); | |
| 372 | - std::cout << " " << *iter << " is " | |
| 373 | - << (qtest.getKey(*iter).isIndirect() ? "in" : "") | |
| 370 | + iter.second.isIndirect() ? 1 : 0); | |
| 371 | + std::cout << " " << iter.first << " is " | |
| 372 | + << (iter.second.isIndirect() ? "in" : "") | |
| 374 | 373 | << "direct" << std::endl; |
| 375 | 374 | } |
| 376 | 375 | } |
| ... | ... | @@ -1539,7 +1538,38 @@ void runtest(int n, char const* filename1, char const* arg2) |
| 1539 | 1538 | QPDFObjectHandle integer = qtest.getKey("/Integer"); |
| 1540 | 1539 | QPDFObjectHandle null = QPDFObjectHandle::newNull(); |
| 1541 | 1540 | assert(array.isArray()); |
| 1541 | + { | |
| 1542 | + // Exercise iterators directly | |
| 1543 | + QPDFArrayItems ai(array); | |
| 1544 | + auto i = ai.begin(); | |
| 1545 | + assert(i->getName() == "/Item0"); | |
| 1546 | + auto& i_value = *i; | |
| 1547 | + --i; | |
| 1548 | + assert(i->getName() == "/Item0"); | |
| 1549 | + ++i; | |
| 1550 | + ++i; | |
| 1551 | + ++i; | |
| 1552 | + assert(i == ai.end()); | |
| 1553 | + ++i; | |
| 1554 | + assert(i == ai.end()); | |
| 1555 | + assert(! i_value.isInitialized()); | |
| 1556 | + --i; | |
| 1557 | + assert(i_value.getName() == "/Item2"); | |
| 1558 | + assert(i->getName() == "/Item2"); | |
| 1559 | + } | |
| 1542 | 1560 | assert(dictionary.isDictionary()); |
| 1561 | + { | |
| 1562 | + // Exercise iterators directly | |
| 1563 | + QPDFDictItems di(dictionary); | |
| 1564 | + auto i = di.begin(); | |
| 1565 | + assert(i->first == "/Key1"); | |
| 1566 | + auto& i_value = *i; | |
| 1567 | + assert(i->second.getName() == "/Value1"); | |
| 1568 | + ++i; | |
| 1569 | + ++i; | |
| 1570 | + assert(i == di.end()); | |
| 1571 | + assert(! i_value.second.isInitialized()); | |
| 1572 | + } | |
| 1543 | 1573 | assert("" == qtest.getStringValue()); |
| 1544 | 1574 | array.getArrayItem(-1).assertNull(); |
| 1545 | 1575 | array.getArrayItem(16059).assertNull(); |
| ... | ... | @@ -1599,6 +1629,10 @@ void runtest(int n, char const* filename1, char const* arg2) |
| 1599 | 1629 | (r1.lly > 3.39) && (r1.lly < 3.41) && |
| 1600 | 1630 | (r1.urx > 5.59) && (r1.urx < 5.61) && |
| 1601 | 1631 | (r1.ury > 7.79) && (r1.ury < 7.81)); |
| 1632 | + QPDFObjectHandle uninitialized; | |
| 1633 | + assert(! uninitialized.isInitialized()); | |
| 1634 | + assert(! uninitialized.isInteger()); | |
| 1635 | + assert(! uninitialized.isDictionary()); | |
| 1602 | 1636 | } |
| 1603 | 1637 | else if (n == 43) |
| 1604 | 1638 | { | ... | ... |