/src/dcmtk/dcmdata/libsrc/dcbytstr.cc
Line | Count | Source |
1 | | /* |
2 | | * |
3 | | * Copyright (C) 1994-2025, OFFIS e.V. |
4 | | * All rights reserved. See COPYRIGHT file for details. |
5 | | * |
6 | | * This software and supporting documentation were developed by |
7 | | * |
8 | | * OFFIS e.V. |
9 | | * R&D Division Health |
10 | | * Escherweg 2 |
11 | | * D-26121 Oldenburg, Germany |
12 | | * |
13 | | * |
14 | | * Module: dcmdata |
15 | | * |
16 | | * Author: Gerd Ehlers, Andreas Barth |
17 | | * |
18 | | * Purpose: Implementation of class DcmByteString |
19 | | * |
20 | | */ |
21 | | |
22 | | |
23 | | #include "dcmtk/config/osconfig.h" /* make sure OS specific configuration is included first */ |
24 | | #include "dcmtk/ofstd/ofstream.h" |
25 | | #include "dcmtk/ofstd/ofstring.h" |
26 | | #include "dcmtk/ofstd/ofstd.h" |
27 | | #include "dcmtk/dcmdata/dcjson.h" |
28 | | #include "dcmtk/dcmdata/dcbytstr.h" |
29 | | #include "dcmtk/dcmdata/dcvr.h" |
30 | | #include "dcmtk/dcmdata/dcmatch.h" |
31 | | |
32 | | // global flags |
33 | | |
34 | | OFGlobal<OFBool> dcmEnableVRCheckerForStringValues(OFTrue); |
35 | | |
36 | | |
37 | | // global function to get a particular component of a DICOM string |
38 | | OFCondition getStringPart(OFString &result, |
39 | | const char *str, |
40 | | const unsigned long len, |
41 | | const unsigned long pos) |
42 | 0 | { |
43 | 0 | OFCondition l_error = EC_Normal; |
44 | | /* check string parameter */ |
45 | 0 | if (str != NULL) |
46 | 0 | { |
47 | | /* search for beginning of specified string component */ |
48 | 0 | unsigned long i = 0; |
49 | 0 | unsigned long curPos = 0; |
50 | 0 | while ((curPos < pos) && (i++ < len)) |
51 | 0 | { |
52 | 0 | if (*str++ == '\\') |
53 | 0 | curPos++; |
54 | 0 | } |
55 | | /* if found ... */ |
56 | 0 | if (curPos == pos) |
57 | 0 | { |
58 | | /* search for end of specified string component */ |
59 | 0 | const char *p = str; |
60 | 0 | while ((*p != '\\') && (i++ < len)) |
61 | 0 | p++; |
62 | | /* check whether string component is non-empty */ |
63 | 0 | if (p - str > 0) |
64 | 0 | result.assign(str, p - str); |
65 | 0 | else |
66 | 0 | result.clear(); |
67 | 0 | } else { |
68 | | /* specified component index not found in string */ |
69 | 0 | l_error = EC_IllegalParameter; |
70 | 0 | } |
71 | 0 | } else |
72 | 0 | l_error = EC_IllegalParameter; |
73 | 0 | return l_error; |
74 | 0 | } |
75 | | |
76 | | |
77 | | // ******************************** |
78 | | |
79 | | DcmByteString::DcmByteString(const DcmTag &tag) |
80 | 0 | : DcmElement(tag, 0), |
81 | 0 | paddingChar(' '), |
82 | 0 | maxLength(DCM_UndefinedLength), |
83 | 0 | realLength(0), |
84 | 0 | fStringMode(DCM_UnknownString), |
85 | 0 | nonSignificantChars() |
86 | 0 | { |
87 | 0 | } |
88 | | |
89 | | |
90 | | DcmByteString::DcmByteString(const DcmTag &tag, |
91 | | const Uint32 len) |
92 | 10.7k | : DcmElement(tag, len), |
93 | 10.7k | paddingChar(' '), |
94 | 10.7k | maxLength(DCM_UndefinedLength), |
95 | 10.7k | realLength(len), |
96 | 10.7k | fStringMode(DCM_UnknownString), |
97 | 10.7k | nonSignificantChars() |
98 | 10.7k | { |
99 | 10.7k | } |
100 | | |
101 | | |
102 | | DcmByteString::DcmByteString(const DcmByteString &old) |
103 | 0 | : DcmElement(old), |
104 | 0 | paddingChar(old.paddingChar), |
105 | 0 | maxLength(old.maxLength), |
106 | 0 | realLength(old.realLength), |
107 | 0 | fStringMode(old.fStringMode), |
108 | 0 | nonSignificantChars(old.nonSignificantChars) |
109 | 0 | { |
110 | 0 | } |
111 | | |
112 | | |
113 | | DcmByteString::~DcmByteString() |
114 | 10.7k | { |
115 | 10.7k | } |
116 | | |
117 | | |
118 | | DcmByteString &DcmByteString::operator=(const DcmByteString &obj) |
119 | 0 | { |
120 | 0 | if (this != &obj) |
121 | 0 | { |
122 | 0 | DcmElement::operator=(obj); |
123 | | |
124 | | /* copy member variables */ |
125 | 0 | paddingChar = obj.paddingChar; |
126 | 0 | maxLength = obj.maxLength; |
127 | 0 | realLength = obj.realLength; |
128 | 0 | fStringMode = obj.fStringMode; |
129 | 0 | nonSignificantChars = obj.nonSignificantChars; |
130 | 0 | } |
131 | 0 | return *this; |
132 | 0 | } |
133 | | |
134 | | |
135 | | int DcmByteString::compare(const DcmElement& rhs) const |
136 | 0 | { |
137 | 0 | int result = DcmElement::compare(rhs); |
138 | 0 | if (result != 0) |
139 | 0 | { |
140 | 0 | return result; |
141 | 0 | } |
142 | | |
143 | | /* cast away constness (dcmdata is not const correct...) */ |
144 | 0 | DcmByteString* myThis = NULL; |
145 | 0 | DcmByteString* myRhs = NULL; |
146 | 0 | myThis = OFconst_cast(DcmByteString*, this); |
147 | 0 | myRhs = OFstatic_cast(DcmByteString*, OFconst_cast(DcmElement*, &rhs)); |
148 | | |
149 | | /* compare number of values */ |
150 | 0 | unsigned long rhsNumValues = myRhs->getNumberOfValues(); |
151 | 0 | unsigned long thisNumValues = myThis->getNumberOfValues(); |
152 | 0 | if (thisNumValues < rhsNumValues) |
153 | 0 | { |
154 | 0 | return -1; |
155 | 0 | } |
156 | 0 | else if (thisNumValues > rhsNumValues) |
157 | 0 | { |
158 | 0 | return 1; |
159 | 0 | } |
160 | | |
161 | | /* iterate over all components and test equality */ |
162 | 0 | for (unsigned long count = 0; count < thisNumValues; count++) |
163 | 0 | { |
164 | 0 | OFString val; |
165 | 0 | if (myThis->getOFString(val, count).good()) |
166 | 0 | { |
167 | 0 | OFString rhsVal; |
168 | 0 | if (myRhs->getOFString(rhsVal, count).good()) |
169 | 0 | { |
170 | 0 | result = val.compare(rhsVal); |
171 | 0 | if (result != 0) |
172 | 0 | { |
173 | 0 | return result; |
174 | 0 | } |
175 | 0 | } |
176 | 0 | } |
177 | 0 | } |
178 | | /* all values equal */ |
179 | 0 | return 0; |
180 | 0 | } |
181 | | |
182 | | |
183 | | |
184 | | |
185 | | OFCondition DcmByteString::copyFrom(const DcmObject& rhs) |
186 | 0 | { |
187 | 0 | if (this != &rhs) |
188 | 0 | { |
189 | 0 | if (rhs.ident() != ident()) return EC_IllegalCall; |
190 | 0 | *this = OFstatic_cast(const DcmByteString &, rhs); |
191 | 0 | } |
192 | 0 | return EC_Normal; |
193 | 0 | } |
194 | | |
195 | | |
196 | | // ******************************** |
197 | | |
198 | | |
199 | | DcmEVR DcmByteString::ident() const |
200 | 0 | { |
201 | | /* valid type identifier is set by derived classes */ |
202 | 0 | return EVR_UNKNOWN; |
203 | 0 | } |
204 | | |
205 | | |
206 | | unsigned long DcmByteString::getVM() |
207 | 0 | { |
208 | 0 | char *str = NULL; |
209 | 0 | Uint32 len = 0; |
210 | | /* get stored string value */ |
211 | 0 | getString(str, len); |
212 | | /* and determine the VM */ |
213 | 0 | return DcmElement::determineVM(str, len); |
214 | 0 | } |
215 | | |
216 | | |
217 | | unsigned long DcmByteString::getNumberOfValues() |
218 | 0 | { |
219 | | /* same as value multiplicity unless overwritten in a derived class */ |
220 | 0 | return getVM(); |
221 | 0 | } |
222 | | |
223 | | |
224 | | OFCondition DcmByteString::clear() |
225 | 0 | { |
226 | | /* call inherited method */ |
227 | 0 | errorFlag = DcmElement::clear(); |
228 | | /* set string representation to unknown */ |
229 | 0 | fStringMode = DCM_UnknownString; |
230 | 0 | realLength = 0; |
231 | 0 | return errorFlag; |
232 | 0 | } |
233 | | |
234 | | |
235 | | Uint32 DcmByteString::getRealLength() |
236 | 0 | { |
237 | | /* convert string to internal representation (if required) */ |
238 | 0 | if (fStringMode != DCM_MachineString) |
239 | 0 | { |
240 | | /* strips non-significant trailing spaces (padding) and determines 'realLength' */ |
241 | 0 | makeMachineByteString(); |
242 | 0 | } |
243 | | /* string length of the internal representation */ |
244 | 0 | return realLength; |
245 | 0 | } |
246 | | |
247 | | |
248 | | Uint32 DcmByteString::getLength(const E_TransferSyntax /*xfer*/, |
249 | | const E_EncodingType /*enctype*/) |
250 | 0 | { |
251 | | /* convert string to DICOM representation, i.e. add padding if required */ |
252 | 0 | makeDicomByteString(); |
253 | | /* DICOM value length is always an even number */ |
254 | 0 | return getLengthField(); |
255 | 0 | } |
256 | | |
257 | | |
258 | | // ******************************** |
259 | | |
260 | | void DcmByteString::print(STD_NAMESPACE ostream& out, |
261 | | const size_t flags, |
262 | | const int level, |
263 | | const char * /*pixelFileName*/, |
264 | | size_t * /*pixelCounter*/) |
265 | 0 | { |
266 | 0 | if (valueLoaded()) |
267 | 0 | { |
268 | | /* get string data */ |
269 | 0 | char *stringVal = NULL; |
270 | 0 | Uint32 stringLen = 0; |
271 | 0 | getString(stringVal, stringLen); |
272 | 0 | if ((stringVal != NULL) && (stringLen > 0)) |
273 | 0 | { |
274 | | /* print line start with tag and VR */ |
275 | 0 | printInfoLineStart(out, flags, level); |
276 | 0 | out << '['; |
277 | |
|
278 | 0 | OFString outString; |
279 | | /* do not create more output than actually needed */ |
280 | 0 | const size_t outStrLen = (flags & DCMTypes::PF_shortenLongTagValues) ? DCM_OptPrintLineLength : 0 /* all characters */; |
281 | | /* check whether string has to be converted to markup or octal representation */ |
282 | 0 | if (flags & DCMTypes::PF_convertToMarkup) |
283 | 0 | { |
284 | 0 | OFString inString(stringVal, stringLen); |
285 | 0 | OFStandard::convertToMarkupString(inString, outString, OFTrue, OFStandard::MM_XML, OFFalse, outStrLen); |
286 | 0 | } |
287 | 0 | else if (flags & DCMTypes::PF_convertToOctalNumbers) |
288 | 0 | { |
289 | 0 | OFString inString(stringVal, stringLen); |
290 | 0 | OFStandard::convertToOctalString(inString, outString, outStrLen); |
291 | 0 | } else { |
292 | | /* check whether we need the full string or the prefix only */ |
293 | 0 | if ((outStrLen == 0) || (outStrLen > stringLen)) |
294 | 0 | outString.assign(stringVal, stringLen); |
295 | 0 | else |
296 | 0 | outString.assign(stringVal, outStrLen); |
297 | 0 | } |
298 | |
|
299 | 0 | size_t printedLength = outString.length() + 2 /* for enclosing brackets */; |
300 | | |
301 | | /* check whether full value text should be printed */ |
302 | 0 | if ((flags & DCMTypes::PF_shortenLongTagValues) && (printedLength > DCM_OptPrintLineLength)) |
303 | 0 | { |
304 | | /* truncate value text and append "..." */ |
305 | 0 | outString.erase(DCM_OptPrintLineLength - 4); |
306 | 0 | out << outString << "..."; |
307 | 0 | printedLength = DCM_OptPrintLineLength; |
308 | 0 | } else |
309 | 0 | out << outString << ']'; |
310 | | |
311 | | /* print line end with length, VM and tag name */ |
312 | 0 | printInfoLineEnd(out, flags, OFstatic_cast(unsigned long, printedLength)); |
313 | 0 | } else |
314 | 0 | printInfoLine(out, flags, level, "(no value available)"); |
315 | 0 | } else |
316 | 0 | printInfoLine(out, flags, level, "(not loaded)"); |
317 | 0 | } |
318 | | |
319 | | |
320 | | // ******************************** |
321 | | |
322 | | |
323 | | OFCondition DcmByteString::write(DcmOutputStream &outStream, |
324 | | const E_TransferSyntax oxfer, |
325 | | const E_EncodingType enctype, |
326 | | DcmWriteCache *wcache) |
327 | 0 | { |
328 | 0 | if (getTransferState() == ERW_notInitialized) |
329 | 0 | errorFlag = EC_IllegalCall; |
330 | 0 | else |
331 | 0 | { |
332 | | /* convert string value to DICOM representation and call inherited method */ |
333 | 0 | if (getTransferState() == ERW_init) |
334 | 0 | makeDicomByteString(); |
335 | |
|
336 | 0 | errorFlag = DcmElement::write(outStream, oxfer, enctype, wcache); |
337 | 0 | } |
338 | 0 | return errorFlag; |
339 | 0 | } |
340 | | |
341 | | |
342 | | OFCondition DcmByteString::writeSignatureFormat(DcmOutputStream &outStream, |
343 | | const E_TransferSyntax oxfer, |
344 | | const E_EncodingType enctype, |
345 | | DcmWriteCache *wcache) |
346 | 0 | { |
347 | 0 | if (getTransferState() == ERW_notInitialized) |
348 | 0 | errorFlag = EC_IllegalCall; |
349 | 0 | else |
350 | 0 | { |
351 | | /* convert string value to DICOM representation and call inherited method */ |
352 | 0 | if (getTransferState() == ERW_init) |
353 | 0 | makeDicomByteString(); |
354 | 0 | errorFlag = DcmElement::writeSignatureFormat(outStream, oxfer, enctype, wcache); |
355 | 0 | } |
356 | 0 | return errorFlag; |
357 | 0 | } |
358 | | |
359 | | |
360 | | // ******************************** |
361 | | |
362 | | |
363 | | OFCondition DcmByteString::getOFString(OFString &stringVal, |
364 | | const unsigned long pos, |
365 | | OFBool /*normalize*/) |
366 | 0 | { |
367 | | /* check given string position index */ |
368 | 0 | if (pos >= getVM()) |
369 | 0 | { |
370 | | /* treat an empty string as a special case */ |
371 | 0 | if (pos == 0) |
372 | 0 | { |
373 | 0 | errorFlag = EC_Normal; |
374 | 0 | stringVal.clear(); |
375 | 0 | } else |
376 | 0 | errorFlag = EC_IllegalParameter; |
377 | 0 | } else { |
378 | | /* get string data */ |
379 | 0 | char *str = NULL; |
380 | 0 | Uint32 len = 0; |
381 | 0 | errorFlag = getString(str, len); |
382 | | /* check whether string value is present */ |
383 | 0 | if ((str != NULL) && (len > 0)) |
384 | 0 | { |
385 | | /* extract specified string component */ |
386 | 0 | errorFlag = getStringPart(stringVal, str, len, pos); |
387 | 0 | } else |
388 | 0 | stringVal.clear(); |
389 | 0 | } |
390 | 0 | return errorFlag; |
391 | 0 | } |
392 | | |
393 | | |
394 | | OFCondition DcmByteString::getOFStringArray(OFString &stringVal, |
395 | | OFBool normalize) |
396 | 0 | { |
397 | | /* check whether time-consuming normalization is really needed */ |
398 | 0 | if (normalize) |
399 | 0 | errorFlag = DcmElement::getOFStringArray(stringVal, normalize); |
400 | 0 | else |
401 | 0 | errorFlag = getStringValue(stringVal); |
402 | 0 | return errorFlag; |
403 | 0 | } |
404 | | |
405 | | |
406 | | OFCondition DcmByteString::getStringValue(OFString &stringVal) |
407 | 0 | { |
408 | 0 | char *str = NULL; |
409 | 0 | Uint32 len = 0; |
410 | 0 | errorFlag = getString(str, len); |
411 | | /* check whether string value is present */ |
412 | 0 | if ((str != NULL) && (len > 0)) |
413 | 0 | stringVal.assign(str, len); |
414 | 0 | else |
415 | 0 | stringVal.clear(); |
416 | 0 | return errorFlag; |
417 | 0 | } |
418 | | |
419 | | |
420 | | OFCondition DcmByteString::getString(char *&stringVal) |
421 | 4.51k | { |
422 | 4.51k | errorFlag = EC_Normal; |
423 | | /* get string data */ |
424 | 4.51k | stringVal = OFstatic_cast(char *, getValue()); |
425 | | /* convert to internal string representation (without padding) if required */ |
426 | 4.51k | if ((stringVal != NULL) && (fStringMode != DCM_MachineString)) |
427 | 964 | makeMachineByteString(); |
428 | 4.51k | return errorFlag; |
429 | 4.51k | } |
430 | | |
431 | | |
432 | | OFCondition DcmByteString::getString(char *&stringVal, |
433 | | Uint32 &stringLen) |
434 | 0 | { |
435 | | /* get string data */ |
436 | 0 | errorFlag = getString(stringVal); |
437 | | /* return the real length of the value */ |
438 | 0 | stringLen = realLength; |
439 | 0 | return errorFlag; |
440 | 0 | } |
441 | | |
442 | | |
443 | | // ******************************** |
444 | | |
445 | | |
446 | | OFCondition DcmByteString::putString(const char *stringVal) |
447 | 0 | { |
448 | | /* determine length of the string value */ |
449 | 0 | const size_t stringLen = (stringVal != NULL) ? strlen(stringVal) : 0; |
450 | | /* call the real function */ |
451 | 0 | return putString(stringVal, OFstatic_cast(Uint32, stringLen)); |
452 | 0 | } |
453 | | |
454 | | |
455 | | OFCondition DcmByteString::putString(const char *stringVal, |
456 | | const Uint32 stringLen) |
457 | 0 | { |
458 | 0 | errorFlag = EC_Normal; |
459 | | /* check for an empty string parameter */ |
460 | 0 | if ((stringVal != NULL) && (stringLen > 0)) |
461 | 0 | putValue(stringVal, stringLen); |
462 | 0 | else |
463 | 0 | putValue(NULL, 0); |
464 | | /* make sure that extra padding is removed from the string */ |
465 | 0 | fStringMode = DCM_UnknownString; |
466 | 0 | makeMachineByteString(stringLen); |
467 | 0 | return errorFlag; |
468 | 0 | } |
469 | | |
470 | | |
471 | | OFCondition DcmByteString::putOFStringAtPos(const OFString& stringVal, |
472 | | const unsigned long pos) |
473 | 0 | { |
474 | 0 | OFCondition result; |
475 | | // Get old value |
476 | 0 | OFString str; |
477 | 0 | result = getOFStringArray( str ); |
478 | 0 | if (result.good()) |
479 | 0 | { |
480 | 0 | size_t currentVM = getNumberOfValues(); |
481 | | // Trivial case: No values are set and new value should go to first position |
482 | 0 | if ( (currentVM == 0) && (pos == 0)) |
483 | 0 | return putOFStringArray(stringVal); |
484 | | |
485 | | // 1st case: Insert at the end |
486 | | // If we insert at a position that does not yet exist, append missing number of components by |
487 | | // adding the corresponding number of backspace chars, append new float value and return. |
488 | 0 | size_t futureVM = pos + 1; |
489 | 0 | if (futureVM > currentVM) |
490 | 0 | { |
491 | 0 | str = str.append(currentVM == 0 ? futureVM - currentVM - 1 : futureVM - currentVM, '\\'); |
492 | 0 | str = str.append(stringVal); |
493 | 0 | return putOFStringArray(str); |
494 | 0 | } |
495 | | |
496 | | // 2nd case: New value should be at position 0 |
497 | 0 | size_t rightPos = 0; |
498 | 0 | if (pos == 0) |
499 | 0 | { |
500 | | // First value is empty: Insert new value |
501 | 0 | if (str[0] == '\\') |
502 | 0 | { |
503 | 0 | str = str.insert(0, stringVal); |
504 | 0 | } |
505 | | // First value is set: Replace old value with new value |
506 | 0 | else |
507 | 0 | { |
508 | 0 | rightPos = str.find_first_of('\\', 0); |
509 | 0 | str = str.replace(0, rightPos, stringVal); |
510 | 0 | } |
511 | 0 | return putOFStringArray(str); |
512 | 0 | } |
513 | | |
514 | | // 3rd case: New value should be inserted somewhere in the middle |
515 | 0 | size_t leftPos = 0; |
516 | 0 | size_t vmPos = 0; |
517 | | // First, find the correct position, and then insert / replace new value |
518 | 0 | do |
519 | 0 | { |
520 | | // Step from value to value by looking for delimiters. |
521 | | // Special handling first search (start looking at position 0 instead of 1) |
522 | 0 | if (vmPos == 0) leftPos = str.find('\\', 0); |
523 | 0 | else leftPos = str.find('\\', leftPos + 1 ); |
524 | | // leftPos = str.find('\\', leftPos == 0 ? 0 : leftPos +1); |
525 | 0 | if (leftPos != OFString_npos) |
526 | 0 | { |
527 | 0 | vmPos++; |
528 | 0 | } |
529 | 0 | } |
530 | 0 | while ( (leftPos != OFString_npos) && (vmPos != pos) ); |
531 | 0 | rightPos = str.find_first_of('\\', leftPos+1); |
532 | 0 | if (rightPos == OFString_npos) rightPos = str.length(); |
533 | | |
534 | | // If we do not have an old value of size 1 or we have an empty value |
535 | 0 | if (rightPos - leftPos == 1) |
536 | 0 | { |
537 | | // Empty value |
538 | 0 | if (str.at(leftPos) == '\\') |
539 | 0 | str = str.insert(rightPos, stringVal); |
540 | | // Old value (length 1) |
541 | 0 | else |
542 | 0 | str = str.replace(leftPos, 1, stringVal); |
543 | 0 | } |
544 | | // Otherwise replace existing old value (length > 1) |
545 | 0 | else |
546 | 0 | { |
547 | 0 | str = str.replace(leftPos+1, rightPos - leftPos - 1, stringVal); |
548 | 0 | } |
549 | | // Finally re-insert all values include new value |
550 | 0 | result = putOFStringArray( str ); |
551 | 0 | } |
552 | 0 | return result; |
553 | 0 | } |
554 | | |
555 | | |
556 | | // ******************************** |
557 | | |
558 | | |
559 | | OFCondition DcmByteString::makeDicomByteString() |
560 | 0 | { |
561 | | /* get string data */ |
562 | 0 | char *value = NULL; |
563 | 0 | errorFlag = getString(value); |
564 | 0 | if (value != NULL) |
565 | 0 | { |
566 | | /* check for odd length */ |
567 | 0 | if (realLength & 1) |
568 | 0 | { |
569 | | /* if so add a padding character */ |
570 | 0 | setLengthField(realLength + 1); |
571 | 0 | value[realLength] = paddingChar; |
572 | 0 | } else if (realLength < getLengthField()) |
573 | 0 | setLengthField(realLength); |
574 | | /* terminate string (removes additional trailing padding characters) */ |
575 | 0 | value[getLengthField()] = '\0'; |
576 | 0 | } |
577 | | /* current string representation is now the DICOM one */ |
578 | 0 | fStringMode = DCM_DicomString; |
579 | 0 | return errorFlag; |
580 | 0 | } |
581 | | |
582 | | |
583 | | OFCondition DcmByteString::makeMachineByteString(const Uint32 length) |
584 | 964 | { |
585 | 964 | errorFlag = EC_Normal; |
586 | | /* get string data */ |
587 | 964 | char *value = OFstatic_cast(char *, getValue()); |
588 | 964 | if (value != NULL) |
589 | 964 | { |
590 | | /* check whether string representation is not the internal one */ |
591 | 964 | if (fStringMode != DCM_MachineString) |
592 | 964 | { |
593 | | /* determine initial string length */ |
594 | 964 | realLength = (length == 0) ? getLengthField() : length; |
595 | | /* remove all trailing spaces if automatic input data correction is enabled */ |
596 | 964 | if (dcmEnableAutomaticInputDataCorrection.get()) |
597 | 964 | { |
598 | | /* |
599 | | ** This code removes extra padding characters at the end of a ByteString. |
600 | | ** Trailing padding can cause problems when comparing strings. This kind |
601 | | ** of padding is non-significant for all string-based value representations. |
602 | | */ |
603 | 964 | if (realLength > 0) |
604 | 964 | { |
605 | 964 | size_t i = OFstatic_cast(size_t, realLength); |
606 | 2.11k | while ((i > 0) && ((value[i - 1] == paddingChar) || (value[i - 1] == '\0'))) |
607 | 1.14k | value[--i] = '\0'; |
608 | 964 | realLength = OFstatic_cast(Uint32, i); |
609 | 964 | } |
610 | 964 | } |
611 | 964 | } |
612 | 964 | } else |
613 | 0 | realLength = 0; |
614 | | /* current string representation is now the internal one */ |
615 | 964 | fStringMode = DCM_MachineString; |
616 | 964 | return errorFlag; |
617 | 964 | } |
618 | | |
619 | | |
620 | | // ******************************** |
621 | | |
622 | | |
623 | | Uint8 *DcmByteString::newValueField() |
624 | 9.17k | { |
625 | 9.17k | Uint8 *value = NULL; |
626 | 9.17k | Uint32 lengthField = getLengthField(); |
627 | | /* check for odd length (in case of a protocol error) */ |
628 | 9.17k | if (lengthField & 1) |
629 | 2.34k | { |
630 | 2.34k | if (lengthField == DCM_UndefinedLength) |
631 | 3 | { |
632 | | /* Print an error message when private attribute states to have an odd length |
633 | | * equal to the maximum length, because we are not able then to make this value even (+1) |
634 | | * which would an overflow on some systems as well as being illegal in DICOM |
635 | | */ |
636 | 3 | DCMDATA_WARN("DcmByteString: Element " << getTagName() << " " << getTag() |
637 | 3 | << " has odd maximum length (" << DCM_UndefinedLength << ") and therefore is not loaded"); |
638 | 3 | errorFlag = EC_CorruptedData; |
639 | 3 | return NULL; |
640 | 3 | } |
641 | | /* allocate space for extra padding character (required for the DICOM representation of the string) */ |
642 | | |
643 | | // we want to use a non-throwing new here if available. |
644 | | // If the allocation fails, we report an EC_MemoryExhausted error |
645 | | // back to the caller. |
646 | 2.34k | value = new (std::nothrow) Uint8[lengthField + 2]; |
647 | | |
648 | | /* terminate string after real length */ |
649 | 2.34k | if (value != NULL) |
650 | 2.29k | { |
651 | 2.29k | value[lengthField] = 0; |
652 | 2.29k | value[lengthField+1] = 0; |
653 | 2.29k | } |
654 | | |
655 | | /* enforce old (pre DCMTK 3.5.2) behaviour? */ |
656 | 2.34k | if (!dcmAcceptOddAttributeLength.get()) |
657 | 0 | { |
658 | | /* make length even */ |
659 | 0 | lengthField++; |
660 | 0 | setLengthField(lengthField); |
661 | 0 | } |
662 | 6.83k | } else { |
663 | | /* length is even, but we need an extra byte for the terminating 0 byte */ |
664 | | |
665 | | // we want to use a non-throwing new here if available. |
666 | | // If the allocation fails, we report an EC_MemoryExhausted error |
667 | | // back to the caller. |
668 | 6.83k | value = new (std::nothrow) Uint8[lengthField + 1]; |
669 | 6.83k | } |
670 | | /* make sure that the string is properly terminated by a 0 byte */ |
671 | 9.17k | if (value != NULL) |
672 | 9.11k | value[lengthField] = 0; |
673 | 58 | else |
674 | 58 | errorFlag = EC_MemoryExhausted; |
675 | 9.17k | return value; |
676 | 9.17k | } |
677 | | |
678 | | |
679 | | // ******************************** |
680 | | |
681 | | |
682 | | void DcmByteString::postLoadValue() |
683 | 9.05k | { |
684 | | /* initially, after loading an attribute the string mode is unknown */ |
685 | 9.05k | fStringMode = DCM_UnknownString; |
686 | | /* correct value length if automatic input data correction is enabled */ |
687 | 9.05k | if (dcmEnableAutomaticInputDataCorrection.get()) |
688 | 9.05k | { |
689 | | /* check for odd length */ |
690 | 9.05k | if (getLengthField() & 1) |
691 | 2.26k | { |
692 | | // newValueField always allocates an even number of bytes and sets |
693 | | // the pad byte to zero, so we can safely increase Length here. |
694 | 2.26k | setLengthField(getLengthField() + 1); |
695 | 2.26k | } |
696 | 9.05k | } |
697 | 9.05k | } |
698 | | |
699 | | |
700 | | // ******************************** |
701 | | |
702 | | |
703 | | OFCondition DcmByteString::verify(const OFBool autocorrect) |
704 | 0 | { |
705 | 0 | char *str = NULL; |
706 | 0 | Uint32 len = 0; |
707 | | /* get string data */ |
708 | 0 | errorFlag = getString(str, len); |
709 | | /* check for non-empty string */ |
710 | 0 | if ((str != NULL) && (len > 0)) |
711 | 0 | { |
712 | | /* check whether there is anything to verify at all */ |
713 | 0 | if (maxLength != DCM_UndefinedLength) |
714 | 0 | { |
715 | 0 | const unsigned long vm = getVM(); |
716 | | /* TODO: is it really a good idea to create a copy of the string? */ |
717 | 0 | OFString value(str, len); |
718 | 0 | size_t posStart = 0; |
719 | 0 | unsigned long vmNum = 0; |
720 | | /* check all string components */ |
721 | 0 | while (posStart != OFString_npos) |
722 | 0 | { |
723 | 0 | ++vmNum; |
724 | | /* search for next component separator */ |
725 | 0 | size_t posEnd = (vm > 1) ? value.find('\\', posStart) : OFString_npos; |
726 | 0 | const size_t fieldLen = (posEnd == OFString_npos) ? value.length() - posStart : posEnd - posStart; |
727 | | /* check size limit for each string component */ |
728 | 0 | if (fieldLen > maxLength) |
729 | 0 | { |
730 | 0 | DCMDATA_DEBUG("DcmByteString::verify() Maximum length violated in element " |
731 | 0 | << getTagName() << " " << getTag() << " value " << vmNum << ": " << fieldLen |
732 | 0 | << " bytes found but only " << maxLength << " bytes allowed"); |
733 | 0 | errorFlag = EC_MaximumLengthViolated; |
734 | 0 | if (autocorrect) |
735 | 0 | { |
736 | 0 | const size_t excess = fieldLen - maxLength; |
737 | 0 | DCMDATA_DEBUG("DcmByteString::verify() Removing " << excess |
738 | 0 | << " bytes from the end of value " << vmNum); |
739 | | /* erase excessive part of the string component */ |
740 | 0 | value.erase(posStart + maxLength, excess); |
741 | | /* correct the position of the end marker by the number of bytes |
742 | | we just cut off, if the end of the string is not already reached */ |
743 | 0 | if (posEnd != OFString_npos) |
744 | 0 | posEnd -= excess; |
745 | 0 | } |
746 | 0 | } |
747 | 0 | posStart = (posEnd == OFString_npos) ? posEnd : posEnd + 1; |
748 | 0 | } |
749 | | /* replace current string value if auto correction is enabled */ |
750 | 0 | if (autocorrect && errorFlag.bad()) |
751 | 0 | { |
752 | 0 | putOFStringArray(value); |
753 | | /* the above method also sets 'errorFlag', so we need to assign the error code again */ |
754 | 0 | errorFlag = EC_MaximumLengthViolated; |
755 | 0 | } |
756 | 0 | } |
757 | 0 | } |
758 | | /* report a debug message if an error occurred */ |
759 | 0 | if (errorFlag.bad()) |
760 | 0 | { |
761 | 0 | DCMDATA_WARN("DcmByteString: One or more illegal values in element " |
762 | 0 | << getTagName() << " " << getTag() << " with VM=" << getVM()); |
763 | 0 | } |
764 | 0 | return errorFlag; |
765 | 0 | } |
766 | | |
767 | | |
768 | | OFBool DcmByteString::containsExtendedCharacters(const OFBool checkAllStrings) |
769 | 0 | { |
770 | 0 | OFBool result = OFFalse; |
771 | | /* only check if parameter is true since derived VRs are not affected |
772 | | by the attribute SpecificCharacterSet (0008,0005) */ |
773 | 0 | if (checkAllStrings) |
774 | 0 | { |
775 | 0 | char *str = NULL; |
776 | 0 | Uint32 len = 0; |
777 | | /* determine length in order to support possibly embedded NULL bytes */ |
778 | 0 | if (getString(str, len).good()) |
779 | 0 | result = containsExtendedCharacters(str, len); |
780 | 0 | } |
781 | 0 | return result; |
782 | 0 | } |
783 | | |
784 | | |
785 | | OFBool DcmByteString::isAffectedBySpecificCharacterSet() const |
786 | 0 | { |
787 | 0 | return OFFalse; |
788 | 0 | } |
789 | | |
790 | | |
791 | | // ******************************** |
792 | | |
793 | | |
794 | | OFBool DcmByteString::isEmpty(const OFBool normalize) |
795 | 0 | { |
796 | 0 | OFBool result = OFFalse; |
797 | 0 | if (normalize && !nonSignificantChars.empty()) |
798 | 0 | { |
799 | 0 | OFString value; |
800 | 0 | DcmByteString::getStringValue(value); |
801 | | /* check whether string value consists of non-significant characters only */ |
802 | 0 | result = (value.find_first_not_of(nonSignificantChars) == OFString_npos); |
803 | 0 | } else |
804 | 0 | result = DcmObject::isEmpty(normalize); |
805 | 0 | return result; |
806 | 0 | } |
807 | | |
808 | | |
809 | | // ******************************** |
810 | | |
811 | | |
812 | | // global function for normalizing a DICOM string |
813 | | void normalizeString(OFString &string, |
814 | | const OFBool multiPart, |
815 | | const OFBool leading, |
816 | | const OFBool trailing, |
817 | | const char paddingChar) |
818 | 0 | { |
819 | | /* check for non-empty string */ |
820 | 0 | if (!string.empty()) |
821 | 0 | { |
822 | 0 | size_t partindex = 0; |
823 | 0 | size_t offset = 0; |
824 | 0 | size_t len = string.length(); |
825 | 0 | while (partindex < len) |
826 | 0 | { |
827 | | // remove leading spaces in every part of the string |
828 | 0 | if (leading) |
829 | 0 | { |
830 | 0 | offset = 0; |
831 | 0 | while ((partindex + offset < len) && (string[partindex + offset] == paddingChar)) |
832 | 0 | offset++; |
833 | 0 | if (offset > 0) |
834 | 0 | string.erase(partindex, offset); |
835 | 0 | } |
836 | 0 | len = string.length(); |
837 | | // compute begin to the next separator index! |
838 | 0 | if (multiPart) |
839 | 0 | { |
840 | 0 | partindex = string.find('\\', partindex); |
841 | 0 | if (partindex == OFString_npos) |
842 | 0 | partindex = len; |
843 | 0 | } else |
844 | 0 | partindex = len; |
845 | | // remove trailing spaces in every part of the string |
846 | 0 | if (trailing && partindex) |
847 | 0 | { |
848 | 0 | offset = partindex - 1; |
849 | 0 | while ((offset > 0) && (string[offset] == paddingChar)) |
850 | 0 | offset--; |
851 | 0 | if (offset != partindex - 1) |
852 | 0 | { |
853 | 0 | if (string[offset] == ' ') |
854 | 0 | { |
855 | 0 | string.erase(offset, partindex - offset); |
856 | 0 | partindex = offset; |
857 | 0 | } else { |
858 | 0 | string.erase(offset + 1, partindex - offset - 1); |
859 | 0 | partindex = offset + 1; |
860 | 0 | } |
861 | 0 | } |
862 | 0 | } |
863 | 0 | len = string.length(); |
864 | 0 | if (partindex != len) |
865 | 0 | ++partindex; |
866 | 0 | } |
867 | 0 | } |
868 | 0 | } |
869 | | |
870 | | |
871 | | // ******************************** |
872 | | |
873 | | |
874 | | OFBool DcmByteString::containsExtendedCharacters(const char *stringVal, |
875 | | const size_t stringLen) |
876 | 0 | { |
877 | 0 | if (stringVal != NULL) |
878 | 0 | { |
879 | 0 | for (size_t i = stringLen; i != 0; --i) |
880 | 0 | { |
881 | | /* check for 8 bit characters */ |
882 | 0 | if (OFstatic_cast(unsigned char, *stringVal++) > 127) |
883 | 0 | return OFTrue; |
884 | 0 | } |
885 | 0 | } |
886 | 0 | return OFFalse; |
887 | 0 | } |
888 | | |
889 | | |
890 | | // ******************************** |
891 | | |
892 | | |
893 | | OFCondition DcmByteString::checkStringValue(const OFString &value, |
894 | | const OFString &vm, |
895 | | const OFString &vr, |
896 | | const int vrID, |
897 | | const size_t maxLen, |
898 | | const OFString &charset) |
899 | 0 | { |
900 | 0 | OFCondition result = EC_Normal; |
901 | 0 | const size_t valLen = value.length(); |
902 | 0 | if (valLen > 0) |
903 | 0 | { |
904 | | /* do we need to search for value components at all? */ |
905 | 0 | if (vm.empty()) |
906 | 0 | { |
907 | | /* check value length (if a maximum is specified) */ |
908 | 0 | if ((maxLen > 0) && (value.length() > maxLen)) |
909 | 0 | result = EC_MaximumLengthViolated; |
910 | 0 | else if (dcmEnableVRCheckerForStringValues.get()) |
911 | 0 | { |
912 | | /* check for non-ASCII characters (if default character set used) */ |
913 | 0 | if (charset.empty() || (charset == "ISO_IR 6")) |
914 | 0 | { |
915 | 0 | if (containsExtendedCharacters(value.c_str(), value.length())) |
916 | 0 | result = EC_InvalidCharacter; |
917 | 0 | } |
918 | 0 | if (result.good()) |
919 | 0 | { |
920 | | /* currently, the VR checker only supports ASCII and Latin-1 */ |
921 | 0 | if (charset.empty() || (charset == "ISO_IR 6") || (charset == "ISO_IR 100")) |
922 | 0 | { |
923 | | /* check value representation (VR) */ |
924 | 0 | if (DcmElement::scanValue(value, vr) != vrID) |
925 | 0 | result = EC_ValueRepresentationViolated; |
926 | 0 | } |
927 | 0 | } |
928 | 0 | } |
929 | 0 | } else { |
930 | 0 | size_t posStart = 0; |
931 | 0 | unsigned long vmNum = 0; |
932 | | /* iterate over all value components */ |
933 | 0 | while (posStart != OFString_npos) |
934 | 0 | { |
935 | 0 | ++vmNum; |
936 | | /* search for next component separator */ |
937 | 0 | const size_t posEnd = value.find('\\', posStart); |
938 | 0 | const size_t length = (posEnd == OFString_npos) ? valLen - posStart : posEnd - posStart; |
939 | | /* check length of current value component */ |
940 | 0 | if ((maxLen > 0) && (length > maxLen)) |
941 | 0 | { |
942 | 0 | result = EC_MaximumLengthViolated; |
943 | 0 | break; |
944 | 0 | } |
945 | 0 | else if (dcmEnableVRCheckerForStringValues.get()) |
946 | 0 | { |
947 | | /* check for non-ASCII characters (if default character set used) */ |
948 | 0 | if (charset.empty() || (charset == "ISO_IR 6")) |
949 | 0 | { |
950 | 0 | if (containsExtendedCharacters(value.c_str() + posStart, length)) |
951 | 0 | { |
952 | 0 | result = EC_InvalidCharacter; |
953 | 0 | break; |
954 | 0 | } |
955 | 0 | } |
956 | | /* currently, the VR checker only supports ASCII and Latin-1 */ |
957 | 0 | if (charset.empty() || (charset == "ISO_IR 6") || (charset == "ISO_IR 100")) |
958 | 0 | { |
959 | | /* check value representation (VR) */ |
960 | 0 | if (DcmElement::scanValue(value, vr, posStart, length) != vrID) |
961 | 0 | { |
962 | 0 | result = EC_ValueRepresentationViolated; |
963 | 0 | break; |
964 | 0 | } |
965 | 0 | } |
966 | 0 | } |
967 | 0 | posStart = (posEnd == OFString_npos) ? posEnd : posEnd + 1; |
968 | 0 | } |
969 | 0 | if (result.good()) |
970 | 0 | { |
971 | | /* check value multiplicity (VM) */ |
972 | 0 | result = DcmElement::checkVM(vmNum, vm); |
973 | 0 | } |
974 | 0 | } |
975 | 0 | } |
976 | 0 | return result; |
977 | 0 | } |
978 | | |
979 | | |
980 | | // ******************************** |
981 | | |
982 | | |
983 | | OFCondition DcmByteString::writeJson(STD_NAMESPACE ostream &out, |
984 | | DcmJsonFormat &format) |
985 | 0 | { |
986 | | /* always write JSON Opener */ |
987 | 0 | DcmElement::writeJsonOpener(out, format); |
988 | | /* write element value (if non-empty) */ |
989 | 0 | if (!isEmpty()) |
990 | 0 | { |
991 | 0 | OFString value; |
992 | 0 | OFCondition status = getOFString(value, 0L); |
993 | 0 | if (status.bad()) |
994 | 0 | return status; |
995 | 0 | format.printValuePrefix(out); |
996 | 0 | DcmJsonFormat::printValueString(out, value); |
997 | 0 | const unsigned long vm = getVM(); |
998 | 0 | for (unsigned long valNo = 1; valNo < vm; ++valNo) |
999 | 0 | { |
1000 | 0 | status = getOFString(value, valNo); |
1001 | 0 | if (status.bad()) |
1002 | 0 | return status; |
1003 | 0 | format.printNextArrayElementPrefix(out); |
1004 | 0 | DcmJsonFormat::printValueString(out, value); |
1005 | 0 | } |
1006 | 0 | format.printValueSuffix(out); |
1007 | 0 | } |
1008 | | /* write JSON Closer */ |
1009 | 0 | DcmElement::writeJsonCloser(out, format); |
1010 | | /* always report success */ |
1011 | 0 | return EC_Normal; |
1012 | 0 | } |
1013 | | |
1014 | | |
1015 | | OFBool DcmByteString::matches(const DcmElement& candidate, |
1016 | | const OFBool enableWildCardMatching) const |
1017 | 0 | { |
1018 | 0 | if (ident() == candidate.ident()) |
1019 | 0 | { |
1020 | | // some const casts to call the getter functions, I do not modify the values, I promise! |
1021 | 0 | DcmByteString& key = OFconst_cast(DcmByteString&,*this); |
1022 | 0 | DcmElement& can = OFconst_cast(DcmElement&,candidate); |
1023 | 0 | OFString a, b; |
1024 | 0 | for (unsigned long ui = 0; ui < key.getVM(); ++ui) |
1025 | 0 | for (unsigned long uj = 0; uj < can.getVM(); ++uj) |
1026 | 0 | if( key.getOFString( a, ui, OFTrue ).good() && can.getOFString( b, uj, OFTrue ).good() && matches( a, b, enableWildCardMatching ) ) |
1027 | 0 | return OFTrue; |
1028 | 0 | return key.getVM() == 0; |
1029 | 0 | } |
1030 | 0 | return OFFalse; |
1031 | 0 | } |
1032 | | |
1033 | | |
1034 | | OFBool DcmByteString::matches(const OFString& key, |
1035 | | const OFString& candidate, |
1036 | | const OFBool enableWildCardMatching) const |
1037 | 0 | { |
1038 | 0 | OFstatic_cast(void,enableWildCardMatching); |
1039 | | // Universal Matching || Single Value Matching |
1040 | 0 | return key.empty() || key == candidate; |
1041 | 0 | } |