/* * * Copyright (C) 1996-2020, 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: dcmwlm * * Author: Thomas Wilkens, Jan Schlamelcher * * Purpose: Class for managing file system interaction. * */ // ---------------------------------------------------------------------------- #include "dcmtk/config/osconfig.h" #include "dcmtk/dcmnet/diutil.h" #include "dcmtk/ofstd/ofconsol.h" #include "dcmtk/ofstd/ofstd.h" #include "dcmtk/ofstd/ofdate.h" #include "dcmtk/ofstd/oftime.h" #include "dcmtk/ofstd/oftypes.h" #include "dcmtk/ofstd/offilsys.h" #include "dcmtk/dcmnet/dicom.h" #include "dcmtk/dcmdata/dcdatset.h" #include "dcmtk/dcmdata/dcitem.h" #include "dcmtk/dcmdata/dcvrda.h" #include "dcmtk/dcmdata/dcvrtm.h" #include "dcmtk/dcmdata/dcmatch.h" #include "dcmtk/dcmwlm/wltypdef.h" #include "dcmtk/dcmwlm/wlds.h" #include "dcmtk/dcmdata/dctk.h" #include #include #include "dcmtk/dcmwlm/wlfsim.h" // ---------------------------------------------------------------------------- class WlmFileSystemInteractionManager::MatchingKeys { MatchingKeys(int) { keys.push_back(OFMake_pair(DCM_PatientName,OFTrue)); keys.push_back(OFMake_pair(DCM_ResponsiblePerson,OFTrue)); keys.push_back(OFMake_pair(DCM_ResponsiblePersonRole,OFTrue)); keys.push_back(OFMake_pair(DCM_PatientID,OFFalse)); keys.push_back(OFMake_pair(DCM_AccessionNumber,OFTrue)); keys.push_back(OFMake_pair(DCM_RequestedProcedureID,OFTrue)); keys.push_back(OFMake_pair(DCM_ReferringPhysicianName,OFTrue)); keys.push_back(OFMake_pair(DCM_PatientSex,OFTrue)); keys.push_back(OFMake_pair(DCM_RequestingPhysician,OFTrue)); keys.push_back(OFMake_pair(DCM_AdmissionID,OFTrue)); keys.push_back(OFMake_pair(DCM_RequestedProcedurePriority,OFTrue)); keys.push_back(OFMake_pair(DCM_PatientBirthDate,OFTrue)); keys.push_back(OFMake_pair(DCM_IssuerOfPatientID,OFTrue)); combinedKeys.push_back(OFMake_pair(DCM_StudyDate,DCM_StudyTime)); sequenceKeys.push_back(OFMake_pair(DCM_ScheduledProcedureStepSequence,MatchingKeys())); sequenceKeys.back().second.keys.push_back(OFMake_pair(DCM_ScheduledStationAETitle,OFFalse)); sequenceKeys.back().second.keys.push_back(OFMake_pair(DCM_Modality,OFFalse)); sequenceKeys.back().second.keys.push_back(OFMake_pair(DCM_ScheduledPerformingPhysicianName,OFTrue)); sequenceKeys.back().second.combinedKeys.push_back(OFMake_pair(DCM_ScheduledProcedureStepStartDate,DCM_ScheduledProcedureStepStartTime)); } public: static const MatchingKeys root; MatchingKeys() {} OFVector > keys; OFVector > combinedKeys; OFVector > sequenceKeys; }; const WlmFileSystemInteractionManager::MatchingKeys WlmFileSystemInteractionManager::MatchingKeys::root(0); // ---------------------------------------------------------------------------- WlmFileSystemInteractionManager::WlmFileSystemInteractionManager() : dfPath() , enableRejectionOfIncompleteWlFiles( OFTrue ) , calledApplicationEntityTitle() , matchingRecords() { } // ---------------------------------------------------------------------------- WlmFileSystemInteractionManager::~WlmFileSystemInteractionManager() // Date : July 11, 2002 // Author : Thomas Wilkens // Task : Destructor. // Parameters : none. // Return Value : none. { } // ---------------------------------------------------------------------------- void WlmFileSystemInteractionManager::SetEnableRejectionOfIncompleteWlFiles( OFBool value ) // Date : May 3, 2005 // Author : Thomas Wilkens // Task : Set value in member variable. // Parameters : value - [in] The value to set. // Return Value : none. { enableRejectionOfIncompleteWlFiles = value; } // ---------------------------------------------------------------------------- OFCondition WlmFileSystemInteractionManager::ConnectToFileSystem( const OFString& dfPathv ) // Date : July 11, 2002 // Author : Thomas Wilkens // Task : Connects to the worklist file system database. // Parameters : dfPathv - [in] Path to worklist file system database. // Return Value : Indicates if the connection could be established or not. { // check parameter if( dfPathv.empty() ) { DCMWLM_ERROR("Invalid parameters, cannot connect to worklist file system database"); return( WLM_EC_CannotConnectToDataSource ); } // copy value dfPath = dfPathv; // check if the specified path is existent and accessible for reading if( !OFStandard::dirExists( dfPath ) || !OFStandard::isReadable( dfPath ) ) return( WLM_EC_CannotConnectToDataSource ); else return( EC_Normal ); } // ---------------------------------------------------------------------------- OFCondition WlmFileSystemInteractionManager::DisconnectFromFileSystem() // Date : July 11, 2002 // Author : Thomas Wilkens // Task : Disconnects from the worklist file system database. // Parameters : none. // Return Value : Indicates if the connection was disconnected successfully. { return( EC_Normal ); } // ---------------------------------------------------------------------------- OFBool WlmFileSystemInteractionManager::IsCalledApplicationEntityTitleSupported( const OFString& calledApplicationEntityTitlev ) // Date : July 11, 2002 // Author : Thomas Wilkens // Task : Checks if the given called application entity title is supported. If this is the case, // OFTrue will be returned, else OFFalse. // Parameters : calledApplicationEntityTitlev - [in] The application entity title which shall be checked // for support. Valid pointer expected. // Return Value : OFTrue - The called application entity title is supported. // OFFalse - The called application entity title is not supported or it is not given. { // copy value calledApplicationEntityTitle = calledApplicationEntityTitlev; // Determine complete path to the files that make up the data source. OFString fullPath( dfPath ); if( !fullPath.empty() && fullPath[fullPath.length()-1] != PATH_SEPARATOR ) fullPath += PATH_SEPARATOR; fullPath += calledApplicationEntityTitle; // in case the path is not existent, we need to return OFFalse if( !( OFStandard::dirExists( OFString( fullPath ) ) ) ) return( OFFalse ); // if we get to here, the path is existent and we need to return OFTrue return( OFTrue ); } // ---------------------------------------------------------------------------- size_t WlmFileSystemInteractionManager::DetermineMatchingRecords( DcmDataset* searchMask ) { assert( searchMask ); matchingRecords.clear(); OFdirectory_iterator it( dfPath / calledApplicationEntityTitle ); if( FindNextWorklistFile( it ) != OFdirectory_iterator() ) { MatchWorklistFile( *searchMask, *it ); while( FindNextWorklistFile( ++it ) != OFdirectory_iterator() ) MatchWorklistFile( *searchMask, *it ); } else DCMWLM_INFO( "" ); return matchingRecords.size(); } // ---------------------------------------------------------------------------- void WlmFileSystemInteractionManager::MatchWorklistFile( DcmDataset& searchMask, const OFpath& worklistFile ) { // read information from worklist file DcmFileFormat file; OFCondition status = file.loadFile( worklistFile ); if( status.bad() ) { DCMWLM_WARN("Could not read worklist file " << worklistFile << ", file will be ignored: " << status.text()); return; } // extract the data set from worklist file, if any // storing it into an OFshared_ptr ensures it will be freed in the end not matter what if( OFshared_ptr pDataset = OFshared_ptr( file.getAndRemoveDataset() ) ) { if( enableRejectionOfIncompleteWlFiles ) { DCMWLM_INFO("Checking whether worklist file " << worklistFile << " is complete"); // in case option --enable-file-reject is set, we have to check if the current // .wl-file meets certain conditions; in detail, the file's dataset has to be // checked whether it contains all necessary return type 1 attributes and contains // information in all these attributes; if this is condition is not met, the // .wl-file shall be rejected if( !DatasetIsComplete( pDataset.get() ) ) { DCMWLM_WARN("Worklist file " << worklistFile << " is incomplete, file will be ignored"); return; } } // check if the current dataset matches the matching key attribute values if( DatasetMatchesSearchMask( *pDataset, searchMask, MatchingKeys::root ) ) { DCMWLM_INFO("Information from worklist file " << worklistFile << " matches query"); // insert the matching dataset into matchingRecords matchingRecords.push_back( pDataset ); } else DCMWLM_INFO("Information from worklist file " << worklistFile << " does not match query"); } else DCMWLM_WARN("Worklist file " << worklistFile << " is empty, file will be ignored"); } // ---------------------------------------------------------------------------- unsigned long WlmFileSystemInteractionManager::GetNumberOfSequenceItemsForMatchingRecord( DcmTagKey sequenceTag, WlmSuperiorSequenceInfoType *superiorSequenceArray, unsigned long numOfSuperiorSequences, unsigned long idx ) // Date : January 6, 2004 // Author : Thomas Wilkens // Task : For the matching record that is identified through idx, this function returns the number // of items that are contained in the sequence element that is referred to by sequenceTag. // In case this sequence element is itself contained in a certain item of another superior // sequence, superiorSequenceArray contains information about where to find the correct // sequence element. // Parameters : sequenceTag - [in] The tag of the sequence element for which the number of items // shall be determined. // superiorSequenceArray - [in] Array which contains information about superior sequence elements // the given sequence element is contained in. // numOfSuperiorSequences - [in] The number of elements in the above array. // idx - [in] Identifies the record from which the number of sequence items // shall be determined. // Return Value : The number of items that are contained in the sequence element that is referred to by // sequenceTag and that can be found in sequence items which are specified in superiorSequenceArray. { OFCondition cond; DcmSequenceOfItems *sequenceElement = NULL, *tmp = NULL; unsigned long i; // initialize result variable unsigned long numOfItems = 0; // if the sequence in question is not contained in another sequence if( numOfSuperiorSequences == 0 ) { // simply find and get this sequence in the matching dataset (on the main level) cond = matchingRecords[idx]->findAndGetSequence( sequenceTag, sequenceElement, OFFalse ); } else { // if it is contained in (an)other sequence(s), find it cond = matchingRecords[idx]->findAndGetSequence( superiorSequenceArray[0].sequenceTag, sequenceElement, OFFalse ); for( i=1 ; igetItem( superiorSequenceArray[i-1].currentItem )->findAndGetSequence( superiorSequenceArray[i].sequenceTag, tmp, OFFalse ); if( cond.good() ) sequenceElement = tmp; } if( cond.good() ) { cond = sequenceElement->getItem( superiorSequenceArray[ numOfSuperiorSequences - 1 ].currentItem )->findAndGetSequence( sequenceTag, tmp, OFFalse ); if( cond.good() ) sequenceElement = tmp; } } // determine number of items for the sequence in question if( cond.good() ) numOfItems = sequenceElement->card(); // return result return( numOfItems ); } // ---------------------------------------------------------------------------- void WlmFileSystemInteractionManager::GetAttributeValueForMatchingRecord( DcmTagKey tag, WlmSuperiorSequenceInfoType *superiorSequenceArray, unsigned long numOfSuperiorSequences, unsigned long idx, char *&value ) // Date : July 11, 2002 // Author : Thomas Wilkens // Task : This function determines an attribute value of a matching record // and returns this value in a newly created string to the caller. // Parameters : tag - [in] Attribute tag. Specifies which attribute's value shall be returned. // superiorSequenceArray - [in] Array which contains information about superior sequence elements // the given element is contained in. // numOfSuperiorSequences - [in] The number of elements in the above array. // idx - [in] Identifies the record from which the attribute value shall be retrieved. // value - [out] Pointer to a newly created string that contains the requested value. // If value was not found an emtpy string will be returned. // Return Value : none. { OFCondition cond; DcmSequenceOfItems *sequenceElement = NULL, *tmp = NULL; unsigned long i; const char *val = NULL; Uint16 v; size_t len = 0; // if the element in question is not contained in another sequence if( numOfSuperiorSequences == 0 ) { // simply find and get this element in the matching dataset (on the main level); // here, we have to distinguish two cases: attribute PregnancyStatus has to be re- // trieved as a Uint16 value (also note for this case, that this attribute can only // occur on the main level, it will never be contained in a sequence), all other // attributes have to be retrieved as strings if( tag == DCM_PregnancyStatus ) { cond = matchingRecords[idx]->findAndGetUint16( tag, v, 0, OFFalse ); if( cond.good() ) { len = 20; value = new char[len]; sprintf( value, "%u", v ); } else { len = 2; value = new char[len]; OFStandard::strlcpy( value, "4", len ); // a value of "4" in attribute PregnancyStatus means "unknown" in DICOM } } else { cond = matchingRecords[idx]->findAndGetString( tag, val, OFFalse ); if( cond.good() && val != NULL ) { len = strlen( val ) + 1; value = new char[len]; OFStandard::strlcpy( value, val, len ); } else { len = 1; value = new char[len]; OFStandard::strlcpy( value, "", len ); } } } else { // if it is contained in (an)other sequence(s), go through all corresponding sequence items cond = matchingRecords[idx]->findAndGetSequence( superiorSequenceArray[0].sequenceTag, sequenceElement, OFFalse ); for( i=1 ; igetItem( superiorSequenceArray[i-1].currentItem )->findAndGetSequence( superiorSequenceArray[i].sequenceTag, tmp, OFFalse ); if( cond.good() ) sequenceElement = tmp; } // now sequenceElement points to the sequence element in which the attribute // in question can be found; retrieve a value for this attribute (note that // all attributes in sequences can actually be retrieved as strings) if( cond.good() ) { cond = sequenceElement->getItem( superiorSequenceArray[ numOfSuperiorSequences - 1 ].currentItem )->findAndGetString( tag, val, OFFalse ); if( cond.good() && val != NULL ) { len = strlen( val ) + 1; value = new char[len]; OFStandard::strlcpy( value, val, len ); } else { len = 1; value = new char[len]; OFStandard::strlcpy( value, "", len ); } } else { len = 1; value = new char[len]; OFStandard::strlcpy( value, "", len ); } } } // ---------------------------------------------------------------------------- void WlmFileSystemInteractionManager::ClearMatchingRecords() { matchingRecords.clear(); } // ---------------------------------------------------------------------------- OFdirectory_iterator& WlmFileSystemInteractionManager::FindNextWorklistFile( OFdirectory_iterator& it ) { for( ; it != OFdirectory_iterator() && ".wl" != it->path().extension(); ++it ); return it; } // ---------------------------------------------------------------------------- OFBool WlmFileSystemInteractionManager::DatasetIsComplete( DcmDataset *dataset ) // Date : May 2, 2005 // Author : Thomas Wilkens // Task : This function checks if the given dataset (which represents the information from a // worklist file) contains all necessary return type 1 information. According to the // DICOM standard part 4 annex K, the following attributes are type 1 attributes in // C-Find RSP messages: // Attribute Tag Return Key Type // ScheduledProcedureStepSequence (0040,0100) 1 // > ScheduledStationAETitle (0040,0001) 1 // > ScheduledProcedureStepStartDate (0040,0002) 1 // > ScheduledProcedureStepStartTime (0040,0003) 1 // > Modality (0008,0060) 1 // > ScheduledProcedureStepDescription (0040,0007) 1C (The ScheduledProcedureStepDescription (0040,0007) or the ScheduledProtocolCodeSequence (0040,0008) or both shall be supported by the SCP; we actually support both, so we have to check if at least one of the two attributes contains valid information.) // > ScheduledProtocolCodeSequence (0040,0008) 1C (see abobve) // > > CodeValue (0008,0100) 1 // > > CodingSchemeDesignator (0008,0102) 1 // > ScheduledProcedureStepID (0040,0009) 1 // RequestedProcedureID (0040,1001) 1 // RequestedProcedureDescription (0032,1060) 1C (The RequestedProcedureDescription (0032,1060) or the RequestedProcedureCodeSequence (0032,1064) or both shall be supported by the SCP; we actually support both, so we have to check if at least one of the two attributes contains valid information.) // RequestedProcedureCodeSequence (0032,1064) 1C (see abobve) // > > CodeValue (0008,0100) 1 // > > CodingSchemeDesignator (0008,0102) 1 // StudyInstanceUID (0020,000D) 1 // ReferencedStudySequence (0008,1110) 2 // > ReferencedSOPClassUID (0008,1150) 1C (Required if a sequence item is present) // > ReferencedSOPInstanceUID (0008,1155) 1C (Required if a sequence item is present) // ReferencedPatientSequence (0008,1120) 2 // > ReferencedSOPClassUID (0008,1150) 1C (Required if a sequence item is present) // > ReferencedSOPInstanceUID (0008,1155) 1C (Required if a sequence item is present) // PatientName (0010,0010) 1 // PatientID (0010,0020) 1 // Parameters : dataset - [in] The dataset of the worklist file which is currently examined. // Return Value : OFTrue in case the given dataset contains all necessary return type 1 information, // OFFalse otherwise. { DcmElement *scheduledProcedureStepSequence = NULL; // intialize returnValue OFBool complete = OFTrue; DCMWLM_DEBUG("Checking whether dataset is complete ..."); // the dataset is considered to be incomplete... // ...if the ScheduledProcedureStepSequence is missing or // ...if the ScheduledProcedureStepSequence does not have exactly one item if( dataset->findAndGetElement( DCM_ScheduledProcedureStepSequence, scheduledProcedureStepSequence ).bad() || ((DcmSequenceOfItems*)scheduledProcedureStepSequence)->card() != 1 ) { DCMWLM_DEBUG("- ScheduledProcedureStepSequence " << DCM_ScheduledProcedureStepSequence << " is missing or does not have exactly one item"); complete = OFFalse; } else { // so the ScheduledProcedureStepSequence is existent and has exactly one item; // get this one and only item from the ScheduledProcedureStepSequence DcmItem *scheduledProcedureStepSequenceItem = ((DcmSequenceOfItems*)scheduledProcedureStepSequence)->getItem(0); // the dataset is considered to be incomplete... // ...if ScheduledStationAETitle is missing or empty in the ScheduledProcedureStepSequence, or // ...if ScheduledProcedureStepStartDate is missing or empty in the ScheduledProcedureStepSequence, or // ...if ScheduledProcedureStepStartTime is missing or empty in the ScheduledProcedureStepSequence, or // ...if Modality is missing or empty in the ScheduledProcedureStepSequence, or // ...if ScheduledProcedureStepID is missing or empty in the ScheduledProcedureStepSequence, or // ...if RequestedProcedureID is missing or empty in the dataset, or // ...if StudyInstanceUID is missing or empty in the dataset, or // ...if PatientName is missing or empty in the dataset, or // ...if PatientID is missing or empty in the dataset, or // ...if inside the ScheduledProcedureStepSequence, no information can be retrieved from // the ScheduledProcedureStepDescription attribute and (at the same time) also no // complete information can be retrieved from the ScheduledProtocolCodeSequence attribute, or // ...if inside the dataset, no information can be retrieved from the RequestedProcedureDescription // attribute and (at the same time) also no complete information can be retrieved from the // RequestedProcedureCodeSequence attribute, or // ...if inside the dataset, the ReferencedStudySequence is either absent or it is existent and non-empty but incomplete, or // ...if inside the dataset, the ReferencedPatientSequence is either absent or it is existent and non-empty but incomplete if( AttributeIsAbsentOrEmpty( DCM_ScheduledStationAETitle, scheduledProcedureStepSequenceItem ) || AttributeIsAbsentOrEmpty( DCM_ScheduledProcedureStepStartDate, scheduledProcedureStepSequenceItem ) || AttributeIsAbsentOrEmpty( DCM_ScheduledProcedureStepStartTime, scheduledProcedureStepSequenceItem ) || AttributeIsAbsentOrEmpty( DCM_Modality, scheduledProcedureStepSequenceItem ) || AttributeIsAbsentOrEmpty( DCM_ScheduledProcedureStepID, scheduledProcedureStepSequenceItem ) || AttributeIsAbsentOrEmpty( DCM_RequestedProcedureID, dataset ) || AttributeIsAbsentOrEmpty( DCM_StudyInstanceUID, dataset ) || AttributeIsAbsentOrEmpty( DCM_PatientName, dataset ) || AttributeIsAbsentOrEmpty( DCM_PatientID, dataset ) || DescriptionAndCodeSequenceAttributesAreIncomplete( DCM_ScheduledProcedureStepDescription, DCM_ScheduledProtocolCodeSequence, scheduledProcedureStepSequenceItem ) || DescriptionAndCodeSequenceAttributesAreIncomplete( DCM_RequestedProcedureDescription, DCM_RequestedProcedureCodeSequence, dataset ) || ReferencedStudyOrPatientSequenceIsAbsentOrExistentButNonEmptyAndIncomplete( DCM_ReferencedStudySequence, dataset ) || ReferencedStudyOrPatientSequenceIsAbsentOrExistentButNonEmptyAndIncomplete( DCM_ReferencedPatientSequence, dataset ) ) complete = OFFalse; } // return result return( complete ); } // ---------------------------------------------------------------------------- OFBool WlmFileSystemInteractionManager::ReferencedStudyOrPatientSequenceIsAbsentOrExistentButNonEmptyAndIncomplete( DcmTagKey sequenceTagKey, DcmItem *dset ) // Date : May 4, 2005 // Author : Thomas Wilkens // Task : This function checks if the specified sequence attribute is absent or existent but non-empty // and incomplete in the given dataset. // Parameters : sequenceTagKey - [in] The sequence attribute which shall be checked. // dset - [in] The dataset in which the attribute is contained. // Return Value : OFTrue in case the sequence attribute is absent (and cannot be added to the dataset) // or existent but non-empty and incomplete, OFFalse otherwise. { DcmElement *sequence = NULL; OFBool result; // check whether the type 2 sequence attribute is absent if( dset->findAndGetElement( sequenceTagKey, sequence ).bad() ) { DCMWLM_DEBUG("- " << DcmTag(sequenceTagKey).getTagName() << " " << sequenceTagKey << " is missing"); // try to add it to the dataset and return OFFalse if successful if ( dset->insertEmptyElement( sequenceTagKey ).good() ) { DCMWLM_WARN("Added missing type 2 sequence attribute " << sequenceTagKey << " to the current record"); result = OFFalse; } else result = OFTrue; } else { // if the sequence attribute is existent but empty, we want to return OFFalse // (note that the sequence is actually type 2, so being empty is ok) if( ((DcmSequenceOfItems*)sequence)->card() == 0 ) result = OFFalse; else { // if the sequence attribute is existent and non-empty, we need // to check every item in the sequence for completeness result = OFFalse; for( unsigned long i=0 ; i<((DcmSequenceOfItems*)sequence)->card() && !result ; i++ ) { if( AttributeIsAbsentOrEmpty( DCM_ReferencedSOPClassUID, ((DcmSequenceOfItems*)sequence)->getItem(i) ) || AttributeIsAbsentOrEmpty( DCM_ReferencedSOPInstanceUID, ((DcmSequenceOfItems*)sequence)->getItem(i) ) ) result = OFTrue; } if ( result ) DCMWLM_DEBUG("- " << DcmTag(sequenceTagKey).getTagName() << " " << sequenceTagKey << " is incomplete"); } } // return result return( result ); } // ---------------------------------------------------------------------------- OFBool WlmFileSystemInteractionManager::DescriptionAndCodeSequenceAttributesAreIncomplete( DcmTagKey descriptionTagKey, DcmTagKey codeSequenceTagKey, DcmItem *dset ) // Date : April 18, 2017 // Author : Michael Onken // Task : This method ensures that either code or description is set to a non-empty value, // and at the same time none of the attributes is present with a zero-length value. // If one of these requirements are not met, then OFTrue is returned, otherwise OFFalse. // Parameters : descriptionTagKey - [in] The description attribute which shall be checked. // codeSequenceTagKey - [in] The codeSequence attribute which shall be checked. // dset - [in] The dataset in which the attributes are contained. // Return Value : OFFalse (i.e. no error regarding the standard) in case at least // one of both attributes has a non-empty, valid value, and none // is set to an empty value. OFTrue otherwise. { // check if the code sequence attribute is complete, // i.e. if complete information can be retrieved from this attribute // if the attribute is not existent or has no items, we consider it incomplete DcmSequenceOfItems* seq = NULL; if (dset->findAndGetSequence( codeSequenceTagKey, seq ).good()) { if (seq->card() == 0) { DCMWLM_DEBUG("- " << DcmTag(codeSequenceTagKey).getTagName() << " " << codeSequenceTagKey << " is empty"); return OFTrue; } else if (seq->card() > 0) { // if it is existent and has items, check every item for completeness for( unsigned long i=0; i< seq->card(); i++ ) { if( AttributeIsAbsentOrEmpty( DCM_CodeValue, seq->getItem(i) ) || AttributeIsAbsentOrEmpty( DCM_CodingSchemeDesignator, seq->getItem(i) ) ) { DCMWLM_DEBUG("- " << DcmTag(codeSequenceTagKey).getTagName() << " " << codeSequenceTagKey << " has incomplete or empty code entries"); return OFTrue; } } } } // check whether description is valid. If sequence does exists, the attribute // must exist with a valid value. OFString description; if (dset->findAndGetOFStringArray(descriptionTagKey, description).bad()) { if (!seq) { DCMWLM_DEBUG("- " << DcmTag(codeSequenceTagKey).getTagName() << " " << codeSequenceTagKey << " and " << DcmTag(descriptionTagKey) << " " << descriptionTagKey << " are both not set"); return OFTrue; } } // in any case, description must not exist with empty value in the dataset else if (description.empty()) { DCMWLM_DEBUG("- " << DcmTag(descriptionTagKey).getTagName() << " " << descriptionTagKey << " is empty"); return OFTrue; } return OFFalse; } // ---------------------------------------------------------------------------- OFBool WlmFileSystemInteractionManager::AttributeIsAbsentOrEmpty( DcmTagKey elemTagKey, DcmItem *dset ) // Date : May 4, 2005 // Author : Thomas Wilkens // Task : This function checks if the specified attribute is absent or contains an empty value in the given dataset. // Parameters : elemTagKey - [in] The attribute which shall be checked. // dset - [in] The dataset in which the attribute is contained. // Return Value : OFTrue in case the attribute is absent or contains an empty value, OFFalse otherwise. { DcmElement *elem = NULL; if( dset->findAndGetElement( elemTagKey, elem ).bad() || elem->getLength() == 0 ) { DCMWLM_DEBUG("- " << DcmTag(elemTagKey).getTagName() << " " << elemTagKey << " is missing or empty"); return( OFTrue ); } else return( OFFalse ); } OFBool WlmFileSystemInteractionManager::IsUniversalMatch( DcmSequenceOfItems& query, const MatchingKeys& matchingKeys, const OFBool normalize, const OFBool normalizeWildCards ) { DcmItem* pQueryItem = OFstatic_cast( DcmItem*, query.nextInContainer( OFnullptr ) ); if( pQueryItem ) { #ifdef HAVE_CXX11 for( auto& key : matchingKeys.keys ) { #else // remove this bloated version of the code if C++11 ever becomes a requirement of DCMTK for( OFVector >::const_iterator it = matchingKeys.keys.begin(); it != matchingKeys.keys.end(); ++it ) { const OFPair& key = *it; #endif DcmElement* query_elem = OFnullptr; if( pQueryItem->findAndGetElement( key.first, query_elem, OFFalse ).good() && query_elem && !query_elem->isUniversalMatch( normalize, normalizeWildCards && key.second ) ) return OFFalse; } #ifdef HAVE_CXX11 for( auto& combinedKey : matchingKeys.combinedKeys ) { #else // remove this bloated version of the code if C++11 ever becomes a requirement of DCMTK for( OFVector >::const_iterator it = matchingKeys.combinedKeys.begin(); it != matchingKeys.combinedKeys.end(); ++it ) { const OFPair& combinedKey = *it; #endif DcmElement* query_elem = OFnullptr; if( pQueryItem->findAndGetElement( combinedKey.first, query_elem, OFFalse ).good() && query_elem && !query_elem->isUniversalMatch( normalize, normalizeWildCards ) ) return OFFalse; else if( pQueryItem->findAndGetElement( combinedKey.second, query_elem, OFFalse ).good() && query_elem && !query_elem->isUniversalMatch( normalize, normalizeWildCards ) ) return OFFalse; } // sequence matching #ifdef HAVE_CXX11 for( auto& sequenceKey : matchingKeys.sequenceKeys ) { #else // remove this bloated version of the code if C++11 ever becomes a requirement of DCMTK for( OFVector >::const_iterator it = matchingKeys.sequenceKeys.begin(); it != matchingKeys.sequenceKeys.end(); ++it ) { const OFPair& sequenceKey = *it; #endif DcmElement* query_elem = OFnullptr; if( pQueryItem->findAndGetElement( sequenceKey.first, query_elem, OFFalse ).good() && query_elem && query_elem->ident() == EVR_SQ && !IsUniversalMatch( OFstatic_cast( DcmSequenceOfItems&, *query_elem ), sequenceKey.second, normalize, normalizeWildCards ) ) return OFFalse; } } return OFTrue; } OFBool WlmFileSystemInteractionManager::MatchSequences( DcmSequenceOfItems& candidate, DcmSequenceOfItems& query, const MatchingKeys& matchingKeys ) { for( DcmObject* pQueryItem = query.nextInContainer( OFnullptr); pQueryItem; pQueryItem = query.nextInContainer( pQueryItem ) ) for( DcmObject* pCandidateItem = candidate.nextInContainer( OFnullptr); pCandidateItem; pCandidateItem = candidate.nextInContainer( pCandidateItem ) ) if( DatasetMatchesSearchMask( *OFstatic_cast( DcmItem*, pCandidateItem ), *OFstatic_cast( DcmItem*, pQueryItem ), matchingKeys ) ) return OFTrue; return OFFalse; } // ---------------------------------------------------------------------------- OFBool WlmFileSystemInteractionManager::DatasetMatchesSearchMask( DcmItem& dataset, DcmItem& searchMask, const MatchingKeys& matchingKeys ) { #ifdef HAVE_CXX11 for( auto& key : matchingKeys.keys ) { #else // remove this bloated version of the code if C++11 ever becomes a requirement of DCMTK for( OFVector >::const_iterator it = matchingKeys.keys.begin(); it != matchingKeys.keys.end(); ++it ) { const OFPair& key = *it; #endif DcmElement* query = OFnullptr; if( searchMask.findAndGetElement( key.first, query, OFFalse ).good() && query && !query->isUniversalMatch() ) { DcmElement* candidate = OFnullptr; if( dataset.findAndGetElement( key.first, candidate, OFFalse ).bad() || !candidate || !query->matches( *candidate, key.second ) ) return OFFalse; } } #ifdef HAVE_CXX11 for( auto& combinedKey : matchingKeys.combinedKeys ) { #else // remove this bloated version of the code if C++11 ever becomes a requirement of DCMTK for( OFVector >::const_iterator it = matchingKeys.combinedKeys.begin(); it != matchingKeys.combinedKeys.end(); ++it ) { const OFPair& combinedKey = *it; #endif DcmElement* query = OFnullptr; if( searchMask.findAndGetElement( combinedKey.first, query, OFFalse ).good() && query && !query->isUniversalMatch() ) { DcmElement* candidate = OFnullptr; if( dataset.findAndGetElement( combinedKey.first, candidate, OFFalse ).bad() || !candidate ) return OFFalse; DcmElement* secondQuery = OFnullptr; if( searchMask.findAndGetElement( combinedKey.second, secondQuery, OFFalse ).good() && secondQuery && !secondQuery->isUniversalMatch() ) { DcmElement* secondCandidate = OFnullptr; if( dataset.findAndGetElement( combinedKey.second, secondCandidate, OFFalse ).bad() || !secondCandidate || !query->combinationMatches( *secondQuery, *candidate, *secondCandidate ) ) return OFFalse; } else if( !query->matches( *candidate ) ) { return OFFalse; } } else if( searchMask.findAndGetElement( combinedKey.second, query, OFFalse ).good() && query && !query->isUniversalMatch() ) { DcmElement* candidate = OFnullptr; if( dataset.findAndGetElement( combinedKey.second, candidate, OFFalse ).bad() || !candidate || !query->matches( *candidate ) ) return OFFalse; } } // sequence matching #ifdef HAVE_CXX11 for( auto& sequenceKey : matchingKeys.sequenceKeys ) { #else // remove this bloated version of the code if C++11 ever becomes a requirement of DCMTK for( OFVector >::const_iterator it = matchingKeys.sequenceKeys.begin(); it != matchingKeys.sequenceKeys.end(); ++it ) { const OFPair& sequenceKey = *it; #endif DcmElement* query = OFnullptr; if( searchMask.findAndGetElement( sequenceKey.first, query, OFFalse ).good() && query && query->ident() == EVR_SQ && !IsUniversalMatch( OFstatic_cast( DcmSequenceOfItems&, *query ), sequenceKey.second ) ) { DcmElement* candidate = OFnullptr; if( dataset.findAndGetElement( sequenceKey.first, candidate, OFFalse ).bad() || !candidate || candidate->ident() != EVR_SQ || !MatchSequences( OFstatic_cast( DcmSequenceOfItems&, *candidate ), OFstatic_cast( DcmSequenceOfItems&, *query ), sequenceKey.second ) ) return OFFalse; } } return OFTrue; }