Egy szöveges állomány – például .txt vagy .csv kiterjesztésű fájl – beimportálása az Excelbe makró nélkül sem egy bonyolult mutatvány. Ha rendszeresen azonos struktúrájú szövegfájlokat kell beimportálnunk, akkor egy rövid programkóddal egyszerűen automatizálhatjuk ezt a tevékenységet. Mindez addig igaz, amíg a szövegfájl struktúrája megegyezik azzal a struktúrával, amit az Excelben látni szeretnénk. Ám ha a szövegfájl nincs jól strukturálva, csak időigényes manuális munkával tudjuk az Excel táblánkat a kívánt formára hozni – itt jön be a makró a képbe, amivel ezt a manuális munkát néhány tizedmásodperces (nagyméretű állományok esetén néhány másodperces) gépi futásidőre cserélhetjük.
Lássunk egy példát egy Excel szempontból nem ideálisan strukturált szöveges állományra:
Ügyviteli szoftverek export állományaként találkozhatunk hasonlóval. A példa kedvéért legyen ez egy .txt kiterjesztésű fájl, amiben a mezők tabbal vannak elválasztva. Láthatjuk, hogy egy-egy rendelés adatai több és változó számú sort foglalnak el. A rekord első sorában található egy rendelésszám, egy fizetési mód, egy rendelési időpont, a második sorában a vezetéknév, keresztnév, email cím, telefonszám és a város neve, ezután jön tetszőleges számú sorban a tételek felsorolása: darabszám, egységár és cikkszám, majd a rekordot egy összegző sor zárja a megrendelésszámmal és a fizetendő összeggel.
Ha ezt a fájlt egyszerűen beimportáljuk az Excelbe, akkor – nem meglepő módon – ezt kapjuk:
Ez a későbbi feldolgozás szempontjából – például szűrések, pivot táblák, stb. – nem a legideálisabb. Inkább valami ilyesmire lenne szükségünk:
A továbbiakban két módszert fogok bemutatni a fenti rendezett tábla előállítására: elsőként megnézzük, hogy az Excel beépített szövegimportálója hogyan vezérelhető, illetve hogyan lehet utána a mezőket rendezni; másodikként pedig megnézünk egy alternatív – nagy állományoknál sokkal gyorsabban működő – megoldást.
Az Excel beépített megoldása: Text to Columns
Az Excel saját megoldását alkalmazhatjuk már a táblánkban lévő, szöveget tartalmazó cellák újratördelésére, illetve szöveges fájl Excelben történő megnyitására. Az elsőt a Range objektumon alkalmazott TextToColumns metódussal tudjuk elvégezni, a másodikat pedig a Workbooks gyűjteményen alkalmazott OpenText metódussal. A paraméterezés mindkét esetben ugyanaz – ahogyan menüből manuálisan indítva mindkét módszerrel ugyanahhoz a párbeszédablakhoz jutunk. Példánkban a Workbooks.OpenText metódussal fogjuk a szövegfájlunkat megnyitni.
Ismét kezdjük az elmélettel, és lássuk a legfontosabb paramétereket:
- Filename – kötelező paraméter, a megnyitandó szövegfájl nevét és elérési útvonalát kell itt megadni.
- DataType – ezzel a paraméterrel meghatározhatjuk, hogy valamilyen karakter választja el a cellákat (xlDelimited) vagy fix karakterszám szélességű oszlopaink vannak (xlFixedWidth)
- Tab / Semicolon / Comma / Space / Other – amennyiben a DataType-nál xlDelimited-et adtunk meg, ezekkel a paraméterekkel definiálhatjuk, hogy mi(k) legyen(ek) az elválasztó karakter(ek): tab, pontosvessző, vessző, szóköz, vagy egyéb karakter; bármelyik paramétert állíthatjuk True vagy False-ra. Az Other paraméter kiválasztása esetén az OtherChar paraméterben adhatjuk meg, hogy mi legyen az a bizonyos karakter.
- FieldInfo – ennek a paraméternek az értelmezése attól függ, hogy mit adtunk meg a DataType paraméternél. Mindkét esetben kételemű tömbökből álló tömböt kell itt megadni (bonyolultan hangzik elsőre…).
- FixedWidth esetén a kételemű tömb első eleme az oszlop kezdő karakterpozíciója (nullától indítva a számozást), a második eleme pedig az oszlop adattípusa (1: általános, 2: szöveges, vagy 3-10: valamilyen dátum). Például egy, a 10. karakternél kezdődő szöveges mezőt így adunk meg: Array(10,2). Az egyes oszlopokat szintén egy tömb formájában kell megadni, ezért beszélünk tömbökből álló tömbről. Például egy három oszlopos táblát így kell definiálni:
Array(Array(0,2),Array(9,2),Array(20,2)). - Delimited esetén a kételemű tömb első eleme az oszlop sorszáma, a második eleme itt is az oszlop adattípusa. Amelyik oszlopot nem definiáljuk, azt az Excel általános adattípusként fogja értelmezni. Például, ha 5 oszlopból az első és harmadik oszlopot szövegként szeretnénk értelmeztetni, a többit pedig általános típusúként, azt így adhatjuk meg:
Array(Array(1,2),Array(3,2)), de akár ki is írhatjuk:
Array(Array(1,2),Array(2,1), Array(3,2),Array(4,1),Array(5,1)).
- FixedWidth esetén a kételemű tömb első eleme az oszlop kezdő karakterpozíciója (nullától indítva a számozást), a második eleme pedig az oszlop adattípusa (1: általános, 2: szöveges, vagy 3-10: valamilyen dátum). Például egy, a 10. karakternél kezdődő szöveges mezőt így adunk meg: Array(10,2). Az egyes oszlopokat szintén egy tömb formájában kell megadni, ezért beszélünk tömbökből álló tömbről. Például egy három oszlopos táblát így kell definiálni:
- DecimalSeparator – ezzel megadhatjuk, hogy egy számként értelmezendő mezőnél mi a tizedes elválasztó karakter.
- ThousandsSeparator – ezzel megadhatjuk, hogy egy számként értelmezendő mezőnél mi az ezres elválasztó karakter.
Térjünk vissza a konkrét példánkra, és nézzük meg, hogy a fentiek hogy is mutatnak a gyakorlatban. Először is deklaráljuk a globális változókat, majd a korábbi bejegyzésben megismert módszerrel kérjük be a megnyitandó fájlt:
Option Explicit Dim cancel As Boolean Dim TxtFajlNev As String Sub Fajl_Betoltes() TxtFajlNev = Application.GetOpenFilename("Text Files (*.txt), *.txt") If TxtFajlNev = "False" Then cancel = True Exit Sub End If End Sub
A fájl elérési útvonalát az előző eljárásban eltároltuk (TxtFajlNev), nyissuk meg a fent ismertetett metódussal. A szövegfájlunkban a mezők tabbal vannak elválasztva, és 6 szöveges oszlopot szeretnénk kapni a művelet eredményeképp:
Sub Text_To_Columns() Workbooks.OpenText _ Filename:=TxtFajlNev, _ DataType:=xlDelimited, _ Tab:=True, _ Semicolon:=False, _ Comma:=False, _ Space:=False, _ Other:=False, _ FieldInfo:= _ Array(Array(1, 2), Array(2, 2), Array(3, 2), _ Array(4, 2), Array(5, 2), Array(6, 2)) End Sub
A fenti eljárás eredményeképp megnyílt egy új munkafüzet, amiben még mindig strukturálatlanul szerepelnek az adatok, pont úgy, mint a bejegyzés elején látható képernyőmentésen. Azért, hogy az adatok a kívánt struktúrában, és abban a munkafüzetben jelenjenek meg, amiből a makrókat futtatjuk, egy kis adatrendező eljárásra lesz szükségünk.
Ebben egymásba ágyazott ciklusokkal végigmegyünk az egyes megrendeléseken, illetve a megrendelések egyes tételein, és az adatokat – már a kívánt struktúrában – az Excel táblánk megfelelő cellájába másoljuk. Az algoritmushoz felhasználjuk, hogy:
- az új megrendelések az A oszlopban kezdődnek, míg a megrendeléshez tartozó adatok és tételek a B oszlopban,
- a megrendelésszámok közötti sorokban az A oszlopban egy szóköz szerepel a Text To Columns tördelés eredményeképp (és nem üres cella),
- az új megrendeléssor előtti sor mindig egy összegző sor, amire a strukturált táblában nincs szükségünk.
Első lépésként állítsunk össze egy fejlécet (feltételezve, hogy a táblánk még nem tartalmazza azt). Erre számtalan megoldás lehetséges, a példánkban erre egy gyűjteményt fogunk használni:
Sub FejlecKeszites() '********** Deklarációk *************** Dim FejlecAs Collection Dim i As Integer '************************************** ' Fejléc elemeinek betöltése gyűjteménybe Set Fejlec = New Collection With Fejlec .Add "Megrendelésszám" .Add "Fizetési mód" .Add "Megrendelés időpontja" .Add "Vezetéknév" .Add "Keresztnév" .Add "Email" .Add "Telefonszám" .Add "Város" .Add "Darabszám" .Add "Egységár" .Add "Cikkszám" End With ' Fejléc kiírása és formázása ThisWorkbook.Sheets(1).Activate For i = 1 To Fejlec.Count With Cells(1, i) .Value = Fejlec(i) With .Font .Bold = True .ColorIndex = 2 End With With .Interior .ColorIndex = 11 .Pattern = xlSolid End With End With Next i End Sub
Ezután jöhet maga a rendező eljárás:
Sub Rendezes() '********** Deklarációk *************** Dim Megrendelesszam As String Dim Fizetesi_mod As String Dim Megrendeles_idopontja As String Dim Vezeteknev As String Dim Keresztnev As String Dim Email As String Dim Telefonszam As String Dim Varos As String Dim Darabszam As Integer Dim Egysegar As Single Dim Cikkszam As String Dim MegrendelesSor As Long Dim TetelSor As Long Dim TablaSor As Long '************************************** MegrendelesSor = 1 TablaSor = 2 'Megrendelések ciklusa Do Workbooks(Workbooks.Count).Activate 'Megrendelés adatainak elmentése változókba Megrendelesszam = Cells(MegrendelesSor, 1) Fizetesi_mod = Cells(MegrendelesSor, 2) Megrendeles_idopontja = Cells(MegrendelesSor, 3) Vezeteknev = Cells(MegrendelesSor + 1, 2) Keresztnev = Cells(MegrendelesSor + 1, 3) Email = Cells(MegrendelesSor + 1, 4) Telefonszam = Cells(MegrendelesSor + 1, 5) Varos = Cells(MegrendelesSor + 1, 6) TetelSor = MegrendelesSor + 2 'Megrendelés tételek ciklusa Do Darabszam = Cells(TetelSor, 2) Egysegar = Cells(TetelSor, 3) Cikkszam = Cells(TetelSor, 4) 'rekord kiírása excelbe + formázás ThisWorkbook.Sheets(1).Activate With Cells(TablaSor, 1) .NumberFormat = "@" .Value = Megrendelesszam End With With Cells(TablaSor, 2) .NumberFormat = "@" .Value = Fizetesi_mod End With With Cells(TablaSor, 3) .NumberFormat = "yyyy.mm.dd hh:mm:ss" .Value = Megrendeles_idopontja End With With Cells(TablaSor, 4) .NumberFormat = "@" .Value = Vezeteknev End With With Cells(TablaSor, 5) .NumberFormat = "@" .Value = Keresztnev End With With Cells(TablaSor, 6) .NumberFormat = "@" .Value = Email End With With Cells(TablaSor, 7) .NumberFormat = "@" .Value = Telefonszam End With With Cells(TablaSor, 8) .NumberFormat = "@" .Value = Varos End With With Cells(TablaSor, 9) .NumberFormat = "#,##0" .Value = Darabszam End With With Cells(TablaSor, 10) .NumberFormat = "#,##0" .Value = Egysegar End With With Cells(TablaSor, 11) .NumberFormat = "@" .Value = Cikkszam End With 'számlálók léptetése TetelSor = TetelSor + 1 TablaSor = TablaSor + 1 'ellenőrizzük, hogy a tábla végére értünk-e Workbooks(Workbooks.Count).Activate If TetelSor = Cells(1, 1).CurrentRegion.Rows.Count Then Exit Do Loop While Cells(TetelSor + 1, 1) = " " 'számláló léptetése MegrendelesSor = TetelSor + 1 Loop While MegrendelesSor <= Cells(1, 1).CurrentRegion.Rows.Count End Sub
Már csak egy rövid vezérlő eljárás hiányzik, ami a fenti eljárásokat összefogja, valamint bezárja a nyers adatokat tartalmazó munkafüzetet, mivel arra már nincs szükségünk:
Sub Vezerles() Application.ScreenUpdating = False FejlecKeszites Fajl_Betoltes Text_To_Columns If cancel = True Then Exit Sub Rendezes 'Temp fájl bezárása Workbooks(Workbooks.Count).Close 'oszlopok szélességének beállítása ThisWorkbook.Sheets(1).Activate Range("A:K").EntireColumn.AutoFit Range("A1").Select Application.ScreenUpdating = True End Sub
A szövegfájl közvetlen elérése és tömbök használata
Az alternatív megoldásunkban nem Excelbe importáljuk a szövegfájlt és nem munkafüzetek között ugrálva végezzük a rendezést, hanem a szövegfájlt közvetlenül egy tömb változóba olvassuk be, a rendezést is a tömbben végezzük el, és csak a végeredményt írjuk ki Excelbe. Ezzel a megoldással nagyméretű adatállományok esetében jelentősen felgyorsíthatjuk a műveletet az előző megoldáshoz képest.
A szövegfájlból való közvetlen adatkinyerésre az Open utasítást használjuk, méghozzá Input módban. Ez esetben az utasítás így néz ki: Open [fájl neve] For Input As #[fájl száma]. (A szövegfájl megnyitása ilyenkor csak memóriában történik meg, ténylegesen nem jelenik meg.) A fájl megnyitása után egy ciklussal végigmegyünk a szövegfájl sorain, a Split függvénnyel mezőkre daraboljuk, majd elmentjük egy tömbbe.
Option Explicit Option Base 1 Dim cancel As Boolean Dim TxtFajlNev As String Dim TxtArr As Variant Dim TxtArrSor As Long Dim TxtArrOszl As Long Sub Txt_Tombbe() '********** Deklarációk *************** Dim fNum As Integer Dim TxtSor As String Dim tmpArr As Variant Dim Mezo As Variant '************************************** TxtArrSor = 0 fNum = FreeFile ReDim TxtArr(Txt_MaxSor(TxtFajlNev), 6) Open TxtFajlNev For Input As #fNum Do While Not EOF(fNum) TxtArrSor = TxtArrSor + 1 Line Input #fNum, TxtSor tmpArr = Split(TxtSor, vbTab) TxtArrOszl = 1 For Each Mezo In tmpArr TxtArr(TxtArrSor, TxtArrOszl) = Mezo TxtArrOszl = TxtArrOszl + 1 Next Mezo Loop Close #fNum End Sub
Kiegészítések:
Az Open utasítás használatakor az eljáráson belül a megnyitott fájlra a számával hivatkozunk. Most nincs jelentősége hogy konkrétan mi az a szám, ezért a fNum = FreeFile utasítással az fNum változóhoz hozzárendeljük a következő szabad fájlszámot.
A fájl elérési útvonalát az előző megoldással megegyező módon kérjük be a felhasználótól.
Tulajdonképpen két tömböt használunk: az egyiket (tmpArr) átmeneti jelleggel, csak 1-1 sor eltárolására, majd azt mindig átmásoljuk a másik tömb (TxtArr) megfelelő sorába. A példánkban a teljes táblát tartalmazó tömb dimenzióit előre definiáljuk: azt tudjuk hogy mindig 6 oszlopos; azt hogy hány soros, pedig egy külön függvénnyel (Txt_MaxSor) állapítjuk meg (lásd később).
A Do… Loop ciklusunkban az EOF (mint End Of File) függvénnyel utasítjuk az eljárást, hogy a fájl végéig minden egyes soron menjen végig és végezze el a cikluson belül írt műveleteket.
A Line Input utasítás a paraméterként megadott fájl (fNum) aktuális sorát a paraméterként megadott String típusú változóba (TxtSor) menti.
A Split függvénnyel az adott sort mezőkre daraboljuk: a függvény paraméterként itt is megadható, hogy a tabot vegye elválasztóként (vbTab).
A következő For… Next ciklussal az átmeneti tömbből átmásoljuk a mezőket a teljes táblát tartalmazó tömbbe.
Végül a Close utasítással bezárjuk a paraméterként megadott szövegfájlt.
Egy kis kitérő : Szövegfájl sorainak megszámlálása
Korábban említésre került, hogy a tömb méretének meghatározásához egy külön függvénnyel számoljuk meg, hogy hány soros a szövegfájlunk. Sajnos szövegfájl esetében nem működnek az Excelben használatos tulajdonságok és metódusok, mint a Row és a Count, ezért egy kis trükkhöz kell folyamodnunk. Kézenfekvő megoldás lenne, hogy a fenti eljárásban használt Do While Not EOF… Loop ciklust használjuk fel arra, hogy egyenként megszámoljuk a fájl sorait – ez azonban hosszú fájlok esetében lassú lehet. Ehelyett a szövegben található sortöréseket számoljuk meg. Ehhez bináris módban nyitjuk meg a szövegfájlt olvasásra (Open [fájl neve] For Binary Access Read As #[fájl száma]), majd megszámoljuk a sortörést reprezentáló “Lf” (Line Feed) karaktereket. A függvény elsőre bonyolultnak tűnik, de kitűnően teszi a dolgát: egy több százezer soros fájl sorait is pillanatok alatt képes megszámolni. További magyarázatok nélkül álljon itt a függvény:
Function Txt_MaxSor(FajlNev As String) As Long '********** Deklarációk *************** Const BufferMeret As Long = 100000 Dim LfAnsi As String Dim fNum As Integer Dim FileMeretByte As Long Dim MaradekByte As Long Dim Buffer() As Byte Dim strBuffer As String Dim BufferPozicio As Long '************************************** LfAnsi = StrConv(vbLf, vbFromUnicode) fNum = FreeFile Open FajlNev For Binary Access Read As fNum FileMeretByte = LOF(fNum) ReDim Buffer(BufferMeret - 1) MaradekByte = FileMeretByte Do Until MaradekByte = 0 If BufferPozicio = 0 Then If MaradekByte < BufferMeret Then ReDim Buffer(MaradekByte) Get #fNum, , Buffer strBuffer = Buffer MaradekByte = MaradekByte - LenB(strBuffer) BufferPozicio = 1 End If Do Until BufferPozicio = 0 BufferPozicio = InStrB(BufferPozicio, strBuffer, LfAnsi) If BufferPozicio > 0 Then Txt_MaxSor = Txt_MaxSor + 1 BufferPozicio = BufferPozicio + 1 End If Loop Loop Close #fNum End Function
A fenti eljárások eredményeképp a szövegfájlunk mezői egy tömbben vannak – egyelőre még nem a kívánt struktúrában. A rendezést az első megoldásban ismertetett módon tudjuk most is megejteni, annyi különbséggel, hogy nem egy munkalap celláit címezzük meg, hanem egy tömb elemeit.
Sub Rendezes() '********** Deklarációk *************** Dim Megrendelesszam As String Dim Fizetesi_mod As String Dim Megrendeles_idopontja As String Dim Vezeteknev As String Dim Keresztnev As String Dim Email As String Dim Telefonszam As String Dim Varos As String Dim Darabszam As Integer Dim Egysegar As Single Dim Cikkszam As String Dim MegrendelesSor As Long Dim TetelSor As Long Dim TablaSor As Long '************************************** MegrendelesSor = 1 TablaSor = 2 Do 'Megrendelés adatainak elmentése változókba Megrendelesszam = TxtArr(MegrendelesSor, 1) Fizetesi_mod = TxtArr(MegrendelesSor, 2) Megrendeles_idopontja = TxtArr(MegrendelesSor, 3) Vezeteknev = TxtArr(MegrendelesSor + 1, 2) Keresztnev = TxtArr(MegrendelesSor + 1, 3) Email = TxtArr(MegrendelesSor + 1, 4) Telefonszam = TxtArr(MegrendelesSor + 1, 5) Varos = TxtArr(MegrendelesSor + 1, 6) TetelSor = MegrendelesSor + 2 Do Darabszam = TxtArr(TetelSor, 2) Egysegar = TxtArr(TetelSor, 3) Cikkszam = TxtArr(TetelSor, 4) 'rekord kiírása excelbe + formázás With Cells(TablaSor, 1) .NumberFormat = "@" .Value = Megrendelesszam End With With Cells(TablaSor, 2) .NumberFormat = "@" .Value = Fizetesi_mod End With With Cells(TablaSor, 3) .NumberFormat = "yyyy.mm.dd hh:mm:ss" .Value = Megrendeles_idopontja End With With Cells(TablaSor, 4) .NumberFormat = "@" .Value = Vezeteknev End With With Cells(TablaSor, 5) .NumberFormat = "@" .Value = Keresztnev End With With Cells(TablaSor, 6) .NumberFormat = "@" .Value = Email End With With Cells(TablaSor, 7) .NumberFormat = "@" .Value = Telefonszam End With With Cells(TablaSor, 8) .NumberFormat = "@" .Value = Varos End With With Cells(TablaSor, 9) .NumberFormat = "#,##0" .Value = Darabszam End With With Cells(TablaSor, 10) .NumberFormat = "#,##0" .Value = Egysegar End With With Cells(TablaSor, 11) .NumberFormat = "@" .Value = Cikkszam End With 'számlálók léptetése TetelSor = TetelSor + 1 TablaSor = TablaSor + 1 'Ellenőrizzük, hogy a tábla végére értünk-e If TetelSor = UBound(TxtArr) Then Exit Do Loop While TxtArr(TetelSor + 1, 1) = " " 'számláló léptetése MegrendelesSor = TetelSor + 1 Loop While MegrendelesSor <= UBound(TxtArr) End Sub
Az itt nem részletezett lépések (fájl elérési útvonalának bekérése, fejléc készítése, vezérlő eljárás) a második megoldás esetében is ugyanúgy történik, mint az elsőben.