/src/qpdf/libqpdf/QPDF_Dictionary.cc
Line | Count | Source |
1 | | #include <qpdf/QPDFObjectHandle_private.hh> |
2 | | |
3 | | #include <qpdf/QPDFObject_private.hh> |
4 | | #include <qpdf/QTC.hh> |
5 | | #include <qpdf/Util.hh> |
6 | | |
7 | | using namespace std::literals; |
8 | | using namespace qpdf; |
9 | | |
10 | | QPDF_Dictionary* |
11 | | BaseDictionary::dict() const |
12 | 10.3k | { |
13 | 10.3k | if (auto d = as<QPDF_Dictionary>()) { |
14 | 10.3k | return d; |
15 | 10.3k | } |
16 | 0 | throw std::runtime_error("Expected a dictionary but found a non-dictionary object"); |
17 | 0 | return nullptr; // unreachable |
18 | 10.3k | } |
19 | | |
20 | | QPDFObjectHandle const& |
21 | | BaseHandle::operator[](std::string const& key) const |
22 | 897k | { |
23 | 897k | if (auto d = as<QPDF_Dictionary>()) { |
24 | 875k | auto it = d->items.find(key); |
25 | 875k | if (it != d->items.end()) { |
26 | 458k | return it->second; |
27 | 458k | } |
28 | 875k | } |
29 | 438k | static const QPDFObjectHandle null_obj; |
30 | 438k | return null_obj; |
31 | 897k | } |
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 | 0 | { |
47 | 0 | auto d = as<QPDF_Dictionary>(); |
48 | 0 | if (!d) { |
49 | 0 | throw std::runtime_error("Expected a dictionary but found a non-dictionary object"); |
50 | 0 | } |
51 | 0 | return d->items[key]; |
52 | 0 | } |
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. |
61 | | bool |
62 | | BaseHandle::contains(std::string const& key) const |
63 | 97.2k | { |
64 | 97.2k | return !(*this)[key].null(); |
65 | 97.2k | } |
66 | | |
67 | | /// @brief Retrieves the value associated with the given key from a 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 | 0 | { |
86 | 0 | static const QPDFObjectHandle null_obj; |
87 | 0 | qpdf_invariant(!null_obj); |
88 | 0 | if (auto d = as<QPDF_Dictionary>()) { |
89 | 0 | auto it = d->items.find(key); |
90 | 0 | if (it != d->items.end()) { |
91 | 0 | return it->second; |
92 | 0 | } |
93 | 0 | } |
94 | 0 | return const_cast<QPDFObjectHandle&>(null_obj); |
95 | 0 | } |
96 | | |
97 | | std::set<std::string> |
98 | | BaseDictionary::getKeys() |
99 | 10.3k | { |
100 | 10.3k | std::set<std::string> result; |
101 | 38.9k | for (auto& iter: dict()->items) { |
102 | 38.9k | if (!iter.second.null()) { |
103 | 33.8k | result.insert(iter.first); |
104 | 33.8k | } |
105 | 38.9k | } |
106 | 10.3k | return result; |
107 | 10.3k | } |
108 | | |
109 | | std::map<std::string, QPDFObjectHandle> const& |
110 | | BaseDictionary::getAsMap() const |
111 | 0 | { |
112 | 0 | return dict()->items; |
113 | 0 | } |
114 | | |
115 | | size_t |
116 | | BaseHandle::erase(const std::string& key) |
117 | 262k | { |
118 | | // no-op if key does not exist |
119 | 262k | if (auto d = as<QPDF_Dictionary>()) { |
120 | 262k | return d->items.erase(key); |
121 | 262k | } |
122 | 0 | return 0; |
123 | 262k | } |
124 | | |
125 | | /// @brief Replaces or removes the value associated with the given key in a dictionary. |
126 | | /// |
127 | | /// If the current object is a dictionary, this method updates the value at the specified key. |
128 | | /// If the value is a direct null object, the key is removed from the dictionary (since the PDF |
129 | | /// specification doesn't distinguish between keys with null values and missing keys). Indirect |
130 | | /// null values are preserved as they represent dangling references, which are permitted by the |
131 | | /// specification. |
132 | | /// |
133 | | /// @param key The key for which the value should be replaced. |
134 | | /// @param value The new value to associate with the key. If this is a direct null, the key is |
135 | | /// removed instead. |
136 | | /// @return Returns true if the operation was performed (i.e., the current object is a dictionary). |
137 | | /// Returns false if the current object is not a dictionary. |
138 | | bool |
139 | | BaseHandle::replace(std::string const& key, QPDFObjectHandle value) |
140 | 63.5k | { |
141 | 63.5k | if (auto d = as<QPDF_Dictionary>()) { |
142 | 63.5k | if (value.null() && !value.indirect()) { |
143 | | // The PDF spec doesn't distinguish between keys with null values and missing keys. |
144 | | // Allow indirect nulls which are equivalent to a dangling reference, which is permitted |
145 | | // by the spec. |
146 | 0 | d->items.erase(key); |
147 | 63.5k | } else { |
148 | | // add or replace value |
149 | 63.5k | d->items[key] = value; |
150 | 63.5k | } |
151 | 63.5k | return true; |
152 | 63.5k | } |
153 | 0 | return false; |
154 | 63.5k | } |
155 | | |
156 | | /// @brief Replaces or removes the value associated with the given key in a dictionary. |
157 | | /// |
158 | | /// This method provides a stricter version of `BaseHandle::replace()` that throws an exception |
159 | | /// if the current object is not a dictionary, instead of silently returning false. It delegates |
160 | | /// to `BaseHandle::replace()` for the actual replacement logic. |
161 | | /// |
162 | | /// If the value is a direct null object, the key is removed from the dictionary (since the PDF |
163 | | /// specification doesn't distinguish between keys with null values and missing keys). Indirect |
164 | | /// null values are preserved as they represent dangling references. |
165 | | /// |
166 | | /// @param key The key for which the value should be replaced. |
167 | | /// @param value The new value to associate with the key. If this is a direct null, the key is |
168 | | /// removed instead. |
169 | | /// @throws std::runtime_error if the current object is not a dictionary. |
170 | | void |
171 | | BaseDictionary::replace(std::string const& key, QPDFObjectHandle value) |
172 | 63.5k | { |
173 | 63.5k | if (!BaseHandle::replace(key, value)) { |
174 | 0 | (void)dict(); |
175 | 0 | } |
176 | 63.5k | } |
177 | | |
178 | | Dictionary::Dictionary(std::map<std::string, QPDFObjectHandle>&& dict) : |
179 | 0 | BaseDictionary(std::move(dict)) |
180 | 0 | { |
181 | 0 | } |
182 | | |
183 | | Dictionary::Dictionary(std::shared_ptr<QPDFObject> const& obj) : |
184 | 191k | BaseDictionary(obj) |
185 | 191k | { |
186 | 191k | } |
187 | | |
188 | | Dictionary |
189 | | Dictionary::empty() |
190 | 0 | { |
191 | 0 | return Dictionary(std::map<std::string, QPDFObjectHandle>()); |
192 | 0 | } |
193 | | |
194 | | void |
195 | | QPDFObjectHandle::checkOwnership(QPDFObjectHandle const& item) const |
196 | 60.8k | { |
197 | 60.8k | auto qpdf = getOwningQPDF(); |
198 | 60.8k | auto item_qpdf = item.getOwningQPDF(); |
199 | 60.8k | if (qpdf && item_qpdf && qpdf != item_qpdf) { |
200 | 0 | throw std::logic_error( |
201 | 0 | "Attempting to add an object from a different QPDF. Use " |
202 | 0 | "QPDF::copyForeignObject to add objects from another file."); |
203 | 0 | } |
204 | 60.8k | } |
205 | | |
206 | | bool |
207 | | QPDFObjectHandle::hasKey(std::string const& key) const |
208 | 73.2k | { |
209 | 73.2k | if (Dictionary dict = *this) { |
210 | 73.1k | return dict.contains(key); |
211 | 73.1k | } else { |
212 | 115 | typeWarning("dictionary", "returning false for a key containment request"); |
213 | 115 | QTC::TC("qpdf", "QPDFObjectHandle dictionary false for hasKey"); |
214 | 115 | return false; |
215 | 115 | } |
216 | 73.2k | } |
217 | | |
218 | | QPDFObjectHandle |
219 | | QPDFObjectHandle::getKey(std::string const& key) const |
220 | 381k | { |
221 | 381k | if (auto result = get(key)) { |
222 | 203k | return result; |
223 | 203k | } |
224 | 178k | if (isDictionary()) { |
225 | 178k | static auto constexpr msg = " -> dictionary key $VD"sv; |
226 | 178k | return QPDF_Null::create(obj, msg, key); |
227 | 178k | } |
228 | 1 | typeWarning("dictionary", "returning null for attempted key retrieval"); |
229 | 1 | static auto constexpr msg = " -> null returned from getting key $VD from non-Dictionary"sv; |
230 | 1 | return QPDF_Null::create(obj, msg, ""); |
231 | 178k | } |
232 | | |
233 | | QPDFObjectHandle |
234 | | QPDFObjectHandle::getKeyIfDict(std::string const& key) const |
235 | 0 | { |
236 | 0 | return isNull() ? newNull() : getKey(key); |
237 | 0 | } |
238 | | |
239 | | std::set<std::string> |
240 | | QPDFObjectHandle::getKeys() const |
241 | 10.4k | { |
242 | 10.4k | if (auto dict = as_dictionary(strict)) { |
243 | 10.3k | return dict.getKeys(); |
244 | 10.3k | } |
245 | 114 | typeWarning("dictionary", "treating as empty"); |
246 | 114 | QTC::TC("qpdf", "QPDFObjectHandle dictionary empty set for getKeys"); |
247 | 114 | return {}; |
248 | 10.4k | } |
249 | | |
250 | | std::map<std::string, QPDFObjectHandle> |
251 | | QPDFObjectHandle::getDictAsMap() const |
252 | 0 | { |
253 | 0 | if (auto dict = as_dictionary(strict)) { |
254 | 0 | return dict.getAsMap(); |
255 | 0 | } |
256 | 0 | typeWarning("dictionary", "treating as empty"); |
257 | 0 | QTC::TC("qpdf", "QPDFObjectHandle dictionary empty map for asMap"); |
258 | 0 | return {}; |
259 | 0 | } |
260 | | |
261 | | void |
262 | | QPDFObjectHandle::replaceKey(std::string const& key, QPDFObjectHandle const& value) |
263 | 63.5k | { |
264 | 63.5k | if (auto dict = as_dictionary(strict)) { |
265 | 60.8k | checkOwnership(value); |
266 | 60.8k | dict.replace(key, value); |
267 | 60.8k | return; |
268 | 60.8k | } |
269 | 2.64k | typeWarning("dictionary", "ignoring key replacement request"); |
270 | 2.64k | } |
271 | | |
272 | | QPDFObjectHandle |
273 | | QPDFObjectHandle::replaceKeyAndGetNew(std::string const& key, QPDFObjectHandle const& value) |
274 | 143 | { |
275 | 143 | replaceKey(key, value); |
276 | 143 | return value; |
277 | 143 | } |
278 | | |
279 | | QPDFObjectHandle |
280 | | QPDFObjectHandle::replaceKeyAndGetOld(std::string const& key, QPDFObjectHandle const& value) |
281 | 0 | { |
282 | 0 | QPDFObjectHandle old = removeKeyAndGetOld(key); |
283 | 0 | replaceKey(key, value); |
284 | 0 | return old; |
285 | 0 | } |
286 | | |
287 | | void |
288 | | QPDFObjectHandle::removeKey(std::string const& key) |
289 | 64.6k | { |
290 | 64.6k | if (erase(key) || isDictionary()) { |
291 | 64.6k | return; |
292 | 64.6k | } |
293 | 0 | typeWarning("dictionary", "ignoring key removal request"); |
294 | 0 | } |
295 | | |
296 | | QPDFObjectHandle |
297 | | QPDFObjectHandle::removeKeyAndGetOld(std::string const& key) |
298 | 0 | { |
299 | 0 | auto result = get(key); |
300 | 0 | erase(key); |
301 | 0 | return result ? result : newNull(); |
302 | 0 | } |