/* * * Copyright (C) 2000-2019, OFFIS e.V. * All rights reserved. See COPYRIGHT file for details. * * This software and supporting documentation were developed by * * OFFIS e.V. * R&D Division Health * Escherweg 2 * D-26121 Oldenburg, Germany * * * Module: dcmsr * * Author: Joerg Riesmeier * * Purpose: * classes: DSRDocumentTreeNode * */ #include "dcmtk/config/osconfig.h" /* make sure OS specific configuration is included first */ #include "dcmtk/dcmsr/dsrdoctn.h" #include "dcmtk/dcmsr/dsrdncsr.h" #include "dcmtk/dcmsr/dsrdtitn.h" #include "dcmtk/dcmsr/dsrxmld.h" #include "dcmtk/dcmsr/dsriodcc.h" #include "dcmtk/dcmdata/dcdeftag.h" #include "dcmtk/dcmdata/dcuid.h" #include "dcmtk/dcmdata/dcvrcs.h" #include "dcmtk/dcmdata/dcvrdt.h" #include "dcmtk/dcmdata/dcvrui.h" #include "dcmtk/dcmdata/dcvrul.h" DSRDocumentTreeNode::DSRDocumentTreeNode(const E_RelationshipType relationshipType, const E_ValueType valueType) : DSRTreeNode(), MarkFlag(OFFalse), ReferenceTarget(OFFalse), RelationshipType(relationshipType), ValueType(valueType), ConceptName(), ObservationDateTime(), ObservationUID(), TemplateIdentifier(), MappingResource(), MappingResourceUID(), MACParameters(DCM_MACParametersSequence), DigitalSignatures(DCM_DigitalSignaturesSequence) { } DSRDocumentTreeNode::DSRDocumentTreeNode(const DSRDocumentTreeNode &node) : DSRTreeNode(node.Annotation), MarkFlag(node.MarkFlag), ReferenceTarget(OFFalse), RelationshipType(node.RelationshipType), ValueType(node.ValueType), ConceptName(node.ConceptName), ObservationDateTime(node.ObservationDateTime), ObservationUID(node.ObservationUID), TemplateIdentifier(node.TemplateIdentifier), MappingResource(node.MappingResource), MappingResourceUID(node.MappingResourceUID), MACParameters(DCM_MACParametersSequence), DigitalSignatures(DCM_DigitalSignaturesSequence) { } DSRDocumentTreeNode::~DSRDocumentTreeNode() { } OFBool DSRDocumentTreeNode::operator==(const DSRDocumentTreeNode &node) const { /* only very basic information is used for comparing the two nodes */ return (RelationshipType == node.RelationshipType) && (ValueType == node.ValueType) && (ConceptName == node.ConceptName); } OFBool DSRDocumentTreeNode::operator!=(const DSRDocumentTreeNode &node) const { /* only very basic information is used for comparing the two nodes */ return (RelationshipType != node.RelationshipType) || (ValueType != node.ValueType) || (ConceptName != node.ConceptName); } void DSRDocumentTreeNode::clear() { MarkFlag = OFFalse; ReferenceTarget = OFFalse; ConceptName.clear(); ObservationDateTime.clear(); ObservationUID.clear(); TemplateIdentifier.clear(); MappingResource.clear(); MappingResourceUID.clear(); MACParameters.clear(); DigitalSignatures.clear(); } OFBool DSRDocumentTreeNode::isValid() const { return (RelationshipType != RT_invalid) && (ValueType != VT_invalid); } OFBool DSRDocumentTreeNode::hasValidValue() const { return OFTrue; } OFBool DSRDocumentTreeNode::isShort(const size_t /*flags*/) const { return OFTrue; } OFBool DSRDocumentTreeNode::hasTemplateIdentification() const { /* mapping resource UID is optional, so do not check it */ return !TemplateIdentifier.empty() && !MappingResource.empty(); } OFCondition DSRDocumentTreeNode::print(STD_NAMESPACE ostream &stream, const size_t flags) const { if (RelationshipType != RT_isRoot) { DCMSR_PRINT_ANSI_ESCAPE_CODE(DCMSR_ANSI_ESCAPE_CODE_RELATIONSHIP_TYPE) stream << relationshipTypeToReadableName(RelationshipType) << " "; } DCMSR_PRINT_ANSI_ESCAPE_CODE(DCMSR_ANSI_ESCAPE_CODE_VALUE_TYPE) stream << valueTypeToDefinedTerm(ValueType); DCMSR_PRINT_ANSI_ESCAPE_CODE(DCMSR_ANSI_ESCAPE_CODE_DELIMITER) stream << ":"; /* only print valid concept name codes */ if (ConceptName.isValid() || (flags & PF_printInvalidCodes)) { DCMSR_PRINT_ANSI_ESCAPE_CODE(DCMSR_ANSI_ESCAPE_CODE_CONCEPT_NAME) ConceptName.print(stream, (flags & PF_printConceptNameCodes) > 0 /*printCodeValue*/, flags); } return EC_Normal; } OFCondition DSRDocumentTreeNode::printExtended(STD_NAMESPACE ostream &stream, const size_t flags) const { /* print observation date/time (optional) */ if (!ObservationDateTime.empty()) { OFString tmpString; DCMSR_PRINT_ANSI_ESCAPE_CODE(DCMSR_ANSI_ESCAPE_CODE_DELIMITER) stream << " {" << dicomToReadableDateTime(ObservationDateTime, tmpString) << "}"; } /* print annotation (optional) */ if (hasAnnotation() && (flags & PF_printAnnotation)) { DCMSR_PRINT_ANSI_ESCAPE_CODE(DCMSR_ANSI_ESCAPE_CODE_ANNOTATION) stream << " \"" << getAnnotation().getText() << "\""; } /* print template identification (conditional) */ if (hasTemplateIdentification() && (flags & PF_printTemplateIdentification)) { DCMSR_PRINT_ANSI_ESCAPE_CODE(DCMSR_ANSI_ESCAPE_CODE_DELIMITER) stream << " # "; DCMSR_PRINT_ANSI_ESCAPE_CODE(DCMSR_ANSI_ESCAPE_CODE_TEMPLATE_ID) stream << "TID " << TemplateIdentifier; stream << " (" << MappingResource; if (!MappingResourceUID.empty()) stream << ", " << MappingResourceUID; stream << ")"; } return EC_Normal; } OFCondition DSRDocumentTreeNode::read(DcmItem &dataset, const DSRIODConstraintChecker *constraintChecker, const size_t flags) { return readSRDocumentContentModule(dataset, constraintChecker, flags); } OFCondition DSRDocumentTreeNode::write(DcmItem &dataset, DcmStack *markedItems) { return writeSRDocumentContentModule(dataset, markedItems); } OFCondition DSRDocumentTreeNode::readXML(const DSRXMLDocument &doc, DSRXMLCursor cursor, const E_DocumentType documentType, const size_t flags) { OFCondition result = SR_EC_InvalidDocument; if (cursor.valid()) { OFString idAttr; OFString mappingResource; OFString mappingResourceUID; OFString templateIdentifier; /* important: NULL indicates first child node */ DSRDocumentTreeNode *node = NULL; /* read "id" attribute (optional) and compare with expected value */ if (!doc.getStringFromAttribute(cursor, idAttr, "id", OFFalse /*encoding*/, OFFalse /*required*/).empty() && (stringToNumber(idAttr.c_str()) != getNodeID())) { /* create warning message */ DCMSR_WARN("XML attribute 'id' (" << idAttr << ") deviates from current node number (" << getNodeID() << ")"); } /* template identification information expected "inside" content item */ if (!(flags & XF_templateElementEnclosesItems)) { /* check for optional template identification */ const DSRXMLCursor childCursor = doc.getNamedChildNode(cursor, "template", OFFalse /*required*/); if (childCursor.valid()) { /* check whether information is stored as XML attributes */ if (doc.hasAttribute(childCursor, "tid")) { doc.getStringFromAttribute(childCursor, mappingResource, "resource"); doc.getStringFromAttribute(childCursor, mappingResourceUID, "uid", OFFalse /*encoding*/, OFFalse /*required*/); doc.getStringFromAttribute(childCursor, templateIdentifier, "tid"); } else { const DSRXMLCursor resourceCursor = doc.getNamedChildNode(childCursor, "resource"); if (resourceCursor.valid()) { doc.getStringFromAttribute(resourceCursor, mappingResourceUID, "uid", OFFalse /*encoding*/, OFFalse /*required*/); doc.getStringFromNodeContent(resourceCursor, mappingResource); } doc.getStringFromNodeContent(doc.getNamedChildNode(childCursor, "id"), templateIdentifier); } if (setTemplateIdentification(templateIdentifier, mappingResource, mappingResourceUID).bad()) DCMSR_WARN("Content item has invalid/incomplete template identification"); } } /* read concept name (not required in some cases) */ ConceptName.readXML(doc, doc.getNamedChildNode(cursor, "concept", OFFalse /*required*/), flags); /* read observation UID and date/time (optional) */ const DSRXMLCursor childCursor = doc.getNamedChildNode(cursor, "observation", OFFalse /*required*/); if (childCursor.valid()) { doc.getStringFromAttribute(childCursor, ObservationUID, "uid", OFFalse /*encoding*/, OFFalse /*required*/); DSRDateTimeTreeNode::getValueFromXMLNodeContent(doc, doc.getNamedChildNode(childCursor, "datetime", OFFalse /*required*/), ObservationDateTime); } /* read node content (depends on value type) */ result = readXMLContentItem(doc, cursor, flags); /* goto first child node */ cursor.gotoChild(); /* iterate over all child content items */ while (cursor.valid() && result.good()) { /* template identification information expected "outside" content item */ if (flags & XF_templateElementEnclosesItems) { /* check for optional template identification */ if (doc.matchNode(cursor, "template")) { doc.getStringFromAttribute(cursor, mappingResource, "resource"); doc.getStringFromAttribute(cursor, mappingResourceUID, "uid", OFFalse /*encoding*/, OFFalse /*required*/); doc.getStringFromAttribute(cursor, templateIdentifier, "tid"); /* goto first child of the "template" element */ cursor.gotoChild(); } } /* get SR value type from current XML node, also supports "by-reference" detection */ E_ValueType valueType = doc.getValueTypeFromNode(cursor); /* invalid types are silently ignored */ if (valueType != VT_invalid) { /* get SR relationship type */ E_RelationshipType relationshipType = doc.getRelationshipTypeFromNode(cursor); /* create new node (by-value or by-reference), do not check constraints */ result = createAndAppendNewNode(node, relationshipType, valueType); if (result.good()) { if ((flags & XF_templateElementEnclosesItems) && (valueType != VT_byReference)) { /* set template identification (if any) */ if (node->setTemplateIdentification(templateIdentifier, mappingResource, mappingResourceUID).bad()) DCMSR_WARN("Content item has invalid/incomplete template identification"); } /* proceed with reading child nodes */ result = node->readXML(doc, cursor, documentType, flags); /* print node error message (if any) */ doc.printGeneralNodeError(cursor, result); } else { /* create new node failed */ DCMSR_ERROR("Cannot add \"" << relationshipTypeToReadableName(relationshipType) << " " << valueTypeToDefinedTerm(valueType /*target item*/) << "\" to " << valueTypeToDefinedTerm(ValueType /*source item*/) << " in " << documentTypeToReadableName(documentType)); } } /* proceed with next node */ cursor.gotoNext(); } } return result; } OFCondition DSRDocumentTreeNode::readXMLContentItem(const DSRXMLDocument & /*doc*/, DSRXMLCursor /*cursor*/, const size_t /*flags*/) { return EC_IllegalCall; } OFCondition DSRDocumentTreeNode::writeXML(STD_NAMESPACE ostream &stream, const size_t flags) const { OFCondition result = EC_Normal; /* check for validity */ if (!isValid()) printInvalidContentItemMessage("Writing to XML", this); /* write optional template identification */ if ((flags & XF_writeTemplateIdentification) && !(flags & XF_templateElementEnclosesItems)) { if (hasTemplateIdentification()) { if (flags & XF_templateIdentifierAsAttribute) { stream << "