File I/O - very simple

Monkey Forums/Monkey Beginners/File I/O - very simple

zxretrosoft(Posted 2016) [#1]
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.


dawlane(Posted 2016) [#2]
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



zxretrosoft(Posted 2016) [#3]
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
 



Steve Ancell(Posted 2016) [#4]
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()




zxretrosoft(Posted 2016) [#5]
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.


dawlane(Posted 2016) [#6]
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.


Steve Ancell(Posted 2016) [#7]
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. ;-)


Steve Ancell(Posted 2016) [#8]
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.


Steve Ancell(Posted 2016) [#9]
I'm aware that my above explanation is a bit long winded, I'm better at doing than explaining. I hope it helps though.


dawlane(Posted 2016) [#10]
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 functions
Function 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



zxretrosoft(Posted 2016) [#11]
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



zxretrosoft(Posted 2016) [#12]
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



Steve Ancell(Posted 2016) [#13]
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. ;-)


zxretrosoft(Posted 2016) [#14]
OK, Steve, Thank you!


Shinkiro1(Posted 2016) [#15]
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.


Steve Ancell(Posted 2016) [#16]
@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.


Steve Ancell(Posted 2016) [#17]
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




zxretrosoft(Posted 2016) [#18]
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




Steve Ancell(Posted 2016) [#19]
If the Playniax framework works for you then it's all good. ;-)