Free cookie consent management tool by TermsFeed Policy Generator

source: trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.EPPlus/4.0.3/EPPlus-4.0.3/Packaging/DotNetZip/FileSelector.cs @ 12074

Last change on this file since 12074 was 12074, checked in by sraggl, 9 years ago

#2341: Added EPPlus-4.0.3 to ExtLibs

File size: 62.7 KB
Line 
1//#define SelectorTrace
2
3// FileSelector.cs
4// ------------------------------------------------------------------
5//
6// Copyright (c) 2008-2011 Dino Chiesa.
7// All rights reserved.
8//
9// This code module is part of DotNetZip, a zipfile class library.
10//
11// ------------------------------------------------------------------
12//
13// This code is licensed under the Microsoft Public License.
14// See the file License.txt for the license details.
15// More info on: http://dotnetzip.codeplex.com
16//
17// ------------------------------------------------------------------
18//
19// last saved: <2011-August-05 11:03:11>
20//
21// ------------------------------------------------------------------
22//
23// This module implements a "file selector" that finds files based on a
24// set of inclusion criteria, including filename, size, file time, and
25// potentially file attributes.  The criteria are given in a string with
26// a simple expression language. Examples:
27//
28// find all .txt files:
29//     name = *.txt
30//
31// shorthand for the above
32//     *.txt
33//
34// all files modified after January 1st, 2009
35//     mtime > 2009-01-01
36//
37// All .txt files modified after the first of the year
38//     name = *.txt  AND  mtime > 2009-01-01
39//
40// All .txt files modified after the first of the year, or any file with the archive bit set
41//     (name = *.txt  AND  mtime > 2009-01-01) or (attribtues = A)
42//
43// All .txt files or any file greater than 1mb in size
44//     (name = *.txt  or  size > 1mb)
45//
46// and so on.
47// ------------------------------------------------------------------
48
49
50using System;
51using System.Globalization;
52using System.IO;
53using System.Text;
54using System.Reflection;
55using System.ComponentModel;
56using System.Text.RegularExpressions;
57using System.Collections.Generic;
58#if SILVERLIGHT
59using System.Linq;
60#endif
61
62namespace OfficeOpenXml.Packaging.Ionic
63{
64
65    /// <summary>
66    /// Enumerates the options for a logical conjunction. This enum is intended for use
67    /// internally by the FileSelector class.
68    /// </summary>
69    internal enum LogicalConjunction
70    {
71        NONE,
72        AND,
73        OR,
74        XOR,
75    }
76
77    internal enum WhichTime
78    {
79        atime,
80        mtime,
81        ctime,
82    }
83
84
85    internal enum ComparisonOperator
86    {
87        [Description(">")]
88        GreaterThan,
89        [Description(">=")]
90        GreaterThanOrEqualTo,
91        [Description("<")]
92        LesserThan,
93        [Description("<=")]
94        LesserThanOrEqualTo,
95        [Description("=")]
96        EqualTo,
97        [Description("!=")]
98        NotEqualTo
99    }
100
101
102    internal abstract partial class SelectionCriterion
103    {
104        internal virtual bool Verbose
105        {
106            get;set;
107        }
108        internal abstract bool Evaluate(string filename);
109
110        [System.Diagnostics.Conditional("SelectorTrace")]
111        protected static void CriterionTrace(string format, params object[] args)
112        {
113            //System.Console.WriteLine("  " + format, args);
114        }
115    }
116
117
118    internal partial class SizeCriterion : SelectionCriterion
119    {
120        internal ComparisonOperator Operator;
121        internal Int64 Size;
122
123        public override String ToString()
124        {
125            StringBuilder sb = new StringBuilder();
126            sb.Append("size ").Append(EnumUtil.GetDescription(Operator)).Append(" ").Append(Size.ToString());
127            return sb.ToString();
128        }
129
130        internal override bool Evaluate(string filename)
131        {
132            System.IO.FileInfo fi = new System.IO.FileInfo(filename);
133            CriterionTrace("SizeCriterion::Evaluate('{0}' [{1}])",
134                           filename, this.ToString());
135            return _Evaluate(fi.Length);
136        }
137
138        private bool _Evaluate(Int64 Length)
139        {
140            bool result = false;
141            switch (Operator)
142            {
143                case ComparisonOperator.GreaterThanOrEqualTo:
144                    result = Length >= Size;
145                    break;
146                case ComparisonOperator.GreaterThan:
147                    result = Length > Size;
148                    break;
149                case ComparisonOperator.LesserThanOrEqualTo:
150                    result = Length <= Size;
151                    break;
152                case ComparisonOperator.LesserThan:
153                    result = Length < Size;
154                    break;
155                case ComparisonOperator.EqualTo:
156                    result = Length == Size;
157                    break;
158                case ComparisonOperator.NotEqualTo:
159                    result = Length != Size;
160                    break;
161                default:
162                    throw new ArgumentException("Operator");
163            }
164            return result;
165        }
166
167    }
168
169
170
171    internal partial class TimeCriterion : SelectionCriterion
172    {
173        internal ComparisonOperator Operator;
174        internal WhichTime Which;
175        internal DateTime Time;
176
177        public override String ToString()
178        {
179            StringBuilder sb = new StringBuilder();
180            sb.Append(Which.ToString()).Append(" ").Append(EnumUtil.GetDescription(Operator)).Append(" ").Append(Time.ToString("yyyy-MM-dd-HH:mm:ss"));
181            return sb.ToString();
182        }
183
184        internal override bool Evaluate(string filename)
185        {
186            DateTime x;
187            switch (Which)
188            {
189                case WhichTime.atime:
190                    x = System.IO.File.GetLastAccessTime(filename).ToUniversalTime();
191                    break;
192                case WhichTime.mtime:
193                    x = System.IO.File.GetLastWriteTime(filename).ToUniversalTime();
194                    break;
195                case WhichTime.ctime:
196                    x = System.IO.File.GetCreationTime(filename).ToUniversalTime();
197                    break;
198                default:
199                    throw new ArgumentException("Operator");
200            }
201            CriterionTrace("TimeCriterion({0},{1})= {2}", filename, Which.ToString(), x);
202            return _Evaluate(x);
203        }
204
205
206        private bool _Evaluate(DateTime x)
207        {
208            bool result = false;
209            switch (Operator)
210            {
211                case ComparisonOperator.GreaterThanOrEqualTo:
212                    result = (x >= Time);
213                    break;
214                case ComparisonOperator.GreaterThan:
215                    result = (x > Time);
216                    break;
217                case ComparisonOperator.LesserThanOrEqualTo:
218                    result = (x <= Time);
219                    break;
220                case ComparisonOperator.LesserThan:
221                    result = (x < Time);
222                    break;
223                case ComparisonOperator.EqualTo:
224                    result = (x == Time);
225                    break;
226                case ComparisonOperator.NotEqualTo:
227                    result = (x != Time);
228                    break;
229                default:
230                    throw new ArgumentException("Operator");
231            }
232
233            CriterionTrace("TimeCriterion: {0}", result);
234            return result;
235        }
236    }
237
238
239
240    internal partial class NameCriterion : SelectionCriterion
241    {
242        private Regex _re;
243        private String _regexString;
244        internal ComparisonOperator Operator;
245        private string _MatchingFileSpec;
246        internal virtual string MatchingFileSpec
247        {
248            set
249            {
250                // workitem 8245
251                if (Directory.Exists(value))
252                {
253                    _MatchingFileSpec = ".\\" + value + "\\*.*";
254                }
255                else
256                {
257                    _MatchingFileSpec = value;
258                }
259
260                _regexString = "^" +
261                Regex.Escape(_MatchingFileSpec)
262                    .Replace(@"\\\*\.\*", @"\\([^\.]+|.*\.[^\\\.]*)")
263                    .Replace(@"\.\*", @"\.[^\\\.]*")
264                    .Replace(@"\*", @".*")
265                    //.Replace(@"\*", @"[^\\\.]*") // ill-conceived
266                    .Replace(@"\?", @"[^\\\.]")
267                    + "$";
268
269                CriterionTrace("NameCriterion regexString({0})", _regexString);
270
271                _re = new Regex(_regexString, RegexOptions.IgnoreCase);
272            }
273        }
274
275
276        public override String ToString()
277        {
278            StringBuilder sb = new StringBuilder();
279            sb.Append("name ").Append(EnumUtil.GetDescription(Operator))
280                .Append(" '")
281                .Append(_MatchingFileSpec)
282                .Append("'");
283            return sb.ToString();
284        }
285
286
287        internal override bool Evaluate(string filename)
288        {
289            CriterionTrace("NameCriterion::Evaluate('{0}' pattern[{1}])",
290                           filename, _MatchingFileSpec);
291            return _Evaluate(filename);
292        }
293
294        private bool _Evaluate(string fullpath)
295        {
296            CriterionTrace("NameCriterion::Evaluate({0})", fullpath);
297            // No slash in the pattern implicitly means recurse, which means compare to
298            // filename only, not full path.
299            String f = (_MatchingFileSpec.IndexOf('\\') == -1)
300                ? System.IO.Path.GetFileName(fullpath)
301                : fullpath; // compare to fullpath
302
303            bool result = _re.IsMatch(f);
304
305            if (Operator != ComparisonOperator.EqualTo)
306                result = !result;
307            return result;
308        }
309    }
310
311
312    internal partial class TypeCriterion : SelectionCriterion
313    {
314        private char ObjectType;  // 'D' = Directory, 'F' = File
315        internal ComparisonOperator Operator;
316        internal string AttributeString
317        {
318            get
319            {
320                return ObjectType.ToString();
321            }
322            set
323            {
324                if (value.Length != 1 ||
325                    (value[0]!='D' && value[0]!='F'))
326                    throw new ArgumentException("Specify a single character: either D or F");
327                ObjectType = value[0];
328            }
329        }
330
331        public override String ToString()
332        {
333            StringBuilder sb = new StringBuilder();
334            sb.Append("type ").Append(EnumUtil.GetDescription(Operator)).Append(" ").Append(AttributeString);
335            return sb.ToString();
336        }
337
338        internal override bool Evaluate(string filename)
339        {
340            CriterionTrace("TypeCriterion::Evaluate({0})", filename);
341
342            bool result = (ObjectType == 'D')
343                ? Directory.Exists(filename)
344                : File.Exists(filename);
345
346            if (Operator != ComparisonOperator.EqualTo)
347                result = !result;
348            return result;
349        }
350    }
351
352
353#if !SILVERLIGHT
354    internal partial class AttributesCriterion : SelectionCriterion
355    {
356        private FileAttributes _Attributes;
357        internal ComparisonOperator Operator;
358        internal string AttributeString
359        {
360            get
361            {
362                string result = "";
363                if ((_Attributes & FileAttributes.Hidden) != 0)
364                    result += "H";
365                if ((_Attributes & FileAttributes.System) != 0)
366                    result += "S";
367                if ((_Attributes & FileAttributes.ReadOnly) != 0)
368                    result += "R";
369                if ((_Attributes & FileAttributes.Archive) != 0)
370                    result += "A";
371                if ((_Attributes & FileAttributes.ReparsePoint) != 0)
372                    result += "L";
373                if ((_Attributes & FileAttributes.NotContentIndexed) != 0)
374                    result += "I";
375                return result;
376            }
377
378            set
379            {
380                _Attributes = FileAttributes.Normal;
381                foreach (char c in value.ToUpper(CultureInfo.InvariantCulture))
382                {
383                    switch (c)
384                    {
385                        case 'H':
386                            if ((_Attributes & FileAttributes.Hidden) != 0)
387                                throw new ArgumentException(String.Format("Repeated flag. ({0})", c), "value");
388                            _Attributes |= FileAttributes.Hidden;
389                            break;
390
391                        case 'R':
392                            if ((_Attributes & FileAttributes.ReadOnly) != 0)
393                                throw new ArgumentException(String.Format("Repeated flag. ({0})", c), "value");
394                            _Attributes |= FileAttributes.ReadOnly;
395                            break;
396
397                        case 'S':
398                            if ((_Attributes & FileAttributes.System) != 0)
399                                throw new ArgumentException(String.Format("Repeated flag. ({0})", c), "value");
400                            _Attributes |= FileAttributes.System;
401                            break;
402
403                        case 'A':
404                            if ((_Attributes & FileAttributes.Archive) != 0)
405                                throw new ArgumentException(String.Format("Repeated flag. ({0})", c), "value");
406                            _Attributes |= FileAttributes.Archive;
407                            break;
408
409                        case 'I':
410                            if ((_Attributes & FileAttributes.NotContentIndexed) != 0)
411                                throw new ArgumentException(String.Format("Repeated flag. ({0})", c), "value");
412                            _Attributes |= FileAttributes.NotContentIndexed;
413                            break;
414
415                        case 'L':
416                            if ((_Attributes & FileAttributes.ReparsePoint) != 0)
417                                throw new ArgumentException(String.Format("Repeated flag. ({0})", c), "value");
418                            _Attributes |= FileAttributes.ReparsePoint;
419                            break;
420
421                        default:
422                            throw new ArgumentException(value);
423                    }
424                }
425            }
426        }
427
428
429        public override String ToString()
430        {
431            StringBuilder sb = new StringBuilder();
432            sb.Append("attributes ").Append(EnumUtil.GetDescription(Operator)).Append(" ").Append(AttributeString);
433            return sb.ToString();
434        }
435
436        private bool _EvaluateOne(FileAttributes fileAttrs, FileAttributes criterionAttrs)
437        {
438            bool result = false;
439            if ((_Attributes & criterionAttrs) == criterionAttrs)
440                result = ((fileAttrs & criterionAttrs) == criterionAttrs);
441            else
442                result = true;
443            return result;
444        }
445
446
447
448        internal override bool Evaluate(string filename)
449        {
450            // workitem 10191
451            if (Directory.Exists(filename))
452            {
453                // Directories don't have file attributes, so the result
454                // of an evaluation is always NO. This gets negated if
455                // the operator is NotEqualTo.
456                return (Operator != ComparisonOperator.EqualTo);
457            }
458#if NETCF
459            FileAttributes fileAttrs = NetCfFile.GetAttributes(filename);
460#else
461            FileAttributes fileAttrs = System.IO.File.GetAttributes(filename);
462#endif
463
464            return _Evaluate(fileAttrs);
465        }
466
467        private bool _Evaluate(FileAttributes fileAttrs)
468        {
469            bool result = _EvaluateOne(fileAttrs, FileAttributes.Hidden);
470            if (result)
471                result = _EvaluateOne(fileAttrs, FileAttributes.System);
472            if (result)
473                result = _EvaluateOne(fileAttrs, FileAttributes.ReadOnly);
474            if (result)
475                result = _EvaluateOne(fileAttrs, FileAttributes.Archive);
476            if (result)
477                result = _EvaluateOne(fileAttrs, FileAttributes.NotContentIndexed);
478            if (result)
479                result = _EvaluateOne(fileAttrs, FileAttributes.ReparsePoint);
480
481            if (Operator != ComparisonOperator.EqualTo)
482                result = !result;
483
484            return result;
485        }
486    }
487#endif
488
489
490    internal partial class CompoundCriterion : SelectionCriterion
491    {
492        internal LogicalConjunction Conjunction;
493        internal SelectionCriterion Left;
494
495        private SelectionCriterion _Right;
496        internal SelectionCriterion Right
497        {
498            get { return _Right; }
499            set
500            {
501                _Right = value;
502                if (value == null)
503                    Conjunction = LogicalConjunction.NONE;
504                else if (Conjunction == LogicalConjunction.NONE)
505                    Conjunction = LogicalConjunction.AND;
506            }
507        }
508
509
510        internal override bool Evaluate(string filename)
511        {
512            bool result = Left.Evaluate(filename);
513            switch (Conjunction)
514            {
515                case LogicalConjunction.AND:
516                    if (result)
517                        result = Right.Evaluate(filename);
518                    break;
519                case LogicalConjunction.OR:
520                    if (!result)
521                        result = Right.Evaluate(filename);
522                    break;
523                case LogicalConjunction.XOR:
524                    result ^= Right.Evaluate(filename);
525                    break;
526                default:
527                    throw new ArgumentException("Conjunction");
528            }
529            return result;
530        }
531
532
533        public override String ToString()
534        {
535            StringBuilder sb = new StringBuilder();
536            sb.Append("(")
537            .Append((Left != null) ? Left.ToString() : "null")
538            .Append(" ")
539            .Append(Conjunction.ToString())
540            .Append(" ")
541            .Append((Right != null) ? Right.ToString() : "null")
542            .Append(")");
543            return sb.ToString();
544        }
545    }
546
547
548
549    /// <summary>
550    ///   FileSelector encapsulates logic that selects files from a source - a zip file
551    ///   or the filesystem - based on a set of criteria.  This class is used internally
552    ///   by the DotNetZip library, in particular for the AddSelectedFiles() methods.
553    ///   This class can also be used independently of the zip capability in DotNetZip.
554    /// </summary>
555    ///
556    /// <remarks>
557    ///
558    /// <para>
559    ///   The FileSelector class is used internally by the ZipFile class for selecting
560    ///   files for inclusion into the ZipFile, when the <see
561    ///   cref="Ionic.Zip.ZipFile.AddSelectedFiles(String,String)"/> method, or one of
562    ///   its overloads, is called.  It's also used for the <see
563    ///   cref="Ionic.Zip.ZipFile.ExtractSelectedEntries(String)"/> methods.  Typically, an
564    ///   application that creates or manipulates Zip archives will not directly
565    ///   interact with the FileSelector class.
566    /// </para>
567    ///
568    /// <para>
569    ///   Some applications may wish to use the FileSelector class directly, to
570    ///   select files from disk volumes based on a set of criteria, without creating or
571    ///   querying Zip archives.  The file selection criteria include: a pattern to
572    ///   match the filename; the last modified, created, or last accessed time of the
573    ///   file; the size of the file; and the attributes of the file.
574    /// </para>
575    ///
576    /// <para>
577    ///   Consult the documentation for <see cref="SelectionCriteria"/>
578    ///   for more information on specifying the selection criteria.
579    /// </para>
580    ///
581    /// </remarks>
582    internal partial class FileSelector
583    {
584        internal SelectionCriterion _Criterion;
585
586#if NOTUSED
587        /// <summary>
588        ///   The default constructor.
589        /// </summary>
590        /// <remarks>
591        ///   Typically, applications won't use this constructor.  Instead they'll
592        ///   call the constructor that accepts a selectionCriteria string.  If you
593        ///   use this constructor, you'll want to set the SelectionCriteria
594        ///   property on the instance before calling SelectFiles().
595        /// </remarks>
596        protected FileSelector() { }
597#endif
598        /// <summary>
599        ///   Constructor that allows the caller to specify file selection criteria.
600        /// </summary>
601        ///
602        /// <remarks>
603        /// <para>
604        ///   This constructor allows the caller to specify a set of criteria for
605        ///   selection of files.
606        /// </para>
607        ///
608        /// <para>
609        ///   See <see cref="FileSelector.SelectionCriteria"/> for a description of
610        ///   the syntax of the selectionCriteria string.
611        /// </para>
612        ///
613        /// <para>
614        ///   By default the FileSelector will traverse NTFS Reparse Points.  To
615        ///   change this, use <see cref="FileSelector(String,
616        ///   bool)">FileSelector(String, bool)</see>.
617        /// </para>
618        /// </remarks>
619        ///
620        /// <param name="selectionCriteria">The criteria for file selection.</param>
621        public FileSelector(String selectionCriteria)
622        : this(selectionCriteria, true)
623        {
624        }
625
626        /// <summary>
627        ///   Constructor that allows the caller to specify file selection criteria.
628        /// </summary>
629        ///
630        /// <remarks>
631        /// <para>
632        ///   This constructor allows the caller to specify a set of criteria for
633        ///   selection of files.
634        /// </para>
635        ///
636        /// <para>
637        ///   See <see cref="FileSelector.SelectionCriteria"/> for a description of
638        ///   the syntax of the selectionCriteria string.
639        /// </para>
640        /// </remarks>
641        ///
642        /// <param name="selectionCriteria">The criteria for file selection.</param>
643        /// <param name="traverseDirectoryReparsePoints">
644        /// whether to traverse NTFS reparse points (junctions).
645        /// </param>
646        public FileSelector(String selectionCriteria, bool traverseDirectoryReparsePoints)
647        {
648            if (!String.IsNullOrEmpty(selectionCriteria))
649                _Criterion = _ParseCriterion(selectionCriteria);
650            TraverseReparsePoints = traverseDirectoryReparsePoints;
651        }
652
653
654
655        /// <summary>
656        ///   The string specifying which files to include when retrieving.
657        /// </summary>
658        /// <remarks>
659        ///
660        /// <para>
661        ///   Specify the criteria in statements of 3 elements: a noun, an operator,
662        ///   and a value.  Consider the string "name != *.doc" .  The noun is
663        ///   "name".  The operator is "!=", implying "Not Equal".  The value is
664        ///   "*.doc".  That criterion, in English, says "all files with a name that
665        ///   does not end in the .doc extension."
666        /// </para>
667        ///
668        /// <para>
669        ///   Supported nouns include "name" (or "filename") for the filename;
670        ///   "atime", "mtime", and "ctime" for last access time, last modfied time,
671        ///   and created time of the file, respectively; "attributes" (or "attrs")
672        ///   for the file attributes; "size" (or "length") for the file length
673        ///   (uncompressed); and "type" for the type of object, either a file or a
674        ///   directory.  The "attributes", "type", and "name" nouns all support =
675        ///   and != as operators.  The "size", "atime", "mtime", and "ctime" nouns
676        ///   support = and !=, and &gt;, &gt;=, &lt;, &lt;= as well.  The times are
677        ///   taken to be expressed in local time.
678        /// </para>
679        ///
680        /// <para>
681        ///   Specify values for the file attributes as a string with one or more of
682        ///   the characters H,R,S,A,I,L in any order, implying file attributes of
683        ///   Hidden, ReadOnly, System, Archive, NotContextIndexed, and ReparsePoint
684        ///   (symbolic link) respectively.
685        /// </para>
686        ///
687        /// <para>
688        ///   To specify a time, use YYYY-MM-DD-HH:mm:ss or YYYY/MM/DD-HH:mm:ss as
689        ///   the format.  If you omit the HH:mm:ss portion, it is assumed to be
690        ///   00:00:00 (midnight).
691        /// </para>
692        ///
693        /// <para>
694        ///   The value for a size criterion is expressed in integer quantities of
695        ///   bytes, kilobytes (use k or kb after the number), megabytes (m or mb),
696        ///   or gigabytes (g or gb).
697        /// </para>
698        ///
699        /// <para>
700        ///   The value for a name is a pattern to match against the filename,
701        ///   potentially including wildcards.  The pattern follows CMD.exe glob
702        ///   rules: * implies one or more of any character, while ?  implies one
703        ///   character.  If the name pattern contains any slashes, it is matched to
704        ///   the entire filename, including the path; otherwise, it is matched
705        ///   against only the filename without the path.  This means a pattern of
706        ///   "*\*.*" matches all files one directory level deep, while a pattern of
707        ///   "*.*" matches all files in all directories.
708        /// </para>
709        ///
710        /// <para>
711        ///   To specify a name pattern that includes spaces, use single quotes
712        ///   around the pattern.  A pattern of "'* *.*'" will match all files that
713        ///   have spaces in the filename.  The full criteria string for that would
714        ///   be "name = '* *.*'" .
715        /// </para>
716        ///
717        /// <para>
718        ///   The value for a type criterion is either F (implying a file) or D
719        ///   (implying a directory).
720        /// </para>
721        ///
722        /// <para>
723        ///   Some examples:
724        /// </para>
725        ///
726        /// <list type="table">
727        ///   <listheader>
728        ///     <term>criteria</term>
729        ///     <description>Files retrieved</description>
730        ///   </listheader>
731        ///
732        ///   <item>
733        ///     <term>name != *.xls </term>
734        ///     <description>any file with an extension that is not .xls
735        ///     </description>
736        ///   </item>
737        ///
738        ///   <item>
739        ///     <term>name = *.mp3 </term>
740        ///     <description>any file with a .mp3 extension.
741        ///     </description>
742        ///   </item>
743        ///
744        ///   <item>
745        ///     <term>*.mp3</term>
746        ///     <description>(same as above) any file with a .mp3 extension.
747        ///     </description>
748        ///   </item>
749        ///
750        ///   <item>
751        ///     <term>attributes = A </term>
752        ///     <description>all files whose attributes include the Archive bit.
753        ///     </description>
754        ///   </item>
755        ///
756        ///   <item>
757        ///     <term>attributes != H </term>
758        ///     <description>all files whose attributes do not include the Hidden bit.
759        ///     </description>
760        ///   </item>
761        ///
762        ///   <item>
763        ///     <term>mtime > 2009-01-01</term>
764        ///     <description>all files with a last modified time after January 1st, 2009.
765        ///     </description>
766        ///   </item>
767        ///
768        ///   <item>
769        ///     <term>ctime > 2009/01/01-03:00:00</term>
770        ///     <description>all files with a created time after 3am (local time),
771        ///     on January 1st, 2009.
772        ///     </description>
773        ///   </item>
774        ///
775        ///   <item>
776        ///     <term>size > 2gb</term>
777        ///     <description>all files whose uncompressed size is greater than 2gb.
778        ///     </description>
779        ///   </item>
780        ///
781        ///   <item>
782        ///     <term>type = D</term>
783        ///     <description>all directories in the filesystem. </description>
784        ///   </item>
785        ///
786        /// </list>
787        ///
788        /// <para>
789        ///   You can combine criteria with the conjunctions AND, OR, and XOR. Using
790        ///   a string like "name = *.txt AND size &gt;= 100k" for the
791        ///   selectionCriteria retrieves entries whose names end in .txt, and whose
792        ///   uncompressed size is greater than or equal to 100 kilobytes.
793        /// </para>
794        ///
795        /// <para>
796        ///   For more complex combinations of criteria, you can use parenthesis to
797        ///   group clauses in the boolean logic.  Absent parenthesis, the
798        ///   precedence of the criterion atoms is determined by order of
799        ///   appearance.  Unlike the C# language, the AND conjunction does not take
800        ///   precendence over the logical OR.  This is important only in strings
801        ///   that contain 3 or more criterion atoms.  In other words, "name = *.txt
802        ///   and size &gt; 1000 or attributes = H" implies "((name = *.txt AND size
803        ///   &gt; 1000) OR attributes = H)" while "attributes = H OR name = *.txt
804        ///   and size &gt; 1000" evaluates to "((attributes = H OR name = *.txt)
805        ///   AND size &gt; 1000)".  When in doubt, use parenthesis.
806        /// </para>
807        ///
808        /// <para>
809        ///   Using time properties requires some extra care. If you want to
810        ///   retrieve all entries that were last updated on 2009 February 14,
811        ///   specify "mtime &gt;= 2009-02-14 AND mtime &lt; 2009-02-15".  Read this
812        ///   to say: all files updated after 12:00am on February 14th, until
813        ///   12:00am on February 15th.  You can use the same bracketing approach to
814        ///   specify any time period - a year, a month, a week, and so on.
815        /// </para>
816        ///
817        /// <para>
818        ///   The syntax allows one special case: if you provide a string with no
819        ///   spaces, it is treated as a pattern to match for the filename.
820        ///   Therefore a string like "*.xls" will be equivalent to specifying "name
821        ///   = *.xls".  This "shorthand" notation does not work with compound
822        ///   criteria.
823        /// </para>
824        ///
825        /// <para>
826        ///   There is no logic in this class that insures that the inclusion
827        ///   criteria are internally consistent.  For example, it's possible to
828        ///   specify criteria that says the file must have a size of less than 100
829        ///   bytes, as well as a size that is greater than 1000 bytes.  Obviously
830        ///   no file will ever satisfy such criteria, but this class does not check
831        ///   for or detect such inconsistencies.
832        /// </para>
833        ///
834        /// </remarks>
835        ///
836        /// <exception cref="System.Exception">
837        ///   Thrown in the setter if the value has an invalid syntax.
838        /// </exception>
839        public String SelectionCriteria
840        {
841            get
842            {
843                if (_Criterion == null) return null;
844                return _Criterion.ToString();
845            }
846            set
847            {
848                if (value == null) _Criterion = null;
849                else if (value.Trim() == "") _Criterion = null;
850                else
851                    _Criterion = _ParseCriterion(value);
852            }
853        }
854
855        /// <summary>
856        ///  Indicates whether searches will traverse NTFS reparse points, like Junctions.
857        /// </summary>
858        public bool TraverseReparsePoints
859        {
860            get; set;
861        }
862
863
864        private enum ParseState
865        {
866            Start,
867            OpenParen,
868            CriterionDone,
869            ConjunctionPending,
870            Whitespace,
871        }
872
873
874        private static class RegexAssertions
875        {
876            public static readonly String PrecededByOddNumberOfSingleQuotes = "(?<=(?:[^']*'[^']*')*'[^']*)";
877            public static readonly String FollowedByOddNumberOfSingleQuotesAndLineEnd = "(?=[^']*'(?:[^']*'[^']*')*[^']*$)";
878
879            public static readonly String PrecededByEvenNumberOfSingleQuotes = "(?<=(?:[^']*'[^']*')*[^']*)";
880            public static readonly String FollowedByEvenNumberOfSingleQuotesAndLineEnd = "(?=(?:[^']*'[^']*')*[^']*$)";
881        }
882
883
884        private static string NormalizeCriteriaExpression(string source)
885        {
886            // The goal here is to normalize the criterion expression. At output, in
887            // the transformed criterion string, every significant syntactic element
888            // - a property element, grouping paren for the boolean logic, operator
889            // ( = < > != ), conjunction, or property value - will be separated from
890            // its neighbors by at least one space. Thus,
891            //
892            // before                         after
893            // -------------------------------------------------------------------
894            // name=*.txt                     name = *.txt
895            // (size>100)AND(name=*.txt)      ( size > 100 ) AND ( name = *.txt )
896            //
897            // This is relatively straightforward using regular expression
898            // replacement. This method applies a distinct regex pattern and
899            // corresponding replacement string for each one of a number of cases:
900            // an open paren followed by a word; a word followed by a close-paren; a
901            // pair of open parens; a close paren followed by a word (which should
902            // then be followed by an open paren). And so on. These patterns and
903            // replacements are all stored in prPairs. By applying each of these
904            // regex replacements in turn, we get the transformed string. Easy.
905            //
906            // The resulting "normalized" criterion string, is then used as the
907            // subject that gets parsed, by splitting the string into tokens that
908            // are separated by spaces.  Here, there's a twist. The spaces within
909            // single-quote delimiters do not delimit distinct tokens.  So, this
910            // normalization method temporarily replaces those spaces with
911            // ASCII 6 (0x06), a control character which is not a legal
912            // character in a filename. The parsing logic that happens later will
913            // revert that change, restoring the original value of the filename
914            // specification.
915            //
916            // To illustrate, for a "before" string of [(size>100)AND(name='Name
917            // (with Parens).txt')] , the "after" string is [( size > 100 ) AND
918            // ( name = 'Name\u0006(with\u0006Parens).txt' )].
919            //
920
921            string[][] prPairs =
922                {
923                    // A. opening double parens - insert a space between them
924                    new string[] { @"([^']*)\(\(([^']+)", "$1( ($2" },
925
926                    // B. closing double parens - insert a space between
927                    new string[] { @"(.)\)\)", "$1) )" },
928
929                    // C. single open paren with a following word - insert a space between
930                    new string[] { @"\((\S)", "( $1" },
931
932                    // D. single close paren with a preceding word - insert a space between the two
933                    new string[] { @"(\S)\)", "$1 )" },
934
935                    // E. close paren at line start?, insert a space before the close paren
936                    // this seems like a degenerate case.  I don't recall why it's here.
937                    new string[] { @"^\)", " )" },
938
939                    // F. a word (likely a conjunction) followed by an open paren - insert a space between
940                    new string[] { @"(\S)\(", "$1 (" },
941
942                    // G. single close paren followed by word - insert a paren after close paren
943                    new string[] { @"\)(\S)", ") $1" },
944
945                    // H. insert space between = and a following single quote
946                    //new string[] { @"(=|!=)('[^']*')", "$1 $2" },
947                    new string[] { @"(=)('[^']*')", "$1 $2" },
948
949                    // I. insert space between property names and the following operator
950                    //new string[] { @"([^ ])([><(?:!=)=])", "$1 $2" },
951                    new string[] { @"([^ !><])(>|<|!=|=)", "$1 $2" },
952
953                    // J. insert spaces between operators and the following values
954                    //new string[] { @"([><(?:!=)=])([^ ])", "$1 $2" },
955                    new string[] { @"(>|<|!=|=)([^ =])", "$1 $2" },
956
957                    // K. replace fwd slash with backslash
958                    new string[] { @"/", "\\" },
959                };
960
961            string interim = source;
962
963            for (int i=0; i < prPairs.Length; i++)
964            {
965                //char caseIdx = (char)('A' + i);
966                string pattern = RegexAssertions.PrecededByEvenNumberOfSingleQuotes +
967                    prPairs[i][0] +
968                    RegexAssertions.FollowedByEvenNumberOfSingleQuotesAndLineEnd;
969
970                interim = Regex.Replace(interim, pattern, prPairs[i][1]);
971            }
972
973            // match a fwd slash, followed by an odd number of single quotes.
974            // This matches fwd slashes only inside a pair of single quote delimiters,
975            // eg, a filename.  This must be done as well as the case above, to handle
976            // filenames specified inside quotes as well as filenames without quotes.
977            var regexPattern = @"/" +
978                                RegexAssertions.FollowedByOddNumberOfSingleQuotesAndLineEnd;
979            // replace with backslash
980            interim = Regex.Replace(interim, regexPattern, "\\");
981
982            // match a space, followed by an odd number of single quotes.
983            // This matches spaces only inside a pair of single quote delimiters.
984            regexPattern = " " +
985                RegexAssertions.FollowedByOddNumberOfSingleQuotesAndLineEnd;
986
987            // Replace all spaces that appear inside single quotes, with
988            // ascii 6.  This allows a split on spaces to get tokens in
989            // the expression. The split will not split any filename or
990            // wildcard that appears within single quotes. After tokenizing, we
991            // need to replace ascii 6 with ascii 32 to revert the
992            // spaces within quotes.
993            return Regex.Replace(interim, regexPattern, "\u0006");
994        }
995
996
997        private static SelectionCriterion _ParseCriterion(String s)
998        {
999            if (s == null) return null;
1000
1001            // inject spaces after open paren and before close paren, etc
1002            s = NormalizeCriteriaExpression(s);
1003
1004            // no spaces in the criteria is shorthand for filename glob
1005            if (s.IndexOf(" ") == -1)
1006                s = "name = " + s;
1007
1008            // split the expression into tokens
1009            string[] tokens = s.Trim().Split(' ', '\t');
1010
1011            if (tokens.Length < 3) throw new ArgumentException(s);
1012
1013            SelectionCriterion current = null;
1014
1015            LogicalConjunction pendingConjunction = LogicalConjunction.NONE;
1016
1017            ParseState state;
1018            var stateStack = new System.Collections.Generic.Stack<ParseState>();
1019            var critStack = new System.Collections.Generic.Stack<SelectionCriterion>();
1020            stateStack.Push(ParseState.Start);
1021
1022            for (int i = 0; i < tokens.Length; i++)
1023            {
1024                string tok1 = tokens[i].ToLower(CultureInfo.InvariantCulture);
1025                switch (tok1)
1026                {
1027                    case "and":
1028                    case "xor":
1029                    case "or":
1030                        state = stateStack.Peek();
1031                        if (state != ParseState.CriterionDone)
1032                            throw new ArgumentException(String.Join(" ", tokens, i, tokens.Length - i));
1033
1034                        if (tokens.Length <= i + 3)
1035                            throw new ArgumentException(String.Join(" ", tokens, i, tokens.Length - i));
1036
1037                        pendingConjunction = (LogicalConjunction)Enum.Parse(typeof(LogicalConjunction), tokens[i].ToUpper(CultureInfo.InvariantCulture), true);
1038                        current = new CompoundCriterion { Left = current, Right = null, Conjunction = pendingConjunction };
1039                        stateStack.Push(state);
1040                        stateStack.Push(ParseState.ConjunctionPending);
1041                        critStack.Push(current);
1042                        break;
1043
1044                    case "(":
1045                        state = stateStack.Peek();
1046                        if (state != ParseState.Start && state != ParseState.ConjunctionPending && state != ParseState.OpenParen)
1047                            throw new ArgumentException(String.Join(" ", tokens, i, tokens.Length - i));
1048
1049                        if (tokens.Length <= i + 4)
1050                            throw new ArgumentException(String.Join(" ", tokens, i, tokens.Length - i));
1051
1052                        stateStack.Push(ParseState.OpenParen);
1053                        break;
1054
1055                    case ")":
1056                        state = stateStack.Pop();
1057                        if (stateStack.Peek() != ParseState.OpenParen)
1058                            throw new ArgumentException(String.Join(" ", tokens, i, tokens.Length - i));
1059
1060                        stateStack.Pop();
1061                        stateStack.Push(ParseState.CriterionDone);
1062                        break;
1063
1064                    case "atime":
1065                    case "ctime":
1066                    case "mtime":
1067                        if (tokens.Length <= i + 2)
1068                            throw new ArgumentException(String.Join(" ", tokens, i, tokens.Length - i));
1069
1070                        DateTime t;
1071                        try
1072                        {
1073                            t = DateTime.ParseExact(tokens[i + 2], "yyyy-MM-dd-HH:mm:ss", null);
1074                        }
1075                        catch (FormatException)
1076                        {
1077                            try
1078                            {
1079                                t = DateTime.ParseExact(tokens[i + 2], "yyyy/MM/dd-HH:mm:ss", null);
1080                            }
1081                            catch (FormatException)
1082                            {
1083                                try
1084                                {
1085                                    t = DateTime.ParseExact(tokens[i + 2], "yyyy/MM/dd", null);
1086                                }
1087                                catch (FormatException)
1088                                {
1089                                    try
1090                                    {
1091                                        t = DateTime.ParseExact(tokens[i + 2], "MM/dd/yyyy", null);
1092                                    }
1093                                    catch (FormatException)
1094                                    {
1095                                        t = DateTime.ParseExact(tokens[i + 2], "yyyy-MM-dd", null);
1096                                    }
1097                                }
1098                            }
1099                        }
1100                        t= DateTime.SpecifyKind(t, DateTimeKind.Local).ToUniversalTime();
1101                        current = new TimeCriterion
1102                        {
1103                            Which = (WhichTime)Enum.Parse(typeof(WhichTime), tokens[i], true),
1104                            Operator = (ComparisonOperator)EnumUtil.Parse(typeof(ComparisonOperator), tokens[i + 1]),
1105                            Time = t
1106                        };
1107                        i += 2;
1108                        stateStack.Push(ParseState.CriterionDone);
1109                        break;
1110
1111
1112                    case "length":
1113                    case "size":
1114                        if (tokens.Length <= i + 2)
1115                            throw new ArgumentException(String.Join(" ", tokens, i, tokens.Length - i));
1116
1117                        Int64 sz = 0;
1118                        string v = tokens[i + 2];
1119                        if (v.EndsWith("K", StringComparison.InvariantCultureIgnoreCase))
1120                            sz = Int64.Parse(v.Substring(0, v.Length - 1)) * 1024;
1121                        else if (v.EndsWith("KB", StringComparison.InvariantCultureIgnoreCase))
1122                            sz = Int64.Parse(v.Substring(0, v.Length - 2)) * 1024;
1123                        else if (v.EndsWith("M", StringComparison.InvariantCultureIgnoreCase))
1124                            sz = Int64.Parse(v.Substring(0, v.Length - 1)) * 1024 * 1024;
1125                        else if (v.EndsWith("MB", StringComparison.InvariantCultureIgnoreCase))
1126                            sz = Int64.Parse(v.Substring(0, v.Length - 2)) * 1024 * 1024;
1127                        else if (v.EndsWith("G", StringComparison.InvariantCultureIgnoreCase))
1128                            sz = Int64.Parse(v.Substring(0, v.Length - 1)) * 1024 * 1024 * 1024;
1129                        else if (v.EndsWith("GB", StringComparison.InvariantCultureIgnoreCase))
1130                            sz = Int64.Parse(v.Substring(0, v.Length - 2)) * 1024 * 1024 * 1024;
1131                        else sz = Int64.Parse(tokens[i + 2]);
1132
1133                        current = new SizeCriterion
1134                        {
1135                            Size = sz,
1136                            Operator = (ComparisonOperator)EnumUtil.Parse(typeof(ComparisonOperator), tokens[i + 1])
1137                        };
1138                        i += 2;
1139                        stateStack.Push(ParseState.CriterionDone);
1140                        break;
1141
1142                    case "filename":
1143                    case "name":
1144                        {
1145                            if (tokens.Length <= i + 2)
1146                                throw new ArgumentException(String.Join(" ", tokens, i, tokens.Length - i));
1147
1148                            ComparisonOperator c =
1149                                (ComparisonOperator)EnumUtil.Parse(typeof(ComparisonOperator), tokens[i + 1]);
1150
1151                            if (c != ComparisonOperator.NotEqualTo && c != ComparisonOperator.EqualTo)
1152                                throw new ArgumentException(String.Join(" ", tokens, i, tokens.Length - i));
1153
1154                            string m = tokens[i + 2];
1155
1156                            // handle single-quoted filespecs (used to include
1157                            // spaces in filename patterns)
1158                            if (m.StartsWith("'") && m.EndsWith("'"))
1159                            {
1160                                // trim off leading and trailing single quotes and
1161                                // revert the control characters to spaces.
1162                                m = m.Substring(1, m.Length - 2)
1163                                    .Replace("\u0006", " ");
1164                            }
1165
1166                            // if (m.StartsWith("'"))
1167                            //     m = m.Replace("\u0006", " ");
1168
1169                            current = new NameCriterion
1170                            {
1171                                MatchingFileSpec = m,
1172                                Operator = c
1173                            };
1174                            i += 2;
1175                            stateStack.Push(ParseState.CriterionDone);
1176                        }
1177                        break;
1178
1179#if !SILVERLIGHT
1180                    case "attrs":
1181                    case "attributes":
1182#endif
1183                    case "type":
1184                        {
1185                            if (tokens.Length <= i + 2)
1186                                throw new ArgumentException(String.Join(" ", tokens, i, tokens.Length - i));
1187
1188                            ComparisonOperator c =
1189                                (ComparisonOperator)EnumUtil.Parse(typeof(ComparisonOperator), tokens[i + 1]);
1190
1191                            if (c != ComparisonOperator.NotEqualTo && c != ComparisonOperator.EqualTo)
1192                                throw new ArgumentException(String.Join(" ", tokens, i, tokens.Length - i));
1193
1194#if SILVERLIGHT
1195                            current = (SelectionCriterion) new TypeCriterion
1196                                    {
1197                                        AttributeString = tokens[i + 2],
1198                                        Operator = c
1199                                    };
1200#else
1201                            current = (tok1 == "type")
1202                                ? (SelectionCriterion) new TypeCriterion
1203                                    {
1204                                        AttributeString = tokens[i + 2],
1205                                        Operator = c
1206                                    }
1207                                : (SelectionCriterion) new AttributesCriterion
1208                                    {
1209                                        AttributeString = tokens[i + 2],
1210                                        Operator = c
1211                                    };
1212#endif
1213                            i += 2;
1214                            stateStack.Push(ParseState.CriterionDone);
1215                        }
1216                        break;
1217
1218                    case "":
1219                        // NOP
1220                        stateStack.Push(ParseState.Whitespace);
1221                        break;
1222
1223                    default:
1224                        throw new ArgumentException("'" + tokens[i] + "'");
1225                }
1226
1227                state = stateStack.Peek();
1228                if (state == ParseState.CriterionDone)
1229                {
1230                    stateStack.Pop();
1231                    if (stateStack.Peek() == ParseState.ConjunctionPending)
1232                    {
1233                        while (stateStack.Peek() == ParseState.ConjunctionPending)
1234                        {
1235                            var cc = critStack.Pop() as CompoundCriterion;
1236                            cc.Right = current;
1237                            current = cc; // mark the parent as current (walk up the tree)
1238                            stateStack.Pop();   // the conjunction is no longer pending
1239
1240                            state = stateStack.Pop();
1241                            if (state != ParseState.CriterionDone)
1242                                throw new ArgumentException("??");
1243                        }
1244                    }
1245                    else stateStack.Push(ParseState.CriterionDone);  // not sure?
1246                }
1247
1248                if (state == ParseState.Whitespace)
1249                    stateStack.Pop();
1250            }
1251
1252            return current;
1253        }
1254
1255
1256        /// <summary>
1257        /// Returns a string representation of the FileSelector object.
1258        /// </summary>
1259        /// <returns>The string representation of the boolean logic statement of the file
1260        /// selection criteria for this instance. </returns>
1261        public override String ToString()
1262        {
1263            return "FileSelector("+_Criterion.ToString()+")";
1264        }
1265
1266
1267        private bool Evaluate(string filename)
1268        {
1269            // dinoch - Thu, 11 Feb 2010  18:34
1270            SelectorTrace("Evaluate({0})", filename);
1271            bool result = _Criterion.Evaluate(filename);
1272            return result;
1273        }
1274
1275        [System.Diagnostics.Conditional("SelectorTrace")]
1276        private void SelectorTrace(string format, params object[] args)
1277        {
1278            if (_Criterion != null && _Criterion.Verbose)
1279                System.Console.WriteLine(format, args);
1280        }
1281
1282        /// <summary>
1283        ///   Returns the names of the files in the specified directory
1284        ///   that fit the selection criteria specified in the FileSelector.
1285        /// </summary>
1286        ///
1287        /// <remarks>
1288        ///   This is equivalent to calling <see cref="SelectFiles(String, bool)"/>
1289        ///   with recurseDirectories = false.
1290        /// </remarks>
1291        ///
1292        /// <param name="directory">
1293        ///   The name of the directory over which to apply the FileSelector
1294        ///   criteria.
1295        /// </param>
1296        ///
1297        /// <returns>
1298        ///   A collection of strings containing fully-qualified pathnames of files
1299        ///   that match the criteria specified in the FileSelector instance.
1300        /// </returns>
1301        public System.Collections.Generic.ICollection<String> SelectFiles(String directory)
1302        {
1303            return SelectFiles(directory, false);
1304        }
1305
1306
1307        /// <summary>
1308        ///   Returns the names of the files in the specified directory that fit the
1309        ///   selection criteria specified in the FileSelector, optionally recursing
1310        ///   through subdirectories.
1311        /// </summary>
1312        ///
1313        /// <remarks>
1314        ///   This method applies the file selection criteria contained in the
1315        ///   FileSelector to the files contained in the given directory, and
1316        ///   returns the names of files that conform to the criteria.
1317        /// </remarks>
1318        ///
1319        /// <param name="directory">
1320        ///   The name of the directory over which to apply the FileSelector
1321        ///   criteria.
1322        /// </param>
1323        ///
1324        /// <param name="recurseDirectories">
1325        ///   Whether to recurse through subdirectories when applying the file
1326        ///   selection criteria.
1327        /// </param>
1328        ///
1329        /// <returns>
1330        ///   A collection of strings containing fully-qualified pathnames of files
1331        ///   that match the criteria specified in the FileSelector instance.
1332        /// </returns>
1333        public System.Collections.ObjectModel.ReadOnlyCollection<String>
1334            SelectFiles(String directory,
1335                        bool recurseDirectories)
1336        {
1337            if (_Criterion == null)
1338                throw new ArgumentException("SelectionCriteria has not been set");
1339
1340            var list = new List<String>();
1341            try
1342            {
1343                if (Directory.Exists(directory))
1344                {
1345                    String[] filenames = Directory.GetFiles(directory);
1346
1347                    // add the files:
1348                    foreach (String filename in filenames)
1349                    {
1350                        if (Evaluate(filename))
1351                            list.Add(filename);
1352                    }
1353
1354                    if (recurseDirectories)
1355                    {
1356                        // add the subdirectories:
1357                        String[] dirnames = Directory.GetDirectories(directory);
1358                        foreach (String dir in dirnames)
1359                        {
1360                            if (this.TraverseReparsePoints
1361#if !SILVERLIGHT
1362                                || ((File.GetAttributes(dir) & FileAttributes.ReparsePoint) == 0)
1363#endif
1364                                )
1365                            {
1366                                // workitem 10191
1367                                if (Evaluate(dir)) list.Add(dir);
1368                                list.AddRange(this.SelectFiles(dir, recurseDirectories));
1369                            }
1370                        }
1371                    }
1372                }
1373            }
1374            // can get System.UnauthorizedAccessException here
1375            catch (System.UnauthorizedAccessException)
1376            {
1377            }
1378            catch (System.IO.IOException)
1379            {
1380            }
1381
1382            return list.AsReadOnly();
1383        }
1384    }
1385
1386
1387
1388    /// <summary>
1389    /// Summary description for EnumUtil.
1390    /// </summary>
1391    internal sealed class EnumUtil
1392    {
1393        private EnumUtil() { }
1394        /// <summary>
1395        ///   Returns the value of the DescriptionAttribute if the specified Enum
1396        ///   value has one.  If not, returns the ToString() representation of the
1397        ///   Enum value.
1398        /// </summary>
1399        /// <param name="value">The Enum to get the description for</param>
1400        /// <returns></returns>
1401        internal static string GetDescription(System.Enum value)
1402        {
1403            FieldInfo fi = value.GetType().GetField(value.ToString());
1404            var attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
1405            if (attributes.Length > 0)
1406                return attributes[0].Description;
1407            else
1408                return value.ToString();
1409        }
1410
1411        /// <summary>
1412        ///   Converts the string representation of the name or numeric value of one
1413        ///   or more enumerated constants to an equivalent enumerated object.
1414        ///   Note: use the DescriptionAttribute on enum values to enable this.
1415        /// </summary>
1416        /// <param name="enumType">The System.Type of the enumeration.</param>
1417        /// <param name="stringRepresentation">
1418        ///   A string containing the name or value to convert.
1419        /// </param>
1420        /// <returns></returns>
1421        internal static object Parse(Type enumType, string stringRepresentation)
1422        {
1423            return Parse(enumType, stringRepresentation, false);
1424        }
1425
1426
1427#if SILVERLIGHT
1428       public static System.Enum[] GetEnumValues(Type type)
1429        {
1430            if (!type.IsEnum)
1431                throw new ArgumentException("not an enum");
1432
1433            return (
1434              from field in type.GetFields(BindingFlags.Public | BindingFlags.Static)
1435              where field.IsLiteral
1436              select (System.Enum)field.GetValue(null)
1437            ).ToArray();
1438        }
1439
1440        public static string[] GetEnumStrings<T>()
1441        {
1442            var type = typeof(T);
1443            if (!type.IsEnum)
1444                throw new ArgumentException("not an enum");
1445
1446            return (
1447              from field in type.GetFields(BindingFlags.Public | BindingFlags.Static)
1448              where field.IsLiteral
1449              select field.Name
1450            ).ToArray();
1451        }
1452#endif
1453
1454        /// <summary>
1455        ///   Converts the string representation of the name or numeric value of one
1456        ///   or more enumerated constants to an equivalent enumerated object.  A
1457        ///   parameter specified whether the operation is case-sensitive.  Note:
1458        ///   use the DescriptionAttribute on enum values to enable this.
1459        /// </summary>
1460        /// <param name="enumType">The System.Type of the enumeration.</param>
1461        /// <param name="stringRepresentation">
1462        ///   A string containing the name or value to convert.
1463        /// </param>
1464        /// <param name="ignoreCase">
1465        ///   Whether the operation is case-sensitive or not.</param>
1466        /// <returns></returns>
1467        internal static object Parse(Type enumType, string stringRepresentation, bool ignoreCase)
1468        {
1469            if (ignoreCase)
1470                stringRepresentation = stringRepresentation.ToLower(CultureInfo.InvariantCulture);
1471
1472#if SILVERLIGHT
1473            foreach (System.Enum enumVal in GetEnumValues(enumType))
1474#else
1475            foreach (System.Enum enumVal in System.Enum.GetValues(enumType))
1476#endif
1477            {
1478                string description = GetDescription(enumVal);
1479                if (ignoreCase)
1480                    description = description.ToLower(CultureInfo.InvariantCulture);
1481                if (description == stringRepresentation)
1482                    return enumVal;
1483            }
1484
1485            return System.Enum.Parse(enumType, stringRepresentation, ignoreCase);
1486        }
1487    }
1488
1489
1490#if DEMO
1491    internal class DemonstrateFileSelector
1492    {
1493        private string _directory;
1494        private bool _recurse;
1495        private bool _traverse;
1496        private bool _verbose;
1497        private string _selectionCriteria;
1498        private FileSelector f;
1499
1500        public DemonstrateFileSelector()
1501        {
1502            this._directory = ".";
1503            this._recurse = true;
1504        }
1505
1506        public DemonstrateFileSelector(string[] args) : this()
1507        {
1508            for (int i = 0; i < args.Length; i++)
1509            {
1510                switch(args[i])
1511                {
1512                case"-?":
1513                    Usage();
1514                    Environment.Exit(0);
1515                    break;
1516                case "-d":
1517                    i++;
1518                    if (args.Length <= i)
1519                        throw new ArgumentException("-directory");
1520                    this._directory = args[i];
1521                    break;
1522                case "-norecurse":
1523                    this._recurse = false;
1524                    break;
1525
1526                case "-j-":
1527                    this._traverse = false;
1528                    break;
1529
1530                case "-j+":
1531                    this._traverse = true;
1532                    break;
1533
1534                case "-v":
1535                    this._verbose = true;
1536                    break;
1537
1538                default:
1539                    if (this._selectionCriteria != null)
1540                        throw new ArgumentException(args[i]);
1541                    this._selectionCriteria = args[i];
1542                    break;
1543                }
1544
1545                if (this._selectionCriteria != null)
1546                    this.f = new FileSelector(this._selectionCriteria);
1547            }
1548        }
1549
1550
1551        public static void Main(string[] args)
1552        {
1553            try
1554            {
1555                Console.WriteLine();
1556                new DemonstrateFileSelector(args).Run();
1557            }
1558            catch (Exception exc1)
1559            {
1560                Console.WriteLine("Exception: {0}", exc1.ToString());
1561                Usage();
1562            }
1563        }
1564
1565
1566        public void Run()
1567        {
1568            if (this.f == null)
1569                this.f = new FileSelector("name = *.jpg AND (size > 1000 OR atime < 2009-02-14-01:00:00)");
1570
1571            this.f.TraverseReparsePoints = _traverse;
1572            this.f.Verbose = this._verbose;
1573            Console.WriteLine();
1574            Console.WriteLine(new String(':', 88));
1575            Console.WriteLine("Selecting files:\n" + this.f.ToString());
1576            var files = this.f.SelectFiles(this._directory, this._recurse);
1577            if (files.Count == 0)
1578            {
1579                Console.WriteLine("no files.");
1580            }
1581            else
1582            {
1583                Console.WriteLine("files: {0}", files.Count);
1584                foreach (string file in files)
1585                {
1586                    Console.WriteLine("  " + file);
1587                }
1588            }
1589        }
1590
1591        public static void Usage()
1592        {
1593            Console.WriteLine("FileSelector: select files based on selection criteria.\n");
1594            Console.WriteLine("Usage:\n  FileSelector <selectionCriteria>  [options]\n" +
1595                              "\n" +
1596                              "  -d <dir>   directory to select from (Default .)\n" +
1597                              " -norecurse  don't recurse into subdirs\n" +
1598                              " -j-         don't traverse junctions\n" +
1599                              " -v          verbose output\n");
1600        }
1601    }
1602
1603#endif
1604
1605
1606
1607}
1608
1609
Note: See TracBrowser for help on using the repository browser.