aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJay Berkenbilt <ejb@ql.org>2021-01-29 16:02:05 +0100
committerJay Berkenbilt <ejb@ql.org>2021-01-30 21:15:23 +0100
commitde0b11fc4793213dc6156d34412580a6e4df0c48 (patch)
treec8b3f45840bd439aeeb5498e0f0fd8107d0e2d05
parent35e7859bc7d903c0177ce2a14f2842e1a2dbb09a (diff)
downloadqpdf-de0b11fc4793213dc6156d34412580a6e4df0c48.tar.zst
Add C++ iterator API around array and dictionary objects
-rw-r--r--ChangeLog14
-rw-r--r--include/qpdf/QPDFObjectHandle.hh181
-rw-r--r--libqpdf/QPDFObjectHandle.cc177
-rw-r--r--manual/qpdf-manual.xml11
-rw-r--r--qpdf/test_driver.cc58
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 <ejb@ql.org>
+ * 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<std::string, QPDFObjectHandle>>
+ {
+ 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<std::string> keys;
+ std::set<std::string>::iterator iter;
+ bool is_end;
+ };
+ PointerHolder<Members> 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<Members> 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
@@ -4849,6 +4849,17 @@ print "\n";
<itemizedlist>
<listitem>
<para>
+ Add <classname>QPDFDictItems</classname> and
+ <classname>QPDFArrayItems</classname> wrappers around
+ <classname>QPDFObjectHandle</classname>, allowing C++-style
+ iteration, including range-for iteration, over dictionary
+ and array QPDFObjectHandles. See comments in
+ <filename>include/qpdf/QPDFObjectHandle.hh</filename> for
+ details.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
Add <function>warn</function> to
<classname>QPDF</classname>'s public API.
</para>
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<std::string> keys = qtest.getKeys();
- for (std::set<std::string>::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)
{