diff --git a/include/qpdf/QPDFNameTreeObjectHelper.hh b/include/qpdf/QPDFNameTreeObjectHelper.hh index f0a9b479..ebc84735 100644 --- a/include/qpdf/QPDFNameTreeObjectHelper.hh +++ b/include/qpdf/QPDFNameTreeObjectHelper.hh @@ -112,6 +112,19 @@ class QPDFNameTreeObjectHelper: public QPDFObjectHelper return ! operator==(other); } + // DANGER: this method can create inconsistent trees if not + // used properly! Insert a new item immediately after the + // current iterator and increment so that it points to the new + // item. If the current iterator is end(), insert at the + // beginning. This method does not check for proper ordering, + // so if you use it, you must ensure that the item you are + // inserting belongs where you are putting it. The reason for + // this method is that it is more efficient than insert() and + // can be used safely when you are creating a new tree and + // inserting items in sorted order. + QPDF_DLL + void insertAfter(std::string const& key, QPDFObjectHandle value); + private: iterator(std::shared_ptr const&); std::shared_ptr impl; diff --git a/include/qpdf/QPDFNumberTreeObjectHelper.hh b/include/qpdf/QPDFNumberTreeObjectHelper.hh index 43c438e4..040dc3b1 100644 --- a/include/qpdf/QPDFNumberTreeObjectHelper.hh +++ b/include/qpdf/QPDFNumberTreeObjectHelper.hh @@ -131,6 +131,19 @@ class QPDFNumberTreeObjectHelper: public QPDFObjectHelper return ! operator==(other); } + // DANGER: this method can create inconsistent trees if not + // used properly! Insert a new item immediately after the + // current iterator and increment so that it points to the new + // item. If the current iterator is end(), insert at the + // beginning. This method does not check for proper ordering, + // so if you use it, you must ensure that the item you are + // inserting belongs where you are putting it. The reason for + // this method is that it is more efficient than insert() and + // can be used safely when you are creating a new tree and + // inserting items in sorted order. + QPDF_DLL + void insertAfter(numtree_number key, QPDFObjectHandle value); + private: iterator(std::shared_ptr const&); std::shared_ptr impl; diff --git a/libqpdf/NNTree.cc b/libqpdf/NNTree.cc index 05398602..02237939 100644 --- a/libqpdf/NNTree.cc +++ b/libqpdf/NNTree.cc @@ -444,6 +444,14 @@ NNTreeIterator::lastPathElement() void NNTreeIterator::insertAfter(QPDFObjectHandle key, QPDFObjectHandle value) { + if (! valid()) + { + QTC::TC("qpdf", "NNTree insertAfter inserts first"); + impl.insertFirst(key, value); + deepen(impl.oh, true, false); + return; + } + auto items = this->node.getKey(impl.details.itemsKey()); if (! items.isArray()) { @@ -457,6 +465,7 @@ NNTreeIterator::insertAfter(QPDFObjectHandle key, QPDFObjectHandle value) items.insertItem(this->item_number + 3, value); resetLimits(this->node, lastPathElement()); split(this->node, lastPathElement()); + increment(false); } NNTreeIterator& @@ -968,7 +977,6 @@ NNTreeImpl::insert(QPDFObjectHandle key, QPDFObjectHandle value) { QTC::TC("qpdf", "NNTree insert inserts after"); iter.insertAfter(key, value); - ++iter; } return iter; } diff --git a/libqpdf/QPDFNameTreeObjectHelper.cc b/libqpdf/QPDFNameTreeObjectHelper.cc index ffcf95a3..c43281bd 100644 --- a/libqpdf/QPDFNameTreeObjectHelper.cc +++ b/libqpdf/QPDFNameTreeObjectHelper.cc @@ -102,6 +102,13 @@ QPDFNameTreeObjectHelper::iterator::operator==(iterator const& other) const return *(impl) == *(other.impl); } +void +QPDFNameTreeObjectHelper::iterator::insertAfter( + std::string const& key, QPDFObjectHandle value) +{ + impl->insertAfter(QPDFObjectHandle::newUnicodeString(key), value); +} + QPDFNameTreeObjectHelper::iterator QPDFNameTreeObjectHelper::begin() const { diff --git a/libqpdf/QPDFNumberTreeObjectHelper.cc b/libqpdf/QPDFNumberTreeObjectHelper.cc index 053fa421..ceda9482 100644 --- a/libqpdf/QPDFNumberTreeObjectHelper.cc +++ b/libqpdf/QPDFNumberTreeObjectHelper.cc @@ -98,6 +98,13 @@ QPDFNumberTreeObjectHelper::iterator::operator==(iterator const& other) const return *(impl) == *(other.impl); } +void +QPDFNumberTreeObjectHelper::iterator::insertAfter( + numtree_number key, QPDFObjectHandle value) +{ + impl->insertAfter(QPDFObjectHandle::newInteger(key), value); +} + QPDFNumberTreeObjectHelper::iterator QPDFNumberTreeObjectHelper::begin() const { diff --git a/qpdf/qpdf.testcov b/qpdf/qpdf.testcov index 996c565a..aa07b45f 100644 --- a/qpdf/qpdf.testcov +++ b/qpdf/qpdf.testcov @@ -552,3 +552,4 @@ NNTree bad node during find 0 NNTree node is not a dictionary 0 NNTree limits didn't change 0 NNTree increment end() 0 +NNTree insertAfter inserts first 0 diff --git a/qpdf/qtest/qpdf/name-tree.out b/qpdf/qtest/qpdf/name-tree.out index 2292d5d7..377d3ab5 100644 --- a/qpdf/qtest/qpdf/name-tree.out +++ b/qpdf/qtest/qpdf/name-tree.out @@ -16,6 +16,9 @@ 20 twenty -> twenty. 22 twenty-two -> twenty-two! 29 twenty-nine -> twenty-nine! +insertAfter +3 (3!) +4 (4!) /Empty1 /Empty2 /Bad1: deprecated API diff --git a/qpdf/qtest/qpdf/number-tree.out b/qpdf/qtest/qpdf/number-tree.out index 4ea689ce..d1ad8302 100644 --- a/qpdf/qtest/qpdf/number-tree.out +++ b/qpdf/qtest/qpdf/number-tree.out @@ -26,6 +26,9 @@ 22 twenty-two 23 twenty-three 29 twenty-nine +insertAfter +3 (3!) +4 (4!) /Bad1: deprecated API /Bad1 WARNING: number-tree.pdf (Name/Number tree node (object 14)): name/number tree node has neither non-empty /Nums nor /Kids diff --git a/qpdf/test_driver.cc b/qpdf/test_driver.cc index 526b0123..a0aab3a8 100644 --- a/qpdf/test_driver.cc +++ b/qpdf/test_driver.cc @@ -1802,6 +1802,19 @@ void runtest(int n, char const* filename1, char const* arg2) --iter1; assert((*iter1).first == 2); + std::cout << "insertAfter" << std::endl; + auto new2 = QPDFNumberTreeObjectHelper::newEmpty(pdf); + auto iter2 = new2.begin(); + assert(iter2 == new2.end()); + iter2.insertAfter(3, QPDFObjectHandle::newString("3!")); + assert((*iter2).first == 3); + iter2.insertAfter(4, QPDFObjectHandle::newString("4!")); + assert((*iter2).first == 4); + for (auto i: new2) + { + std::cout << i.first << " " << i.second.unparse() << std::endl; + } + // Exercise deprecated API until qpdf 11 std::cout << "/Bad1: deprecated API" << std::endl; auto bad1 = QPDFNumberTreeObjectHelper( @@ -1961,6 +1974,19 @@ void runtest(int n, char const* filename1, char const* arg2) --iter1; assert((*iter1).first == "2"); + std::cout << "insertAfter" << std::endl; + auto new2 = QPDFNameTreeObjectHelper::newEmpty(pdf); + auto iter2 = new2.begin(); + assert(iter2 == new2.end()); + iter2.insertAfter("3", QPDFObjectHandle::newString("3!")); + assert((*iter2).first == "3"); + iter2.insertAfter("4", QPDFObjectHandle::newString("4!")); + assert((*iter2).first == "4"); + for (auto i: new2) + { + std::cout << i.first << " " << i.second.unparse() << std::endl; + } + std::vector empties = {"/Empty1", "/Empty2"}; for (auto const& k: empties) {