Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
cpdf_nametree.cpp
Go to the documentation of this file.
1// Copyright 2016 The PDFium Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
6
7#include "core/fpdfdoc/cpdf_nametree.h"
8
9#include <set>
10#include <utility>
11#include <vector>
12
13#include "core/fpdfapi/parser/cpdf_array.h"
14#include "core/fpdfapi/parser/cpdf_dictionary.h"
15#include "core/fpdfapi/parser/cpdf_document.h"
16#include "core/fpdfapi/parser/cpdf_reference.h"
17#include "core/fpdfapi/parser/cpdf_string.h"
18#include "core/fpdfapi/parser/fpdf_parser_decode.h"
19#include "core/fxcrt/stl_util.h"
20#include "third_party/base/check.h"
21#include "third_party/base/memory/ptr_util.h"
22
23namespace {
24
25constexpr int kNameTreeMaxRecursion = 32;
26
27std::pair<WideString, WideString> GetNodeLimitsAndSanitize(
28 CPDF_Array* pLimits) {
29 DCHECK(pLimits);
30 WideString csLeft = pLimits->GetUnicodeTextAt(0);
31 WideString csRight = pLimits->GetUnicodeTextAt(1);
32 // If the lower limit is greater than the upper limit, swap them.
33 if (csLeft.Compare(csRight) > 0) {
34 pLimits->SetNewAt<CPDF_String>(0, csRight.AsStringView());
35 pLimits->SetNewAt<CPDF_String>(1, csLeft.AsStringView());
36 csLeft = pLimits->GetUnicodeTextAt(0);
37 csRight = pLimits->GetUnicodeTextAt(1);
38 }
39 while (pLimits->size() > 2)
40 pLimits->RemoveAt(pLimits->size() - 1);
41 return {csLeft, csRight};
42}
43
44// Get the limit arrays that leaf array |pFind| is under in the tree with root
45// |pNode|. |pLimits| will hold all the limit arrays from the leaf up to before
46// the root. Return true if successful.
47bool GetNodeAncestorsLimitsInternal(const RetainPtr<CPDF_Dictionary>& pNode,
48 const CPDF_Array* pFind,
49 int nLevel,
50 std::vector<CPDF_Array*>* pLimits) {
51 if (nLevel > kNameTreeMaxRecursion)
52 return false;
53
54 if (pNode->GetArrayFor("Names") == pFind) {
55 pLimits->push_back(pNode->GetMutableArrayFor("Limits").Get());
56 return true;
57 }
58
59 RetainPtr<CPDF_Array> pKids = pNode->GetMutableArrayFor("Kids");
60 if (!pKids)
61 return false;
62
63 for (size_t i = 0; i < pKids->size(); ++i) {
64 RetainPtr<CPDF_Dictionary> pKid = pKids->GetMutableDictAt(i);
65 if (!pKid)
66 continue;
67
68 if (GetNodeAncestorsLimitsInternal(pKid, pFind, nLevel + 1, pLimits)) {
69 pLimits->push_back(pNode->GetMutableArrayFor("Limits").Get());
70 return true;
71 }
72 }
73 return false;
74}
75
76// Wrapper for GetNodeAncestorsLimitsInternal() so callers do not need to know
77// about the details.
78std::vector<CPDF_Array*> GetNodeAncestorsLimits(
79 const RetainPtr<CPDF_Dictionary>& pNode,
80 const CPDF_Array* pFind) {
81 std::vector<CPDF_Array*> results;
82 GetNodeAncestorsLimitsInternal(pNode, pFind, 0, &results);
83 return results;
84}
85
86// Upon the deletion of |csName| from leaf array |pFind|, update the ancestors
87// of |pFind|. Specifically, the limits of |pFind|'s ancestors will be updated
88// if needed, and any ancestors that are now empty will be removed.
89bool UpdateNodesAndLimitsUponDeletion(CPDF_Dictionary* pNode,
90 const CPDF_Array* pFind,
91 const WideString& csName,
92 int nLevel) {
93 if (nLevel > kNameTreeMaxRecursion)
94 return false;
95
96 RetainPtr<CPDF_Array> pLimits = pNode->GetMutableArrayFor("Limits");
97 WideString csLeft;
98 WideString csRight;
99 if (pLimits)
100 std::tie(csLeft, csRight) = GetNodeLimitsAndSanitize(pLimits.Get());
101
102 RetainPtr<const CPDF_Array> pNames = pNode->GetArrayFor("Names");
103 if (pNames) {
104 if (pNames != pFind)
105 return false;
106 if (pNames->IsEmpty() || !pLimits)
107 return true;
108 if (csLeft != csName && csRight != csName)
109 return true;
110
111 // Since |csName| defines |pNode|'s limits, we need to loop through the
112 // names to find the new lower and upper limits.
113 WideString csNewLeft = csRight;
114 WideString csNewRight = csLeft;
115 for (size_t i = 0; i < pNames->size() / 2; ++i) {
116 WideString wsName = pNames->GetUnicodeTextAt(i * 2);
117 if (wsName.Compare(csNewLeft) < 0)
118 csNewLeft = wsName;
119 if (wsName.Compare(csNewRight) > 0)
120 csNewRight = wsName;
121 }
122 pLimits->SetNewAt<CPDF_String>(0, csNewLeft.AsStringView());
123 pLimits->SetNewAt<CPDF_String>(1, csNewRight.AsStringView());
124 return true;
125 }
126
127 RetainPtr<CPDF_Array> pKids = pNode->GetMutableArrayFor("Kids");
128 if (!pKids)
129 return false;
130
131 // Loop through the kids to find the leaf array |pFind|.
132 for (size_t i = 0; i < pKids->size(); ++i) {
133 RetainPtr<CPDF_Dictionary> pKid = pKids->GetMutableDictAt(i);
134 if (!pKid)
135 continue;
136 if (!UpdateNodesAndLimitsUponDeletion(pKid.Get(), pFind, csName,
137 nLevel + 1)) {
138 continue;
139 }
140 // Remove this child node if it's empty.
141 if ((pKid->KeyExist("Names") && pKid->GetArrayFor("Names")->IsEmpty()) ||
142 (pKid->KeyExist("Kids") && pKid->GetArrayFor("Kids")->IsEmpty())) {
143 pKids->RemoveAt(i);
144 }
145 if (pKids->IsEmpty() || !pLimits)
146 return true;
147 if (csLeft != csName && csRight != csName)
148 return true;
149
150 // Since |csName| defines |pNode|'s limits, we need to loop through the
151 // kids to find the new lower and upper limits.
152 WideString csNewLeft = csRight;
153 WideString csNewRight = csLeft;
154 for (size_t j = 0; j < pKids->size(); ++j) {
155 RetainPtr<const CPDF_Array> pKidLimits =
156 pKids->GetDictAt(j)->GetArrayFor("Limits");
157 DCHECK(pKidLimits);
158 if (pKidLimits->GetUnicodeTextAt(0).Compare(csNewLeft) < 0)
159 csNewLeft = pKidLimits->GetUnicodeTextAt(0);
160 if (pKidLimits->GetUnicodeTextAt(1).Compare(csNewRight) > 0)
161 csNewRight = pKidLimits->GetUnicodeTextAt(1);
162 }
163 pLimits->SetNewAt<CPDF_String>(0, csNewLeft.AsStringView());
164 pLimits->SetNewAt<CPDF_String>(1, csNewRight.AsStringView());
165 return true;
166 }
167 return false;
168}
169
170bool IsTraversedObject(const CPDF_Object* obj,
171 std::set<uint32_t>* seen_obj_nums) {
172 uint32_t obj_num = obj->GetObjNum();
173 if (!obj_num)
174 return false;
175
176 bool inserted = seen_obj_nums->insert(obj_num).second;
177 return !inserted;
178}
179
180bool IsArrayWithTraversedObject(const CPDF_Array* array,
181 std::set<uint32_t>* seen_obj_nums) {
182 if (IsTraversedObject(array, seen_obj_nums))
183 return true;
184
185 CPDF_ArrayLocker locker(array);
186 for (const auto& item : locker) {
187 if (IsTraversedObject(item.Get(), seen_obj_nums))
188 return true;
189 }
190 return false;
191}
192
193// Search for |csName| in the tree with root |pNode|. If successful, return the
194// value that |csName| points to; |nIndex| will be the index of |csName|,
195// |ppFind| will be the leaf array that |csName| is found in, and |pFindIndex|
196// will be the index of |csName| in |ppFind|. If |csName| is not found, |ppFind|
197// will be the leaf array that |csName| should be added to, and |pFindIndex|
198// will be the index that it should be added at.
199RetainPtr<const CPDF_Object> SearchNameNodeByNameInternal(
200 const RetainPtr<CPDF_Dictionary>& pNode,
201 const WideString& csName,
202 int nLevel,
203 size_t* nIndex,
204 RetainPtr<CPDF_Array>* ppFind,
205 int* pFindIndex,
206 std::set<uint32_t>* seen_obj_nums) {
207 if (nLevel > kNameTreeMaxRecursion)
208 return nullptr;
209
210 RetainPtr<CPDF_Array> pLimits = pNode->GetMutableArrayFor("Limits");
211 RetainPtr<CPDF_Array> pNames = pNode->GetMutableArrayFor("Names");
212 if (pNames && IsArrayWithTraversedObject(pNames.Get(), seen_obj_nums))
213 pNames.Reset();
214 if (pLimits && IsArrayWithTraversedObject(pLimits.Get(), seen_obj_nums))
215 pLimits.Reset();
216
217 if (pLimits) {
218 WideString csLeft;
219 WideString csRight;
220 std::tie(csLeft, csRight) = GetNodeLimitsAndSanitize(pLimits.Get());
221 // Skip this node if the name to look for is smaller than its lower limit.
222 if (csName.Compare(csLeft) < 0)
223 return nullptr;
224
225 // Skip this node if the name to look for is greater than its higher limit,
226 // and the node itself is a leaf node.
227 if (csName.Compare(csRight) > 0 && pNames) {
228 if (ppFind)
229 *ppFind = pNames;
230 if (pFindIndex)
231 *pFindIndex = fxcrt::CollectionSize<int32_t>(*pNames) / 2 - 1;
232 return nullptr;
233 }
234 }
235
236 // If the node is a leaf node, look for the name in its names array.
237 if (pNames) {
238 size_t dwCount = pNames->size() / 2;
239 for (size_t i = 0; i < dwCount; i++) {
240 WideString csValue = pNames->GetUnicodeTextAt(i * 2);
241 int32_t iCompare = csValue.Compare(csName);
242 if (iCompare > 0)
243 break;
244 if (ppFind)
245 *ppFind = pNames;
246 if (pFindIndex)
247 *pFindIndex = pdfium::base::checked_cast<int32_t>(i);
248 if (iCompare < 0)
249 continue;
250
251 *nIndex += i;
252 return pNames->GetDirectObjectAt(i * 2 + 1);
253 }
254 *nIndex += dwCount;
255 return nullptr;
256 }
257
258 // Search through the node's children.
259 RetainPtr<CPDF_Array> pKids = pNode->GetMutableArrayFor("Kids");
260 if (!pKids || IsTraversedObject(pKids.Get(), seen_obj_nums))
261 return nullptr;
262
263 for (size_t i = 0; i < pKids->size(); i++) {
264 RetainPtr<CPDF_Dictionary> pKid = pKids->GetMutableDictAt(i);
265 if (!pKid || IsTraversedObject(pKid.Get(), seen_obj_nums))
266 continue;
267
268 RetainPtr<const CPDF_Object> pFound = SearchNameNodeByNameInternal(
269 pKid, csName, nLevel + 1, nIndex, ppFind, pFindIndex, seen_obj_nums);
270 if (pFound)
271 return pFound;
272 }
273 return nullptr;
274}
275
276// Wrapper for SearchNameNodeByNameInternal() so callers do not need to know
277// about the details.
278RetainPtr<const CPDF_Object> SearchNameNodeByName(
279 const RetainPtr<CPDF_Dictionary>& pNode,
280 const WideString& csName,
281 RetainPtr<CPDF_Array>* ppFind,
282 int* pFindIndex) {
283 size_t nIndex = 0;
284 std::set<uint32_t> seen_obj_nums;
285 return SearchNameNodeByNameInternal(pNode, csName, 0, &nIndex, ppFind,
286 pFindIndex, &seen_obj_nums);
287}
288
289struct IndexSearchResult {
290 // For the n-th object in a tree, the key and value.
291 WideString key;
292 RetainPtr<CPDF_Object> value;
293 // The leaf node that holds `key` and `value`.
294 RetainPtr<CPDF_Array> container;
295 // The index for `key` in `container`. Must be even.
296 size_t index;
297};
298
299// Find the `nTargetPairIndex` node in the tree with root `pNode`. `nLevel`
300// tracks the recursion level and `nCurPairIndex` tracks the progress towards
301// `nTargetPairIndex`.
302absl::optional<IndexSearchResult> SearchNameNodeByIndexInternal(
303 CPDF_Dictionary* pNode,
304 size_t nTargetPairIndex,
305 int nLevel,
306 size_t* nCurPairIndex) {
307 if (nLevel > kNameTreeMaxRecursion)
308 return absl::nullopt;
309
310 RetainPtr<CPDF_Array> pNames = pNode->GetMutableArrayFor("Names");
311 if (pNames) {
312 size_t nCount = pNames->size() / 2;
313 if (nTargetPairIndex >= *nCurPairIndex + nCount) {
314 *nCurPairIndex += nCount;
315 return absl::nullopt;
316 }
317
318 size_t index = 2 * (nTargetPairIndex - *nCurPairIndex);
319 RetainPtr<CPDF_Object> value = pNames->GetMutableDirectObjectAt(index + 1);
320 if (!value)
321 return absl::nullopt;
322
323 IndexSearchResult result;
324 result.key = pNames->GetUnicodeTextAt(index);
325 result.value = std::move(value);
326 result.container = std::move(pNames);
327 result.index = index;
328 return result;
329 }
330
331 RetainPtr<CPDF_Array> pKids = pNode->GetMutableArrayFor("Kids");
332 if (!pKids)
333 return absl::nullopt;
334
335 for (size_t i = 0; i < pKids->size(); i++) {
336 RetainPtr<CPDF_Dictionary> pKid = pKids->GetMutableDictAt(i);
337 if (!pKid)
338 continue;
339 absl::optional<IndexSearchResult> result = SearchNameNodeByIndexInternal(
340 pKid.Get(), nTargetPairIndex, nLevel + 1, nCurPairIndex);
341 if (result.has_value())
342 return result;
343 }
344 return absl::nullopt;
345}
346
347// Wrapper for SearchNameNodeByIndexInternal() so callers do not need to know
348// about the details.
349absl::optional<IndexSearchResult> SearchNameNodeByIndex(
350 CPDF_Dictionary* pNode,
351 size_t nTargetPairIndex) {
352 size_t nCurPairIndex = 0;
353 return SearchNameNodeByIndexInternal(pNode, nTargetPairIndex, 0,
354 &nCurPairIndex);
355}
356
357// Get the total number of key-value pairs in the tree with root |pNode|.
358size_t CountNamesInternal(const CPDF_Dictionary* pNode,
359 int nLevel,
360 std::set<const CPDF_Dictionary*>& seen) {
361 if (nLevel > kNameTreeMaxRecursion)
362 return 0;
363
364 const bool inserted = seen.insert(pNode).second;
365 if (!inserted)
366 return 0;
367
368 RetainPtr<const CPDF_Array> pNames = pNode->GetArrayFor("Names");
369 if (pNames)
370 return pNames->size() / 2;
371
372 RetainPtr<const CPDF_Array> pKids = pNode->GetArrayFor("Kids");
373 if (!pKids)
374 return 0;
375
376 size_t nCount = 0;
377 for (size_t i = 0; i < pKids->size(); i++) {
378 RetainPtr<const CPDF_Dictionary> pKid = pKids->GetDictAt(i);
379 if (!pKid)
380 continue;
381
382 nCount += CountNamesInternal(pKid.Get(), nLevel + 1, seen);
383 }
384 return nCount;
385}
386
387RetainPtr<const CPDF_Array> GetNamedDestFromObject(
388 RetainPtr<const CPDF_Object> obj) {
389 RetainPtr<const CPDF_Array> array = ToArray(obj);
390 if (array)
391 return array;
392 RetainPtr<const CPDF_Dictionary> dict = ToDictionary(obj);
393 if (dict)
394 return dict->GetArrayFor("D");
395 return nullptr;
396}
397
398RetainPtr<const CPDF_Array> LookupOldStyleNamedDest(CPDF_Document* pDoc,
399 const ByteString& name) {
400 RetainPtr<const CPDF_Dictionary> pDests =
401 pDoc->GetRoot()->GetDictFor("Dests");
402 if (!pDests)
403 return nullptr;
404 return GetNamedDestFromObject(pDests->GetDirectObjectFor(name));
405}
406
407} // namespace
408
409CPDF_NameTree::CPDF_NameTree(RetainPtr<CPDF_Dictionary> pRoot)
410 : m_pRoot(std::move(pRoot)) {
411 DCHECK(m_pRoot);
412}
413
414CPDF_NameTree::~CPDF_NameTree() = default;
415
416// static
417std::unique_ptr<CPDF_NameTree> CPDF_NameTree::Create(
418 CPDF_Document* pDoc,
419 const ByteString& category) {
420 RetainPtr<CPDF_Dictionary> pRoot = pDoc->GetMutableRoot();
421 if (!pRoot)
422 return nullptr;
423
424 RetainPtr<CPDF_Dictionary> pNames = pRoot->GetMutableDictFor("Names");
425 if (!pNames)
426 return nullptr;
427
428 RetainPtr<CPDF_Dictionary> pCategory = pNames->GetMutableDictFor(category);
429 if (!pCategory)
430 return nullptr;
431
432 return pdfium::WrapUnique(
433 new CPDF_NameTree(std::move(pCategory))); // Private ctor.
434}
435
436// static
437std::unique_ptr<CPDF_NameTree> CPDF_NameTree::CreateWithRootNameArray(
438 CPDF_Document* pDoc,
439 const ByteString& category) {
440 RetainPtr<CPDF_Dictionary> pRoot = pDoc->GetMutableRoot();
441 if (!pRoot)
442 return nullptr;
443
444 // Retrieve the document's Names dictionary; create it if missing.
445 RetainPtr<CPDF_Dictionary> pNames = pRoot->GetMutableDictFor("Names");
446 if (!pNames) {
447 pNames = pDoc->NewIndirect<CPDF_Dictionary>();
448 pRoot->SetNewFor<CPDF_Reference>("Names", pDoc, pNames->GetObjNum());
449 }
450
451 // Create the |category| dictionary if missing.
452 RetainPtr<CPDF_Dictionary> pCategory = pNames->GetMutableDictFor(category);
453 if (!pCategory) {
454 pCategory = pDoc->NewIndirect<CPDF_Dictionary>();
455 pCategory->SetNewFor<CPDF_Array>("Names");
456 pNames->SetNewFor<CPDF_Reference>(category, pDoc, pCategory->GetObjNum());
457 }
458
459 return pdfium::WrapUnique(new CPDF_NameTree(pCategory)); // Private ctor.
460}
461
462// static
463std::unique_ptr<CPDF_NameTree> CPDF_NameTree::CreateForTesting(
464 CPDF_Dictionary* pRoot) {
465 return pdfium::WrapUnique(
466 new CPDF_NameTree(pdfium::WrapRetain(pRoot))); // Private ctor.
467}
468
469// static
471 CPDF_Document* pDoc,
472 const ByteString& name) {
473 RetainPtr<const CPDF_Array> dest_array;
474 std::unique_ptr<CPDF_NameTree> name_tree = Create(pDoc, "Dests");
475 if (name_tree)
476 dest_array = name_tree->LookupNewStyleNamedDest(name);
477 if (!dest_array)
478 dest_array = LookupOldStyleNamedDest(pDoc, name);
479 return dest_array;
480}
481
483 std::set<const CPDF_Dictionary*> seen;
484 return CountNamesInternal(m_pRoot.Get(), 0, seen);
485}
486
488 const WideString& name) {
489 RetainPtr<CPDF_Array> pFind;
490 int nFindIndex = -1;
491 // Handle the corner case where the root node is empty. i.e. No kids and no
492 // names. In which case, just insert into it and skip all the searches.
493 RetainPtr<CPDF_Array> pNames = m_pRoot->GetMutableArrayFor("Names");
494 if (pNames && pNames->IsEmpty() && !m_pRoot->GetArrayFor("Kids"))
495 pFind = pNames;
496
497 if (!pFind) {
498 // Fail if the tree already contains this name or if the tree is too deep.
499 if (SearchNameNodeByName(m_pRoot, name, &pFind, &nFindIndex))
500 return false;
501 }
502
503 // If the returned |pFind| is a nullptr, then |name| is smaller than all
504 // existing entries in the tree, and we did not find a leaf array to place
505 // |name| into. We instead will find the leftmost leaf array in which to place
506 // |name| and |pObj|.
507 if (!pFind) {
508 absl::optional<IndexSearchResult> result =
509 SearchNameNodeByIndex(m_pRoot.Get(), 0);
510 if (!result.has_value()) {
511 // Give up if that fails too.
512 return false;
513 }
514
515 pFind = result.value().container;
516 DCHECK(pFind);
517 }
518
519 // Insert the name and the object into the leaf array found. Note that the
520 // insertion position is right after the key-value pair returned by |index|.
521 size_t nNameIndex = (nFindIndex + 1) * 2;
522 size_t nValueIndex = nNameIndex + 1;
523 pFind->InsertNewAt<CPDF_String>(nNameIndex, name.AsStringView());
524 pFind->InsertAt(nValueIndex, std::move(pObj));
525
526 // Expand the limits that the newly added name is under, if the name falls
527 // outside of the limits of its leaf array or any arrays above it.
528 std::vector<CPDF_Array*> all_limits =
529 GetNodeAncestorsLimits(m_pRoot, pFind.Get());
530 for (auto* pLimits : all_limits) {
531 if (!pLimits)
532 continue;
533
534 if (name.Compare(pLimits->GetUnicodeTextAt(0)) < 0)
535 pLimits->SetNewAt<CPDF_String>(0, name.AsStringView());
536
537 if (name.Compare(pLimits->GetUnicodeTextAt(1)) > 0)
538 pLimits->SetNewAt<CPDF_String>(1, name.AsStringView());
539 }
540 return true;
541}
542
543bool CPDF_NameTree::DeleteValueAndName(size_t nIndex) {
544 absl::optional<IndexSearchResult> result =
545 SearchNameNodeByIndex(m_pRoot.Get(), nIndex);
546 if (!result) {
547 // Fail if the tree does not contain |nIndex|.
548 return false;
549 }
550
551 // Remove the name and the object from the leaf array |pFind|.
552 RetainPtr<CPDF_Array> pFind = result.value().container;
553 pFind->RemoveAt(result.value().index + 1);
554 pFind->RemoveAt(result.value().index);
555
556 // Delete empty nodes and update the limits of |pFind|'s ancestors as needed.
557 UpdateNodesAndLimitsUponDeletion(m_pRoot.Get(), pFind.Get(),
558 result.value().key, 0);
559 return true;
560}
561
563 size_t nIndex,
564 WideString* csName) const {
565 absl::optional<IndexSearchResult> result =
566 SearchNameNodeByIndex(m_pRoot.Get(), nIndex);
567 if (!result) {
568 csName->clear();
569 return nullptr;
570 }
571
572 *csName = std::move(result.value().key);
573 return result.value().value;
574}
575
577 const WideString& csName) const {
578 return SearchNameNodeByName(m_pRoot, csName, nullptr, nullptr);
579}
580
581RetainPtr<const CPDF_Array> CPDF_NameTree::LookupNewStyleNamedDest(
582 const ByteString& sName) {
583 return GetNamedDestFromObject(LookupValue(PDF_DecodeText(sName.raw_span())));
584}
CPDF_ArrayLocker(const CPDF_Array *pArray)
bool AddValueAndName(RetainPtr< CPDF_Object > pObj, const WideString &name)
static RetainPtr< const CPDF_Array > LookupNamedDest(CPDF_Document *doc, const ByteString &name)
RetainPtr< const CPDF_Object > LookupValue(const WideString &csName) const
RetainPtr< CPDF_Object > LookupValueAndName(size_t nIndex, WideString *csName) const
size_t GetCount() const
bool DeleteValueAndName(size_t nIndex)
uint32_t GetObjNum() const
Definition cpdf_object.h:65
bool operator!=(const WideString &other) const
Definition widestring.h:140
WideString & operator=(const WideString &that)
int Compare(const WideString &str) const