/* * * Copyright (C) 2017-2018, 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: dcmdata * * Author: Jan Schlamelcher * * Purpose: Implementing attribute matching for being used in dcmqrdb and dcmwlm etc. * */ #include "dcmtk/config/osconfig.h" /* make sure OS specific configuration is included first */ #include "dcmtk/ofstd/ofmem.h" #include "dcmtk/ofstd/ofdiag.h" #include "dcmtk/dcmdata/dcmatch.h" #include "dcmtk/dcmdata/dcvr.h" #include "dcmtk/dcmdata/dcvrda.h" #include "dcmtk/dcmdata/dcvrdt.h" #include "dcmtk/dcmdata/dcvrtm.h" class DcmAttributeMatching::WildCardMatcher { public: #include DCMTK_DIAGNOSTIC_PUSH #include DCMTK_DIAGNOSTIC_IGNORE_SHADOW // constructor, remembering the end of the query and candidate strings WildCardMatcher( const char* queryDataEnd, const char* candidateDataEnd ) : queryDataEnd( queryDataEnd ) , candidateDataEnd( candidateDataEnd ) { } #include DCMTK_DIAGNOSTIC_POP // the actual match function, taking two pointers to the beginning of // the query and the candidate string OFBool match( const char* queryData, const char* candidateData ) const { // matches all regular chars and '?' wildcard with the candidate string while( queryData != queryDataEnd && candidateData != candidateDataEnd && *queryData != '*' ) { if( *queryData == '?' || *queryData == *candidateData ) { ++queryData; ++candidateData; } else { return OFFalse; } } // if the end of the query is reached, there was no '*' wildcard // therefor it is either a match (if the end of the candidate was // also reached) or not if( queryData == queryDataEnd ) return candidateData == candidateDataEnd; // if the current char in the query is not the '*' wildcard, the // values don't match, since all other chars would have been // matched by the previous while loop if( *queryData != '*' ) return OFFalse; // skip all '*' wildcard characters, because even a string like "****" // equals the sematics of '*'. If the end of the query is reached // any remaining part of the candidate is a match, therefore return // OFTrue do if( ++queryData == queryDataEnd ) return OFTrue; while( *queryData == '*' ); // If this part of the code is reached, at least one non wildcard // character exists in the query after the previously skipped // wildcards. Search for a match of the remaining query characters // in the remaining candidate characters, by recursively calling // match. while( candidateData != candidateDataEnd ) { if( !match( queryData, candidateData ) ) ++candidateData; else return OFTrue; } // if the end of the candidate is reached, both strings don't match. return OFFalse; } private: // the ends of both the query and the candidate string, will remain // constant per match operation const char* const queryDataEnd; const char* const candidateDataEnd; }; DcmAttributeMatching::Range::Range( const void* const data, const size_t size, const char separator ) : first( OFreinterpret_cast( const char* const, data ) ) , firstSize( 0 ) , second( first ) , secondSize( size ) { while( firstSize != secondSize && separator != first[firstSize] ) ++firstSize; if( firstSize != secondSize ) { secondSize = secondSize - firstSize - 1; second = second + firstSize + 1; } } OFBool DcmAttributeMatching::Range::isRange() const { return first != second; } OFBool DcmAttributeMatching::Range::hasOpenBeginning() const { return !firstSize; } OFBool DcmAttributeMatching::Range::hasOpenEnd() const { return !secondSize; } OFBool DcmAttributeMatching::singleValueMatching( const void* queryData, const size_t querySize, const void* candidateData, const size_t candidateSize ) { return !querySize || ( querySize == candidateSize && !memcmp( queryData, candidateData, querySize ) ); } OFBool DcmAttributeMatching::wildCardMatching( const void* queryData, const size_t querySize, const void* candidateData, const size_t candidateSize ) { return !querySize || WildCardMatcher ( OFreinterpret_cast( const char*, queryData ) + querySize, OFreinterpret_cast( const char*, candidateData ) + candidateSize ) .match ( OFreinterpret_cast( const char*, queryData ), OFreinterpret_cast( const char*, candidateData ) ); } OFBool DcmAttributeMatching::checkRangeQuery( OFBool (*check)(const char*,const size_t), const void* queryData, const size_t querySize ) { const Range range( queryData, querySize ); if( !range.isRange() ) return check( range.first, range.firstSize ); return ( range.hasOpenBeginning() || check( range.first, range.firstSize ) ) && ( range.hasOpenEnd() || check( range.second, range.secondSize ) ) ; } template OFBool DcmAttributeMatching::rangeMatchingTemplate( OFCondition (*parse)(const char*,const size_t,T&), const Range& query, const T& candidate ) { T first; if( query.hasOpenBeginning() || parse( query.first, query.firstSize, first ).good() ) { if( !query.isRange() ) return query.firstSize && first == candidate; T second; if( query.hasOpenEnd() || parse( query.second, query.secondSize, second ).good() ) return ( query.hasOpenBeginning() || first <= candidate ) && ( query.hasOpenEnd() || second >= candidate ); } return OFFalse; } template OFBool DcmAttributeMatching::rangeMatchingTemplate( OFCondition (*parse)(const char*,const size_t,T&), const void* queryData, const size_t querySize, const void* candidateData, const size_t candidateSize ) { if( !querySize ) return OFTrue; T candidate; if( parse( OFreinterpret_cast( const char*, candidateData ), candidateSize, candidate ).bad() ) return OFFalse; return rangeMatchingTemplate( parse, Range( queryData, querySize ), candidate ); } OFBool DcmAttributeMatching::isDateQuery( const void* queryData, const size_t querySize ) { return checkRangeQuery( &DcmDate::check, queryData, querySize ); } OFBool DcmAttributeMatching::isTimeQuery( const void* queryData, const size_t querySize ) { return checkRangeQuery( &DcmTime::check, queryData, querySize ); } OFBool DcmAttributeMatching::isDateTimeQuery( const void* queryData, const size_t querySize ) { return checkRangeQuery( &DcmDateTime::check, queryData, querySize ); } OFBool DcmAttributeMatching::rangeMatchingDate( const void* queryData, const size_t querySize, const void* candidateData, const size_t candidateSize ) { return rangeMatchingTemplate( &DcmDate::getOFDateFromString, queryData, querySize, candidateData, candidateSize ); } OFBool DcmAttributeMatching::rangeMatchingTime( const void* queryData, const size_t querySize, const void* candidateData, const size_t candidateSize ) { return rangeMatchingTemplate( &DcmTime::getOFTimeFromString, queryData, querySize, candidateData, candidateSize ); } OFBool DcmAttributeMatching::rangeMatchingDateTime( const void* queryData, const size_t querySize, const void* candidateData, const size_t candidateSize ) { return rangeMatchingTemplate( &DcmDateTime::getOFDateTimeFromString, queryData, querySize, candidateData, candidateSize ); } OFBool DcmAttributeMatching::rangeMatchingDateTime( const void* dateQueryData, const size_t dateQuerySize, const void* timeQueryData, const size_t timeQuerySize, const void* dateCandidateData, const size_t dateCandidateSize, const void* timeCandidateData, const size_t timeCandidateSize ) { if( !dateQuerySize ) return rangeMatchingTime( timeQueryData, timeQuerySize, timeCandidateData, timeCandidateSize ); if( !timeQuerySize ) return rangeMatchingDate( dateQueryData, dateQuerySize, dateCandidateData, dateCandidateSize ); OFDateTime candidate; if( DcmDate::getOFDateFromString( OFreinterpret_cast( const char*, dateCandidateData ), dateCandidateSize, candidate.Date ).bad() ) return OFFalse; if( timeCandidateSize && DcmTime::getOFTimeFromString( OFreinterpret_cast( const char*, timeCandidateData ), timeCandidateSize, candidate.Time ).bad() ) return OFFalse; const Range dateQuery( dateQueryData, dateQuerySize ); const Range timeQuery( timeQueryData, timeQuerySize ); // check that both date/time ranges have the same structure if ( ( dateQuery.isRange() != timeQuery.isRange() ) || ( dateQuery.hasOpenBeginning() && !timeQuery.hasOpenBeginning() ) || ( dateQuery.hasOpenEnd() && !timeQuery.hasOpenEnd() ) ) { // fall back to individually matching them in case they don't return rangeMatchingTemplate( &DcmDate::getOFDateFromString, dateQuery, candidate.getDate() ) && rangeMatchingTemplate( &DcmTime::getOFTimeFromString, timeQuery, candidate.getTime() ); } OFDateTime first; // parse the first date/time if( !dateQuery.hasOpenBeginning() ) { if( DcmDate::getOFDateFromString( dateQuery.first, dateQuery.firstSize, first.Date ).bad() ) return OFFalse; if( !timeQuery.hasOpenBeginning() && DcmTime::getOFTimeFromString( timeQuery.first, timeQuery.firstSize, first.Time ).bad() ) return OFFalse; } if( !dateQuery.isRange() ) return dateQuery.firstSize && first == candidate; OFDateTime second; // parse the second date/time if( !dateQuery.hasOpenEnd() ) { if( DcmDate::getOFDateFromString( dateQuery.second, dateQuery.secondSize, second.Date ).bad() ) return OFFalse; if( !timeQuery.hasOpenEnd() && DcmTime::getOFTimeFromString( timeQuery.second, timeQuery.secondSize, second.Time ).bad() ) return OFFalse; } // compare candidate with the date/time range return ( dateQuery.hasOpenBeginning() || first <= candidate ) && ( dateQuery.hasOpenEnd() || second >= candidate ); } OFBool DcmAttributeMatching::listOfUIDMatching( const void* queryData, const size_t querySize, const void* candidateData, const size_t candidateSize ) { if( !querySize ) return OFTrue; const char* pQuery = OFreinterpret_cast( const char*, queryData ); const char* const pQueryEnd = pQuery + querySize; const char* pCandidate = OFreinterpret_cast( const char*, candidateData ); const char* const pCandidateEnd = pCandidate + candidateSize; // character wise match both strings, reset candidate pointer whenever a // '\\' character is encountered within a multi-valued query. while( pQuery != pQueryEnd ) { if( pCandidate != pCandidateEnd && *pQuery == *pCandidate ) { ++pQuery; ++pCandidate; } else { // test whether the candidate matches with the current value from the query if( pCandidate == pCandidateEnd && *pQuery == '\\' ) return OFTrue; // mismatch, search for a '\\' char to try again with the next value from the query, // return OFFalse if none can be found, i.e. this was the last value. while( *pQuery != '\\' ) if( ++pQuery == pQueryEnd ) return OFFalse; // skip the '\\' character ++pQuery; // reset candidate pointer to the beginning of the candidate pCandidate = OFreinterpret_cast( const char*, candidateData ); } } // the query is at its end, we have a match if the candidate is also return pCandidate == pCandidateEnd; } DcmAttributeMatching::DcmAttributeMatching() : m_pMatch( OFnullptr ) { } DcmAttributeMatching::DcmAttributeMatching( const DcmVR vr ) : m_pMatch( OFnullptr ) { switch( vr.getEVR() ) { default: m_pMatch = &DcmAttributeMatching::singleValueMatching; break; case EVR_AE: case EVR_CS: case EVR_LO: case EVR_LT: case EVR_PN: case EVR_SH: case EVR_ST: case EVR_UC: case EVR_UR: case EVR_UT: m_pMatch = &DcmAttributeMatching::wildCardMatching; break; case EVR_DA: m_pMatch = &DcmAttributeMatching::rangeMatchingDate; break; case EVR_TM: m_pMatch = &DcmAttributeMatching::rangeMatchingTime; break; case EVR_DT: m_pMatch = &DcmAttributeMatching::rangeMatchingDateTime; break; case EVR_UI: m_pMatch = &DcmAttributeMatching::listOfUIDMatching; break; } } DcmAttributeMatching::operator OFBool() const { #include DCMTK_DIAGNOSTIC_PUSH #include DCMTK_DIAGNOSTIC_IGNORE_VISUAL_STUDIO_PERFORMANCE_WARNING return m_pMatch; #include DCMTK_DIAGNOSTIC_POP } OFBool DcmAttributeMatching::operator!() const { return !m_pMatch; } OFBool DcmAttributeMatching::operator()( const void* queryData, const size_t querySize, const void* candidateData, const size_t candidateSize ) const { assert( m_pMatch ); return m_pMatch( queryData, querySize, candidateData, candidateSize ); }