Code archives/File Utilities/BlitzXML

This code has been declared by its author to be Public Domain code.

Download source code

BlitzXML by John J.2005
BlitzXML makes it easy to load, manipulate, and save XML files. BlitzXML is a library of functions for manipulating xml data, including a fast (parses roughly twice as fast as Microsoft Internet Explorer's XML viewer) xml parser and saver. XML is widely used anywhere from word-processors to level builders.

Download the BlitzXML Documentation, Blitz3D Example Code and media (example.xml)
The example code should give you a good idea how XML can be used in game development, world builders, applications, etc.

Original BlitzXML Forum Thread
;============================= BlitzXML =================================
;Copyright (C) 2005 John Judnich

;BlitzXML is an XML (eXtendable Markup Language) function library for
;blitz. You don't even need to have any knowledge of the syntax of XML
;to use BlitzXML, although an understanding of the terms and structure
;is helpful.

;At the user's (programmer's) point of view,
;BlitzXML is a way of storing bits of data similar to the way folders
;are stored on a hard drive. For example an node (item) named "inventory" may contain
;child nodes (sub-items) such as, "key". Each node may have an unlimited
;amount of sub-nodes, and each sub-node may have sub-sub-nodes, etc.
;Each node may have a name, and a number of attributes. Nodes may
;contain both sub-nodes (called children), and text data. To get a
;better idea how xml works, look at "example.xml".

;When a data structure is constructed with BltizXML, it may be saved
;to a file using XML syntax, which is really just a more strict form of
;HTML. XML files may also be loaded and parsed into BlitzXML just as
;easily. Due to the flexibility of XML, this library should be very
;useful for level editors, games that load levels from XML files,
;or any program requiring structured Or
;complex data to be saved to and loaded from a file.

;========================================================================



;**** Constant Declarations =============================================

Const MAX_ATTRIBUTES = 32	;The maximum number of attributes a xmlNode may have
Const MAX_ERRORS = 64		;The maximum number of errors and warnings that will be processed until the xml parser aborts the operation
Const PARSER_RECURSE = 1024 ;The maximum number of virtually "recursive" steps for the parser.
Global XML_INDENTATION$ = Chr$(9)	;The character(s) used to indent when saving files

;**** Type Declarations =================================================

;xmlNode Type - This is the main building block of BlitzXML. All xml data
;is stored with this Type, which gets manipulated by the user. The xml data
;can then be loaded from and saved to files.
Type xmlNode
	Field Name$								;The name of the node
	Field AttributeCount					;The number of attributes for the node
	Field AttributeName$[MAX_ATTRIBUTES]	;The attribute name array
	Field AttributeValue$[MAX_ATTRIBUTES]	;The attribute value array
	Field Contents$							;The data contents of the node
	Field Parent.xmlNode					;This node's parent node. If this is set to Null, then this node is the "root" node
	Field Level								;This is the node's level
	
	;These fields are manipulated by xml_RegisterChild(), xml_GetChild(), and xml_UnregisterChild()
	Field ChildCount						;The number of the node's children
	Field ChildBank							;A memory bank of handles to the node's children
End Type


;**** Global Declarations ===============================================

Global xml_Error$[MAX_ERRORS]
Global xml_ErrorPos[MAX_ERRORS]
Global xml_ErrorCount


;**** Interface Functions ===============================================
;Interface functions are the functions the user (the programmer) uses
;in their program, unlike the internal functions, which are only called
;by these functions.

;This function returns the level the node is at. If the node is the
;root node, it is at level 0. If it is a child of the root node,
;the level will be 1. If it is a child of a child of the root node,
;the level of 2 will be returned, etc.
Function xmlNodeLevel(Node)
	this.xmlNode = Object.xmlNode(Node)
	Return this\Level
End Function

;This returns a node's parent node. If the node has no parent (if it's
;the root node), 0 will be returned.
Function xmlNodeParent(Node)
	this.xmlNode = Object.xmlNode(Node)
	If this\Parent = Null Then Return 0
	Return Handle(this\Parent)
End Function

;This returns the number of children the node has. In many cases,
;the node will contain no children, therefore returning 0.
Function xmlNodeChildCount(Node)
	this.xmlNode = Object.xmlNode(Node)
	Return this\ChildCount
End Function

;This returns one of the node's children, specified by ChildIndex.
;ChildIndex may be set anywhere from 1 to the amount of children
;the node has, which can be obtained from the xmlNodeChildCount()
;function.
Function xmlNodeChild(Node, ChildIndex)
	this.xmlNode = Object.xmlNode(Node)
	Return Handle(xml_GetChild(this, ChildIndex))
End Function

;This function will search for the first node matching the specified
;name and parent. Specifying a parent (optional) will only search
;nodes that are children of the specified parent node. If you only want
;to find a direct child of this node (not sub-childs), set Recurse to False.
Function xmlNodeFind(Name$, Parent, Recurse = True)
	parentnode.xmlNode = Object.xmlNode(Parent)
	For this.xmlNode = Each xmlNode
		If this\Parent = parentnode Then
			If Lower(this\Name) = Lower(Name) Then
				Return Handle(this)
			End If
			If Recurse = True And this\ChildCount > 0 Then
				ret = xmlNodeFind(Name, Handle(this), True)
				If ret <> 0 Then Return ret
			End If
		End If
	Next
End Function

;This function adds a new node to the "tree" of existing xml nodes. Set
;ParentNode to the node you would like this to be a child of, or set it
;to 0 if this is the "root" node. Note: only one root node is allowed.
;Optionally, Name$ can be set to a name the node will initially be given,
;although the node can be renamed later with xmlNodeNameSet()
Function xmlNodeAdd(ParentNode, Name$="NewNode")
	this.xmlNode = New xmlNode
	parent.xmlNode = Object.xmlNode(ParentNode)
	
	this\Parent = parent
	If parent = Null Then
		this\Level = 0
	Else
		top.xmlNode = parent
		If parent\ChildCount = 0 Then
			top.xmlNode = parent
		Else
			top.xmlNode = Object.xmlNode( xmlNodeChild(ParentNode, 1) )
		End If
		Insert this After top
		this\Level = parent\Level + 1
		xml_RegisterChild(parent, this)
	End If
	
	this\Name = Name
	Return Handle(this)
End Function

;This function deletes the given node, including all of it's children
;(sub-nodes), if there are any. Ignore the ChildIndex variable, as it
;is used internally when recursively deleting the node's children.
;This can be used to delete an entire XML file in memory by deleting it's
;handle (root node)
Function xmlNodeDelete(Node, ChildIndex = 0)
	this.xmlNode = Object.xmlNode(Node)
	
	For i = 1 To this\ChildCount
		xmlNodeDelete(Handle(xml_GetChild(this, 1)), 1) ;The index is always 1 because the list will keep getting smaller while they are getting deleted - (just like holding down the delete key at the beginning of a document)
	Next
	
	If this\Parent <> Null Then
		If ChildIndex = 0 Then
			For i = 1 To this\Parent\ChildCount
				If xml_GetChild(this\Parent, i) = this Then ChildIndex = i:Exit
			Next
		End If
		xml_UnregisterChild(this\Parent, ChildIndex)
	End If
	
	FreeBank this\ChildBank
	Delete this
End Function

;This sets a node's name. Note: A node's name must not be a blank string
Function xmlNodeNameSet(Node, Name$)
	this.xmlNode = Object.xmlNode(Node)
	this\Name = Name
End Function

;This returns the name of a node
Function xmlNodeNameGet$(Node)
	this.xmlNode = Object.xmlNode(Node)
	Return this\Name
End Function

;This sets the value of an attribute of a node. If the attribute does
;not exist, it will be created. The attribute's value may be any valid
;string of characters, not including double quotes. The value is allowed
;to be a blank string.
;Example:
;xmlNodeAttributeSet(node, "alpha", "0.7")
Function xmlNodeAttributeValueSet(Node, Attribute$, Value$)
	this.xmlNode = Object.xmlNode(Node)
	
	;Check if the attribute exists or not
	indx = 0
	For i = 1 To this\AttributeCount
		If Attribute = this\AttributeName[i] Then indx = i:Exit
	Next
	
	;Create a new attribute if it doesn't exist
	If indx = 0 Then
		this\AttributeCount = this\AttributeCount + 1
		this\AttributeName[this\AttributeCount] = Attribute
		indx = this\AttributeCount
	End If
	
	;Set the attribute's value
	this\AttributeValue[indx] = Value
End Function

;This returns the value of the specified attribute, if it exists. If it
;doesn't exist, a blank string will be returned.
;Example:
;EntityAlpha Entity\Mesh, xmlNodeAttributeGet(Entity\Node, "alpha")
Function xmlNodeAttributeValueGet$(Node, Attribute$)
	this.xmlNode = Object.xmlNode(Node)
	
	;Find the attribute
	indx=0
	For i = 1 To this\AttributeCount
		If Attribute = this\AttributeName[i] Then indx = i:Exit
	Next
	
	;If the attribute exists, return it's value. If not, return a blank string
	If indx = 0 Then
		Return ""
	Else
		Return this\AttributeValue[indx]
	End If
End Function

;This sets the name of an attribute (NOT it's value). Note: attribute
;names are case sensitive
;Example:
;xmlNodeAttributeNameSet(node,"pitch","Xang")
Function xmlNodeAttributeNameSet(Node, Attribute$, NewName$)
	this.xmlNode = Object.xmlNode(Node)
	
	;Find the attribute
	indx = 0
	For i = 1 To this\AttributeCount
		If Attribute = this\AttributeName[i] Then indx = i:Exit
	Next

	;If the attribute exists, rename it
	If indx <> 0 Then
		this\AttributeName[indx] = NewName
	End If
End Function

;This deletes an attribute. Once a new attribute is created when
;using the xmlNodeAttributeSet() function, it will continue to
;reside in memory, and be saved to a file even if it's value is 
;blank. To remove an un-used (or used) attribute of a node, use
;this function.
;Example:
;xmlNodeAttributeDelete(node, "hidden")
Function xmlNodeAttributeDelete(Node, Attribute$)
	this.xmlNode = Object.xmlNode(Node)
	
	;Find the attribute
	indx = 0
	For i = 1 To this\AttributeCount
		If Attribute = this\AttributeName[i] Then indx = i:Exit
	Next
	
	;Delete the attribute, if it exists
	If indx <> 0 Then
		this\AttributeName[indx] = this\AttributeName[this\AttributeCount]
		this\AttributeValue[indx] = this\AttributeValue[this\AttributeCount]
		this\AttributeCount = this\AttributeCount - 1
	End If
End Function

;This sets a node's data string. A node's data is a string of
;text contained within the opening and closing node tags.
;Example:
;xmlNodeDataSet(titlenode, "BlitzXML")
Function xmlNodeDataSet(Node, NodeData$)
	this.xmlNode = Object.xmlNode(Node)
	this\Contents = NodeData
End Function

;This returns a node's data string. A node's data is a string
;of text contained within the opening and closing node tags.
Function xmlNodeDataGet$(Node)
	this.xmlNode = Object.xmlNode(Node)
	Return this\Contents
End Function

;This function saves all XML nodes to the specified file.
;If any errors occur, false will be returned, if not, true
;will be returned.
Function xmlSave(FileName$, Node)
	this.xmlNode = Object.xmlNode(Node)

	file = WriteFile(FileName)
	If file = 0 Then xml_AddError("Error writing XML file (possibly, file is in use, or is the folder/drive/file is write protected).", 0):Return
	
	WriteLine file, "<?xml version="+Chr(34)+"1.0"+Chr(34)+" ?>"
	xml_WriteNode(file, this)
	
	CloseFile file
End Function

Function xml_WriteNode(File, Node.xmlNode)
	Local NodeContents$, Indent$, Indent2$
	
	NodeContents = Node\Name
	For i = 1 To Node\AttributeCount
		NodeContents = NodeContents + " " + Node\AttributeName[i] + "=" + Chr$(34) + Node\AttributeValue[i] + Chr$(34)
	Next
	
	Indent = String$(XML_INDENTATION$, Node\Level)
	Indent2 = String$(XML_INDENTATION$, Node\Level+1)
	
	If Node\ChildCount = 0 Then
		If Node\Contents = "" Then
			WriteLine File, Indent + "<" + NodeContents + "/>" 
		Else
		    WriteLine File, Indent + "<" + NodeContents + ">" + Node\Contents + "</" + Node\Name + ">"
		End If
	Else
		WriteLine File, Indent + "<" + NodeContents + ">"
		If Node\Contents <> "" Then WriteLine File, Indent2 + Node\Contents
		
		For i = 1 To Node\ChildCount
			xml_WriteNode(File, Object.xmlNode(xmlNodeChild(Handle(Node), i)))
		Next
		
		WriteLine File, Indent + "</" + Node\Name + ">"
	End If
End Function

;This function loads and parses XML nodes from the specified XML file.
;Note: This (BlitzXML's xml parser) only supports xml files with standard
;xml tags and attributes with values enclosed in quotes. If the file
;is loaded successfully with no errors, a handle to the root node of the file
;will be returned. If not, 0 will be returned.
;Errors can be accessed using the xmlError$() and xmlErrorCount() functions.
Function xmlLoad(FileName$)
	Local attribute$[MAX_ATTRIBUTES]
	Local value$[MAX_ATTRIBUTES]
	Local nodestack[PARSER_RECURSE]
	Local rootnode
	
	DebugLog "Loading XML file: " + FileName
	xml_ClearErrors()
	begintime = MilliSecs()
	
	;Open the file
	file = ReadFile(FileName)
	If file = False Then
		xml_AddError("Error opening XML file: File does not exist.", 0)
		Return 0
	End If
	If Eof(file) = -1 Then
		xml_AddError("Error opening XML file: File is already in use by another program.", 0)
		Return 0
	End If
	
	;Read in all tags
	stacklevel = 0
	While Eof(file) = False
		;Get the next tag or data section
		tag$ = xml_NextItem(file)
		
		If tag$ <> "" Then 
		
			If xml_ItemType = 2 Then
				;Node contents
				xmlNodeDataSet(nodestack[stacklevel - 1], Trim(Trim(xmlNodeDataGet(nodestack[stacklevel - 1])) + " " + Trim(tag)))
			Else
				;Check if it's a closing tag, opening tag, or stand-alone tag
				If Left(tag,1) = "/" Then
					;Closing tag
					stacklevel = stacklevel - 1
					
					tmp.xmlNode = Object.xmlNode(nodestack[stacklevel])
					If tag <> "/" + tmp\Name Then xml_AddError("Unclosed tag (found <"+tag+">, expected </"+tmp\Name+">", FilePos(file))
				Else
					;Create a new node
					If stacklevel > 0 Then parent = nodestack[stacklevel - 1] Else parent = 0
					node = xmlNodeAdd(parent)
					If stacklevel = 0 Then rootnode = node
					
					;Get the name and attributes from the tag
					For i = 0 To attr:attribute[i] = "":value[i] = "":Next:attr = 0:opened = False:name$ = ""
					length = Len(tag)
					For i = 1 To length
						ch$ = Mid(tag, i, 1)
						If attr = 0 And ch = " " Then attr = attr + 1
						If ch = "=" Then attr = -attr
						If ch = Chr(34) Then
							If attr > 0 Then xml_AddError("Expecting equals symbol", FilePos(file))
							opened = 1 - opened
							If opened = False Then attr = Abs(attr):attr = attr + 1
						End If
						If ch <> Chr(34) And attr < 0 And opened Then value[-attr] = value[-attr] + ch
						If attr = 0 Then
							name = name + ch
						Else
							If attr > 0 And ch <> Chr(34) And ch<>" " Then attribute[attr] = attribute[attr] + ch
						End If
					Next
					For i = 1 To attr-1
						xmlNodeAttributeValueSet(node, attribute[i], value[i])
					Next
					xmlNodeNameSet(node, name)
					nodestack[stacklevel] = node
					
					If Right(tag,1) = "/" Then
						;Stand-alone tag
					Else
						;Opening tag
						stacklevel = stacklevel + 1
					End If
				End If
			End If
		End If
	Wend
	
	CloseFile file
	
	endtime = MilliSecs()
	parsetime# = (endtime - begintime) / 1000.0
	If xmlErrorCount > 0 Then DebugLog "Parse failed" Else DebugLog "Parse completed in "+parsetime+" seconds."
	
	If xmlErrorCount > 0 Then Return 0 Else Return rootnode
End Function

;This function returns the number of errors and warnings from the last
;file parse performed.
Function xmlErrorCount()
	Return xml_ErrorCount
End Function

;This returns the position of the specified error (in characters from
;the beginning of the file
Function xmlErrorPosition(ErrorNumber)
	If ErrorNumber > xml_ErrorCount Then Return 0
	Return xml_ErrorPos[ErrorNumber]
End Function

;This returns the description of the requested error.
Function xmlError$(ErrorNumber)
	If ErrorNumber > xml_ErrorCount Then Return ""
	Return xml_Error[ErrorNumber]
End Function



;**** Internal Functions ================================================
;Internal functions should not be called from ANYWHERE but from other
;BlitzXML functions. These functions are undocumented, and you should
;NOT use them.

Global xml_ItemType
Function xml_NextItem$(file)
	Local tag$
	While Eof(file) = False
		ch = ReadByte(file)
		If txt$ <> "" And (ch = 60 Or ch = 13) Then xml_ItemType = 2:SeekFile file,FilePos(file)-1:Return txt
		If ch <> 13 And ch <> 15 And ch <> 10 Then txt$ = txt$ + Chr(ch)
		If ch = 13 And txt <> "" Then txt = txt + " "
		
		If ch = 60 Then ;<
			If opened = True Then xml_AddError("Expecting closing bracket (>)", FilePos(file))
			opened = True
		End If
		If ch = 62 Then ;>
			txt = ""
			If opened = False Then xml_AddError("Expecting opening bracket (<)", FilePos(file))
			opened = False
			If Left(tag,4) = "<!--" Or Left(tag,2) = "<?" Then
				If Left(tag,4) = "<!--" And Right(tag,2) <> "--" Then xml_AddError("Expecting correct comment closure (-->)", FilePos(file))
				If Left(tag,4) = "<?" And Right(tag,2) <> "?" Then xml_AddError("Expecting correct header closure (?>)", FilePos(file))
				tag = ""
			Else
				xml_ItemType = 1
				Return Right(tag,Len(tag)-1)
			End If
		End If
		If opened Then tag = tag + Chr(ch)
	Wend
End Function

Function xml_RegisterChild(Node.xmlNode, Child.xmlNode)
	;Incriment the child count
	Node\ChildCount = Node\ChildCount + 1
	
	;Allocate memory for the data
	If Node\ChildBank = False Then
		Node\ChildBank = CreateBank(4)
	Else
		ResizeBank Node\ChildBank, Node\ChildCount * 4
	End If
	
	;Write the data
	Value = Handle(Child)
	PokeInt Node\ChildBank, (Node\ChildCount - 1) * 4, Value
End Function

Function xml_GetChild.xmlNode(Node.xmlNode, ChildIndex)
	;Check if the ChildIndex is valid
	If ChildIndex > Node\ChildCount Then Return Null
	
	;Get the child xmlNode object and return it
	Value = PeekInt(Node\ChildBank, (ChildIndex - 1) * 4)
	this.xmlNode = Object.xmlNode(Value)
	Return this
End Function

Function xml_UnregisterChild(Node.xmlNode, ChildIndex)
	;Check if the ChildIndex is valid
	If ChildIndex > Node\ChildCount Then Return False

	;"Swap" the child-to-be-deleted with the last child on the list, so the last child on the list is now the child to be deleted
	;(actually, it doesn't swap - to optimize it a little, the child-to-be-deleted doesn't get copied anywhere because it's not gonna be used)
	Value = PeekInt(Node\ChildBank, (Node\ChildCount - 1) * 4)
	PokeInt Node\ChildBank, (ChildIndex - 1) * 4, Value
	
	;Downsize the bank, erasing the last child on the list which would be the child-to-be-deleted
	ResizeBank Node\ChildBank, (Node\ChildCount - 1) * 4
	Node\ChildCount = Node\ChildCount - 1
	
	Return True
End Function

Function xml_ClearErrors()
	xml_ErrorCount = 0
End Function

Function xml_AddError(Description$, pos)
	xml_ErrorCount = xml_ErrorCound + 1
	xml_ErrorPos[xml_ErrorCount] = pos
	xml_Error[xml_ErrorCount] = Description
	
	DebugLog "Error at char #"+pos+":  "+Description
End Function

Comments

Cold Harbour2006
All looks great and I can see you to load an XML file.
How about a sample of how to save an XML file?


Roland2006
Hey John J, This is absolutely awesome. Thanks for sharing such a handy set of functions!

Cold Harbour -- I just worked up a little piece that saves an XML file out. Do you want me to post it? I actually found that saving the XML was much simpler than loading it-- Just make a bunch of nodes using XMLNodeAdd, then call XMLSave(filename$) and you're done!

best,
roland


Jams2006
Really great stuff! Using this alot in the project i'm working on!


@rtur2006
Thanks! Great work.


John J.2006
Note: This code archive item has been out of date for a while (this is now up-to-date with the latest version). Please re-download now to get the latest version (with bug-fixes, extra features, etc.)


Cold Harbour2006
Roland, thanks the penny's dropped now.

Thanks for a great library John J.


Wayne2006
The results were not what I expected for the material "logs" :

- <mesh>
- <submeshes>
- <submesh material="Logs" usesharedvertices="false" use32bitindexes="false">
- <faces count="10">
  <face v1="0" v2="1" v3="2" /> 
  <face v1="1" v2="0" v3="3" /> 
  <face v1="3" v2="0" v3="4" /> 
  <face v1="5" v2="6" v3="7" /> 
  <face v1="6" v2="5" v3="8" /> 
  <face v1="9" v2="10" v3="11" /> 
  <face v1="10" v2="9" v3="12" /> 
  <face v1="12" v2="9" v3="13" /> 
  <face v1="14" v2="15" v3="16" /> 
  <face v1="15" v2="14" v3="17" /> 
  </faces>
- <geometry vertexcount="18">
- <vertexbuffer positions="true" normals="true" colours_diffuse="false" texture_coords="1" texture_coord_dimensions_0="2">
- <vertex>
  <position x="3.34101563592847" y="2.13995" z="-11.7980971035329" /> 
  <normal x="0.0" y="0.0" z="-1.0" /> 
  <texcoord u="-3.65377912940559" v="-1.34027777777778" /> 
  </vertex>
- <vertex>
  <position x="10.2053656359285" y="0.0" z="-11.7980971035329" /> 
  <normal x="0.0" y="0.0" z="-1.0" /> 
  <texcoord u="-11.16072357385" v="1.0" /> 
  </vertex>
- <vertex>
  <position x="3.34101563592847" y="0.0" z="-11.7980971035329" /> 
  <normal x="0.0" y="0.0" z="-1.0" /> 
  <texcoord u="-3.65377912940559" v="1.0" /> 
  </vertex>
- <vertex>
  <position x="10.2053656359285" y="2.13995" z="-11.7980971035329" /> 
  <normal x="0.0" y="0.0" z="-1.0" /> 
  <texcoord u="-11.16072357385" v="-1.34027777777778" /> 
  </vertex>
- <vertex>
  <position x="6.77319063592847" y="4.07035" z="-11.7980971035329" /> 
  <normal x="0.0" y="0.0" z="-1.0" /> 
  <texcoord u="-7.40725135162781" v="-3.45138888888889" /> 
  </vertex>
- <vertex>
  <position x="10.2053656359285" y="0.0" z="-11.7980971035329" /> 
  <normal x="1.0" y="0.0" z="0.0" /> 
  <texcoord u="12.9025558875031" v="1.0" /> 
  </vertex>
- <vertex>
  <position x="10.2053656359285" y="2.13995" z="-2.33024710353288" /> 
  <normal x="1.0" y="0.0" z="0.0" /> 
  <texcoord u="2.54838922083648" v="-1.34027777777778" /> 
  </vertex>
- <vertex>
  <position x="10.2053656359285" y="0.0" z="-2.33024710353288" /> 
  <normal x="1.0" y="0.0" z="0.0" /> 
  <texcoord u="2.54838922083648" v="1.0" /> 
  </vertex>
- <vertex>
  <position x="10.2053656359285" y="2.13995" z="-11.7980971035329" /> 
  <normal x="1.0" y="0.0" z="0.0" /> 
  <texcoord u="12.9025558875031" v="-1.34027777777778" /> 
  </vertex>
- <vertex>
  <position x="10.2053656359285" y="2.13995" z="-2.33024710353288" /> 
  <normal x="0.0" y="0.0" z="1.0" /> 
  <texcoord u="11.16072357385" v="-1.34027777777778" /> 
  </vertex>
- <vertex>
  <position x="3.34101563592847" y="0.0" z="-2.33024710353288" /> 
  <normal x="0.0" y="0.0" z="1.0" /> 
  <texcoord u="3.65377912940559" v="1.0" /> 
  </vertex>
- <vertex>
  <position x="10.2053656359285" y="0.0" z="-2.33024710353288" /> 
  <normal x="0.0" y="0.0" z="1.0" /> 
  <texcoord u="11.16072357385" v="1.0" /> 
  </vertex>
- <vertex>
  <position x="3.34101563592847" y="2.13995" z="-2.33024710353288" /> 
  <normal x="0.0" y="0.0" z="1.0" /> 
  <texcoord u="3.65377912940559" v="-1.34027777777778" /> 
  </vertex>
- <vertex>
  <position x="6.77319063592847" y="4.07035" z="-2.33024710353288" /> 
  <normal x="0.0" y="0.0" z="1.0" /> 
  <texcoord u="7.40725135162781" v="-3.45138888888889" /> 
  </vertex>
- <vertex>
  <position x="3.34101563592847" y="2.13995" z="-11.7980971035329" /> 
  <normal x="-1.0" y="0.0" z="0.0" /> 
  <texcoord u="-12.9025558875031" v="-1.34027777777778" /> 
  </vertex>
- <vertex>
  <position x="3.34101563592847" y="0.0" z="-2.33024710353288" /> 
  <normal x="-1.0" y="0.0" z="0.0" /> 
  <texcoord u="-2.54838922083648" v="1.0" /> 
  </vertex>
- <vertex>
  <position x="3.34101563592847" y="2.13995" z="-2.33024710353288" /> 
  <normal x="-1.0" y="0.0" z="0.0" /> 
  <texcoord u="-2.54838922083648" v="-1.34027777777778" /> 
  </vertex>
- <vertex>
  <position x="3.34101563592847" y="0.0" z="-11.7980971035329" /> 
  <normal x="-1.0" y="0.0" z="0.0" /> 
  <texcoord u="-12.9025558875031" v="1.0" /> 
  </vertex>
  </vertexbuffer>
  </geometry>
  </submesh>
- <submesh material="Shingles-Asphalt01" usesharedvertices="false" use32bitindexes="false">
- <faces count="4">
  <face v1="0" v2="1" v3="2" /> 
  <face v1="1" v2="0" v3="3" /> 
  <face v1="4" v2="5" v3="6" /> 
  <face v1="5" v2="4" v3="7" /> 
  </faces>
- <geometry vertexcount="8">
- <vertexbuffer positions="true" normals="true" colours_diffuse="false" texture_coords="1" texture_coord_dimensions_0="2">
- <vertex>
  <position x="10.2053656359285" y="2.13995" z="-2.33024710353288" /> 
  <normal x="0.490222958435144" y="0.871597069191433" z="0.0" /> 
  <texcoord u="4.58710059750566" v="16.4447129102341" /> 
  </vertex>
- <vertex>
  <position x="6.77319063592847" y="4.07035" z="-11.7980971035329" /> 
  <normal x="0.490222958435144" y="0.871597069191433" z="0.0" /> 
  <texcoord u="23.2246005975057" v="8.69313796945595" /> 
  </vertex>
- <vertex>
  <position x="6.77319063592847" y="4.07035" z="-2.33024710353288" /> 
  <normal x="0.490222958435144" y="0.871597069191433" z="0.0" /> 
  <texcoord u="4.58710059750566" v="8.69313796945595" /> 
  </vertex>
- <vertex>
  <position x="10.2053656359285" y="2.13995" z="-11.7980971035329" /> 
  <normal x="0.490222958435144" y="0.871597069191433" z="0.0" /> 
  <texcoord u="23.2246005975057" v="16.4447129102341" /> 
  </vertex>
- <vertex>
  <position x="6.77319063592847" y="4.07035" z="-2.33024710353288" /> 
  <normal x="-0.490222958435144" y="0.871597069191433" z="0.0" /> 
  <texcoord u="-4.58710059750566" v="-14.5489608783791" /> 
  </vertex>
- <vertex>
  <position x="3.34101563592847" y="2.13995" z="-11.7980971035329" /> 
  <normal x="-0.490222958435144" y="0.871597069191433" z="0.0" /> 
  <texcoord u="-23.2246005975057" v="-6.79738593760097" /> 
  </vertex>
- <vertex>
  <position x="3.34101563592847" y="2.13995" z="-2.33024710353288" /> 
  <normal x="-0.490222958435144" y="0.871597069191433" z="0.0" /> 
  <texcoord u="-4.58710059750566" v="-6.79738593760097" /> 
  </vertex>
- <vertex>
  <position x="6.77319063592847" y="4.07035" z="-11.7980971035329" /> 
  <normal x="-0.490222958435144" y="0.871597069191433" z="0.0" /> 
  <texcoord u="-23.2246005975057" v="-14.5489608783791" /> 
  </vertex>
  </vertexbuffer>
  </geometry>
  </submesh>
- <submesh material="Concrete" usesharedvertices="false" use32bitindexes="false">
- <faces count="2">
  <face v1="0" v2="1" v3="2" /> 
  <face v1="1" v2="0" v3="3" /> 
  </faces>
- <geometry vertexcount="4">
- <vertexbuffer positions="true" normals="true" colours_diffuse="false" texture_coords="1" texture_coord_dimensions_0="2">
- <vertex>
  <position x="10.2053656359285" y="0.0" z="-11.7980971035329" /> 
  <normal x="0.0" y="-1.0" z="0.0" /> 
  <texcoord u="-8.37054268038753" v="-8.67691691562736" /> 
  </vertex>
- <vertex>
  <position x="3.34101563592847" y="0.0" z="-2.33024710353288" /> 
  <normal x="0.0" y="-1.0" z="0.0" /> 
  <texcoord u="-2.74033434705419" v="-0.911291915627359" /> 
  </vertex>
- <vertex>
  <position x="3.34101563592847" y="0.0" z="-11.7980971035329" /> 
  <normal x="0.0" y="-1.0" z="0.0" /> 
  <texcoord u="-2.74033434705419" v="-8.67691691562736" /> 
  </vertex>
- <vertex>
  <position x="10.2053656359285" y="0.0" z="-2.33024710353288" /> 
  <normal x="0.0" y="-1.0" z="0.0" /> 
  <texcoord u="-8.37054268038753" v="-0.911291915627359" /> 
  </vertex>
  </vertexbuffer>
  </geometry>
  </submesh>
  </submeshes>
  </mesh>


Results after processing:
Loading XML file: f:\ogre\media\models\house1.mesh.xml
Parse completed in 0.297 seconds.
1
Name=mesh
Name=submeshes
Name=submesh
material,Logs
usesharedvertices,false
use32bitindexes,false
Name=submesh
material,Concrete
usesharedvertices,false
use32bitindexes,false
Name=faces
count,2
Name=geometry
vertexcount,4
Name=vertexbuffer
positions,true
normals,true
colours_diffuse,false
texture_coords,1
texture_coord_dimensions_0,2
Name=vertex
Name=vertex
Name=position
x,10.2053656359285
y,0.0
z,-2.33024710353288
Name=texcoord
u,-8.37054268038753
v,-0.911291915627359
Name=normal
x,0.0
y,-1.0
z,0.0
Name=vertex
Name=position
x,3.34101563592847
y,0.0
z,-11.7980971035329
Name=texcoord
u,-2.74033434705419
v,-8.67691691562736
Name=normal
x,0.0
y,-1.0
z,0.0
Name=vertex
Name=position
x,3.34101563592847
y,0.0
z,-2.33024710353288
Name=texcoord
u,-2.74033434705419
v,-0.911291915627359
Name=normal
x,0.0
y,-1.0
z,0.0
Name=position
x,10.2053656359285
y,0.0
z,-11.7980971035329
Name=texcoord
u,-8.37054268038753
v,-8.67691691562736
Name=normal
x,0.0
y,-1.0
z,0.0
Name=face
v1,0
v2,1
v3,2
Name=face
v1,1
v2,0
v3,3
Name=submesh
material,Shingles-Asphalt01
usesharedvertices,false
use32bitindexes,false
Name=faces
count,4
Name=geometry
vertexcount,8
Name=vertexbuffer
positions,true
normals,true
colours_diffuse,false
texture_coords,1
texture_coord_dimensions_0,2
Name=vertex
Name=vertex
Name=position
x,6.77319063592847
y,4.07035
z,-11.7980971035329
Name=texcoord
u,-23.2246005975057
v,-14.5489608783791
Name=normal
x,-0.490222958435144
y,0.871597069191433
z,0.0
Name=vertex
Name=position
x,3.34101563592847
y,2.13995
z,-2.33024710353288
Name=texcoord
u,-4.58710059750566
v,-6.79738593760097
Name=normal
x,-0.490222958435144
y,0.871597069191433
z,0.0
Name=vertex
Name=position
x,3.34101563592847
y,2.13995
z,-11.7980971035329
Name=texcoord
u,-23.2246005975057
v,-6.79738593760097
Name=normal
x,-0.490222958435144
y,0.871597069191433
z,0.0
Name=vertex
Name=position
x,6.77319063592847
y,4.07035
z,-2.33024710353288
Name=texcoord
u,-4.58710059750566
v,-14.5489608783791
Name=normal
x,-0.490222958435144
y,0.871597069191433
z,0.0
Name=vertex
Name=position
x,10.2053656359285
y,2.13995
z,-11.7980971035329
Name=texcoord
u,23.2246005975057
v,16.4447129102341
Name=normal
x,0.490222958435144
y,0.871597069191433
z,0.0
Name=vertex
Name=position
x,6.77319063592847
y,4.07035
z,-2.33024710353288
Name=texcoord
u,4.58710059750566
v,8.69313796945595
Name=normal
x,0.490222958435144
y,0.871597069191433
z,0.0
Name=vertex
Name=position
x,6.77319063592847
y,4.07035
z,-11.7980971035329
Name=texcoord
u,23.2246005975057
v,8.69313796945595
Name=normal
x,0.490222958435144
y,0.871597069191433
z,0.0
Name=position
x,10.2053656359285
y,2.13995
z,-2.33024710353288
Name=texcoord
u,4.58710059750566
v,16.4447129102341
Name=normal
x,0.490222958435144
y,0.871597069191433
z,0.0
Name=face
v1,0
v2,1
v3,2
Name=face
v1,5
v2,4
v3,7
Name=face
v1,4
v2,5
v3,6
Name=face
v1,1
v2,0
v3,3
Name=faces
count,10
Name=geometry
vertexcount,18
Name=vertexbuffer
positions,true
normals,true
colours_diffuse,false
texture_coords,1
texture_coord_dimensions_0,2
Name=vertex
Name=vertex
Name=position
x,3.34101563592847
y,0.0
z,-11.7980971035329
Name=texcoord
u,-12.9025558875031
v,1.0
Name=normal
x,-1.0
y,0.0
z,0.0
Name=vertex
Name=position
x,3.34101563592847
y,2.13995
z,-2.33024710353288
Name=texcoord
u,-2.54838922083648
v,-1.34027777777778
Name=normal
x,-1.0
y,0.0
z,0.0
Name=vertex
Name=position
x,3.34101563592847
y,0.0
z,-2.33024710353288
Name=texcoord
u,-2.54838922083648
v,1.0
Name=normal
x,-1.0
y,0.0
z,0.0
Name=vertex
Name=position
x,3.34101563592847
y,2.13995
z,-11.7980971035329
Name=texcoord
u,-12.9025558875031
v,-1.34027777777778
Name=normal
x,-1.0
y,0.0
z,0.0
Name=vertex
Name=position
x,6.77319063592847
y,4.07035
z,-2.33024710353288
Name=texcoord
u,7.40725135162781
v,-3.45138888888889
Name=normal
x,0.0
y,0.0
z,1.0
Name=vertex
Name=position
x,3.34101563592847
y,2.13995
z,-2.33024710353288
Name=texcoord
u,3.65377912940559
v,-1.34027777777778
Name=normal
x,0.0
y,0.0
z,1.0
Name=vertex
Name=position
x,10.2053656359285
y,0.0
z,-2.33024710353288
Name=texcoord
u,11.16072357385
v,1.0
Name=normal
x,0.0
y,0.0
z,1.0
Name=vertex
Name=position
x,3.34101563592847
y,0.0
z,-2.33024710353288
Name=texcoord
u,3.65377912940559
v,1.0
Name=normal
x,0.0
y,0.0
z,1.0
Name=vertex
Name=position
x,10.2053656359285
y,2.13995
z,-2.33024710353288
Name=texcoord
u,11.16072357385
v,-1.34027777777778
Name=normal
x,0.0
y,0.0
z,1.0
Name=vertex
Name=position
x,10.2053656359285
y,2.13995
z,-11.7980971035329
Name=texcoord
u,12.9025558875031
v,-1.34027777777778
Name=normal
x,1.0
y,0.0
z,0.0
Name=vertex
Name=position
x,10.2053656359285
y,0.0
z,-2.33024710353288
Name=texcoord
u,2.54838922083648
v,1.0
Name=normal
x,1.0
y,0.0
z,0.0
Name=vertex
Name=position
x,10.2053656359285
y,2.13995
z,-2.33024710353288
Name=texcoord
u,2.54838922083648
v,-1.34027777777778
Name=normal
x,1.0
y,0.0
z,0.0
Name=vertex
Name=position
x,10.2053656359285
y,0.0
z,-11.7980971035329
Name=texcoord
u,12.9025558875031
v,1.0
Name=normal
x,1.0
y,0.0
z,0.0
Name=vertex
Name=position
x,6.77319063592847
y,4.07035
z,-11.7980971035329
Name=texcoord
u,-7.40725135162781
v,-3.45138888888889
Name=normal
x,0.0
y,0.0
z,-1.0
Name=vertex
Name=position
x,10.2053656359285
y,2.13995
z,-11.7980971035329
Name=texcoord
u,-11.16072357385
v,-1.34027777777778
Name=normal
x,0.0
y,0.0
z,-1.0
Name=vertex
Name=position
x,3.34101563592847
y,0.0
z,-11.7980971035329
Name=texcoord
u,-3.65377912940559
v,1.0
Name=normal
x,0.0
y,0.0
z,-1.0
Name=vertex
Name=position
x,10.2053656359285
y,0.0
z,-11.7980971035329
Name=texcoord
u,-11.16072357385
v,1.0
Name=normal
x,0.0
y,0.0
z,-1.0
Name=position
x,3.34101563592847
y,2.13995
z,-11.7980971035329
Name=texcoord
u,-3.65377912940559
v,-1.34027777777778
Name=normal
x,0.0
y,0.0
z,-1.0
Name=face
v1,0
v2,1
v3,2
Name=face
v1,15
v2,14
v3,17
Name=face
v1,14
v2,15
v3,16
Name=face
v1,12
v2,9
v3,13
Name=face
v1,10
v2,9
v3,12
Name=face
v1,9
v2,10
v3,11
Name=face
v1,6
v2,5
v3,8
Name=face
v1,5
v2,6
v3,7
Name=face
v1,3
v2,0
v3,4
Name=face
v1,1
v2,0
v3,3



Axel Wheeler2010
(Crossposted from showcase thread)

Hate to bump this thread but:

A: BlitzXML is sufficiently wonderful to merit it anyway, and

B: I found a bug:

Self-closing tags without attributes seem to add a spare "/" with each save. As in <players////>. When reloaded, it then keeps all but the last slash as part of the node\Name$ string, which generally makes it unusable.

Here's my simple fix:

	xmlRootNode=xmlLoad("settings.xml")
	
	;This next loop is due to a bug in BlitzXML which passes the closing / in a tag as part of the name itself.  We remove it here.
	For n.xmlNode=Each xmlNode
		name$=n\name$
		While Right$(name$,1)="/"
			name$=Left$(name$,Len(name$)-1)
		Wend
		n\name$=name$
	Next


Thanks John J. for a great library. I'm just getting into config files and this is definitely the way to go. This website sorely needs a recommended solutions area, or rankings or something so folks don't have to browse everything to find gems like this.


Bobysait2011
little bug on error count

Function xml_AddError(Description$, pos)
	xml_ErrorCount = xml_ErrorCount + 1


xml_ErrorCount instead of xml_ErrorCound


Nice job whatever !

[edit]
an other error in the function "xml_NextItem"
1/ If txt$ <> "" And (ch = 60 Or ch = 13) Then [...]:Return txt
2/ If ch <> 13 And ch <> 15 And ch <> 10 Then txt$ = txt$ + Chr(ch)
3/ If ch = 13 And txt <> "" Then txt = txt + " "
The last condition will never happen


other error in the same function :

If Left(tag,4) = "<?" And Right(tag,2) <> "?" Then [...]

length of words does not match conditions


tyoud2014
I was having some problems with it bombing out if a tag was empty, like if you set in their example.xml - <author/>

The contents should be blank, but instead they just don't exist - and Blitz3d would bomb out. Fixed with:

;This returns a node's data string. A node's data is a string
;of text contained within the opening and closing node tags.
Function xmlNodeDataGet$(Node)
	this.xmlNode = Object.xmlNode(Node)
	If this.xmlNode <> Null Then
		Return this\Contents 	
	Else
		Return ""
	EndIf	
End Function


I was also having problems writing out empty tags, as if the contents were ending in "/", so to address that:

Function xml_WriteNode(File, Node.xmlNode)
	Local NodeContents$, Indent$, Indent2$
	
	NodeContents = Node\Name
	For i = 1 To Node\AttributeCount
		NodeContents = NodeContents + " " + Node\AttributeName[i] + "=" + Chr$(34) + Node\AttributeValue[i] + Chr$(34)
	Next
	
	Indent = String$(XML_INDENTATION$, Node\Level)
	Indent2 = String$(XML_INDENTATION$, Node\Level+1)
	
	If Node\ChildCount = 0 Then
		If Node\Contents = "" Then
			If (Right(NodeContents,1) <> "/") Then
				WriteLine File, Indent + "<" + NodeContents + "/>" 
			Else
				WriteLine File, Indent + "<" + NodeContents + ">" 
			EndIf
		Else
		    WriteLine File, Indent + "<" + NodeContents + ">" + Node\Contents + "</" + Node\Name + ">"
		End If
	Else
		WriteLine File, Indent + "<" + NodeContents + ">"
		If Node\Contents <> "" Then WriteLine File, Indent2 + Node\Contents
		
		For i = 1 To Node\ChildCount
			xml_WriteNode(File, Object.xmlNode(xmlNodeChild(Handle(Node), i)))
		Next
		
		WriteLine File, Indent + "</" + Node\Name + ">"
	End If
End Function



tyoud2014
I also editted the xmlNodeAttributeValueGet$() function so that if I had a value like <tag value="<test>"> and I needed to escape the less than and greater than symbols, I can use <tag value="&lt;test&gt;"> and translate those back to < / > on the fly back to whoever called the function, so it doesn't error out trying to match up < with < inside quotes.

Function xmlNodeAttributeValueGet$(Node, Attribute$)
	this.xmlNode = Object.xmlNode(Node)
	
	;Find the attribute
	indx=0
	For i = 1 To this\AttributeCount
		If Attribute = this\AttributeName[i] Then indx = i:Exit
	Next
	
	;If the attribute exists, return it's value. If not, return a blank string
	If indx = 0 Then
		Return ""
	Else
		; old code - return the value directly
		;Return this\AttributeValue[indx]
		; new code - substituate &lt; to < and &gt; to > 
		Return Replace$(Replace$(this\AttributeValue[indx], "&lt;", "<"), "&gt;", ">")
	End If
End Function



Guy Fawkes2015
Another preserved Library thanks to WaybackMachine! =D

http://web.archive.org/web/20070111180625/http://www.alsbonsai.com/john/BlitzXML_v1.71.zip


Code Archives Forum