Remove extra line break written with TextStream.WriteLine() - vba

I have following vba code which write items in the ArrayList to file with TextStream.
Sub WriteListAsCSV(list As Object, filePath As String)
Dim fso As New FileSystemObject
Dim ts As TextStream
Set ts = fso.CreateTextFile(filePath, True)
Dim line As Variant
For Each line In list
ts.WriteLine (line)
Next line
ts.Close
End Sub
Problem is I'm getting extra linebreak at the end of the file.
I can do like following but I don't want to check at each loop for that single linebreak.
Sub WriteListAsCSV(list As Object, filePath As String)
Dim fso As New FileSystemObject
Dim ts As TextStream
Set ts = fso.CreateTextFile(filePath, True)
Dim line As Variant
Dim i As Integer
For i = 0 To list.Count
line = list(i)
ts.Write (line)
'If not last line
If Not i = list.Count Then
'Write blankline
ts.WriteLine()
End If
Next
ts.Close
End Sub
Is there any way to remove one character back like Backspace button in VBA? Or another neat trick to do this?

Idea
My idea was to remove the last charaters corresponding to the linebreak, which are Chr(10) or Chr(13) and sometimes both, using the file length.
How to do
While I was checking about how to use a function I found this: Remove last carriage return file which covers exactly my idea ... Maybe have a look it's pretty clear.
EDIT (cf # Siddharth Rout comment)
Modified and commented version of the code (In case of the link dies. I take no credit for this code)
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objFile = objFSO.OpenTextFile("path\to\file", 1) ' -- 1:read
strFile = objFile.ReadAll
objFile.Close
' We check if the two last characters correspond to a linebreak:
If Right(strFile, 2) = vbCrLf Then
' If so, we remove those charaters:
strFile = Left(strFile, Len(strFile)- 2)
Set objFile = objFSO.OpenTextFile("path\to\file", 2) ' -- 2:write
objFile.Write strFile
objFile.Close
End If
I'm not sure it's the more elegant way to do but it seems to be a pretty valid one. Hope this works for you.

Related

Documents.Open using FileSystemObject returns "5174 - could not find the file" but not always

I want to convert all docx files in a folder to PDF.
To accomplish my goals I put all the files (only docx) in the same folder than the docm and run the macro. It worked, but now it doesn't, even with the same files doesn't work anymore. Sometimes works for the first file and stop working with the following alert:
"Runtime error '5174':
This file could not be found
(C:\Users...\Archive.docx)"
The problem is always on the Documents.Open
Tried "OpenAndRepair", "ReadOnly", Putting nothing, etc.
Sub Converter()
Dim CurrentFolder As String
Dim FileName As String
Dim myPath As String
'Store Information About Word File
myPath = ActiveDocument.FullName
FileName = Mid(myPath, InStrRev(myPath, "\") + 1)
Dim strCaminho As String
strCaminho = ActiveDocument.Path
Dim fso As Object 'Scripting.FileSystemObject
Dim fld As Object 'Scripting.Folder
Dim fl As Object 'Scripting.File
Dim atual As String
Set fso = CreateObject("Scripting.FileSystemObject")
Set fld = fso.GetFolder(strCaminho)
For Each fl In fld.Files
If fl.Name <> FileName Then 'doesn't try to open the file with macro
Documents.Open FileName:=fl.Name
Word_ExportPDF 'A function that works
ActiveDocument.Close SaveChanges:=wdDoNotSaveChanges
End If
Next fl
End Sub
My code is a Frankenstein from other macros, is there a better way to Automatize this conversion?
Implement what Comintern had proposed:
You don't need to parse out the FileName - Word.Document give you direct access to that with .Name. The first thing I would do is collect the names of the documents first, then export them. You're modifying the directory contents as you iterate over it. - Comintern
Then, the following can be added to the code to check for valid document extensions:
If fl.Name <> FileName Then 'doesn't try to open the file with macro
If LCase(fso.GetExtensionName(fl.Path)) = "docx" Then '<----This Line
Documents.Open FileName:=fl.Path '<--------------------This Line
Word_ExportPDF 'A function that works
ActiveDocument.Close SaveChanges:=wdDoNotSaveChanges
End if
End if

Generate automatic ids

I have to do the following:
Open mail -> Check Subject -> If subject is not like : ..... cID#[4digit] -> Add a cID#[4digit] to it, based on other mail's subjects in your folders and sub-folders -> other operations.
Basically check for the highest value of cID#, increment it by 1, and add it to the new subject. For example if your subject is: H&H 2013 allocation.
-It checks if the subject contains the cID# part.✓
-It can't find it, so it checks the folders, and sub-folders for the highest cID#; increment it by 1. ✗
Getting the ID of a single mail as integer is done, because it is just the Val(Right(subjectstring.4)) (It will always be on the right, which is easier for me, because I couldn't find other methods, but they are more than welcome) From these values, it is easy to build the 4 length long string, and insert it to the subject.
My question is, how to get the highest valued cID#-s.
Following Max's advice, my code is based on this, if anyone else has the same problem.
I use the szamid's to set the numbers.
Sub readtextfile()
Dim oFSO As New FileSystemObject
Dim oFS As TextStream
Dim oFSBU As TextStream
Dim filePath As String
Dim filePathBU As String
Dim szamid As Integer
Dim My_filenumber As Integer
filePath = "C:\Casenumber.txt"
filePathBU = "C:\CasenumberBU.txt"
If Not fileExist(filePath) Then GoTo FileDoesntExist
On Error GoTo Err
Set oFS = oFSO.OpenTextFile(filePath, ForReading)
szamid = oFS.Read(7)
szamid = szamid + 1
szamid = CStr(szamid)
oFS.Close
Set oFS = oFSO.OpenTextFile(filePath, ForWriting)
oFS.WriteLine (szamid)
oFS.Close
Set oFSBU = oFSO.OpenTextFile(filePathBU, ForWriting)
oFSBU.WriteLine (szamid)
oFSBU.Close
MsgBox szamid
Exit Sub
FileDoesntExist:
Set oFSBU = oFSO.OpenTextFile(filePathBU, ForReading)
szamid = oFSBU.Read(7)
szamid = szamid + 1
szamid = CStr(szamid)
oFSBU.Close
Const FILENAME = "C:\Casenumber.txt"
My_filenumber = FreeFile
Open FILENAME For Output As #My_filenumber
Close #My_filenumber
Set oFS = oFSO.OpenTextFile(filePath, ForWriting)
oFS.WriteLine (szamid)
oFS.Close
Set oFSBU = oFSO.OpenTextFile(filePathBU, ForWriting)
oFSBU.WriteLine (szamid)
oFSBU.Close
MsgBox szamid
Exit Sub
Err:
MsgBox "Error while reading the file.", vbCritical, vbNullString
oFS.Close
Exit Sub
End Sub
reading all existing e-mails will use some time and resources.
I would keep a text file on the hard-disk, in which you only store the highest value; when reading it and adding +1 for the next mail, also put the new number into your text file.
How-to: see my answer here: read value from text file, Forward email
Max

How can I edit a line in a file opened with FileSystemObject in VBA?

I'm looping through an input file and using the readline command to read each line, check it for various criteria, then I want to make changes based on the result. Here is a very simple version of what I'm trying to do:
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objFile = objFSO.OpenTextFile(strFileLoc, 1)
Do While Not objFile.AtEndOfStream
strLineRead = objFile.readline
if strLineRead Like "*text to change*" Then
'Some code to change the line
end if
Loop
What I've been doing is saving the entire file to a string called strFileText, and then using a Replace function to replace the strLineRead within that string with the changed version. Something like this:
strFileText = Replace(strFileText, strLineRead, strNewLine)
and then write that entire string to a new text file.
The problem is, sometimes I might have a line where it's entire text is "NC", and then doing a find/replace on the entire file for "NC" changes more than just the one line.
So is there a command in the FileSystemObject, while on a certain line, to be able to alter the file directly? I'm thinking something like a "writeline" command.
Have these private subs somewhere in your file and on an event, call them. First call replace_text and fill it with the requirements. See my sample code.
Private Sub Command3_Click()
Dim sFileName As String
Dim fileSys As Variant
' Edit as needed
sFileName = Me.FileList.Value
Set fileSys = CreateObject("Scripting.FileSystemObject")
Replace_Text sFileName, "bad text", "good text", fileSys
End Sub
Private Sub Replace_Text(targetFile As String, targetText As String, replaceText As String, fileSys As Variant)
If Right(targetFile, 3) = "filepath extension you want (example: xml or doc etc.)" Then
Update_File targetFile, targetText, replaceText, fileSys
Else
MsgBox "You did not select the right file. Please try again."
End If
End Sub
Private Sub Update_File(fileToUpdate As String, targetText As String, replaceText As String, fileSys As Variant)
Dim tempName As String
Dim tempFile As Variant
Dim file As Variant
Dim currentLine As String
Dim newLine As String
'creates a temp file and outputs the original files contents but with the replacements
tempName = fileToUpdate & ".tmp"
Set tempFile = fileSys.CreateTextFile(tempName, True)
'open the original file and for each line replace any matching text
Set file = fileSys.OpenTextFile(fileToUpdate)
Do Until file.AtEndOfStream
currentLine = file.ReadLine
newLine = Replace(currentLine, targetText, replaceText)
'write to the new line containing replacements to the temp file
tempFile.WriteLine newLine
Loop
file.Close
tempFile.Close
'delete the original file and replace with the temporary file
fileSys.DeleteFile fileToUpdate, True
fileSys.MoveFile tempName, fileToUpdate
End Sub

using Application.FileDialog to rename a file in VBA

Using VBA. My script moves a file into a directory. If that filename already exists in the target directory, I want the user to be prompted to rename the source file (the one that's being moved) before the move is executed.
Because I want the user to know what other files are in the directory already (so they don't choose the name of another file that's already there), my idea is to open a FileDialog box listing the contents of the directory, so that the user can use the FileDialog box's native renaming capability. Then I'll loop that FileDialog until the source file and target file names are no longer the same.
Here's some sample code:
Sub testMoveFile()
Dim fso As FileSystemObject
Dim file1 As File
Dim file2 As File
Dim dialog As FileDialog
Set fso = New FileSystemObject
fso.CreateFolder "c:\dir1"
fso.CreateFolder "c:\dir2"
fso.CreateTextFile "c:\dir1\test.txt"
fso.CreateTextFile "c:\dir2\test.txt"
Set file1 = fso.GetFile("c:\dir1\test.txt")
Set file2 = fso.GetFile("c:\dir2\test.txt")
Set dialog = Application.FileDialog(msoFileDialogOpen)
While file1.Name = file2.Name
dialog.InitialFileName = fso.GetParentFolderName(file2.Path)
If dialog.Show = 0 Then
Exit Sub
End If
Wend
file1.Move "c:\dir2\" & file1.Name
End Sub
But when I rename file2 and click 'OK', I get an error:
Run-time error '53': File not found
and then going into the debugger shows that the value of file2.name is <File not found>.
I'm not sure what's happening here--is the object reference being lost once the file's renamed? Is there an easier way to let the user rename from a dialog that shows all files in the target directory? I'd also like to provide a default new name for the file, but I can't see how I'd do that using this method.
edit: at this point I'm looking into making a UserForm with a listbox that gets populated w/ the relevant filenames, and an input box with a default value for entering the new name. Still not sure how to hold onto the object reference once the file gets renamed, though.
Here's a sample of using Application.FileDialog to return a filename that the user selected. Maybe it will help, as it demonstrates getting the value the user provided.
EDIT: Modified to be a "Save As" dialog instead of "File Open" dialog.
Sub TestFileDialog()
Dim Dlg As FileDialog
Set Dlg = Application.FileDialog(msoFileDialogSaveAs)
Dlg.InitialFileName = "D:\Temp\Testing.txt" ' Set suggested name for user
' This could be your "File2"
If Dlg.Show = -1 Then
Dim s As String
s = Dlg.SelectedItems.Item(1) ` Note that this is for single-selections!
Else
s = "No selection"
End If
MsgBox s
End Sub
Edit two: Based on comments, I cobbled together a sample that appears to do exactly what you want. You'll need to modify the variable assignments, of course, unless you're wanting to copy the same file from "D:\Temp" to "D:\Temp\Backup" over and over. :)
Sub TestFileMove()
Dim fso As FileSystemObject
Dim SourceFolder As String
Dim DestFolder As String
Dim SourceFile As String
Dim DestFile As String
Set fso = New FileSystemObject
SourceFolder = "D:\Temp\"
DestFolder = "D:\Temp\Backup\"
SourceFile = "test.txt"
Set InFile = fso.GetFile(SourceFolder & SourceFile)
DestFile = DestFolder & SourceFile
If fso.FileExists(DestFile) Then
Dim Dlg As FileDialog
Set Dlg = Application.FileDialog(msoFileDialogSaveAs)
Dlg.InitialFileName = DestFile
Do While True
If Dlg.Show = 0 Then
Exit Sub
End If
DestFile = Dlg.Item
If Not fso.FileExists(DestFile) Then
Exit Do
End If
Loop
End If
InFile.Move DestFile
End Sub
Here's some really quick code that I knocked up but basically looks at it from a different angle. You could put a combobox on a userform and get it to list the items as the user types. Not pretty, but it's a start for you to make more robust. I have hardcoded the directory c:\ here, but this could come from a text box
Private Sub ComboBox1_KeyUp(ByVal KeyCode As MSForms.ReturnInteger,
ByVal Shift As Integer)
Dim varListing() As Variant
Dim strFilename As String
Dim strFilePart As String
Dim intFiles As Integer
ComboBox1.MatchEntry = fmMatchEntryNone
strFilePart = ComboBox1.Value
strFilename = Dir("C:\" & strFilePart & "*.*", vbDirectory)
Do While strFilename <> ""
intFiles = intFiles + 1
ReDim Preserve varListing(1 To intFiles)
varListing(intFiles) = strFilename
strFilename = Dir()
Loop
On Error Resume Next
ComboBox1.List() = varListing
On Error GoTo 0
ComboBox1.DropDown
End Sub
Hope this helps. On error resume next is not the best thing to do but in this example stops it erroring if the variant has no files

getting VBA to Append txt from bottom to top

I would like to append a text (.txt) file backwards is this possible?
By backwards I mean writing text from bottom to top rather then the standards top to bottom.
Why because the txt file I want to compile is read so that items at the top of the list are given priority to those at the bottom.
I can't think of any other way than to create a new file whenver you want to insert data at the top and then delete/rename the old one and rename the new file to the new one.
Not sure exactly your requirements BUT the easiest way in VBA is to
1. Add a reference to the Microsoft Scripting Runtime.
Public Sub Reverse()
Dim lReverseString As String
Dim lFSO As FileSystemObject
Set lFSO = New Scripting.FileSystemObject
With lFSO.OpenTextFile("SourceName", ForReading)
While Not .AtEndOfStream
' Note if you are looking to read a line at at a time use .ReadLine Instead of .Read
lReverseString = .Read & lReverseString
Wend
End With
' now you have a string in reverse
With lFSO.CreateTextFile("TargetName", True, False)
.Write lReverseString
.Close
End With
End Sub
This is a basic form which should get you going.
How about an array? It would not be suitable with a very large file:
Dim fs As Object
Dim ts As Object
Dim AllTextArray As Variant
''Late binding, no reference required
Set fs = CreateObject("Scripting.FileSystemObject")
''ForReading=1
Set ts = fs.OpenTextFile("c:\docs\BookX.csv", 1)
AllTextArray = Split(ts.ReadAll, vbCrLf)
For i = UBound(AllTextArray) To 0 Step -1
Debug.Print AllTextArray(i)
Next
If it's just writing each paragraph or sentence in the reverse order than it appears:
Sub Test()
Dim currentDocument As Document
Set currentDocument = ActiveDocument
Dim sourceDocument As Document
Set sourceDocument = Documents.Add("c:\words.txt")
Dim i As Long
For i = sourceDocument.Paragraphs.Count To 1 Step -1
currentDocument.Range.InsertAfter sourceDocument.Paragraphs(i).Range.Text
DoEvents
Next
sourceDocument.Close wdDoNotSaveChanges
End Sub