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