From de0b11fc4793213dc6156d34412580a6e4df0c48 Mon Sep 17 00:00:00 2001 From: Jay Berkenbilt Date: Fri, 29 Jan 2021 10:02:05 -0500 Subject: [PATCH] Add C++ iterator API around array and dictionary objects --- ChangeLog | 14 +++ include/qpdf/QPDFObjectHandle.hh | 181 ++++++++++++++++++++++++++++++- libqpdf/QPDFObjectHandle.cc | 177 ++++++++++++++++++++++++++++++ manual/qpdf-manual.xml | 11 ++ qpdf/test_driver.cc | 58 ++++++++-- 5 files changed, 427 insertions(+), 14 deletions(-) diff --git a/ChangeLog b/ChangeLog index 4b284beb..90177d36 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,19 @@ 2021-01-29 Jay Berkenbilt + * Add wrappers QPDFDictItems and QPDFArrayItems around + QPDFObjectHandle that provide a C++ iterator API, including C++11 + range-for iteration, over arrays and dictionaries. With this, you + can do + + for (auto i: QPDFDictItems(oh)) + { + // i.first is a string, i.second is a QPDFObjectHandle + } + for (auto i: QPDFArrayItems(oh)) + { + // i is a QPDFObjectHandle + } + * QPDFObjectHandle::is* methods to check type now return false on uninitialized objects rather than crashing or throwing a logic error. diff --git a/include/qpdf/QPDFObjectHandle.hh b/include/qpdf/QPDFObjectHandle.hh index b28568ed..554c7131 100644 --- a/include/qpdf/QPDFObjectHandle.hh +++ b/include/qpdf/QPDFObjectHandle.hh @@ -631,7 +631,8 @@ class QPDFObjectHandle QPDF_DLL std::string getInlineImageValue(); - // Methods for array objects; see also name and array objects + // Methods for array objects; see also name and array objects. See + // also QPDFArrayItems later in this file. QPDF_DLL int getArrayNItems(); QPDF_DLL @@ -655,7 +656,8 @@ class QPDFObjectHandle QPDF_DLL Matrix getArrayAsMatrix(); - // Methods for dictionary objects + // Methods for dictionary objects. See also QPDFDictItems later in + // this file. QPDF_DLL bool hasKey(std::string const&); QPDF_DLL @@ -1224,4 +1226,179 @@ class QPDFObjectHandle bool reserved; }; +class QPDFDictItems +{ + // This class allows C++-style iteration, including range-for + // iteration, around dictionaries. You can write + + // for (auto iter: QPDFDictItems(dictionary_obj)) + // { + // // iter.first is a string + // // iter.second is a QPDFObjectHandle + // } + + public: + QPDF_DLL + QPDFDictItems(QPDFObjectHandle& oh); + + class iterator: public std::iterator< + std::bidirectional_iterator_tag, + std::pair> + { + friend class QPDFDictItems; + public: + QPDF_DLL + virtual ~iterator() = default; + QPDF_DLL + iterator& operator++(); + QPDF_DLL + iterator operator++(int) + { + iterator t = *this; + ++(*this); + return t; + } + QPDF_DLL + iterator& operator--(); + QPDF_DLL + iterator operator--(int) + { + iterator t = *this; + --(*this); + return t; + } + QPDF_DLL + reference operator*(); + QPDF_DLL + pointer operator->(); + QPDF_DLL + bool operator==(iterator const& other) const; + QPDF_DLL + bool operator!=(iterator const& other) const + { + return ! operator==(other); + } + + private: + iterator(QPDFObjectHandle& oh, bool for_begin); + void updateIValue(); + + class Members + { + friend class QPDFDictItems::iterator; + + public: + QPDF_DLL + ~Members() = default; + + private: + Members(QPDFObjectHandle& oh, bool for_begin); + Members() = delete; + Members(Members const&) = delete; + + QPDFObjectHandle& oh; + std::set keys; + std::set::iterator iter; + bool is_end; + }; + PointerHolder m; + value_type ivalue; + }; + + QPDF_DLL + iterator begin(); + QPDF_DLL + iterator end(); + + private: + QPDFObjectHandle& oh; +}; + +class QPDFArrayItems +{ + // This class allows C++-style iteration, including range-for + // iteration, around arrays. You can write + + // for (auto iter: QPDFArrayItems(array_obj)) + // { + // // iter is a QPDFObjectHandle + // } + + public: + QPDF_DLL + QPDFArrayItems(QPDFObjectHandle& oh); + + class iterator: public std::iterator< + std::bidirectional_iterator_tag, + QPDFObjectHandle> + { + friend class QPDFArrayItems; + public: + QPDF_DLL + virtual ~iterator() = default; + QPDF_DLL + iterator& operator++(); + QPDF_DLL + iterator operator++(int) + { + iterator t = *this; + ++(*this); + return t; + } + QPDF_DLL + iterator& operator--(); + QPDF_DLL + iterator operator--(int) + { + iterator t = *this; + --(*this); + return t; + } + QPDF_DLL + reference operator*(); + QPDF_DLL + pointer operator->(); + QPDF_DLL + bool operator==(iterator const& other) const; + QPDF_DLL + bool operator!=(iterator const& other) const + { + return ! operator==(other); + } + + private: + iterator(QPDFObjectHandle& oh, bool for_begin); + void updateIValue(); + + class Members + { + friend class QPDFArrayItems::iterator; + + public: + QPDF_DLL + ~Members() = default; + + private: + Members(QPDFObjectHandle& oh, bool for_begin); + Members() = delete; + Members(Members const&) = delete; + + QPDFObjectHandle& oh; + int item_number; + bool is_end; + }; + PointerHolder m; + value_type ivalue; + }; + + QPDF_DLL + iterator begin(); + QPDF_DLL + iterator end(); + + private: + QPDFObjectHandle& oh; +}; + + #endif // QPDFOBJECTHANDLE_HH diff --git a/libqpdf/QPDFObjectHandle.cc b/libqpdf/QPDFObjectHandle.cc index 93f49792..288b5256 100644 --- a/libqpdf/QPDFObjectHandle.cc +++ b/libqpdf/QPDFObjectHandle.cc @@ -3079,3 +3079,180 @@ QPDFObjectHandle::warn(QPDF* qpdf, QPDFExc const& e) throw e; } } + +QPDFDictItems::QPDFDictItems(QPDFObjectHandle& oh) : + oh(oh) +{ +} + +QPDFDictItems::iterator& +QPDFDictItems::iterator::operator++() +{ + ++this->m->iter; + updateIValue(); + return *this; +} + +QPDFDictItems::iterator& +QPDFDictItems::iterator::operator--() +{ + --this->m->iter; + updateIValue(); + return *this; +} + +QPDFDictItems::iterator::reference +QPDFDictItems::iterator:: operator*() +{ + updateIValue(); + return this->ivalue; +} + +QPDFDictItems::iterator::pointer +QPDFDictItems::iterator::operator->() +{ + updateIValue(); + return &this->ivalue; +} + +bool +QPDFDictItems::iterator::operator==(iterator const& other) const +{ + if (this->m->is_end && other.m->is_end) + { + return true; + } + if (this->m->is_end || other.m->is_end) + { + return false; + } + return (this->ivalue.first == other.ivalue.first); +} + +QPDFDictItems::iterator::iterator(QPDFObjectHandle& oh, bool for_begin) : + m(new Members(oh, for_begin)) +{ + updateIValue(); +} + +void +QPDFDictItems::iterator::updateIValue() +{ + this->m->is_end = (this->m->iter == this->m->keys.end()); + if (this->m->is_end) + { + this->ivalue.first = ""; + this->ivalue.second = QPDFObjectHandle(); + } + else + { + this->ivalue.first = *(this->m->iter); + this->ivalue.second = this->m->oh.getKey(this->ivalue.first); + } +} + +QPDFDictItems::iterator::Members::Members( + QPDFObjectHandle& oh, bool for_begin) : + oh(oh) +{ + this->keys = oh.getKeys(); + this->iter = for_begin ? this->keys.begin() : this->keys.end(); +} + +QPDFDictItems::iterator +QPDFDictItems::begin() +{ + return iterator(oh, true); +} + +QPDFDictItems::iterator +QPDFDictItems::end() +{ + return iterator(oh, false); +} + +QPDFArrayItems::QPDFArrayItems(QPDFObjectHandle& oh) : + oh(oh) +{ +} + +QPDFArrayItems::iterator& +QPDFArrayItems::iterator::operator++() +{ + if (! this->m->is_end) + { + ++this->m->item_number; + updateIValue(); + } + return *this; +} + +QPDFArrayItems::iterator& +QPDFArrayItems::iterator::operator--() +{ + if (this->m->item_number > 0) + { + --this->m->item_number; + updateIValue(); + } + return *this; +} + +QPDFArrayItems::iterator::reference +QPDFArrayItems::iterator:: operator*() +{ + updateIValue(); + return this->ivalue; +} + +QPDFArrayItems::iterator::pointer +QPDFArrayItems::iterator::operator->() +{ + updateIValue(); + return &this->ivalue; +} + +bool +QPDFArrayItems::iterator::operator==(iterator const& other) const +{ + return (this->m->item_number == other.m->item_number); +} + +QPDFArrayItems::iterator::iterator(QPDFObjectHandle& oh, bool for_begin) : + m(new Members(oh, for_begin)) +{ + updateIValue(); +} + +void +QPDFArrayItems::iterator::updateIValue() +{ + this->m->is_end = (this->m->item_number >= this->m->oh.getArrayNItems()); + if (this->m->is_end) + { + this->ivalue = QPDFObjectHandle(); + } + else + { + this->ivalue = this->m->oh.getArrayItem(this->m->item_number); + } +} + +QPDFArrayItems::iterator::Members::Members( + QPDFObjectHandle& oh, bool for_begin) : + oh(oh) +{ + this->item_number = for_begin ? 0 : oh.getArrayNItems(); +} + +QPDFArrayItems::iterator +QPDFArrayItems::begin() +{ + return iterator(oh, true); +} + +QPDFArrayItems::iterator +QPDFArrayItems::end() +{ + return iterator(oh, false); +} diff --git a/manual/qpdf-manual.xml b/manual/qpdf-manual.xml index ccc80d07..9a62f1da 100644 --- a/manual/qpdf-manual.xml +++ b/manual/qpdf-manual.xml @@ -4847,6 +4847,17 @@ print "\n"; Library Enhancements + + + Add QPDFDictItems and + QPDFArrayItems wrappers around + QPDFObjectHandle, allowing C++-style + iteration, including range-for iteration, over dictionary + and array QPDFObjectHandles. See comments in + include/qpdf/QPDFObjectHandle.hh for + details. + + Add warn to diff --git a/qpdf/test_driver.cc b/qpdf/test_driver.cc index 99c5cf53..6cb5fc80 100644 --- a/qpdf/test_driver.cc +++ b/qpdf/test_driver.cc @@ -347,30 +347,29 @@ void runtest(int n, char const* filename1, char const* arg2) else if (qtest.isArray()) { QTC::TC("qpdf", "main QTest array"); - int nitems = qtest.getArrayNItems(); std::cout << "/QTest is an array with " - << nitems << " items" << std::endl; - for (int i = 0; i < nitems; ++i) + << qtest.getArrayNItems() << " items" << std::endl; + int i = 0; + for (auto& iter: QPDFArrayItems(qtest)) { QTC::TC("qpdf", "main QTest array indirect", - qtest.getArrayItem(i).isIndirect() ? 1 : 0); + iter.isIndirect() ? 1 : 0); std::cout << " item " << i << " is " - << (qtest.getArrayItem(i).isIndirect() ? "in" : "") + << (iter.isIndirect() ? "in" : "") << "direct" << std::endl; + ++i; } } else if (qtest.isDictionary()) { QTC::TC("qpdf", "main QTest dictionary"); std::cout << "/QTest is a dictionary" << std::endl; - std::set keys = qtest.getKeys(); - for (std::set::iterator iter = keys.begin(); - iter != keys.end(); ++iter) - { + for (auto& iter: QPDFDictItems(qtest)) + { QTC::TC("qpdf", "main QTest dictionary indirect", - (qtest.getKey(*iter).isIndirect() ? 1 : 0)); - std::cout << " " << *iter << " is " - << (qtest.getKey(*iter).isIndirect() ? "in" : "") + iter.second.isIndirect() ? 1 : 0); + std::cout << " " << iter.first << " is " + << (iter.second.isIndirect() ? "in" : "") << "direct" << std::endl; } } @@ -1539,7 +1538,38 @@ void runtest(int n, char const* filename1, char const* arg2) QPDFObjectHandle integer = qtest.getKey("/Integer"); QPDFObjectHandle null = QPDFObjectHandle::newNull(); assert(array.isArray()); + { + // Exercise iterators directly + QPDFArrayItems ai(array); + auto i = ai.begin(); + assert(i->getName() == "/Item0"); + auto& i_value = *i; + --i; + assert(i->getName() == "/Item0"); + ++i; + ++i; + ++i; + assert(i == ai.end()); + ++i; + assert(i == ai.end()); + assert(! i_value.isInitialized()); + --i; + assert(i_value.getName() == "/Item2"); + assert(i->getName() == "/Item2"); + } assert(dictionary.isDictionary()); + { + // Exercise iterators directly + QPDFDictItems di(dictionary); + auto i = di.begin(); + assert(i->first == "/Key1"); + auto& i_value = *i; + assert(i->second.getName() == "/Value1"); + ++i; + ++i; + assert(i == di.end()); + assert(! i_value.second.isInitialized()); + } assert("" == qtest.getStringValue()); array.getArrayItem(-1).assertNull(); array.getArrayItem(16059).assertNull(); @@ -1599,6 +1629,10 @@ void runtest(int n, char const* filename1, char const* arg2) (r1.lly > 3.39) && (r1.lly < 3.41) && (r1.urx > 5.59) && (r1.urx < 5.61) && (r1.ury > 7.79) && (r1.ury < 7.81)); + QPDFObjectHandle uninitialized; + assert(! uninitialized.isInitialized()); + assert(! uninitialized.isInteger()); + assert(! uninitialized.isDictionary()); } else if (n == 43) {