
Чтение текстового файла построчно в Collection на VB6: фильтр по словам и RegExp
Нужна не весь файл, а только строки с ошибками или без комментариев? Собираем функцию, которая читает файл в Collection и сразу отбирает строки по словам или регулярному выражению.
Зачем читать файл сразу с фильтром
Чтение файла в строку подходит, когда текст нужен целиком. Но часто программе интересна только часть строк: записи лога с уровнем ERROR, строки конфигурации без комментариев, позиции выгрузки с определённым артикулом. Загружать весь файл в память и потом отдельным циклом выбирать нужное неудобно и расточительно.
Функция Text2Col решает задачу за один проход. Она читает файл построчно, проверяет каждую строку условием и складывает подходящие в Collection. Вызывающий код получает готовый список и перебирает его через For Each. Если условие не задано, в коллекцию попадают все строки, и функция работает как обычное построчное чтение.
Параметры и перечисления
Чтобы вызов читался без комментариев, режимы оформлены перечислениями. GrabMode задаёт, что делать с найденными строками: оставлять (Include) или, наоборот, отбрасывать (Exclude). SearchMode выбирает механизм поиска: строковые функции VB или регулярные выражения.
Поисковая строка в строковом режиме может содержать несколько вариантов через вертикальную черту: «ERROR|FATAL» найдёт строки с любым из двух слов. В режиме RegExp та же черта уже часть синтаксиса шаблона и означает альтернативу, так что запись остаётся привычной.
Параметр CompareMode принимает стандартные vbBinaryCompare и vbTextCompare. Первый различает регистр, второй нет. Для регулярного выражения он переводится в свойство IgnoreCase.
Public Enum GrabMode
Include = 0 ' оставить найденные строки
Exclude = 1 ' оставить все, кроме найденных
End Enum
Public Enum SearchMode
UseStringFunctions = 0
UseRegex = 1
End EnumФункция Text2Col
Для регулярных выражений в проект добавляется ссылка Microsoft VBScript Regular Expressions 5.5. Шаблон компилируется один раз до цикла, а не на каждой строке, это заметно ускоряет обработку больших файлов. Для проверки совпадения достаточно метода Test: он возвращает True или False и не создаёт коллекцию совпадений, как Execute.
Номер файла берётся через FreeFile, а при ошибке файл закрывается до того, как исключение уйдёт наверх.
Public Function Text2Col(ByVal strPath As String, _
Optional ByVal strSearch As String, _
Optional ByVal Grab As GrabMode = Include, _
Optional ByVal Compare As VbCompareMethod = vbTextCompare, _
Optional ByVal SearchBy As SearchMode = UseStringFunctions) As Collection
Dim col As New Collection, rx As New VBScript_RegExp_55.RegExp
Dim arr() As String, s As String, i As Long, f As Integer, found As Boolean, hasFilter As Boolean
On Error GoTo ErrTrap
hasFilter = Len(Trim$(strSearch)) > 0
If SearchBy = UseRegex Then
rx.Pattern = strSearch
rx.IgnoreCase = (Compare = vbTextCompare)
Else
arr = Split(strSearch, "|")
End If
f = FreeFile
Open strPath For Input As #f
Do Until EOF(f)
Line Input #f, s
If hasFilter Then
found = False
If SearchBy = UseRegex Then
found = rx.Test(s)
Else
For i = 0 To UBound(arr)
If InStr(1, s, arr(i), Compare) > 0 Then found = True: Exit For
Next
End If
If found = (Grab = Include) Then col.Add s
Else
col.Add s
End If
Loop
Close #f
Set Text2Col = col
Exit Function
ErrTrap:
If f > 0 Then Close #f
Err.Raise Err.Number, "Text2Col", Err.Description
End FunctionКак работает условие отбора
Ключевая строка функции: If found = (Grab = Include). Она объединяет четыре случая в одно сравнение. При режиме Include строка попадает в коллекцию, когда совпадение найдено. При режиме Exclude, когда совпадения нет. Такой приём короче вложенных If и не даёт ошибиться в одной из веток.
В строковом режиме цикл по вариантам прерывается на первом совпадении через Exit For. Если вариантов много, ставьте самые частые первыми.
- Text2Col(path) вернёт все строки файла;
- Text2Col(path, «ERROR|FATAL») оставит только аварийные записи лога;
- Text2Col(path, «^\s*(;|#)», Exclude, , UseRegex) уберёт комментарии из ini-подобного файла;
- Text2Col(path, «\d{2}\.\d{2}\.\d{4}», Include, , UseRegex) отберёт строки с датой.
Ограничения Line Input и Collection
Line Input считает концом строки пару CR LF и одиночный CR. Файлы из Unix с одним LF он прочитает как одну огромную строку. Если такие файлы возможны, надёжнее прочитать файл целиком, заменить vbLf на vbCrLf после нормализации и разбить через Split.
Open For Input читает файл в ANSI-кодировке системы. Текст в UTF-8 придёт с искажёнными русскими буквами, а UTF-16 вообще не прочитается построчно. Для UTF-8 используйте ADODB.Stream с Charset utf-8 и методом ReadText(adReadLine).
Collection удобна для перебора, но доступ по индексу в ней медленный: col(i) каждый раз проходит список. Перебирайте результат через For Each. Если нужен произвольный доступ к десяткам тысяч строк, лучше вернуть массив строк.
Как это делают сегодня
Вопросы и ответы
Почему файл из Linux читается одной строкой?
Line Input не считает одиночный LF концом строки. Прочитайте файл целиком, нормализуйте переводы строк и разбейте текст через Split по vbCrLf.
Чем RegExp.Test лучше Execute для фильтра?
Test только проверяет наличие совпадения и не создаёт объект MatchCollection. Для отбора строк этого достаточно, и работает он быстрее.
Как искать без учёта регистра?
Передайте vbTextCompare в параметр Compare. В строковом режиме он уйдёт в InStr, в режиме RegExp превратится в IgnoreCase = True.