Commit d16e822d77d41c026e44bce459e5417c4e7b885d

Authored by m-holger
Committed by GitHub
2 parents 979dc51b 4eb9f113

Merge pull request #1532 from m-holger/oh_dict

Refactor private-API class Dictionary
include/qpdf/ObjectHandle.hh
@@ -61,6 +61,13 @@ namespace qpdf @@ -61,6 +61,13 @@ namespace qpdf
61 61
62 // The rest of the header file is for qpdf internal use only. 62 // The rest of the header file is for qpdf internal use only.
63 63
  64 + // Return true if both object handles refer to the same underlying object.
  65 + bool
  66 + operator==(BaseHandle const& other) const
  67 + {
  68 + return obj == other.obj;
  69 + }
  70 +
64 // For arrays, return the number of items in the array. 71 // For arrays, return the number of items in the array.
65 // For null-like objects, return 0. 72 // For null-like objects, return 0.
66 // For all other objects, return 1. 73 // For all other objects, return 1.
@@ -76,6 +83,10 @@ namespace qpdf @@ -76,6 +83,10 @@ namespace qpdf
76 QPDFObjectHandle operator[](size_t n) const; 83 QPDFObjectHandle operator[](size_t n) const;
77 QPDFObjectHandle operator[](int n) const; 84 QPDFObjectHandle operator[](int n) const;
78 85
  86 + bool contains(std::string const& key) const;
  87 + size_t erase(std::string const& key);
  88 + QPDFObjectHandle const& operator[](std::string const& key) const;
  89 +
79 std::shared_ptr<QPDFObject> copy(bool shallow = false) const; 90 std::shared_ptr<QPDFObject> copy(bool shallow = false) const;
80 // Recursively remove association with any QPDF object. This method may only be called 91 // Recursively remove association with any QPDF object. This method may only be called
81 // during final destruction. 92 // during final destruction.
libqpdf/NNTree.cc
@@ -57,75 +57,64 @@ NNTreeIterator::updateIValue(bool allow_invalid) @@ -57,75 +57,64 @@ NNTreeIterator::updateIValue(bool allow_invalid)
57 // we must call updateIValue as well. These cases are handled, and for good measure, we also 57 // we must call updateIValue as well. These cases are handled, and for good measure, we also
58 // call updateIValue in operator* and operator->. 58 // call updateIValue in operator* and operator->.
59 59
60 - if (item_number < 0 || !node.isDictionary()) { 60 + Array items = node[impl.itemsKey()];
  61 + ivalue.first = items[item_number];
  62 + ivalue.second = items[item_number + 1];
  63 + if (ivalue.second) {
  64 + return;
  65 + }
  66 +
  67 + if (item_number < 0 || !node) {
61 if (!allow_invalid) { 68 if (!allow_invalid) {
62 throw std::logic_error( 69 throw std::logic_error(
63 "attempt made to dereference an invalid name/number tree iterator"); 70 "attempt made to dereference an invalid name/number tree iterator");
64 } 71 }
65 - ivalue.first = QPDFObjectHandle();  
66 - ivalue.second = QPDFObjectHandle();  
67 return; 72 return;
68 } 73 }
69 - Array items = node.getKey(impl.itemsKey());  
70 - if (!std::cmp_less(item_number + 1, items.size())) {  
71 - impl.error(node, "update ivalue: items array is too short");  
72 - }  
73 - ivalue.first = items[item_number];  
74 - ivalue.second = items[1 + item_number];  
75 -}  
76 -  
77 -NNTreeIterator::PathElement::PathElement(QPDFObjectHandle const& node, int kid_number) :  
78 - node(node),  
79 - kid_number(kid_number)  
80 -{ 74 + impl.error(node, "update ivalue: items array is too short");
81 } 75 }
82 76
83 -QPDFObjectHandle 77 +Dictionary
84 NNTreeIterator::getNextKid(PathElement& pe, bool backward) 78 NNTreeIterator::getNextKid(PathElement& pe, bool backward)
85 { 79 {
86 while (true) { 80 while (true) {
87 pe.kid_number += backward ? -1 : 1; 81 pe.kid_number += backward ? -1 : 1;
88 - Array kids = pe.node.getKey("/Kids");  
89 - if (pe.kid_number >= 0 && std::cmp_less(pe.kid_number, kids.size())) {  
90 - auto result = kids[pe.kid_number];  
91 - if (result.isDictionary() &&  
92 - (result.hasKey("/Kids") || result.hasKey(impl.itemsKey()))) {  
93 - return result;  
94 - } else {  
95 - impl.warn(  
96 - pe.node, "skipping over invalid kid at index " + std::to_string(pe.kid_number));  
97 - }  
98 - } else {  
99 - return QPDFObjectHandle::newNull(); 82 + Dictionary result = pe.node["/Kids"][pe.kid_number];
  83 + if (result.contains("/Kids") || result.contains(impl.itemsKey())) {
  84 + return result;
  85 + }
  86 + if (pe.kid_number < 0 || std::cmp_greater_equal(pe.kid_number, pe.node["/Kids"].size())) {
  87 + return {};
100 } 88 }
  89 + impl.warn(pe.node, "skipping over invalid kid at index " + std::to_string(pe.kid_number));
101 } 90 }
102 } 91 }
103 void 92 void
104 NNTreeIterator::increment(bool backward) 93 NNTreeIterator::increment(bool backward)
105 { 94 {
106 if (item_number < 0) { 95 if (item_number < 0) {
107 - deepen(impl.oh, !backward, true); 96 + deepen(impl.tree_root, !backward, true);
108 return; 97 return;
109 } 98 }
110 99
111 while (valid()) { 100 while (valid()) {
112 item_number += backward ? -2 : 2; 101 item_number += backward ? -2 : 2;
113 - Array items = node.getKey(impl.itemsKey()); 102 + Array items = node[impl.itemsKey()];
114 if (item_number < 0 || std::cmp_greater_equal(item_number, items.size())) { 103 if (item_number < 0 || std::cmp_greater_equal(item_number, items.size())) {
115 - bool found = false;  
116 setItemNumber(QPDFObjectHandle(), -1); 104 setItemNumber(QPDFObjectHandle(), -1);
117 - while (!(found || path.empty())) { 105 + while (!path.empty()) {
118 auto& element = path.back(); 106 auto& element = path.back();
119 - auto pe_node = getNextKid(element, backward);  
120 - if (pe_node.null()) {  
121 - path.pop_back(); 107 + if (auto pe_node = getNextKid(element, backward)) {
  108 + if (deepen(pe_node, !backward, false)) {
  109 + break;
  110 + }
122 } else { 111 } else {
123 - found = deepen(pe_node, !backward, false); 112 + path.pop_back();
124 } 113 }
125 } 114 }
126 } 115 }
127 if (item_number >= 0) { 116 if (item_number >= 0) {
128 - items = node.getKey(impl.itemsKey()); 117 + items = node[impl.itemsKey()];
129 if (std::cmp_greater_equal(item_number + 1, items.size())) { 118 if (std::cmp_greater_equal(item_number + 1, items.size())) {
130 impl.warn(node, "items array doesn't have enough elements"); 119 impl.warn(node, "items array doesn't have enough elements");
131 } else if (!impl.keyValid(items[item_number])) { 120 } else if (!impl.keyValid(items[item_number])) {
@@ -140,65 +129,59 @@ NNTreeIterator::increment(bool backward) @@ -140,65 +129,59 @@ NNTreeIterator::increment(bool backward)
140 } 129 }
141 130
142 void 131 void
143 -NNTreeIterator::resetLimits(QPDFObjectHandle a_node, std::list<PathElement>::iterator parent) 132 +NNTreeIterator::resetLimits(Dictionary a_node, std::list<PathElement>::iterator parent)
144 { 133 {
145 while (true) { 134 while (true) {
146 if (parent == path.end()) { 135 if (parent == path.end()) {
147 - a_node.removeKey("/Limits");  
148 - break; 136 + a_node.erase("/Limits");
  137 + return;
149 } 138 }
150 - Array kids = a_node.getKey("/Kids");  
151 - size_t nkids = kids.size();  
152 - Array items = a_node.getKey(impl.itemsKey());  
153 - size_t nitems = items.size();  
154 139
155 - bool changed = true;  
156 QPDFObjectHandle first; 140 QPDFObjectHandle first;
157 QPDFObjectHandle last; 141 QPDFObjectHandle last;
  142 + Array items = a_node[impl.itemsKey()];
  143 + size_t nitems = items.size();
158 if (nitems >= 2) { 144 if (nitems >= 2) {
159 first = items[0]; 145 first = items[0];
160 last = items[(nitems - 1u) & ~1u]; 146 last = items[(nitems - 1u) & ~1u];
161 - } else if (nkids > 0) {  
162 - auto first_kid = kids[0];  
163 - auto last_kid = kids[nkids - 1u];  
164 - if (first_kid.isDictionary() && last_kid.isDictionary()) {  
165 - Array first_limits = first_kid.getKey("/Limits");  
166 - Array last_limits = last_kid.getKey("/Limits");  
167 - if (first_limits.size() >= 2 && last_limits.size() >= 2) { 147 + } else {
  148 + Array kids = a_node["/Kids"];
  149 + size_t nkids = kids.size();
  150 + if (nkids > 0) {
  151 + Array first_limits = kids[0]["/Limits"];
  152 + if (first_limits.size() >= 2) {
168 first = first_limits[0]; 153 first = first_limits[0];
169 - last = last_limits[1]; 154 + last = kids[nkids - 1u]["/Limits"][1];
170 } 155 }
171 } 156 }
172 } 157 }
173 - if (first && last) {  
174 - Array limits({first, last});  
175 - Array olimits = a_node.getKey("/Limits"); 158 + if (!(first && last)) {
  159 + impl.warn(a_node, "unable to determine limits");
  160 + } else {
  161 + Array olimits = a_node["/Limits"];
176 if (olimits.size() == 2) { 162 if (olimits.size() == 2) {
177 auto ofirst = olimits[0]; 163 auto ofirst = olimits[0];
178 auto olast = olimits[1]; 164 auto olast = olimits[1];
179 if (impl.keyValid(ofirst) && impl.keyValid(olast) && 165 if (impl.keyValid(ofirst) && impl.keyValid(olast) &&
180 impl.compareKeys(first, ofirst) == 0 && impl.compareKeys(last, olast) == 0) { 166 impl.compareKeys(first, ofirst) == 0 && impl.compareKeys(last, olast) == 0) {
181 - changed = false; 167 + return;
182 } 168 }
183 } 169 }
184 - if (changed && !a_node.isSameObjectAs(path.begin()->node)) {  
185 - a_node.replaceKey("/Limits", limits); 170 + if (a_node != path.begin()->node) {
  171 + a_node.replaceKey("/Limits", Array({first, last}));
186 } 172 }
187 - } else {  
188 - impl.warn(a_node, "unable to determine limits");  
189 } 173 }
190 174
191 - if (!changed || parent == path.begin()) {  
192 - break;  
193 - } else {  
194 - a_node = parent->node;  
195 - --parent; 175 + if (parent == path.begin()) {
  176 + return;
196 } 177 }
  178 + a_node = parent->node;
  179 + --parent;
197 } 180 }
198 } 181 }
199 182
200 void 183 void
201 -NNTreeIterator::split(QPDFObjectHandle to_split, std::list<PathElement>::iterator parent) 184 +NNTreeIterator::split(Dictionary to_split, std::list<PathElement>::iterator parent)
202 { 185 {
203 // Split some node along the path to the item pointed to by this iterator, and adjust the 186 // Split some node along the path to the item pointed to by this iterator, and adjust the
204 // iterator so it points to the same item. 187 // iterator so it points to the same item.
@@ -232,9 +215,9 @@ NNTreeIterator::split(QPDFObjectHandle to_split, std::list&lt;PathElement&gt;::iterato @@ -232,9 +215,9 @@ NNTreeIterator::split(QPDFObjectHandle to_split, std::list&lt;PathElement&gt;::iterato
232 } 215 }
233 216
234 // Find the array we actually need to split, which is either this node's kids or items. 217 // Find the array we actually need to split, which is either this node's kids or items.
235 - Array kids = to_split.getKey("/Kids"); 218 + Array kids = to_split["/Kids"];
236 size_t nkids = kids.size(); 219 size_t nkids = kids.size();
237 - Array items = to_split.getKey(impl.itemsKey()); 220 + Array items = to_split[impl.itemsKey()];
238 size_t nitems = items.size(); 221 size_t nitems = items.size();
239 222
240 Array first_half; 223 Array first_half;
@@ -278,12 +261,11 @@ NNTreeIterator::split(QPDFObjectHandle to_split, std::list&lt;PathElement&gt;::iterato @@ -278,12 +261,11 @@ NNTreeIterator::split(QPDFObjectHandle to_split, std::list&lt;PathElement&gt;::iterato
278 // is the new first half. In this way, we make the root case identical to the non-root case 261 // is the new first half. In this way, we make the root case identical to the non-root case
279 // so remaining logic can handle them in the same way. 262 // so remaining logic can handle them in the same way.
280 263
281 - auto first_node = impl.qpdf.makeIndirectObject(QPDFObjectHandle::newDictionary());  
282 - first_node.replaceKey(key, first_half);  
283 - Array new_kids; 264 + Dictionary first_node = impl.qpdf.makeIndirectObject(Dictionary({{key, first_half}}));
  265 + auto new_kids = Array::empty();
284 new_kids.push_back(first_node); 266 new_kids.push_back(first_node);
285 - to_split.removeKey("/Limits"); // already shouldn't be there for root  
286 - to_split.removeKey(impl.itemsKey()); 267 + to_split.erase("/Limits"); // already shouldn't be there for root
  268 + to_split.erase(impl.itemsKey());
287 to_split.replaceKey("/Kids", new_kids); 269 to_split.replaceKey("/Kids", new_kids);
288 if (is_leaf) { 270 if (is_leaf) {
289 node = first_node; 271 node = first_node;
@@ -301,7 +283,7 @@ NNTreeIterator::split(QPDFObjectHandle to_split, std::list&lt;PathElement&gt;::iterato @@ -301,7 +283,7 @@ NNTreeIterator::split(QPDFObjectHandle to_split, std::list&lt;PathElement&gt;::iterato
301 283
302 // Create a second half array, and transfer the second half of the items into the second half 284 // Create a second half array, and transfer the second half of the items into the second half
303 // array. 285 // array.
304 - Array second_half; 286 + auto second_half = Array::empty();
305 auto start_idx = static_cast<int>((n / 2) & ~1u); 287 auto start_idx = static_cast<int>((n / 2) & ~1u);
306 while (std::cmp_greater(first_half.size(), start_idx)) { 288 while (std::cmp_greater(first_half.size(), start_idx)) {
307 second_half.push_back(first_half[start_idx]); 289 second_half.push_back(first_half[start_idx]);
@@ -310,8 +292,7 @@ NNTreeIterator::split(QPDFObjectHandle to_split, std::list&lt;PathElement&gt;::iterato @@ -310,8 +292,7 @@ NNTreeIterator::split(QPDFObjectHandle to_split, std::list&lt;PathElement&gt;::iterato
310 resetLimits(to_split, parent); 292 resetLimits(to_split, parent);
311 293
312 // Create a new node to contain the second half 294 // Create a new node to contain the second half
313 - QPDFObjectHandle second_node = impl.qpdf.makeIndirectObject(QPDFObjectHandle::newDictionary());  
314 - second_node.replaceKey(key, second_half); 295 + Dictionary second_node = impl.qpdf.makeIndirectObject(Dictionary({{key, second_half}}));
315 resetLimits(second_node, parent); 296 resetLimits(second_node, parent);
316 297
317 // CURRENT STATE: half the items from the kids or items array in the node being split have been 298 // CURRENT STATE: half the items from the kids or items array in the node being split have been
@@ -322,7 +303,10 @@ NNTreeIterator::split(QPDFObjectHandle to_split, std::list&lt;PathElement&gt;::iterato @@ -322,7 +303,10 @@ NNTreeIterator::split(QPDFObjectHandle to_split, std::list&lt;PathElement&gt;::iterato
322 // kid_number to traverse through it. We need to update to_split's path element, or the node if 303 // kid_number to traverse through it. We need to update to_split's path element, or the node if
323 // this is a leaf, so that the kid/item number points to the right place. 304 // this is a leaf, so that the kid/item number points to the right place.
324 305
325 - Array parent_kids = parent->node.getKey("/Kids"); 306 + Array parent_kids = parent->node["/Kids"];
  307 + if (!parent_kids) {
  308 + impl.error(parent->node, "parent node has no /Kids array");
  309 + }
326 parent_kids.insert(parent->kid_number + 1, second_node); 310 parent_kids.insert(parent->kid_number + 1, second_node);
327 auto cur_elem = parent; 311 auto cur_elem = parent;
328 ++cur_elem; // points to end() for leaf nodes 312 ++cur_elem; // points to end() for leaf nodes
@@ -347,11 +331,7 @@ NNTreeIterator::split(QPDFObjectHandle to_split, std::list&lt;PathElement&gt;::iterato @@ -347,11 +331,7 @@ NNTreeIterator::split(QPDFObjectHandle to_split, std::list&lt;PathElement&gt;::iterato
347 std::list<NNTreeIterator::PathElement>::iterator 331 std::list<NNTreeIterator::PathElement>::iterator
348 NNTreeIterator::lastPathElement() 332 NNTreeIterator::lastPathElement()
349 { 333 {
350 - auto result = path.end();  
351 - if (!path.empty()) {  
352 - --result;  
353 - }  
354 - return result; 334 + return path.empty() ? path.end() : std::prev(path.end());
355 } 335 }
356 336
357 void 337 void
@@ -359,11 +339,11 @@ NNTreeIterator::insertAfter(QPDFObjectHandle const&amp; key, QPDFObjectHandle const&amp; @@ -359,11 +339,11 @@ NNTreeIterator::insertAfter(QPDFObjectHandle const&amp; key, QPDFObjectHandle const&amp;
359 { 339 {
360 if (!valid()) { 340 if (!valid()) {
361 impl.insertFirst(key, value); 341 impl.insertFirst(key, value);
362 - deepen(impl.oh, true, false); 342 + deepen(impl.tree_root, true, false);
363 return; 343 return;
364 } 344 }
365 345
366 - Array items = node.getKey(impl.itemsKey()); 346 + Array items = node[impl.itemsKey()];
367 if (!items) { 347 if (!items) {
368 impl.error(node, "node contains no items array"); 348 impl.error(node, "node contains no items array");
369 } 349 }
@@ -392,7 +372,7 @@ NNTreeIterator::remove() @@ -392,7 +372,7 @@ NNTreeIterator::remove()
392 if (!valid()) { 372 if (!valid()) {
393 throw std::logic_error("attempt made to remove an invalid iterator"); 373 throw std::logic_error("attempt made to remove an invalid iterator");
394 } 374 }
395 - Array items = node.getKey(impl.itemsKey()); 375 + Array items = node[impl.itemsKey()];
396 int nitems = static_cast<int>(items.size()); 376 int nitems = static_cast<int>(items.size());
397 if (std::cmp_greater(item_number + 2, nitems)) { 377 if (std::cmp_greater(item_number + 2, nitems)) {
398 impl.error(node, "found short items array while removing an item"); 378 impl.error(node, "found short items array while removing an item");
@@ -429,7 +409,7 @@ NNTreeIterator::remove() @@ -429,7 +409,7 @@ NNTreeIterator::remove()
429 409
430 if (path.empty()) { 410 if (path.empty()) {
431 // Special case: if this is the root node, we can leave it empty. 411 // Special case: if this is the root node, we can leave it empty.
432 - setItemNumber(impl.oh, -1); 412 + setItemNumber(impl.tree_root, -1);
433 return; 413 return;
434 } 414 }
435 415
@@ -439,7 +419,7 @@ NNTreeIterator::remove() @@ -439,7 +419,7 @@ NNTreeIterator::remove()
439 auto element = lastPathElement(); 419 auto element = lastPathElement();
440 auto parent = element; 420 auto parent = element;
441 --parent; 421 --parent;
442 - Array kids = element->node.getKey("/Kids"); 422 + Array kids = element->node["/Kids"];
443 kids.erase(element->kid_number); 423 kids.erase(element->kid_number);
444 auto nkids = kids.size(); 424 auto nkids = kids.size();
445 if (nkids > 0) { 425 if (nkids > 0) {
@@ -465,10 +445,10 @@ NNTreeIterator::remove() @@ -465,10 +445,10 @@ NNTreeIterator::remove()
465 445
466 if (parent == path.end()) { 446 if (parent == path.end()) {
467 // We erased the very last item. Convert the root to an empty items array. 447 // We erased the very last item. Convert the root to an empty items array.
468 - element->node.removeKey("/Kids");  
469 - element->node.replaceKey(impl.itemsKey(), Array()); 448 + element->node.erase("/Kids");
  449 + element->node.replaceKey(impl.itemsKey(), Array::empty());
470 path.clear(); 450 path.clear();
471 - setItemNumber(impl.oh, -1); 451 + setItemNumber(impl.tree_root, -1);
472 return; 452 return;
473 } 453 }
474 454
@@ -499,14 +479,14 @@ NNTreeIterator::operator==(NNTreeIterator const&amp; other) const @@ -499,14 +479,14 @@ NNTreeIterator::operator==(NNTreeIterator const&amp; other) const
499 } 479 }
500 480
501 bool 481 bool
502 -NNTreeIterator::deepen(QPDFObjectHandle a_node, bool first, bool allow_empty) 482 +NNTreeIterator::deepen(Dictionary a_node, bool first, bool allow_empty)
503 { 483 {
504 // Starting at this node, descend through the first or last kid until we reach a node with 484 // Starting at this node, descend through the first or last kid until we reach a node with
505 // items. If we succeed, return true; otherwise return false and leave path alone. 485 // items. If we succeed, return true; otherwise return false and leave path alone.
506 486
507 auto opath = path; 487 auto opath = path;
508 488
509 - auto fail = [this, &opath](QPDFObjectHandle const& failed_node, std::string const& msg) { 489 + auto fail = [this, &opath](Dictionary const& failed_node, std::string const& msg) {
510 impl.warn(failed_node, msg); 490 impl.warn(failed_node, msg);
511 path = opath; 491 path = opath;
512 return false; 492 return false;
@@ -521,58 +501,59 @@ NNTreeIterator::deepen(QPDFObjectHandle a_node, bool first, bool allow_empty) @@ -521,58 +501,59 @@ NNTreeIterator::deepen(QPDFObjectHandle a_node, bool first, bool allow_empty)
521 return fail(a_node, "loop detected while traversing name/number tree"); 501 return fail(a_node, "loop detected while traversing name/number tree");
522 } 502 }
523 503
524 - if (!a_node.isDictionary()) { 504 + if (!a_node) {
525 return fail(a_node, "non-dictionary node while traversing name/number tree"); 505 return fail(a_node, "non-dictionary node while traversing name/number tree");
526 } 506 }
527 507
528 - Array items = a_node.getKey(impl.itemsKey()); 508 + Array items = a_node[impl.itemsKey()];
529 int nitems = static_cast<int>(items.size()); 509 int nitems = static_cast<int>(items.size());
530 if (nitems > 1) { 510 if (nitems > 1) {
531 setItemNumber(a_node, first ? 0 : nitems - 2); 511 setItemNumber(a_node, first ? 0 : nitems - 2);
532 - break; 512 + return true;
533 } 513 }
534 514
535 - Array kids = a_node.getKey("/Kids"); 515 + Array kids = a_node["/Kids"];
536 int nkids = static_cast<int>(kids.size()); 516 int nkids = static_cast<int>(kids.size());
537 - if (nkids > 0) {  
538 - int kid_number = first ? 0 : nkids - 1;  
539 - addPathElement(a_node, kid_number);  
540 - auto next = kids[kid_number];  
541 - if (!next) {  
542 - return fail(a_node, "kid number " + std::to_string(kid_number) + " is invalid");  
543 - }  
544 - if (!next.indirect()) {  
545 - if (impl.auto_repair) {  
546 - impl.warn(  
547 - a_node,  
548 - "converting kid number " + std::to_string(kid_number) +  
549 - " to an indirect object");  
550 - next = impl.qpdf.makeIndirectObject(next);  
551 - kids.set(kid_number, next);  
552 - } else {  
553 - impl.warn(  
554 - a_node,  
555 - "kid number " + std::to_string(kid_number) + " is not an indirect object");  
556 - } 517 + if (nkids == 0) {
  518 + if (allow_empty && items) {
  519 + setItemNumber(a_node, -1);
  520 + return true;
557 } 521 }
558 - a_node = next;  
559 - } else if (allow_empty && items) {  
560 - setItemNumber(a_node, -1);  
561 - break;  
562 - } else {  
563 return fail( 522 return fail(
564 a_node, 523 a_node,
565 "name/number tree node has neither non-empty " + impl.itemsKey() + " nor /Kids"); 524 "name/number tree node has neither non-empty " + impl.itemsKey() + " nor /Kids");
566 } 525 }
  526 +
  527 + int kid_number = first ? 0 : nkids - 1;
  528 + addPathElement(a_node, kid_number);
  529 + Dictionary next = kids[kid_number];
  530 + if (!next) {
  531 + return fail(a_node, "kid number " + std::to_string(kid_number) + " is invalid");
  532 + }
  533 + if (!next.indirect()) {
  534 + if (impl.auto_repair) {
  535 + impl.warn(
  536 + a_node,
  537 + "converting kid number " + std::to_string(kid_number) +
  538 + " to an indirect object");
  539 + next = impl.qpdf.makeIndirectObject(next);
  540 + kids.set(kid_number, next);
  541 + } else {
  542 + impl.warn(
  543 + a_node,
  544 + "kid number " + std::to_string(kid_number) + " is not an indirect object");
  545 + }
  546 + }
  547 +
  548 + a_node = next;
567 } 549 }
568 - return true;  
569 } 550 }
570 551
571 NNTreeImpl::iterator 552 NNTreeImpl::iterator
572 NNTreeImpl::begin() 553 NNTreeImpl::begin()
573 { 554 {
574 iterator result(*this); 555 iterator result(*this);
575 - result.deepen(oh, true, true); 556 + result.deepen(tree_root, true, true);
576 return result; 557 return result;
577 } 558 }
578 559
@@ -580,7 +561,7 @@ NNTreeImpl::iterator @@ -580,7 +561,7 @@ NNTreeImpl::iterator
580 NNTreeImpl::last() 561 NNTreeImpl::last()
581 { 562 {
582 iterator result(*this); 563 iterator result(*this);
583 - result.deepen(oh, false, true); 564 + result.deepen(tree_root, false, true);
584 return result; 565 return result;
585 } 566 }
586 567
@@ -606,19 +587,19 @@ NNTreeImpl::binarySearch( @@ -606,19 +587,19 @@ NNTreeImpl::binarySearch(
606 Array const& items, 587 Array const& items,
607 size_t num_items, 588 size_t num_items,
608 bool return_prev_if_not_found, 589 bool return_prev_if_not_found,
609 - int (NNTreeImpl::*compare)(QPDFObjectHandle const& key, Array const& arr, int item) const) const 590 + bool search_kids) const
610 { 591 {
611 size_t max_idx = std::bit_ceil(num_items); 592 size_t max_idx = std::bit_ceil(num_items);
612 593
613 int step = static_cast<int>(max_idx / 2); 594 int step = static_cast<int>(max_idx / 2);
614 - size_t checks = max_idx; 595 + int checks = static_cast<int>(std::bit_width(max_idx)); // AppImage gcc version returns size_t
615 int idx = step; 596 int idx = step;
616 int found_idx = -1; 597 int found_idx = -1;
617 598
618 - while (checks > 0) { 599 + for (int i = 0; i < checks; ++i) {
619 int status = -1; 600 int status = -1;
620 if (std::cmp_less(idx, num_items)) { 601 if (std::cmp_less(idx, num_items)) {
621 - status = (this->*compare)(key, items, idx); 602 + status = search_kids ? compareKeyKid(key, items, idx) : compareKeyItem(key, items, idx);
622 if (status == 0) { 603 if (status == 0) {
623 return idx; 604 return idx;
624 } 605 }
@@ -626,21 +607,9 @@ NNTreeImpl::binarySearch( @@ -626,21 +607,9 @@ NNTreeImpl::binarySearch(
626 found_idx = idx; 607 found_idx = idx;
627 } 608 }
628 } 609 }
629 - checks >>= 1;  
630 - if (checks > 0) {  
631 - step >>= 1;  
632 - if (step == 0) {  
633 - step = 1;  
634 - }  
635 -  
636 - if (status < 0) {  
637 - idx -= step;  
638 - } else {  
639 - idx += step;  
640 - }  
641 - } 610 + step = std::max(step / 2, 1);
  611 + idx += status * step;
642 } 612 }
643 -  
644 return return_prev_if_not_found ? found_idx : -1; 613 return return_prev_if_not_found ? found_idx : -1;
645 } 614 }
646 615
@@ -648,7 +617,7 @@ int @@ -648,7 +617,7 @@ int
648 NNTreeImpl::compareKeyItem(QPDFObjectHandle const& key, Array const& items, int idx) const 617 NNTreeImpl::compareKeyItem(QPDFObjectHandle const& key, Array const& items, int idx) const
649 { 618 {
650 if (!keyValid(items[2 * idx])) { 619 if (!keyValid(items[2 * idx])) {
651 - error(oh, ("item at index " + std::to_string(2 * idx) + " is not the right type")); 620 + error(tree_root, ("item at index " + std::to_string(2 * idx) + " is not the right type"));
652 } 621 }
653 return compareKeys(key, items[2 * idx]); 622 return compareKeys(key, items[2 * idx]);
654 } 623 }
@@ -656,10 +625,11 @@ NNTreeImpl::compareKeyItem(QPDFObjectHandle const&amp; key, Array const&amp; items, int @@ -656,10 +625,11 @@ NNTreeImpl::compareKeyItem(QPDFObjectHandle const&amp; key, Array const&amp; items, int
656 int 625 int
657 NNTreeImpl::compareKeyKid(QPDFObjectHandle const& key, Array const& kids, int idx) const 626 NNTreeImpl::compareKeyKid(QPDFObjectHandle const& key, Array const& kids, int idx) const
658 { 627 {
659 - if (!kids[idx].isDictionary()) {  
660 - error(oh, "invalid kid at index " + std::to_string(idx)); 628 + Dictionary kid = kids[idx];
  629 + if (!kid) {
  630 + error(tree_root, "invalid kid at index " + std::to_string(idx));
661 } 631 }
662 - Array limits = kids[idx].getKey("/Limits"); 632 + Array limits = kid["/Limits"];
663 if (!(keyValid(limits[0]) && keyValid(limits[1]))) { 633 if (!(keyValid(limits[0]) && keyValid(limits[1]))) {
664 error(kids[idx], "node is missing /Limits"); 634 error(kids[idx], "node is missing /Limits");
665 } 635 }
@@ -675,26 +645,25 @@ NNTreeImpl::compareKeyKid(QPDFObjectHandle const&amp; key, Array const&amp; kids, int id @@ -675,26 +645,25 @@ NNTreeImpl::compareKeyKid(QPDFObjectHandle const&amp; key, Array const&amp; kids, int id
675 void 645 void
676 NNTreeImpl::repair() 646 NNTreeImpl::repair()
677 { 647 {
678 - auto new_node = QPDFObjectHandle::newDictionary();  
679 - new_node.replaceKey(itemsKey(), Array()); 648 + auto new_node = Dictionary({{itemsKey(), Array::empty()}});
680 NNTreeImpl repl(qpdf, new_node, key_type, value_valid, false); 649 NNTreeImpl repl(qpdf, new_node, key_type, value_valid, false);
681 for (auto const& [key, value]: *this) { 650 for (auto const& [key, value]: *this) {
682 if (key && value) { 651 if (key && value) {
683 repl.insert(key, value); 652 repl.insert(key, value);
684 } 653 }
685 } 654 }
686 - oh.replaceKey("/Kids", new_node.getKey("/Kids"));  
687 - oh.replaceKey(itemsKey(), new_node.getKey(itemsKey())); 655 + tree_root.replaceKey("/Kids", new_node["/Kids"]);
  656 + tree_root.replaceKey(itemsKey(), new_node[itemsKey()]);
688 } 657 }
689 658
690 NNTreeImpl::iterator 659 NNTreeImpl::iterator
691 -NNTreeImpl::find(QPDFObjectHandle key, bool return_prev_if_not_found) 660 +NNTreeImpl::find(QPDFObjectHandle const& key, bool return_prev_if_not_found)
692 { 661 {
693 try { 662 try {
694 return findInternal(key, return_prev_if_not_found); 663 return findInternal(key, return_prev_if_not_found);
695 } catch (QPDFExc& e) { 664 } catch (QPDFExc& e) {
696 if (auto_repair) { 665 if (auto_repair) {
697 - warn(oh, std::string("attempting to repair after error: ") + e.what()); 666 + warn(tree_root, std::string("attempting to repair after error: ") + e.what());
698 repair(); 667 repair();
699 return findInternal(key, return_prev_if_not_found); 668 return findInternal(key, return_prev_if_not_found);
700 } else { 669 } else {
@@ -707,26 +676,22 @@ NNTreeImpl::iterator @@ -707,26 +676,22 @@ NNTreeImpl::iterator
707 NNTreeImpl::findInternal(QPDFObjectHandle const& key, bool return_prev_if_not_found) 676 NNTreeImpl::findInternal(QPDFObjectHandle const& key, bool return_prev_if_not_found)
708 { 677 {
709 auto first_item = begin(); 678 auto first_item = begin();
710 - auto last_item = end();  
711 - if (first_item == end()) { 679 + if (!first_item.valid()) {
712 return end(); 680 return end();
713 } 681 }
714 - if (first_item.valid()) {  
715 - if (!keyValid(first_item->first)) {  
716 - error(oh, "encountered invalid key in find");  
717 - }  
718 - if (!value_valid(first_item->second)) {  
719 - error(oh, "encountered invalid value in find");  
720 - }  
721 - if (compareKeys(key, first_item->first) < 0) {  
722 - // Before the first key  
723 - return end();  
724 - } 682 + if (!keyValid(first_item->first)) {
  683 + error(tree_root, "encountered invalid key in find");
  684 + }
  685 + if (!value_valid(first_item->second)) {
  686 + error(tree_root, "encountered invalid value in find");
  687 + }
  688 + if (compareKeys(key, first_item->first) < 0) {
  689 + // Before the first key
  690 + return end();
725 } 691 }
726 - qpdf_assert_debug(!last_item.valid());  
727 692
728 QPDFObjGen::set seen; 693 QPDFObjGen::set seen;
729 - auto node = oh; 694 + auto node = tree_root;
730 iterator result(*this); 695 iterator result(*this);
731 696
732 while (true) { 697 while (true) {
@@ -734,35 +699,33 @@ NNTreeImpl::findInternal(QPDFObjectHandle const&amp; key, bool return_prev_if_not_fo @@ -734,35 +699,33 @@ NNTreeImpl::findInternal(QPDFObjectHandle const&amp; key, bool return_prev_if_not_fo
734 error(node, "loop detected in find"); 699 error(node, "loop detected in find");
735 } 700 }
736 701
737 - Array items = node.getKey(itemsKey()); 702 + Array items = node[itemsKey()];
738 size_t nitems = items.size(); 703 size_t nitems = items.size();
739 if (nitems > 1) { 704 if (nitems > 1) {
740 - int idx = binarySearch(  
741 - key, items, nitems / 2, return_prev_if_not_found, &NNTreeImpl::compareKeyItem); 705 + int idx = binarySearch(key, items, nitems / 2, return_prev_if_not_found, false);
742 if (idx >= 0) { 706 if (idx >= 0) {
743 result.setItemNumber(node, 2 * idx); 707 result.setItemNumber(node, 2 * idx);
744 if (!result.impl.keyValid(result.ivalue.first)) { 708 if (!result.impl.keyValid(result.ivalue.first)) {
745 error(node, "encountered invalid key in find"); 709 error(node, "encountered invalid key in find");
746 } 710 }
747 if (!result.impl.value_valid(result.ivalue.second)) { 711 if (!result.impl.value_valid(result.ivalue.second)) {
748 - error(oh, "encountered invalid value in find"); 712 + error(tree_root, "encountered invalid value in find");
749 } 713 }
750 } 714 }
751 return result; 715 return result;
752 } 716 }
753 717
754 - Array kids = node.getKey("/Kids"); 718 + Array kids = node["/Kids"];
755 size_t nkids = kids.size(); 719 size_t nkids = kids.size();
756 - if (nkids > 0) {  
757 - int idx = binarySearch(key, kids, nkids, true, &NNTreeImpl::compareKeyKid);  
758 - if (idx == -1) {  
759 - error(node, "unexpected -1 from binary search of kids; limits may by wrong");  
760 - }  
761 - result.addPathElement(node, idx);  
762 - node = kids[idx];  
763 - } else { 720 + if (nkids == 0) {
764 error(node, "bad node during find"); 721 error(node, "bad node during find");
765 } 722 }
  723 + int idx = binarySearch(key, kids, nkids, true, true);
  724 + if (idx == -1) {
  725 + error(node, "unexpected -1 from binary search of kids; limits may by wrong");
  726 + }
  727 + result.addPathElement(node, idx);
  728 + node = kids[idx];
766 } 729 }
767 } 730 }
768 731
@@ -770,18 +733,15 @@ NNTreeImpl::iterator @@ -770,18 +733,15 @@ NNTreeImpl::iterator
770 NNTreeImpl::insertFirst(QPDFObjectHandle const& key, QPDFObjectHandle const& value) 733 NNTreeImpl::insertFirst(QPDFObjectHandle const& key, QPDFObjectHandle const& value)
771 { 734 {
772 auto iter = begin(); 735 auto iter = begin();
773 - Array items(nullptr);  
774 - if (iter.node.isDictionary()) {  
775 - items = iter.node.getKey(itemsKey());  
776 - } 736 + Array items = iter.node[items_key];
777 if (!items) { 737 if (!items) {
778 - error(oh, "unable to find a valid items node"); 738 + error(tree_root, "unable to find a valid items node");
779 } 739 }
780 if (!(key && value)) { 740 if (!(key && value)) {
781 - error(oh, "unable to insert null key or value"); 741 + error(tree_root, "unable to insert null key or value");
782 } 742 }
783 if (!value_valid(value)) { 743 if (!value_valid(value)) {
784 - error(oh, "attempting to insert an invalid value"); 744 + error(tree_root, "attempting to insert an invalid value");
785 } 745 }
786 items.insert(0, key); 746 items.insert(0, key);
787 items.insert(1, value); 747 items.insert(1, value);
@@ -798,7 +758,7 @@ NNTreeImpl::insert(QPDFObjectHandle const&amp; key, QPDFObjectHandle const&amp; value) @@ -798,7 +758,7 @@ NNTreeImpl::insert(QPDFObjectHandle const&amp; key, QPDFObjectHandle const&amp; value)
798 if (!iter.valid()) { 758 if (!iter.valid()) {
799 return insertFirst(key, value); 759 return insertFirst(key, value);
800 } else if (compareKeys(key, iter->first) == 0) { 760 } else if (compareKeys(key, iter->first) == 0) {
801 - Array items = iter.node.getKey(itemsKey()); 761 + Array items = iter.node[itemsKey()];
802 items.set(iter.item_number + 1, value); 762 items.set(iter.item_number + 1, value);
803 iter.updateIValue(); 763 iter.updateIValue();
804 } else { 764 } else {
@@ -829,21 +789,21 @@ NNTreeImpl::validate(bool a_repair) @@ -829,21 +789,21 @@ NNTreeImpl::validate(bool a_repair)
829 try { 789 try {
830 for (auto const& [key, value]: *this) { 790 for (auto const& [key, value]: *this) {
831 if (!keyValid(key)) { 791 if (!keyValid(key)) {
832 - error(oh, "invalid key in validate"); 792 + error(tree_root, "invalid key in validate");
833 } 793 }
834 if (!value_valid(value)) { 794 if (!value_valid(value)) {
835 - error(oh, "invalid value in validate"); 795 + error(tree_root, "invalid value in validate");
836 } 796 }
837 if (first) { 797 if (first) {
838 first = false; 798 first = false;
839 } else if (last_key && compareKeys(last_key, key) != -1) { 799 } else if (last_key && compareKeys(last_key, key) != -1) {
840 - error(oh, "keys are not sorted in validate"); 800 + error(tree_root, "keys are not sorted in validate");
841 } 801 }
842 last_key = key; 802 last_key = key;
843 } 803 }
844 } catch (QPDFExc& e) { 804 } catch (QPDFExc& e) {
845 if (a_repair) { 805 if (a_repair) {
846 - warn(oh, std::string("attempting to repair after error: ") + e.what()); 806 + warn(tree_root, std::string("attempting to repair after error: ") + e.what());
847 repair(); 807 repair();
848 } 808 }
849 return false; 809 return false;
libqpdf/QPDF_Array.cc
@@ -61,11 +61,6 @@ Array::array() const @@ -61,11 +61,6 @@ Array::array() const
61 return nullptr; // unreachable 61 return nullptr; // unreachable
62 } 62 }
63 63
64 -Array::Array(bool empty) :  
65 - BaseHandle(empty ? QPDFObject::create<QPDF_Array>() : nullptr)  
66 -{  
67 -}  
68 -  
69 Array::Array(std::vector<QPDFObjectHandle> const& items) : 64 Array::Array(std::vector<QPDFObjectHandle> const& items) :
70 BaseHandle(QPDFObject::create<QPDF_Array>(items)) 65 BaseHandle(QPDFObject::create<QPDF_Array>(items))
71 { 66 {
@@ -76,6 +71,12 @@ Array::Array(std::vector&lt;QPDFObjectHandle&gt;&amp;&amp; items) : @@ -76,6 +71,12 @@ Array::Array(std::vector&lt;QPDFObjectHandle&gt;&amp;&amp; items) :
76 { 71 {
77 } 72 }
78 73
  74 +Array
  75 +Array::empty()
  76 +{
  77 + return Array(std::vector<QPDFObjectHandle>());
  78 +}
  79 +
79 Array::iterator 80 Array::iterator
80 Array::begin() 81 Array::begin()
81 { 82 {
@@ -399,7 +400,6 @@ QPDFObjectHandle::getArrayItem(int n) const @@ -399,7 +400,6 @@ QPDFObjectHandle::getArrayItem(int n) const
399 return newNull(); 400 return newNull();
400 } 401 }
401 objectWarning("returning null for out of bounds array access"); 402 objectWarning("returning null for out of bounds array access");
402 -  
403 } else { 403 } else {
404 typeWarning("array", "returning null"); 404 typeWarning("array", "returning null");
405 } 405 }
libqpdf/QPDF_Dictionary.cc
@@ -16,26 +16,23 @@ BaseDictionary::dict() const @@ -16,26 +16,23 @@ BaseDictionary::dict() const
16 return nullptr; // unreachable 16 return nullptr; // unreachable
17 } 17 }
18 18
19 -bool  
20 -BaseDictionary::hasKey(std::string const& key) const 19 +QPDFObjectHandle const&
  20 +BaseHandle::operator[](std::string const& key) const
21 { 21 {
22 - auto d = dict();  
23 - return d->items.contains(key) && !d->items[key].null(); 22 + if (auto d = as<QPDF_Dictionary>()) {
  23 + auto it = d->items.find(key);
  24 + if (it != d->items.end()) {
  25 + return it->second;
  26 + }
  27 + }
  28 + static const QPDFObjectHandle null_obj;
  29 + return null_obj;
24 } 30 }
25 31
26 -QPDFObjectHandle  
27 -BaseDictionary::getKey(std::string const& key) const 32 +bool
  33 +BaseHandle::contains(std::string const& key) const
28 { 34 {
29 - auto d = dict();  
30 -  
31 - // PDF spec says fetching a non-existent key from a dictionary returns the null object.  
32 - auto item = d->items.find(key);  
33 - if (item != d->items.end()) {  
34 - // May be a null object  
35 - return item->second;  
36 - }  
37 - static auto constexpr msg = " -> dictionary key $VD"sv;  
38 - return QPDF_Null::create(obj, msg, key); 35 + return !(*this)[key].null();
39 } 36 }
40 37
41 std::set<std::string> 38 std::set<std::string>
@@ -56,11 +53,14 @@ BaseDictionary::getAsMap() const @@ -56,11 +53,14 @@ BaseDictionary::getAsMap() const
56 return dict()->items; 53 return dict()->items;
57 } 54 }
58 55
59 -void  
60 -BaseDictionary::removeKey(std::string const& key) 56 +size_t
  57 +BaseHandle::erase(const std::string& key)
61 { 58 {
62 // no-op if key does not exist 59 // no-op if key does not exist
63 - dict()->items.erase(key); 60 + if (auto d = as<QPDF_Dictionary>()) {
  61 + return d->items.erase(key);
  62 + }
  63 + return 0;
64 } 64 }
65 65
66 void 66 void
@@ -78,13 +78,28 @@ BaseDictionary::replaceKey(std::string const&amp; key, QPDFObjectHandle value) @@ -78,13 +78,28 @@ BaseDictionary::replaceKey(std::string const&amp; key, QPDFObjectHandle value)
78 } 78 }
79 } 79 }
80 80
  81 +Dictionary::Dictionary(std::map<std::string, QPDFObjectHandle>&& dict) :
  82 + BaseDictionary(std::move(dict))
  83 +{
  84 +}
  85 +
  86 +Dictionary::Dictionary(std::shared_ptr<QPDFObject> const& obj) :
  87 + BaseDictionary(obj)
  88 +{
  89 +}
  90 +
  91 +Dictionary
  92 +Dictionary::empty()
  93 +{
  94 + return Dictionary(std::map<std::string, QPDFObjectHandle>());
  95 +}
  96 +
81 void 97 void
82 QPDFObjectHandle::checkOwnership(QPDFObjectHandle const& item) const 98 QPDFObjectHandle::checkOwnership(QPDFObjectHandle const& item) const
83 { 99 {
84 auto qpdf = getOwningQPDF(); 100 auto qpdf = getOwningQPDF();
85 auto item_qpdf = item.getOwningQPDF(); 101 auto item_qpdf = item.getOwningQPDF();
86 if (qpdf && item_qpdf && qpdf != item_qpdf) { 102 if (qpdf && item_qpdf && qpdf != item_qpdf) {
87 - QTC::TC("qpdf", "QPDFObjectHandle check ownership");  
88 throw std::logic_error( 103 throw std::logic_error(
89 "Attempting to add an object from a different QPDF. Use " 104 "Attempting to add an object from a different QPDF. Use "
90 "QPDF::copyForeignObject to add objects from another file."); 105 "QPDF::copyForeignObject to add objects from another file.");
@@ -94,9 +109,8 @@ QPDFObjectHandle::checkOwnership(QPDFObjectHandle const&amp; item) const @@ -94,9 +109,8 @@ QPDFObjectHandle::checkOwnership(QPDFObjectHandle const&amp; item) const
94 bool 109 bool
95 QPDFObjectHandle::hasKey(std::string const& key) const 110 QPDFObjectHandle::hasKey(std::string const& key) const
96 { 111 {
97 - auto dict = as_dictionary(strict);  
98 - if (dict) {  
99 - return dict.hasKey(key); 112 + if (Dictionary dict = *this) {
  113 + return dict.contains(key);
100 } else { 114 } else {
101 typeWarning("dictionary", "returning false for a key containment request"); 115 typeWarning("dictionary", "returning false for a key containment request");
102 QTC::TC("qpdf", "QPDFObjectHandle dictionary false for hasKey"); 116 QTC::TC("qpdf", "QPDFObjectHandle dictionary false for hasKey");
@@ -107,11 +121,14 @@ QPDFObjectHandle::hasKey(std::string const&amp; key) const @@ -107,11 +121,14 @@ QPDFObjectHandle::hasKey(std::string const&amp; key) const
107 QPDFObjectHandle 121 QPDFObjectHandle
108 QPDFObjectHandle::getKey(std::string const& key) const 122 QPDFObjectHandle::getKey(std::string const& key) const
109 { 123 {
110 - if (auto dict = as_dictionary(strict)) {  
111 - return dict.getKey(key); 124 + if (auto result = (*this)[key]) {
  125 + return result;
  126 + }
  127 + if (isDictionary()) {
  128 + static auto constexpr msg = " -> dictionary key $VD"sv;
  129 + return QPDF_Null::create(obj, msg, key);
112 } 130 }
113 typeWarning("dictionary", "returning null for attempted key retrieval"); 131 typeWarning("dictionary", "returning null for attempted key retrieval");
114 - QTC::TC("qpdf", "QPDFObjectHandle dictionary null for getKey");  
115 static auto constexpr msg = " -> null returned from getting key $VD from non-Dictionary"sv; 132 static auto constexpr msg = " -> null returned from getting key $VD from non-Dictionary"sv;
116 return QPDF_Null::create(obj, msg, ""); 133 return QPDF_Null::create(obj, msg, "");
117 } 134 }
@@ -174,21 +191,16 @@ QPDFObjectHandle::replaceKeyAndGetOld(std::string const&amp; key, QPDFObjectHandle c @@ -174,21 +191,16 @@ QPDFObjectHandle::replaceKeyAndGetOld(std::string const&amp; key, QPDFObjectHandle c
174 void 191 void
175 QPDFObjectHandle::removeKey(std::string const& key) 192 QPDFObjectHandle::removeKey(std::string const& key)
176 { 193 {
177 - if (auto dict = as_dictionary(strict)) {  
178 - dict.removeKey(key); 194 + if (erase(key) || isDictionary()) {
179 return; 195 return;
180 } 196 }
181 typeWarning("dictionary", "ignoring key removal request"); 197 typeWarning("dictionary", "ignoring key removal request");
182 - QTC::TC("qpdf", "QPDFObjectHandle dictionary ignoring removeKey");  
183 } 198 }
184 199
185 QPDFObjectHandle 200 QPDFObjectHandle
186 QPDFObjectHandle::removeKeyAndGetOld(std::string const& key) 201 QPDFObjectHandle::removeKeyAndGetOld(std::string const& key)
187 { 202 {
188 - auto result = QPDFObjectHandle::newNull();  
189 - if (auto dict = as_dictionary(strict)) {  
190 - result = dict.getKey(key);  
191 - }  
192 - removeKey(key);  
193 - return result; 203 + auto result = (*this)[key];
  204 + erase(key);
  205 + return result ? result : newNull();
194 } 206 }
libqpdf/qpdf/NNTree.hh
@@ -81,9 +81,13 @@ class NNTreeIterator final @@ -81,9 +81,13 @@ class NNTreeIterator final
81 class PathElement 81 class PathElement
82 { 82 {
83 public: 83 public:
84 - PathElement(QPDFObjectHandle const& node, int kid_number); 84 + PathElement(qpdf::Dictionary const& node, int kid_number) :
  85 + node(node),
  86 + kid_number(kid_number)
  87 + {
  88 + }
85 89
86 - QPDFObjectHandle node; 90 + qpdf::Dictionary node;
87 int kid_number; 91 int kid_number;
88 }; 92 };
89 93
@@ -92,7 +96,7 @@ class NNTreeIterator final @@ -92,7 +96,7 @@ class NNTreeIterator final
92 { 96 {
93 } 97 }
94 void updateIValue(bool allow_invalid = true); 98 void updateIValue(bool allow_invalid = true);
95 - bool deepen(QPDFObjectHandle node, bool first, bool allow_empty); 99 + bool deepen(qpdf::Dictionary node, bool first, bool allow_empty);
96 void 100 void
97 setItemNumber(QPDFObjectHandle const& a_node, int n) 101 setItemNumber(QPDFObjectHandle const& a_node, int n)
98 { 102 {
@@ -105,16 +109,16 @@ class NNTreeIterator final @@ -105,16 +109,16 @@ class NNTreeIterator final
105 { 109 {
106 path.emplace_back(a_node, kid_number); 110 path.emplace_back(a_node, kid_number);
107 } 111 }
108 - QPDFObjectHandle getNextKid(PathElement& element, bool backward); 112 + qpdf::Dictionary getNextKid(PathElement& element, bool backward);
109 void increment(bool backward); 113 void increment(bool backward);
110 - void resetLimits(QPDFObjectHandle node, std::list<PathElement>::iterator parent); 114 + void resetLimits(qpdf::Dictionary node, std::list<PathElement>::iterator parent);
111 115
112 - void split(QPDFObjectHandle to_split, std::list<PathElement>::iterator parent); 116 + void split(qpdf::Dictionary to_split, std::list<PathElement>::iterator parent);
113 std::list<PathElement>::iterator lastPathElement(); 117 std::list<PathElement>::iterator lastPathElement();
114 118
115 NNTreeImpl& impl; 119 NNTreeImpl& impl;
116 std::list<PathElement> path; 120 std::list<PathElement> path;
117 - QPDFObjectHandle node; 121 + qpdf::Dictionary node;
118 int item_number{-1}; 122 int item_number{-1};
119 value_type ivalue; 123 value_type ivalue;
120 }; 124 };
@@ -128,12 +132,12 @@ class NNTreeImpl final @@ -128,12 +132,12 @@ class NNTreeImpl final
128 132
129 NNTreeImpl( 133 NNTreeImpl(
130 QPDF& qpdf, 134 QPDF& qpdf,
131 - QPDFObjectHandle& oh, 135 + qpdf::Dictionary tree_root,
132 qpdf_object_type_e key_type, 136 qpdf_object_type_e key_type,
133 std::function<bool(QPDFObjectHandle const&)> value_validator, 137 std::function<bool(QPDFObjectHandle const&)> value_validator,
134 bool auto_repair) : 138 bool auto_repair) :
135 qpdf(qpdf), 139 qpdf(qpdf),
136 - oh(oh), 140 + tree_root(std::move(tree_root)),
137 key_type(key_type), 141 key_type(key_type),
138 items_key(key_type == ::ot_string ? "/Names" : "/Nums"), 142 items_key(key_type == ::ot_string ? "/Names" : "/Nums"),
139 value_valid(value_validator), 143 value_valid(value_validator),
@@ -147,7 +151,7 @@ class NNTreeImpl final @@ -147,7 +151,7 @@ class NNTreeImpl final
147 return {*this}; 151 return {*this};
148 } 152 }
149 iterator last(); 153 iterator last();
150 - iterator find(QPDFObjectHandle key, bool return_prev_if_not_found = false); 154 + iterator find(QPDFObjectHandle const& key, bool return_prev_if_not_found = false);
151 iterator insertFirst(QPDFObjectHandle const& key, QPDFObjectHandle const& value); 155 iterator insertFirst(QPDFObjectHandle const& key, QPDFObjectHandle const& value);
152 iterator insert(QPDFObjectHandle const& key, QPDFObjectHandle const& value); 156 iterator insert(QPDFObjectHandle const& key, QPDFObjectHandle const& value);
153 bool remove(QPDFObjectHandle const& key, QPDFObjectHandle* value = nullptr); 157 bool remove(QPDFObjectHandle const& key, QPDFObjectHandle* value = nullptr);
@@ -170,8 +174,7 @@ class NNTreeImpl final @@ -170,8 +174,7 @@ class NNTreeImpl final
170 qpdf::Array const& items, 174 qpdf::Array const& items,
171 size_t num_items, 175 size_t num_items,
172 bool return_prev_if_not_found, 176 bool return_prev_if_not_found,
173 - int (NNTreeImpl::*compare)(QPDFObjectHandle const& key, qpdf::Array const& arr, int item)  
174 - const) const; 177 + bool search_kids) const;
175 int compareKeyItem(QPDFObjectHandle const& key, qpdf::Array const& items, int idx) const; 178 int compareKeyItem(QPDFObjectHandle const& key, qpdf::Array const& items, int idx) const;
176 int compareKeyKid(QPDFObjectHandle const& key, qpdf::Array const& items, int idx) const; 179 int compareKeyKid(QPDFObjectHandle const& key, qpdf::Array const& items, int idx) const;
177 void warn(QPDFObjectHandle const& node, std::string const& msg); 180 void warn(QPDFObjectHandle const& node, std::string const& msg);
@@ -191,7 +194,7 @@ class NNTreeImpl final @@ -191,7 +194,7 @@ class NNTreeImpl final
191 194
192 QPDF& qpdf; 195 QPDF& qpdf;
193 int split_threshold{32}; 196 int split_threshold{32};
194 - QPDFObjectHandle oh; 197 + qpdf::Dictionary tree_root;
195 const qpdf_object_type_e key_type; 198 const qpdf_object_type_e key_type;
196 const std::string items_key; 199 const std::string items_key;
197 const std::function<bool(QPDFObjectHandle const&)> value_valid; 200 const std::function<bool(QPDFObjectHandle const&)> value_valid;
libqpdf/qpdf/QPDFObjectHandle_private.hh
@@ -12,18 +12,19 @@ namespace qpdf @@ -12,18 +12,19 @@ namespace qpdf
12 class Array final: public BaseHandle 12 class Array final: public BaseHandle
13 { 13 {
14 public: 14 public:
15 - // Create an empty Array. If 'empty' is false, create a null Array.  
16 - Array(bool empty = true); 15 + Array() = default;
17 16
18 - Array(std::vector<QPDFObjectHandle> const& items); 17 + explicit Array(std::vector<QPDFObjectHandle> const& items);
19 18
20 - Array(std::vector<QPDFObjectHandle>&& items); 19 + explicit Array(std::vector<QPDFObjectHandle>&& items);
21 20
22 Array(Array const& other) : 21 Array(Array const& other) :
23 BaseHandle(other.obj) 22 BaseHandle(other.obj)
24 { 23 {
25 } 24 }
26 25
  26 + static Array empty();
  27 +
27 Array& 28 Array&
28 operator=(Array const& other) 29 operator=(Array const& other)
29 { 30 {
@@ -118,6 +119,11 @@ namespace qpdf @@ -118,6 +119,11 @@ namespace qpdf
118 class BaseDictionary: public BaseHandle 119 class BaseDictionary: public BaseHandle
119 { 120 {
120 public: 121 public:
  122 + // The following methods are not part of the public API.
  123 + std::set<std::string> getKeys();
  124 + std::map<std::string, QPDFObjectHandle> const& getAsMap() const;
  125 + void replaceKey(std::string const& key, QPDFObjectHandle value);
  126 +
121 using iterator = std::map<std::string, QPDFObjectHandle>::iterator; 127 using iterator = std::map<std::string, QPDFObjectHandle>::iterator;
122 using const_iterator = std::map<std::string, QPDFObjectHandle>::const_iterator; 128 using const_iterator = std::map<std::string, QPDFObjectHandle>::const_iterator;
123 using reverse_iterator = std::map<std::string, QPDFObjectHandle>::reverse_iterator; 129 using reverse_iterator = std::map<std::string, QPDFObjectHandle>::reverse_iterator;
@@ -196,41 +202,87 @@ namespace qpdf @@ -196,41 +202,87 @@ namespace qpdf
196 return {}; 202 return {};
197 } 203 }
198 204
199 - // The following methods are not part of the public API.  
200 - bool hasKey(std::string const& key) const;  
201 - QPDFObjectHandle getKey(std::string const& key) const;  
202 - std::set<std::string> getKeys();  
203 - std::map<std::string, QPDFObjectHandle> const& getAsMap() const;  
204 - void removeKey(std::string const& key);  
205 - void replaceKey(std::string const& key, QPDFObjectHandle value);  
206 -  
207 protected: 205 protected:
208 BaseDictionary() = default; 206 BaseDictionary() = default;
209 - BaseDictionary(std::shared_ptr<QPDFObject> const& obj) :  
210 - BaseHandle(obj) {};  
211 - BaseDictionary(std::shared_ptr<QPDFObject>&& obj) :  
212 - BaseHandle(std::move(obj)) {}; 207 +
  208 + explicit BaseDictionary(std::map<std::string, QPDFObjectHandle> const& dict) :
  209 + BaseHandle(QPDFObject::create<QPDF_Dictionary>(dict))
  210 + {
  211 + }
  212 +
  213 + explicit BaseDictionary(std::map<std::string, QPDFObjectHandle>&& dict) :
  214 + BaseHandle(QPDFObject::create<QPDF_Dictionary>(std::move(dict)))
  215 + {
  216 + }
  217 +
  218 + explicit BaseDictionary(std::shared_ptr<QPDFObject> const& obj) :
  219 + BaseHandle(obj)
  220 + {
  221 + }
  222 + explicit BaseDictionary(std::shared_ptr<QPDFObject>&& obj) :
  223 + BaseHandle(std::move(obj))
  224 + {
  225 + }
213 BaseDictionary(BaseDictionary const&) = default; 226 BaseDictionary(BaseDictionary const&) = default;
214 BaseDictionary& operator=(BaseDictionary const&) = default; 227 BaseDictionary& operator=(BaseDictionary const&) = default;
215 BaseDictionary(BaseDictionary&&) = default; 228 BaseDictionary(BaseDictionary&&) = default;
216 BaseDictionary& operator=(BaseDictionary&&) = default; 229 BaseDictionary& operator=(BaseDictionary&&) = default;
  230 +
  231 + explicit BaseDictionary(QPDFObjectHandle const& oh) :
  232 + BaseHandle(oh.resolved_type_code() == ::ot_dictionary ? oh : QPDFObjectHandle())
  233 + {
  234 + }
  235 +
  236 + explicit BaseDictionary(QPDFObjectHandle&& oh) :
  237 + BaseHandle(
  238 + oh.resolved_type_code() == ::ot_dictionary ? std::move(oh) : QPDFObjectHandle())
  239 + {
  240 + }
217 ~BaseDictionary() = default; 241 ~BaseDictionary() = default;
218 242
219 QPDF_Dictionary* dict() const; 243 QPDF_Dictionary* dict() const;
220 }; 244 };
221 245
  246 + // Dictionary only defines con/destructors. All other methods are inherited from BaseDictionary.
222 class Dictionary final: public BaseDictionary 247 class Dictionary final: public BaseDictionary
223 { 248 {
224 public: 249 public:
225 - explicit Dictionary(std::shared_ptr<QPDFObject> const& obj) :  
226 - BaseDictionary(obj) 250 + Dictionary() = default;
  251 + explicit Dictionary(std::map<std::string, QPDFObjectHandle>&& dict);
  252 + explicit Dictionary(std::shared_ptr<QPDFObject> const& obj);
  253 +
  254 + static Dictionary empty();
  255 +
  256 + Dictionary(Dictionary const&) = default;
  257 + Dictionary& operator=(Dictionary const&) = default;
  258 + Dictionary(Dictionary&&) = default;
  259 + Dictionary& operator=(Dictionary&&) = default;
  260 +
  261 + Dictionary(QPDFObjectHandle const& oh) :
  262 + BaseDictionary(oh)
227 { 263 {
228 } 264 }
229 265
230 - explicit Dictionary(std::shared_ptr<QPDFObject>&& obj) :  
231 - BaseDictionary(std::move(obj)) 266 + Dictionary&
  267 + operator=(QPDFObjectHandle const& oh)
  268 + {
  269 + assign(::ot_dictionary, oh);
  270 + return *this;
  271 + }
  272 +
  273 + Dictionary(QPDFObjectHandle&& oh) :
  274 + BaseDictionary(std::move(oh))
232 { 275 {
233 } 276 }
  277 +
  278 + Dictionary&
  279 + operator=(QPDFObjectHandle&& oh)
  280 + {
  281 + assign(::ot_dictionary, std::move(oh));
  282 + return *this;
  283 + }
  284 +
  285 + ~Dictionary() = default;
234 }; 286 };
235 287
236 class Name final: public BaseHandle 288 class Name final: public BaseHandle
qpdf/qpdf.testcov
@@ -304,11 +304,9 @@ QPDFObjectHandle insert array bounds 0 @@ -304,11 +304,9 @@ QPDFObjectHandle insert array bounds 0
304 QPDFObjectHandle array ignoring append item 0 304 QPDFObjectHandle array ignoring append item 0
305 QPDFObjectHandle array ignoring erase item 0 305 QPDFObjectHandle array ignoring erase item 0
306 QPDFObjectHandle dictionary false for hasKey 0 306 QPDFObjectHandle dictionary false for hasKey 0
307 -QPDFObjectHandle dictionary null for getKey 0  
308 QPDFObjectHandle dictionary empty set for getKeys 0 307 QPDFObjectHandle dictionary empty set for getKeys 0
309 QPDFObjectHandle dictionary empty map for asMap 0 308 QPDFObjectHandle dictionary empty map for asMap 0
310 QPDFObjectHandle dictionary ignoring replaceKey 0 309 QPDFObjectHandle dictionary ignoring replaceKey 0
311 -QPDFObjectHandle dictionary ignoring removeKey 0  
312 QPDFObjectHandle numeric non-numeric 0 310 QPDFObjectHandle numeric non-numeric 0
313 QPDFObjectHandle erase array bounds 0 311 QPDFObjectHandle erase array bounds 0
314 qpdf-c called qpdf_check_pdf 0 312 qpdf-c called qpdf_check_pdf 0
@@ -537,7 +535,6 @@ QPDFWriter preserve object streams 0 @@ -537,7 +535,6 @@ QPDFWriter preserve object streams 0
537 QPDFWriter preserve object streams preserve unreferenced 0 535 QPDFWriter preserve object streams preserve unreferenced 0
538 QPDFWriter exclude from object stream 0 536 QPDFWriter exclude from object stream 0
539 QPDF_pages findPage not found 0 537 QPDF_pages findPage not found 0
540 -QPDFObjectHandle check ownership 0  
541 QPDFJob weak crypto error 0 538 QPDFJob weak crypto error 0
542 qpdf-c called qpdf_oh_is_initialized 0 539 qpdf-c called qpdf_oh_is_initialized 0
543 qpdf-c registered progress reporter 0 540 qpdf-c registered progress reporter 0
qpdf/qtest/qpdf/name-tree.out
@@ -33,7 +33,7 @@ WARNING: name-tree.pdf (Name/Number tree node (object 17)): skipping over invali @@ -33,7 +33,7 @@ WARNING: name-tree.pdf (Name/Number tree node (object 17)): skipping over invali
33 B 33 B
34 W 34 W
35 /Bad3 -- invalid kid 35 /Bad3 -- invalid kid
36 -WARNING: name-tree.pdf (Name/Number tree node (object 25)): non-dictionary node while traversing name/number tree 36 +WARNING: name-tree.pdf (Name/Number tree node (object 22)): kid number 0 is invalid
37 /Bad4 -- invalid kid 37 /Bad4 -- invalid kid
38 WARNING: name-tree.pdf (Name/Number tree node (object 23)): attempting to repair after error: name-tree.pdf (Name/Number tree node (object 23)): invalid kid at index 1 38 WARNING: name-tree.pdf (Name/Number tree node (object 23)): attempting to repair after error: name-tree.pdf (Name/Number tree node (object 23)): invalid kid at index 1
39 WARNING: name-tree.pdf (Name/Number tree node (object 23)): skipping over invalid kid at index 1 39 WARNING: name-tree.pdf (Name/Number tree node (object 23)): skipping over invalid kid at index 1