Reading International/Special Characters in VBA - excel

I read an Excel spreadsheet row by row and for each row create a textfile including information from the columns.
From time to time there is foreign text in some of the spreadsheet cells. In the debugger the foreign text appears as '?' question marks. It fails when trying to write these question marks to the text file.
This is a snippet of the code that reads the values from a row to a string array
Set oFS = CreateObject("Scripting.Filesystemobject")
For Each rID In oSh.UsedRange.Columns("A").Cells
For Each rValue In oSh.UsedRange.Rows(rowCount).Cells
ReDim Preserve columnValues(columnCount)
columnValues(columnCount) = rValue
columnCount = columnCount + 1
Next
Next
This is the code which writes to a text file
sFNText = sMakeFolder & "\" & rID.Value & ".txt"
Set oTxt = oFS.OpenTextFile(sFNText, 2, True)
For i = 0 To UBound(columnTitles)
oTxt.Write columnTitles(i) & ": " & columnValues(i) & vbNewLine
Next i
oTxt.Close
I have experimented with changing the format of opentextfile and also using AscW and ChrW to convert to and from ansi.
EDIT: In particular I am trying to read in Greek symbols (pi, omega etc.) and write them back out to a textfile. I have used the
StrConv(Cells(1, 1), vbUnicode)
method that was detailed in How can I create text files with special characters in their filenames and have got that example working. It seems now a problem with writing this to a textfile. nixda's example seems to work in isolation when using his Print command, however when I try
otxt.Write
to write my stored variable to a textfile it writes out garbage, as opposed to the print method which produces the correct result. Looking at the debugger both variables are stored identically (print method + write), so I believe it is now down to the output method (otxt.Write) which is converting the stored variable into garbage. I have tried using the -1 & -2 options for OpenTextFile - both producing garbage results.

I have the following sheet:
and the following code:
Sub writeUnicodeText()
Dim arr_Strings() As String
i = 0
For Each oCell In ActiveSheet.Range("A1:A4")
ReDim Preserve arr_Strings(i)
arr_Strings(i) = oCell.Value
i = i + 1
Next
Set oFS = CreateObject("Scripting.Filesystemobject")
Set oTxt = oFS.OpenTextFile("C:\users\axel\documents\test.txt", 2, True, -1)
For i = 0 To UBound(arr_Strings)
oTxt.Write arr_Strings(i) & vbNewLine
Next i
oTxt.Close
End Sub
This produces the following file:

This is the code I use to write to a text. I've tried many methods and this has worked the best.
Sub ProcessX()
FName1 = "Location of File"
txtStrngX = OpenTextFileToString2(FName1)
end sub
Public Function OpenTextFileToString2(ByVal strFile As String) As String
Dim hFile As Long
hFile = FreeFile
Open strFile For Input As #hFile
OpenTextFileToString2 = Input$(LOF(hFile), hFile)
Close #hFile
End Function
As for reading in from rows just be sure to set your variable to a string when compiling and any method should work fine.

sorry. That's reading from a text. Here is writing.
Public Function RecordsetToText(rs As Object, Optional FullPath _
As String, Optional ValueDelimiter As String = " ") As Boolean
'PURPOSE: EXPORTS DATA FROM AN ADO RECORDSET TO A TEXT FILE
'PARAMETERS:
'RS: Recordset to Export. Open the recordset before
'passing it to this function
'FullPath (Optional): FullPath of text file.
'if not specified, the function uses app.path +
'rs.txt
'ValueDelmiter (Optional): String to delimiter
'values within a row. If not specified, an tab
'is used
'RETURNS: True if successful, false if an error occurs
'COMMENTS: Rows are delimited by a carriage return
Dim sFullPath As String
Dim sDelimiter As String
Dim iFileNum As Integer
Dim lFieldCount As Long
Dim lCtr As Long
Dim oField As ADODB.Field
On Error GoTo ErrorHandler:
If RecordSetReady(rs) = False Then Exit Function
sDelimiter = ValueDelimiter
If FullPath = "" Then
sFullPath = App.Path
If Right(sFullPath, 1) <> "\" Then sFullPath = _
sFullPath & "\"
sFullPath = sFullPath & "rs.txt"
Else
sFullPath = FullPath
End If
iFileNum = FreeFile
Open sFullPath For Output As #iFileNum
With rs
lFieldCount = .Fields.Count - 1
On Error Resume Next
.MoveFirst
On Error GoTo ErrorHandler
For lCtr = 0 To lFieldCount
Set oField = .Fields(lCtr)
If lCtr < lFieldCount Then
Print #iFileNum, oField.Name & sDelimiter;
Else
Print #iFileNum, oField.Name
End If
Next
Do While Not .EOF
For lCtr = 0 To lFieldCount
Set oField = .Fields(lCtr)
If lCtr < lFieldCount Then
Print #iFileNum, oField.Value & sDelimiter;
Else
Print #iFileNum, oField.Value
End If
Next
.MoveNext
Loop
End With
RecordsetToText = True
ErrorHandler:
On Error Resume Next
Close #iFileNum
End Function

Related

Input Txt file lines to array using vba

I have a txt file and I need to input it into a string array, where each line is one item in the array.
I've done a good deal with vba before but never editing files other than Word and Excel, so this is new to me.
The below is part of my sub (copied from somewhere online so I don't really understand it)
Sub TxtFileToArray(FilePath As String, LineArray As Variant, Optional Delimiter As String = vbCrLf)
'adapted from https://www.thespreadsheetguru.com/blog/vba-guide-text-files
Dim TextFile As Integer
Dim FileContent As String
'Open the text file in a Read State
TextFile = FreeFile
Open FilePath For Input As TextFile
'Store file content inside a variable
FileContent = Input(LOF(TextFile), TextFile)
'Close Text File
Close TextFile
It fails on the line FileContent = Input(LOF(TextFile), TextFile). Error message is:
Run-time error '62':
Input past end of file
The Variable Textfile = 1, and LOF(Textfile) = 4480
What should I do?
EDIT:
The File is full of xml data (it's actually an .odc file that's been converted to .txt). Is there something I should be doing to convert it all that to a string? Perhaps I could import it as a huge string somehow and then split it into the array?
Text File to Array
This is just an addition to a possibly upcoming answer, to show how you can use a function for your task (I don't know exactly what binary or a binary file is).
In my short investigation, it was tested with a json file. Interesting to me is that it works with Input and Binary, and that it needs vbLf instead of vbCrLf as the Delimiter.
Note that you might get one value in the array if you choose the wrong delimiter, like it happened in this case.
The test procedure will write the lines (the values in the array) to the cells in column A of the ActiveSheet.
The Code
Option Explicit
Sub TESTtextFileToArray()
Const FilePath As String = "F:\Test\2020\TXT\test.json"
Dim TextLines As Variant
' Note the 'vbLf' instead of 'vbCrLf'.
TextLines = TextFileToArray(FilePath, vbLf)
If Not IsEmpty(TextLines) Then
' Note that 'Transpose' has a 65536 limit per dimension.
Range("A1").Resize(UBound(TextLines) + 1).Value _
= Application.Transpose(TextLines)
'Debug.Print Join(TextLines, vbLf)
MsgBox "Found " & UBound(TextLines) + 1 & " lines."
Else
MsgBox "No lines found."
End If
End Sub
' The result is a 0-based 1D array.
Function TextFileToArray( _
ByVal FilePath As String, _
Optional Delimiter As String = vbCrLf) _
As Variant
Const ProcName As String = "TextFileToArray"
On Error GoTo clearError
Dim TextFile As Long
TextFile = FreeFile
Open FilePath For Input Access Read As TextFile
On Error Resume Next
TextFileToArray = Split(Input(LOF(TextFile), TextFile), Delimiter)
On Error GoTo clearError
Close TextFile
ProcExit:
Exit Function
clearError:
Debug.Print "'" & ProcName & "': Unexpected Error!" & vbLf _
& " " & "Run-time error '" & Err.Number & "':" & vbLf _
& " " & Err.Description
Resume ProcExit
End Function
The easiest way is to use a Scripting.Dictionary and the FileSystemObject.
Public Function GetAsStrings(ByVal ipPath As String) As Variant
Dim myFso As Scripting.FileSystemObject
Set myFso = New Scripting.FileSystemObject
Dim myfile As TextStream
Set myfile = myFso.OpenTextFile(ipPath, Scripting.IOMode.ForReading)
Dim myStrings As Scripting.Dictionary
Set myStrings = New Scripting.DIctionary
Do Until myfile.AtEndOfStream
myStrings.Add mystrings.count, myfile.ReadLine
Loop
myfile.Close
Set GetAsStrings = myStrings.Items
End Function

Problems with Worksheetfunction.Match in a closed workbook. Cannot work out why no match is found

I'm writing a code to delete a log entry in a .csv file. The code starts with opening the .csv file, using Application.Match to return the row number, and then deleting that and closing the file again. The problems I'm experiencing are I get a type mismatch (my error handling is activated) OR (and here it gets weird) it works (a match is found, the row is deleted) but then the logfile is messed up - all data is one string in column a with either ";" or "," delimiters (this varies somehow, relevant note: I use Dutch language excel). Of course, this makes it impossible for the macro to find a match in any case.
I found that the type mismatch problems I'm experiencing will most likely be caused by the code not finding a match, and this is what I don't understand since I checked and doublechecked the input and the data in the logfile - by all means it simply should find a match. And sometimes it does find a match, deletes the row and messes up formatting. (NOTE: Mostly it does NOT find a match.)
I check data in the .csv file before running the macro. I have tried running the macro with the .csv file already opened. I have tried to Set the matchArray from outside the With. I have tried both sweet talking my laptop and a more aggressive approach, to no avail.
Sub DeleteRowFromFile(ByVal matchValue As String)
Dim filePath As String
Dim fileName As String
Dim fileType As String
Dim matchArray As Range
Dim rowToDelete As Variant
'Naming variables for flexibility
filePath = "C:\Users\Maxim\Documents\Log\"
fileName = "TestRegister"
fileType = ".csv"
Application.ScreenUpdating = False
Workbooks.Open (filePath & fileName & fileType)
With Workbooks(fileName).Worksheets(1)
Set matchArray = .Range("A:A") 'set range in the logfile
'Type mismatch here:
rowToDelete = Application.Match(matchValue, matchArray, 0)
If Not IsError(rowToDelete) Then
Rows(rowToDelete).Delete
Else:
MsgBox "Orderno. " & matchValue & " not found.", vbOKOnly + vbExclamation, "Error"
End If
End With
'Closing the log file
Workbooks(fileName).Close SaveChanges:=True
Application.ScreenUpdating = True
End Sub
Sub MatchAndDelete()
Dim matchValueRange As String
matchValueRange = ActiveWorkbook.Worksheets(1).Range("A1").Value
DeleteRowFromFile (matchValueRange)
End Sub
Footnote:
I'm a struggling enthusiast, I have a lot to learn. Sorry in advance if I have left out any crucial information for you to be of help, and thanks a lot for any and all help.
When you open or save a csv file using a VBA macro Excel will always use the standard (US English delimiters) while if you do the same via the user interface it will use the separators as defined in the Windows regional settings, which probably is ";" in your case.
You can check with .?application.International(xlListSeparator) in the immediate window of your VBEditor.
You can tell Excel to use a different separator, by e.g. adding sep=; as line 1 of your file. Hoever this entry is gone after opening the file. The following code - added before you open the csv file will add this:
Dim oFSo As Object
Dim oTxtFile As Object
Dim strData As String
Set oFSo = CreateObject("Scripting.FileSystemObject")
strData = oFSo.OpenTextFile(filePath & fileName & fileType, 1).ReadAll
Set oTxtFile = oFSo.OpenTextFile(filePath & fileName & fileType, 2)
oTxtFile.writeline "sep=;"
oTxtFile.writeline strData
oTxtFile.Close
You can save your changed file by using the Excel UserInterface Shortcuts via the Application.SendKeys thus achieving what you want:
Application.SendKeys ("^s") 'Save
Application.SendKeys ("^{F4}") 'Close
Dont run this code from the VBE Immeditate window as it will probabaly act on the wrong file!
The full code - just with an alternate way to make the requested change:
Sub DeleteRowFromFile(ByVal matchValue As String)
Dim filePath As String
Dim fileName As String
Dim fileType As String
Dim matchArray As Range
Dim rowToDelete As Variant
Dim oFSo As Object
Dim oTxtFile As Object
Dim strData As String
Dim content As Variant
Dim i As Long
'Naming variables for flexibility
filePath = "C:\Users\Maxim\Documents\Log\"
fileName = "TestRegister"
fileType = ".csv"
Application.ScreenUpdating = False
'Adding "sep =" ; as line 1 of the log file
Set oFSo = CreateObject("Scripting.FileSystemObject")
strData = oFSo.OpenTextFile(filePath & fileName & fileType, 1).ReadAll
Set oTxtFile = oFSo.OpenTextFile(filePath & fileName & fileType, 2)
oTxtFile.writeline "sep=;"
oTxtFile.writeline strData
oTxtFile.Close
'Open logfile
Workbooks.Open (filePath & fileName & fileType)
'Make your changes
With Workbooks(fileName).Worksheets(1)
content = .UsedRange.Value
For i = UBound(content, 1) To 1 Step -1
If content(i, 1) = matchValue Then
.Rows(i).Delete
End If
Next i
End With
'Closing the log file via Sendkeys using excel shortcuts
Application.SendKeys ("^s") 'Save
Application.SendKeys ("^{F4}") 'Close
Application.ScreenUpdating = True
I think that Match it is not required. Try this one.
Sub DeleteRowFromFile(ByVal matchValue As String)
Dim filePath As String
Dim fileName As String
Dim fileType As String
Dim matchArray As Range
Dim rowToDelete As Variant
'Naming variables for flexibility
filePath = "C:\Users\Maxim\Documents\Log\"
fileName = "TestRegister"
fileType = ".csv"
Application.ScreenUpdating = False
Workbooks.Open (filePath & fileName & fileType)
With Workbooks(fileName).Worksheets(1)
For i = .UsedRange.SpecialCells(xlCellTypeLastCell).Row To 1 Step -1
If .Cells(i, 1).Value2 = matchValue Then
.Cells(i, 1).EntireRow.Delete
End If
Next
End With
'Closing the log file
Workbooks(fileName & fileType).SaveAs Filename:= _
(filePath & fileName & fileType) _
, FileFormat:=xlCSVMSDOS, CreateBackup:=False 'Saving the file
Workbooks(fileName & fileType).Close 'Closing the file
Application.ScreenUpdating = True
End Sub
Hope it helps

VBA Excel Macro error: 1004 unable to get the substitute property of the WorkbooFunction class

I am using a vba macro to find a big string in a text file.
For that I read the text file , read the text to compare (which is saved in one of the cell). After that i would replace CRLF with CR (since the saved text does not contain CRLF). Then compare. Its working fine if the file size is less. But throwing the error when the file size is high (Around 50 KB is fine).
Any guess about the maximum size of the file?
The below part of the code is throwing error
Open LogFilePath For Input As #iFile
strFileContent = Input(LOF(iFile), iFile)
Close #iFile
strFileContent = Application.WorksheetFunction.Substitute(strFileContent, vbCrLf, "")
strSearch = Application.WorksheetFunction.Substitute(strSearch, vbLf, "")
strFileContent = Application.WorksheetFunction.Substitute(strFileContent, vbTab, " ")
If InStr(1, strFileContent, strSearch, vbBinaryCompare) > 0 Then
SearchTextFile = "success"
Else
SearchTextFile = "failed"
End If
Any guess or suggestion please.
I note the error refers to the Substitute property and that you are using Application.WorksheetFunction.Substitute.
Personally, in VBA I always tend to use the REPLACE function which works in the same way.
I also use this when processing some large .txt files (20,000 rows/30MB) and don't encounter problems with it.
The Substitute is more for use e.g. with a cell formula. You should try following code snippet using Replace for your needs.
Private Sub CommandButton1_Click()
Dim LogFilePath As String
Dim ifile As Integer
ifile = 1
LogFilePath = "D:/_working/application-log-file-small.txt"
strSearch = "Temp Path :"
Open LogFilePath For Input As #ifile
strFileContent = Input(LOF(ifile), ifile)
Close #ifile
'--- Show len of file content string -----
MsgBox (Len(strFileContent))
strFileContent = Replace(strFileContent, vbCrLf, "")
strSearch = Replace(strSearch, vbLf, "")
strFileContent = Replace(strFileContent, vbTab, " ")
If InStr(1, strFileContent, strSearch, vbBinaryCompare) > 0 Then
SearchTextFile = "success"
MsgBox ("success")
Else
SearchTextFile = "failed"
MsgBox ("failed")
End If
End Sub

VBA excel multiline string search

I want to search a multi line string in a text file using VBA excel macro.
I tried using InStr function.But its not working as I expected. My exact aim is to read a multi line string stored in a cell and check whether it is available in a text file. For that what i did is read the text file in to a variable, reading the string saved in the cell to another variable and comparing using Instr using binary comparison. Will InStr work for multi line string? If not any any other way to compare it?
This is my code
Public Function string_compare() As String
Dim strFilename As String
Dim strSearch As String
strFilename = "D:\test.txt"
Dim strFileContent As String
Dim iFile As Integer: iFile = FreeFile
Open strFilename For Input As #iFile
strFileContent = Input(LOF(iFile), iFile)
Close #iFile
strSearch = Sheet1.Cells(9, 1).Value
If InStr(1, strFileContent, strSearch, vbBinaryCompare) > 0 Then
MsgBox "success"
Else
MsgBox "failed"
End If
End Function
When I checked the strings both seems to be identical.Even though the strings are identical, the searching result always failing. Any suggestions will be helpful.
As Tim and Mrig suggested I removed the cr and crlf from the text as follows. Now its working fine.I could use this for comparing multi line strings.I am posting my code segment here.Hope it may help somebody else too.
Public Function stringcompare(sourcefile As String, Workbookname As Worksheet) As String
Dim strSearch As String
Dim strFileContent As String
Dim iFile As Integer: iFile = FreeFile
Open sourcefile For Input As #iFile
strFileContent = Input(LOF(iFile), iFile)
Close #iFile
strSearch = Workbookname.Cells(1, 1).Value
strFileContent = Application.WorksheetFunction.Substitute(strFileContent, vbCrLf, "")
strSearch = Application.WorksheetFunction.Substitute(strSearch, vbLf, "")
If StrComp(strFileContent, strSearch, vbBinaryCompare) = 0 Then
MsgBox "success"
Else
MsgBox "failed"
End If
End Function

Function to direct debug to text file

I would like to write a function to would allow me to use Print #Debug, "text" throughout my future modules to collect debug statements.
Sub output_debug()
Dim WshShell As Object
Dim Desktop As String
Dim Debug As Integer
Debug = FreeFile()
Set WshShell = CreateObject("WScript.shell")
Desktop = WshShell.specialfolders("Desktop")
Open Desktop & "\VBA_output.txt" For Output As #Debug
Print #Debug, "test"
Close #Debug
End Sub
How can I move from the above, to defining a function that would allow me to use call output_debug() in a module so all my Print #Debug, would print to that file ? I would imagine I need to create another function called close_output() that has close #Debug
I did something like this in the past. Here is what I came up with. It relies on having a reference to Microsoft Scripting Runtime in any project that uses it. You can store the following subs in a module e.g. DebugLogger (which is what I use) that can be first exported then imported into any module that you want to have this functionality. It mimics the behavior of Debug.Print but sends the output to a file whose name is a function of the workbook's name. I toyed with the idea of time-stamping individual entries but rejected the idea as being too far from the functionality of Debug.Print (I do, however, time stamp the date of creation). Once you import the module and establish the right reference then you can just use DebugLog anywhere you would have used DebugPrint. As a default it also prints to the debug window. You can drop that part of the code entirely or switch what the default is.
Function GetFullDebugName() As String
'This function returns a string of the form
'*xldebug.txt, where *.* is the full name of the workbook
Dim MyName As String
Dim NameParts As Variant
MyName = ThisWorkbook.FullName
NameParts = Split(MyName, ".")
GetFullDebugName = NameParts(0) & "xldebug.txt"
End Function
Sub CreateDebugFile()
'file created in same directory as
'calling workbook
Dim DebugName As String
Dim fso As FileSystemObject
Dim MyStream As TextStream
Set fso = New FileSystemObject
DebugName = GetFullDebugName
Set MyStream = fso.CreateTextFile(DebugName)
MyStream.WriteLine "This debug file was created " _
& FormatDateTime(Date) _
& " at " & FormatDateTime(Time)
MyStream.Close
End Sub
Sub DebugLog(DebugItem As Variant, Optional ToImmediate As Boolean = True)
Dim DebugName As String
Dim fso As FileSystemObject
Dim MyStream As TextStream
Set fso = New FileSystemObject
DebugName = GetFullDebugName
'check to see if DebugFile exist
'if not, create it:
If Not fso.FileExists(DebugName) Then CreateDebugFile
Set MyStream = fso.OpenTextFile(DebugName, ForAppending)
MyStream.WriteLine DebugItem
MyStream.Close
If ToImmediate Then Debug.Print DebugItem
End Sub
Try a subroutine like this...
It will log text to a text file with a date stamp, so new file new day.
You have an option to pass it the ERR object if you trap the error in your code and it will log the error message with a highlight.
call debuglog("my log entry")
call debuglog("my log entry",err)
Public Sub DebugLog(sLogEntry As String, Optional ByVal oErr As Object)
' write debug information to a log file
Dim iFile As Integer
Dim sDirectory As String
Dim errNumber, errDescription As Variant
Dim l As Integer
If Not oErr Is Nothing Then
errNumber = oErr.Number
errDescription = oErr.Description
l = IIf(Len(errDescription) > Len(sLogEntry), Len(errDescription), Len(sLogEntry))
End If
On Error GoTo bail
sfilename = VBA.Environ("Homedrive") & VBA.Environ("Homepath") & "\My Documents\Debuglog" & "\debuglog" & Format$(Now, "YYMMDD") & ".txt"
iFile = FreeFile
Open sfilename For Append As iFile
If Not oErr Is Nothing Then
sLogEntry = "/" & String(5 + (l - Len(sLogEntry)), "-") & " " & sLogEntry & " " & String(5 + (l - Len(sLogEntry)), "-") & "\"
Print #iFile, Now; " "; sLogEntry
Print #iFile, Now; " "; errNumber
Print #iFile, Now; " "; errDescription
Print #iFile, Now; " "; "\" & String(Len(sLogEntry) - 2, "-") & "/"
Else
Print #iFile, Now; " "; sLogEntry
End If
bail:
Close iFile
End Sub
example logfile output
27/03/2015 10:44:27 -- COMIT Form Initialize - Complete
27/03/2015 10:44:27 - COMIT Active
27/03/2015 10:44:34 /----- -- Error Populating Opportunity Form: frmBluesheet.PopulateForm() -----\
27/03/2015 10:44:34 381
27/03/2015 10:44:34 Could not get the Column property. Invalid property array index.
27/03/2015 10:44:34 \-----------------------------------------------------------------------------/

Resources