Change invalid characters in file names VBA - vba

There is code below that I used to change ".." to ".". For example I have a name of file like "file..pdf" and I would like to have "file.pdf", but It removes all dots. I don't know how to change it:
Function strLegalFileName2(ByVal FName) As String
Dim i As Integer
Const strIllegals = "*&..&*"
strLegalFileName2 = FName
For i = 1 To Len(strIllegals)
strLegalFileName2 = Replace(strLegalFileName2, Mid$(strIllegals, i, 1), ".")
Next i
End Function
Sub LoopThroughFiles2()
Dim FName As Variant
Dim strNew As String
Dim strDir As String
strDir = "path"
FName = Dir(strDir & "*..*")
Do While Len(FName) > 0
strNew = strLegalFileName2(FName)
If StrComp(strNew, FName) <> 0 Then Name (strDir & FName) As (strDir & strNew)
FName = Dir
Loop
End Sub

Because you go through all character in your file name:
For i = 1 To Len(strIllegals)
Therefore it will remove all "." in your file name.
Just simple use:
Function strLegalFileName2(ByVal FName) As String
Dim i As Integer
Const strIllegals = "*&..&*"
strLegalFileName2 = FName
strLegalFileName2 = Replace(strLegalFileName2, "..", ".")
End Function

If you want to change some special characters to some other characters I suggest you to use this function:
Function strLegalFileName2(ByVal FName) As String
Dim strIllegal() As String
Dim i As Integer
Const strIllegals = "..#=>#."
strIllegal = Split(strIllegals, "#|#")
For i = LBound(strIllegal) To UBound(strIllegal)
FName = Replace(FName, Mid(strIllegal(i), 1, InStr(1, strIllegal(i), "#=>#") - 1), Mid(strIllegal(i), InStr(1, strIllegal(i), "#=>#") + 4))
Next i
strLegalFileName2 = FName
End Function
For more illegal you can change strIllegals = "..#=>#." to something like strIllegals = "..#=>#.#|#&#=># AND " that will change & to AND.

Related

How to loop through sub folders of folder A to get file name in each subfolder and copy other file with same name from folder B using VBA

There is folder A which contains multiple subfolders like A1,A2, A3 etc which every subfolder has mostly one sometimes 2 word files with the name(eg file_a1) in it. Then, there is other folder B (not a subfolder of A) which contains multiple word files with standard similar (file_a1_XZ) names.
I want to loop in subfolders of A and copy word files from B to respective sub folder e.g A1
File Structure:
Parent Folder
|
|
----Parent B
|
|
--- B
|
-file_a1_XZ
-file_a2_XZ
----Parent A
|
|
--- A
|
|
-- A1
|
-file_a1
-- A2
|
-file_a2
Move Files to Specific Folders Using Dir
Moves files from B to subfolders of A i.e. the filenames contain the names of the subfolders.
Option Explicit
Sub MoveFiles()
Const sFolderPath As String = "C:\Test\T2022\71752347\B\"
Const dFolderPath As String = "C:\Test\T2022\71752347\A\"
Const sExtensionPattern As String = ".doc*"
Dim dFolderName As String: dFolderName = Dir(dFolderPath, vbDirectory)
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
Do Until Len(dFolderName) = 0
If dFolderName <> "." And dFolderName <> ".." Then
dict(dFolderName) = Empty
End If
dFolderName = Dir
Loop
Dim Key As Variant
Dim sFileName As String
Dim fCount As Long
For Each Key In dict.Keys
sFileName = Dir(sFolderPath & "*" & Key & "*" & sExtensionPattern)
Do Until Len(sFileName) = 0
fCount = fCount + 1
FileCopy sFolderPath & sFileName, _
dFolderPath & Key & "\" & sFileName
Kill sFolderPath & sFileName
sFileName = Dir
Loop
Next
MsgBox "Files moved: " & fCount, vbInformation
End Sub
If the files in B are in various subfolders, use the following.
Sub MoveFiles()
Const sFolderPath As String = "C:\Test\T2022\71752347\B\"
Const dFolderPath As String = "C:\Test\T2022\71752347\A\"
Const sExtensionPattern As String = ".doc*"
Dim dFolderName As String: dFolderName = Dir(dFolderPath, vbDirectory)
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
Do Until Len(dFolderName) = 0
If dFolderName <> "." And dFolderName <> ".." Then
dict(dFolderName) = Empty
End If
dFolderName = Dir
Loop
Dim sFilePaths() As String
Dim sFilePath As String
Dim dFilePath As String
Dim Key As Variant
Dim f As Long
Dim fCount As Long
For Each Key In dict.Keys
sFilePaths = ArrFilePaths(sFolderPath, _
"*" & Key & "*" & sExtensionPattern)
For f = 0 To UBound(sFilePaths)
fCount = fCount + 1
sFilePath = sFilePaths(f)
dFilePath = dFolderPath & Key & "\" & Right(sFilePath, _
Len(sFilePath) - InStrRev(sFilePath, "\"))
FileCopy sFilePath, dFilePath
Kill sFilePath
Next f
Next Key
MsgBox "Files moved: " & fCount, vbInformation
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Returns the file paths of the files in a folder
' in a zero-based string array.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function ArrFilePaths( _
ByVal FolderPath As String, _
Optional ByVal FilePattern As String = "*.*", _
Optional ByVal DirSwitches As String = "/s/b/a-d") _
As String()
Const ProcName As String = "ArrFilePaths"
On Error GoTo ClearError
' Ensuring that a string array is passed if an error occurs.
ArrFilePaths = Split("") ' LB = 0 , UB = -1
Dim pSep As String: pSep = Application.PathSeparator
If Right(FolderPath, 1) <> pSep Then FolderPath = FolderPath & pSep
Dim ExecString As String ' '%comspec%' or 'cmd.exe' ?
ExecString = "%comspec% /c Dir """ _
& FolderPath & FilePattern & """ " & DirSwitches
Dim Arr() As String: Arr = Split(CreateObject("WScript.Shell") _
.Exec(ExecString).StdOut.ReadAll, vbCrLf)
If UBound(Arr) > 0 Then
ReDim Preserve Arr(0 To UBound(Arr) - 1)
End If
ArrFilePaths = Arr
ProcExit:
Exit Function
ClearError:
Debug.Print "'" & ProcName & "' Run-time error '" _
& Err.Number & "':" & vbLf & " " & Err.Description
Resume ProcExit
End Function

Parse and format text file

I have a text file that is not in a format that I can use for printing labels. The current format is like this:
DY234-02 0.5 0.5 Qty 6
U21 U12 U14 U28
TR459-09 0.5 0.5 Qty 9
U11 U78 U7 U8 U30 U24
I need the file to end up like this:
DY234-02 0.5 0.5 Qty 6 U21 U12 U14 U28
TR459-09 0.5 0.5 Qty 9 U11 U78 U7 U8 U30 U24
The files contain about 100 lines of this format I have used vbscript to try to get what I need but the format is not much different. If someone could get me pointed in the right direction that would be great. I am open to all other methods for accomplishing this. Thanks
This is my code in vbscript, but is not doing the job correctly:
Const ForReading = 1
Const ForWriting = 2
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objFile = objFSO.OpenTextFile("C:\Scripts\parse.txt", ForReading)
Do Until objFile.AtEndOfStream
strLine1 = objFile.ReadLine
strLine2 = ""
If Not objFile.AtEndOfStream Then
strLine2 = objFile.ReadLine
End If
strNewLine = strLine1 & strLine2
strNewContents = strNewContents & strNewLine & vbCrLf
Loop
objFile.Close
Set objFile = objFSO.OpenTextFile("C:\Scripts\B3.txt", ForWriting, True)
objFile.Write strNewContents
objFile.Close
If the format is repeated like this, you can read in the text file line by line, and check if there is data on each line. If so join the data to an output string, otherwise add a carriage return to the output string, before finally outputting it to a new text file. Something like this perhaps:
Dim strInFile As String
Dim strOutFile As String
Dim intInFile As Integer
Dim intOutFile As Integer
Dim strInput As String
Dim strOutput As String
strInFile = "J:\downloads\data-in.txt"
strOutFile = "J:\downloads\data-out.txt"
intInFile = FreeFile
Open strInFile For Input As intInFile
intOutFile = FreeFile
Open strOutFile For Output As intOutFile
Do
Line Input #intInFile, strInput
If Len(Trim(strInput)) > 0 Then
strOutput = strOutput & " " & strInput
Else
strOutput = strOutput & vbCrLf
End If
Loop Until EOF(intInFile)
Print #intOutFile, strOutput
Reset
Regards,
Try next code, please. It is fast due to the fact it reads all the text value at once and drop the result, also at once. Everything is happening in memory.
Sub testSplitTextFile()
Dim objFSO As Object, objTF As Object, strIn As String, fullFilename As String, retFile As String
Dim arrIn As Variant, strRet As String, i As Long
'use here your path
fullFilename = "C:\Teste VBA Excel\Teste StackOverflow\TestSplit.txt"
retFile = "C:\Teste VBA Excel\Teste StackOverflow\RetFile.txt"'your path
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objTF = objFSO.OpenTextFile(fullFilename, 1)
strIn = objTF.ReadAll 'it reads all the txt file string
objTF.Close
arrIn = Split(strIn, vbCrLf) 'it splits the above string on lines
'Then, it builds a string based on your conditions:
For i = 0 To UBound(arrIn) - 1
If arrIn(i) <> "" And arrIn(i + 1) <> "" Then
strRet = strRet & arrIn(i) & " " & arrIn(i + 1) & vbCrLf
End If
Next i
strRet = left(strRet, Len(strRet) - 1)' it eliminates the last vbCrLf character
FreeFile 1
Open retFile For Output As #1
Print #1, strRet 'it drops, at once the created string
Close #1
End Sub

Import EDI file into Access line by line

I need to import into Access table this EDI text file ( ' as line terminator):
UNA:+.?'
UNB+UNOC:3+BRANDEUROPE+ANYCODE+180206:1121+5439'
UNH+5439-1+DESADV:D:99B:UN'
BGM+351+0089430043+9'
DTM+11:20180205:102'
DTM+137:20180205:102'
MEA+WT+AAD+KGM:2126.100'
MEA+CT+SQ+NMP:00000'
NAD+DP+0017309707++NameStreet 22+Rome++00100+IT'
CTA+DL'
NAD+SU+DE++BRAND Systems+Rome+Rome++00100+IT'
CTA+DL'
TOD+6++CIP'
CPS+1'
PAC+2++BX'
MEA+WT+G+KGM:88'
PCI+24'
GIN+ML+AL7B009435+AL7B009438'
LIN+1++46550705:VP'
PIA+1+4114793:BP'
IMD+A++:::C833dn-EURO'
QTY+12:2'
RFF+OP:44CKV07S:000001'
CPS+2'
PAC+1++BX'
MEA+WT+G+KGM:0.01'
PCI+24'
LIN+1++01182907:VP'
PIA+1+4113617:BP'
IMD+A++:::RAM-256MB-C3/C5/C6/C7/MC3/MC5/C8'
QTY+12:1'
RFF+OP:44CKV07S:000003'
CPS+3'
PAC+4++BX'
MEA+WT+G+KGM:43.2'
PCI+24'
LIN+1++46361802:VP'
PIA+1+4114805:BP'
IMD+A++:::Tray-C5x2/MC5x3'
QTY+12:4'
RFF+OP:44CKV07S:000006'
This is the result I need:
0089430043 05/02/2018 46550705 AL7B009435
0089430043 05/02/2018 46550705 AL7B009438
etc...
and this is what I tried:
Public Function import1()
Dim strFilename As String: strFilename = "C:\despatch.txt"
Dim strTextLine, CodProd, DataDoc As String
Dim SNarray() As String
Dim NumDoc As Long
Dim nPAC, NumRig, intCount As Integer
Dim iFile As Integer: iFile = FreeFile
Open strFilename For Input As #iFile
Do Until EOF(1)
Line Input #1, strTextLine
strTextLine = Replace(strTextLine, "'", "")
'BGM
If Left(strTextLine, 3) = "BGM" Then
NumDoc = Mid(strTextLine, 9, 10)
End If
'DTM
If Left(strTextLine, 6) = "DTM+11" Then
DataDoc = Mid(strTextLine, 14, 2) & "/" & Mid(strTextLine, 12, 2) & "/" & Mid(strTextLine, 8, 4)
End If
'CPS = numero record
If Left(strTextLine, 3) = "CPS" Then
NumRig = Val(Mid(strTextLine, 5, 3))
End If
'PAC = numero di matricole da estrarre
If Left(strTextLine, 3) = "PAC" Then
nPAC = Val(Mid(strTextLine, 5, 3))
End If
'GIN
If Left(strTextLine, 3) = "GIN" Then
'strTextLine.MoveNext
End If
'LIN
If Left(strTextLine, 3) = "LIN" Then
CodProd = Mid(strTextLine, 8, 8)
End If
'strTextLine.MovePrevious
SNarray = Split(Mid(strTextLine, 8), "+")
For intCount = LBound(SNarray) To UBound(SNarray)
Debug.Print NumDoc & " " & DataDoc & " " & NumRig & " " & CodProd & " " & SNarray(intCount)
Next
'strTextLine.MovePrevious
'strTextLine.MovePrevious
Loop
Close #iFile
End Function
Before to import GIN record with serial numbers, I need to achieve the LIN record with che product code, and then pass them to variables.
I've tried with .MoveNext and then with two .MovePrevious but it gives me error: object needed.
Any help would be appreciated.
Thanks.
This is an example of a function that parses an EDIFACT segment, it's not debugged but it shows the algorithm to read the EDI data. It can be easily adapted to read ANSI X12.
Function GetLine() as String()
Dim Elements as String(99,3)
Do Until EOF(1)
mychar = Input(1, #1) ' Get one character
If mychar = vbCr Or \
mychar = vbLf Then ' Skip Line Breaks
Continue
Else If mychar = "?" Then ' Process Escape
If EOF(1) Then Exit Do ' Reached end of file
mychar = Input(1, #1)
data = data & mychar ' Treat next char as regular
Else If mychar = "'" Then ' End of Segment
Exit Do
Else If mychar = "+" Then ' Element separator
Elements(Elem,Comp) = data
data = ""
Comp = 1
Elem = Elem + 1
Else If mychar = ":" Then ' Composite separator
Elements(Elem,Comp) = data
data = ""
Comp = Comp + 1
Else ' Regular data
data = data & mychar
End If
Loop
Elements(Elem,Comp) = data
GetLine = Elements
End Function
Example use
'BGM
If Elements(0,0) = "BGM" Then
NumDoc = Elements(2,1)
End If
Finally I solved (I really don't know how I did), here my code:
Function GetLine() As String()
Dim FSO As Object, objFile, objFolderIN, objFolderOUT As Object
Set FSO = CreateObject("Scripting.FileSystemObject")
Set objFolderIN = FSO.GetFolder("C:\IN")
Set objFolderOUT = FSO.GetFolder("C:\Archivio")
Dim data, elem, comp
Dim i As Integer
Dim iFile As Integer: iFile = FreeFile
Dim Elements(99, 3) As String
Dim mychar As String
Dim NumDoc As Long
i = 1
For Each objFile In objFolderIN.Files
Open objFile For Input As #iFile
Do Until EOF(1)
Line Input #1, data
'Debug.Print data
mychar = Input(1, #1) ' Get one character.
If mychar = "'" Then Exit Do ' End of Segment
If mychar = vbCr Or _
mychar = vbLf Then
'Continue
ElseIf mychar = "?" Then
mychar = Input(1, #1) ' Skip Line Breaks and Escape
data = data & mychar
ElseIf mychar = "'" Then
Exit Do
ElseIf mychar = "+" Then ' Element separator
Elements(elem, comp) = data
data = ""
comp = 1
elem = elem + 1
ElseIf mychar = ":" Then ' Composite separator
Elements(elem, comp) = data
data = ""
comp = comp + 1
Else ' Regular data
data = data & mychar
End If
Loop
Elements(elem, comp) = data
GetLine = Elements
Close #iFile
i = i + 1
Next objFile
'BGM
If Elements(0, 0) = "BGM" Then
NumDoc = Elements(2, 1)
Debug.Print NumDoc
End If
End Function

Condition for Empty String in VBA

how do I check if the string variable is empty in vba?
if:
Dim StrFile1 As String, StrFile2 As String
Dim Text3 As String
Dim Len1 as Integer, Len2 As Integer
With NewMail
Text3 = Cells(i, 3).Value
StrPath = Cells(i, 2).Value & Text3
Text = Cells(i, 1).Value
.Subject =
' adds the data in column3 with space as subject
.From =
.To = Text
.BCC = ""
.TextBody =
StrFile1 = Dir(StrPath & "*.txt")
Len1 = Len(StrFile1)
Do While Len(StrFile1) > 0
.AddAttachment StrPath & StrFile1
StrFile1 = Dir
Loop
StrFile2 = Dir(StrPath & "*.pdf")
Len2 = Len(StrFile2)
Do While Len(StrFile2) > 0
.AddAttachment StrPath & StrFile2
StrFile2 = Dir
Loop
If (Len1 & Len2) = 0 Then
GoTo Last
'.AddAttachment Text3
.Send
End With
i = i + 1
Loop
Last:
End With
i = i + 1
Loop
Now i want to check simultaneously if Len1 and Len2 are 0, if so then I want to go to Last.
When I use this code I get a message/Compile error "Want to end with without with"
and
i am not sure if
If (Len1 & Len2) = 0 Then
GoTo Last
this is a proper code.
and Do i need to declare the label Last??
You have many way to do that like below :
Dim StrFiles As String
StrFiles = Trim(StrFile1 & StrFile2)
If IsEmpty(StrFiles) Then
If StrFiles = vbNullString Then
If StrFiles = "" Then
If StrFiles = Empty Then
If Len(StrFiles) = 0 Then
you can use + operator to check 2 strings are empty reference to your code, because Len Function returns an integer containing either the number of characters in a string
If (Len1 + Len2) = 0 Then
You can use Trim(strFile1 & vbNullString) = vbNullString to check if the string is empty.
So:
If Trim(strFile1 & vbNullString) = vbNullString Then
Debug.print "Empty String!"
End If
Thanks to #LordPeter
is.empty doesn't exist for VBA, but the second option works.
Alternatively, you can write:
(strFile1 & strFile2) = vbNullString
or
(strFile1 & strFile2) = ""
Yet another way is:
If Len(strFile1 & strFile2) > 0 Then
I did test to ensure that strings which aren't set return a length of 0, which appeared to be the case.

Displaying MacroOptions

In Excel/VBA it's possible to define some informations relative to a macro or function using the function MacroOptions. Is it possible to access such information once entered via VBA ? Thank you
I've been searching for a while but I found nothing great.
The only workaround I found is to use the code build by Chip Pearson and described on his website.
With this code, you can get some general information about a procedure.
Public Enum ProcScope
ScopePrivate = 1
ScopePublic = 2
ScopeFriend = 3
ScopeDefault = 4
End Enum
Public Enum LineSplits
LineSplitRemove = 0
LineSplitKeep = 1
LineSplitConvert = 2
End Enum
Public Type ProcInfo
ProcName As String
ProcKind As VBIDE.vbext_ProcKind
ProcStartLine As Long
ProcBodyLine As Long
ProcCountLines As Long
ProcScope As ProcScope
ProcDeclaration As String
End Type
Function ProcedureInfo(ProcName As String, ProcKind As VBIDE.vbext_ProcKind, _
CodeMod As VBIDE.CodeModule) As ProcInfo
Dim PInfo As ProcInfo
Dim BodyLine As Long
Dim Declaration As String
Dim FirstLine As String
BodyLine = CodeMod.ProcStartLine(ProcName, ProcKind)
If BodyLine > 0 Then
With CodeMod
PInfo.ProcName = ProcName
PInfo.ProcKind = ProcKind
PInfo.ProcBodyLine = .ProcBodyLine(ProcName, ProcKind)
PInfo.ProcCountLines = .ProcCountLines(ProcName, ProcKind)
PInfo.ProcStartLine = .ProcStartLine(ProcName, ProcKind)
FirstLine = .Lines(PInfo.ProcBodyLine, 1)
If StrComp(Left(FirstLine, Len("Public")), "Public", vbBinaryCompare) = 0 Then
PInfo.ProcScope = ScopePublic
ElseIf StrComp(Left(FirstLine, Len("Private")), "Private", vbBinaryCompare) = 0 Then
PInfo.ProcScope = ScopePrivate
ElseIf StrComp(Left(FirstLine, Len("Friend")), "Friend", vbBinaryCompare) = 0 Then
PInfo.ProcScope = ScopeFriend
Else
PInfo.ProcScope = ScopeDefault
End If
PInfo.ProcDeclaration = GetProcedureDeclaration(CodeMod, ProcName, ProcKind, LineSplitKeep)
End With
End If
ProcedureInfo = PInfo
End Function
Public Function GetProcedureDeclaration(CodeMod As VBIDE.CodeModule, _
ProcName As String, ProcKind As VBIDE.vbext_ProcKind, _
Optional LineSplitBehavior As LineSplits = LineSplitRemove)
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' GetProcedureDeclaration
' This return the procedure declaration of ProcName in CodeMod. The LineSplitBehavior
' determines what to do with procedure declaration that span more than one line using
' the "_" line continuation character. If LineSplitBehavior is LineSplitRemove, the
' entire procedure declaration is converted to a single line of text. If
' LineSplitBehavior is LineSplitKeep the "_" characters are retained and the
' declaration is split with vbNewLine into multiple lines. If LineSplitBehavior is
' LineSplitConvert, the "_" characters are removed and replaced with vbNewLine.
' The function returns vbNullString if the procedure could not be found.
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Dim LineNum As Long
Dim S As String
Dim Declaration As String
On Error Resume Next
LineNum = CodeMod.ProcBodyLine(ProcName, ProcKind)
If Err.Number <> 0 Then
Exit Function
End If
S = CodeMod.Lines(LineNum, 1)
Do While Right(S, 1) = "_"
Select Case True
Case LineSplitBehavior = LineSplitConvert
S = Left(S, Len(S) - 1) & vbNewLine
Case LineSplitBehavior = LineSplitKeep
S = S & vbNewLine
Case LineSplitBehavior = LineSplitRemove
S = Left(S, Len(S) - 1) & " "
End Select
Declaration = Declaration & S
LineNum = LineNum + 1
S = CodeMod.Lines(LineNum, 1)
Loop
Declaration = SingleSpace(Declaration & S)
GetProcedureDeclaration = Declaration
End Function
Private Function SingleSpace(ByVal Text As String) As String
Dim Pos As String
Pos = InStr(1, Text, Space(2), vbBinaryCompare)
Do Until Pos = 0
Text = Replace(Text, Space(2), Space(1))
Pos = InStr(1, Text, Space(2), vbBinaryCompare)
Loop
SingleSpace = Text
End Function
You can call the ProcedureInfo function using code like the following:
Sub ShowProcedureInfo()
Dim VBProj As VBIDE.VBProject
Dim VBComp As VBIDE.VBComponent
Dim CodeMod As VBIDE.CodeModule
Dim CompName As String
Dim ProcName As String
Dim ProcKind As VBIDE.vbext_ProcKind
Dim PInfo As ProcInfo
CompName = "modVBECode"
ProcName = "ProcedureInfo"
ProcKind = vbext_pk_Proc
Set VBProj = ActiveWorkbook.VBProject
Set VBComp = VBProj.VBComponents(CompName)
Set CodeMod = VBComp.CodeModule
PInfo = ProcedureInfo(ProcName, ProcKind, CodeMod)
Debug.Print "ProcName: " & PInfo.ProcName
Debug.Print "ProcKind: " & CStr(PInfo.ProcKind)
Debug.Print "ProcStartLine: " & CStr(PInfo.ProcStartLine)
Debug.Print "ProcBodyLine: " & CStr(PInfo.ProcBodyLine)
Debug.Print "ProcCountLines: " & CStr(PInfo.ProcCountLines)
Debug.Print "ProcScope: " & CStr(PInfo.ProcScope)
Debug.Print "ProcDeclaration: " & PInfo.ProcDeclaration
End Sub