Commit e51eee74071546f15be46f73a4ff93f89bd8d191
Committed by
GitHub
Merge pull request #1585 from m-holger/dict
Refactor private-API dictionary methods
Showing
14 changed files
with
202 additions
and
52 deletions
include/qpdf/ObjectHandle.hh
| @@ -83,8 +83,11 @@ namespace qpdf | @@ -83,8 +83,11 @@ namespace qpdf | ||
| 83 | QPDFObjectHandle operator[](size_t n) const; | 83 | QPDFObjectHandle operator[](size_t n) const; |
| 84 | QPDFObjectHandle operator[](int n) const; | 84 | QPDFObjectHandle operator[](int n) const; |
| 85 | 85 | ||
| 86 | + QPDFObjectHandle& at(std::string const& key) const; | ||
| 86 | bool contains(std::string const& key) const; | 87 | bool contains(std::string const& key) const; |
| 87 | size_t erase(std::string const& key); | 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 | QPDFObjectHandle const& operator[](std::string const& key) const; | 91 | QPDFObjectHandle const& operator[](std::string const& key) const; |
| 89 | 92 | ||
| 90 | std::shared_ptr<QPDFObject> copy(bool shallow = false) const; | 93 | std::shared_ptr<QPDFObject> copy(bool shallow = false) const; |
| @@ -134,6 +137,8 @@ namespace qpdf | @@ -134,6 +137,8 @@ namespace qpdf | ||
| 134 | 137 | ||
| 135 | std::string description() const; | 138 | std::string description() const; |
| 136 | 139 | ||
| 140 | + inline QPDFObjectHandle const& get(std::string const& key) const; | ||
| 141 | + | ||
| 137 | void no_ci_warn_if(bool condition, std::string const& warning) const; | 142 | void no_ci_warn_if(bool condition, std::string const& warning) const; |
| 138 | void no_ci_stop_if(bool condition, std::string const& warning) const; | 143 | void no_ci_stop_if(bool condition, std::string const& warning) const; |
| 139 | void no_ci_stop_damaged_if(bool condition, std::string const& warning) const; | 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<PathElement>::iterator | @@ -168,7 +168,7 @@ NNTreeIterator::resetLimits(Dictionary a_node, std::list<PathElement>::iterator | ||
| 168 | } | 168 | } |
| 169 | } | 169 | } |
| 170 | if (a_node != path.begin()->node) { | 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<PathElement>::iterator pare | @@ -266,7 +266,7 @@ NNTreeIterator::split(Dictionary to_split, std::list<PathElement>::iterator pare | ||
| 266 | new_kids.push_back(first_node); | 266 | new_kids.push_back(first_node); |
| 267 | to_split.erase("/Limits"); // already shouldn't be there for root | 267 | to_split.erase("/Limits"); // already shouldn't be there for root |
| 268 | to_split.erase(impl.itemsKey()); | 268 | to_split.erase(impl.itemsKey()); |
| 269 | - to_split.replaceKey("/Kids", new_kids); | 269 | + to_split.replace("/Kids", new_kids); |
| 270 | if (is_leaf) { | 270 | if (is_leaf) { |
| 271 | node = first_node; | 271 | node = first_node; |
| 272 | } else { | 272 | } else { |
| @@ -446,7 +446,7 @@ NNTreeIterator::remove() | @@ -446,7 +446,7 @@ NNTreeIterator::remove() | ||
| 446 | if (parent == path.end()) { | 446 | if (parent == path.end()) { |
| 447 | // 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. |
| 448 | element->node.erase("/Kids"); | 448 | element->node.erase("/Kids"); |
| 449 | - element->node.replaceKey(impl.itemsKey(), Array::empty()); | 449 | + element->node.replace(impl.itemsKey(), Array::empty()); |
| 450 | path.clear(); | 450 | path.clear(); |
| 451 | setItemNumber(impl.tree_root, -1); | 451 | setItemNumber(impl.tree_root, -1); |
| 452 | return; | 452 | return; |
| @@ -673,8 +673,8 @@ NNTreeImpl::repair() | @@ -673,8 +673,8 @@ NNTreeImpl::repair() | ||
| 673 | for (auto const& [key, value]: items) { | 673 | for (auto const& [key, value]: items) { |
| 674 | repl.insert(key, value); | 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 | NNTreeImpl::iterator | 680 | NNTreeImpl::iterator |
libqpdf/QPDF.cc
| @@ -572,7 +572,7 @@ Objects::Foreign::Copier::replace_indirect_object(QPDFObjectHandle const& foreig | @@ -572,7 +572,7 @@ Objects::Foreign::Copier::replace_indirect_object(QPDFObjectHandle const& foreig | ||
| 572 | auto result = Dictionary::empty(); | 572 | auto result = Dictionary::empty(); |
| 573 | for (auto const& [key, value]: Dictionary(foreign)) { | 573 | for (auto const& [key, value]: Dictionary(foreign)) { |
| 574 | if (!value.null()) { | 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 | return result; | 578 | return result; |
| @@ -584,7 +584,7 @@ Objects::Foreign::Copier::replace_indirect_object(QPDFObjectHandle const& foreig | @@ -584,7 +584,7 @@ Objects::Foreign::Copier::replace_indirect_object(QPDFObjectHandle const& foreig | ||
| 584 | auto dict = result.getDict(); | 584 | auto dict = result.getDict(); |
| 585 | for (auto const& [key, value]: stream.getDict()) { | 585 | for (auto const& [key, value]: stream.getDict()) { |
| 586 | if (!value.null()) { | 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 | stream.copy_data_to(result); | 590 | stream.copy_data_to(result); |
| @@ -658,7 +658,7 @@ QPDF::getRoot() | @@ -658,7 +658,7 @@ QPDF::getRoot() | ||
| 658 | // approach to more extensive checks and warning levels. | 658 | // approach to more extensive checks and warning levels. |
| 659 | if (m->cf.check_mode() && Name(Root["/Type"]) != "/Catalog") { | 659 | if (m->cf.check_mode() && Name(Root["/Type"]) != "/Catalog") { |
| 660 | warn(m->c.damagedPDF("", -1, "catalog /Type entry missing or invalid")); | 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 | return Root.oh(); | 663 | return Root.oh(); |
| 664 | } | 664 | } |
libqpdf/QPDFAcroFormDocumentHelper.cc
| @@ -799,7 +799,7 @@ QPDFAcroFormDocumentHelper::transformAnnotations( | @@ -799,7 +799,7 @@ QPDFAcroFormDocumentHelper::transformAnnotations( | ||
| 799 | } | 799 | } |
| 800 | QPDFObjectHandle(dr).makeResourcesIndirect(qpdf); | 800 | QPDFObjectHandle(dr).makeResourcesIndirect(qpdf); |
| 801 | if (!dr.indirect()) { | 801 | if (!dr.indirect()) { |
| 802 | - acroform.replaceKey("/DR", qpdf.makeIndirectObject(dr)); | 802 | + acroform.replace("/DR", qpdf.makeIndirectObject(dr)); |
| 803 | dr = acroform["/DR"]; | 803 | dr = acroform["/DR"]; |
| 804 | } | 804 | } |
| 805 | // Merge the other document's /DR, creating a conflict map. mergeResources checks to | 805 | // Merge the other document's /DR, creating a conflict map. mergeResources checks to |
| @@ -839,7 +839,7 @@ QPDFAcroFormDocumentHelper::transformAnnotations( | @@ -839,7 +839,7 @@ QPDFAcroFormDocumentHelper::transformAnnotations( | ||
| 839 | if (parent.indirect()) { | 839 | if (parent.indirect()) { |
| 840 | auto parent_og = parent.id_gen(); | 840 | auto parent_og = parent.id_gen(); |
| 841 | if (orig_to_copy.contains(parent_og)) { | 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 | } else { | 843 | } else { |
| 844 | parent.warn( | 844 | parent.warn( |
| 845 | "while traversing field " + obj.id_gen().unparse(',') + | 845 | "while traversing field " + obj.id_gen().unparse(',') + |
| @@ -869,7 +869,7 @@ QPDFAcroFormDocumentHelper::transformAnnotations( | @@ -869,7 +869,7 @@ QPDFAcroFormDocumentHelper::transformAnnotations( | ||
| 869 | // chrome, firefox, the mac Preview application, and several of the free | 869 | // chrome, firefox, the mac Preview application, and several of the free |
| 870 | // readers on Linux all ignore /DR at the field level. | 870 | // readers on Linux all ignore /DR at the field level. |
| 871 | if (obj.contains("/DR")) { | 871 | if (obj.contains("/DR")) { |
| 872 | - obj.replaceKey("/DR", dr); | 872 | + obj.replace("/DR", dr); |
| 873 | } | 873 | } |
| 874 | if (obj["/DA"].isString() && !dr_map.empty()) { | 874 | if (obj["/DA"].isString() && !dr_map.empty()) { |
| 875 | adjustDefaultAppearances(obj, dr_map); | 875 | adjustDefaultAppearances(obj, dr_map); |
| @@ -988,7 +988,7 @@ QPDFAcroFormDocumentHelper::transformAnnotations( | @@ -988,7 +988,7 @@ QPDFAcroFormDocumentHelper::transformAnnotations( | ||
| 988 | Dictionary apdict = ah.getAppearanceDictionary(); | 988 | Dictionary apdict = ah.getAppearanceDictionary(); |
| 989 | std::vector<QPDFObjectHandle> streams; | 989 | std::vector<QPDFObjectHandle> streams; |
| 990 | auto replace_stream = [](auto& dict, auto& key, auto& old) { | 990 | auto replace_stream = [](auto& dict, auto& key, auto& old) { |
| 991 | - dict.replaceKey(key, old.copyStream()); | 991 | + dict.replace(key, old.copyStream()); |
| 992 | return dict[key]; | 992 | return dict[key]; |
| 993 | }; | 993 | }; |
| 994 | 994 | ||
| @@ -1015,7 +1015,7 @@ QPDFAcroFormDocumentHelper::transformAnnotations( | @@ -1015,7 +1015,7 @@ QPDFAcroFormDocumentHelper::transformAnnotations( | ||
| 1015 | } | 1015 | } |
| 1016 | apcm.concat(cm); | 1016 | apcm.concat(cm); |
| 1017 | if (omatrix || apcm != QPDFMatrix()) { | 1017 | if (omatrix || apcm != QPDFMatrix()) { |
| 1018 | - dict.replaceKey("/Matrix", QPDFObjectHandle::newFromMatrix(apcm)); | 1018 | + dict.replace("/Matrix", QPDFObjectHandle::newFromMatrix(apcm)); |
| 1019 | } | 1019 | } |
| 1020 | Dictionary resources = dict["/Resources"]; | 1020 | Dictionary resources = dict["/Resources"]; |
| 1021 | if (!dr_map.empty() && resources) { | 1021 | if (!dr_map.empty() && resources) { |
libqpdf/QPDFAnnotationObjectHelper.cc
| @@ -33,14 +33,14 @@ QPDFAnnotationObjectHelper::getAppearanceDictionary() | @@ -33,14 +33,14 @@ QPDFAnnotationObjectHelper::getAppearanceDictionary() | ||
| 33 | std::string | 33 | std::string |
| 34 | QPDFAnnotationObjectHelper::getAppearanceState() | 34 | QPDFAnnotationObjectHelper::getAppearanceState() |
| 35 | { | 35 | { |
| 36 | - Name AS = (*this)["/AS"]; | 36 | + Name AS = get("/AS"); |
| 37 | return AS ? AS.value() : ""; | 37 | return AS ? AS.value() : ""; |
| 38 | } | 38 | } |
| 39 | 39 | ||
| 40 | int | 40 | int |
| 41 | QPDFAnnotationObjectHelper::getFlags() | 41 | QPDFAnnotationObjectHelper::getFlags() |
| 42 | { | 42 | { |
| 43 | - Integer flags_obj = (*this)["/F"]; | 43 | + Integer flags_obj = get("/F"); |
| 44 | return flags_obj ? flags_obj : 0; | 44 | return flags_obj ? flags_obj : 0; |
| 45 | } | 45 | } |
| 46 | 46 |
libqpdf/QPDFEFStreamObjectHelper.cc
| @@ -31,7 +31,7 @@ void | @@ -31,7 +31,7 @@ void | ||
| 31 | QPDFEFStreamObjectHelper::setParam(std::string const& pkey, QPDFObjectHandle const& pval) | 31 | QPDFEFStreamObjectHelper::setParam(std::string const& pkey, QPDFObjectHandle const& pval) |
| 32 | { | 32 | { |
| 33 | if (Dictionary Params = oh().getDict()["/Params"]) { | 33 | if (Dictionary Params = oh().getDict()["/Params"]) { |
| 34 | - Params.replaceKey(pkey, pval); | 34 | + Params.replace(pkey, pval); |
| 35 | return; | 35 | return; |
| 36 | } | 36 | } |
| 37 | oh().getDict().replaceKey("/Params", Dictionary({{pkey, pval}})); | 37 | oh().getDict().replaceKey("/Params", Dictionary({{pkey, pval}})); |
libqpdf/QPDFFileSpecObjectHelper.cc
| @@ -42,7 +42,7 @@ std::string | @@ -42,7 +42,7 @@ std::string | ||
| 42 | QPDFFileSpecObjectHelper::getFilename() | 42 | QPDFFileSpecObjectHelper::getFilename() |
| 43 | { | 43 | { |
| 44 | for (auto const& i: name_keys) { | 44 | for (auto const& i: name_keys) { |
| 45 | - if (String k = oh()[i]) { | 45 | + if (String k = get(i)) { |
| 46 | return k.utf8_value(); | 46 | return k.utf8_value(); |
| 47 | } | 47 | } |
| 48 | } | 48 | } |
| @@ -54,7 +54,7 @@ QPDFFileSpecObjectHelper::getFilenames() | @@ -54,7 +54,7 @@ QPDFFileSpecObjectHelper::getFilenames() | ||
| 54 | { | 54 | { |
| 55 | std::map<std::string, std::string> result; | 55 | std::map<std::string, std::string> result; |
| 56 | for (auto const& i: name_keys) { | 56 | for (auto const& i: name_keys) { |
| 57 | - if (String k = oh()[i]) { | 57 | + if (String k = get(i)) { |
| 58 | result[i] = k.utf8_value(); | 58 | result[i] = k.utf8_value(); |
| 59 | } | 59 | } |
| 60 | } | 60 | } |
| @@ -64,7 +64,7 @@ QPDFFileSpecObjectHelper::getFilenames() | @@ -64,7 +64,7 @@ QPDFFileSpecObjectHelper::getFilenames() | ||
| 64 | QPDFObjectHandle | 64 | QPDFObjectHandle |
| 65 | QPDFFileSpecObjectHelper::getEmbeddedFileStream(std::string const& key) | 65 | QPDFFileSpecObjectHelper::getEmbeddedFileStream(std::string const& key) |
| 66 | { | 66 | { |
| 67 | - if (Dictionary EF = oh()["/EF"]) { | 67 | + if (Dictionary EF = get("/EF")) { |
| 68 | if (!key.empty() && EF.contains(key)) { | 68 | if (!key.empty() && EF.contains(key)) { |
| 69 | if (auto result = EF[key]) { | 69 | if (auto result = EF[key]) { |
| 70 | return result; | 70 | return result; |
libqpdf/QPDFFormFieldObjectHelper.cc
| @@ -956,7 +956,7 @@ FormField::generateTextAppearance(QPDFAnnotationObjectHelper& aoh) | @@ -956,7 +956,7 @@ FormField::generateTextAppearance(QPDFAnnotationObjectHelper& aoh) | ||
| 956 | aoh.getObjectHandle().replaceKey("/AP", Dictionary::empty()); | 956 | aoh.getObjectHandle().replaceKey("/AP", Dictionary::empty()); |
| 957 | AP = aoh.getAppearanceDictionary(); | 957 | AP = aoh.getAppearanceDictionary(); |
| 958 | } | 958 | } |
| 959 | - AP.replaceKey("/N", AS); | 959 | + AP.replace("/N", AS); |
| 960 | } | 960 | } |
| 961 | if (!AS.isStream()) { | 961 | if (!AS.isStream()) { |
| 962 | aoh.warn("unable to get normal appearance stream for update"); | 962 | aoh.warn("unable to get normal appearance stream for update"); |
libqpdf/QPDFOutlineObjectHelper.cc
| @@ -53,9 +53,9 @@ QPDFOutlineObjectHelper::getKids() | @@ -53,9 +53,9 @@ QPDFOutlineObjectHelper::getKids() | ||
| 53 | QPDFObjectHandle | 53 | QPDFObjectHandle |
| 54 | QPDFOutlineObjectHelper::getDest() | 54 | QPDFOutlineObjectHelper::getDest() |
| 55 | { | 55 | { |
| 56 | - auto dest = (*this)["/Dest"]; | 56 | + auto dest = get("/Dest"); |
| 57 | if (dest.null()) { | 57 | if (dest.null()) { |
| 58 | - auto const& A = (*this)["/A"]; | 58 | + auto const& A = get("/A"); |
| 59 | if (Name(A["/S"]) == "/GoTo") { | 59 | if (Name(A["/S"]) == "/GoTo") { |
| 60 | dest = A["/D"]; | 60 | dest = A["/D"]; |
| 61 | } | 61 | } |
libqpdf/QPDF_Dictionary.cc
| @@ -2,6 +2,7 @@ | @@ -2,6 +2,7 @@ | ||
| 2 | 2 | ||
| 3 | #include <qpdf/QPDFObject_private.hh> | 3 | #include <qpdf/QPDFObject_private.hh> |
| 4 | #include <qpdf/QTC.hh> | 4 | #include <qpdf/QTC.hh> |
| 5 | +#include <qpdf/Util.hh> | ||
| 5 | 6 | ||
| 6 | using namespace std::literals; | 7 | using namespace std::literals; |
| 7 | using namespace qpdf; | 8 | using namespace qpdf; |
| @@ -29,12 +30,70 @@ BaseHandle::operator[](std::string const& key) const | @@ -29,12 +30,70 @@ BaseHandle::operator[](std::string const& key) const | ||
| 29 | return null_obj; | 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 | bool | 61 | bool |
| 33 | BaseHandle::contains(std::string const& key) const | 62 | BaseHandle::contains(std::string const& key) const |
| 34 | { | 63 | { |
| 35 | return !(*this)[key].null(); | 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 | std::set<std::string> | 97 | std::set<std::string> |
| 39 | BaseDictionary::getKeys() | 98 | BaseDictionary::getKeys() |
| 40 | { | 99 | { |
| @@ -63,18 +122,29 @@ BaseHandle::erase(const std::string& key) | @@ -63,18 +122,29 @@ BaseHandle::erase(const std::string& key) | ||
| 63 | return 0; | 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 | void | 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& key) const | @@ -121,7 +191,7 @@ QPDFObjectHandle::hasKey(std::string const& key) const | ||
| 121 | QPDFObjectHandle | 191 | QPDFObjectHandle |
| 122 | QPDFObjectHandle::getKey(std::string const& key) const | 192 | QPDFObjectHandle::getKey(std::string const& key) const |
| 123 | { | 193 | { |
| 124 | - if (auto result = (*this)[key]) { | 194 | + if (auto result = get(key)) { |
| 125 | return result; | 195 | return result; |
| 126 | } | 196 | } |
| 127 | if (isDictionary()) { | 197 | if (isDictionary()) { |
| @@ -166,11 +236,10 @@ QPDFObjectHandle::replaceKey(std::string const& key, QPDFObjectHandle const& val | @@ -166,11 +236,10 @@ QPDFObjectHandle::replaceKey(std::string const& key, QPDFObjectHandle const& val | ||
| 166 | { | 236 | { |
| 167 | if (auto dict = as_dictionary(strict)) { | 237 | if (auto dict = as_dictionary(strict)) { |
| 168 | checkOwnership(value); | 238 | checkOwnership(value); |
| 169 | - dict.replaceKey(key, value); | 239 | + dict.replace(key, value); |
| 170 | return; | 240 | return; |
| 171 | } | 241 | } |
| 172 | typeWarning("dictionary", "ignoring key replacement request"); | 242 | typeWarning("dictionary", "ignoring key replacement request"); |
| 173 | - QTC::TC("qpdf", "QPDFObjectHandle dictionary ignoring replaceKey"); | ||
| 174 | } | 243 | } |
| 175 | 244 | ||
| 176 | QPDFObjectHandle | 245 | QPDFObjectHandle |
| @@ -200,7 +269,7 @@ QPDFObjectHandle::removeKey(std::string const& key) | @@ -200,7 +269,7 @@ QPDFObjectHandle::removeKey(std::string const& key) | ||
| 200 | QPDFObjectHandle | 269 | QPDFObjectHandle |
| 201 | QPDFObjectHandle::removeKeyAndGetOld(std::string const& key) | 270 | QPDFObjectHandle::removeKeyAndGetOld(std::string const& key) |
| 202 | { | 271 | { |
| 203 | - auto result = (*this)[key]; | 272 | + auto result = get(key); |
| 204 | erase(key); | 273 | erase(key); |
| 205 | return result ? result : newNull(); | 274 | return result ? result : newNull(); |
| 206 | } | 275 | } |
libqpdf/qpdf/QPDFObjectHandle_private.hh
| @@ -127,7 +127,7 @@ namespace qpdf | @@ -127,7 +127,7 @@ namespace qpdf | ||
| 127 | // The following methods are not part of the public API. | 127 | // The following methods are not part of the public API. |
| 128 | std::set<std::string> getKeys(); | 128 | std::set<std::string> getKeys(); |
| 129 | std::map<std::string, QPDFObjectHandle> const& getAsMap() const; | 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 | using iterator = std::map<std::string, QPDFObjectHandle>::iterator; | 132 | using iterator = std::map<std::string, QPDFObjectHandle>::iterator; |
| 133 | using const_iterator = std::map<std::string, QPDFObjectHandle>::const_iterator; | 133 | using const_iterator = std::map<std::string, QPDFObjectHandle>::const_iterator; |
| @@ -806,6 +806,20 @@ namespace qpdf | @@ -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 | inline bool | 823 | inline bool |
| 810 | BaseHandle::null() const | 824 | BaseHandle::null() const |
| 811 | { | 825 | { |
libtests/objects.cc
| @@ -54,35 +54,30 @@ test_0(QPDF& pdf, char const* arg2) | @@ -54,35 +54,30 @@ test_0(QPDF& pdf, char const* arg2) | ||
| 54 | assert_compare_numbers(INT_MAX, t.getKey("/Q1").getIntValueAsInt()); | 54 | assert_compare_numbers(INT_MAX, t.getKey("/Q1").getIntValueAsInt()); |
| 55 | try { | 55 | try { |
| 56 | assert_compare_numbers(0u, QPDFObjectHandle::newNull().getUIntValueAsUInt()); | 56 | assert_compare_numbers(0u, QPDFObjectHandle::newNull().getUIntValueAsUInt()); |
| 57 | - std::cerr << "convert null to uint did not throw\n"; | ||
| 58 | } catch (QPDFExc const&) { | 57 | } catch (QPDFExc const&) { |
| 59 | std::cerr << "caught expected type error\n"; | 58 | std::cerr << "caught expected type error\n"; |
| 60 | } | 59 | } |
| 61 | assert_compare_numbers(std::numeric_limits<int8_t>::max(), Integer(q1).value<int8_t>()); | 60 | assert_compare_numbers(std::numeric_limits<int8_t>::max(), Integer(q1).value<int8_t>()); |
| 62 | assert_compare_numbers(std::numeric_limits<int8_t>::min(), Integer(-q1).value<int8_t>()); | 61 | assert_compare_numbers(std::numeric_limits<int8_t>::min(), Integer(-q1).value<int8_t>()); |
| 63 | try { | 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 | } catch (std::overflow_error const&) { | 64 | } catch (std::overflow_error const&) { |
| 67 | std::cerr << "caught expected int8_t overflow error\n"; | 65 | std::cerr << "caught expected int8_t overflow error\n"; |
| 68 | } | 66 | } |
| 69 | try { | 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 | } catch (std::underflow_error const&) { | 69 | } catch (std::underflow_error const&) { |
| 73 | std::cerr << "caught expected int8_t underflow error\n"; | 70 | std::cerr << "caught expected int8_t underflow error\n"; |
| 74 | } | 71 | } |
| 75 | assert_compare_numbers(std::numeric_limits<uint8_t>::max(), Integer(q1).value<uint8_t>()); | 72 | assert_compare_numbers(std::numeric_limits<uint8_t>::max(), Integer(q1).value<uint8_t>()); |
| 76 | assert_compare_numbers(0, Integer(-q1).value<uint8_t>()); | 73 | assert_compare_numbers(0, Integer(-q1).value<uint8_t>()); |
| 77 | try { | 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 | } catch (std::overflow_error const&) { | 76 | } catch (std::overflow_error const&) { |
| 81 | std::cerr << "caught expected uint8_t overflow error\n"; | 77 | std::cerr << "caught expected uint8_t overflow error\n"; |
| 82 | } | 78 | } |
| 83 | try { | 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 | } catch (std::underflow_error const&) { | 81 | } catch (std::underflow_error const&) { |
| 87 | std::cerr << "caught expected uint8_t underflow error\n"; | 82 | std::cerr << "caught expected uint8_t underflow error\n"; |
| 88 | } | 83 | } |
| @@ -95,6 +90,70 @@ test_0(QPDF& pdf, char const* arg2) | @@ -95,6 +90,70 @@ test_0(QPDF& pdf, char const* arg2) | ||
| 95 | assert_compare_numbers(UINT_MAX, t.getKey("/Q3").getUIntValueAsUInt()); | 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 | void | 157 | void |
| 99 | runtest(int n, char const* filename1, char const* arg2) | 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,7 +161,7 @@ runtest(int n, char const* filename1, char const* arg2) | ||
| 102 | // the test suite to see how the test is invoked to find the file | 161 | // the test suite to see how the test is invoked to find the file |
| 103 | // that the test is supposed to operate on. | 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 | QPDF pdf; | 166 | QPDF pdf; |
| 108 | std::shared_ptr<char> file_buf; | 167 | std::shared_ptr<char> file_buf; |
| @@ -116,7 +175,7 @@ runtest(int n, char const* filename1, char const* arg2) | @@ -116,7 +175,7 @@ runtest(int n, char const* filename1, char const* arg2) | ||
| 116 | } | 175 | } |
| 117 | 176 | ||
| 118 | std::map<int, void (*)(QPDF&, char const*)> test_functions = { | 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 | auto fn = test_functions.find(n); | 181 | auto fn = test_functions.find(n); |
libtests/qtest/objects.test
| @@ -11,12 +11,16 @@ require TestDriver; | @@ -11,12 +11,16 @@ require TestDriver; | ||
| 11 | 11 | ||
| 12 | my $td = new TestDriver('objects'); | 12 | my $td = new TestDriver('objects'); |
| 13 | 13 | ||
| 14 | -my $n_tests = 1; | ||
| 15 | - | 14 | +my $n_tests = 2; |
| 16 | 15 | ||
| 17 | $td->runtest("integer type checks", | 16 | $td->runtest("integer type checks", |
| 18 | {$td->COMMAND => "objects 0 minimal.pdf"}, | 17 | {$td->COMMAND => "objects 0 minimal.pdf"}, |
| 19 | {$td->FILE => "test0.out", $td->EXIT_STATUS => 0}, | 18 | {$td->FILE => "test0.out", $td->EXIT_STATUS => 0}, |
| 20 | $td->NORMALIZE_NEWLINES); | 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 | $td->report($n_tests); | 26 | $td->report($n_tests); |
qpdf/qpdf.testcov
| @@ -171,7 +171,6 @@ QPDFObjectHandle array ignoring erase item 0 | @@ -171,7 +171,6 @@ QPDFObjectHandle array ignoring erase item 0 | ||
| 171 | QPDFObjectHandle dictionary false for hasKey 0 | 171 | QPDFObjectHandle dictionary false for hasKey 0 |
| 172 | QPDFObjectHandle dictionary empty set for getKeys 0 | 172 | QPDFObjectHandle dictionary empty set for getKeys 0 |
| 173 | QPDFObjectHandle dictionary empty map for asMap 0 | 173 | QPDFObjectHandle dictionary empty map for asMap 0 |
| 174 | -QPDFObjectHandle dictionary ignoring replaceKey 0 | ||
| 175 | QPDFObjectHandle numeric non-numeric 0 | 174 | QPDFObjectHandle numeric non-numeric 0 |
| 176 | QPDFObjectHandle erase array bounds 0 | 175 | QPDFObjectHandle erase array bounds 0 |
| 177 | qpdf-c called qpdf_check_pdf 0 | 176 | qpdf-c called qpdf_check_pdf 0 |