using FileSearch.Logic.Model.EncodingDetection; using FileSearch.Logic.Model.Engine; using System.Text; namespace FileSearch.Logic.Model.CriterionSchemas { internal class ContentCriterion : CriterionBase, ICriterion { private const int BufferSize = 32 * 1024; // 32KB private readonly string _text; private readonly char[][] _textInChars; private readonly bool _ignoreCase; private readonly bool _matchFullWords; private readonly IEncodingFactory _encodingFactory; public ContentCriterion(string text, bool ignoreCase, bool matchFullWords, IEncodingFactory encodingFactory) { if (text == null) throw new ArgumentNullException("text"); if (encodingFactory == null) throw new ArgumentNullException("encodingFactory"); _text = text; _ignoreCase = ignoreCase; _matchFullWords = matchFullWords; _encodingFactory = encodingFactory; _textInChars = StringToCharArrays(text, ignoreCase); } public string Name { get { return "File content"; } } public CriterionWeight Weight { get { return CriterionWeight.Heavy; } } public bool DirectorySupport { get { return false; } } public bool FileSupport { get { return true; } } public bool IsMatch(FileSystemInfo fileSystemInfo, ICriterionContext context) { var fileInfo = (FileInfo)fileSystemInfo; var buffer = new byte[BufferSize]; var textLength = _text.Length; Encoding[] encodings = new Encoding[1]; // Проверить несколько кодировок for (int encodingIndex = 0; encodingIndex < encodings.Length; encodingIndex++) { // Кодирование текущего цикла. Первая попытка будет NULL. Encoding encoding = encodings[encodingIndex]; bool characterShouldBeNonWord = false; char characterBefore = '\0'; using (var stream = fileInfo.OpenRead()) { int length; int foundMatchingSymbols = 0; while ((length = stream.Read(buffer, 0, BufferSize)) > 0) { // Если кодировка еще не определена, определите ее сейчас. if (encoding == null) { encodings = _encodingFactory.DetectEncoding(buffer); encoding = encodings[encodingIndex]; } var currentString = encoding.GetString(buffer, 0, length); var currentStringLength = currentString.Length; // Кэш bool startAtBegin = foundMatchingSymbols > 0; var charIndex = 0; // Первый символ должен быть символом, отличным от слова, если предыдущие прочитанные байты заканчиваются соответствующей строкой. if (_matchFullWords && characterShouldBeNonWord && currentStringLength > 0) { characterShouldBeNonWord = false; if (!CharIsWordChar(currentString[0])) return true; } // Проверьте, находится ли первый или следующий совпадающий символ в текущей строке. if ((charIndex = currentString.IndexOfAny(_textInChars[foundMatchingSymbols], charIndex)) >= 0) { do { // Назначьте персонажа заранее. if (charIndex > 0 && foundMatchingSymbols == 0 && _matchFullWords) characterBefore = currentString[charIndex - 1]; // Не первый символ _text, поэтому проверьте, находится ли второй символ в первой позиции. if (startAtBegin) { startAtBegin = false; if (charIndex > 0) // Буква должна быть на первой позиции! Если нет, начните заново с первого символа в _text. { foundMatchingSymbols = 0; continue; } } // Скопируйте переменную, чтобы она не была изменена в приведенном ниже цикле. var current = charIndex; // Постарайтесь сопоставить как можно больше символов. while (++foundMatchingSymbols < textLength && currentStringLength > ++current && (currentString[current] == _text[foundMatchingSymbols] || _ignoreCase && _textInChars[foundMatchingSymbols].Any(c => c == currentString[current]))) ; // Нашел! if (foundMatchingSymbols == textLength && (!_matchFullWords || !CharIsWordChar(characterBefore))) { if (_matchFullWords) { // Попытайтесь определить следующее чтение, заканчивается ли строка символом, отличным от слова. if (current >= currentStringLength - 1) characterShouldBeNonWord = true; // Проверьте, не является ли следующая буква словом char. Если нет, верните true. В противном случае убедитесь, что индекс равен +1, чтобы он был сброшен в следующем операторе IF. else if (!CharIsWordChar(currentString[++current])) return true; } else // Возвращает true, если не проверяется слово вместо части строки. return true; } // Сбросить счетчик совпадающих символов, если конец текущей строки не достигнут. Если да, продолжайте тестирование при следующем чтении. if (currentStringLength != current) foundMatchingSymbols = 0; // Проверьте, есть ли следующий соответствующий символ в текущей строке. } while ((charIndex = currentString.IndexOfAny(_textInChars[foundMatchingSymbols], ++charIndex)) >= 0); // Назначьте последний символ, чтобы в следующем раунде он знал предыдущий символ. if (foundMatchingSymbols == 0 && _matchFullWords) characterBefore = currentString[currentStringLength - 1]; } else { foundMatchingSymbols = 0; // Переназначьте предыдущий символ последнему символу в строке. if (_matchFullWords) characterBefore = currentString[currentStringLength - 1]; } } if (_matchFullWords && characterShouldBeNonWord) return true; } } return false; } private static bool CharIsWordChar(char c) { return char.IsLetterOrDigit(c) || c == '_'; } private static char[][] StringToCharArrays(string input, bool ignoreCase) { var list = new List(); foreach (var c in input) { if (!ignoreCase || !char.IsLetter(c)) list.Add(new[] { c }); else { var u = char.ToUpperInvariant(c); var l = char.ToLowerInvariant(c); list.Add(u != l ? new[] { u, l } : new[] { c }); } } return list.ToArray(); } } }