Add C++ iterator API around array and dictionary objects

This commit is contained in:
Jay Berkenbilt 2021-01-29 10:02:05 -05:00
parent 35e7859bc7
commit de0b11fc47
5 changed files with 427 additions and 14 deletions

View File

@ -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.

View File

@ -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

View File

@ -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);
}

View File

@ -4847,6 +4847,17 @@ print "\n";
Library Enhancements
</para>
<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

View File

@ -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)
{