Coverage Report

Created: 2026-05-16 06:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dcmtk/dcmdata/libsrc/dcmatch.cc
Line
Count
Source
1
/*
2
 *
3
 *  Copyright (C) 2017-2022, 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:  dcmdata
15
 *
16
 *  Author:  Jan Schlamelcher
17
 *
18
 *  Purpose: Implementing attribute matching for being used in dcmqrdb and dcmwlm etc.
19
 *
20
 */
21
22
#include "dcmtk/config/osconfig.h"    /* make sure OS specific configuration is included first */
23
24
#include "dcmtk/ofstd/ofmem.h"
25
#include "dcmtk/ofstd/ofdiag.h"
26
#include "dcmtk/dcmdata/dcmatch.h"
27
#include "dcmtk/dcmdata/dcvr.h"
28
#include "dcmtk/dcmdata/dcvrda.h"
29
#include "dcmtk/dcmdata/dcvrdt.h"
30
#include "dcmtk/dcmdata/dcvrtm.h"
31
32
class DcmAttributeMatching::WildCardMatcher
33
{
34
public:
35
36
#include DCMTK_DIAGNOSTIC_PUSH
37
#include DCMTK_DIAGNOSTIC_IGNORE_SHADOW
38
    // constructor, remembering the end of the query and candidate strings
39
    WildCardMatcher( const char* queryDataEnd, const char* candidateDataEnd )
40
0
    : queryDataEnd( queryDataEnd )
41
0
    , candidateDataEnd( candidateDataEnd )
42
0
    {
43
44
0
    }
45
#include DCMTK_DIAGNOSTIC_POP
46
47
    // the actual match function, taking two pointers to the beginning of
48
    // the query and the candidate string
49
    OFBool match( const char* queryData, const char* candidateData ) const
50
0
    {
51
        // matches all regular chars and '?' wildcard with the candidate string
52
0
        while( queryData != queryDataEnd && candidateData != candidateDataEnd && *queryData != '*' )
53
0
        {
54
0
            if( *queryData == '?' || *queryData == *candidateData )
55
0
            {
56
0
                ++queryData;
57
0
                ++candidateData;
58
0
            }
59
0
            else
60
0
            {
61
0
                return OFFalse;
62
0
            }
63
0
        }
64
        // if the end of the query is reached, there was no '*' wildcard
65
        // therefore it is either a match (if the end of the candidate was
66
        // also reached) or not
67
0
        if( queryData == queryDataEnd )
68
0
            return candidateData == candidateDataEnd;
69
        // if the current char in the query is not the '*' wildcard, the
70
        // values don't match, since all other chars would have been
71
        // matched by the previous while loop
72
0
        if( *queryData != '*' )
73
0
            return OFFalse;
74
        // skip all '*' wildcard characters, because even a string like "****"
75
        // equals the semantics of '*'. If the end of the query is reached
76
        // any remaining part of the candidate is a match, therefore return
77
        // OFTrue
78
0
        do if( ++queryData == queryDataEnd )
79
0
            return OFTrue;
80
0
        while( *queryData == '*' );
81
        // If this part of the code is reached, at least one non wildcard
82
        // character exists in the query after the previously skipped
83
        // wildcards. Search for a match of the remaining query characters
84
        // in the remaining candidate characters, by recursively calling
85
        // match.
86
0
        while( candidateData != candidateDataEnd )
87
0
        {
88
0
            if( !match( queryData, candidateData ) )
89
0
                ++candidateData;
90
0
            else
91
0
                return OFTrue;
92
0
        }
93
        // if the end of the candidate is reached, both strings don't match.
94
0
        return OFFalse;
95
0
    }
96
97
private:
98
    // the ends of both the query and the candidate string, will remain
99
    // constant per match operation
100
    const char* const queryDataEnd;
101
    const char* const candidateDataEnd;
102
};
103
104
DcmAttributeMatching::Range::Range( const void* const data, const size_t size, const char separator )
105
0
: first( OFreinterpret_cast( const char* const, data ) )
106
0
, firstSize( 0 )
107
0
, second( first )
108
0
, secondSize( size )
109
0
{
110
0
    while( firstSize != secondSize && separator != first[firstSize] )
111
0
        ++firstSize;
112
0
    if( firstSize != secondSize )
113
0
    {
114
0
        secondSize = secondSize - firstSize - 1;
115
0
        second = second + firstSize + 1;
116
0
    }
117
0
}
118
119
OFBool DcmAttributeMatching::Range::isRange() const
120
0
{
121
0
    return first != second;
122
0
}
123
124
125
OFBool DcmAttributeMatching::Range::hasOpenBeginning() const
126
0
{
127
0
    return !firstSize;
128
0
}
129
130
OFBool DcmAttributeMatching::Range::hasOpenEnd() const
131
0
{
132
0
    return !secondSize;
133
0
}
134
135
OFBool DcmAttributeMatching::singleValueMatching( const void* queryData, const size_t querySize,
136
                                                  const void* candidateData, const size_t candidateSize )
137
0
{
138
0
    return !querySize || ( querySize == candidateSize && !memcmp( queryData, candidateData, querySize ) );
139
0
}
140
141
OFBool DcmAttributeMatching::wildCardMatching( const void* queryData, const size_t querySize,
142
                                               const void* candidateData, const size_t candidateSize )
143
0
{
144
0
    return !querySize || WildCardMatcher
145
0
    (
146
0
        OFreinterpret_cast( const char*, queryData ) + querySize,
147
0
        OFreinterpret_cast( const char*, candidateData ) + candidateSize
148
0
    )
149
0
    .match
150
0
    (
151
0
        OFreinterpret_cast( const char*, queryData ),
152
0
        OFreinterpret_cast( const char*, candidateData )
153
0
    );
154
0
}
155
156
OFBool DcmAttributeMatching::checkRangeQuery( OFBool (*check)(const char*,const size_t),
157
                                              const void* queryData, const size_t querySize )
158
0
{
159
0
    const Range range( queryData, querySize );
160
0
    if( !range.isRange() )
161
0
        return check( range.first, range.firstSize );
162
0
    return ( range.hasOpenBeginning() || check( range.first, range.firstSize ) ) &&
163
0
        ( range.hasOpenEnd() || check( range.second, range.secondSize ) )
164
0
    ;
165
0
}
166
167
template<typename T>
168
OFBool DcmAttributeMatching::rangeMatchingTemplate( OFCondition (*parse)(const char*,const size_t,T&),
169
                                                    const Range& query, const T& candidate )
170
0
{
171
0
    T first;
172
0
    if( query.hasOpenBeginning() || parse( query.first, query.firstSize, first ).good() )
173
0
    {
174
0
        if( !query.isRange() )
175
0
            return query.firstSize && first == candidate;
176
0
        T second;
177
0
        if( query.hasOpenEnd() || parse( query.second, query.secondSize, second ).good() )
178
0
            return ( query.hasOpenBeginning() || first <= candidate )
179
0
                && ( query.hasOpenEnd() || second >= candidate );
180
0
    }
181
0
    return OFFalse;
182
0
}
Unexecuted instantiation: bool DcmAttributeMatching::rangeMatchingTemplate<OFDateTime>(OFCondition (*)(char const*, unsigned long, OFDateTime&), DcmAttributeMatching::Range const&, OFDateTime const&)
Unexecuted instantiation: bool DcmAttributeMatching::rangeMatchingTemplate<OFDate>(OFCondition (*)(char const*, unsigned long, OFDate&), DcmAttributeMatching::Range const&, OFDate const&)
Unexecuted instantiation: bool DcmAttributeMatching::rangeMatchingTemplate<OFTime>(OFCondition (*)(char const*, unsigned long, OFTime&), DcmAttributeMatching::Range const&, OFTime const&)
183
184
template<typename T>
185
OFBool DcmAttributeMatching::rangeMatchingTemplate( OFCondition (*parse)(const char*,const size_t,T&),
186
                                                    const void* queryData, const size_t querySize,
187
                                                    const void* candidateData, const size_t candidateSize )
188
0
{
189
0
    if( !querySize )
190
0
        return OFTrue;
191
0
    T candidate;
192
0
    if( parse( OFreinterpret_cast( const char*, candidateData ), candidateSize, candidate ).bad() )
193
0
        return OFFalse;
194
0
    return rangeMatchingTemplate( parse, Range( queryData, querySize ), candidate );
195
0
}
Unexecuted instantiation: bool DcmAttributeMatching::rangeMatchingTemplate<OFDate>(OFCondition (*)(char const*, unsigned long, OFDate&), void const*, unsigned long, void const*, unsigned long)
Unexecuted instantiation: bool DcmAttributeMatching::rangeMatchingTemplate<OFTime>(OFCondition (*)(char const*, unsigned long, OFTime&), void const*, unsigned long, void const*, unsigned long)
Unexecuted instantiation: bool DcmAttributeMatching::rangeMatchingTemplate<OFDateTime>(OFCondition (*)(char const*, unsigned long, OFDateTime&), void const*, unsigned long, void const*, unsigned long)
196
197
OFBool DcmAttributeMatching::isDateQuery( const void* queryData, const size_t querySize )
198
0
{
199
0
    return checkRangeQuery( &DcmDate::check, queryData, querySize );
200
0
}
201
202
OFBool DcmAttributeMatching::isTimeQuery( const void* queryData, const size_t querySize )
203
0
{
204
0
    return checkRangeQuery( &DcmTime::check, queryData, querySize );
205
0
}
206
207
OFBool DcmAttributeMatching::isDateTimeQuery( const void* queryData, const size_t querySize )
208
0
{
209
0
    return checkRangeQuery( &DcmDateTime::check, queryData, querySize );
210
0
}
211
212
OFBool DcmAttributeMatching::rangeMatchingDate( const void* queryData, const size_t querySize,
213
                                                const void* candidateData, const size_t candidateSize )
214
0
{
215
0
    return rangeMatchingTemplate( &DcmDate::getOFDateFromString, queryData, querySize, candidateData, candidateSize );
216
0
}
217
218
OFBool DcmAttributeMatching::rangeMatchingTime( const void* queryData, const size_t querySize,
219
                                                const void* candidateData, const size_t candidateSize )
220
0
{
221
0
    return rangeMatchingTemplate( &DcmTime::getOFTimeFromString, queryData, querySize, candidateData, candidateSize );
222
0
}
223
224
OFBool DcmAttributeMatching::rangeMatchingDateTime( const void* queryData, const size_t querySize,
225
                                                    const void* candidateData, const size_t candidateSize )
226
0
{
227
0
    return rangeMatchingTemplate( &DcmDateTime::getOFDateTimeFromString, queryData, querySize, candidateData, candidateSize );
228
0
}
229
230
OFBool DcmAttributeMatching::rangeMatchingDateTime( const void* dateQueryData, const size_t dateQuerySize,
231
                                                    const void* timeQueryData, const size_t timeQuerySize,
232
                                                    const void* dateCandidateData, const size_t dateCandidateSize,
233
                                                    const void* timeCandidateData, const size_t timeCandidateSize )
234
0
{
235
0
    if( !dateQuerySize )
236
0
        return rangeMatchingTime( timeQueryData, timeQuerySize, timeCandidateData, timeCandidateSize );
237
0
    if( !timeQuerySize )
238
0
        return rangeMatchingDate( dateQueryData, dateQuerySize, dateCandidateData, dateCandidateSize );
239
0
    OFDateTime candidate;
240
0
    if( DcmDate::getOFDateFromString( OFreinterpret_cast( const char*, dateCandidateData ), dateCandidateSize, candidate.Date ).bad() )
241
0
        return OFFalse;
242
0
    if( timeCandidateSize && DcmTime::getOFTimeFromString( OFreinterpret_cast( const char*, timeCandidateData ), timeCandidateSize, candidate.Time ).bad() )
243
0
        return OFFalse;
244
0
    const Range dateQuery( dateQueryData, dateQuerySize );
245
0
    const Range timeQuery( timeQueryData, timeQuerySize );
246
    // check that both date/time ranges have the same structure
247
0
    if
248
0
    (
249
0
        ( dateQuery.isRange() != timeQuery.isRange() )                    ||
250
0
        ( dateQuery.hasOpenBeginning() && !timeQuery.hasOpenBeginning() ) ||
251
0
        ( dateQuery.hasOpenEnd() && !timeQuery.hasOpenEnd() )
252
0
    )
253
0
    {
254
        // fall back to individually matching them in case they don't
255
0
        return rangeMatchingTemplate( &DcmDate::getOFDateFromString, dateQuery, candidate.getDate() )
256
0
            && rangeMatchingTemplate( &DcmTime::getOFTimeFromString, timeQuery, candidate.getTime() );
257
0
    }
258
0
    OFDateTime first;
259
    // parse the first date/time
260
0
    if( !dateQuery.hasOpenBeginning() )
261
0
    {
262
0
        if( DcmDate::getOFDateFromString( dateQuery.first, dateQuery.firstSize, first.Date ).bad() )
263
0
            return OFFalse;
264
0
        if( !timeQuery.hasOpenBeginning() && DcmTime::getOFTimeFromString( timeQuery.first, timeQuery.firstSize, first.Time ).bad() )
265
0
            return OFFalse;
266
0
    }
267
0
    if( !dateQuery.isRange() )
268
0
        return dateQuery.firstSize && first == candidate;
269
0
    OFDateTime second;
270
    // parse the second date/time
271
0
    if( !dateQuery.hasOpenEnd() )
272
0
    {
273
0
        if( DcmDate::getOFDateFromString( dateQuery.second, dateQuery.secondSize, second.Date ).bad() )
274
0
            return OFFalse;
275
0
        if( !timeQuery.hasOpenEnd() && DcmTime::getOFTimeFromString( timeQuery.second, timeQuery.secondSize, second.Time ).bad() )
276
0
            return OFFalse;
277
0
    }
278
    // compare candidate with the date/time range
279
0
    return ( dateQuery.hasOpenBeginning() || first <= candidate )
280
0
        && ( dateQuery.hasOpenEnd() || second >= candidate );
281
0
}
282
283
OFBool DcmAttributeMatching::listOfUIDMatching( const void* queryData, const size_t querySize,
284
                                                const void* candidateData, const size_t candidateSize )
285
0
{
286
0
    if( !querySize )
287
0
        return OFTrue;
288
0
    const char* pQuery = OFreinterpret_cast( const char*, queryData );
289
0
    const char* const pQueryEnd = pQuery + querySize;
290
0
    const char* pCandidate = OFreinterpret_cast( const char*, candidateData );
291
0
    const char* const pCandidateEnd = pCandidate + candidateSize;
292
    // character wise match both strings, reset candidate pointer whenever a
293
    // '\\' character is encountered within a multi-valued query.
294
0
    while( pQuery != pQueryEnd )
295
0
    {
296
0
        if( pCandidate != pCandidateEnd && *pQuery == *pCandidate )
297
0
        {
298
0
            ++pQuery;
299
0
            ++pCandidate;
300
0
        }
301
0
        else
302
0
        {
303
            // test whether the candidate matches with the current value from the query
304
0
            if( pCandidate == pCandidateEnd && *pQuery == '\\' )
305
0
                return OFTrue;
306
            // mismatch, search for a '\\' char to try again with the next value from the query,
307
            // return OFFalse if none can be found, i.e. this was the last value.
308
0
            while( *pQuery != '\\' )
309
0
                if( ++pQuery == pQueryEnd )
310
0
                    return OFFalse;
311
            // skip the '\\' character
312
0
            ++pQuery;
313
            // reset candidate pointer to the beginning of the candidate
314
0
            pCandidate = OFreinterpret_cast( const char*, candidateData );
315
0
        }
316
0
    }
317
    // the query is at its end, we have a match if the candidate is also
318
0
    return pCandidate == pCandidateEnd;
319
0
}
320
321
DcmAttributeMatching::DcmAttributeMatching()
322
0
: m_pMatch( OFnullptr )
323
0
{
324
325
0
}
326
327
DcmAttributeMatching::DcmAttributeMatching( const DcmVR vr )
328
0
: m_pMatch( OFnullptr )
329
0
{
330
0
    switch( vr.getEVR() )
331
0
    {
332
0
    default:
333
0
        m_pMatch = &DcmAttributeMatching::singleValueMatching;
334
0
        break;
335
336
0
    case EVR_AE:
337
0
    case EVR_CS:
338
0
    case EVR_LO:
339
0
    case EVR_LT:
340
0
    case EVR_PN:
341
0
    case EVR_SH:
342
0
    case EVR_ST:
343
0
    case EVR_UC:
344
0
    case EVR_UR:
345
0
    case EVR_UT:
346
0
        m_pMatch = &DcmAttributeMatching::wildCardMatching;
347
0
        break;
348
349
0
    case EVR_DA:
350
0
        m_pMatch = &DcmAttributeMatching::rangeMatchingDate;
351
0
        break;
352
353
0
    case EVR_TM:
354
0
        m_pMatch = &DcmAttributeMatching::rangeMatchingTime;
355
0
        break;
356
357
0
    case EVR_DT:
358
0
        m_pMatch = &DcmAttributeMatching::rangeMatchingDateTime;
359
0
        break;
360
361
0
    case EVR_UI:
362
0
        m_pMatch = &DcmAttributeMatching::listOfUIDMatching;
363
0
        break;
364
0
    }
365
0
}
366
367
DcmAttributeMatching::operator OFBool() const
368
0
{
369
0
#include DCMTK_DIAGNOSTIC_PUSH
370
0
#include DCMTK_DIAGNOSTIC_IGNORE_VISUAL_STUDIO_PERFORMANCE_WARNING
371
0
    return m_pMatch;
372
0
#include DCMTK_DIAGNOSTIC_POP
373
0
}
374
375
OFBool DcmAttributeMatching::operator!() const
376
0
{
377
0
    return !m_pMatch;
378
0
}
379
380
OFBool DcmAttributeMatching::operator()( const void* queryData, const size_t querySize,
381
                                         const void* candidateData, const size_t candidateSize ) const
382
0
{
383
    assert( m_pMatch );
384
0
    return m_pMatch( queryData, querySize, candidateData, candidateSize );
385
0
}