File name without extension name VBA - vba

I need to get file name without extension name by VBA. I know ActiveWorkbook.Name property , but if user haves Windows property Hide extensions for known file types turn off, the result of my code will be [Name.Extension]. How can I return only name of Workbook independent of windows property?
I try even ActiveWorkbook.Application.Caption but I can't customize this property.

The answers given here already may work in limited situations, but are certainly not the best way to go about it. Don't reinvent the wheel. The File System Object in the Microsoft Scripting Runtime library already has a method to do exactly this. It's called GetBaseName. It handles periods in the file name as is.
Public Sub Test()
Dim fso As New Scripting.FileSystemObject
Debug.Print fso.GetBaseName(ActiveWorkbook.Name)
End Sub
Public Sub Test2()
Dim fso As New Scripting.FileSystemObject
Debug.Print fso.GetBaseName("MyFile.something.txt")
End Sub
Instructions for adding a reference to the Scripting Library

Simple but works well for me
FileName = ActiveWorkbook.Name
If InStr(FileName, ".") > 0 Then
FileName = Left(FileName, InStr(FileName, ".") - 1)
End If

Using the Split function seems more elegant than InStr and Left, in my opinion.
Private Sub CommandButton2_Click()
Dim ThisFileName As String
Dim BaseFileName As String
Dim FileNameArray() As String
ThisFileName = ThisWorkbook.Name
FileNameArray = Split(ThisFileName, ".")
BaseFileName = FileNameArray(0)
MsgBox "Base file name is " & BaseFileName
End Sub

This gets the file type as from the last character (so avoids the problem with dots in file names)
Function getFileType(fn As String) As String
''get last instance of "." (full stop) in a filename then returns the part of the filename starting at that dot to the end
Dim strIndex As Integer
Dim x As Integer
Dim myChar As String
strIndex = Len(fn)
For x = 1 To Len(fn)
myChar = Mid(fn, strIndex, 1)
If myChar = "." Then
Exit For
End If
strIndex = strIndex - 1
Next x
getFileType = UCase(Mid(fn, strIndex, Len(fn) - x + 1))
End Function

You could always use Replace() since you're performing this on the workbook's Name, which will almost certainly end with .xlsm by virtue of using VBA.
Using ActiveWorkbook per your example:
Replace(Application.ActiveWorkbook.Name, ".xlsm", "")
Using ThisWorkbook:
Replace(Application.ThisWorkbook.Name, ".xlsm", "")

This thread has been very helpful to me lately. Just to extend on the answer by #RubberDuck, the File System Object in the Microsoft Scripting Runtime library is already there to achieve this. Also if you define it as an Object as below, it will save you the hassle of having to enable 'Microsoft Scripting Runtime' in VBA Tools > References:
Dim fso As Object
Set fso = CreateObject("Scripting.FileSystemObject")
Debug.Print fso.GetBaseName(ActiveWorkbook.Name)
In this way it will return name of the ActiveWorkbook without extension.
There is another way by using INSTRREV function as below:
Dim fname As String
fname = Left(ActiveWorkbook.Name, InStrRev(ActiveWorkbook.Name, ".") - 1)
MsgBox fname
Both will return the same result. Also in both of the methods above, they will retain any full-stops in the file name and only get rid of the last full-stop and the file extension.

To be verbose it the removal of extension is demonstrated for
workbooks.. which now have a variety of extensions .
. a new unsaved Book1 has no ext
. works the same for files
Function WorkbookIsOpen(FWNa$, Optional AnyExt As Boolean = False) As Boolean
Dim wWB As Workbook, WBNa$, PD%
FWNa = Trim(FWNa)
If FWNa <> "" Then
For Each wWB In Workbooks
WBNa = wWB.Name
If AnyExt Then
PD = InStr(WBNa, ".")
If PD > 0 Then WBNa = Left(WBNa, PD - 1)
PD = InStr(FWNa, ".")
If PD > 0 Then FWNa = Left(FWNa, PD - 1)
'
' the alternative of using split.. see commented out below
' looks neater but takes a bit longer then the pair of instr and left
' VBA does about 800,000 of these small splits/sec
' and about 20,000,000 Instr Lefts per sec
' of course if not checking for other extensions they do not matter
' and to any reasonable program
' THIS DISCUSSIONOF TIME TAKEN DOES NOT MATTER
' IN doing about doing 2000 of this routine per sec
' WBNa = Split(WBNa, ".")(0)
'FWNa = Split(FWNa, ".")(0)
End If
If WBNa = FWNa Then
WorkbookIsOpen = True
Exit Function
End If
Next wWB
End If
End Function

I use a macro from my personal.xlsb and run it on both xlsm and xlsx files so a variation on David Metcalfe's answer that I use is
Dim Wrkbook As String
Wrkbook = Replace(Application.ActiveWorkbook.Name, ".xlsx", ".pdf")
Wrkbook = Replace(Application.ActiveWorkbook.Name, ".xlsm", ".pdf")

Here is a solution if you do not want to use FSO.
There were some similar answers before, but here some checks are done to handle multiple dots in name and name without extension.
Function getFileNameWithoutExtension(FullFileName As String)
Dim a() As String
Dim ext_len As Integer, name_len As Integer
If InStr(FullFileName, ".") = 0 Then
getFileNameWithoutExtension = FullFileName
Exit Function
End If
a = Split(ActiveWorkbook.Name, ".")
ext_len = Len(a(UBound(a))) 'extension length (last element of array)
name_len = Len(FullFileName) - ext_len - 1 'length of name without extension and a dot before it
getFileNameWithoutExtension = Left(FullFileName, name_len)
End Function
Sub test1() 'testing the function
MsgBox (getFileNameWithoutExtension("test.xls.xlsx")) ' -> test.xls
MsgBox (getFileNameWithoutExtension("test")) ' -> test
MsgBox (getFileNameWithoutExtension("test.xlsx")) ' -> test
End Sub

strTestString = Left(ThisWorkbook.Name, (InStrRev(ThisWorkbook.Name, ".", -1, vbTextCompare) - 1))
full credit: http://mariaevert.dk/vba/?p=162

Related

MS Word VBA: Saving a document using the header

I have been trying to figure out a way to, after performing a mail merge, separate the documents into individual ones and name them after a specific item, preferably the first line of the header. I have only been able to find ways to split the document, but cannot figure out how to name it. Any help with how to write the VBA code to save a document as the header would be very much appreciated.
Since you already separated the documents, the code below might give them names using their first sentence.
Private Function DocName(Doc As Document) As String
' 23 Aug 2017
Const Illegals As String = "\:/;?*|>"""
Static FaultCounter As Integer
Dim Fun As String
Dim Title As String
Dim Ch As String
Dim i As Integer
Title = Trim(Doc.Sentences(1))
For i = 1 To Len(Title)
Ch = Mid(Title, i, 1)
If (Asc(Ch) > 31) And (Asc(Ch) < 129) Then
If InStr(Illegals, Ch) = 0 Then Fun = Fun & Ch
End If
Next i
If Len(Fun) = 0 Then
FaultCounter = FaultCounter + 1
Fun = Format(FaultCounter, """Default File Name (""0"")""")
End If
DocName = Fun
End Function
Before saving the file you might want to check for duplicates. Use the Dir() function for that and add a number to duplicate names using the system I included above to name files where the first sentence might be empty.
You may also have to review the characters which aren't permitted in file names. I have simply excluded all below ASCII(32) and above ASCII(128), and then the known ones Windows doesn't like. You might want to modify that range further.
To call the above function use code like this:-
Private Sub GetName()
Debug.Print DocName(ActiveDocument)
End Sub
This is the code I have so far, I was able to find it off of a very helpful website, but the code saves as the word "report" which I set it to right now while I'm trying to figure it out, and then the number of the document.
Option Explicit
Sub splitter()
' splitter Macro
' Macro created by Doug Robbins to save each letter created by a mailmergeas
a separate file.
Application.ScreenUpdating = False
Dim Program As String
Dim DocName As String
Dim Letters As Integer, Counter As Integer
Letters = ActiveDocument.Sections.Count
Selection.HomeKey Unit:=wdStory
Counter = 1
While Counter < Letters
'program = ActiveDocument.MailMerge.DataSource.DataFields("Program_Outcomes_PlanReport_Name").Value
DocName = "Reports" & LTrim$(Str$(Counter)) 'Generic name of document
ActiveDocument.Sections.First.Range.Cut
Documents.Add
Selection.Paste
ActiveDocument.Sections(2).PageSetup.SectionStart = wdSectionContinuous
ActiveDocument.SaveAs filename:="E:\assessment rubrics\Templates" & "\" & DocName, FileFormat:=wdFormatDocument, LockComments:=False, Password:="",
AddToRecentFiles:=False, WritePassword:="", ReadOnlyRecommended:=False, EmbedTrueTypeFonts:=False, SaveNativePictureFormat:=False, SaveFormsData:=False, SaveAsAOCELetter:=False
ActiveWindow.Close
Counter = Counter + 1
Wend
Application.ScreenUpdating = True
End Sub

Access variables and their names from the current scope

Is it possible to:
Access a list of all variables in a VBA runtime environment?
Access the name of a variable with VBA?
Example:
function v2S(str as string) as string
For each variable in Variables
dim I as integer
for I = 1 to 10
v2S = replace(v2S,"%" & variable.name & "%", variable.value)
next
next
end function
Example use case:
Dim skyColor as string
skyColor = "green"
Debug.Print v2S("The sky is %skyColor% today!")
There is an application I can send commands to via a com object and I wish to do something along the lines of:
Dim i a integer
for i = 1 to MI.Eval("numtables()")
MI = GetObject(,"MapInfo.Application.x64")
debug.print MI.Eval(v2S("tableinfo(%i%,1)")) ' Print name of table
next
The above looks much cleaner than:
Dim i a integer
for i = 1 to MI.Eval("numtables()")
MI = GetObject(,"MapInfo.Application.x64")
debug.print MI.Eval(v2S("tableinfo(" & i & ",1)")) ' Print name of table
next
But of course if it were possible I would want it to be general which may be difficult...
For my own use case this is pretty good.
However it still isn't very readable. This is another option. It's more readable but also more cluttered:
Sub Main()
Dim Vars as object, myString as string
set Vars = CreateObject("scripting.Dictionary")
Vars.add "Var1","Val1"
Vars.add "Var2","Val2"
'...
myString = r("Var1: #{Var1} and Var2: #{Var2}", Vars)
End Sub
function r(byval s as string, byval o as object) as string
for each key in o.keys
s = replace(s,"#{" & key & "}",o.item(key))
next
r = s
end function
I wish string interpolation functionality existed by default in VBA.

Excel VBA user defined function to find images in folder (match excel names to folder names of images)

Currently i am using a function to match image names from excel sheet to image folder, but i want one more thing... that if i save image and forget to add its name in excel then it should show me that i forget to add name.
for example if i save 3 images in image folder
16095_1.jpg,16095_2.jpg,16095_3.jpg
and i add image names in excel sheet as
16095_1.jpg,16095_2.jpg
then it should warn me that i forget one image name in excel cell.
my image name format is - 16095_1.jpg,16095_2.jpg
function i am using is...
Function findimage(Path As String, ImageList As String)
Dim results
Dim x As Long
Dim dc 'double comma
results = Split(ImageList, ",")
If Not Right(Path, 1) = "\" Then Path = Path & "\"
For x = 0 To UBound(results)
results(x) = Len(Dir(Path & results(x))) > 0
Next
dc = InStr(ImageList, ",,")
If dc = 0 Then
findimage = Join(results, ",")
Else
findimage = ("Double_comma")
End If
End Function
This function takes a folder path and a variable number of patterns (See MSDN - Parameter Arrays (Visual Basic)). Using the MSDN - Dir Function to iterates over the file names in the folder path and compares them against the patterns with the MSDN - Like Operator (Visual Basic) to count the number of files that match the patterns.
Usage:
getFileCount("C:\Users\Owner\Pictures",".gif",".png")
getFileCount("C:\Users\Owner\Pictures","*.gif"
getFileCount("C:\Users\Owner\Pictures","apple_.gif","banana_.gif", "orange_##.*")
getFileCount("C:\Users\Owner\Pictures","#####_#.gif")
Function getFileCount(DirPath As String, ParamArray Patterns() As Variant) As Integer
Dim MyFile As String
Dim count As Integer, x As Long
If Not Right(DirPath, 1) = "\" Then DirPath = DirPath & "\"
MyFile = Dir(DirPath, vbDirectory)
Do While MyFile <> ""
For x = 0 To UBound(Patterns)
If MyFile Like Patterns(x) Then
count = count + 1
Exit For
End If
Next
MyFile = Dir()
Loop
getFileCount = count
End Function

vba that searches for a string in all the files inside a folder and its subfolders

I have a huge script to make which I had partly completed (parsing xml file to vba and deleting certain unwanted childs),but i am struck at one point.
I have strings (that are obtained from my previous output) in cells A1:A1500 in my worksheet and I have a folder named "model" in the same path where my workbook is placed (the folder has many subfolders and inside subfolders many .c , .h , .xml file types are present).
I need a script that will take the string in A1 and search inside all the files in the folder "model" and its subfolders and if the string is present in any of the files I have to print/put "string found" in cell B1 and if the string is not present in any of the files I have to print/put "Not found" in cell B1. In the same way I need to search all the strings from A2:A1500 inside all the files in the folder "model" and print/put "string found"/not found in the cells B2:B1500.
Below are some of the strings I have in my worksheet in column A1:A4:
vel_gradient
D_speed_20
AGB_router_1
F10_35_XS
I am somewhat familiar with vba but I am not sure how to implement this.
Any help regarding the script is accepted. Can someone help me with this.
As noted in the question comments, the answer to this question involves recursion, which means that one or more sub routines or functions call themselves again and again and again, etc. Fortunately, Excel will keep track of all of this for you. My solution also takes advantage of an Excel trick that allows you to create or unload arrays without iterating by using the Range.Value property. Also included is a string indent variable to help visualize how the recursion is happening. Just comment out the Debug.Print statements when no longer needed.
The solution involves 3 steps.
Create an array of all of the strings which could be matched along with 2 parallel arrays to hold the found / not found strings and the first file where the string was matched
Pass the 3 arrays ByRef to a sub routine that processes all of the sub folders and files for a given folder. Any sub folders recurse back into the folder sub routine, while files are processed by a separate file routine.
After all sub folders and files have been processed, the found / not found column is populated from the associated array.
Enjoy
Step 1 - The main method
' The main sub routine.
Public Sub FindStrings(strFolder As String, Optional wksSheet As Worksheet = Nothing)
' Used examples given, better to convert to variables and calculate at run time.
Const lngFirstRow As Long = 1
Const lngLasstRow As Long = 1500
Const strStringsCol As String = "A"
Const strMatchesFoundCol As String = "B"
Const strFileNamesCol As String = "C"
Dim lngIndex As Long, lngFolderCount As Long, lngFileCount As Long
Dim strIndent As String
Dim varStrings As Variant, varMatchesFound As Variant, varFileNames As Variant
If wksSheet Is Nothing Then
Set wksSheet = ActiveSheet
End If
With wksSheet
' Create the strings array from the given range value.
varStrings = .Range(.Cells(lngFirstRow, strStringsCol), .Cells(lngLasstRow, strStringsCol)).Value
' Transpose the strings array into a one dimentional array.
varStrings = Application.WorksheetFunction.Transpose(varStrings)
End With
' Initialize file names array to empty strings.
ReDim varFileNames(LBound(varStrings) To UBound(varStrings))
For lngIndex = LBound(varFileNames) To UBound(varFileNames)
varFileNames(lngIndex) = vbNullString
Next
' Initialize matches found array to empty strings.
ReDim varMatchesFound(LBound(varStrings) To UBound(varStrings))
For lngIndex = LBound(varMatchesFound) To UBound(varMatchesFound)
varMatchesFound(lngIndex) = vbNullString
Next
' Process the main folder.
Call ProcessFolder(strFolder, strIndent, varStrings, varMatchesFound, varFileNames, lngFolderCount, lngFileCount)
' Finish setting up matches found array.
For lngIndex = LBound(varMatchesFound) To UBound(varMatchesFound)
If Len(Trim$(varMatchesFound(lngIndex))) = 0 Then
varMatchesFound(lngIndex) = "Not found"
End If
Next
' Transpose the associated arrays so we can use them to load found / not found and file names columns.
varFileNames = Application.WorksheetFunction.Transpose(varFileNames)
varMatchesFound = Application.WorksheetFunction.Transpose(varMatchesFound)
' Set up the found / not found column data from the matches found array.
With wksSheet
.Range(.Cells(lngFirstRow, strFileNamesCol), .Cells(lngLasstRow, strFileNamesCol)).Value = varFileNames
.Range(.Cells(lngFirstRow, strMatchesFoundCol), .Cells(lngLasstRow, strMatchesFoundCol)).Value = varMatchesFound
End With
Debug.Print "Folders: "; lngFolderCount, "Files: "; lngFileCount
End Sub
Step 2 - The process sub folder method
Private Sub ProcessFolder(strFolder As String, ByRef strIndent As String, ByRef varStrings As Variant, ByRef varMatchesFound As Variant, ByRef varFileNames As Variant, ByRef lngFolderCount As Long, lngFileCount As Long)
Dim objFileSystemObject As Object, objFolder As Object, objFile As Object
' Use late binding throughout this method to avoid having to set any references.
Set objFileSystemObject = CreateObject("Scripting.FileSystemObject")
lngFolderCount = lngFolderCount + 1
Debug.Print strIndent & "Dir: " & Format(lngFolderCount, "###,##0 ") & strFolder
For Each objFolder In objFileSystemObject.GetFolder(strFolder).SubFolders
If objFolder.Name = "history" Then
'Do Nothing
Else
' Recurse with the current sub folder.
Call ProcessFolder(objFolder.Path, strIndent & " ", varStrings, varMatchesFound, varFileNames, lngFolderCount, lngFileCount)
End If
Next
' Process any files found in the current folder.
For Each objFile In objFileSystemObject.GetFolder(strFolder).Files
Call ProcessFile(objFile.Path, strIndent & " ", varStrings, varMatchesFound, varFileNames, lngFileCount)
Next
Set objFileSystemObject = Nothing: Set objFolder = Nothing: Set objFile = Nothing
End Sub
Step 3 - The process file method
Private Sub ProcessFile(strFullPath As String, ByRef strIndent As String, ByRef varStrings As Variant, ByRef varMatchesFound As Variant, ByRef varFileNames As Variant, ByRef lngFileCount As Long)
On Error Resume Next
Dim objFileSystemObject As Object
Dim strFileContent As String
Dim lngIndex As Long
lngFileCount = lngFileCount + 1
Debug.Print strIndent & "File: " & Format(lngFileCount, "###,##0 ") & strFullPath
' Use late binding throughout this method to avoid having to set any references.
Set objFileSystemObject = CreateObject("Scripting.FileSystemObject")
strFileContent = objFileSystemObject.OpenTextFile(strFullPath).Readall()
If Err.Number = 0 Then
' Check for matched strings by iterating over the strings array.
For lngIndex = LBound(varStrings) To UBound(varStrings)
' Skip zero length strings.
If Len(Trim$(varStrings(lngIndex))) > 0 Then
' We have a matched string.
If InStr(1, strFileContent, varStrings(lngIndex), vbTextCompare) > 0 Then
' Set up parallel arrays the first time the string is matched.
If Len(Trim$(varMatchesFound(lngIndex))) = 0 Then
' Set corresponding array value.
varMatchesFound(lngIndex) = "String found"
' Save file name where first match was found.
varFileNames(lngIndex) = strFullPath
End If
End If
End If
Next
Else
Err.Clear
End If
Set objFileSystemObject = Nothing
On Error GoTo 0
End Sub
If your files are not too large you can read all the content in one shot:
Sub Tester()
Debug.Print StringInFile("C:\_Stuff\test\File_Val2.txt", "xxx")
End Sub
Function StringInFile(fPath, txtSearch) As Boolean
StringInFile = InStr(CreateObject("scripting.filesystemobject").opentextfile( _
fPath).Readall(), txtSearch) > 0
End Function
However if you need to test for multiple strings it would be more efficient to read the file once and then check for each string using instr()

Using Split function to find last folder in file path with only one line

I have a function that looks like
Dim arrFold() As String
Dim lastFold As String
arrFold = Split(filePath, "\")
lastFold = arrFold(ubound(arrFold))
I am looking for a way to simplify this into just finding the last element on the first line, rather than having to define an array initially, what can be done to avoid adding that to memory?
debug.print mid(filePath, instrrev(filePath, chr(92))+1)
debug.print trim(right(replace(filePath, chr(92), space(999)), 999))
Sub findLast()
Dim myString As String
myString = "\File\lesser\evenless\least"
MsgBox InStrRev(myString, "\")
End Sub