Import af csv-filer til Excel med VBA makroer
Det er blevet meget almindeligt, at man kan downloade data som en csv-fil. Det kan fx være fra din netbank eller en tjeneste som Google Analytics. Csv-filer er simpelthen tekstfiler, hvor værdierne er adskilt af et separatortegn.
Det er nemt at åbne en csv-fil i Excel, man dobbeltklikker bare på ikonet. Det er også lige ud ad landevejen at åbne dem med VBA, lige undtaget hvis separatortegnet er semikolon - så kan det være tricky.
Det hænger sammen med, at "csv" står for "comma separated values", og at VBA "tænker" på amerikansk og ikke automatisk bruger de lokale indstillinger. Hvis vi, der bruger komma som decimalseparator, også brugte komma til at adskille værdierne, ville det blive noget rod.
I det følgende viser jeg eksempler på, hvordan man kan importere csv-filer med VBA, og der er også et eksempel på, hvordan man kan parse ("læse") filen med kode i stedet for at bruge Excels indbyggede import.
Makrooptageren fejler
Hvis du gemmer et regneark som csv-fil og efterfølgende åbner filen i Notepad, vil du se, at værdierne er nydeligt adskilt med semikolon.
Hvis du nu slår makrooptageren til og åbner filen, går det som smurt, og alt ser godt ud. Den optagede makro ser sådan ud:
Sub Makro1()
Workbooks.Open Filename:="C:\csvtest\semikolon-csv.csv"
End Sub
Men hvis du efterfølgende afspiller makroen, opdager du, at hele 1. række står i celle A1 med værdierne adskilt af semikolon, 2. række i celle A2 og så fremdeles. (Makroen virker fint, hvis separatortegnet fx er tabulator.)
Sagen er, at da du gemte som csv, brugte Excel de lokale indstillinger og brugte semikolon som separator, men VBA bruger IKKE de lokale indstillinger, medmindre man tvinger det igennem, og det gør man ved at tilføje ", Local:= True". Sådan:
Sub Makro1()
Workbooks.Open Filename:= _
"C:\csvtest\semikolon-csv.csv", Local:=True
End Sub
Man kan også bruge OpenText-metoden:
Sub OpenCsv()
Workbooks.OpenText Filename:= _
" C:\csvtest\semikolon-csv.csv ", Local:=True
End Sub
Da det er en csv-fil, behøver man ikke at fortælle, at det er en fil med separatorer, som hvis det var en "almindelig" tekstfil. I eksemplet herunder åbner vi en semikolon-separeret tekstfil, som ligger i samme mappe som regnearket:
Sub SemicolonAdskilt()
Dim sPath As String
sPath = ThisWorkbook.Path & "\semicolonseparated.txt"
Workbooks.OpenText Filename:= _
sPath, DataType:=xlDelimited, Semicolon:=True, Local:=True
End Sub
Normalt er der ikke "lokal-forvirring", når det drejer sig om almindelige tekstfiler, men jeg har oplevet det af og til (fx med datoformatering), og det kan undgås ved at tilføje ", Local:= True".
Makro til at læse en csv- eller txt-fil
Hvis man af en eller anden grund ikke vil benytte Excels indbyggede importfunktioner, kan man parse ("læse") en csv- eller txt-fil med kode.
Der er ingen grund til at opfinde den dybe tallerken mere end én gang, og koden i det følgende har jeg fundet på nettet, og den fungerer glimrende. Jeg har set den flere steder, og jeg ved ikke, hvem forfatteren er, men han skal have tak.
Du kan kopiere koden og sætte den ind i et VBA-modul ved at markere den med musen, trykke CTRL+C og indsætte med CTRL+V. Læser du dette på en lille skærm, kan nogen af kodelinjerne se "knækkede" ud, men linjeskiftene er OK, når du indsætter i VBA-editoren.
Option Explicit
Sub ImportFile()
Dim sPath As String
sPath = ThisWorkbook.Path & "\csvtest.csv"
copyDataFromCsvFileToSheet sPath, ";", "Ark2"
End Sub
'**************************************************************
Private Sub copyDataFromCsvFileToSheet(parFileName As String, _
parDelimiter As String, parSheetName As String)
Dim Data As Variant
Data = getDataFromFile(parFileName, parDelimiter)
If Not isArrayEmpty(Data) Then
With Sheets(parSheetName)
.Cells.ClearContents
.Cells(1, 1).Resize(UBound(Data, 1), UBound(Data, 2)) = Data
End With
End If
End Sub
'**************************************************************
Public Function isArrayEmpty(parArray As Variant) As Boolean
If IsArray(parArray) = False Then isArrayEmpty = True
On Error Resume Next
If UBound(parArray) < LBound(parArray) Then
isArrayEmpty = True
Exit Function
Else
isArrayEmpty = False
End If
End Function
'**************************************************************
Private Function getDataFromFile(parFileName As String, _
parDelimiter As String, _
Optional parExcludeCharacter As String = "") As Variant
Dim locLinesList() As Variant
Dim locData As Variant
Dim i As Long
Dim j As Long
Dim locNumRows As Long
Dim locNumCols As Long
Dim fso As Variant
Dim ts As Variant
Const REDIM_STEP = 10000
Set fso = CreateObject("Scripting.FileSystemObject")
On Error GoTo error_open_file
Set ts = fso.OpenTextFile(parFileName)
On Error GoTo unhandled_error
ReDim locLinesList(1 To 1) As Variant
i = 0
Do While Not ts.AtEndOfStream
If i Mod REDIM_STEP = 0 Then
ReDim Preserve locLinesList _
(1 To UBound(locLinesList, 1) + REDIM_STEP) As Variant
End If
locLinesList(i + 1) = Split(ts.ReadLine, parDelimiter)
j = UBound(locLinesList(i + 1), 1)
If locNumCols < j Then locNumCols = j
i = i + 1
Loop
ts.Close
locNumRows = i
If locNumRows = 0 Then Exit Function
ReDim locData(1 To locNumRows, 1 To locNumCols + 1) As Variant
If parExcludeCharacter <> "" Then
For i = 1 To locNumRows
For j = 0 To UBound(locLinesList(i), 1)
If Left(locLinesList(i)(j), 1) = parExcludeCharacter Then
If Right(locLinesList(i)(j), 1) = parExcludeCharacter Then
locLinesList(i)(j) = _
Mid(locLinesList(i)(j), 2, Len(locLinesList(i)(j)) - 2)
Else
locLinesList(i)(j) = _
Right(locLinesList(i)(j), Len(locLinesList(i)(j)) - 1)
End If
ElseIf Right(locLinesList(i)(j), 1) = parExcludeCharacter Then
locLinesList(i)(j) = _
Left(locLinesList(i)(j), Len(locLinesList(i)(j)) - 1)
End If
locData(i, j + 1) = locLinesList(i)(j)
Next j
Next i
Else
For i = 1 To locNumRows
For j = 0 To UBound(locLinesList(i), 1)
locData(i, j + 1) = locLinesList(i)(j)
Next j
Next i
End If
getDataFromFile = locData
Exit Function
error_open_file:
unhandled_error:
End Function
Sådan. Det kan synes som meget kode for at parse en csv- eller tekstfil, men makroen er i sin helhed meget alsidig og kører faktisk ret hurtigt. Og som jeg skrev tidligere: Hvorfor opfinde den dybe tallerken mere end én gang?
Relateret
|