Commit de0b11fc4793213dc6156d34412580a6e4df0c48

Authored by Jay Berkenbilt
1 parent 35e7859b

Add C++ iterator API around array and dictionary objects

ChangeLog
1 2021-01-29 Jay Berkenbilt <ejb@ql.org> 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 * QPDFObjectHandle::is* methods to check type now return false on 17 * QPDFObjectHandle::is* methods to check type now return false on
4 uninitialized objects rather than crashing or throwing a logic 18 uninitialized objects rather than crashing or throwing a logic
5 error. 19 error.
include/qpdf/QPDFObjectHandle.hh
@@ -631,7 +631,8 @@ class QPDFObjectHandle @@ -631,7 +631,8 @@ class QPDFObjectHandle
631 QPDF_DLL 631 QPDF_DLL
632 std::string getInlineImageValue(); 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 QPDF_DLL 636 QPDF_DLL
636 int getArrayNItems(); 637 int getArrayNItems();
637 QPDF_DLL 638 QPDF_DLL
@@ -655,7 +656,8 @@ class QPDFObjectHandle @@ -655,7 +656,8 @@ class QPDFObjectHandle
655 QPDF_DLL 656 QPDF_DLL
656 Matrix getArrayAsMatrix(); 657 Matrix getArrayAsMatrix();
657 658
658 - // Methods for dictionary objects 659 + // Methods for dictionary objects. See also QPDFDictItems later in
  660 + // this file.
659 QPDF_DLL 661 QPDF_DLL
660 bool hasKey(std::string const&); 662 bool hasKey(std::string const&);
661 QPDF_DLL 663 QPDF_DLL
@@ -1224,4 +1226,179 @@ class QPDFObjectHandle @@ -1224,4 +1226,179 @@ class QPDFObjectHandle
1224 bool reserved; 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 #endif // QPDFOBJECTHANDLE_HH 1404 #endif // QPDFOBJECTHANDLE_HH
libqpdf/QPDFObjectHandle.cc
@@ -3079,3 +3079,180 @@ QPDFObjectHandle::warn(QPDF* qpdf, QPDFExc const&amp; e) @@ -3079,3 +3079,180 @@ QPDFObjectHandle::warn(QPDF* qpdf, QPDFExc const&amp; e)
3079 throw e; 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 &quot;\n&quot;; @@ -4849,6 +4849,17 @@ print &quot;\n&quot;;
4849 <itemizedlist> 4849 <itemizedlist>
4850 <listitem> 4850 <listitem>
4851 <para> 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 Add <function>warn</function> to 4863 Add <function>warn</function> to
4853 <classname>QPDF</classname>'s public API. 4864 <classname>QPDF</classname>'s public API.
4854 </para> 4865 </para>
qpdf/test_driver.cc
@@ -347,30 +347,29 @@ void runtest(int n, char const* filename1, char const* arg2) @@ -347,30 +347,29 @@ void runtest(int n, char const* filename1, char const* arg2)
347 else if (qtest.isArray()) 347 else if (qtest.isArray())
348 { 348 {
349 QTC::TC("qpdf", "main QTest array"); 349 QTC::TC("qpdf", "main QTest array");
350 - int nitems = qtest.getArrayNItems();  
351 std::cout << "/QTest is an array with " 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 QTC::TC("qpdf", "main QTest array indirect", 355 QTC::TC("qpdf", "main QTest array indirect",
356 - qtest.getArrayItem(i).isIndirect() ? 1 : 0); 356 + iter.isIndirect() ? 1 : 0);
357 std::cout << " item " << i << " is " 357 std::cout << " item " << i << " is "
358 - << (qtest.getArrayItem(i).isIndirect() ? "in" : "") 358 + << (iter.isIndirect() ? "in" : "")
359 << "direct" << std::endl; 359 << "direct" << std::endl;
  360 + ++i;
360 } 361 }
361 } 362 }
362 else if (qtest.isDictionary()) 363 else if (qtest.isDictionary())
363 { 364 {
364 QTC::TC("qpdf", "main QTest dictionary"); 365 QTC::TC("qpdf", "main QTest dictionary");
365 std::cout << "/QTest is a dictionary" << std::endl; 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 QTC::TC("qpdf", "main QTest dictionary indirect", 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 << "direct" << std::endl; 373 << "direct" << std::endl;
375 } 374 }
376 } 375 }
@@ -1539,7 +1538,38 @@ void runtest(int n, char const* filename1, char const* arg2) @@ -1539,7 +1538,38 @@ void runtest(int n, char const* filename1, char const* arg2)
1539 QPDFObjectHandle integer = qtest.getKey("/Integer"); 1538 QPDFObjectHandle integer = qtest.getKey("/Integer");
1540 QPDFObjectHandle null = QPDFObjectHandle::newNull(); 1539 QPDFObjectHandle null = QPDFObjectHandle::newNull();
1541 assert(array.isArray()); 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 assert(dictionary.isDictionary()); 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 assert("" == qtest.getStringValue()); 1573 assert("" == qtest.getStringValue());
1544 array.getArrayItem(-1).assertNull(); 1574 array.getArrayItem(-1).assertNull();
1545 array.getArrayItem(16059).assertNull(); 1575 array.getArrayItem(16059).assertNull();
@@ -1599,6 +1629,10 @@ void runtest(int n, char const* filename1, char const* arg2) @@ -1599,6 +1629,10 @@ void runtest(int n, char const* filename1, char const* arg2)
1599 (r1.lly > 3.39) && (r1.lly < 3.41) && 1629 (r1.lly > 3.39) && (r1.lly < 3.41) &&
1600 (r1.urx > 5.59) && (r1.urx < 5.61) && 1630 (r1.urx > 5.59) && (r1.urx < 5.61) &&
1601 (r1.ury > 7.79) && (r1.ury < 7.81)); 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 else if (n == 43) 1637 else if (n == 43)
1604 { 1638 {