File I/O - very simple
Monkey Forums/Monkey Beginners/File I/O - very simple
| ||
Hello friends! I need to make a simple code on the I/O file (eg. TXT). I have it like this: Import mojo Import brl Import filesystem Import fileio Global file$ Global data:Int,data2$ Class MyApp Extends App Field data:DataBuffer Method OnCreate() file="monkey://data/text.txt" data=DataBuffer.Load(file) data2=LoadString(file) SetUpdateRate 60 End Method OnUpdate() End Method OnRender() Cls DrawText data2,20,40 DrawText "data.Length="+data.Length,0,12 End End 'main Function Main() New MyApp End But unfortunately, the whole file uploads. I need to be able to first work with the first line, then the second line, and so on. Do you have any advice please? Thank you in advance! P. S. Monkey-X, target PC or HTML5. |
| ||
If you are going to use data buffers you will have to walk the data to look for the new line marker (CR/LF, LF or CR depending on the operating system the file was saved). To do it with strings, you need to split the string into an array with the new line marker (~n in MonkeyX). So make data2$ in to an empty array with data2$[] Then use data2=LoadString(file).Split("~n") You can access any line with data2[n]. Note: When you post code you should use the code and /code or codebox and /codebox tags encased in square brackets e.g [/code] as opening and closing. You should also indent you code to make it more readable like so Import mojo Import brl Import filesystem Import fileio Global file$ Global data:Int,data2$ Class MyApp Extends App Field data:DataBuffer Method OnCreate() file="monkey://data/text.txt" data=DataBuffer.Load(file) data2=LoadString(file) SetUpdateRate 60 End Method OnUpdate() End Method OnRender() Cls DrawText data2,20,40 DrawText "data.Length="+data.Length,0,12 End End 'main Function Main() New MyApp End |
| ||
Wow, super, thank you very much! ;) It works perfectly! Thanks! The final code: Import mojo Import brl Import filesystem Global file$ Global data:Int,data2$[] Class MyApp Extends App Field data:DataBuffer Method OnCreate() file="monkey://data/text.txt" data=DataBuffer.Load(file) data2=LoadString(file).Split("~n") SetUpdateRate 60 End Method OnUpdate() End Method OnRender() Cls DrawText data2[0],20,40 DrawText data2[1],20,60 DrawText data2[2],20,80 DrawText "data.Length="+data.Length,0,12 End End 'main Function Main() New MyApp End |
| ||
Hi zxretrosoft, I made this rather handy class a few years back, you're welcome to use this. ;-) Scroll down further on this comment for of the ways it can be used. Class C_FileLoader Field fileData:String[] Field currentLine:Int Method New(fileName:String) fileData = LoadString(fileName).Split("~n") currentLine = 0 End Method ReadLine:String() Local currentData:String if Eof() = False currentData = Self.fileData[Self.currentLine] currentLine = (currentLine + 1) Else currentData = "Data Exhausted!" EndIf Return currentData End Method Eof:Bool() if Self.currentLine > (fileData.Length() - 1) Return True Else Return False EndIf End Method Purge:Void() Local counter:Int For counter = 0 to (fileData.Length() - 1) fileData[counter] = "" Next fileData =[] End End And heres the way to use it in its simplest form, although you would most probably use it in other ways. Local stream:C_FileLoader = New C_FileLoader("LevelData/testfile.dat") While not stream.Eof() Print(stream.ReadLine()) Wend stream.Purge() |
| ||
Hello Steve, thank you very much! Unfortunately, I do not know how to use this class in my program? Can you please be more vivid? :-) Best whole code. Or use it in my code? P. S. I'am sorry, I bought Monkey-X Studio this week, and I'm acquainted with it. |
| ||
For simple testing with the C++-Tool target (no graphics). You can do it like so:Class C_FileLoader Field fileData:String[] Field currentLine:Int Method New(fileName:String) fileData = LoadString(fileName).Split("~n") currentLine = 0 End Method ReadLine:String() Local currentData:String if Eof() = False currentData = Self.fileData[Self.currentLine] currentLine = (currentLine + 1) Else currentData = "Data Exhausted!" EndIf Return currentData End Method Eof:Bool() if Self.currentLine > (fileData.Length() - 1) Return True Else Return False EndIf End Method Purge:Void() Local counter:Int For counter = 0 to (fileData.Length() - 1) fileData[counter] = "" Next fileData =[] End End Function Main() Local stream:C_FileLoader = New C_FileLoader("LevelData/testfile.dat") While not stream.Eof() Print(stream.ReadLine()) Wend stream.Purge() End Function To include this within your own code you would put the class into a separate file and use Import to include the class, then create a data object either as a local/global variable, or as a field member of one of your own classes, or extend the class to make a new one. |
| ||
Thanks for what you did there dawlane. I was assuming that zxretrosoft was already sort of comfortable with Monkey-X, hence why I didn't include the Function Main bit. ;-) |
| ||
Here's a little somthing to get you going, it will output some strings and floats to the browser's console when compiled to HTML5. Graphics cannot render outside the OnRender loop, hence the text output; but you're probably clever enough to make further sense on how to do this once you get the jist of it. ;-) 1) Create a folder, name it "Project" or somthing similar. 2) Create a file named "LoadParse.monkey", create a floder named "LoadParse.data", and then create a subfolder inside that LoadParse.data and name it AppData. You can name the LoadParse bit any way you like as long as the monkey and data prefixes are present. Next open up LoadParse.Monkey, copy and paste the following code into the editor and then save it. Strict Import mojo Global loader:C_FileLoader Function Main:Int() Local app:C_App = New C_App() Return 0 End Class C_App Extends App Method OnCreate:Int() F_Load("AppData/data01.dat") Return 0 End Method OnUpdate:Int() Return 0 End Method OnRender:Int() Cls Return 0 End End Function F_Load:Void(fileName:String) Local lineData:String If loader loader.Purge() loader = Null EndIf loader = New C_FileLoader(fileName) While not loader.Eof() lineData = loader.ReadLine() F_ParseUserData(lineData) Wend loader.Purge() End Function F_ParseUserData:Void(cmdLine:String) Local tokenList:String[] Local command:String tokenList = F_TokeniseCommandLine(cmdLine) command = tokenList[0] Select command Case "Rect" F_Rect(tokenList) Case "Ellipse" F_Ellipse(tokenList) Case "Circle" F_Circle(tokenList) Default 'Do nothing! End End Function F_Rect:Void(tokenList:String[]) Local x:Float, y:Float Local width:Float, height:Float x = Float(tokenList[1]) y = Float(tokenList[2]) width = Float(tokenList[3]) height = Float(tokenList[4]) Print("Item: " + tokenList[0]) Print("Coordinates: " + x + ", " + y) Print("Area: " + width + " x " + height) Print("") End Function F_Ellipse:Void(tokenList:String[]) Local x:Float, y:Float Local width:Float, height:Float x = Float(tokenList[1]) y = Float(tokenList[2]) width = Float(tokenList[3]) height = Float(tokenList[4]) Print("Item: " + tokenList[0]) Print("Coordinates: " + x + ", " + y) Print("Area: " + width + " x " + height) Print("") End Function F_Circle:Void(tokenList:String[]) Local x:Float, y:Float Local radius:Float x = Float(tokenList[1]) y = Float(tokenList[2]) radius = Float(tokenList[3]) Print("Item: " + tokenList[0]) Print("Coordinates: " + x + ", " + y) Print("Radius: " + radius) Print("") End Function F_TokeniseCommandLine:String[] (cmdLine:String) 'Take a string, split it up, put each part in an array and then return it. Local outputList:String[] Local commandName:String Local valueList:String[] Local index:Int commandName = cmdLine[0..(cmdLine.Find(" "))].Trim() valueList = cmdLine[(cmdLine.Find(" ") + 1)..].Trim().Split(", ") outputList = outputList.Resize(valueList.Length() + 1) outputList[0] = commandName For index = 0 To(valueList.Length() -1) outputList[index + 1] = valueList[index] Next Return outputList End Class C_FileLoader Field fileData:String[] Field currentLine:Int Method New(fileName:String) fileData = LoadString(fileName).Split("~n") currentLine = 0 End Method ReadLine:String() Local currentData:String if Eof() = False currentData = Self.fileData[Self.currentLine] currentLine = (currentLine + 1) Else currentData = "Data Exhausted!" EndIf Return currentData End Method Eof:Bool() if Self.currentLine > (fileData.Length() - 1) Return True Else Return False EndIf End Method Purge:Void() Local counter:Int For counter = 0 to (fileData.Length() - 1) fileData[counter] = "" Next fileData =[] End End 3 Open up notepad or notepad++, copy and paste the following text into the editor. Next click File->Save As, then name it "data01.dat", select "All Files" from the Save as type dropdown menu and then save it to LoadParse.data/AppData. Rect 50, 50, 100, 75 Ellipse 200, 200, 75, 100 Circle 300, 300, 50 Finally compile as HTML5 and then run it in the browser. Note that output will be outputted as text in this example. |
| ||
I'm aware that my above explanation is a bit long winded, I'm better at doing than explaining. I hope it helps though. |
| ||
As I think that you may ask about this at some point. Here's a small example of string slicing and some of the String Class functionsFunction Main() Local myVar:String="abcdefgh" Local myArray:Int[]=[65,98,67,100,69,102,71,104] ' AbCdEfGh Local myToArray:Int[]="MONKEY-X".ToChars() ' Convert a string to a number array Print "myVar is 'abcdefgh'" Print "myArray is [65,98,67,100,69,102,71,104] ' AbCdEfGh" Print "myToArray is MONKEY-X" Print "String are zero index arrays [0=a,1=b,etc]" Print "Print the fourth letter ('d') using String.FromChar(myVar[3]) this would be equivalent to Chr$(numeric value) -> " + String.FromChar(myVar[3]) Print "Print the ascii character code for the fourth letter ('d') with myVar[3] this would be equivalent the Asc(character_at_variable_index) -> " + myVar[3] Print String.FromChar(34)+"A"+String.FromChar(34)+ "[0] would be the same as doing Asc(" + String.FromChar(34) + "A" + String.FromChar(34) + ") -> " + "A"[0] Print "Print the letters 'abcde' using what would be the equivalent of Left(myVar, length) with myVar[..5] -> " + myVar[..5] Print "Print the middle letters 'def' using what would be the equivalent of Mid(myVar, start, length) with myVar[3..((myVar.Length+1) - 3)] -> " + myVar[3..((myVar.Length+1) - 3)] Print "Print the last letters 'fgh' using what would be the equivalent of Right(myVar, length from right) with myVar[((myVar.Length) - 3)..] -> " + myVar[((myVar.Length) - 3)..] Print "Print the letters 'defgh'. Some languages would use Mid(myVar,start) with myVar[3..] -> " + myVar[3..] Print "Convert an array of integer character codes to a string with String.FromChars(myArray) -> " + String.FromChars(myArray) Print "'MONKEY-X' that was converted to an array" For Local i:Int = 0 To myToArray.Length - 1 Print "Index = " + i + ": code: " + myToArray[i] + " -> " + String.FromChar(myToArray[i]) Next End Function |
| ||
Thank you so much guys! It works. But I can not do my specific file. In CSV file I have this structure: 2016-09-23 09:10:45.177815 +02:00;00283771;A343;CR8100 2016-09-26 22:33:55.992692 +02:00;00007064;A418;CR7030 2016-09-19 08:23:14.491140 +02:00;00238821;A343;CR8100 2016-09-28 01:47:27.143027 +02:00;72080043;A385;CR323 etc. And now, I need to count how many times, for example, A343 (next A418, next A343...) occurs in the entire group (like COUNTIF() function in Excel). I modified the code. It reads semicolons. But I can still get out of it what I need :/ Rect;50;50;100;75 Ellipse;200;200;75;100 Circle;300;300;50 Strict Import mojo Global loader:C_FileLoader Function Main:Int() Local app:C_App = New C_App() Return 0 End Class C_App Extends App Method OnCreate:Int() F_Load("AppData/data01.dat") Return 0 End Method OnUpdate:Int() Return 0 End Method OnRender:Int() Cls Return 0 End End Function F_Load:Void(fileName:String) Local lineData:String If loader loader.Purge() loader = Null EndIf loader = New C_FileLoader(fileName) While not loader.Eof() lineData = loader.ReadLine() F_ParseUserData(lineData) Wend loader.Purge() End Function F_ParseUserData:Void(cmdLine:String) Local tokenList:String[] Local command:String tokenList = F_TokeniseCommandLine(cmdLine) command = tokenList[0] Select command Case "Rect" F_Rect(tokenList) Case "Ellipse" F_Ellipse(tokenList) Case "Circle" F_Circle(tokenList) Default 'Do nothing! End End Function F_Rect:Void(tokenList:String[]) Local x:Float, y:Float Local width:Float, height:Float x = Float(tokenList[1]) y = Float(tokenList[2]) width = Float(tokenList[3]) height = Float(tokenList[4]) Print("Item: " + tokenList[0]) Print("Coordinates: " + x + ", " + y) Print("Area: " + width + " x " + height) Print("") End Function F_Ellipse:Void(tokenList:String[]) Local x:Float, y:Float Local width:Float, height:Float x = Float(tokenList[1]) y = Float(tokenList[2]) width = Float(tokenList[3]) height = Float(tokenList[4]) Print("Item: " + tokenList[0]) Print("Coordinates: " + x + ", " + y) Print("Area: " + width + " x " + height) Print("") End Function F_Circle:Void(tokenList:String[]) Local x:Float, y:Float Local radius:Float x = Float(tokenList[1]) y = Float(tokenList[2]) radius = Float(tokenList[3]) Print("Item: " + tokenList[0]) Print("Coordinates: " + x + ", " + y) Print("Radius: " + radius) Print("") End Function F_TokeniseCommandLine:String[] (cmdLine:String) 'Take a string, split it up, put each part in an array and then return it. Local outputList:String[] Local commandName:String Local valueList:String[] Local index:Int commandName = cmdLine[0 .. (cmdLine.Find(";"))].Trim() valueList = cmdLine[ (cmdLine.Find(";") + 1) ..].Trim().Split(";") outputList = outputList.Resize(valueList.Length() + 1) outputList[0] = commandName For index = 0 To(valueList.Length() -1) outputList[index + 1] = valueList[index] Next Return outputList End Class C_FileLoader Field fileData:String[] Field currentLine:Int Method New(fileName:String) fileData = LoadString(fileName).Split("~n") currentLine = 0 End Method ReadLine:String() Local currentData:String if Eof() = False currentData = Self.fileData[Self.currentLine] currentLine = (currentLine + 1) Else currentData = "Data Exhausted!" EndIf Return currentData End Method Eof:Bool() if Self.currentLine > (fileData.Length() - 1) Return True Else Return False EndIf End Method Purge:Void() Local counter:Int For counter = 0 to (fileData.Length() - 1) fileData[counter] = "" Next fileData =[] End End |
| ||
OK, I have almost everything. Thank you all! And now, the last thing. When I run this code in GLFW (or HTML5), command Print(a[343]) works OK. But I need this data written to disk. And that's the latest issue :( Thank you in advance! ;) Import brl.filestream Method Eof:Bool() If Self.currentLine > (fileData.Length() -1) #rem Local file:= FileStream.Open("data/output.txt", "u") For Local i:Int = 342 To 343 file.WriteString(i) '+ i + ";" + a[i]) Next i #end Print(a[343]) Return True Else Return False EndIf End Method |
| ||
You can write to disk in Windows, Linux and Mac as far as I know, I assume you can save on the web but I've never tried to. As far as I know the only way to save any data on Android and iOS is with SaveState, this does not let you see and actual file however. I'll have a pokesie around when I get back home a bit later. ;-) |
| ||
OK, Steve, Thank you! |
| ||
I didn't read the whole thread ... Writing files in HTML5 alone won't work, therefore I use NWJS which gives you a window with a webview in it. Then I use this js function: var native = new Object(); native.WriteFile = function(filePath, content) { var fs = require('fs'); if (!fs) return; fs.writeFileSync(filePath, content, "utf-8"); } PS: There is a thread around here how to setup NWJS with monkey. |
| ||
@zxretrosoft: I found something that may be useful to you. Open the folder where you installed Monkey, then open bananas/mak/filetest/filetest.monkey. This seems to work OK for Desktop_game_(Glfw3), the result is saved in the "internal" folder within the buld folder. |
| ||
I've done a couple of modifications to my C_FileLoader class, if you dont specify a filename when calling C_FileLoader.New() then it can be used for saving data in a desktop application. First you store data, line by line by calling WriteLine("stuff"), and then call the Save("fileName") method, once done you then call the Purge() method. I forgot to mention in my previous comments, you should make the C_FileLoader object Null when finished. P.S: Also note the line fStream.Save("internal/testfile.dat"). The data can only be saved to the "internal" folder, this folder is automatically created when the project is built. If you try to save to a different folder then the program will end prematurely without saving anything, I would assume that a sub folder could probably be put inside the "internal" folder though. Strict Import mojo Import brl.filestream Global loader:C_FileLoader Function Main:Int() Local app:C_App = New C_App() Return 0 End Class C_App Extends App Method OnCreate:Int() Local fStream:C_FileLoader = New C_FileLoader() fStream.WriteLine("Big") fStream.WriteLine("Round") fStream.WriteLine("Shiny") fStream.WriteLine("Bulbz") fStream.Save("internal/testfile.dat") fStream.Purge() fStream = Null Return 0 End Method OnUpdate:Int() Return 0 End Method OnRender:Int() Cls Return 0 End End Class C_FileLoader Field fileData:String[] Field currentLine:Int Method New(fileName:String = "") If fileName <> "" fileData = LoadString(fileName).Split("~n") currentLine = 0 EndIf End Method ReadLine:String() Local currentData:String if Eof() = False currentData = Self.fileData[Self.currentLine] currentLine = (currentLine + 1) Else currentData = "Data Exhausted!" EndIf Return currentData End Method WriteLine:Void(data:String) Local index:Int index = Self.fileData.Length() Self.fileData = Self.fileData.Resize(index + 1) Self.fileData[index] = data End Method Save:Void(fileName:String) Local file:Stream Local data:String file = FileStream.Open(fileName, "w") For data = EachIn Self.fileData file.WriteString(data + "~n") Next End Method Eof:Bool() if Self.currentLine > (fileData.Length() - 1) Return True Else Return False EndIf End Method Purge:Void() Local counter:Int For counter = 0 to (fileData.Length() - 1) fileData[counter] = "" Next fileData =[] End End |
| ||
Thank you friends! Thank you so much Steve! I'll try it all. I thought of another idea. Would not it be easier to do this using the framework Playniax? The code seems to me easier? Strict Import playniax.ignitionx.framework.storage Function Main:Int() New MyApp Return 0 End Class MyApp Extends App Method OnCreate:Int() iSaveState("This is a test") ' iEraseData() iStorage.WriteString("MyGame/Player/Name", "Jason") iStorage.WriteInt("MyGame/Player/Lives", 5) iStorage.WriteFloat("MyGame/Cash", 100.5) iStorage.Save Print "Player: " + iStorage.ReadString("MyGame/Player/Name") Print "Lives: " + iStorage.ReadInt("MyGame/Player/Lives") Print "Money In the bank: $" + iStorage.ReadFloat("MyGame/Cash") Print iLoadState() Print "" Print "RAW data:" iStorage.Show Return 0 End End |
| ||
If the Playniax framework works for you then it's all good. ;-) |