|  | //#define SelectorTrace | 
|  |  | 
|  | // FileSelector.cs | 
|  | // ------------------------------------------------------------------ | 
|  | // | 
|  | // Copyright (c) 2008-2011 Dino Chiesa. | 
|  | // All rights reserved. | 
|  | // | 
|  | // This code module is part of DotNetZip, a zipfile class library. | 
|  | // | 
|  | // ------------------------------------------------------------------ | 
|  | // | 
|  | // This code is licensed under the Microsoft Public License. | 
|  | // See the file License.txt for the license details. | 
|  | // More info on: http://dotnetzip.codeplex.com | 
|  | // | 
|  | // ------------------------------------------------------------------ | 
|  | // | 
|  | // last saved: <2011-August-05 11:03:11> | 
|  | // | 
|  | // ------------------------------------------------------------------ | 
|  | // | 
|  | // This module implements a "file selector" that finds files based on a | 
|  | // set of inclusion criteria, including filename, size, file time, and | 
|  | // potentially file attributes.  The criteria are given in a string with | 
|  | // a simple expression language. Examples: | 
|  | // | 
|  | // find all .txt files: | 
|  | //     name = *.txt | 
|  | // | 
|  | // shorthand for the above | 
|  | //     *.txt | 
|  | // | 
|  | // all files modified after January 1st, 2009 | 
|  | //     mtime > 2009-01-01 | 
|  | // | 
|  | // All .txt files modified after the first of the year | 
|  | //     name = *.txt  AND  mtime > 2009-01-01 | 
|  | // | 
|  | // All .txt files modified after the first of the year, or any file with the archive bit set | 
|  | //     (name = *.txt  AND  mtime > 2009-01-01) or (attribtues = A) | 
|  | // | 
|  | // All .txt files or any file greater than 1mb in size | 
|  | //     (name = *.txt  or  size > 1mb) | 
|  | // | 
|  | // and so on. | 
|  | // ------------------------------------------------------------------ | 
|  |  | 
|  |  | 
|  | using System; | 
|  | using System.Globalization; | 
|  | using System.IO; | 
|  | using System.Text; | 
|  | using System.Reflection; | 
|  | using System.ComponentModel; | 
|  | using System.Text.RegularExpressions; | 
|  | using System.Collections.Generic; | 
|  | #if SILVERLIGHT | 
|  | using System.Linq; | 
|  | #endif | 
|  |  | 
|  | namespace OfficeOpenXml.Packaging.Ionic | 
|  | { | 
|  |  | 
|  | /// <summary> | 
|  | /// Enumerates the options for a logical conjunction. This enum is intended for use | 
|  | /// internally by the FileSelector class. | 
|  | /// </summary> | 
|  | internal enum LogicalConjunction | 
|  | { | 
|  | NONE, | 
|  | AND, | 
|  | OR, | 
|  | XOR, | 
|  | } | 
|  |  | 
|  | internal enum WhichTime | 
|  | { | 
|  | atime, | 
|  | mtime, | 
|  | ctime, | 
|  | } | 
|  |  | 
|  |  | 
|  | internal enum ComparisonOperator | 
|  | { | 
|  | [Description(">")] | 
|  | GreaterThan, | 
|  | [Description(">=")] | 
|  | GreaterThanOrEqualTo, | 
|  | [Description("<")] | 
|  | LesserThan, | 
|  | [Description("<=")] | 
|  | LesserThanOrEqualTo, | 
|  | [Description("=")] | 
|  | EqualTo, | 
|  | [Description("!=")] | 
|  | NotEqualTo | 
|  | } | 
|  |  | 
|  |  | 
|  | internal abstract partial class SelectionCriterion | 
|  | { | 
|  | internal virtual bool Verbose | 
|  | { | 
|  | get;set; | 
|  | } | 
|  | internal abstract bool Evaluate(string filename); | 
|  |  | 
|  | [System.Diagnostics.Conditional("SelectorTrace")] | 
|  | protected static void CriterionTrace(string format, params object[] args) | 
|  | { | 
|  | //System.Console.WriteLine("  " + format, args); | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | internal partial class SizeCriterion : SelectionCriterion | 
|  | { | 
|  | internal ComparisonOperator Operator; | 
|  | internal Int64 Size; | 
|  |  | 
|  | public override String ToString() | 
|  | { | 
|  | StringBuilder sb = new StringBuilder(); | 
|  | sb.Append("size ").Append(EnumUtil.GetDescription(Operator)).Append(" ").Append(Size.ToString()); | 
|  | return sb.ToString(); | 
|  | } | 
|  |  | 
|  | internal override bool Evaluate(string filename) | 
|  | { | 
|  | System.IO.FileInfo fi = new System.IO.FileInfo(filename); | 
|  | CriterionTrace("SizeCriterion::Evaluate('{0}' [{1}])", | 
|  | filename, this.ToString()); | 
|  | return _Evaluate(fi.Length); | 
|  | } | 
|  |  | 
|  | private bool _Evaluate(Int64 Length) | 
|  | { | 
|  | bool result = false; | 
|  | switch (Operator) | 
|  | { | 
|  | case ComparisonOperator.GreaterThanOrEqualTo: | 
|  | result = Length >= Size; | 
|  | break; | 
|  | case ComparisonOperator.GreaterThan: | 
|  | result = Length > Size; | 
|  | break; | 
|  | case ComparisonOperator.LesserThanOrEqualTo: | 
|  | result = Length <= Size; | 
|  | break; | 
|  | case ComparisonOperator.LesserThan: | 
|  | result = Length < Size; | 
|  | break; | 
|  | case ComparisonOperator.EqualTo: | 
|  | result = Length == Size; | 
|  | break; | 
|  | case ComparisonOperator.NotEqualTo: | 
|  | result = Length != Size; | 
|  | break; | 
|  | default: | 
|  | throw new ArgumentException("Operator"); | 
|  | } | 
|  | return result; | 
|  | } | 
|  |  | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  | internal partial class TimeCriterion : SelectionCriterion | 
|  | { | 
|  | internal ComparisonOperator Operator; | 
|  | internal WhichTime Which; | 
|  | internal DateTime Time; | 
|  |  | 
|  | public override String ToString() | 
|  | { | 
|  | StringBuilder sb = new StringBuilder(); | 
|  | sb.Append(Which.ToString()).Append(" ").Append(EnumUtil.GetDescription(Operator)).Append(" ").Append(Time.ToString("yyyy-MM-dd-HH:mm:ss")); | 
|  | return sb.ToString(); | 
|  | } | 
|  |  | 
|  | internal override bool Evaluate(string filename) | 
|  | { | 
|  | DateTime x; | 
|  | switch (Which) | 
|  | { | 
|  | case WhichTime.atime: | 
|  | x = System.IO.File.GetLastAccessTime(filename).ToUniversalTime(); | 
|  | break; | 
|  | case WhichTime.mtime: | 
|  | x = System.IO.File.GetLastWriteTime(filename).ToUniversalTime(); | 
|  | break; | 
|  | case WhichTime.ctime: | 
|  | x = System.IO.File.GetCreationTime(filename).ToUniversalTime(); | 
|  | break; | 
|  | default: | 
|  | throw new ArgumentException("Operator"); | 
|  | } | 
|  | CriterionTrace("TimeCriterion({0},{1})= {2}", filename, Which.ToString(), x); | 
|  | return _Evaluate(x); | 
|  | } | 
|  |  | 
|  |  | 
|  | private bool _Evaluate(DateTime x) | 
|  | { | 
|  | bool result = false; | 
|  | switch (Operator) | 
|  | { | 
|  | case ComparisonOperator.GreaterThanOrEqualTo: | 
|  | result = (x >= Time); | 
|  | break; | 
|  | case ComparisonOperator.GreaterThan: | 
|  | result = (x > Time); | 
|  | break; | 
|  | case ComparisonOperator.LesserThanOrEqualTo: | 
|  | result = (x <= Time); | 
|  | break; | 
|  | case ComparisonOperator.LesserThan: | 
|  | result = (x < Time); | 
|  | break; | 
|  | case ComparisonOperator.EqualTo: | 
|  | result = (x == Time); | 
|  | break; | 
|  | case ComparisonOperator.NotEqualTo: | 
|  | result = (x != Time); | 
|  | break; | 
|  | default: | 
|  | throw new ArgumentException("Operator"); | 
|  | } | 
|  |  | 
|  | CriterionTrace("TimeCriterion: {0}", result); | 
|  | return result; | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  | internal partial class NameCriterion : SelectionCriterion | 
|  | { | 
|  | private Regex _re; | 
|  | private String _regexString; | 
|  | internal ComparisonOperator Operator; | 
|  | private string _MatchingFileSpec; | 
|  | internal virtual string MatchingFileSpec | 
|  | { | 
|  | set | 
|  | { | 
|  | // workitem 8245 | 
|  | if (Directory.Exists(value)) | 
|  | { | 
|  | _MatchingFileSpec = ".\\" + value + "\\*.*"; | 
|  | } | 
|  | else | 
|  | { | 
|  | _MatchingFileSpec = value; | 
|  | } | 
|  |  | 
|  | _regexString = "^" + | 
|  | Regex.Escape(_MatchingFileSpec) | 
|  | .Replace(@"\\\*\.\*", @"\\([^\.]+|.*\.[^\\\.]*)") | 
|  | .Replace(@"\.\*", @"\.[^\\\.]*") | 
|  | .Replace(@"\*", @".*") | 
|  | //.Replace(@"\*", @"[^\\\.]*") // ill-conceived | 
|  | .Replace(@"\?", @"[^\\\.]") | 
|  | + "$"; | 
|  |  | 
|  | CriterionTrace("NameCriterion regexString({0})", _regexString); | 
|  |  | 
|  | _re = new Regex(_regexString, RegexOptions.IgnoreCase); | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | public override String ToString() | 
|  | { | 
|  | StringBuilder sb = new StringBuilder(); | 
|  | sb.Append("name ").Append(EnumUtil.GetDescription(Operator)) | 
|  | .Append(" '") | 
|  | .Append(_MatchingFileSpec) | 
|  | .Append("'"); | 
|  | return sb.ToString(); | 
|  | } | 
|  |  | 
|  |  | 
|  | internal override bool Evaluate(string filename) | 
|  | { | 
|  | CriterionTrace("NameCriterion::Evaluate('{0}' pattern[{1}])", | 
|  | filename, _MatchingFileSpec); | 
|  | return _Evaluate(filename); | 
|  | } | 
|  |  | 
|  | private bool _Evaluate(string fullpath) | 
|  | { | 
|  | CriterionTrace("NameCriterion::Evaluate({0})", fullpath); | 
|  | // No slash in the pattern implicitly means recurse, which means compare to | 
|  | // filename only, not full path. | 
|  | String f = (_MatchingFileSpec.IndexOf('\\') == -1) | 
|  | ? System.IO.Path.GetFileName(fullpath) | 
|  | : fullpath; // compare to fullpath | 
|  |  | 
|  | bool result = _re.IsMatch(f); | 
|  |  | 
|  | if (Operator != ComparisonOperator.EqualTo) | 
|  | result = !result; | 
|  | return result; | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | internal partial class TypeCriterion : SelectionCriterion | 
|  | { | 
|  | private char ObjectType;  // 'D' = Directory, 'F' = File | 
|  | internal ComparisonOperator Operator; | 
|  | internal string AttributeString | 
|  | { | 
|  | get | 
|  | { | 
|  | return ObjectType.ToString(); | 
|  | } | 
|  | set | 
|  | { | 
|  | if (value.Length != 1 || | 
|  | (value[0]!='D' && value[0]!='F')) | 
|  | throw new ArgumentException("Specify a single character: either D or F"); | 
|  | ObjectType = value[0]; | 
|  | } | 
|  | } | 
|  |  | 
|  | public override String ToString() | 
|  | { | 
|  | StringBuilder sb = new StringBuilder(); | 
|  | sb.Append("type ").Append(EnumUtil.GetDescription(Operator)).Append(" ").Append(AttributeString); | 
|  | return sb.ToString(); | 
|  | } | 
|  |  | 
|  | internal override bool Evaluate(string filename) | 
|  | { | 
|  | CriterionTrace("TypeCriterion::Evaluate({0})", filename); | 
|  |  | 
|  | bool result = (ObjectType == 'D') | 
|  | ? Directory.Exists(filename) | 
|  | : File.Exists(filename); | 
|  |  | 
|  | if (Operator != ComparisonOperator.EqualTo) | 
|  | result = !result; | 
|  | return result; | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | #if !SILVERLIGHT | 
|  | internal partial class AttributesCriterion : SelectionCriterion | 
|  | { | 
|  | private FileAttributes _Attributes; | 
|  | internal ComparisonOperator Operator; | 
|  | internal string AttributeString | 
|  | { | 
|  | get | 
|  | { | 
|  | string result = ""; | 
|  | if ((_Attributes & FileAttributes.Hidden) != 0) | 
|  | result += "H"; | 
|  | if ((_Attributes & FileAttributes.System) != 0) | 
|  | result += "S"; | 
|  | if ((_Attributes & FileAttributes.ReadOnly) != 0) | 
|  | result += "R"; | 
|  | if ((_Attributes & FileAttributes.Archive) != 0) | 
|  | result += "A"; | 
|  | if ((_Attributes & FileAttributes.ReparsePoint) != 0) | 
|  | result += "L"; | 
|  | if ((_Attributes & FileAttributes.NotContentIndexed) != 0) | 
|  | result += "I"; | 
|  | return result; | 
|  | } | 
|  |  | 
|  | set | 
|  | { | 
|  | _Attributes = FileAttributes.Normal; | 
|  | foreach (char c in value.ToUpper(CultureInfo.InvariantCulture)) | 
|  | { | 
|  | switch (c) | 
|  | { | 
|  | case 'H': | 
|  | if ((_Attributes & FileAttributes.Hidden) != 0) | 
|  | throw new ArgumentException(String.Format("Repeated flag. ({0})", c), "value"); | 
|  | _Attributes |= FileAttributes.Hidden; | 
|  | break; | 
|  |  | 
|  | case 'R': | 
|  | if ((_Attributes & FileAttributes.ReadOnly) != 0) | 
|  | throw new ArgumentException(String.Format("Repeated flag. ({0})", c), "value"); | 
|  | _Attributes |= FileAttributes.ReadOnly; | 
|  | break; | 
|  |  | 
|  | case 'S': | 
|  | if ((_Attributes & FileAttributes.System) != 0) | 
|  | throw new ArgumentException(String.Format("Repeated flag. ({0})", c), "value"); | 
|  | _Attributes |= FileAttributes.System; | 
|  | break; | 
|  |  | 
|  | case 'A': | 
|  | if ((_Attributes & FileAttributes.Archive) != 0) | 
|  | throw new ArgumentException(String.Format("Repeated flag. ({0})", c), "value"); | 
|  | _Attributes |= FileAttributes.Archive; | 
|  | break; | 
|  |  | 
|  | case 'I': | 
|  | if ((_Attributes & FileAttributes.NotContentIndexed) != 0) | 
|  | throw new ArgumentException(String.Format("Repeated flag. ({0})", c), "value"); | 
|  | _Attributes |= FileAttributes.NotContentIndexed; | 
|  | break; | 
|  |  | 
|  | case 'L': | 
|  | if ((_Attributes & FileAttributes.ReparsePoint) != 0) | 
|  | throw new ArgumentException(String.Format("Repeated flag. ({0})", c), "value"); | 
|  | _Attributes |= FileAttributes.ReparsePoint; | 
|  | break; | 
|  |  | 
|  | default: | 
|  | throw new ArgumentException(value); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | public override String ToString() | 
|  | { | 
|  | StringBuilder sb = new StringBuilder(); | 
|  | sb.Append("attributes ").Append(EnumUtil.GetDescription(Operator)).Append(" ").Append(AttributeString); | 
|  | return sb.ToString(); | 
|  | } | 
|  |  | 
|  | private bool _EvaluateOne(FileAttributes fileAttrs, FileAttributes criterionAttrs) | 
|  | { | 
|  | bool result = false; | 
|  | if ((_Attributes & criterionAttrs) == criterionAttrs) | 
|  | result = ((fileAttrs & criterionAttrs) == criterionAttrs); | 
|  | else | 
|  | result = true; | 
|  | return result; | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  | internal override bool Evaluate(string filename) | 
|  | { | 
|  | // workitem 10191 | 
|  | if (Directory.Exists(filename)) | 
|  | { | 
|  | // Directories don't have file attributes, so the result | 
|  | // of an evaluation is always NO. This gets negated if | 
|  | // the operator is NotEqualTo. | 
|  | return (Operator != ComparisonOperator.EqualTo); | 
|  | } | 
|  | #if NETCF | 
|  | FileAttributes fileAttrs = NetCfFile.GetAttributes(filename); | 
|  | #else | 
|  | FileAttributes fileAttrs = System.IO.File.GetAttributes(filename); | 
|  | #endif | 
|  |  | 
|  | return _Evaluate(fileAttrs); | 
|  | } | 
|  |  | 
|  | private bool _Evaluate(FileAttributes fileAttrs) | 
|  | { | 
|  | bool result = _EvaluateOne(fileAttrs, FileAttributes.Hidden); | 
|  | if (result) | 
|  | result = _EvaluateOne(fileAttrs, FileAttributes.System); | 
|  | if (result) | 
|  | result = _EvaluateOne(fileAttrs, FileAttributes.ReadOnly); | 
|  | if (result) | 
|  | result = _EvaluateOne(fileAttrs, FileAttributes.Archive); | 
|  | if (result) | 
|  | result = _EvaluateOne(fileAttrs, FileAttributes.NotContentIndexed); | 
|  | if (result) | 
|  | result = _EvaluateOne(fileAttrs, FileAttributes.ReparsePoint); | 
|  |  | 
|  | if (Operator != ComparisonOperator.EqualTo) | 
|  | result = !result; | 
|  |  | 
|  | return result; | 
|  | } | 
|  | } | 
|  | #endif | 
|  |  | 
|  |  | 
|  | internal partial class CompoundCriterion : SelectionCriterion | 
|  | { | 
|  | internal LogicalConjunction Conjunction; | 
|  | internal SelectionCriterion Left; | 
|  |  | 
|  | private SelectionCriterion _Right; | 
|  | internal SelectionCriterion Right | 
|  | { | 
|  | get { return _Right; } | 
|  | set | 
|  | { | 
|  | _Right = value; | 
|  | if (value == null) | 
|  | Conjunction = LogicalConjunction.NONE; | 
|  | else if (Conjunction == LogicalConjunction.NONE) | 
|  | Conjunction = LogicalConjunction.AND; | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | internal override bool Evaluate(string filename) | 
|  | { | 
|  | bool result = Left.Evaluate(filename); | 
|  | switch (Conjunction) | 
|  | { | 
|  | case LogicalConjunction.AND: | 
|  | if (result) | 
|  | result = Right.Evaluate(filename); | 
|  | break; | 
|  | case LogicalConjunction.OR: | 
|  | if (!result) | 
|  | result = Right.Evaluate(filename); | 
|  | break; | 
|  | case LogicalConjunction.XOR: | 
|  | result ^= Right.Evaluate(filename); | 
|  | break; | 
|  | default: | 
|  | throw new ArgumentException("Conjunction"); | 
|  | } | 
|  | return result; | 
|  | } | 
|  |  | 
|  |  | 
|  | public override String ToString() | 
|  | { | 
|  | StringBuilder sb = new StringBuilder(); | 
|  | sb.Append("(") | 
|  | .Append((Left != null) ? Left.ToString() : "null") | 
|  | .Append(" ") | 
|  | .Append(Conjunction.ToString()) | 
|  | .Append(" ") | 
|  | .Append((Right != null) ? Right.ToString() : "null") | 
|  | .Append(")"); | 
|  | return sb.ToString(); | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  | /// <summary> | 
|  | ///   FileSelector encapsulates logic that selects files from a source - a zip file | 
|  | ///   or the filesystem - based on a set of criteria.  This class is used internally | 
|  | ///   by the DotNetZip library, in particular for the AddSelectedFiles() methods. | 
|  | ///   This class can also be used independently of the zip capability in DotNetZip. | 
|  | /// </summary> | 
|  | /// | 
|  | /// <remarks> | 
|  | /// | 
|  | /// <para> | 
|  | ///   The FileSelector class is used internally by the ZipFile class for selecting | 
|  | ///   files for inclusion into the ZipFile, when the <see | 
|  | ///   cref="Ionic.Zip.ZipFile.AddSelectedFiles(String,String)"/> method, or one of | 
|  | ///   its overloads, is called.  It's also used for the <see | 
|  | ///   cref="Ionic.Zip.ZipFile.ExtractSelectedEntries(String)"/> methods.  Typically, an | 
|  | ///   application that creates or manipulates Zip archives will not directly | 
|  | ///   interact with the FileSelector class. | 
|  | /// </para> | 
|  | /// | 
|  | /// <para> | 
|  | ///   Some applications may wish to use the FileSelector class directly, to | 
|  | ///   select files from disk volumes based on a set of criteria, without creating or | 
|  | ///   querying Zip archives.  The file selection criteria include: a pattern to | 
|  | ///   match the filename; the last modified, created, or last accessed time of the | 
|  | ///   file; the size of the file; and the attributes of the file. | 
|  | /// </para> | 
|  | /// | 
|  | /// <para> | 
|  | ///   Consult the documentation for <see cref="SelectionCriteria"/> | 
|  | ///   for more information on specifying the selection criteria. | 
|  | /// </para> | 
|  | /// | 
|  | /// </remarks> | 
|  | internal partial class FileSelector | 
|  | { | 
|  | internal SelectionCriterion _Criterion; | 
|  |  | 
|  | #if NOTUSED | 
|  | /// <summary> | 
|  | ///   The default constructor. | 
|  | /// </summary> | 
|  | /// <remarks> | 
|  | ///   Typically, applications won't use this constructor.  Instead they'll | 
|  | ///   call the constructor that accepts a selectionCriteria string.  If you | 
|  | ///   use this constructor, you'll want to set the SelectionCriteria | 
|  | ///   property on the instance before calling SelectFiles(). | 
|  | /// </remarks> | 
|  | protected FileSelector() { } | 
|  | #endif | 
|  | /// <summary> | 
|  | ///   Constructor that allows the caller to specify file selection criteria. | 
|  | /// </summary> | 
|  | /// | 
|  | /// <remarks> | 
|  | /// <para> | 
|  | ///   This constructor allows the caller to specify a set of criteria for | 
|  | ///   selection of files. | 
|  | /// </para> | 
|  | /// | 
|  | /// <para> | 
|  | ///   See <see cref="FileSelector.SelectionCriteria"/> for a description of | 
|  | ///   the syntax of the selectionCriteria string. | 
|  | /// </para> | 
|  | /// | 
|  | /// <para> | 
|  | ///   By default the FileSelector will traverse NTFS Reparse Points.  To | 
|  | ///   change this, use <see cref="FileSelector(String, | 
|  | ///   bool)">FileSelector(String, bool)</see>. | 
|  | /// </para> | 
|  | /// </remarks> | 
|  | /// | 
|  | /// <param name="selectionCriteria">The criteria for file selection.</param> | 
|  | public FileSelector(String selectionCriteria) | 
|  | : this(selectionCriteria, true) | 
|  | { | 
|  | } | 
|  |  | 
|  | /// <summary> | 
|  | ///   Constructor that allows the caller to specify file selection criteria. | 
|  | /// </summary> | 
|  | /// | 
|  | /// <remarks> | 
|  | /// <para> | 
|  | ///   This constructor allows the caller to specify a set of criteria for | 
|  | ///   selection of files. | 
|  | /// </para> | 
|  | /// | 
|  | /// <para> | 
|  | ///   See <see cref="FileSelector.SelectionCriteria"/> for a description of | 
|  | ///   the syntax of the selectionCriteria string. | 
|  | /// </para> | 
|  | /// </remarks> | 
|  | /// | 
|  | /// <param name="selectionCriteria">The criteria for file selection.</param> | 
|  | /// <param name="traverseDirectoryReparsePoints"> | 
|  | /// whether to traverse NTFS reparse points (junctions). | 
|  | /// </param> | 
|  | public FileSelector(String selectionCriteria, bool traverseDirectoryReparsePoints) | 
|  | { | 
|  | if (!String.IsNullOrEmpty(selectionCriteria)) | 
|  | _Criterion = _ParseCriterion(selectionCriteria); | 
|  | TraverseReparsePoints = traverseDirectoryReparsePoints; | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  | /// <summary> | 
|  | ///   The string specifying which files to include when retrieving. | 
|  | /// </summary> | 
|  | /// <remarks> | 
|  | /// | 
|  | /// <para> | 
|  | ///   Specify the criteria in statements of 3 elements: a noun, an operator, | 
|  | ///   and a value.  Consider the string "name != *.doc" .  The noun is | 
|  | ///   "name".  The operator is "!=", implying "Not Equal".  The value is | 
|  | ///   "*.doc".  That criterion, in English, says "all files with a name that | 
|  | ///   does not end in the .doc extension." | 
|  | /// </para> | 
|  | /// | 
|  | /// <para> | 
|  | ///   Supported nouns include "name" (or "filename") for the filename; | 
|  | ///   "atime", "mtime", and "ctime" for last access time, last modfied time, | 
|  | ///   and created time of the file, respectively; "attributes" (or "attrs") | 
|  | ///   for the file attributes; "size" (or "length") for the file length | 
|  | ///   (uncompressed); and "type" for the type of object, either a file or a | 
|  | ///   directory.  The "attributes", "type", and "name" nouns all support = | 
|  | ///   and != as operators.  The "size", "atime", "mtime", and "ctime" nouns | 
|  | ///   support = and !=, and >, >=, <, <= as well.  The times are | 
|  | ///   taken to be expressed in local time. | 
|  | /// </para> | 
|  | /// | 
|  | /// <para> | 
|  | ///   Specify values for the file attributes as a string with one or more of | 
|  | ///   the characters H,R,S,A,I,L in any order, implying file attributes of | 
|  | ///   Hidden, ReadOnly, System, Archive, NotContextIndexed, and ReparsePoint | 
|  | ///   (symbolic link) respectively. | 
|  | /// </para> | 
|  | /// | 
|  | /// <para> | 
|  | ///   To specify a time, use YYYY-MM-DD-HH:mm:ss or YYYY/MM/DD-HH:mm:ss as | 
|  | ///   the format.  If you omit the HH:mm:ss portion, it is assumed to be | 
|  | ///   00:00:00 (midnight). | 
|  | /// </para> | 
|  | /// | 
|  | /// <para> | 
|  | ///   The value for a size criterion is expressed in integer quantities of | 
|  | ///   bytes, kilobytes (use k or kb after the number), megabytes (m or mb), | 
|  | ///   or gigabytes (g or gb). | 
|  | /// </para> | 
|  | /// | 
|  | /// <para> | 
|  | ///   The value for a name is a pattern to match against the filename, | 
|  | ///   potentially including wildcards.  The pattern follows CMD.exe glob | 
|  | ///   rules: * implies one or more of any character, while ?  implies one | 
|  | ///   character.  If the name pattern contains any slashes, it is matched to | 
|  | ///   the entire filename, including the path; otherwise, it is matched | 
|  | ///   against only the filename without the path.  This means a pattern of | 
|  | ///   "*\*.*" matches all files one directory level deep, while a pattern of | 
|  | ///   "*.*" matches all files in all directories. | 
|  | /// </para> | 
|  | /// | 
|  | /// <para> | 
|  | ///   To specify a name pattern that includes spaces, use single quotes | 
|  | ///   around the pattern.  A pattern of "'* *.*'" will match all files that | 
|  | ///   have spaces in the filename.  The full criteria string for that would | 
|  | ///   be "name = '* *.*'" . | 
|  | /// </para> | 
|  | /// | 
|  | /// <para> | 
|  | ///   The value for a type criterion is either F (implying a file) or D | 
|  | ///   (implying a directory). | 
|  | /// </para> | 
|  | /// | 
|  | /// <para> | 
|  | ///   Some examples: | 
|  | /// </para> | 
|  | /// | 
|  | /// <list type="table"> | 
|  | ///   <listheader> | 
|  | ///     <term>criteria</term> | 
|  | ///     <description>Files retrieved</description> | 
|  | ///   </listheader> | 
|  | /// | 
|  | ///   <item> | 
|  | ///     <term>name != *.xls </term> | 
|  | ///     <description>any file with an extension that is not .xls | 
|  | ///     </description> | 
|  | ///   </item> | 
|  | /// | 
|  | ///   <item> | 
|  | ///     <term>name = *.mp3 </term> | 
|  | ///     <description>any file with a .mp3 extension. | 
|  | ///     </description> | 
|  | ///   </item> | 
|  | /// | 
|  | ///   <item> | 
|  | ///     <term>*.mp3</term> | 
|  | ///     <description>(same as above) any file with a .mp3 extension. | 
|  | ///     </description> | 
|  | ///   </item> | 
|  | /// | 
|  | ///   <item> | 
|  | ///     <term>attributes = A </term> | 
|  | ///     <description>all files whose attributes include the Archive bit. | 
|  | ///     </description> | 
|  | ///   </item> | 
|  | /// | 
|  | ///   <item> | 
|  | ///     <term>attributes != H </term> | 
|  | ///     <description>all files whose attributes do not include the Hidden bit. | 
|  | ///     </description> | 
|  | ///   </item> | 
|  | /// | 
|  | ///   <item> | 
|  | ///     <term>mtime > 2009-01-01</term> | 
|  | ///     <description>all files with a last modified time after January 1st, 2009. | 
|  | ///     </description> | 
|  | ///   </item> | 
|  | /// | 
|  | ///   <item> | 
|  | ///     <term>ctime > 2009/01/01-03:00:00</term> | 
|  | ///     <description>all files with a created time after 3am (local time), | 
|  | ///     on January 1st, 2009. | 
|  | ///     </description> | 
|  | ///   </item> | 
|  | /// | 
|  | ///   <item> | 
|  | ///     <term>size > 2gb</term> | 
|  | ///     <description>all files whose uncompressed size is greater than 2gb. | 
|  | ///     </description> | 
|  | ///   </item> | 
|  | /// | 
|  | ///   <item> | 
|  | ///     <term>type = D</term> | 
|  | ///     <description>all directories in the filesystem. </description> | 
|  | ///   </item> | 
|  | /// | 
|  | /// </list> | 
|  | /// | 
|  | /// <para> | 
|  | ///   You can combine criteria with the conjunctions AND, OR, and XOR. Using | 
|  | ///   a string like "name = *.txt AND size >= 100k" for the | 
|  | ///   selectionCriteria retrieves entries whose names end in .txt, and whose | 
|  | ///   uncompressed size is greater than or equal to 100 kilobytes. | 
|  | /// </para> | 
|  | /// | 
|  | /// <para> | 
|  | ///   For more complex combinations of criteria, you can use parenthesis to | 
|  | ///   group clauses in the boolean logic.  Absent parenthesis, the | 
|  | ///   precedence of the criterion atoms is determined by order of | 
|  | ///   appearance.  Unlike the C# language, the AND conjunction does not take | 
|  | ///   precendence over the logical OR.  This is important only in strings | 
|  | ///   that contain 3 or more criterion atoms.  In other words, "name = *.txt | 
|  | ///   and size > 1000 or attributes = H" implies "((name = *.txt AND size | 
|  | ///   > 1000) OR attributes = H)" while "attributes = H OR name = *.txt | 
|  | ///   and size > 1000" evaluates to "((attributes = H OR name = *.txt) | 
|  | ///   AND size > 1000)".  When in doubt, use parenthesis. | 
|  | /// </para> | 
|  | /// | 
|  | /// <para> | 
|  | ///   Using time properties requires some extra care. If you want to | 
|  | ///   retrieve all entries that were last updated on 2009 February 14, | 
|  | ///   specify "mtime >= 2009-02-14 AND mtime < 2009-02-15".  Read this | 
|  | ///   to say: all files updated after 12:00am on February 14th, until | 
|  | ///   12:00am on February 15th.  You can use the same bracketing approach to | 
|  | ///   specify any time period - a year, a month, a week, and so on. | 
|  | /// </para> | 
|  | /// | 
|  | /// <para> | 
|  | ///   The syntax allows one special case: if you provide a string with no | 
|  | ///   spaces, it is treated as a pattern to match for the filename. | 
|  | ///   Therefore a string like "*.xls" will be equivalent to specifying "name | 
|  | ///   = *.xls".  This "shorthand" notation does not work with compound | 
|  | ///   criteria. | 
|  | /// </para> | 
|  | /// | 
|  | /// <para> | 
|  | ///   There is no logic in this class that insures that the inclusion | 
|  | ///   criteria are internally consistent.  For example, it's possible to | 
|  | ///   specify criteria that says the file must have a size of less than 100 | 
|  | ///   bytes, as well as a size that is greater than 1000 bytes.  Obviously | 
|  | ///   no file will ever satisfy such criteria, but this class does not check | 
|  | ///   for or detect such inconsistencies. | 
|  | /// </para> | 
|  | /// | 
|  | /// </remarks> | 
|  | /// | 
|  | /// <exception cref="System.Exception"> | 
|  | ///   Thrown in the setter if the value has an invalid syntax. | 
|  | /// </exception> | 
|  | public String SelectionCriteria | 
|  | { | 
|  | get | 
|  | { | 
|  | if (_Criterion == null) return null; | 
|  | return _Criterion.ToString(); | 
|  | } | 
|  | set | 
|  | { | 
|  | if (value == null) _Criterion = null; | 
|  | else if (value.Trim() == "") _Criterion = null; | 
|  | else | 
|  | _Criterion = _ParseCriterion(value); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// <summary> | 
|  | ///  Indicates whether searches will traverse NTFS reparse points, like Junctions. | 
|  | /// </summary> | 
|  | public bool TraverseReparsePoints | 
|  | { | 
|  | get; set; | 
|  | } | 
|  |  | 
|  |  | 
|  | private enum ParseState | 
|  | { | 
|  | Start, | 
|  | OpenParen, | 
|  | CriterionDone, | 
|  | ConjunctionPending, | 
|  | Whitespace, | 
|  | } | 
|  |  | 
|  |  | 
|  | private static class RegexAssertions | 
|  | { | 
|  | public static readonly String PrecededByOddNumberOfSingleQuotes = "(?<=(?:[^']*'[^']*')*'[^']*)"; | 
|  | public static readonly String FollowedByOddNumberOfSingleQuotesAndLineEnd = "(?=[^']*'(?:[^']*'[^']*')*[^']*$)"; | 
|  |  | 
|  | public static readonly String PrecededByEvenNumberOfSingleQuotes = "(?<=(?:[^']*'[^']*')*[^']*)"; | 
|  | public static readonly String FollowedByEvenNumberOfSingleQuotesAndLineEnd = "(?=(?:[^']*'[^']*')*[^']*$)"; | 
|  | } | 
|  |  | 
|  |  | 
|  | private static string NormalizeCriteriaExpression(string source) | 
|  | { | 
|  | // The goal here is to normalize the criterion expression. At output, in | 
|  | // the transformed criterion string, every significant syntactic element | 
|  | // - a property element, grouping paren for the boolean logic, operator | 
|  | // ( = < > != ), conjunction, or property value - will be separated from | 
|  | // its neighbors by at least one space. Thus, | 
|  | // | 
|  | // before                         after | 
|  | // ------------------------------------------------------------------- | 
|  | // name=*.txt                     name = *.txt | 
|  | // (size>100)AND(name=*.txt)      ( size > 100 ) AND ( name = *.txt ) | 
|  | // | 
|  | // This is relatively straightforward using regular expression | 
|  | // replacement. This method applies a distinct regex pattern and | 
|  | // corresponding replacement string for each one of a number of cases: | 
|  | // an open paren followed by a word; a word followed by a close-paren; a | 
|  | // pair of open parens; a close paren followed by a word (which should | 
|  | // then be followed by an open paren). And so on. These patterns and | 
|  | // replacements are all stored in prPairs. By applying each of these | 
|  | // regex replacements in turn, we get the transformed string. Easy. | 
|  | // | 
|  | // The resulting "normalized" criterion string, is then used as the | 
|  | // subject that gets parsed, by splitting the string into tokens that | 
|  | // are separated by spaces.  Here, there's a twist. The spaces within | 
|  | // single-quote delimiters do not delimit distinct tokens.  So, this | 
|  | // normalization method temporarily replaces those spaces with | 
|  | // ASCII 6 (0x06), a control character which is not a legal | 
|  | // character in a filename. The parsing logic that happens later will | 
|  | // revert that change, restoring the original value of the filename | 
|  | // specification. | 
|  | // | 
|  | // To illustrate, for a "before" string of [(size>100)AND(name='Name | 
|  | // (with Parens).txt')] , the "after" string is [( size > 100 ) AND | 
|  | // ( name = 'Name\u0006(with\u0006Parens).txt' )]. | 
|  | // | 
|  |  | 
|  | string[][] prPairs = | 
|  | { | 
|  | // A. opening double parens - insert a space between them | 
|  | new string[] { @"([^']*)\(\(([^']+)", "$1( ($2" }, | 
|  |  | 
|  | // B. closing double parens - insert a space between | 
|  | new string[] { @"(.)\)\)", "$1) )" }, | 
|  |  | 
|  | // C. single open paren with a following word - insert a space between | 
|  | new string[] { @"\((\S)", "( $1" }, | 
|  |  | 
|  | // D. single close paren with a preceding word - insert a space between the two | 
|  | new string[] { @"(\S)\)", "$1 )" }, | 
|  |  | 
|  | // E. close paren at line start?, insert a space before the close paren | 
|  | // this seems like a degenerate case.  I don't recall why it's here. | 
|  | new string[] { @"^\)", " )" }, | 
|  |  | 
|  | // F. a word (likely a conjunction) followed by an open paren - insert a space between | 
|  | new string[] { @"(\S)\(", "$1 (" }, | 
|  |  | 
|  | // G. single close paren followed by word - insert a paren after close paren | 
|  | new string[] { @"\)(\S)", ") $1" }, | 
|  |  | 
|  | // H. insert space between = and a following single quote | 
|  | //new string[] { @"(=|!=)('[^']*')", "$1 $2" }, | 
|  | new string[] { @"(=)('[^']*')", "$1 $2" }, | 
|  |  | 
|  | // I. insert space between property names and the following operator | 
|  | //new string[] { @"([^ ])([><(?:!=)=])", "$1 $2" }, | 
|  | new string[] { @"([^ !><])(>|<|!=|=)", "$1 $2" }, | 
|  |  | 
|  | // J. insert spaces between operators and the following values | 
|  | //new string[] { @"([><(?:!=)=])([^ ])", "$1 $2" }, | 
|  | new string[] { @"(>|<|!=|=)([^ =])", "$1 $2" }, | 
|  |  | 
|  | // K. replace fwd slash with backslash | 
|  | new string[] { @"/", "\\" }, | 
|  | }; | 
|  |  | 
|  | string interim = source; | 
|  |  | 
|  | for (int i=0; i < prPairs.Length; i++) | 
|  | { | 
|  | //char caseIdx = (char)('A' + i); | 
|  | string pattern = RegexAssertions.PrecededByEvenNumberOfSingleQuotes + | 
|  | prPairs[i][0] + | 
|  | RegexAssertions.FollowedByEvenNumberOfSingleQuotesAndLineEnd; | 
|  |  | 
|  | interim = Regex.Replace(interim, pattern, prPairs[i][1]); | 
|  | } | 
|  |  | 
|  | // match a fwd slash, followed by an odd number of single quotes. | 
|  | // This matches fwd slashes only inside a pair of single quote delimiters, | 
|  | // eg, a filename.  This must be done as well as the case above, to handle | 
|  | // filenames specified inside quotes as well as filenames without quotes. | 
|  | var regexPattern = @"/" + | 
|  | RegexAssertions.FollowedByOddNumberOfSingleQuotesAndLineEnd; | 
|  | // replace with backslash | 
|  | interim = Regex.Replace(interim, regexPattern, "\\"); | 
|  |  | 
|  | // match a space, followed by an odd number of single quotes. | 
|  | // This matches spaces only inside a pair of single quote delimiters. | 
|  | regexPattern = " " + | 
|  | RegexAssertions.FollowedByOddNumberOfSingleQuotesAndLineEnd; | 
|  |  | 
|  | // Replace all spaces that appear inside single quotes, with | 
|  | // ascii 6.  This allows a split on spaces to get tokens in | 
|  | // the expression. The split will not split any filename or | 
|  | // wildcard that appears within single quotes. After tokenizing, we | 
|  | // need to replace ascii 6 with ascii 32 to revert the | 
|  | // spaces within quotes. | 
|  | return Regex.Replace(interim, regexPattern, "\u0006"); | 
|  | } | 
|  |  | 
|  |  | 
|  | private static SelectionCriterion _ParseCriterion(String s) | 
|  | { | 
|  | if (s == null) return null; | 
|  |  | 
|  | // inject spaces after open paren and before close paren, etc | 
|  | s = NormalizeCriteriaExpression(s); | 
|  |  | 
|  | // no spaces in the criteria is shorthand for filename glob | 
|  | if (s.IndexOf(" ") == -1) | 
|  | s = "name = " + s; | 
|  |  | 
|  | // split the expression into tokens | 
|  | string[] tokens = s.Trim().Split(' ', '\t'); | 
|  |  | 
|  | if (tokens.Length < 3) throw new ArgumentException(s); | 
|  |  | 
|  | SelectionCriterion current = null; | 
|  |  | 
|  | LogicalConjunction pendingConjunction = LogicalConjunction.NONE; | 
|  |  | 
|  | ParseState state; | 
|  | var stateStack = new System.Collections.Generic.Stack<ParseState>(); | 
|  | var critStack = new System.Collections.Generic.Stack<SelectionCriterion>(); | 
|  | stateStack.Push(ParseState.Start); | 
|  |  | 
|  | for (int i = 0; i < tokens.Length; i++) | 
|  | { | 
|  | string tok1 = tokens[i].ToLower(CultureInfo.InvariantCulture); | 
|  | switch (tok1) | 
|  | { | 
|  | case "and": | 
|  | case "xor": | 
|  | case "or": | 
|  | state = stateStack.Peek(); | 
|  | if (state != ParseState.CriterionDone) | 
|  | throw new ArgumentException(String.Join(" ", tokens, i, tokens.Length - i)); | 
|  |  | 
|  | if (tokens.Length <= i + 3) | 
|  | throw new ArgumentException(String.Join(" ", tokens, i, tokens.Length - i)); | 
|  |  | 
|  | pendingConjunction = (LogicalConjunction)Enum.Parse(typeof(LogicalConjunction), tokens[i].ToUpper(CultureInfo.InvariantCulture), true); | 
|  | current = new CompoundCriterion { Left = current, Right = null, Conjunction = pendingConjunction }; | 
|  | stateStack.Push(state); | 
|  | stateStack.Push(ParseState.ConjunctionPending); | 
|  | critStack.Push(current); | 
|  | break; | 
|  |  | 
|  | case "(": | 
|  | state = stateStack.Peek(); | 
|  | if (state != ParseState.Start && state != ParseState.ConjunctionPending && state != ParseState.OpenParen) | 
|  | throw new ArgumentException(String.Join(" ", tokens, i, tokens.Length - i)); | 
|  |  | 
|  | if (tokens.Length <= i + 4) | 
|  | throw new ArgumentException(String.Join(" ", tokens, i, tokens.Length - i)); | 
|  |  | 
|  | stateStack.Push(ParseState.OpenParen); | 
|  | break; | 
|  |  | 
|  | case ")": | 
|  | state = stateStack.Pop(); | 
|  | if (stateStack.Peek() != ParseState.OpenParen) | 
|  | throw new ArgumentException(String.Join(" ", tokens, i, tokens.Length - i)); | 
|  |  | 
|  | stateStack.Pop(); | 
|  | stateStack.Push(ParseState.CriterionDone); | 
|  | break; | 
|  |  | 
|  | case "atime": | 
|  | case "ctime": | 
|  | case "mtime": | 
|  | if (tokens.Length <= i + 2) | 
|  | throw new ArgumentException(String.Join(" ", tokens, i, tokens.Length - i)); | 
|  |  | 
|  | DateTime t; | 
|  | try | 
|  | { | 
|  | t = DateTime.ParseExact(tokens[i + 2], "yyyy-MM-dd-HH:mm:ss", null); | 
|  | } | 
|  | catch (FormatException) | 
|  | { | 
|  | try | 
|  | { | 
|  | t = DateTime.ParseExact(tokens[i + 2], "yyyy/MM/dd-HH:mm:ss", null); | 
|  | } | 
|  | catch (FormatException) | 
|  | { | 
|  | try | 
|  | { | 
|  | t = DateTime.ParseExact(tokens[i + 2], "yyyy/MM/dd", null); | 
|  | } | 
|  | catch (FormatException) | 
|  | { | 
|  | try | 
|  | { | 
|  | t = DateTime.ParseExact(tokens[i + 2], "MM/dd/yyyy", null); | 
|  | } | 
|  | catch (FormatException) | 
|  | { | 
|  | t = DateTime.ParseExact(tokens[i + 2], "yyyy-MM-dd", null); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | t= DateTime.SpecifyKind(t, DateTimeKind.Local).ToUniversalTime(); | 
|  | current = new TimeCriterion | 
|  | { | 
|  | Which = (WhichTime)Enum.Parse(typeof(WhichTime), tokens[i], true), | 
|  | Operator = (ComparisonOperator)EnumUtil.Parse(typeof(ComparisonOperator), tokens[i + 1]), | 
|  | Time = t | 
|  | }; | 
|  | i += 2; | 
|  | stateStack.Push(ParseState.CriterionDone); | 
|  | break; | 
|  |  | 
|  |  | 
|  | case "length": | 
|  | case "size": | 
|  | if (tokens.Length <= i + 2) | 
|  | throw new ArgumentException(String.Join(" ", tokens, i, tokens.Length - i)); | 
|  |  | 
|  | Int64 sz = 0; | 
|  | string v = tokens[i + 2]; | 
|  | if (v.EndsWith("K", StringComparison.InvariantCultureIgnoreCase)) | 
|  | sz = Int64.Parse(v.Substring(0, v.Length - 1)) * 1024; | 
|  | else if (v.EndsWith("KB", StringComparison.InvariantCultureIgnoreCase)) | 
|  | sz = Int64.Parse(v.Substring(0, v.Length - 2)) * 1024; | 
|  | else if (v.EndsWith("M", StringComparison.InvariantCultureIgnoreCase)) | 
|  | sz = Int64.Parse(v.Substring(0, v.Length - 1)) * 1024 * 1024; | 
|  | else if (v.EndsWith("MB", StringComparison.InvariantCultureIgnoreCase)) | 
|  | sz = Int64.Parse(v.Substring(0, v.Length - 2)) * 1024 * 1024; | 
|  | else if (v.EndsWith("G", StringComparison.InvariantCultureIgnoreCase)) | 
|  | sz = Int64.Parse(v.Substring(0, v.Length - 1)) * 1024 * 1024 * 1024; | 
|  | else if (v.EndsWith("GB", StringComparison.InvariantCultureIgnoreCase)) | 
|  | sz = Int64.Parse(v.Substring(0, v.Length - 2)) * 1024 * 1024 * 1024; | 
|  | else sz = Int64.Parse(tokens[i + 2]); | 
|  |  | 
|  | current = new SizeCriterion | 
|  | { | 
|  | Size = sz, | 
|  | Operator = (ComparisonOperator)EnumUtil.Parse(typeof(ComparisonOperator), tokens[i + 1]) | 
|  | }; | 
|  | i += 2; | 
|  | stateStack.Push(ParseState.CriterionDone); | 
|  | break; | 
|  |  | 
|  | case "filename": | 
|  | case "name": | 
|  | { | 
|  | if (tokens.Length <= i + 2) | 
|  | throw new ArgumentException(String.Join(" ", tokens, i, tokens.Length - i)); | 
|  |  | 
|  | ComparisonOperator c = | 
|  | (ComparisonOperator)EnumUtil.Parse(typeof(ComparisonOperator), tokens[i + 1]); | 
|  |  | 
|  | if (c != ComparisonOperator.NotEqualTo && c != ComparisonOperator.EqualTo) | 
|  | throw new ArgumentException(String.Join(" ", tokens, i, tokens.Length - i)); | 
|  |  | 
|  | string m = tokens[i + 2]; | 
|  |  | 
|  | // handle single-quoted filespecs (used to include | 
|  | // spaces in filename patterns) | 
|  | if (m.StartsWith("'") && m.EndsWith("'")) | 
|  | { | 
|  | // trim off leading and trailing single quotes and | 
|  | // revert the control characters to spaces. | 
|  | m = m.Substring(1, m.Length - 2) | 
|  | .Replace("\u0006", " "); | 
|  | } | 
|  |  | 
|  | // if (m.StartsWith("'")) | 
|  | //     m = m.Replace("\u0006", " "); | 
|  |  | 
|  | current = new NameCriterion | 
|  | { | 
|  | MatchingFileSpec = m, | 
|  | Operator = c | 
|  | }; | 
|  | i += 2; | 
|  | stateStack.Push(ParseState.CriterionDone); | 
|  | } | 
|  | break; | 
|  |  | 
|  | #if !SILVERLIGHT | 
|  | case "attrs": | 
|  | case "attributes": | 
|  | #endif | 
|  | case "type": | 
|  | { | 
|  | if (tokens.Length <= i + 2) | 
|  | throw new ArgumentException(String.Join(" ", tokens, i, tokens.Length - i)); | 
|  |  | 
|  | ComparisonOperator c = | 
|  | (ComparisonOperator)EnumUtil.Parse(typeof(ComparisonOperator), tokens[i + 1]); | 
|  |  | 
|  | if (c != ComparisonOperator.NotEqualTo && c != ComparisonOperator.EqualTo) | 
|  | throw new ArgumentException(String.Join(" ", tokens, i, tokens.Length - i)); | 
|  |  | 
|  | #if SILVERLIGHT | 
|  | current = (SelectionCriterion) new TypeCriterion | 
|  | { | 
|  | AttributeString = tokens[i + 2], | 
|  | Operator = c | 
|  | }; | 
|  | #else | 
|  | current = (tok1 == "type") | 
|  | ? (SelectionCriterion) new TypeCriterion | 
|  | { | 
|  | AttributeString = tokens[i + 2], | 
|  | Operator = c | 
|  | } | 
|  | : (SelectionCriterion) new AttributesCriterion | 
|  | { | 
|  | AttributeString = tokens[i + 2], | 
|  | Operator = c | 
|  | }; | 
|  | #endif | 
|  | i += 2; | 
|  | stateStack.Push(ParseState.CriterionDone); | 
|  | } | 
|  | break; | 
|  |  | 
|  | case "": | 
|  | // NOP | 
|  | stateStack.Push(ParseState.Whitespace); | 
|  | break; | 
|  |  | 
|  | default: | 
|  | throw new ArgumentException("'" + tokens[i] + "'"); | 
|  | } | 
|  |  | 
|  | state = stateStack.Peek(); | 
|  | if (state == ParseState.CriterionDone) | 
|  | { | 
|  | stateStack.Pop(); | 
|  | if (stateStack.Peek() == ParseState.ConjunctionPending) | 
|  | { | 
|  | while (stateStack.Peek() == ParseState.ConjunctionPending) | 
|  | { | 
|  | var cc = critStack.Pop() as CompoundCriterion; | 
|  | cc.Right = current; | 
|  | current = cc; // mark the parent as current (walk up the tree) | 
|  | stateStack.Pop();   // the conjunction is no longer pending | 
|  |  | 
|  | state = stateStack.Pop(); | 
|  | if (state != ParseState.CriterionDone) | 
|  | throw new ArgumentException("??"); | 
|  | } | 
|  | } | 
|  | else stateStack.Push(ParseState.CriterionDone);  // not sure? | 
|  | } | 
|  |  | 
|  | if (state == ParseState.Whitespace) | 
|  | stateStack.Pop(); | 
|  | } | 
|  |  | 
|  | return current; | 
|  | } | 
|  |  | 
|  |  | 
|  | /// <summary> | 
|  | /// Returns a string representation of the FileSelector object. | 
|  | /// </summary> | 
|  | /// <returns>The string representation of the boolean logic statement of the file | 
|  | /// selection criteria for this instance. </returns> | 
|  | public override String ToString() | 
|  | { | 
|  | return "FileSelector("+_Criterion.ToString()+")"; | 
|  | } | 
|  |  | 
|  |  | 
|  | private bool Evaluate(string filename) | 
|  | { | 
|  | // dinoch - Thu, 11 Feb 2010  18:34 | 
|  | SelectorTrace("Evaluate({0})", filename); | 
|  | bool result = _Criterion.Evaluate(filename); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | [System.Diagnostics.Conditional("SelectorTrace")] | 
|  | private void SelectorTrace(string format, params object[] args) | 
|  | { | 
|  | if (_Criterion != null && _Criterion.Verbose) | 
|  | System.Console.WriteLine(format, args); | 
|  | } | 
|  |  | 
|  | /// <summary> | 
|  | ///   Returns the names of the files in the specified directory | 
|  | ///   that fit the selection criteria specified in the FileSelector. | 
|  | /// </summary> | 
|  | /// | 
|  | /// <remarks> | 
|  | ///   This is equivalent to calling <see cref="SelectFiles(String, bool)"/> | 
|  | ///   with recurseDirectories = false. | 
|  | /// </remarks> | 
|  | /// | 
|  | /// <param name="directory"> | 
|  | ///   The name of the directory over which to apply the FileSelector | 
|  | ///   criteria. | 
|  | /// </param> | 
|  | /// | 
|  | /// <returns> | 
|  | ///   A collection of strings containing fully-qualified pathnames of files | 
|  | ///   that match the criteria specified in the FileSelector instance. | 
|  | /// </returns> | 
|  | public System.Collections.Generic.ICollection<String> SelectFiles(String directory) | 
|  | { | 
|  | return SelectFiles(directory, false); | 
|  | } | 
|  |  | 
|  |  | 
|  | /// <summary> | 
|  | ///   Returns the names of the files in the specified directory that fit the | 
|  | ///   selection criteria specified in the FileSelector, optionally recursing | 
|  | ///   through subdirectories. | 
|  | /// </summary> | 
|  | /// | 
|  | /// <remarks> | 
|  | ///   This method applies the file selection criteria contained in the | 
|  | ///   FileSelector to the files contained in the given directory, and | 
|  | ///   returns the names of files that conform to the criteria. | 
|  | /// </remarks> | 
|  | /// | 
|  | /// <param name="directory"> | 
|  | ///   The name of the directory over which to apply the FileSelector | 
|  | ///   criteria. | 
|  | /// </param> | 
|  | /// | 
|  | /// <param name="recurseDirectories"> | 
|  | ///   Whether to recurse through subdirectories when applying the file | 
|  | ///   selection criteria. | 
|  | /// </param> | 
|  | /// | 
|  | /// <returns> | 
|  | ///   A collection of strings containing fully-qualified pathnames of files | 
|  | ///   that match the criteria specified in the FileSelector instance. | 
|  | /// </returns> | 
|  | public System.Collections.ObjectModel.ReadOnlyCollection<String> | 
|  | SelectFiles(String directory, | 
|  | bool recurseDirectories) | 
|  | { | 
|  | if (_Criterion == null) | 
|  | throw new ArgumentException("SelectionCriteria has not been set"); | 
|  |  | 
|  | var list = new List<String>(); | 
|  | try | 
|  | { | 
|  | if (Directory.Exists(directory)) | 
|  | { | 
|  | String[] filenames = Directory.GetFiles(directory); | 
|  |  | 
|  | // add the files: | 
|  | foreach (String filename in filenames) | 
|  | { | 
|  | if (Evaluate(filename)) | 
|  | list.Add(filename); | 
|  | } | 
|  |  | 
|  | if (recurseDirectories) | 
|  | { | 
|  | // add the subdirectories: | 
|  | String[] dirnames = Directory.GetDirectories(directory); | 
|  | foreach (String dir in dirnames) | 
|  | { | 
|  | if (this.TraverseReparsePoints | 
|  | #if !SILVERLIGHT | 
|  | || ((File.GetAttributes(dir) & FileAttributes.ReparsePoint) == 0) | 
|  | #endif | 
|  | ) | 
|  | { | 
|  | // workitem 10191 | 
|  | if (Evaluate(dir)) list.Add(dir); | 
|  | list.AddRange(this.SelectFiles(dir, recurseDirectories)); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | // can get System.UnauthorizedAccessException here | 
|  | catch (System.UnauthorizedAccessException) | 
|  | { | 
|  | } | 
|  | catch (System.IO.IOException) | 
|  | { | 
|  | } | 
|  |  | 
|  | return list.AsReadOnly(); | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  | /// <summary> | 
|  | /// Summary description for EnumUtil. | 
|  | /// </summary> | 
|  | internal sealed class EnumUtil | 
|  | { | 
|  | private EnumUtil() { } | 
|  | /// <summary> | 
|  | ///   Returns the value of the DescriptionAttribute if the specified Enum | 
|  | ///   value has one.  If not, returns the ToString() representation of the | 
|  | ///   Enum value. | 
|  | /// </summary> | 
|  | /// <param name="value">The Enum to get the description for</param> | 
|  | /// <returns></returns> | 
|  | internal static string GetDescription(System.Enum value) | 
|  | { | 
|  | FieldInfo fi = value.GetType().GetField(value.ToString()); | 
|  | var attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false); | 
|  | if (attributes.Length > 0) | 
|  | return attributes[0].Description; | 
|  | else | 
|  | return value.ToString(); | 
|  | } | 
|  |  | 
|  | /// <summary> | 
|  | ///   Converts the string representation of the name or numeric value of one | 
|  | ///   or more enumerated constants to an equivalent enumerated object. | 
|  | ///   Note: use the DescriptionAttribute on enum values to enable this. | 
|  | /// </summary> | 
|  | /// <param name="enumType">The System.Type of the enumeration.</param> | 
|  | /// <param name="stringRepresentation"> | 
|  | ///   A string containing the name or value to convert. | 
|  | /// </param> | 
|  | /// <returns></returns> | 
|  | internal static object Parse(Type enumType, string stringRepresentation) | 
|  | { | 
|  | return Parse(enumType, stringRepresentation, false); | 
|  | } | 
|  |  | 
|  |  | 
|  | #if SILVERLIGHT | 
|  | public static System.Enum[] GetEnumValues(Type type) | 
|  | { | 
|  | if (!type.IsEnum) | 
|  | throw new ArgumentException("not an enum"); | 
|  |  | 
|  | return ( | 
|  | from field in type.GetFields(BindingFlags.Public | BindingFlags.Static) | 
|  | where field.IsLiteral | 
|  | select (System.Enum)field.GetValue(null) | 
|  | ).ToArray(); | 
|  | } | 
|  |  | 
|  | public static string[] GetEnumStrings<T>() | 
|  | { | 
|  | var type = typeof(T); | 
|  | if (!type.IsEnum) | 
|  | throw new ArgumentException("not an enum"); | 
|  |  | 
|  | return ( | 
|  | from field in type.GetFields(BindingFlags.Public | BindingFlags.Static) | 
|  | where field.IsLiteral | 
|  | select field.Name | 
|  | ).ToArray(); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | /// <summary> | 
|  | ///   Converts the string representation of the name or numeric value of one | 
|  | ///   or more enumerated constants to an equivalent enumerated object.  A | 
|  | ///   parameter specified whether the operation is case-sensitive.  Note: | 
|  | ///   use the DescriptionAttribute on enum values to enable this. | 
|  | /// </summary> | 
|  | /// <param name="enumType">The System.Type of the enumeration.</param> | 
|  | /// <param name="stringRepresentation"> | 
|  | ///   A string containing the name or value to convert. | 
|  | /// </param> | 
|  | /// <param name="ignoreCase"> | 
|  | ///   Whether the operation is case-sensitive or not.</param> | 
|  | /// <returns></returns> | 
|  | internal static object Parse(Type enumType, string stringRepresentation, bool ignoreCase) | 
|  | { | 
|  | if (ignoreCase) | 
|  | stringRepresentation = stringRepresentation.ToLower(CultureInfo.InvariantCulture); | 
|  |  | 
|  | #if SILVERLIGHT | 
|  | foreach (System.Enum enumVal in GetEnumValues(enumType)) | 
|  | #else | 
|  | foreach (System.Enum enumVal in System.Enum.GetValues(enumType)) | 
|  | #endif | 
|  | { | 
|  | string description = GetDescription(enumVal); | 
|  | if (ignoreCase) | 
|  | description = description.ToLower(CultureInfo.InvariantCulture); | 
|  | if (description == stringRepresentation) | 
|  | return enumVal; | 
|  | } | 
|  |  | 
|  | return System.Enum.Parse(enumType, stringRepresentation, ignoreCase); | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | #if DEMO | 
|  | internal class DemonstrateFileSelector | 
|  | { | 
|  | private string _directory; | 
|  | private bool _recurse; | 
|  | private bool _traverse; | 
|  | private bool _verbose; | 
|  | private string _selectionCriteria; | 
|  | private FileSelector f; | 
|  |  | 
|  | public DemonstrateFileSelector() | 
|  | { | 
|  | this._directory = "."; | 
|  | this._recurse = true; | 
|  | } | 
|  |  | 
|  | public DemonstrateFileSelector(string[] args) : this() | 
|  | { | 
|  | for (int i = 0; i < args.Length; i++) | 
|  | { | 
|  | switch(args[i]) | 
|  | { | 
|  | case"-?": | 
|  | Usage(); | 
|  | Environment.Exit(0); | 
|  | break; | 
|  | case "-d": | 
|  | i++; | 
|  | if (args.Length <= i) | 
|  | throw new ArgumentException("-directory"); | 
|  | this._directory = args[i]; | 
|  | break; | 
|  | case "-norecurse": | 
|  | this._recurse = false; | 
|  | break; | 
|  |  | 
|  | case "-j-": | 
|  | this._traverse = false; | 
|  | break; | 
|  |  | 
|  | case "-j+": | 
|  | this._traverse = true; | 
|  | break; | 
|  |  | 
|  | case "-v": | 
|  | this._verbose = true; | 
|  | break; | 
|  |  | 
|  | default: | 
|  | if (this._selectionCriteria != null) | 
|  | throw new ArgumentException(args[i]); | 
|  | this._selectionCriteria = args[i]; | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (this._selectionCriteria != null) | 
|  | this.f = new FileSelector(this._selectionCriteria); | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | public static void Main(string[] args) | 
|  | { | 
|  | try | 
|  | { | 
|  | Console.WriteLine(); | 
|  | new DemonstrateFileSelector(args).Run(); | 
|  | } | 
|  | catch (Exception exc1) | 
|  | { | 
|  | Console.WriteLine("Exception: {0}", exc1.ToString()); | 
|  | Usage(); | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | public void Run() | 
|  | { | 
|  | if (this.f == null) | 
|  | this.f = new FileSelector("name = *.jpg AND (size > 1000 OR atime < 2009-02-14-01:00:00)"); | 
|  |  | 
|  | this.f.TraverseReparsePoints = _traverse; | 
|  | this.f.Verbose = this._verbose; | 
|  | Console.WriteLine(); | 
|  | Console.WriteLine(new String(':', 88)); | 
|  | Console.WriteLine("Selecting files:\n" + this.f.ToString()); | 
|  | var files = this.f.SelectFiles(this._directory, this._recurse); | 
|  | if (files.Count == 0) | 
|  | { | 
|  | Console.WriteLine("no files."); | 
|  | } | 
|  | else | 
|  | { | 
|  | Console.WriteLine("files: {0}", files.Count); | 
|  | foreach (string file in files) | 
|  | { | 
|  | Console.WriteLine("  " + file); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | public static void Usage() | 
|  | { | 
|  | Console.WriteLine("FileSelector: select files based on selection criteria.\n"); | 
|  | Console.WriteLine("Usage:\n  FileSelector <selectionCriteria>  [options]\n" + | 
|  | "\n" + | 
|  | "  -d <dir>   directory to select from (Default .)\n" + | 
|  | " -norecurse  don't recurse into subdirs\n" + | 
|  | " -j-         don't traverse junctions\n" + | 
|  | " -v          verbose output\n"); | 
|  | } | 
|  | } | 
|  |  | 
|  | #endif | 
|  |  | 
|  |  | 
|  |  | 
|  | } | 
|  |  | 
|  |  |