A continuación encontrarás traducido al castellano el artículo escrito por Gabriel Ludosanu y publicado originalmente en el Blog oficial de Xojo.
En el anterior artículo creamos una utilidad para buscar texto en el contenido de los archivos de una carpeta. En este artículo actualizaremos dicha utilidad para que soporte la búsqueda recursiva con la capacidad de controlar su profundidad (niveles de recursión).
También refactorizaremos el código para que resulte sencillo su mantenimiento a medida que se torne más complejo.
El objetivo
Esto es lo que queremos obtener:
- Buscar automáticamente en las sub-carpetas.
- Añadir el parámetro maxDepth de modo que no busquemos por accidente en todo el disco duro.
- Mantener la versión sencilla utilizando la sobrecarga de métodos.
Paso 1: Mover la lectura de archivos a su propio método
La función FindInFiles original se encargaba de todo: validar la carpeta, recorrer su contenido, leer el contenido del archivo y encontrar el texto coincidente. Al añadir la recursión resultaría en un código difícil de leer y mantener; de modo que refactorizaremos dicho código.
Comenzaremos moviendo la lógica de lectura de archivos a su propio método privado: SearchFile. Esto mantendrá la lógica de “búsqueda” separada de la lógica encargada de recorrer el contenido de la carpeta.
Private Sub SearchFile(file As FolderItem, searchTerm As String, hits() As SearchHit)
Try
Var input As TextInputStream = TextInputStream.Open(file)
input.Encoding = Encodings.UTF8
Var lineNumber As Integer = 0
While Not input.EndOfFile
Var line As String = input.ReadLine
lineNumber = lineNumber + 1
If line.Contains(searchTerm, ComparisonOptions.CaseInsensitive) Then
hits.Add(New SearchHit(file.NativePath, lineNumber, line))
End If
Wend
input.Close
Catch error As IOException
// Skip unreadable files
End Try
End Sub
Paso 2: Recorrido trasversal de Carpetas
Ahora crearemos SearchFolder. Este método recorre los contenidos de una carpeta. Si encuentra un archivo, entonces llamará al método SearchFile. Si encuentra una carpeta, se llamará a sí mismo.
Para mantener la seguridad en cuanto a la profundidad de recursión utilizaremos maxDepth:
- maxDepth = 0: Buscar sólo en la carpeta actual.
- maxDepth > 0: Buscar esta carpeta y N niveles de sub-carpetas.
- maxDepth = -1: Buscar todo (sin límites).
Private Sub SearchFolder(folder As FolderItem, searchTerm As String, hits() As SearchHit, maxDepth As Integer)
Try
For Each item As FolderItem In folder.Children
If item Is Nil Or Not item.Exists Or Not item.IsReadable Then Continue
If item.IsFolder Then
If maxDepth <> 0 Then
Var nextDepth As Integer = If(maxDepth > 0, maxDepth - 1, maxDepth)
SearchFolder(item, searchTerm, hits, nextDepth)
End If
Else
SearchFile(item, searchTerm, hits)
End If
Next
Catch error As IOException
// Skip inaccessible folders
End Try
End Sub
La lógica en maxDepth nos permite finalizar la recursión en el caso de que exista un límite, o bien realizar una búsqueda completa en caso de que su valor sea -1.
Paso 3: Soportar ambas versiones con la sobrecarga de métodos
La sobrecarga es una característica que nos permite contar con múltiples métodos con el mismo nombre, siempre y cuando cuenten con parámetros diferentes. En nuestro caso nos permite proporcionar una versión simple para una carpeta, y una versión recursiva con un límite de niveles de profundidad. Xojo seleccionará automáticamente el método correcto basándose en los argumentos proporcionados. Puedes leer más sobre este aspecto en la documentación de Xojo.
La versión por omisión (depth 0):
Public Function FindInFiles(targetFolder As FolderItem, searchTerm As String) As SearchHit() Return FindInFiles(targetFolder, searchTerm, 0) End Function
La versión recursiva:
Public Function FindInFiles(targetFolder As FolderItem, searchTerm As String, maxDepth As Integer) As SearchHit()
Var hits() As SearchHit
If targetFolder Is Nil Or Not targetFolder.Exists Or Not targetFolder.IsFolder Or searchTerm.IsEmpty Then
Return hits
End If
Try
SearchFolder(targetFolder, searchTerm, hits, maxDepth)
Catch error As IOException
// Skip inaccessible folders
End Try
Return hits
End Function
Al separar la validación, el recorrido trasversal y el procesado, el código resulta más sencillo de modificar. Si quieres añadir soporte RegExt, sólo has de cambiar SearchFile. Si quieres filtrar los contenidos en base a la extensión de archivo, sólo has de cambiar SearchFolder.
En resumen
Ahora puedes buscar en una sola carpeta tal y como hacías con anterioridad, o bien recorrer un directorio utilizando un parámetro adicional:
// Search up to 3 levels deep
Var results() As SearchUtils.SearchHit = SearchUtils.FindInFiles(myFolder, "TODO", 3)
The Complete Recursive Code
The SearchUtils Module
Module SearchUtils
Class SearchHit
Public Property FilePath As String
Public Property LineNumber As Integer
Public Property LineText As String
Public Sub Constructor(filePath As String, lineNumber As Integer, lineText As String)
Self.FilePath = filePath
Self.LineNumber = lineNumber
Self.LineText = lineText
End Sub
End Class
Public Function FindInFiles(targetFolder As FolderItem, searchTerm As String) As SearchHit()
// Simple usage: depth = 0
Return FindInFiles(targetFolder, searchTerm, 0)
End Function
Public Function FindInFiles(targetFolder As FolderItem, searchTerm As String, maxDepth As Integer) As SearchHit()
// Recursive usage: maxDepth ( -1 is unlimited )
Var hits() As SearchHit
If targetFolder Is Nil Or Not targetFolder.Exists Or Not targetFolder.IsFolder Or searchTerm.IsEmpty Then
Return hits
End If
Try
SearchFolder(targetFolder, searchTerm, hits, maxDepth)
Catch error As IOException
// Skip inaccessible folders
End Try
Return hits
End Function
Private Sub SearchFolder(folder As FolderItem, searchTerm As String, hits() As SearchHit, maxDepth As Integer)
Try
For Each item As FolderItem In folder.Children
If item Is Nil Or Not item.Exists Or Not item.IsReadable Then Continue
If item.IsFolder Then
If maxDepth <> 0 Then
Var nextDepth As Integer = If(maxDepth > 0, maxDepth - 1, maxDepth)
SearchFolder(item, searchTerm, hits, nextDepth)
End If
Else
SearchFile(item, searchTerm, hits)
End If
Next
Catch error As IOException
// Skip inaccessible folders
End Try
End Sub
Private Sub SearchFile(file As FolderItem, searchTerm As String, hits() As SearchHit)
Try
Var input As TextInputStream = TextInputStream.Open(file)
input.Encoding = Encodings.UTF8
Var lineNumber As Integer = 0
While Not input.EndOfFile
Var line As String = input.ReadLine
lineNumber = lineNumber + 1
If line.Contains(searchTerm, ComparisonOptions.CaseInsensitive) Then
hits.Add(New SearchHit(file.NativePath, lineNumber, line))
End If
Wend
input.Close
Catch error As IOException
// Skip unreadable files
End Try
End Sub
End Module
¡Feliz programación con Xojo!