Can you have of file of Type?

BlitzMax Forums/BlitzMax Beginners Area/Can you have of file of Type?

Arabia(Posted 2011) [#1]
I remember using this in Pascal and it was very useful. I have searched, and seem to recall that this isn't possible?

Pascal code which illustrates what I want to do:



I know I can do it manually, step through the array of type, save either string, byte, double etc. but this means the code has to be modified every time I modify my type.


_JIM(Posted 2011) [#2]
You can use reflection and only write it once :)


Arabia(Posted 2011) [#3]

_JIM (Posted 3 minutes ago) #2
You can use reflection and only write it once :)



Is there any example somewhere I look at? Even something very simple like the Pascal example I posted?


_JIM(Posted 2011) [#4]
This thread might help: http://www.blitzbasic.com/Community/posts.php?topic=76396#902424

Sadly, there's no complete documentation on how to do this.

I'll try to dig up some of my old code. It used to parse every type that I chose in my editor and exported everything automagically as an XML. Loading was done the same way. I only needed to write them one :)


Arabia(Posted 2011) [#5]
Thanks. That would be great if you can find it :)


_JIM(Posted 2011) [#6]
Ah, found it!

Type TBasicSerializable
	Method SaveToXML:TxmlNode(node:TxmlNode)
		Local tid:TTypeId = TTypeId.ForObject(Self)
		Local itemNode:TxmlNode = node.addChild(tid.name())
		
		For Local fld:TField = EachIn tid.EnumFields()
			If (Lower(fld.MetaData("Serialize")) <> Lower("False"))
				
				If (fld.TypeId().name() = "TList")
					Local fldNode:TxmlNode = itemNode.addChild(fld.name())
					For Local obj:TBasicSerializable = EachIn(TList(fld.Get(Self)))
						obj.SaveToXML(fldNode)
					Next
				Else
					Local strVal:String
					
					Select fld.TypeId()
						Case FloatTypeId, DoubleTypeId strVal = FloatToString(fld.GetFloat(Self))
						Case IntTypeId, ByteTypeId, ShortTypeId, LongTypeId strVal = String(fld.GetInt(Self))
						Case StringTypeId strVal = fld.GetString(Self)
						Default
							Local val:Object
							val = fld.Get(Self)
							If (val <> Null)
								Local fldNode:TxmlNode = itemNode.addChild(fld.Name())
								Local obj:TBasicSerializable = TBasicSerializable(fld.Get(Self))
								If (obj) obj.SaveToXML(fldNode)
							EndIf
					End Select
					
					If (strVal <> "")
						itemNode.addAttribute(fld.name(), strVal)
					EndIf
				End If
			EndIf
			
		Next
		
		Return itemNode
	End Method
	Method LoadFromXML(node:TxmlNode)
		Local tid:TTypeId = TTypeId.ForObject(Self)
		Local objNode:TxmlNode = node
		
		If (objNode = Null)
			Return
		End If
		
		For Local fld:TField = EachIn tid.EnumFields()
			If (Lower(fld.MetaData("Serialize")) <> Lower("False"))
				
				If (fld.TypeId().name() = "TList")
					Local fldList:TList = objNode.getChildren()
					
					For Local n:TxmlNode = EachIn fldList
						Local fieldName:String = fld.name()
						Local nodeName:String = n.getName()
						
						If (fieldName = nodeName)
						
							Local f:TList = New TList
							Local itemsList:TList = n.getChildren()
							
							If (itemsList <> Null)
								For Local itemNode:TxmlNode = EachIn itemslist
									Local nume:String = itemNode.getName()
									Local objType:TTypeId = TTypeId.ForName(nume)
									Local newObj:Object = objType.NewObject()
									objType.FindMethod("LoadFromXML").Invoke(newObj, [itemNode])
									
									If (objType.MetaData("onLoad"))
										Local mLoaded:TMethod = objType.FindMethod(objType.MetaData("onLoad"))
										If (mLoaded)
											mLoaded.Invoke(newObj, Null)
										End If
									EndIf
									f.AddLast(newObj)
								Next
							EndIf
							
							fld.Set(Self, f)
						End If
						
					Next
				Else
					Select fld.TypeId()
						Case FloatTypeId fld.SetFloat(Self, Float(objNode.getAttribute(fld.name())))
						Case DoubleTypeId fld.SetDouble(Self, Double(objNode.getAttribute(fld.name())))
						Case IntTypeId, ByteTypeId, ShortTypeId, LongTypeId fld.SetInt(Self, Int(objNode.getAttribute(fld.name())))
						Case StringTypeId fld.SetString(Self, objNode.getAttribute(fld.name()))
						Default
							Local chldNode:TXmlNode = FindChild(objNode, fld.Name())
							If (chldNode)
								Local chld2Node:TXmlNode = FindChild(chldNode, fld.TypeId().Name())
								Local new_obj:Object = fld.TypeId().NewObject()
								fld.TypeId().FindMethod("LoadFromXML").Invoke(new_obj, [chld2Node])
								fld.Set(Self, new_obj)
							EndIf
					End Select
				End If
				
				Local tid:TTypeId = fld.TypeId()
				If (tid.MetaData("onLoad"))
					Local mLoaded:TMethod = tid.FindMethod(tid.MetaData("onLoad"))
					If (mLoaded)
						mLoaded.Invoke(Self, Null)
					End If
				EndIf
				
			EndIf
		Next
	End Method
End Type


It uses Brucey's tinyxml mod. IMHO this is too much of an overkill. I want to convert it to use MaXML mod instead, but got no time today.

Now, I think it's pretty neat.

All you have to do is extend it from your type:

Type myType Extends TBasicSerializable
   Field TooSexy# {Serialize = "False"}
   Field Sexy%
End Type

Local myInstance:myType = New myType
myInstance.TooSexy = 3.15
myInstance.Sexy = 2

'save
Local pDoc:TxmlDoc = TxmlDoc.newDoc("1.0")
Local root:TxmlNode = TxmlNode.newNode("Root")
pDoc.setRootElement(root)
myInstance.SaveToXML(root)
pDoc.saveFormatFile("the_cake_is_a_lie.xml", True)

'load
Local pDoc2 = TxmlDoc.parseFile("the_cake_is_a_lie.xml")
Local root:TxmlNode = pDoc.getRootElement()
myInstance.LoadFromXML(FindChild(root, "myType"))


It's not perfect... could still be polished. But it works like a charm. It also handles TLists gracefully. However, it doesn't handle Arrays. Never needed them for this thing so far. Usually my arrays are internal-oly stuff, aka '{Serialize = "False"}'

Hope this helps ;)

Oh, you also have to use:

Import brl.reflection
Import bah.tinyxml


Happy coding!

EDIT:

I forgot to add this (not that it matters much). Code is useable any way one wishes. Credit is nice, but entirely optional.

Last edited 2011


ima747(Posted 2011) [#7]
Neat stuff. Just thought I'd pop in to remind you you can create a TList easily from an array (and vice versa) so it should be easy (though perhaps not truly elegant) to store arrays as well, just pump them through a list if you need to on the way to the file...


Arabia(Posted 2011) [#8]
Very nice. I will have a play with this code a bit later today and see if I can get my brain to comprehend it and use it in my program.

Thanks very much _Jim.


_JIM(Posted 2011) [#9]
I just noticed that it needs these two functions:

Function FloatToString:String(value:Float, places:Int = 3)
	Local sign:Int = Sgn(Value)
	value=Abs(value)
	Local i:Int = Round(Value * 10 ^ places)
	Local ipart:Int = Int(i / 10 ^ places)
	Local dpart:Int = i - ipart * 10 ^ places
	Local si:String = ipart
	Local di:String
	If dpart>0
		di=dpart
		While di.length < places
			di="0"+di
		Wend
		di="."+di
	EndIf
	While Right(di,1)="0"
		di=Left(di,di.length-1)
	Wend
	If di="" di=".0"
	If sign=-1 si="-"+si
	Return si+di
EndFunction

Function Round:Float(val:Float)
	Local dec#
	dec#=val-Floor(val)
	If dec<0.5 Return Floor(val) Else Return Ceil(val)
EndFunction


I believe they're JoshK's work. They were in a PropertyGrid sample.