1 /*
2 *
3 * Copyright (C) 2000-2019, 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: dcmsr
15 *
16 * Author: Joerg Riesmeier
17 *
18 * Purpose:
19 * classes: DSRDocumentTreeNode
20 *
21 */
22
23
24 #include "dcmtk/config/osconfig.h" /* make sure OS specific configuration is included first */
25
26 #include "dcmtk/dcmsr/dsrdoctn.h"
27 #include "dcmtk/dcmsr/dsrdncsr.h"
28 #include "dcmtk/dcmsr/dsrdtitn.h"
29 #include "dcmtk/dcmsr/dsrxmld.h"
30 #include "dcmtk/dcmsr/dsriodcc.h"
31
32 #include "dcmtk/dcmdata/dcdeftag.h"
33 #include "dcmtk/dcmdata/dcuid.h"
34 #include "dcmtk/dcmdata/dcvrcs.h"
35 #include "dcmtk/dcmdata/dcvrdt.h"
36 #include "dcmtk/dcmdata/dcvrui.h"
37 #include "dcmtk/dcmdata/dcvrul.h"
38
39
DSRDocumentTreeNode(const E_RelationshipType relationshipType,const E_ValueType valueType)40 DSRDocumentTreeNode::DSRDocumentTreeNode(const E_RelationshipType relationshipType,
41 const E_ValueType valueType)
42 : DSRTreeNode(),
43 MarkFlag(OFFalse),
44 ReferenceTarget(OFFalse),
45 RelationshipType(relationshipType),
46 ValueType(valueType),
47 ConceptName(),
48 ObservationDateTime(),
49 ObservationUID(),
50 TemplateIdentifier(),
51 MappingResource(),
52 MappingResourceUID(),
53 MACParameters(DCM_MACParametersSequence),
54 DigitalSignatures(DCM_DigitalSignaturesSequence)
55 {
56 }
57
58
DSRDocumentTreeNode(const DSRDocumentTreeNode & node)59 DSRDocumentTreeNode::DSRDocumentTreeNode(const DSRDocumentTreeNode &node)
60 : DSRTreeNode(node.Annotation),
61 MarkFlag(node.MarkFlag),
62 ReferenceTarget(OFFalse),
63 RelationshipType(node.RelationshipType),
64 ValueType(node.ValueType),
65 ConceptName(node.ConceptName),
66 ObservationDateTime(node.ObservationDateTime),
67 ObservationUID(node.ObservationUID),
68 TemplateIdentifier(node.TemplateIdentifier),
69 MappingResource(node.MappingResource),
70 MappingResourceUID(node.MappingResourceUID),
71 MACParameters(DCM_MACParametersSequence),
72 DigitalSignatures(DCM_DigitalSignaturesSequence)
73 {
74 }
75
76
~DSRDocumentTreeNode()77 DSRDocumentTreeNode::~DSRDocumentTreeNode()
78 {
79 }
80
81
operator ==(const DSRDocumentTreeNode & node) const82 OFBool DSRDocumentTreeNode::operator==(const DSRDocumentTreeNode &node) const
83 {
84 /* only very basic information is used for comparing the two nodes */
85 return (RelationshipType == node.RelationshipType) &&
86 (ValueType == node.ValueType) &&
87 (ConceptName == node.ConceptName);
88 }
89
90
operator !=(const DSRDocumentTreeNode & node) const91 OFBool DSRDocumentTreeNode::operator!=(const DSRDocumentTreeNode &node) const
92 {
93 /* only very basic information is used for comparing the two nodes */
94 return (RelationshipType != node.RelationshipType) ||
95 (ValueType != node.ValueType) ||
96 (ConceptName != node.ConceptName);
97 }
98
99
clear()100 void DSRDocumentTreeNode::clear()
101 {
102 MarkFlag = OFFalse;
103 ReferenceTarget = OFFalse;
104 ConceptName.clear();
105 ObservationDateTime.clear();
106 ObservationUID.clear();
107 TemplateIdentifier.clear();
108 MappingResource.clear();
109 MappingResourceUID.clear();
110 MACParameters.clear();
111 DigitalSignatures.clear();
112 }
113
114
isValid() const115 OFBool DSRDocumentTreeNode::isValid() const
116 {
117 return (RelationshipType != RT_invalid) && (ValueType != VT_invalid);
118 }
119
120
hasValidValue() const121 OFBool DSRDocumentTreeNode::hasValidValue() const
122 {
123 return OFTrue;
124 }
125
126
isShort(const size_t) const127 OFBool DSRDocumentTreeNode::isShort(const size_t /*flags*/) const
128 {
129 return OFTrue;
130 }
131
132
hasTemplateIdentification() const133 OFBool DSRDocumentTreeNode::hasTemplateIdentification() const
134 {
135 /* mapping resource UID is optional, so do not check it */
136 return !TemplateIdentifier.empty() && !MappingResource.empty();
137 }
138
139
print(STD_NAMESPACE ostream & stream,const size_t flags) const140 OFCondition DSRDocumentTreeNode::print(STD_NAMESPACE ostream &stream,
141 const size_t flags) const
142 {
143 if (RelationshipType != RT_isRoot)
144 {
145 DCMSR_PRINT_ANSI_ESCAPE_CODE(DCMSR_ANSI_ESCAPE_CODE_RELATIONSHIP_TYPE)
146 stream << relationshipTypeToReadableName(RelationshipType) << " ";
147 }
148 DCMSR_PRINT_ANSI_ESCAPE_CODE(DCMSR_ANSI_ESCAPE_CODE_VALUE_TYPE)
149 stream << valueTypeToDefinedTerm(ValueType);
150 DCMSR_PRINT_ANSI_ESCAPE_CODE(DCMSR_ANSI_ESCAPE_CODE_DELIMITER)
151 stream << ":";
152 /* only print valid concept name codes */
153 if (ConceptName.isValid() || (flags & PF_printInvalidCodes))
154 {
155 DCMSR_PRINT_ANSI_ESCAPE_CODE(DCMSR_ANSI_ESCAPE_CODE_CONCEPT_NAME)
156 ConceptName.print(stream, (flags & PF_printConceptNameCodes) > 0 /*printCodeValue*/, flags);
157 }
158 return EC_Normal;
159 }
160
161
printExtended(STD_NAMESPACE ostream & stream,const size_t flags) const162 OFCondition DSRDocumentTreeNode::printExtended(STD_NAMESPACE ostream &stream,
163 const size_t flags) const
164 {
165 /* print observation date/time (optional) */
166 if (!ObservationDateTime.empty())
167 {
168 OFString tmpString;
169 DCMSR_PRINT_ANSI_ESCAPE_CODE(DCMSR_ANSI_ESCAPE_CODE_DELIMITER)
170 stream << " {" << dicomToReadableDateTime(ObservationDateTime, tmpString) << "}";
171 }
172 /* print annotation (optional) */
173 if (hasAnnotation() && (flags & PF_printAnnotation))
174 {
175 DCMSR_PRINT_ANSI_ESCAPE_CODE(DCMSR_ANSI_ESCAPE_CODE_ANNOTATION)
176 stream << " \"" << getAnnotation().getText() << "\"";
177 }
178 /* print template identification (conditional) */
179 if (hasTemplateIdentification() && (flags & PF_printTemplateIdentification))
180 {
181 DCMSR_PRINT_ANSI_ESCAPE_CODE(DCMSR_ANSI_ESCAPE_CODE_DELIMITER)
182 stream << " # ";
183 DCMSR_PRINT_ANSI_ESCAPE_CODE(DCMSR_ANSI_ESCAPE_CODE_TEMPLATE_ID)
184 stream << "TID " << TemplateIdentifier;
185 stream << " (" << MappingResource;
186 if (!MappingResourceUID.empty())
187 stream << ", " << MappingResourceUID;
188 stream << ")";
189 }
190 return EC_Normal;
191 }
192
193
read(DcmItem & dataset,const DSRIODConstraintChecker * constraintChecker,const size_t flags)194 OFCondition DSRDocumentTreeNode::read(DcmItem &dataset,
195 const DSRIODConstraintChecker *constraintChecker,
196 const size_t flags)
197 {
198 return readSRDocumentContentModule(dataset, constraintChecker, flags);
199 }
200
201
write(DcmItem & dataset,DcmStack * markedItems)202 OFCondition DSRDocumentTreeNode::write(DcmItem &dataset,
203 DcmStack *markedItems)
204 {
205 return writeSRDocumentContentModule(dataset, markedItems);
206 }
207
208
readXML(const DSRXMLDocument & doc,DSRXMLCursor cursor,const E_DocumentType documentType,const size_t flags)209 OFCondition DSRDocumentTreeNode::readXML(const DSRXMLDocument &doc,
210 DSRXMLCursor cursor,
211 const E_DocumentType documentType,
212 const size_t flags)
213 {
214 OFCondition result = SR_EC_InvalidDocument;
215 if (cursor.valid())
216 {
217 OFString idAttr;
218 OFString mappingResource;
219 OFString mappingResourceUID;
220 OFString templateIdentifier;
221 /* important: NULL indicates first child node */
222 DSRDocumentTreeNode *node = NULL;
223 /* read "id" attribute (optional) and compare with expected value */
224 if (!doc.getStringFromAttribute(cursor, idAttr, "id", OFFalse /*encoding*/, OFFalse /*required*/).empty() &&
225 (stringToNumber(idAttr.c_str()) != getNodeID()))
226 {
227 /* create warning message */
228 DCMSR_WARN("XML attribute 'id' (" << idAttr << ") deviates from current node number (" << getNodeID() << ")");
229 }
230 /* template identification information expected "inside" content item */
231 if (!(flags & XF_templateElementEnclosesItems))
232 {
233 /* check for optional template identification */
234 const DSRXMLCursor childCursor = doc.getNamedChildNode(cursor, "template", OFFalse /*required*/);
235 if (childCursor.valid())
236 {
237 /* check whether information is stored as XML attributes */
238 if (doc.hasAttribute(childCursor, "tid"))
239 {
240 doc.getStringFromAttribute(childCursor, mappingResource, "resource");
241 doc.getStringFromAttribute(childCursor, mappingResourceUID, "uid", OFFalse /*encoding*/, OFFalse /*required*/);
242 doc.getStringFromAttribute(childCursor, templateIdentifier, "tid");
243 } else {
244 const DSRXMLCursor resourceCursor = doc.getNamedChildNode(childCursor, "resource");
245 if (resourceCursor.valid())
246 {
247 doc.getStringFromAttribute(resourceCursor, mappingResourceUID, "uid", OFFalse /*encoding*/, OFFalse /*required*/);
248 doc.getStringFromNodeContent(resourceCursor, mappingResource);
249 }
250 doc.getStringFromNodeContent(doc.getNamedChildNode(childCursor, "id"), templateIdentifier);
251 }
252 if (setTemplateIdentification(templateIdentifier, mappingResource, mappingResourceUID).bad())
253 DCMSR_WARN("Content item has invalid/incomplete template identification");
254 }
255 }
256 /* read concept name (not required in some cases) */
257 ConceptName.readXML(doc, doc.getNamedChildNode(cursor, "concept", OFFalse /*required*/), flags);
258 /* read observation UID and date/time (optional) */
259 const DSRXMLCursor childCursor = doc.getNamedChildNode(cursor, "observation", OFFalse /*required*/);
260 if (childCursor.valid())
261 {
262 doc.getStringFromAttribute(childCursor, ObservationUID, "uid", OFFalse /*encoding*/, OFFalse /*required*/);
263 DSRDateTimeTreeNode::getValueFromXMLNodeContent(doc, doc.getNamedChildNode(childCursor, "datetime", OFFalse /*required*/), ObservationDateTime);
264 }
265 /* read node content (depends on value type) */
266 result = readXMLContentItem(doc, cursor, flags);
267 /* goto first child node */
268 cursor.gotoChild();
269 /* iterate over all child content items */
270 while (cursor.valid() && result.good())
271 {
272 /* template identification information expected "outside" content item */
273 if (flags & XF_templateElementEnclosesItems)
274 {
275 /* check for optional template identification */
276 if (doc.matchNode(cursor, "template"))
277 {
278 doc.getStringFromAttribute(cursor, mappingResource, "resource");
279 doc.getStringFromAttribute(cursor, mappingResourceUID, "uid", OFFalse /*encoding*/, OFFalse /*required*/);
280 doc.getStringFromAttribute(cursor, templateIdentifier, "tid");
281 /* goto first child of the "template" element */
282 cursor.gotoChild();
283 }
284 }
285 /* get SR value type from current XML node, also supports "by-reference" detection */
286 E_ValueType valueType = doc.getValueTypeFromNode(cursor);
287 /* invalid types are silently ignored */
288 if (valueType != VT_invalid)
289 {
290 /* get SR relationship type */
291 E_RelationshipType relationshipType = doc.getRelationshipTypeFromNode(cursor);
292 /* create new node (by-value or by-reference), do not check constraints */
293 result = createAndAppendNewNode(node, relationshipType, valueType);
294 if (result.good())
295 {
296 if ((flags & XF_templateElementEnclosesItems) && (valueType != VT_byReference))
297 {
298 /* set template identification (if any) */
299 if (node->setTemplateIdentification(templateIdentifier, mappingResource, mappingResourceUID).bad())
300 DCMSR_WARN("Content item has invalid/incomplete template identification");
301 }
302 /* proceed with reading child nodes */
303 result = node->readXML(doc, cursor, documentType, flags);
304 /* print node error message (if any) */
305 doc.printGeneralNodeError(cursor, result);
306 } else {
307 /* create new node failed */
308 DCMSR_ERROR("Cannot add \"" << relationshipTypeToReadableName(relationshipType) << " "
309 << valueTypeToDefinedTerm(valueType /*target item*/) << "\" to "
310 << valueTypeToDefinedTerm(ValueType /*source item*/) << " in "
311 << documentTypeToReadableName(documentType));
312 }
313 }
314 /* proceed with next node */
315 cursor.gotoNext();
316 }
317 }
318 return result;
319 }
320
321
readXMLContentItem(const DSRXMLDocument &,DSRXMLCursor,const size_t)322 OFCondition DSRDocumentTreeNode::readXMLContentItem(const DSRXMLDocument & /*doc*/,
323 DSRXMLCursor /*cursor*/,
324 const size_t /*flags*/)
325 {
326 return EC_IllegalCall;
327 }
328
329
writeXML(STD_NAMESPACE ostream & stream,const size_t flags) const330 OFCondition DSRDocumentTreeNode::writeXML(STD_NAMESPACE ostream &stream,
331 const size_t flags) const
332 {
333 OFCondition result = EC_Normal;
334 /* check for validity */
335 if (!isValid())
336 printInvalidContentItemMessage("Writing to XML", this);
337 /* write optional template identification */
338 if ((flags & XF_writeTemplateIdentification) && !(flags & XF_templateElementEnclosesItems))
339 {
340 if (hasTemplateIdentification())
341 {
342 if (flags & XF_templateIdentifierAsAttribute)
343 {
344 stream << "<template resource=\"" << MappingResource << "\"";
345 if (!MappingResourceUID.empty())
346 stream << " uid=\"" << MappingResourceUID << "\"";
347 stream << " tid=\"" << TemplateIdentifier << "\"/>" << OFendl;
348 } else {
349 stream << "<template>" << OFendl;
350 stream << "<resource";
351 if (!MappingResourceUID.empty())
352 stream << " uid=\"" << MappingResourceUID << "\"";
353 stream << ">" << MappingResource << "</resource>" << OFendl;
354 writeStringValueToXML(stream, TemplateIdentifier, "id");
355 stream << "</template>" << OFendl;
356 }
357 }
358 }
359 /* relationship type */
360 if ((RelationshipType != RT_isRoot) && !(flags & XF_relationshipTypeAsAttribute))
361 writeStringValueToXML(stream, relationshipTypeToDefinedTerm(RelationshipType), "relationship", (flags & XF_writeEmptyTags) > 0);
362 /* concept name */
363 if (ConceptName.isValid())
364 {
365 if (flags & XF_codeComponentsAsAttribute)
366 stream << "<concept"; // bracket ">" is closed in the next writeXML() routine
367 else
368 stream << "<concept>" << OFendl;
369 ConceptName.writeXML(stream, flags);
370 stream << "</concept>" << OFendl;
371 }
372 if (!(ObservationDateTime.empty() && ObservationUID.empty()))
373 {
374 /* observation UID (optional) */
375 OFString tmpString;
376 stream << "<observation";
377 if (!ObservationUID.empty())
378 stream << " uid=\"" << ObservationUID << "\"";
379 stream << ">" << OFendl;
380 /* observation date/time (optional) */
381 if (!ObservationDateTime.empty())
382 {
383 /* output time in ISO 8601 format */
384 DcmDateTime::getISOFormattedDateTimeFromString(ObservationDateTime, tmpString, OFTrue /*seconds*/, OFFalse /*fraction*/,
385 OFTrue /*timeZone*/, OFFalse /*createMissingPart*/, "T" /*dateTimeSeparator*/, "" /*timeZoneSeparator*/);
386 writeStringValueToXML(stream, tmpString, "datetime");
387 }
388 stream << "</observation>" << OFendl;
389 }
390 /* write child nodes (if any) */
391 DSRDocumentTreeNodeCursor cursor(getDown());
392 if (cursor.isValid())
393 {
394 /* for all child nodes */
395 do {
396 result = cursor.getNode()->writeXML(stream, flags);
397 } while (result.good() && cursor.gotoNext());
398 }
399 return result;
400 }
401
402
writeXMLItemStart(STD_NAMESPACE ostream & stream,const size_t flags,const OFBool closingBracket) const403 void DSRDocumentTreeNode::writeXMLItemStart(STD_NAMESPACE ostream &stream,
404 const size_t flags,
405 const OFBool closingBracket) const
406 {
407 /* write optional template identification */
408 if ((flags & XF_writeTemplateIdentification) && (flags & XF_templateElementEnclosesItems))
409 {
410 if (hasTemplateIdentification())
411 {
412 stream << "<template resource=\"" << MappingResource << "\"";
413 if (!MappingResourceUID.empty())
414 stream << " uid=\"" << MappingResourceUID << "\"";
415 stream << " tid=\"" << TemplateIdentifier << "\">" << OFendl;
416 }
417 }
418 /* write content item */
419 if (flags & XF_valueTypeAsAttribute)
420 {
421 stream << "<item";
422 if (ValueType != VT_byReference)
423 stream << " valType=\"" << valueTypeToDefinedTerm(ValueType) << "\"";
424 } else
425 stream << "<" << valueTypeToXMLTagName(ValueType);
426 if ((RelationshipType != RT_isRoot) && (flags & XF_relationshipTypeAsAttribute))
427 stream << " relType=\"" << relationshipTypeToDefinedTerm(RelationshipType) << "\"";
428 if (ReferenceTarget || (flags & XF_alwaysWriteItemIdentifier))
429 stream << " id=\"" << getNodeID() << "\"";
430 if (closingBracket)
431 stream << ">" << OFendl;
432 }
433
434
writeXMLItemEnd(STD_NAMESPACE ostream & stream,const size_t flags) const435 void DSRDocumentTreeNode::writeXMLItemEnd(STD_NAMESPACE ostream &stream,
436 const size_t flags) const
437 {
438 /* close content item */
439 if (flags & XF_valueTypeAsAttribute)
440 stream << "</item>" << OFendl;
441 else
442 stream << "</" << valueTypeToXMLTagName(ValueType) << ">" << OFendl;
443 /* close optional template identification */
444 if ((flags & XF_writeTemplateIdentification) && (flags & XF_templateElementEnclosesItems))
445 {
446 if (hasTemplateIdentification())
447 stream << "</template>" << OFendl;
448 }
449 }
450
451
renderHTML(STD_NAMESPACE ostream & docStream,STD_NAMESPACE ostream & annexStream,const size_t nestingLevel,size_t & annexNumber,const size_t flags) const452 OFCondition DSRDocumentTreeNode::renderHTML(STD_NAMESPACE ostream &docStream,
453 STD_NAMESPACE ostream &annexStream,
454 const size_t nestingLevel,
455 size_t &annexNumber,
456 const size_t flags) const
457 {
458 /* check for validity */
459 if (!isValid())
460 printInvalidContentItemMessage("Rendering", this);
461 /* declare hyperlink target */
462 if (ReferenceTarget)
463 {
464 const char *attrName = (flags & DSRTypes::HF_XHTML11Compatibility) ? "id" : "name";
465 const char *closeElm = (flags & DSRTypes::HF_XHTML11Compatibility) ? " /" : "></a";
466 docStream << "<a " << attrName << "=\"content_item_" << getNodeID() << "\"" << closeElm << ">" << OFendl;
467 }
468 /* render content item */
469 OFCondition result = renderHTMLContentItem(docStream, annexStream, nestingLevel, annexNumber, flags);
470 /* render child nodes */
471 if (result.good())
472 result = renderHTMLChildNodes(docStream, annexStream, nestingLevel, annexNumber, flags | HF_renderItemsSeparately);
473 else
474 printContentItemErrorMessage("Rendering", result, this);
475 return result;
476 }
477
478
setRelationshipType(const E_RelationshipType relationshipType)479 OFCondition DSRDocumentTreeNode::setRelationshipType(const E_RelationshipType relationshipType)
480 {
481 OFCondition result = EC_Normal;
482 /* check parameter before setting the value, "RT_isRoot" is allowed! */
483 if ((relationshipType != RT_invalid) && (relationshipType != RT_unknown))
484 {
485 /* only "unknown" relationship types can be replaced */
486 if (RelationshipType == RT_unknown)
487 RelationshipType = relationshipType;
488 else
489 result = SR_EC_CannotChangeRelationshipType;
490 } else
491 result = EC_IllegalParameter;
492 return result;
493 }
494
495
getConceptName(DSRCodedEntryValue & conceptName) const496 OFCondition DSRDocumentTreeNode::getConceptName(DSRCodedEntryValue &conceptName) const
497 {
498 conceptName = ConceptName;
499 return EC_Normal;
500 }
501
502
setConceptName(const DSRCodedEntryValue & conceptName,const OFBool check)503 OFCondition DSRDocumentTreeNode::setConceptName(const DSRCodedEntryValue &conceptName,
504 const OFBool check)
505 {
506 OFCondition result = EC_Normal;
507 /* check for valid code (if not disabled) */
508 if (check && !conceptName.isEmpty())
509 result = conceptName.checkCurrentValue();
510 if (result.good())
511 ConceptName = conceptName;
512 return result;
513 }
514
515
setObservationDateTime(const OFString & observationDateTime,const OFBool check)516 OFCondition DSRDocumentTreeNode::setObservationDateTime(const OFString &observationDateTime,
517 const OFBool check)
518 {
519 OFCondition result = (check) ? DcmDateTime::checkStringValue(observationDateTime, "1") : EC_Normal;
520 if (result.good())
521 ObservationDateTime = observationDateTime;
522 return result;
523 }
524
525
setObservationDateTime(const DcmElement & delem,const unsigned long pos,const OFBool check)526 OFCondition DSRDocumentTreeNode::setObservationDateTime(const DcmElement &delem,
527 const unsigned long pos,
528 const OFBool check)
529 {
530 OFString observationDateTime;
531 /* first, get the value from the element (need to cast away "const") */
532 OFCondition result = OFconst_cast(DcmElement &, delem).getOFString(observationDateTime, pos);
533 if (result.good())
534 {
535 /* then, check and set the value */
536 result = setObservationDateTime(observationDateTime, check);
537 }
538 return result;
539 }
540
541
setObservationDateTime(DcmItem & dataset,const DcmTagKey & tagKey,const unsigned long pos,const OFBool check)542 OFCondition DSRDocumentTreeNode::setObservationDateTime(DcmItem &dataset,
543 const DcmTagKey &tagKey,
544 const unsigned long pos,
545 const OFBool check)
546 {
547 OFString observationDateTime;
548 /* first, get the element value from the dataset */
549 OFCondition result = getStringValueFromDataset(dataset, tagKey, observationDateTime, pos);
550 if (result.good())
551 {
552 /* then, check and set the value */
553 result = setObservationDateTime(observationDateTime, check);
554 }
555 return result;
556 }
557
558
setObservationUID(const OFString & observationUID,const OFBool check)559 OFCondition DSRDocumentTreeNode::setObservationUID(const OFString &observationUID,
560 const OFBool check)
561 {
562 OFCondition result = (check) ? DcmUniqueIdentifier::checkStringValue(observationUID, "1") : EC_Normal;
563 if (result.good())
564 ObservationUID = observationUID;
565 return result;
566 }
567
568
compareTemplateIdentification(const OFString & templateIdentifier,const OFString & mappingResource,const OFString & mappingResourceUID) const569 OFBool DSRDocumentTreeNode::compareTemplateIdentification(const OFString &templateIdentifier,
570 const OFString &mappingResource,
571 const OFString &mappingResourceUID) const
572 {
573 OFBool result = (TemplateIdentifier == templateIdentifier) && (MappingResource == mappingResource);
574 /* mapping resource UID is optional, so only check it if present */
575 if (result && !MappingResourceUID.empty() && !mappingResourceUID.empty())
576 result = (MappingResourceUID == mappingResourceUID);
577 return result;
578 }
579
580
getTemplateIdentification(OFString & templateIdentifier,OFString & mappingResource) const581 OFCondition DSRDocumentTreeNode::getTemplateIdentification(OFString &templateIdentifier,
582 OFString &mappingResource) const
583 {
584 OFCondition result = SR_EC_InvalidValue;
585 /* check for valid value pair */
586 if (checkTemplateIdentification(TemplateIdentifier, MappingResource, "" /*mappingResourceUID*/))
587 {
588 templateIdentifier = TemplateIdentifier;
589 mappingResource = MappingResource;
590 result = EC_Normal;
591 }
592 return result;
593 }
594
595
getTemplateIdentification(OFString & templateIdentifier,OFString & mappingResource,OFString & mappingResourceUID) const596 OFCondition DSRDocumentTreeNode::getTemplateIdentification(OFString &templateIdentifier,
597 OFString &mappingResource,
598 OFString &mappingResourceUID) const
599 {
600 OFCondition result = SR_EC_InvalidValue;
601 /* check for valid pair/triple */
602 if (checkTemplateIdentification(TemplateIdentifier, MappingResource, MappingResourceUID))
603 {
604 templateIdentifier = TemplateIdentifier;
605 mappingResource = MappingResource;
606 mappingResourceUID = MappingResourceUID;
607 result = EC_Normal;
608 }
609 return result;
610 }
611
612
setTemplateIdentification(const OFString & templateIdentifier,const OFString & mappingResource,const OFString & mappingResourceUID,const OFBool check)613 OFCondition DSRDocumentTreeNode::setTemplateIdentification(const OFString &templateIdentifier,
614 const OFString &mappingResource,
615 const OFString &mappingResourceUID,
616 const OFBool check)
617 {
618 OFCondition result = EC_Normal;
619 /* basic check for validity (empty or non-empty) */
620 if (!checkTemplateIdentification(templateIdentifier, mappingResource, mappingResourceUID))
621 result = EC_IllegalParameter;
622 /* check more thoroughly whether the passed values are valid */
623 else if (check)
624 {
625 result = DcmCodeString::checkStringValue(templateIdentifier, "1");
626 if (result.good())
627 result = DcmCodeString::checkStringValue(mappingResource, "1");
628 if (result.good())
629 result = DcmUniqueIdentifier::checkStringValue(mappingResourceUID, "1");
630 }
631 if (result.good())
632 {
633 if ((ValueType != VT_Container) && (ValueType != VT_includedTemplate) && !templateIdentifier.empty())
634 DCMSR_WARN("Template identification should only be specified for CONTAINER content items");
635 /* set current values, might be empty */
636 TemplateIdentifier = templateIdentifier;
637 MappingResource = mappingResource;
638 MappingResourceUID = mappingResourceUID;
639 }
640 return result;
641 }
642
643
removeSignatures()644 void DSRDocumentTreeNode::removeSignatures()
645 {
646 MACParameters.clear();
647 DigitalSignatures.clear();
648 }
649
650
readContentItem(DcmItem &,const size_t)651 OFCondition DSRDocumentTreeNode::readContentItem(DcmItem & /*dataset*/,
652 const size_t /*flags*/)
653 {
654 /* no content to read */
655 return EC_Normal;
656 }
657
658
writeContentItem(DcmItem &) const659 OFCondition DSRDocumentTreeNode::writeContentItem(DcmItem & /*dataset*/) const
660 {
661 /* no content to write */
662 return EC_Normal;
663 }
664
665
renderHTMLContentItem(STD_NAMESPACE ostream &,STD_NAMESPACE ostream &,const size_t,size_t &,const size_t) const666 OFCondition DSRDocumentTreeNode::renderHTMLContentItem(STD_NAMESPACE ostream & /*docStream*/,
667 STD_NAMESPACE ostream & /*annexStream*/,
668 const size_t /*nestingLevel*/,
669 size_t & /*annexNumber*/,
670 const size_t /*flags*/) const
671 {
672 /* no content to render */
673 return EC_Normal;
674 }
675
676
readSRDocumentContentModule(DcmItem & dataset,const DSRIODConstraintChecker * constraintChecker,const size_t flags)677 OFCondition DSRDocumentTreeNode::readSRDocumentContentModule(DcmItem &dataset,
678 const DSRIODConstraintChecker *constraintChecker,
679 const size_t flags)
680 {
681 OFCondition result = EC_Normal;
682 /* read DocumentRelationshipMacro */
683 result = readDocumentRelationshipMacro(dataset, constraintChecker, "1" /*posString*/, flags);
684 /* read DocumentContentMacro */
685 if (result.good())
686 result = readDocumentContentMacro(dataset, "1" /*posString*/, flags);
687 return result;
688 }
689
690
writeSRDocumentContentModule(DcmItem & dataset,DcmStack * markedItems)691 OFCondition DSRDocumentTreeNode::writeSRDocumentContentModule(DcmItem &dataset,
692 DcmStack *markedItems)
693 {
694 OFCondition result = EC_Normal;
695 /* write DocumentRelationshipMacro */
696 result = writeDocumentRelationshipMacro(dataset, markedItems);
697 /* write DocumentContentMacro */
698 if (result.good())
699 result = writeDocumentContentMacro(dataset);
700 return result;
701 }
702
703
readDocumentRelationshipMacro(DcmItem & dataset,const DSRIODConstraintChecker * constraintChecker,const OFString & posString,const size_t flags)704 OFCondition DSRDocumentTreeNode::readDocumentRelationshipMacro(DcmItem &dataset,
705 const DSRIODConstraintChecker *constraintChecker,
706 const OFString &posString,
707 const size_t flags)
708 {
709 OFCondition result = EC_Normal;
710 /* read digital signatures sequences (optional) */
711 if (flags & RF_readDigitalSignatures)
712 {
713 getElementFromDataset(dataset, MACParameters);
714 getElementFromDataset(dataset, DigitalSignatures);
715 }
716 /* read ObservationDateTime (conditional) */
717 getAndCheckStringValueFromDataset(dataset, DCM_ObservationDateTime, ObservationDateTime, "1", "1C");
718 /* read ObservationUID (optional) */
719 getAndCheckStringValueFromDataset(dataset, DCM_ObservationUID, ObservationUID, "1", "3");
720 /* determine template identifier expected for this document */
721 OFString expectedTemplateIdentifier;
722 OFString expectedMappingResource;
723 if (constraintChecker != NULL)
724 constraintChecker->getRootTemplateIdentification(expectedTemplateIdentifier, expectedMappingResource);
725 /* read ContentTemplateSequence (conditional) */
726 DcmItem *ditem = NULL;
727 if (dataset.findAndGetSequenceItem(DCM_ContentTemplateSequence, ditem, 0 /*itemNum*/).good())
728 {
729 if (ValueType != VT_Container)
730 DCMSR_WARN("Found Content Template Sequence for content item \"" << posString << "\" which is not a CONTAINER");
731 getAndCheckStringValueFromDataset(*ditem, DCM_MappingResource, MappingResource, "1", "1", "ContentTemplateSequence");
732 getAndCheckStringValueFromDataset(*ditem, DCM_MappingResourceUID, MappingResourceUID, "1", "3", "ContentTemplateSequence");
733 getAndCheckStringValueFromDataset(*ditem, DCM_TemplateIdentifier, TemplateIdentifier, "1", "1", "ContentTemplateSequence");
734 /* in case the DICOM Content Mapping Resource (DCMR) is used */
735 if (MappingResource == "DCMR")
736 {
737 /* check whether the correct Mapping Resource UID is used (if present) */
738 if (!MappingResourceUID.empty() && (MappingResourceUID != UID_DICOMContentMappingResource))
739 {
740 DCMSR_WARN("Incorrect value for Mapping Resource UID (" << MappingResourceUID << "), "
741 << UID_DICOMContentMappingResource << " expected");
742 }
743 /* check for a common error: Template Identifier includes "TID" prefix */
744 if (!TemplateIdentifier.empty())
745 {
746 if ((TemplateIdentifier.find_first_not_of("0123456789") != OFString_npos) || (TemplateIdentifier.at(0) == '0'))
747 {
748 DCMSR_DEBUG("Reading invalid Template Identifier (" << TemplateIdentifier << ")");
749 DCMSR_WARN("Template Identifier shall be a string of digits without leading zeros");
750 }
751 }
752 }
753 /* check whether the expected template (if known) has been used */
754 if (!expectedTemplateIdentifier.empty())
755 {
756 /* compare with expected mapping resource */
757 if (MappingResource != expectedMappingResource)
758 {
759 DCMSR_WARN("Incorrect value for Mapping Resource ("
760 << ((MappingResource.empty()) ? "<empty>" : MappingResource) << "), "
761 << expectedMappingResource << " expected");
762 }
763 /* compare with expected template identifier */
764 if (TemplateIdentifier != expectedTemplateIdentifier)
765 {
766 DCMSR_WARN("Incorrect value for Template Identifier ("
767 << ((TemplateIdentifier.empty()) ? "<empty>" : TemplateIdentifier) << "), "
768 << expectedTemplateIdentifier << " expected");
769 }
770 }
771 }
772 /* only check template identifier on dataset level (root node) */
773 else if ((dataset.ident() == EVR_dataset) && !expectedTemplateIdentifier.empty())
774 {
775 DCMSR_WARN("Content Template Sequence missing or empty, Template Identifier "
776 << expectedTemplateIdentifier << " (" << expectedMappingResource << ") expected");
777 }
778 /* read ContentSequence */
779 if (result.good())
780 result = readContentSequence(dataset, constraintChecker, posString, flags);
781 return result;
782 }
783
784
writeDocumentRelationshipMacro(DcmItem & dataset,DcmStack * markedItems)785 OFCondition DSRDocumentTreeNode::writeDocumentRelationshipMacro(DcmItem &dataset,
786 DcmStack *markedItems)
787 {
788 OFCondition result = EC_Normal;
789 /* write digital signatures sequences (optional) */
790 if (!MACParameters.isEmpty())
791 addElementToDataset(result, dataset, new DcmSequenceOfItems(MACParameters), "1-n", "3", "SOPCommonModule");
792 if (!DigitalSignatures.isEmpty())
793 {
794 addElementToDataset(result, dataset, new DcmSequenceOfItems(DigitalSignatures), "1-n", "3", "SOPCommonModule");
795 DCMSR_WARN("Writing possibly incorrect digital signature - same as read from dataset");
796 }
797 /* add to marked items stack */
798 if (MarkFlag && (markedItems != NULL))
799 markedItems->push(&dataset);
800 /* write ObservationDateTime (conditional) */
801 result = putStringValueToDataset(dataset, DCM_ObservationDateTime, ObservationDateTime, OFFalse /*allowEmpty*/);
802 /* write ObservationUID (optional) */
803 if (result.good())
804 result = putStringValueToDataset(dataset, DCM_ObservationUID, ObservationUID, OFFalse /*allowEmpty*/);
805 /* write ContentTemplateSequence (conditional) */
806 if (result.good())
807 {
808 if (hasTemplateIdentification())
809 {
810 DcmItem *ditem = NULL;
811 /* create sequence with a single item */
812 result = dataset.findOrCreateSequenceItem(DCM_ContentTemplateSequence, ditem, 0 /*position*/);
813 if (result.good())
814 {
815 if (ValueType != VT_Container)
816 DCMSR_WARN("Writing Content Template Sequence for content item that is not a CONTAINER");
817 /* write item data */
818 putStringValueToDataset(*ditem, DCM_MappingResource, MappingResource);
819 putStringValueToDataset(*ditem, DCM_MappingResourceUID, MappingResourceUID, OFFalse /*allowEmpty*/);
820 putStringValueToDataset(*ditem, DCM_TemplateIdentifier, TemplateIdentifier);
821 }
822 }
823 }
824 /* write ContentSequence */
825 if (result.good())
826 result = writeContentSequence(dataset, markedItems);
827 return result;
828 }
829
830
readDocumentContentMacro(DcmItem & dataset,const OFString & posString,const size_t flags)831 OFCondition DSRDocumentTreeNode::readDocumentContentMacro(DcmItem &dataset,
832 const OFString &posString,
833 const size_t flags)
834 {
835 OFCondition result = EC_Normal;
836 /* skip reading ValueType, already done somewhere else */
837
838 /* read ConceptNameCodeSequence */
839 if (RelationshipType == RT_isRoot)
840 {
841 /* the concept name is required for the root container */
842 result = ConceptName.readSequence(dataset, DCM_ConceptNameCodeSequence, "1" /*type*/, flags);
843 } else {
844 /* the concept name might be empty for all other content items */
845 ConceptName.readSequence(dataset, DCM_ConceptNameCodeSequence, "1C" /*type*/, flags);
846 }
847 if (result.good() || (flags & RF_ignoreContentItemErrors))
848 {
849 if (result.bad())
850 DCMSR_DEBUG("Ignoring content item error because of read flag");
851 /* read ContentItem (depending on ValueType) */
852 result = readContentItem(dataset, flags);
853 }
854 /* check for validity, after reading */
855 if (result.bad() || !isValid())
856 {
857 printInvalidContentItemMessage("Reading", this, posString.c_str());
858 /* ignore content item reading/parsing error if flag is set */
859 if (flags & RF_ignoreContentItemErrors)
860 {
861 DCMSR_DEBUG("Ignoring content item error because of read flag");
862 result = EC_Normal;
863 }
864 /* content item is not valid (see above) */
865 else if (result.good())
866 {
867 /* check whether only the value or the entire content item is invalid */
868 result = (!hasValidValue()) ? SR_EC_InvalidValue : SR_EC_InvalidContentItem;
869 }
870 /* accept invalid content item value if flag is set */
871 if ((result == SR_EC_InvalidValue) && (flags & RF_acceptInvalidContentItemValue))
872 {
873 DCMSR_DEBUG("Ignoring invalid content item value because of read flag");
874 result = EC_Normal;
875 }
876 }
877 return result;
878 }
879
880
writeDocumentContentMacro(DcmItem & dataset) const881 OFCondition DSRDocumentTreeNode::writeDocumentContentMacro(DcmItem &dataset) const
882 {
883 OFCondition result = EC_Normal;
884 /* write ValueType */
885 result = putStringValueToDataset(dataset, DCM_ValueType, valueTypeToDefinedTerm(ValueType));
886 /* write ConceptNameCodeSequence */
887 if (result.good())
888 {
889 if (ConceptName.isValid())
890 result = ConceptName.writeSequence(dataset, DCM_ConceptNameCodeSequence);
891 }
892 if (result.good())
893 {
894 /* check for validity, before writing */
895 if (!isValid())
896 printInvalidContentItemMessage("Writing", this);
897 /* write ContentItem (depending on ValueType) */
898 result = writeContentItem(dataset);
899 }
900 return result;
901 }
902
903
createAndAppendNewNode(DSRDocumentTreeNode * & previousNode,const E_RelationshipType relationshipType,const E_ValueType valueType,const DSRIODConstraintChecker * constraintChecker)904 OFCondition DSRDocumentTreeNode::createAndAppendNewNode(DSRDocumentTreeNode *&previousNode,
905 const E_RelationshipType relationshipType,
906 const E_ValueType valueType,
907 const DSRIODConstraintChecker *constraintChecker)
908 {
909 OFCondition result = EC_Normal;
910 /* do not check by-reference relationships here, will be done later (after complete reading) */
911 if ((relationshipType == RT_unknown) || ((relationshipType != RT_invalid) && ((valueType == VT_byReference) ||
912 (constraintChecker == NULL) || constraintChecker->checkContentRelationship(ValueType, relationshipType, valueType))))
913 {
914 DSRDocumentTreeNode *node = createDocumentTreeNode(relationshipType, valueType);
915 if (node != NULL)
916 {
917 /* first child node */
918 if (previousNode == NULL)
919 Down = node;
920 else
921 {
922 /* new sibling */
923 previousNode->Next = node;
924 node->Prev = previousNode;
925 }
926 /* store new node for the next time */
927 previousNode = node;
928 } else {
929 if (valueType == VT_invalid)
930 result = SR_EC_UnknownValueType;
931 else
932 result = EC_MemoryExhausted;
933 }
934 } else {
935 /* summarize what went wrong */
936 if (valueType == VT_invalid)
937 result = SR_EC_UnknownValueType;
938 else if (relationshipType == RT_invalid)
939 result = SR_EC_UnknownRelationshipType;
940 else
941 result = SR_EC_InvalidByValueRelationship;
942 }
943 return result;
944 }
945
946
readContentSequence(DcmItem & dataset,const DSRIODConstraintChecker * constraintChecker,const OFString & posString,const size_t flags)947 OFCondition DSRDocumentTreeNode::readContentSequence(DcmItem &dataset,
948 const DSRIODConstraintChecker *constraintChecker,
949 const OFString &posString,
950 const size_t flags)
951 {
952 OFCondition result = EC_Normal;
953 DcmSequenceOfItems *dseq = NULL;
954 /* read ContentSequence (might be absent or empty) */
955 if (dataset.findAndGetSequence(DCM_ContentSequence, dseq).good())
956 {
957 OFString tmpString;
958 E_ValueType valueType = VT_invalid;
959 E_RelationshipType relationshipType = RT_invalid;
960 /* important: NULL indicates first child node */
961 DSRDocumentTreeNode *node = NULL;
962 /* for all items in the sequence */
963 unsigned long i = 0;
964 DcmObject *dobj = NULL;
965 while (((dobj = dseq->nextInContainer(dobj)) != NULL) && result.good())
966 {
967 DcmItem *ditem = OFstatic_cast(DcmItem *, dobj);
968 DSRDocumentTreeNode *newNode = NULL;
969 /* create current location string */
970 char buffer[20];
971 OFString location = posString;
972 if (!location.empty())
973 location += ".";
974 location += numberToString(OFstatic_cast(size_t, i + 1), buffer);
975 if (flags & RF_showCurrentlyProcessedItem)
976 DCMSR_INFO("Processing content item " << location);
977 /* read RelationshipType */
978 result = getAndCheckStringValueFromDataset(*ditem, DCM_RelationshipType, tmpString, "1", "1", "content item");
979 if (result.good() || (flags & RF_acceptUnknownRelationshipType))
980 {
981 relationshipType = definedTermToRelationshipType(tmpString);
982 /* check relationship type */
983 if (relationshipType == RT_invalid)
984 {
985 printUnknownValueWarningMessage("RelationshipType", tmpString.c_str());
986 if (flags & RF_acceptUnknownRelationshipType)
987 relationshipType = RT_unknown;
988 }
989 /* check for by-reference relationship */
990 DcmUnsignedLong referencedContentItemIdentifier(DCM_ReferencedContentItemIdentifier);
991 if (getAndCheckElementFromDataset(*ditem, referencedContentItemIdentifier, "1-n", "1C", "content item").good())
992 {
993 /* create new node (by-reference, no constraint checker required) */
994 result = createAndAppendNewNode(node, relationshipType, VT_byReference);
995 /* read ReferencedContentItemIdentifier (again) */
996 if (result.good())
997 {
998 newNode = node;
999 result = node->readContentItem(*ditem, flags);
1000 }
1001 } else {
1002 /* read ValueType (from DocumentContentMacro) - required to create new node */
1003 result = getAndCheckStringValueFromDataset(*ditem, DCM_ValueType, tmpString, "1", "1", "content item");
1004 if (result.good())
1005 {
1006 /* read by-value relationship */
1007 valueType = definedTermToValueType(tmpString);
1008 /* check value type */
1009 if (valueType != VT_invalid)
1010 {
1011 /* create new node (by-value) */
1012 result = createAndAppendNewNode(node, relationshipType, valueType, (flags & RF_ignoreRelationshipConstraints) ? NULL : constraintChecker);
1013 /* read RelationshipMacro */
1014 if (result.good())
1015 {
1016 newNode = node;
1017 result = node->readDocumentRelationshipMacro(*ditem, constraintChecker, location, flags);
1018 /* read DocumentContentMacro */
1019 if (result.good())
1020 result = node->readDocumentContentMacro(*ditem, location.c_str(), flags);
1021 } else {
1022 /* create new node failed */
1023
1024 /* determine document type */
1025 const E_DocumentType documentType = (constraintChecker != NULL) ? constraintChecker->getDocumentType() : DT_invalid;
1026 DCMSR_ERROR("Cannot add \"" << relationshipTypeToReadableName(relationshipType) << " "
1027 << valueTypeToDefinedTerm(valueType /*target item*/) << "\" to "
1028 << valueTypeToDefinedTerm(ValueType /*source item*/) << " in "
1029 << documentTypeToReadableName(documentType));
1030 }
1031 } else {
1032 /* unknown/unsupported value type */
1033 printUnknownValueWarningMessage("ValueType", tmpString.c_str());
1034 result = SR_EC_UnknownValueType;
1035 }
1036 }
1037 }
1038 }
1039 /* check for any errors */
1040 if (result.bad())
1041 {
1042 printContentItemErrorMessage("Reading", result, newNode, location.c_str());
1043 /* print current data set (item) that caused the error */
1044 DCMSR_DEBUG(OFString(31, '-') << " DICOM DATA SET " << OFString(31, '-') << OFendl
1045 << DcmObject::PrintHelper(*ditem, DCMTypes::PF_convertToOctalNumbers, 1) << OFString(78, '-'));
1046 }
1047 /* increment the counter (needed for generating the location string) */
1048 i++;
1049 }
1050 /* skipping complete subtree if flag is set */
1051 if (result.bad() && (flags & RF_skipInvalidContentItems))
1052 {
1053 printInvalidContentItemMessage("Skipping", node);
1054 result = EC_Normal;
1055 }
1056 }
1057 return result;
1058 }
1059
1060
writeContentSequence(DcmItem & dataset,DcmStack * markedItems) const1061 OFCondition DSRDocumentTreeNode::writeContentSequence(DcmItem &dataset,
1062 DcmStack *markedItems) const
1063 {
1064 OFCondition result = EC_Normal;
1065 /* goto first child of current node */
1066 DSRDocumentTreeNodeCursor cursor(getDown());
1067 if (cursor.isValid())
1068 {
1069 /* write ContentSequence */
1070 DcmSequenceOfItems *dseq = new DcmSequenceOfItems(DCM_ContentSequence);
1071 if (dseq != NULL)
1072 {
1073 DcmItem *ditem = NULL;
1074 DSRDocumentTreeNode *node = NULL;
1075 /* for all child nodes */
1076 do {
1077 node = cursor.getNode();
1078 ditem = new DcmItem();
1079 if (ditem != NULL)
1080 {
1081 /* write RelationshipType */
1082 result = putStringValueToDataset(*ditem, DCM_RelationshipType, relationshipTypeToDefinedTerm(node->getRelationshipType()));
1083 /* check for by-reference relationship */
1084 if (node->getValueType() == VT_byReference)
1085 {
1086 /* write ReferencedContentItemIdentifier */
1087 if (result.good())
1088 result = node->writeContentItem(*ditem);
1089 } else { // by-value
1090 /* write RelationshipMacro */
1091 if (result.good())
1092 result = node->writeDocumentRelationshipMacro(*ditem, markedItems);
1093 /* write DocumentContentMacro */
1094 if (result.good())
1095 node->writeDocumentContentMacro(*ditem);
1096 }
1097 /* check for any errors */
1098 if (result.bad())
1099 printContentItemErrorMessage("Writing", result, node);
1100 /* insert item into sequence */
1101 if (result.good())
1102 dseq->insert(ditem);
1103 else
1104 delete ditem;
1105 } else
1106 result = EC_MemoryExhausted;
1107 } while (result.good() && cursor.gotoNext());
1108 if (result.good())
1109 result = dataset.insert(dseq, OFTrue /*replaceOld*/);
1110 if (result.bad())
1111 delete dseq;
1112 } else
1113 result = EC_MemoryExhausted;
1114 }
1115 return result;
1116 }
1117
1118
renderHTMLConceptName(STD_NAMESPACE ostream & docStream,const size_t flags) const1119 OFCondition DSRDocumentTreeNode::renderHTMLConceptName(STD_NAMESPACE ostream &docStream,
1120 const size_t flags) const
1121 {
1122 if (!(flags & HF_renderItemInline) && (flags & HF_renderItemsSeparately))
1123 {
1124 const char *lineBreak = (flags & DSRTypes::HF_renderSectionTitlesInline) ? " " :
1125 (flags & DSRTypes::HF_XHTML11Compatibility) ? "<br />" : "<br>";
1126 /* flag indicating whether line is empty or not */
1127 OFBool writeLine = OFFalse;
1128 if (!ConceptName.getCodeMeaning().empty())
1129 {
1130 docStream << "<b>";
1131 /* render ConceptName & Code (if valid) */
1132 ConceptName.renderHTML(docStream, flags, (flags & HF_renderConceptNameCodes) && ConceptName.isValid() /*fullCode*/);
1133 docStream << ":</b>";
1134 writeLine = OFTrue;
1135 }
1136 else if (flags & HF_currentlyInsideAnnex)
1137 {
1138 docStream << "<b>";
1139 /* render ValueType only */
1140 docStream << valueTypeToReadableName(ValueType);
1141 docStream << ":</b>";
1142 writeLine = OFTrue;
1143 }
1144 /* render optional observation date/time */
1145 if (!ObservationDateTime.empty())
1146 {
1147 if (writeLine)
1148 docStream << " ";
1149 OFString tmpString;
1150 if (flags & HF_XHTML11Compatibility)
1151 docStream << "<span class=\"observe\">";
1152 else
1153 docStream << "<small>";
1154 docStream << "(observed: " << dicomToReadableDateTime(ObservationDateTime, tmpString) << ")";
1155 if (flags & HF_XHTML11Compatibility)
1156 docStream << "</span>";
1157 else
1158 docStream << "</small>";
1159 writeLine = OFTrue;
1160 }
1161 if (writeLine)
1162 docStream << lineBreak << OFendl;
1163 }
1164 return EC_Normal;
1165 }
1166
1167
renderHTMLChildNodes(STD_NAMESPACE ostream & docStream,STD_NAMESPACE ostream & annexStream,const size_t nestingLevel,size_t & annexNumber,const size_t flags) const1168 OFCondition DSRDocumentTreeNode::renderHTMLChildNodes(STD_NAMESPACE ostream &docStream,
1169 STD_NAMESPACE ostream &annexStream,
1170 const size_t nestingLevel,
1171 size_t &annexNumber,
1172 const size_t flags) const
1173 {
1174 OFCondition result = EC_Normal;
1175 /* goto first child of current node */
1176 DSRDocumentTreeNodeCursor cursor(getDown());
1177 if (cursor.isValid())
1178 {
1179 /* flag used to format the relationship reference texts */
1180 OFBool paragraphFlag = (flags & HF_createFootnoteReferences) > 0;
1181 /* local version of flags */
1182 size_t newFlags = flags;
1183 /* footnote counter */
1184 size_t footnoteNumber = 1;
1185 /* create memory output stream for the temporal document */
1186 OFOStringStream tempDocStream;
1187 const DSRDocumentTreeNode *node = NULL;
1188 /* for all child nodes */
1189 do {
1190 node = cursor.getNode();
1191 /* set/reset flag for footnote creation*/
1192 newFlags &= ~HF_createFootnoteReferences;
1193 if (!(flags & HF_renderItemsSeparately) && node->hasChildNodes() && (node->getValueType() != VT_Container))
1194 newFlags |= HF_createFootnoteReferences;
1195 /* render (optional) reference to annex */
1196 OFString relationshipText;
1197 if (!getRelationshipText(node->getRelationshipType(), relationshipText, flags).empty())
1198 {
1199 if (paragraphFlag)
1200 {
1201 /* inside paragraph: line break */
1202 if (flags & HF_XHTML11Compatibility)
1203 docStream << "<br />" << OFendl;
1204 else
1205 docStream << "<br>" << OFendl;
1206 } else {
1207 /* open paragraph */
1208 if (flags & HF_XHTML11Compatibility)
1209 {
1210 docStream << "<div class=\"small\">" << OFendl;
1211 docStream << "<p>" << OFendl;
1212 } else {
1213 docStream << "<p>" << OFendl;
1214 docStream << "<small>" << OFendl;
1215 }
1216 paragraphFlag = OFTrue;
1217 }
1218 if (newFlags & HF_XHTML11Compatibility)
1219 docStream << "<span class=\"relation\">" << relationshipText << "</span>: ";
1220 else if (flags & DSRTypes::HF_HTML32Compatibility)
1221 docStream << "<u>" << relationshipText << "</u>: ";
1222 else /* HTML 4.01 */
1223 docStream << "<span class=\"under\">" << relationshipText << "</span>: ";
1224 /* expand short nodes with no children inline (or depending on 'flags' all nodes) */
1225 if ((flags & HF_alwaysExpandChildrenInline) ||
1226 (!(flags & HF_neverExpandChildrenInline) && !node->hasChildNodes() && node->isShort(flags)))
1227 {
1228 if (node->getValueType() != VT_byReference)
1229 {
1230 /* render concept name/code or value type */
1231 if (node->getConceptName().getCodeMeaning().empty())
1232 docStream << valueTypeToReadableName(node->getValueType());
1233 else
1234 node->getConceptName().renderHTML(docStream, flags, (flags & HF_renderConceptNameCodes) && ConceptName.isValid() /*fullCode*/);
1235 docStream << " = ";
1236 }
1237 /* render HTML code (directly to the reference text) */
1238 result = node->renderHTML(docStream, annexStream, 0 /*nesting level*/, annexNumber, newFlags | HF_renderItemInline);
1239 } else {
1240 /* render concept name or value type */
1241 if (node->getConceptName().getCodeMeaning().empty())
1242 docStream << valueTypeToReadableName(node->getValueType()) << " ";
1243 else
1244 docStream << node->getConceptName().getCodeMeaning() << " ";
1245 /* render annex heading and reference */
1246 createHTMLAnnexEntry(docStream, annexStream, "" /*referenceText*/, annexNumber, newFlags);
1247 if (flags & HF_XHTML11Compatibility)
1248 annexStream << "<div class=\"para\">" << OFendl;
1249 else
1250 annexStream << "<div>" << OFendl;
1251 /* create memory output stream for the temporal annex */
1252 OFOStringStream tempAnnexStream;
1253 /* render HTML code (directly to the annex) */
1254 result = node->renderHTML(annexStream, tempAnnexStream, 0 /*nesting level*/, annexNumber, newFlags | HF_currentlyInsideAnnex);
1255 annexStream << "</div>" << OFendl;
1256 /* use empty paragraph for bottom margin */
1257 if (!(flags & HF_XHTML11Compatibility))
1258 annexStream << "<p>" << OFendl;
1259 /* append temporary stream to main stream */
1260 if (result.good())
1261 result = appendStream(annexStream, tempAnnexStream);
1262 }
1263 } else {
1264 /* close paragraph */
1265 if (paragraphFlag)
1266 {
1267 if (flags & HF_XHTML11Compatibility)
1268 {
1269 docStream << "</p>" << OFendl;
1270 docStream << "</div>" << OFendl;
1271 } else {
1272 docStream << "</small>" << OFendl;
1273 docStream << "</p>" << OFendl;
1274 }
1275 paragraphFlag = OFFalse;
1276 }
1277 /* begin new paragraph */
1278 if (flags & HF_renderItemsSeparately)
1279 {
1280 if (flags & HF_XHTML11Compatibility)
1281 docStream << "<div class=\"para\">" << OFendl;
1282 else
1283 docStream << "<div>" << OFendl;
1284 }
1285 /* write footnote text to temporary stream */
1286 if (newFlags & HF_createFootnoteReferences)
1287 {
1288 /* render HTML code (without child nodes) */
1289 result = node->renderHTMLContentItem(docStream, annexStream, 0 /*nestingLevel*/, annexNumber, newFlags);
1290 /* create footnote numbers (individually for each child?) */
1291 if (result.good())
1292 {
1293 /* tags are closed automatically in 'node->renderHTMLChildNodes()' */
1294 if (flags & HF_XHTML11Compatibility)
1295 {
1296 tempDocStream << "<div class=\"small\">" << OFendl;
1297 tempDocStream << "<p>" << OFendl;
1298 } else {
1299 tempDocStream << "<p>" << OFendl;
1300 tempDocStream << "<small>" << OFendl;
1301 }
1302 /* render footnote text and reference */
1303 createHTMLFootnote(docStream, tempDocStream, footnoteNumber, node->getNodeID(), flags);
1304 /* render child nodes to temporary stream */
1305 result = node->renderHTMLChildNodes(tempDocStream, annexStream, 0 /*nestingLevel*/, annexNumber, newFlags);
1306 }
1307 } else {
1308 /* render HTML code (incl. child nodes)*/
1309 result = node->renderHTML(docStream, annexStream, nestingLevel + 1, annexNumber, newFlags);
1310 }
1311 /* end paragraph */
1312 if (flags & HF_renderItemsSeparately)
1313 {
1314 docStream << "</div>" << OFendl;
1315 /* use empty paragraph for bottom margin */
1316 if (!(flags & HF_XHTML11Compatibility))
1317 docStream << "<p>" << OFendl;
1318 }
1319 }
1320 } while (result.good() && cursor.gotoNext());
1321 /* close last open paragraph (if any) */
1322 if (paragraphFlag)
1323 {
1324 if (flags & HF_XHTML11Compatibility)
1325 {
1326 docStream << "</p>" << OFendl;
1327 docStream << "</div>" << OFendl;
1328 } else {
1329 docStream << "</small>" << OFendl;
1330 docStream << "</p>" << OFendl;
1331 }
1332 }
1333 /* append temporary stream to main stream */
1334 if (result.good())
1335 result = appendStream(docStream, tempDocStream);
1336 }
1337 return result;
1338 }
1339
1340
1341 // static functions
1342
getRelationshipText(const E_RelationshipType relationshipType,OFString & relationshipText,const size_t flags)1343 const OFString &DSRDocumentTreeNode::getRelationshipText(const E_RelationshipType relationshipType,
1344 OFString &relationshipText,
1345 const size_t flags)
1346 {
1347 switch (relationshipType)
1348 {
1349 case RT_contains:
1350 if (flags & HF_createFootnoteReferences)
1351 relationshipText = "Contains";
1352 else
1353 relationshipText.clear();
1354 break;
1355 case RT_hasObsContext:
1356 relationshipText = "Observation Context";
1357 break;
1358 case RT_hasAcqContext:
1359 relationshipText = "Acquisition Context";
1360 break;
1361 case RT_hasConceptMod:
1362 relationshipText = "Concept Modifier";
1363 break;
1364 case RT_hasProperties:
1365 relationshipText = "Properties";
1366 break;
1367 case RT_inferredFrom:
1368 relationshipText = "Inferred from";
1369 break;
1370 case RT_selectedFrom:
1371 relationshipText = "Selected from";
1372 break;
1373 default:
1374 relationshipText.clear();
1375 break;
1376 }
1377 return relationshipText;
1378 }
1379
1380
checkTemplateIdentification(const OFString & templateIdentifier,const OFString & mappingResource,const OFString & mappingResourceUID)1381 OFBool DSRDocumentTreeNode::checkTemplateIdentification(const OFString &templateIdentifier,
1382 const OFString &mappingResource,
1383 const OFString &mappingResourceUID)
1384 {
1385 OFBool result = OFFalse;
1386 /* either all three values are empty or the first two are both non-empty */
1387 if (templateIdentifier.empty() && mappingResource.empty() && mappingResourceUID.empty())
1388 result = OFTrue;
1389 else if (!templateIdentifier.empty() && !mappingResource.empty())
1390 result = OFTrue;
1391 return result;
1392 }
1393