Demonstration of my problem
Open a new Excel workbook and save these symbols 設計師協會 to cell [A1]
insert the following VBA code somewhere in the editor (Alt+F11)
execute it line per line (F8)
Sub test()
strCRLF = StrConv(vbCrLf, vbUnicode)
strSpecialchars = StrConv(Cells(1, 1), vbUnicode)
strFilename = "c:\test.txt"
Open strFilename For Output As #1
Print #1, strSpecialchars & strCRLF;
Close #1
End Sub
You will get a textfile which contains the chinese characters from [A1]. This proofs that VBA is able to handle unicode characters if you know the trick with adding StrConv(vbCrLf, vbUnicode)
Now try the same for strFilename = "C:\" & strSpecialchars & ".txt". You will get an error that you can't create a file with this filename. Of course you can't use the same trick adding a new line since its a filename.
How can I create text files with special characters in their filenames using VBA?
Is there a work-around or am I doing something wrong?
Note
I'm using Windows 7 x64. I'm able to create text files with special characters manually
I found a second method using FileSystemObject. But I hope I could avoid setting a reference to the VB script run-time library
Value retrieved from the cell is already in Unicode.
StrConv(vbUnicode) gives you "double unicode" which is broken because it went through a conversion using the current system codepage.
Then the Print command converts it back to "single unicode", again using the current system codepage. Don't do this. You're not saving unicode, you're saving invalid something that may only appear valid on your particular computer under your current settings.
If you want to output Unicode data (that is, avoid the default VB mechanism of auto-converting output text from Unicode to ANSI), you have several options.
The easiest is using FileSystemObject without trying to invent anything about unicode conversions:
With CreateObject("Scripting.FileSystemObject")
With .CreateTextFile("C:\" & Cells(1).Value & ".txt", , True)
.Write Cells(1).Value
.Close
End With
End With
Note the last parameter that controls Unicode.
If you don't want that, you can declare CreateFileW and WriteFile functions:
Private Declare Function CreateFileW Lib "kernel32.dll" (ByVal lpFileName As Long, ByVal dwDesiredAccess As Long, ByVal dwShareMode As Long, ByRef lpSecurityAttributes As Any, ByVal dwCreationDisposition As Long, ByVal dwFlagsAndAttributes As Long, ByVal hTemplateFile As Long) As Long
Private Declare Function CloseHandle Lib "kernel32.dll" (ByVal hObject As Long) As Long
Private Declare Function WriteFile Lib "kernel32.dll" (ByVal hFile As Long, ByRef lpBuffer As Any, ByVal nNumberOfBytesToWrite As Long, ByRef lpNumberOfBytesWritten As Long, ByRef lpOverlapped As Any) As Long
Private Const CREATE_ALWAYS As Long = 2
Private Const GENERIC_WRITE As Long = &H40000000
Dim hFile As Long
hFile = CreateFileW(StrPtr("C:\" & Cells(1).Value & ".txt"), GENERIC_WRITE, 0, ByVal 0&, CREATE_ALWAYS, 0, 0)
Dim val As String
val = Cells(1).Value
WriteFile hFile, &HFEFF, 2, 0, ByVal 0& 'Unicode byte order mark (not required, but to please Notepad)
WriteFile hFile, ByVal StrPtr(val), Len(val) * 2, 0, ByVal 0&
CloseHandle hFile
You are on the right track with the FileSystemObject. As Morbo mentioned you can late bind this so no reference is set. The FSO has a CreateTextFile function which can be set in unicode so the characters will appear as '??????' in VBA but will write correctly to the filename. Note the second parameter of the CreateTextFile function specifies a unicode string for the filename. The following will do the trick for you:
Sub test()
Dim strCRLF As String, strSpecialchars As String, strFilename As String
Dim oFSO As Object, oFile As Object
strCRLF = StrConv(vbCrLf, vbUnicode)
strSpecialchars = StrConv(Cells(1, 1), vbUnicode)
strFilename = "C:\" & Cells(1, 1).Value & ".txt"
Set oFSO = CreateObject("Scripting.FileSystemObject")
Set oFile = oFSO.CreateTextFile(strFilename, , True)
oFile.Write strSpecialchars & strCRLF
oFile.Close
Set oFile = Nothing
Set oFSO = Nothing
End Sub
Related
I have error while trying to give printout action, if any solution provided will be helpful...
Dim objTextStream
Dim fso As Object
Set fso = CreateObject("Scripting.FileSystemObject")
Set objTextStream = fso.opentextfile("E:\NewFile.prn")`
objTextStream.PrintOut(1)
// not executing
Debug error
Object doesn't support this property or method
There is no print method in a TextStream object (this is what opentextfile returns).
To print a file, you have to use a Windows API function apiShellExecute.
This is copied from http://www.dbforums.com/showthread.php?1066955-How-do-i-print-a-txt-file-with-VBA (but untested):
Place the following in the declaration section of a module (at the top) if you place it in a standard module, change Private to Public:
Private Declare Function apiShellExecute Lib "shell32.dll" _
Alias "ShellExecuteA" _
(ByVal hwnd As Long, _
ByVal lpOperation As String, _
ByVal lpFile As String, _
ByVal lpParameters As String, _
ByVal lpDirectory As String, _
ByVal nShowCmd As Long) _
As Long
Then in the routine where you need to print, call it with:
lngReturn = apiShellExecute(hWndAccessApp, "print", strFileAndPath, vbNullString, vbNullString, 0)
where strFileAndPath is your filename (eg "E:\NewFile.prn")
You can loop through each line of the read file like this. You can then either debug.print that line or write it to a separate write file as shown.
Const ForReading = 1, ForWriting = 2
Dim FSO, FileIn, FileOut As Object
Dim strTmp As String
Set FSO = CreateObject("Scripting.FileSystemObject")
Set FileIn = FSO.OpenTextFile("E:\NewFile.prn", ForReading)
Set FileOut = FSO.OpenTextFile("E:\NewWriteFile.prn", ForWriting, True)
Do Until FileIn.AtEndOfStream
strTmp = FileIn.ReadLine
FileOut.WriteLine strTmp
Loop
FileIn.Close
FileOut.Close
I'm trying to write a code to download a very large file that, depending on bandwidth, may take 30 minutes to download. I have a very basic script now, that typically terminates before the file is completely downloaded. Is there a way to use readystate, or something similar, to make VBA allow the entire file to download before moving on?
Here's the code:
Sub Download()
Dim strURL As String
Dim strPath As String
'~~> URL of the Path
strURL = "http://www.aeronav.faa.gov/upload_313-/terminal/DDTPPE_201612.zip"
'~~> Destination for the file
strPath = "c:\Users\username\Desktop\WebTest\database.zip"
Ret = URLDownloadToFile(0, strURL, strPath, 0, 0)
End Sub
Thanks!
You can put your URL in a cell and run the script below.
Private Declare Function URLDownloadToFile Lib "urlmon" Alias _
"URLDownloadToFileA" (ByVal pCaller As Long, ByVal szURL As String, ByVal _
szFileName As String, ByVal dwReserved As Long, ByVal lpfnCB As Long) As Long
Sub DownloadFilefromWeb()
Dim strSavePath As String
Dim URL As String, ext As String
Dim buf, ret As Long
URL = Worksheets("Sheet1").Range("A2").Value
buf = Split(URL, ".")
ext = buf(UBound(buf))
strSavePath = "C:\Users\your_path_here\" & "DownloadedFile." & ext
ret = URLDownloadToFile(0, URL, strSavePath, 0, 0)
If ret = 0 Then
MsgBox "Download has been succeed!"
Else
MsgBox "Error"
End If
End Sub
That's if you want to loop through a range with many URLs. If you want to download just one, try it this way.
Declare Function URLDownloadToFileA Lib "urlmon" _
(ByVal pCaller As Long, _
ByVal szURL As String, _
ByVal szFileName As String, _
ByVal dwReserved As Long, _
ByVal lpfnCB As Long) As Long
Sub ExampleDownload()
Dim IExpl As Object
Set IExpl = CreateObject("InternetExplorer.Application")
With IExpl
.Navigate "http://www.bom.mu/?id=80277" 'You need to change this for a variable and loop
Do Until .Readystate = 4: Loop ' Allow page to load
'Code below to find correct href link in page based on text
For Each lnk In IExpl.Application.Document.Links
If lnk.outertext = "Click Here to Open or Right Click to Download." Then Exit For
Debug.Print lnk.outertext
Next
End With
SuccessfulDownload = URLDownloadToFileA(0, lnk.href, "C:\myfilename.zip", 0, 0)
Set IExpl = Nothing
End Sub
Or, try R, which is blazing fast!! In order to get your data to download and uncompress, you need to set mode="wb"
download.file("...",temp, mode="wb")
unzip(temp, "gbr_Country_en_csv_v2.csv")
dd <- read.table("gbr_Country_en_csv_v2.csv", sep=",",skip=2, header=T)
Then, simply read the CSV from your Excel tool.
I want to create a file using VBA, but have the following three requirements.
The file contents are unicode
The filename is unicode
I want to append to an existing file (if the file exists, otherwise to create it)
I present here two extracts of code. The first extract will do 1 and 2. The second extract will do 1 and 3. However I can't figure out how to do 1, 2 and 3.
I can use the following code from GSerg's answer in How can I create text files with special characters in their filenames to create a unicode filename with unicode contents
Private Declare Function CreateFileW Lib "kernel32.dll" (ByVal lpFileName As Long, ByVal dwDesiredAccess As Long, ByVal dwShareMode As Long, ByRef lpSecurityAttributes As Any, ByVal dwCreationDisposition As Long, ByVal dwFlagsAndAttributes As Long, ByVal hTemplateFile As Long) As Long
Private Declare Function CloseHandle Lib "kernel32.dll" (ByVal hObject As Long) As Long
Private Declare Function WriteFile Lib "kernel32.dll" (ByVal hFile As Long, ByRef lpBuffer As Any, ByVal nNumberOfBytesToWrite As Long, ByRef lpNumberOfBytesWritten As Long, ByRef lpOverlapped As Any) As Long
Private Const CREATE_ALWAYS As Long = 2
Private Const OPEN_ALWAYS As Long = 4
Private Const GENERIC_WRITE As Long = &H40000000
Sub writeLine(aFile As String, val As String)
Dim hFile As Long
hFile = CreateFileW(StrPtr(aFile), GENERIC_WRITE, 0, ByVal 0&, OPEN_ALWAYS, 0, 0)
WriteFile hFile, &HFEFF, 2, 0, ByVal 0& 'Unicode byte order mark (not required, but to please Notepad)
WriteFile hFile, ByVal StrPtr(val), Len(val) * 2, 0, ByVal 0&
CloseHandle hFile
End Sub
I can use the following code to append to a non-unicode fillename with unicode contents
Sub AddToFile(ByVal aFile As String, ByVal aLine As String)
Dim myFSO2 As New Scripting.FileSystemObject
Dim ts2 As TextStream
Set ts2 = myFSO2.OpenTextFile(aFile, ForAppending, True, TristateTrue)
ts2.Write aLine & vbNewLine
ts2.Close
End Sub
How can I adapt either extract of code (or do something else) to append to a Unicode filename with Unicode contents?
(I read about using SetFilePointer with regard to the first extract of code but I couldn't get it to work)
UPDATE
The problem probably lies with the population of the aFile variable. (Thanks #ChipsLetten) I populate it as follows
Set WordApp = CreateObject("Word.Application")
WordApp.Visible = True
Set WordDoc = WordApp.Documents.Open(fileToOpen)
For Each para In WordDoc.Paragraphs
If para.Style.NameLocal = "Style1" Then
aFile= para.Range.Text
I'm currently in the process of upscaling an Excel solution to a web solution. In this process, I need to upload the existing data into the new (SQL Server) database.
Problem is, that I also need to upload the images that are stored in the Excel file (as shapes). In the database, they will be stored as bytearray in PNG format.
What is the best way to retrieve the source of any embedded image?
I'm currently thinking of either using ws.Shapes("img_1").CopyPicture and some API functions to retrieve it - but so far, got stuck in figuring out the proper API functions. Also, not sure if there isn't an easier/more elegant way...
If you don't mind getting all images as files in your disk and afterwards uploading those to your database, you could just save the Excel workbook or worksheet as "Web Page".
That will create a html file and a directory filled with whatever images (one PNG file per image) the original Excel file has.
Okay, finally found a solution. Not sure this is the most elegant version - and right now it requires IrfanView or another converter - but it does the job. Can be called with fctStrConvertImageToString(Sheets("YourSheet").Shapes("YorImage")) and will return the PBG of this image as string:
Option Explicit
Private Type GUID
Data1 As Long
Data2 As Integer
Data3 As Integer
Data4(8) As Byte
End Type
Private Type PICTDESC
cbSize As Long
picType As Long
hImage As Long
End Type
Private Declare Function OpenClipboard& Lib "user32" (ByVal hwnd&)
Private Declare Function EmptyClipboard& Lib "user32" ()
Private Declare Function GetClipboardData& Lib "user32" (ByVal wFormat%)
Private Declare Function SetClipboardData& Lib "user32" (ByVal wFormat&, ByVal hMem&)
Private Declare Function CloseClipboard& Lib "user32" ()
Private Declare Function CopyImage& Lib "user32" (ByVal handle&, ByVal un1&, ByVal n1&, ByVal n2&, ByVal un2&)
Private Declare Function IIDFromString Lib "ole32" (ByVal lpsz As String, ByRef lpiid As GUID) As Long
Private Declare Function OleCreatePictureIndirect Lib "olepro32" (pPictDesc As PICTDESC, ByRef riid As GUID, ByVal fOwn As Long, ByRef ppvObj As IPicture) As Long
Public Function fctStrConvertImageToString(shp As Shape) As String
Const cStrPath As String = "C:\Temp\"
Const cStrFileName As String = "temp"
Const cStrSourceExtension As String = "bmp"
Const cStrTargetExtension As String = "png"
Dim strSource As String, strTarget As String
If shp.Type <> msoPicture Then Exit Function
shp.CopyPicture 1, xlBitmap
strSource = cStrPath & cStrFileName & "." & cStrSourceExtension
strTarget = cStrPath & cStrFileName & "." & cStrTargetExtension
subSavePicAsBitmap strSource
subConvertFile strSource, strTarget
fctStrConvertImageToString = fctStrReadFile(strTarget)
Kill strSource
Kill strTarget
End Function
Private Sub subSavePicAsBitmap(strFile As String)
Const cStrPictureIID = "{7BF80981-BF32-101A-8BBB-00AA00300CAB}"
Dim hCopy&: OpenClipboard 0&
Dim iPic As IPicture
Dim tIID As GUID
Dim tPICTDEST As PICTDESC
Dim lngReturn As Long
hCopy = CopyImage(GetClipboardData(2), 0, 0, 0, &H4)
CloseClipboard
If hCopy = 0 Then Exit Sub
lngReturn = IIDFromString(StrConv(cStrPictureIID, vbUnicode), tIID)
If lngReturn Then Exit Sub
With tPICTDEST
.cbSize = Len(tPICTDEST)
.picType = 1
.hImage = hCopy
End With
lngReturn = OleCreatePictureIndirect(tPICTDEST, tIID, 1, iPic)
SavePicture iPic, strFile
End Sub
Private Sub subConvertFile(strSource As String, strTarget As String)
Const cStrConverter = """c:\Program Files (x86)\IrfanView\i_view32.exe"""
Shell cStrConverter & " " & strSource & " /convert=" & strTarget, 0
End Sub
Private Function fctStrReadFile(strFile As String)
Dim hFile As Long
hFile = FreeFile
Open strFile For Binary Access Read As #hFile
fctStrReadFile = Input$(LOF(hFile), hFile)
Close #hFile
End Function
I have a path in short version or in DOS format ("C:/DOCUME~1" e.g) and want to get the full path/long path of it ("C:/Documents And Settings" e.g).
I tried GetLongPathName api. It WORKED. But when deal with unicode filename it turns out failure.
Private Declare Function GetLongPathName Lib "kernel32" Alias _
"GetLongPathNameA" (ByVal lpszShortPath As String, _
ByVal lpszLongPath As String, ByVal cchBuffer As Long) As Long
I tried to alias GetLongPathNameW instead but it seems do nothing, for BOTH Unicode and non-Unicode filename, always return 0. In MSDN there's only article about GetLongPathNameW for C/C++, not any for VB/VBA. May I do something wrong?
Is there any solution for this case? I spend hours on Google and StackOverflow but can't find out.
Regards,
Does this work for you? I've converted the file path to short path name then converted it back again which gives the correct string even when unicode (eg C:/Tö+)
Private Declare Function GetShortPathName Lib "kernel32" Alias "GetShortPathNameA" _
(ByVal lpszLongPath As String, ByVal lpszShortPath As String, ByVal lBuffer As Long) As Long
Private Declare Function GetLongPathName Lib "kernel32" Alias "GetLongPathNameA" _
(ByVal lpszShortPath As String, ByVal lpszLongPath As String, ByVal cchBuffer As Long) As Long
Public Function GetShortPath(ByVal strFileName As String) As String
'KPD-Team 1999
'URL: [url]http://www.allapi.net/[/url]
'E-Mail: [email]KPDTeam#Allapi.net[/email]
Dim lngRes As Long, strPath As String
'Create a buffer
strPath = String$(165, 0)
'retrieve the short pathname
lngRes = GetShortPathName(strFileName, strPath, 164)
'remove all unnecessary chr$(0)'s
GetShortPath = Left$(strPath, lngRes)
End Function
Public Function GetLongPath(ByVal strFileName As String) As String
Dim lngRes As Long, strPath As String
'Create a buffer
strPath = String$(165, 0)
'retrieve the long pathname
lngRes = GetLongPathName(strFileName, strPath, 164)
'remove all unnecessary chr$(0)'s
GetLongPath = Left$(strPath, lngRes)
End Function
Private Sub Test()
shortpath = GetShortPath("C:/Documents And Settings")
Longpath = GetLongPath(shortpath)
End Sub
To use W-functions from vb6/vba, you declare all string parameters as long:
Private Declare Function GetLongPathName Lib "kernel32" Alias "GetLongPathNameW" _
(ByVal lpszShortPath As Long, _
ByVal lpszLongPath As Long, _
ByVal cchBuffer As Long) As Long
and pass StrPtr(a_string) instead of just a_string.
So if you had:
dim s_path as string
dim l_path as string
s_path = "C:\DOCUME~1"
l_path = string$(1024, vbnullchar)
GetLongPathNameA s_path, l_path, len(l_path)
it would become
dim s_path as string
dim l_path as string
s_path = "C:\DOCUME~1"
l_path = string$(1024, vbnullchar)
GetLongPathNameW strptr(s_path), strptr(l_path), len(l_path)