Skip to content

Commit 705c1ea

Browse files
committed
Support wildcards in equals query
1 parent bae3bed commit 705c1ea

File tree

5 files changed

+47
-31
lines changed

5 files changed

+47
-31
lines changed

DcmFind.Tests/TestsForQuery.cs

+13
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,19 @@ public void ShouldNotMatchWhenDicomTagIsNotPresent()
4646

4747
query.Matches(_dicomDataset).Should().BeFalse();
4848
}
49+
50+
[Theory]
51+
[InlineData("%apple")]
52+
[InlineData("%Apple")]
53+
[InlineData("Pine%")]
54+
[InlineData("pine%")]
55+
[InlineData("Pine%apple")]
56+
public void ShouldMatchWhenValuesMatchesWildcardInBeginning(string value)
57+
{
58+
var query = new EqualsQuery(DicomTag.AccessionNumber, value);
59+
60+
query.Matches(_dicomDataset).Should().BeTrue();
61+
}
4962
}
5063

5164
public class TestsForLowerThanQuery : TestsForQuery

DcmFind/DicomTagParser.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ namespace DcmFind
77
{
88
public static class DicomTagParser
99
{
10-
public static bool TryParse(string dicomTagAsString, out DicomTag dicomTag)
10+
public static bool TryParse(string dicomTagAsString, out DicomTag? dicomTag)
1111
{
1212
try
1313
{
@@ -22,7 +22,7 @@ public static bool TryParse(string dicomTagAsString, out DicomTag dicomTag)
2222
.FirstOrDefault(f => string.Equals(f.Name, dicomTagAsString));
2323
if (field != null)
2424
{
25-
dicomTag = (DicomTag) field.GetValue(null);
25+
dicomTag = (DicomTag?) field.GetValue(null);
2626
return true;
2727
}
2828

DcmFind/Program.cs

+26-25
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,19 @@ public class Options
1616
{
1717
[Option('d', "directory", Default = ".", HelpText = "Search for *.dcm files in this directory")]
1818

19-
public string Directory { get; set; }
19+
public string? Directory { get; set; }
2020

2121
[Option('f', "filePattern", Default = "*.dcm", HelpText = "Only query files that satisfy this file pattern")]
22-
public string FilePattern { get; set; }
22+
public string? FilePattern { get; set; }
2323

2424
[Option('r', "recursive", Default = true, HelpText = "Search recursively in nested directories")]
2525
public bool Recursive { get; set; }
2626

2727
[Option('l', "limit", Default = 100, HelpText = "Limit results and stop finding after this many results")]
2828
public int Limit { get; set; }
2929

30-
[Option(shortName: 'q', longName: "query", Default = "", Required = true, HelpText = "The query that should be applied")]
31-
public string Query { get; set; }
30+
[Option(shortName: 'q', longName: "query", Default = "", Separator = ',', Min = 1, HelpText = "The query that should be applied")]
31+
public IEnumerable<string>? Query { get; set; }
3232
}
3333
// ReSharper restore UnusedAutoPropertyAccessor.Global
3434
// ReSharper restore MemberCanBePrivate.Global
@@ -53,15 +53,20 @@ private static void Fail(IEnumerable<Error> errors)
5353
private static void Query(Options options)
5454
{
5555
var directory = new DirectoryInfo(options.Directory).FullName;
56+
var filePattern = options.FilePattern;
57+
var recursive = options.Recursive;
58+
var query = options.Query;
59+
var limit = options.Limit;
60+
if (filePattern == null || query == null)
61+
return;
5662

5763
if (!Directory.Exists(directory))
5864
{
5965
Console.Error.WriteLine($"Invalid directory: {directory} does not exist");
6066
return;
6167
}
6268

63-
var filePattern = options.FilePattern;
64-
var recursive = options.Recursive;
69+
6570
var files = Files(directory, filePattern, recursive);
6671
// ReSharper disable PossibleMultipleEnumeration
6772
if (!files.Any())
@@ -70,13 +75,19 @@ private static void Query(Options options)
7075
return;
7176
}
7277

73-
if (!QueryParser.TryParse(options.Query, out var query))
78+
var queries = new List<IQuery>();
79+
foreach (var q in options.Query ?? Array.Empty<string>())
7480
{
75-
Console.Error.WriteLine($"Invalid query: {query}");
76-
return;
81+
if (!QueryParser.TryParse(q, out var parsedQuery) || parsedQuery == null)
82+
{
83+
Console.Error.WriteLine($"Invalid query: {query}");
84+
return;
85+
}
86+
87+
queries.Add(parsedQuery);
7788
}
7889

79-
foreach(var result in Results(files, query, options.Limit))
90+
foreach(var result in Results(files, queries, limit))
8091
Console.WriteLine(result);
8192
// ReSharper restore PossibleMultipleEnumeration
8293
}
@@ -86,7 +97,7 @@ private static IEnumerable<string> Files(string directory, string filePattern, b
8697
return Directory.EnumerateFiles(directory, filePattern, recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
8798
}
8899

89-
private static DicomFile ToDicomFile(string file)
100+
private static DicomFile? ToDicomFile(string file)
90101
{
91102
try
92103
{
@@ -100,24 +111,14 @@ private static DicomFile ToDicomFile(string file)
100111
}
101112
}
102113

103-
private static bool NotNull(DicomFile dicomFile)
104-
{
105-
return dicomFile != null;
106-
}
107-
108-
private static string FileName(DicomFile dicomFile)
109-
{
110-
return dicomFile.File.Name;
111-
}
112-
113-
private static IEnumerable<string> Results(IEnumerable<string> files, IQuery query, int limit)
114+
private static IEnumerable<string> Results(IEnumerable<string> files, List<IQuery> queries, int limit)
114115
{
115116
return files
116117
.Select(ToDicomFile)
117-
.Where(NotNull)
118-
.Where(f => query.Matches(f.Dataset))
118+
.Where(f => f != null && queries.All(q => q.Matches(f.Dataset)))
119119
.Take(limit)
120-
.Select(FileName);
120+
.Select(f => f?.File?.Name)
121+
.Where(fileName => fileName != null);
121122
}
122123
}
123124
}

DcmFind/Query.cs

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Globalization;
3+
using System.Text.RegularExpressions;
34
using Dicom;
45

56
namespace DcmFind
@@ -17,8 +18,9 @@ public EqualsQuery(DicomTag dicomTag, string value)
1718
{
1819
if (dicomTag == null) throw new ArgumentNullException(nameof(dicomTag));
1920
if (value == null) throw new ArgumentNullException(nameof(value));
20-
21-
_predicate = dicomDataset => dicomDataset.TryGetString(dicomTag, out var dicomTagValue) && string.Equals(dicomTagValue, value, StringComparison.OrdinalIgnoreCase);
21+
var pattern = $"^{Regex.Escape(value).Replace("%", ".*")}$";
22+
var regex = new Regex(pattern, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.CultureInvariant);
23+
_predicate = dicomDataset => dicomDataset.TryGetString(dicomTag, out var dicomTagValue) && regex.IsMatch(dicomTagValue);
2224
}
2325

2426
public bool Matches(DicomDataset dicomDataset)

DcmFind/QueryParser.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ public static class QueryParser
77
{
88
private static readonly string[] SupportedOperators = {"<=", ">=", "=", "<", ">" };
99

10-
public static bool TryParse(string queryAsString, out IQuery query)
10+
public static bool TryParse(string queryAsString, out IQuery? query)
1111
{
1212
query = null;
1313

@@ -29,7 +29,7 @@ public static bool TryParse(string queryAsString, out IQuery query)
2929

3030
var @operator = matchedOperator.Operator;
3131
var dicomTagAsString = queryAsString.Substring(0, matchedOperator.Index);
32-
if (!DicomTagParser.TryParse(dicomTagAsString, out var dicomTag))
32+
if (!DicomTagParser.TryParse(dicomTagAsString, out var dicomTag) || dicomTag == null)
3333
{
3434
return false;
3535
}

0 commit comments

Comments
 (0)