Commit e51eee74071546f15be46f73a4ff93f89bd8d191

Authored by m-holger
Committed by GitHub
2 parents c290a82f fcc7f702

Merge pull request #1585 from m-holger/dict

Refactor private-API dictionary methods
include/qpdf/ObjectHandle.hh
... ... @@ -83,8 +83,11 @@ namespace qpdf
83 83 QPDFObjectHandle operator[](size_t n) const;
84 84 QPDFObjectHandle operator[](int n) const;
85 85  
  86 + QPDFObjectHandle& at(std::string const& key) const;
86 87 bool contains(std::string const& key) const;
87 88 size_t erase(std::string const& key);
  89 + QPDFObjectHandle& find(std::string const& key) const;
  90 + bool replace(std::string const& key, QPDFObjectHandle value);
88 91 QPDFObjectHandle const& operator[](std::string const& key) const;
89 92  
90 93 std::shared_ptr<QPDFObject> copy(bool shallow = false) const;
... ... @@ -134,6 +137,8 @@ namespace qpdf
134 137  
135 138 std::string description() const;
136 139  
  140 + inline QPDFObjectHandle const& get(std::string const& key) const;
  141 +
137 142 void no_ci_warn_if(bool condition, std::string const& warning) const;
138 143 void no_ci_stop_if(bool condition, std::string const& warning) const;
139 144 void no_ci_stop_damaged_if(bool condition, std::string const& warning) const;
... ...
libqpdf/NNTree.cc
... ... @@ -168,7 +168,7 @@ NNTreeIterator::resetLimits(Dictionary a_node, std::list&lt;PathElement&gt;::iterator
168 168 }
169 169 }
170 170 if (a_node != path.begin()->node) {
171   - a_node.replaceKey("/Limits", Array({first, last}));
  171 + a_node.replace("/Limits", Array({first, last}));
172 172 }
173 173 }
174 174  
... ... @@ -266,7 +266,7 @@ NNTreeIterator::split(Dictionary to_split, std::list&lt;PathElement&gt;::iterator pare
266 266 new_kids.push_back(first_node);
267 267 to_split.erase("/Limits"); // already shouldn't be there for root
268 268 to_split.erase(impl.itemsKey());
269   - to_split.replaceKey("/Kids", new_kids);
  269 + to_split.replace("/Kids", new_kids);
270 270 if (is_leaf) {
271 271 node = first_node;
272 272 } else {
... ... @@ -446,7 +446,7 @@ NNTreeIterator::remove()
446 446 if (parent == path.end()) {
447 447 // We erased the very last item. Convert the root to an empty items array.
448 448 element->node.erase("/Kids");
449   - element->node.replaceKey(impl.itemsKey(), Array::empty());
  449 + element->node.replace(impl.itemsKey(), Array::empty());
450 450 path.clear();
451 451 setItemNumber(impl.tree_root, -1);
452 452 return;
... ... @@ -673,8 +673,8 @@ NNTreeImpl::repair()
673 673 for (auto const& [key, value]: items) {
674 674 repl.insert(key, value);
675 675 }
676   - tree_root.replaceKey("/Kids", new_node["/Kids"]);
677   - tree_root.replaceKey(itemsKey(), new_node[itemsKey()]);
  676 + tree_root.replace("/Kids", new_node["/Kids"]);
  677 + tree_root.replace(itemsKey(), new_node[itemsKey()]);
678 678 }
679 679  
680 680 NNTreeImpl::iterator
... ...
libqpdf/QPDF.cc
... ... @@ -572,7 +572,7 @@ Objects::Foreign::Copier::replace_indirect_object(QPDFObjectHandle const&amp; foreig
572 572 auto result = Dictionary::empty();
573 573 for (auto const& [key, value]: Dictionary(foreign)) {
574 574 if (!value.null()) {
575   - result.replaceKey(key, replace_indirect_object(value));
  575 + result.replace(key, replace_indirect_object(value));
576 576 }
577 577 }
578 578 return result;
... ... @@ -584,7 +584,7 @@ Objects::Foreign::Copier::replace_indirect_object(QPDFObjectHandle const&amp; foreig
584 584 auto dict = result.getDict();
585 585 for (auto const& [key, value]: stream.getDict()) {
586 586 if (!value.null()) {
587   - dict.replaceKey(key, replace_indirect_object(value));
  587 + dict.replace(key, replace_indirect_object(value));
588 588 }
589 589 }
590 590 stream.copy_data_to(result);
... ... @@ -658,7 +658,7 @@ QPDF::getRoot()
658 658 // approach to more extensive checks and warning levels.
659 659 if (m->cf.check_mode() && Name(Root["/Type"]) != "/Catalog") {
660 660 warn(m->c.damagedPDF("", -1, "catalog /Type entry missing or invalid"));
661   - Root.replaceKey("/Type", Name("/Catalog"));
  661 + Root.replace("/Type", Name("/Catalog"));
662 662 }
663 663 return Root.oh();
664 664 }
... ...
libqpdf/QPDFAcroFormDocumentHelper.cc
... ... @@ -799,7 +799,7 @@ QPDFAcroFormDocumentHelper::transformAnnotations(
799 799 }
800 800 QPDFObjectHandle(dr).makeResourcesIndirect(qpdf);
801 801 if (!dr.indirect()) {
802   - acroform.replaceKey("/DR", qpdf.makeIndirectObject(dr));
  802 + acroform.replace("/DR", qpdf.makeIndirectObject(dr));
803 803 dr = acroform["/DR"];
804 804 }
805 805 // Merge the other document's /DR, creating a conflict map. mergeResources checks to
... ... @@ -839,7 +839,7 @@ QPDFAcroFormDocumentHelper::transformAnnotations(
839 839 if (parent.indirect()) {
840 840 auto parent_og = parent.id_gen();
841 841 if (orig_to_copy.contains(parent_og)) {
842   - obj.replaceKey("/Parent", orig_to_copy[parent_og]);
  842 + obj.replace("/Parent", orig_to_copy[parent_og]);
843 843 } else {
844 844 parent.warn(
845 845 "while traversing field " + obj.id_gen().unparse(',') +
... ... @@ -869,7 +869,7 @@ QPDFAcroFormDocumentHelper::transformAnnotations(
869 869 // chrome, firefox, the mac Preview application, and several of the free
870 870 // readers on Linux all ignore /DR at the field level.
871 871 if (obj.contains("/DR")) {
872   - obj.replaceKey("/DR", dr);
  872 + obj.replace("/DR", dr);
873 873 }
874 874 if (obj["/DA"].isString() && !dr_map.empty()) {
875 875 adjustDefaultAppearances(obj, dr_map);
... ... @@ -988,7 +988,7 @@ QPDFAcroFormDocumentHelper::transformAnnotations(
988 988 Dictionary apdict = ah.getAppearanceDictionary();
989 989 std::vector<QPDFObjectHandle> streams;
990 990 auto replace_stream = [](auto& dict, auto& key, auto& old) {
991   - dict.replaceKey(key, old.copyStream());
  991 + dict.replace(key, old.copyStream());
992 992 return dict[key];
993 993 };
994 994  
... ... @@ -1015,7 +1015,7 @@ QPDFAcroFormDocumentHelper::transformAnnotations(
1015 1015 }
1016 1016 apcm.concat(cm);
1017 1017 if (omatrix || apcm != QPDFMatrix()) {
1018   - dict.replaceKey("/Matrix", QPDFObjectHandle::newFromMatrix(apcm));
  1018 + dict.replace("/Matrix", QPDFObjectHandle::newFromMatrix(apcm));
1019 1019 }
1020 1020 Dictionary resources = dict["/Resources"];
1021 1021 if (!dr_map.empty() && resources) {
... ...
libqpdf/QPDFAnnotationObjectHelper.cc
... ... @@ -33,14 +33,14 @@ QPDFAnnotationObjectHelper::getAppearanceDictionary()
33 33 std::string
34 34 QPDFAnnotationObjectHelper::getAppearanceState()
35 35 {
36   - Name AS = (*this)["/AS"];
  36 + Name AS = get("/AS");
37 37 return AS ? AS.value() : "";
38 38 }
39 39  
40 40 int
41 41 QPDFAnnotationObjectHelper::getFlags()
42 42 {
43   - Integer flags_obj = (*this)["/F"];
  43 + Integer flags_obj = get("/F");
44 44 return flags_obj ? flags_obj : 0;
45 45 }
46 46  
... ...
libqpdf/QPDFEFStreamObjectHelper.cc
... ... @@ -31,7 +31,7 @@ void
31 31 QPDFEFStreamObjectHelper::setParam(std::string const& pkey, QPDFObjectHandle const& pval)
32 32 {
33 33 if (Dictionary Params = oh().getDict()["/Params"]) {
34   - Params.replaceKey(pkey, pval);
  34 + Params.replace(pkey, pval);
35 35 return;
36 36 }
37 37 oh().getDict().replaceKey("/Params", Dictionary({{pkey, pval}}));
... ...
libqpdf/QPDFFileSpecObjectHelper.cc
... ... @@ -42,7 +42,7 @@ std::string
42 42 QPDFFileSpecObjectHelper::getFilename()
43 43 {
44 44 for (auto const& i: name_keys) {
45   - if (String k = oh()[i]) {
  45 + if (String k = get(i)) {
46 46 return k.utf8_value();
47 47 }
48 48 }
... ... @@ -54,7 +54,7 @@ QPDFFileSpecObjectHelper::getFilenames()
54 54 {
55 55 std::map<std::string, std::string> result;
56 56 for (auto const& i: name_keys) {
57   - if (String k = oh()[i]) {
  57 + if (String k = get(i)) {
58 58 result[i] = k.utf8_value();
59 59 }
60 60 }
... ... @@ -64,7 +64,7 @@ QPDFFileSpecObjectHelper::getFilenames()
64 64 QPDFObjectHandle
65 65 QPDFFileSpecObjectHelper::getEmbeddedFileStream(std::string const& key)
66 66 {
67   - if (Dictionary EF = oh()["/EF"]) {
  67 + if (Dictionary EF = get("/EF")) {
68 68 if (!key.empty() && EF.contains(key)) {
69 69 if (auto result = EF[key]) {
70 70 return result;
... ...
libqpdf/QPDFFormFieldObjectHelper.cc
... ... @@ -956,7 +956,7 @@ FormField::generateTextAppearance(QPDFAnnotationObjectHelper&amp; aoh)
956 956 aoh.getObjectHandle().replaceKey("/AP", Dictionary::empty());
957 957 AP = aoh.getAppearanceDictionary();
958 958 }
959   - AP.replaceKey("/N", AS);
  959 + AP.replace("/N", AS);
960 960 }
961 961 if (!AS.isStream()) {
962 962 aoh.warn("unable to get normal appearance stream for update");
... ...
libqpdf/QPDFOutlineObjectHelper.cc
... ... @@ -53,9 +53,9 @@ QPDFOutlineObjectHelper::getKids()
53 53 QPDFObjectHandle
54 54 QPDFOutlineObjectHelper::getDest()
55 55 {
56   - auto dest = (*this)["/Dest"];
  56 + auto dest = get("/Dest");
57 57 if (dest.null()) {
58   - auto const& A = (*this)["/A"];
  58 + auto const& A = get("/A");
59 59 if (Name(A["/S"]) == "/GoTo") {
60 60 dest = A["/D"];
61 61 }
... ...
libqpdf/QPDF_Dictionary.cc
... ... @@ -2,6 +2,7 @@
2 2  
3 3 #include <qpdf/QPDFObject_private.hh>
4 4 #include <qpdf/QTC.hh>
  5 +#include <qpdf/Util.hh>
5 6  
6 7 using namespace std::literals;
7 8 using namespace qpdf;
... ... @@ -29,12 +30,70 @@ BaseHandle::operator[](std::string const&amp; key) const
29 30 return null_obj;
30 31 }
31 32  
  33 +/// Retrieves a reference to the QPDFObjectHandle associated with the given key in the
  34 +/// dictionary object contained within this instance.
  35 +///
  36 +/// If the current object is not of dictionary type, a `std::runtime_error` is thrown.
  37 +/// According to the PDF specification, missing keys in the dictionary are treated as
  38 +/// keys with a `null` value. This behavior is reflected in this function's implementation,
  39 +/// where a missing key will still return a reference to a newly inserted null value entry.
  40 +///
  41 +/// @param key The key for which the corresponding value in the dictionary is retrieved.
  42 +/// @return A reference to the QPDFObjectHandle associated with the specified key.
  43 +/// @throws std::runtime_error if the current object is not a dictionary.
  44 +QPDFObjectHandle&
  45 +BaseHandle::at(std::string const& key) const
  46 +{
  47 + auto d = as<QPDF_Dictionary>();
  48 + if (!d) {
  49 + throw std::runtime_error("Expected a dictionary but found a non-dictionary object");
  50 + }
  51 + return d->items[key];
  52 +}
  53 +
  54 +/// @brief Checks if the specified key exists in the object.
  55 +///
  56 +/// This method determines whether the given key is present in the object by verifying if the
  57 +/// associated value is non-null.
  58 +///
  59 +/// @param key The key to look for in the object.
  60 +/// @return True if the key exists and its associated value is non-null. Otherwise, returns false.
32 61 bool
33 62 BaseHandle::contains(std::string const& key) const
34 63 {
35 64 return !(*this)[key].null();
36 65 }
37 66  
  67 +/// @brief Retrieves the value associated with the given key from dictionary.
  68 +///
  69 +/// This method attempts to find the value corresponding to the specified key for objects that can
  70 +/// be interpreted as dictionaries.
  71 +///
  72 +/// - If the object is a dictionary and the specified key exists, it returns a reference
  73 +/// to the associated value.
  74 +/// - If the object is not a dictionary or the specified key does not exist, it returns
  75 +/// a reference to a static uninitialized object handle.
  76 +///
  77 +/// @note Modifying the uninitialized object returned when the key is not found is strictly
  78 +/// prohibited.
  79 +///
  80 +/// @param key The key whose associated value should be retrieved.
  81 +/// @return A reference to the associated value if the key is found or a reference to a static
  82 +/// uninitialized object if the key is not found.
  83 +QPDFObjectHandle&
  84 +BaseHandle::find(std::string const& key) const
  85 +{
  86 + static const QPDFObjectHandle null_obj;
  87 + qpdf_invariant(!null_obj);
  88 + if (auto d = as<QPDF_Dictionary>()) {
  89 + auto it = d->items.find(key);
  90 + if (it != d->items.end()) {
  91 + return it->second;
  92 + }
  93 + }
  94 + return const_cast<QPDFObjectHandle&>(null_obj);
  95 +}
  96 +
38 97 std::set<std::string>
39 98 BaseDictionary::getKeys()
40 99 {
... ... @@ -63,18 +122,29 @@ BaseHandle::erase(const std::string&amp; key)
63 122 return 0;
64 123 }
65 124  
  125 +bool
  126 +BaseHandle::replace(std::string const& key, QPDFObjectHandle value)
  127 +{
  128 + if (auto d = as<QPDF_Dictionary>()) {
  129 + if (value.null() && !value.indirect()) {
  130 + // The PDF spec doesn't distinguish between keys with null values and missing keys.
  131 + // Allow indirect nulls which are equivalent to a dangling reference, which is permitted
  132 + // by the spec.
  133 + d->items.erase(key);
  134 + } else {
  135 + // add or replace value
  136 + d->items[key] = value;
  137 + }
  138 + return true;
  139 + }
  140 + return false;
  141 +}
  142 +
66 143 void
67   -BaseDictionary::replaceKey(std::string const& key, QPDFObjectHandle value)
68   -{
69   - auto d = dict();
70   - if (value.null() && !value.indirect()) {
71   - // The PDF spec doesn't distinguish between keys with null values and missing keys.
72   - // Allow indirect nulls which are equivalent to a dangling reference, which is
73   - // permitted by the spec.
74   - d->items.erase(key);
75   - } else {
76   - // add or replace value
77   - d->items[key] = value;
  144 +BaseDictionary::replace(std::string const& key, QPDFObjectHandle value)
  145 +{
  146 + if (!BaseHandle::replace(key, value)) {
  147 + (void)dict();
78 148 }
79 149 }
80 150  
... ... @@ -121,7 +191,7 @@ QPDFObjectHandle::hasKey(std::string const&amp; key) const
121 191 QPDFObjectHandle
122 192 QPDFObjectHandle::getKey(std::string const& key) const
123 193 {
124   - if (auto result = (*this)[key]) {
  194 + if (auto result = get(key)) {
125 195 return result;
126 196 }
127 197 if (isDictionary()) {
... ... @@ -166,11 +236,10 @@ QPDFObjectHandle::replaceKey(std::string const&amp; key, QPDFObjectHandle const&amp; val
166 236 {
167 237 if (auto dict = as_dictionary(strict)) {
168 238 checkOwnership(value);
169   - dict.replaceKey(key, value);
  239 + dict.replace(key, value);
170 240 return;
171 241 }
172 242 typeWarning("dictionary", "ignoring key replacement request");
173   - QTC::TC("qpdf", "QPDFObjectHandle dictionary ignoring replaceKey");
174 243 }
175 244  
176 245 QPDFObjectHandle
... ... @@ -200,7 +269,7 @@ QPDFObjectHandle::removeKey(std::string const&amp; key)
200 269 QPDFObjectHandle
201 270 QPDFObjectHandle::removeKeyAndGetOld(std::string const& key)
202 271 {
203   - auto result = (*this)[key];
  272 + auto result = get(key);
204 273 erase(key);
205 274 return result ? result : newNull();
206 275 }
... ...
libqpdf/qpdf/QPDFObjectHandle_private.hh
... ... @@ -127,7 +127,7 @@ namespace qpdf
127 127 // The following methods are not part of the public API.
128 128 std::set<std::string> getKeys();
129 129 std::map<std::string, QPDFObjectHandle> const& getAsMap() const;
130   - void replaceKey(std::string const& key, QPDFObjectHandle value);
  130 + void replace(std::string const& key, QPDFObjectHandle value);
131 131  
132 132 using iterator = std::map<std::string, QPDFObjectHandle>::iterator;
133 133 using const_iterator = std::map<std::string, QPDFObjectHandle>::const_iterator;
... ... @@ -806,6 +806,20 @@ namespace qpdf
806 806 }
807 807 }
808 808  
  809 + /// @brief Retrieves the QPDFObjectHandle const& associated with the given key.
  810 + ///
  811 + /// This method provides a convenience alternative to the direct use of the subscript operator
  812 + /// "(*this)[key]" or "oh()[key]" in derived classes, enabling a simplified and readable way to
  813 + /// access object handles by key.
  814 + ///
  815 + /// @param key The string key used to look up the corresponding QPDFObjectHandle.
  816 + /// @return A constant reference to the QPDFObjectHandle associated with the specified key.
  817 + inline QPDFObjectHandle const&
  818 + BaseHandle::get(std::string const& key) const
  819 + {
  820 + return (*this)[key];
  821 + }
  822 +
809 823 inline bool
810 824 BaseHandle::null() const
811 825 {
... ...
libtests/objects.cc
... ... @@ -54,35 +54,30 @@ test_0(QPDF&amp; pdf, char const* arg2)
54 54 assert_compare_numbers(INT_MAX, t.getKey("/Q1").getIntValueAsInt());
55 55 try {
56 56 assert_compare_numbers(0u, QPDFObjectHandle::newNull().getUIntValueAsUInt());
57   - std::cerr << "convert null to uint did not throw\n";
58 57 } catch (QPDFExc const&) {
59 58 std::cerr << "caught expected type error\n";
60 59 }
61 60 assert_compare_numbers(std::numeric_limits<int8_t>::max(), Integer(q1).value<int8_t>());
62 61 assert_compare_numbers(std::numeric_limits<int8_t>::min(), Integer(-q1).value<int8_t>());
63 62 try {
64   - int8_t q1_8 = Integer(q1);
65   - std::cerr << "q1_8: " << std::to_string(q1_8) << '\n';
  63 + [[maybe_unused]] int8_t q1_8 = Integer(q1);
66 64 } catch (std::overflow_error const&) {
67 65 std::cerr << "caught expected int8_t overflow error\n";
68 66 }
69 67 try {
70   - int8_t q1_8 = Integer(-q1);
71   - std::cerr << "q1_8: " << std::to_string(q1_8) << '\n';
  68 + [[maybe_unused]] int8_t q1_8 = Integer(-q1);
72 69 } catch (std::underflow_error const&) {
73 70 std::cerr << "caught expected int8_t underflow error\n";
74 71 }
75 72 assert_compare_numbers(std::numeric_limits<uint8_t>::max(), Integer(q1).value<uint8_t>());
76 73 assert_compare_numbers(0, Integer(-q1).value<uint8_t>());
77 74 try {
78   - uint8_t q1_u8 = Integer(q1);
79   - std::cerr << "q1_u8: " << std::to_string(q1_u8) << '\n';
  75 + [[maybe_unused]] uint8_t q1_u8 = Integer(q1);
80 76 } catch (std::overflow_error const&) {
81 77 std::cerr << "caught expected uint8_t overflow error\n";
82 78 }
83 79 try {
84   - uint8_t q1_u8 = Integer(-q1);
85   - std::cerr << "q1_u8: " << std::to_string(q1_u8) << '\n';
  80 + [[maybe_unused]] uint8_t q1_u8 = Integer(-q1);
86 81 } catch (std::underflow_error const&) {
87 82 std::cerr << "caught expected uint8_t underflow error\n";
88 83 }
... ... @@ -95,6 +90,70 @@ test_0(QPDF&amp; pdf, char const* arg2)
95 90 assert_compare_numbers(UINT_MAX, t.getKey("/Q3").getUIntValueAsUInt());
96 91 }
97 92  
  93 +
  94 +static void
  95 +test_1(QPDF& pdf, char const* arg2)
  96 +{
  97 + // Test new dictionary methods.
  98 + using namespace qpdf;
  99 + auto d = Dictionary({{"/A", {}}, {"/B", Null()}, {"/C", Dictionary::empty()}});
  100 +
  101 + // contains
  102 + assert(!d.contains("/A"));
  103 + assert(!d.contains("/B"));
  104 + assert(d.contains("/C"));
  105 +
  106 + auto i = Integer(42);
  107 + assert(!i.contains("/A"));
  108 +
  109 + // at
  110 + assert(!d.at("/A"));
  111 + assert(d.at("/B"));
  112 + assert(d.at("/B").null());
  113 + assert(d.at("/C"));
  114 + assert(!d.at("/C").null());
  115 + d.at("/C") = Integer(42);
  116 + assert(d.at("/C") == 42);
  117 + assert(!d.at("/D"));
  118 + assert(d.at("/D").null());
  119 + assert(QPDFObjectHandle(d).getDictAsMap().contains("/D"));
  120 + assert(QPDFObjectHandle(d).getDictAsMap().size() == 4);
  121 +
  122 + bool thrown = false;
  123 + try {
  124 + i.at("/A");
  125 + } catch (std::runtime_error const&) {
  126 + thrown = true;
  127 + }
  128 + assert(thrown);
  129 +
  130 + // find
  131 + assert(!d.find("/A"));
  132 + assert(d.find("/B"));
  133 + assert(d.find("/B").null());
  134 + assert(d.find("/C"));
  135 + assert(Integer(d.find("/C")) == 42);
  136 + d.find("/C") = Name("/DontPanic");
  137 + assert(Name(d.find("/C")) == "/DontPanic");
  138 + assert(!d.find("/E"));
  139 + assert(!QPDFObjectHandle(d).getDictAsMap().contains("/E"));
  140 + assert(QPDFObjectHandle(d).getDictAsMap().size() == 4);
  141 +
  142 + // replace
  143 + assert(!i.replace("/A", Name("/DontPanic")));
  144 + Dictionary di = i.oh();
  145 + thrown = false;
  146 + try {
  147 + di.replace("/A", Name("/DontPanic"));
  148 + } catch (std::runtime_error const&) {
  149 + thrown = true;
  150 + }
  151 + assert(thrown);
  152 + d.replace("/C", Integer(42));
  153 + assert(Integer(d["/C"]) == 42);
  154 + assert(QPDFObjectHandle(d).getDictAsMap().size() == 4);
  155 +}
  156 +
98 157 void
99 158 runtest(int n, char const* filename1, char const* arg2)
100 159 {
... ... @@ -102,7 +161,7 @@ runtest(int n, char const* filename1, char const* arg2)
102 161 // the test suite to see how the test is invoked to find the file
103 162 // that the test is supposed to operate on.
104 163  
105   - std::set<int> ignore_filename = {};
  164 + std::set<int> ignore_filename = {1,};
106 165  
107 166 QPDF pdf;
108 167 std::shared_ptr<char> file_buf;
... ... @@ -116,7 +175,7 @@ runtest(int n, char const* filename1, char const* arg2)
116 175 }
117 176  
118 177 std::map<int, void (*)(QPDF&, char const*)> test_functions = {
119   - {0, test_0},
  178 + {0, test_0}, {1, test_1},
120 179 };
121 180  
122 181 auto fn = test_functions.find(n);
... ...
libtests/qtest/objects.test
... ... @@ -11,12 +11,16 @@ require TestDriver;
11 11  
12 12 my $td = new TestDriver('objects');
13 13  
14   -my $n_tests = 1;
15   -
  14 +my $n_tests = 2;
16 15  
17 16 $td->runtest("integer type checks",
18 17 {$td->COMMAND => "objects 0 minimal.pdf"},
19 18 {$td->FILE => "test0.out", $td->EXIT_STATUS => 0},
20 19 $td->NORMALIZE_NEWLINES);
21 20  
  21 +$td->runtest("dictionary checks",
  22 + {$td->COMMAND => "objects 1 -"},
  23 + {$td->STRING => => "test 1 done\n", $td->EXIT_STATUS => 0},
  24 + $td->NORMALIZE_NEWLINES);
  25 +
22 26 $td->report($n_tests);
... ...
qpdf/qpdf.testcov
... ... @@ -171,7 +171,6 @@ QPDFObjectHandle array ignoring erase item 0
171 171 QPDFObjectHandle dictionary false for hasKey 0
172 172 QPDFObjectHandle dictionary empty set for getKeys 0
173 173 QPDFObjectHandle dictionary empty map for asMap 0
174   -QPDFObjectHandle dictionary ignoring replaceKey 0
175 174 QPDFObjectHandle numeric non-numeric 0
176 175 QPDFObjectHandle erase array bounds 0
177 176 qpdf-c called qpdf_check_pdf 0
... ...