Cambiar palabras de un archivo TXT

Iniciado por elqueteconte, 23 Octubre 2015, 17:24 PM

0 Miembros y 2 Visitantes están viendo este tema.

elqueteconte

Saludos a todos;

El siguiente script hace un merge de archivos TXT:
Código (vbnet) [Seleccionar]

  Public Sub FindAndMergeFiles(ByVal sourceDir As String)
       Dim fileNames As String() =
       {
           "CM.txt", "GL.txt",
           "IMP6000.txt", "IMP6001.txt", "IMP6002.txt", "IMP6003.txt"
       }
       Dim curFilename As String = String.Empty
       For Each topDir As DirectoryInfo In New DirectoryInfo(sourceDir).GetDirectories("*", SearchOption.TopDirectoryOnly)
           ' Elimino los archivos principales ("...\topDir\CM.txt", "...\topDir\GL.txt", etc...) de sesiones anteriores.
           For Each txtfile As FileInfo In topDir.GetFiles("*.txt", SearchOption.TopDirectoryOnly)
               If fileNames.Contains(txtfile.Name, StringComparer.OrdinalIgnoreCase) Then
                   txtfile.Delete()
               End If
           Next txtfile
           For Each subDir As DirectoryInfo In topDir.GetDirectories("*", SearchOption.AllDirectories)
               For Each txtfile As FileInfo In subDir.GetFiles("*.txt", SearchOption.AllDirectories)
                   If fileNames.Contains(txtfile.Name, StringComparer.OrdinalIgnoreCase) Then
                       curFilename = fileNames.First(Function(filename) filename.Equals(txtfile.Name, StringComparison.OrdinalIgnoreCase))
                       Using sr As StreamReader = txtfile.OpenText
                           Using sw As New StreamWriter(Path.Combine(topDir.FullName, curFilename), append:=True, encoding:=Encoding.Default, bufferSize:=128)
                               sw.WriteLine(sr.ReadToEnd)
                           End Using ' sw
                       End Using ' sr
                   End If
               Next txtfile
           Next subDir
       Next topDir


Ahora bien en el formulario que usa ese script hay un selector que indica dos opciones A y B; donde si la opción B es la que ha sido seleccionada es nesario buscar en dos archivos particulares una palabra y cambiarla por otra, entiendo que la funcion sería mas o menos así:

Código (vbnet) [Seleccionar]

Private Sub ReemplazaTexto(ByVal Fichero As String, ByVal Texto_Busca As String, ByVal Texto_Reemplaza As String)
   Dim Reader As New StreamReader(Fichero)
   Dim Content As String = Reader.ReadToEnd()
   Reader.Close()
   Content = Regex.Replace(Content, Texto_Busca, Texto_Reemplaza)
   Dim Writer As New StreamWriter(FicheroNuevo)
   Writer.Write(Content)
   writer.Close()
End Sub


Ahora lo que quedaría es que despues que ejecuto el merge es preguntar que opción del combobox está seleccionada, que sería así:
Código (vbnet) [Seleccionar]

'El usuario selecciono la opcion para cambiar texto
If ComboBox1.SelectedIndex = 0 Then
         reemplazatexto("AR.txt","TXTB1","TXTN")
         reemplazatexto("AR.txt","TXTB2","TXTN")
         reemplazatexto("BR.txt","TXTB1","TXTN")
         reemplazatexto("BR.txt","TXTB2","TXTN")
Else          
        return
End If


Creo que así sería; pero si alguien tiene uuna mejor idea será bien recibida.

Desde ya mil gracias.

Eleкtro

#1
El motor RegEx es lento. Si realmente estás usando patrones de expresiones regulares para buscar texto, o si quieres reemplazar texto sin diferenciar mayus/minus entonces te sugiero usar estas extensiones:

( La función StringExtensions.Replace es mucho más rápida que una expresión regular en modo IgnoreCase )

Código (vbnet) [Seleccionar]
Imports System.Runtime.CompilerServices

Public Module StringExtensions

   <Extension>
   Public Function ReplaceRegEx(ByVal sender As String,
                                ByVal findWhat As String,
                                ByVal replaceWith As String,
                                Optional ByVal regexOptions As RegexOptions =
                                               RegularExpressions.RegexOptions.None) As String

       If String.IsNullOrEmpty(sender) Then
           Return sender

       ElseIf String.IsNullOrEmpty(findWhat) Then
           Throw New ArgumentNullException(paramName:="findWhat")

       Else
           Return Regex.Replace(sender, findWhat, replaceWith, regexOptions)

       End If

   End Function

   <Extension>
   Public Function Replace(ByVal sender As String,
                           ByVal findWhat As String,
                           ByVal replaceWith As String,
                           ByVal comparisonType As StringComparison) As String

       If String.IsNullOrEmpty(sender) Then
           Return sender

       ElseIf String.IsNullOrEmpty(findWhat) Then
           Throw New ArgumentNullException(paramName:="findWhat")

       Else
           Return StringExtensions.Replace(sender, findWhat, replaceWith, comparisonType, stringBuilderCapacity:=0)

       End If

   End Function

   ''' ----------------------------------------------------------------------------------------------------
   ''' <remarks>
   ''' Author: Elektro
   ''' Orig. : http://www.codeproject.com/Articles/10890/Fastest-C-Case-Insenstive-String-Replace?msg=1835929#xx1835929xx
   ''' </remarks>
   ''' ----------------------------------------------------------------------------------------------------
   ''' <param name="stringBuilderCapacity">
   ''' The initial buffer size of the <see cref="Stringbuilder"/>.
   ''' This parameter is reserved for testing purposes.
   ''' </param>
   ''' ----------------------------------------------------------------------------------------------------
   <DebuggerHidden>
   <DebuggerStepThrough>
   <Extension>
   Private Function Replace(ByVal sender As String,
                            ByVal findWhat As String,
                            ByVal replaceWith As String,
                            ByVal comparisonType As StringComparison,
                            ByVal stringBuilderCapacity As Integer) As String

       If String.IsNullOrEmpty(sender) Then
           Return sender

       ElseIf String.IsNullOrEmpty(findWhat) Then
           Throw New ArgumentNullException(paramName:="findWhat")

       Else
           Dim posCurrent As Integer = 0
           Dim lenPattern As Integer = findWhat.Length
           Dim idxNext As Integer = sender.IndexOf(findWhat, comparisonType)
           Dim result As New StringBuilder(capacity:=If(stringBuilderCapacity <= 0, Math.Min(4096, sender.Length), stringBuilderCapacity))

           While (idxNext >= 0)
               result.Append(sender, posCurrent, (idxNext - posCurrent))
               result.Append(replaceWith)

               posCurrent = (idxNext + lenPattern)
               idxNext = sender.IndexOf(findWhat, posCurrent, comparisonType)
           End While

           result.Append(sender, posCurrent, (sender.Length - posCurrent))

           Return result.ToString

       End If

   End Function

End Module





Citar
Código (vbnet) [Seleccionar]
Private Sub ReemplazaTexto(ByVal Fichero As String, ByVal Texto_Busca As String, ByVal Texto_Reemplaza As String)
   Dim Reader As New StreamReader(Fichero)
   Dim Content As String = Reader.ReadToEnd()
   Reader.Close()
   Content = Regex.Replace(Content, Texto_Busca, Texto_Reemplaza)
   Dim Writer As New StreamWriter(FicheroNuevo)
   Writer.Write(Content)
   writer.Close()
End Sub

Usando el módulo de arriba, yo lo dejaría tal que así (pero sin los nombres en Español xD):
Código (vbnet) [Seleccionar]
Private Sub ReemplazaTexto(ByVal fichero As String,
                          ByVal texto_Busca As String, ByVal texto_Reemplaza As String,
                          ByVal comparisonType As StringComparison)

   If Not File.Exists(fichero) Then
       Throw New FileNotFoundException(message:="File not found", fileName:=fichero)

   Else
       Dim txt As String = File.ReadAllText(fichero, Encoding.Default).
                                Replace(texto_Busca, texto_Reemplaza, comparisonType)

       File.WriteAllText(fichero, txt, Encoding.Default)

   End If

End Sub


EDITO: Pero vamos, si sabes que la palabra va a ser toda en mayúscula o minúscula o como sea entonces ignora el módulo de arriba y usa un String.Replace normal claro está.

Saludos!








elqueteconte

Chamo de pana que tu comes .net....

Q arrech.... Tu codigo....

Tengo varias preguntas:
1.- Al momento de crear el módulo me da error con las variables RegexOptions y Regex; el compilador me indica que no están definidas.

2.- Como lo acoplo esa función en el script que hemos venido trabajando?
Aqui hago el llamado al merge de los archivos:
Código (vbnet) [Seleccionar]

' Merge text files.
        DateUtil.FindAndMergeFiles(Me.fdg.SelectedPath)


Luego de hacer l merge pregunto por el valor del combobox
Código (vbnet) [Seleccionar]

'El usuario selecciono la opcion para cambiar texto
If ComboBox1.SelectedIndex = 0 Then
          ReplaceText("GL.txt","TXT01","TXT1")
          ReplaceText("GL.txt","TXT02","TXT1")
          ReplaceText("IMP6000.txt","TXT01","TXT1")
          ReplaceText("IMP6000.txt","TXT02","TXT1")
Else         
         return
End If


3.- La función ReplaceText (ya la puse en ingles jejejejejeje)
Código (vbnet) [Seleccionar]

    Private Sub ReplaceText(ByVal txtfile As String,
                               ByVal txtfind As String, ByVal txtreplace As String,
                               ByVal comparisonType As StringComparison)
         If Not File.Exists(txtfile) Then
            Throw New FileNotFoundException(message:="File not found", fileName:=txtfile)
         Else
            Dim txt As String = File.ReadAllText(txtfile, Encoding.Default).
                                     Replace(txtfind, txtreplace, comparisonType)
             File.WriteAllText(txtfile, txt, Encoding.Default)
         End If
     End Sub

iria dentro del formulario; correcto?

Mil gracias de nuevo pana.

Eleкtro

Cita de: elqueteconte en 23 Octubre 2015, 21:42 PM1.- Al momento de crear el módulo me da error con las variables RegexOptions y Regex; el compilador me indica que no están definidas.

Tienes que importar los espacios de nombres o NameSpaces necesarios.

Código (vbnet) [Seleccionar]
Imports System.Text.RegularExpressions

Sinceramente, si tienes pensado seguir utilizando VB.Net para otro tipo de tareas en el futuro, entonces esto es algo a lo que te deberías acostumbrar a poder resolverlo por ti mismo ya que es una situación de lo más común.

Coloca por 1 o 2 segundos el puntero del mouse sobre la orden o el miembro que se marca en rojo en la IDE (en este caso la palabra "RegEx"), aparecerá un icono al final del nombre con una flechita, si le aprietas a la flecha aparecerá una lista con varias opciones resolutivas entre ellas importar "X" namespace (no siempre aparecerá esa opción, pero en este caso debería salir), si le pinchas automaticamente se añadirá la instrucción Imports que sea necesaria.




Cita de: elqueteconte en 23 Octubre 2015, 21:42 PM2.- Como lo acoplo esa función en el script que hemos venido trabajando?

Creo que te refieres a hacer algo así:

Código (vbnet) [Seleccionar]
Sub...

'El usuario selecciono la opcion para cambiar texto
If (Me.ComboBox1.SelectedIndex = 0) Then
   Dim comparisonType As StringComparison = StringComparison.OrdinalIgnoreCase
   FindFileAndReplaceTextIn(sourceDir, "GL.txt", "TXT01", "TXT1", comparisonType)
   FindFileAndReplaceTextIn(sourceDir, "GL.txt", "TXT02", "TXT1", comparisonType)
   FindFileAndReplaceTextIn(sourceDir, "IMP6000.txt", "TXT01", "TXT1", comparisonType)
   FindFileAndReplaceTextIn(sourceDir, "IMP6000.txt", "TXT02", "TXT1", comparisonType)

Else
   Exit Sub

End If

End Sub

+
Código (vbnet) [Seleccionar]
Public Sub FindFileAndReplaceTextIn(ByVal sourceDir As String, ByVal filenameFind As String,
                                   ByVal txtfind As String, ByVal txtreplace As String,
                                   ByVal comparisonType As StringComparison)

   For Each topDir As DirectoryInfo In New DirectoryInfo(sourceDir).GetDirectories("*", SearchOption.TopDirectoryOnly)

       For Each txtfile As FileInfo In topDir.GetFiles("*.txt", SearchOption.TopDirectoryOnly)

           If txtfile.Name.Equals(filenameFind, StringComparison.OrdinalIgnoreCase) Then
               Dim txt As String = File.ReadAllText(txtfile.FullName, Encoding.Default).
                                        Replace(txtfile.FullName, txtfind, txtreplace, comparisonType)

               File.WriteAllText(txtfile.FullName, txt, Encoding.Default)
           End If

       Next txtfile

   Next topDir

End Sub


El método ReplaceText ya no sería necesario.




Citar3.- La función ReplaceText (ya la puse en ingles jejejejejeje)

Genial, aunque no hacia falta, si te entiendes mejor con la programación en Español entonces no cambies tus hábitos por un comentario que hice, de todas formas así en Inglés está todo mucho mejor ...es algo positivo.




Citariria dentro del formulario; correcto?

Todo código que no forme parte del Form (como el módulo que compartí, o tus funciones de reempalzar texto y tal) deberías aislarlo de la class del form, en la medida de lo posible, es decir, las llamadas a esos métodos las realizas desde la Clas del Form claro está (Form1.vb o lo que sea), pero los bloques de código de esos métodos los declaras en módulos/classes distintas.

Saludos








elqueteconte

#4
Ante todo, gracias de nuevo Elektro;

Capté perfectamente el mensaje, de hecho te comparto lo que hice:
1.- En el módulo StringExtensions colgué la función FindFileAndReplaceTextIn
2.- En el módulo DateUtil colgué en la función FindAndMergeFiles justo despues de terminar con el último for la consulta al valor del ComboBox y si el valor es el esperado entonces llamo a la función FindFileAndReplaceTextIn que está en el otro módulo.

Pero hay un detalle y trate de solucionarlo con el mismo asistente que me recomendaste pero no funcionó con ello.

El detalle es que en la función FindFileAndReplaceTextIn se manejan 5 parámetros de los cuales hay uno llamado txtreplace que supuestamente es un String pero debería ser (segun el Visual) StringComparison pero cuando hago el cambo sugerido entonces en la sentencia donde hago el llamado:

Código (vbnet) [Seleccionar]

FindFileAndReplaceTextIn(sourceDir, "GL.txt", "textbuscar", "textoreemplaza", comparisonType)


Me da un warning y el Visual me sugiere que debo cambiarlo a:
Código (vbnet) [Seleccionar]

FindFileAndReplaceTextIn(sourceDir, "GL.txt", "textbuscar", CType("textoreemplaza", StringComparison), comparisonType)


Que es lo que realmente hice mal?
Porque me da esos warnings?

Desde ya mil gracias amigo.

Eleкtro

#5
Cita de: elqueteconte en 26 Octubre 2015, 17:29 PMQue es lo que realmente hice mal?
Porque me da esos warnings?

Supuestamente no estás haciendo nada mal y tampoco te debería saltar ningún warning.

Los 4 primeros parámetros del método FindFileAndReplaceTextIn son de tipo String, y el quinto parámetro es del tipo StringComparison, por ende, si tu le estás pasando 4 strings + un StringComparison entonces no entiendo en absoluto por que te salta el warning, ya que no debería.

Solo se me ocurre que hayas modificado la parametización del método FindFileAndReplaceTextIn cambiando el orden del último parámetro ...¿?

De todas formas, tengas el orden de los parámetros como lo tengas, siempre puedes usar parámetros nombrados (named parameters) para especificar estrictamente a que parámetro correspodne cada valor que le pasas al método:
Código (vbnet) [Seleccionar]
FindFileAndReplaceTextIn(sourceDir:=sourceDir, filenameFind:="GL.txt",
                        txtfind:="textbuscar", txtreplace:="textoreemplaza",
                        comparisonType:=comparisonType)


Eso debería solucionar el problema, a menos que hayas modificado algo xD.

Saludos!








elqueteconte

Saludos mi pana,

Cada consulta contigo es una clase de .net jejejejejeje  ;-)

Esta es la función FindFileAndReplaceTextIn que me pasaste:
Código (vbnet) [Seleccionar]

Public Sub FindFileAndReplaceTextIn(ByVal sourceDir As String, ByVal filenameFind As String,
                                    ByVal txtfind As String, ByVal txtreplace As String,
                                    ByVal comparisonType As StringComparison)
        For Each topDir As DirectoryInfo In New DirectoryInfo(sourceDir).GetDirectories("*", SearchOption.TopDirectoryOnly)
            For Each txtfile As FileInfo In topDir.GetFiles("*.txt", SearchOption.TopDirectoryOnly)
                If txtfile.Name.Equals(filenameFind, StringComparison.OrdinalIgnoreCase) Then
                    Dim txt As String = File.ReadAllText(txtfile.FullName, Encoding.Default).
                                             Replace(txtfile.FullName, txtfind, txtreplace, comparisonType)
                    File.WriteAllText(txtfile.FullName, txt, Encoding.Default)
                End If
            Next txtfile
        Next topDir
    End Sub


En la definición de los parámetros está txtreplace como String donde según entiendo debería ser StringComparison; ahora bien si lo cambio entonces en el llamado a la función me da otro warning. Precisamente con ese parámetro.

Entonces hago el llamado a la misma de la siguiente forma:
Código (vbnet) [Seleccionar]
FindFileAndReplaceTextIn(sourceDir, "GL.txt", "AGENO-02", "AGEN-03", comparisonType)

Gracias nuevamente.




elqueteconte

Cita de: Eleкtro en 26 Octubre 2015, 17:55 PM
Supuestamente no estás haciendo nada mal y tampoco te debería saltar ningún warning.

Los 4 primeros parámetros del método FindFileAndReplaceTextIn son de tipo String, y el quinto parámetro es del tipo StringComparison, por ende, si tu le estás pasando 4 strings + un StringComparison entonces no entiendo en absoluto por que te salta el warning, ya que no debería.

Solo se me ocurre que hayas modificado la parametización del método FindFileAndReplaceTextIn cambiando el orden del último parámetro ...¿?

De todas formas, tengas el orden de los parámetros como lo tengas, siempre puedes usar parámetros nombrados (named parameters) para especificar estrictamente a que parámetro correspodne cada valor que le pasas al método:
Código (vbnet) [Seleccionar]
FindFileAndReplaceTextIn(sourceDir:=sourceDir, filenameFind:="GL.txt",
                        txtfind:="textbuscar", txtreplace:="textoreemplaza",
                        comparisonType:=comparisonType)


Eso debería solucionar el problema, a menos que hayas modificado algo xD.

Saludos!

Saludos mi pana,

Probé exactamente lo que indicas y me dió error.
Este es el error:


BC30512 Option Strict On disallows implicit conversions from 'String' to 'StringComparison'. WindowsApplication6 C:\Users\liderapp\Documents\Visual Studio 2015\Projects\WindowsApplication6\WindowsApplication6\StringExtensions.vb 91


Al irme a la linea 91 el error lo da aqui:
Replace(txtfile.FullName, txtfind, [b]txtreplace[/b], comparisonType)

Que sugieres hermano...?


Eleкtro

#8
Imagino que estas usando esta extensión de método?:

Cita de: http://foro.elhacker.net/net/cambiar_palabras_de_un_archivo_txt-t443323.0.html;msg2044424#msg2044424
Código (vbnet) [Seleccionar]
   Private Function Replace(ByVal sender As String,
                            ByVal findWhat As String,
                            ByVal replaceWith As String,
                            ByVal comparisonType As StringComparison,
                            ByVal stringBuilderCapacity As Integer) As String

...

   End Function

La cual por cierto no se por que la declaré von visibilidad Private, modifica el keyword Private por Public para poder utilizarla.

Aparte, el parámetro sender se asigna automaticamente, debes ignorarlo y dejarlo así:
Código (vbnet) [Seleccionar]
...Replace(txtfind, txtreplace, comparisonType, 128)

saludos








elqueteconte

Cita de: Eleкtro en 27 Octubre 2015, 02:16 AM
Imagino que estas usando esta extensión de método?:

La cual por cierto no se por que la declaré von visibilidad Private, modifica el keyword Private por Public para poder utilizarla.

Aparte, el parámetro sender se asigna automaticamente, debes ignorarlo y dejarlo así:
Código (vbnet) [Seleccionar]
...Replace(txtfind, txtreplace, comparisonType)

saludos

Buenos días bro;

Si estoy usando tal cual lo que me diste.

La función queda así:

Código (vbnet) [Seleccionar]

    Private Function Replace(ByVal sender As String,
                             ByVal findWhat As String,
                             ByVal replaceWith As String,
                             ByVal comparisonType As StringComparison,
                             ByVal stringBuilderCapacity As Integer) As String

        If String.IsNullOrEmpty(sender) Then
            Return sender

        ElseIf String.IsNullOrEmpty(findWhat) Then
            Throw New ArgumentNullException(paramName:="findWhat")

        Else
            Return StringExtensions.Replace(findWhat, replaceWith, comparisonType)

        End If


Ahora hay otro detalle del que me estoy dando cuenta; tienes dos funciones llamas igual, una privada y otra publica; pero al hacerlas ambas Publicas el VS da error.

Estas son las funciones:
Código (vbnet) [Seleccionar]

    Public Function Replace(ByVal sender As String,
                            ByVal findWhat As String,
                            ByVal replaceWith As String,
                            ByVal comparisonType As StringComparison) As String
        If String.IsNullOrEmpty(sender) Then
            Return sender
        ElseIf String.IsNullOrEmpty(findWhat) Then
            Throw New ArgumentNullException(paramName:="findWhat")
        Else
            Return StringExtensions.Replace(sender, findWhat, replaceWith, comparisonType, stringBuilderCapacity:=0)
        End If
    End Function


Esta es la otra:

Código (vbnet) [Seleccionar]

Private Function Replace(ByVal sender As String,
                             ByVal findWhat As String,
                             ByVal replaceWith As String,
                             ByVal comparisonType As StringComparison,
                             ByVal stringBuilderCapacity As Integer) As String
        If String.IsNullOrEmpty(sender) Then
            Return sender
        ElseIf String.IsNullOrEmpty(findWhat) Then
            Throw New ArgumentNullException(paramName:="findWhat")
        Else
            Dim posCurrent As Integer = 0
            Dim lenPattern As Integer = findWhat.Length
            Dim idxNext As Integer = sender.IndexOf(findWhat, comparisonType)
            Dim result As New StringBuilder(capacity:=If(stringBuilderCapacity <= 0, Math.Min(4096, sender.Length), stringBuilderCapacity))
            While (idxNext >= 0)
                result.Append(sender, posCurrent, (idxNext - posCurrent))
                result.Append(replaceWith)
                posCurrent = (idxNext + lenPattern)
                idxNext = sender.IndexOf(findWhat, posCurrent, comparisonType)
            End While
            result.Append(sender, posCurrent, (sender.Length - posCurrent))
            Return result.ToString
        End If
    End Function


Gracias mil de nuevo amigo.