Hi,
I have written an XML parser because I thought it would be a good way to save data for games. It's a bit easier to read than a list of unidentified text or colon delimited text which is what I used to use.
There are limits:
The reader isn't exactly universal. The XML definition is far too broad for that. Whilst the reader may be able to read a random XML that you throw at it, it is better if you stick to reading XMLs that have been created with this. You can see from the attached sample XML what it can handle.
There is code to read an XML from file, write an XML to file and create an XML in memory ready to write to file.
I have included a sample XML and sample code.
The sample code reads the sample XML and writes it back again. It also creates an XML in memory and writes that to file.
The code is not very commented but there is a large comments section at the start which explains the functions that you need to use.
I have attached a rar file containing the main project, the XML include file and the test xml.
But for anyone that doesn't like downloading archives (You know who you are) then here are the three files:
Main.agk
// Project: XML test
// Created: 2015-05-16
// set window properties
SetWindowTitle( "XML test" )
SetWindowSize( 640, 480, 0 )
// set display properties
SetVirtualResolution( 640, 480 )
#include "XML.agc"
#include "../_libs/String Functions.agc"
//Read an XML file from disk into memory
testXML = XML_Read( 1, "test.xml" )
//Now write the very same file in a different place
XML_Write( testXML, 1, "TestWrite.xml" )
//Call a function in which we create an XML file entirely with code
fruitBowlXML = XML_CreateFromScratch()
do
//Demonstrate that we can parse the contents of the file we just read from disk
//Print the root
print( "Root = " + XML_GetRoot( testXML ) )
//Where is the player?
Print( "Player is positioned at " + XML_GetAttributeValue( testXML, "player", "pos", "x" ) + ", " + XML_GetAttributeValue( testXML, "player", "pos", "y" ))
//What is the alpha avalue of the player?
Print( "Players alpha = " + XML_GetAttributeValue( testXML, "player", "color", "alpha" ) )
//Where is the enemy?
Print( "Enemy is positioned at " + XML_GetAttributeValue( testXML, "enemy", "pos", "x" ) + ", " + XML_GetAttributeValue( testXML, "enemy", "pos", "y" ))
//What zoom level are we set to?
print( "Zoom = " + XML_GetElementContent( testXML, "settings", "zoom" ) )
//How many clouds?
print("There are " + XML_GetElementContent( testXML, "XMLtest", "clouds" ) + " clouds")
//How many birds?
//NOTE: If the elements parent is the Root we can use an empty string
print("There are " + XML_GetElementContent( testXML, "", "birds" ) + " birds")
//Again - an empty root for attributes
print( "The score for a bullseye is " + XML_GetAttributeValue( testXML, "", "score", "bullseye" ))
//Now what about that Fruit Bowl? - it's still in memory
print("")
//What color is a banana?
Print( "The banana is " + XML_GetElementContent( fruitBowlXML, "banana", "color" ) )
Sync()
loop
function XML_CreateFromScratch()
//Create an internal XML file to write to
xmlID = XML_Create()
//Set the root
XML_SetRoot( xmlID, "Fruit_Bowl" )
// Add Contents
XML_SetElement( xmlID, "", "settings", "" )
XML_SetElement( xmlID, "settings", "name", "In The Fruit Bowl" )
XML_SetElement( xmlID, "settings", "resolution", "percentage" )
XML_SetElement( xmlID, "settings", "zoom", "1.0" )
XML_SetAttribute( xmlID, "settings", "screen", "width", "1280" )
XML_SetAttribute( xmlID, "settings", "screen", "height", "720" )
XML_SetElement( xmlID, "", "banana", "" )
XML_SetAttribute( xmlID, "banana", "pos", "x", "90" )
XML_SetAttribute( xmlID, "banana", "pos", "y", "4.2" )
XML_SetAttribute( xmlID, "banana", "pos", "z", "12" )
XML_SetElement( xmlID, "banana", "bent", "yes" )
XML_SetElement( xmlID, "banana", "color", "yellow" )
XML_SetElement( xmlID, "banana", "scale", "0.1" )
XML_SetElement( xmlID, "", "grapes", "10" )
XML_SetElement( xmlID, "", "apples", "5" )
XML_SetElement( xmlID, "", "oranges", "" )
XML_SetAttribute( xmlID, "", "oranges", "zesty", "very" )
XML_SetAttribute( xmlID, "", "oranges", "juicy", "hell yeah!" )
XML_SetElement( xmlID, "", "Bowl", "" )
XML_SetAttribute( xmlID, "Bowl", "pos", "x", "10" )
XML_SetAttribute( xmlID, "Bowl", "pos", "y", "25" )
XML_SetAttribute( xmlID, "Bowl", "pos", "z", "64" )
XML_SetElement( xmlID, "Bowl", "size", "10" )
XML_SetElement( xmlID, "Bowl", "color", "Red" )
XML_SetElement( xmlID, "Bowl", "scale", "1.5" )
//Write the XML to file
XML_Write( xmlID, 1, "Fruit.xml" )
endfunction xmlID
Sample.xml
<?xml version="1.0"?>
<!--XML file created using Scraggle's XML writer-->
<XMLtest>
<settings>
<name>My New Game</name>
<resolution>percentage</resolution>
<zoom>1.0</zoom>
<size width="1280" height="720"/>
</settings>
<player>
<pos x="90" y="4.2"/>
<angle>180</angle>
<color red="255" green="128" blue="64" alpha="255"/>
<scale>0.5</scale>
</player>
<clouds>10</clouds>
<birds>5</birds>
<score outer="100" inner="200" bullseye="500"/>
<enemy>
<pos x="25" y="36"/>
<angle>270</angle>
<color red="0" green="128" blue="255" alpha="255"/>
<scale>0.75</scale>
<speed>5</speed>
</enemy>
</XMLtest>
XML.agc
/*******************************************************************
Title: XML Parser
Author: Scraggle - aka Craig Bryan
Published - 17th May 2015
********************************************************************
********************************************************************
USER FUNCTIONS:
ID = XML_Read( fID, filename$ )
XML_Write( ID, fID, filename$ )
XML_Create()
XML_SetRoot( ID, root$ )
XML_SetElement( ID, parent$, tag$, content$ )
XML_SetAttribute( ID, parent$, element$, name$, value$ )
XML_GetRoot( ID )
XML_GetElementContent( ID, parent$, element$ )
XML_GetAttributeValue( ID, parent$, element$, name$ )
To read an XML:
ID = XML_Read( fID, filename$ )
Parameters:
fID - The AGK file ID to be used during OpenToRead()
filename$ - The filename and path of the XML file to read
Returns:
ID - an internal idnetifier to this XML file in memory
To write a file:
XML_Write( ID, fID, filename$ )
Parameters:
ID - The internal reference to the XML in memory
fID - The AGK file ID to be used during OpenToWrite()
filename$ - The filename and path of the XML file to read
Returns:
TRUE or FALSE depending if write was successfull
To create a file in memory ready to write it to disk:
XML_Create()
Parameters:
{none}
Returns:
The ID of this XML file in memory
XML_SetRoot( ID, root$ )
Parameters:
ID - The internal reference to the XML in memory
root$ = A string to name the root element in the file
Returns:
TRUE or FALSE depending if the function was successfull
XML_SetElement( ID, parent$, tag$, content$ )
Parameters:
ID - The internal reference to the XML in memory
parent$ - The name of the parent for the element - if an empty string is passed the the root element is assumed
tag$ - the <tag> part of the element
content$ - The <tag>content</tag> part of the element
Returns:
TRUE or FALSE depending if the function was successfull
EXAMPLE ELEMENT 1:
<this_elemnt>I am the content of the element called this_lement</this_element>
EXAMPLE ELEMENT 2:
<element>
<another_element>This one has conent</another_element>
<yet_another_element>This one has conent</yet_another_element>
</element>
XML_SetAttribute( ID, parent$, element$, name$, value$ )
Parameters:
ID - The internal reference to the XML in memory
parent$ - The name of the parent for the element - if an empty string is passed the the root element is assumed
element$ - The element that this attribute belongs to - if this doesn't already exist it will be created
name$ - the identifeying name of the attribute
value - the value of the attribute
Returns:
TRUE or FALSE depending if the function was successfull
EXAMPLE ATTRIBUTE
<element attribute1="Hello" attribute2="World"/>
To parse the contents of an XML file the has been loaded or created in memory:
XML_GetRoot( ID )
Parametrs:
ID - The internal reference to the XML in memory
Returns:
The root name of the XML file
XML_GetElementContent( ID, parent$, element$ )
Parametrs:
ID - The internal reference to the XML in memory
parent$ - the parent of the element you are wanting to read - a blank string assumes the root
element$ = The name of the element to read
Returns:
The content of the given element
XML_GetAttributeValue( ID, parent$, element$, name$ )
Parametrs:
ID - The internal reference to the XML in memory
parent$ - the parent of the element that the attribute belongs to
element$ = The name of the element that the attribute belongs to
name - the name of the attribute whose value we are to read
Returns:
The value aof thh attribute
*******************************************************************/
#constant TRUE 1
#constant FALSE 0
#constant EMPTYLINE 0
#constant STARTTAG 1
#constant CLOSETAG 2
#constant ELEMENT 3
#constant ATTRIBUTE 4
#constant SINGLE 1
#constant MULTI 2
type tXML
root as string
ele as tElement[]
qtyMulti as integer
endtype
type tElement
written as integer
parent as string
tag as string
content as string
att as tAttribute[]
endtype
type tAttribute
name as string
value as string
endtype
global xml as tXML[]
function XML_Create()
xml.length = xml.length + 1
endfunction xml.length
/*******************************************************************
SETTERS
*******************************************************************/
function XML_SetRoot( ID, root$ )
//Sets the ROOT element for the XML file
//Fail if not a valid XML ID
if ID > xml.length or ID < 0
exitfunction -1
endif
XML[ID].root = root$
endfunction 1
function XML_SetElement( ID, parent$, tag$, content$ )
//Creates an element that is the child of parent$ which may or may not be the root$
//Fail if not a valid XML ID
if (ID < 0) or (ID > xml.length )
exitfunction -1
endif
//If parent$ is blank assign it to root
if parent$ = "" or parent$ = xml[ID].root
parent$ = xml[ID].root
else
//Fail if parent does not exist
fail = TRUE
for k = 0 to xml[ID].ele.length
if xml[ID].ele[k].tag = parent$
fail = FALSE
continue
endif
next k
if fail = TRUE
exitfunction -2
endif
endif
//It's all good - insert element
this as tElement
this.parent = parent$
this.tag = tag$
this.content = content$
this.written = FALSE
xml[ID].ele.insert(this)
if content$ = ""
inc xml[ID].qtyMulti
endif
endfunction 1
function XML_SetAttribute( ID, parent$, element$, name$, value$ )
//Creates attribute name and value pairs and assignes them to a previously declared blank element
//or creates the element if it doesn't already exist
//Fail if not a valid XML ID
if ID > xml.length or ID < 0
exitfunction -1
endif
//If parent$ is blank assign it to root
if parent$ = "" or parent$ = xml[ID].root
parent$ = xml[ID].root
else
//Fail if parent does not exist
fail = TRUE
for k = 0 to xml[ID].ele.length
if xml[ID].ele[k].tag = parent$
fail = FALSE
continue
endif
next k
if fail = TRUE
exitfunction -1
endif
endif
//if element doesn't exist then create it
e = XML_GetElementID( ID, parent$, element$ )
if e = -1
XML_SetElement( ID, parent$, element$, "" )
e = xml[ID].ele.length
endif
//It's all good - insert attribute
this as tAttribute
this.name = name$
this.value = value$
xml[ID].ele[e].att.insert(this)
endfunction 1
/*******************************************************************
GETTERS
/******************************************************************/
function XML_GetElementStyle( ID, e )
if xml[ID].ele[e].att.length > -1
exitfunction ATTRIBUTE
endif
if xml[ID].ele[e].content = ""
exitfunction MULTI
endif
endfunction SINGLE
function XML_GetTabs( qtyTabs )
t$ = ""
if qtyTabs > 0
for t = 1 to qtyTabs
t$ = t$ + TAB$
next t
endif
endfunction t$
function XML_GetElementID( ID, parent$, element$ )
for k = 0 to xml[ID].ele.length
if (xml[ID].ele[k].parent = parent$) and (xml[ID].ele[k].tag = element$ )
exitfunction k
endif
next k
endfunction -1
function XML_GetElementContent( ID, parent$, element$ )
if parent$ = "" then parent$ = XML_GetRoot( ID )
for k = 0 to xml[ID].ele.length
if (xml[ID].ele[k].parent = parent$) and (xml[ID].ele[k].tag = element$ )
exitfunction xml[ID].ele[k].content
endif
next k
endfunction "ERROR"
function XML_GetAttributeValue( ID, parent$, element$, name$ )
if parent$ = "" then parent$ = XML_GetRoot( ID )
for k = 0 to xml[ID].ele.length
if (xml[ID].ele[k].parent = parent$) and (xml[ID].ele[k].tag = element$ )
for a = 0 to xml[ID].ele[k].att.length
if xml[ID].ele[k].att[a].name = name$
exitfunction xml[ID].ele[k].att[a].value
endif
next a
endif
next k
endfunction "ERROR"
function XML_GetRoot( ID )
endfunction xml[ID].root
/*******************************************************************
WRITE XML FILE
*******************************************************************/
function XML_Write( ID, fID, filename$ )
//Fail if not a valid XML ID
if ID > xml.length or ID < 0
exitfunction -1
endif
OpenToWrite( fID, filename$ )
WriteLine( fID, "<?xml version="+ QUOTE$ + "1.0" + QUOTE$ + "?>" )
WriteLine( fID, "<!--XML file created using Scraggle's XML writer-->" + CRLF$ )
WriteLine( fID, "<" + xml[ID].root + ">" )
WriteLine( fID, "" )
for k = 1 to xml[ID].qtyMulti
XML_WriteElement( ID, fID, xml[ID].root, 0, 1 )
next k
WriteLine( fID, "</" + xml[ID].root + ">" )
CloseFile( fID )
endfunction 1
function XML_WriteElement( ID, fID, parent$, k, qtyTabs )
if k > xml[ID].ele.length
dec qtyTabs
WriteLine( fID, XML_GetTabs( qtyTabs ) + "</" + parent$ + ">" )
WriteLine( fID, "" )
exitfunction
endif
if xml[ID].ele[k].written = TRUE
XML_WriteElement( ID, fID, parent$, k+1, qtyTabs )
exitfunction
endif
if xml[ID].ele[k].parent <> parent$
XML_WriteElement( ID, fID, parent$, k+1, qtyTabs )
exitfunction
endif
t$ = XML_GetTabs( qtyTabs )
style = XML_GetElementStyle( ID, k )
select style
case SINGLE
writeLine( fID, t$ + "<" + xml[ID].ele[k].tag + ">" + xml[ID].ele[k].content + "</" + xml[ID].ele[k].tag + ">" )
xml[ID].ele[k].written = TRUE
if xml[ID].ele[k].parent = xml[ID].root
WriteLine( fID, "" )
endif
XML_WriteElement( ID, fID, parent$, k+1, qtyTabs )
endcase
case MULTI
inc qtyTabs
writeLine( fID, t$ + "<" + xml[ID].ele[k].tag + ">" )
xml[ID].ele[k].written = TRUE
XML_WriteElement( ID, fID, xml[ID].ele[k].tag, k+1, qtyTabs )
endcase
case ATTRIBUTE
line$ = t$ + "<" + xml[ID].ele[k].tag
for a = 0 to xml[ID].ele[k].att.length
line$ = line$ + " " + xml[ID].ele[k].att[a].name + "=" + QUOTE$ + xml[ID].ele[k].att[a].value + QUOTE$
next a
WriteLine( fID, line$ + "/>" )
if xml[ID].ele[k].parent = xml[ID].root
WriteLine( fID, "" )
endif
xml[ID].ele[k].written = TRUE
dec xml[ID].qtyMulti
XML_WriteElement( ID, fID, parent$, k+1, qtyTabs )
endcase
endselect
endfunction
/*******************************************************************
READ XML FILE
*******************************************************************/
function XML_Read( fID, filename$ )
ID = XML_Create()
if not GetFileExists( filename$ )
exitfunction -1
endif
tempEle as tElement
OpenToRead( fID, filename$ )
h$ = ReadLine( fID )
c$ = ReadLine( fID )
b$ = ReadLine( fID )
r$ = ReadLine( fID )
xml[ID].root = extractString( r$, "<", ">", 0 )
parent$ = xml[ID].root
repeat
l$ = ReadLine(fID)
value = XML_AnalyseLine(l$)
select value
case STARTTAG
XML_SetElement( ID, parent$, extractString(l$, "<", ">", 0 ), "" )
parent$ = extractString(l$, "<", ">", 0 )
endcase
case CLOSETAG
parent$ = xml[ID].root
endcase
case ELEMENT
XML_SetElement( ID, parent$, extractString(l$, "<", ">", 0 ), extractString(l$, ">", "<", 0 ) )
endcase
case ATTRIBUTE
qty = countString( l$, "=", 0 )-1
attList as tAttribute[10]
XML_ExtractAttributes( l$, attList )
ele$ = extractString(l$, "<", " ", 0)
for k = 0 to qty
XML_SetAttribute( ID, parent$, ele$, attList[k].name, attlist[k].value )
next k
endcase
endselect
until FileEOF( fID )
CloseFile( fID )
endfunction ID
function XML_AnalyseLine(l$)
if instr(l$, "<", 0) = 0
exitfunction EMPTYLINE
endif
openBrackets = CountString( l$, "<", 0 )
if openBrackets = 2
exitfunction ELEMENT
endif
if openBrackets = 1
//It's either an open/close tag or it contains attributes
if CountString(l$, chr(34), 0)
exitfunction ATTRIBUTE
else
//Not an attribute so...
if CountString(l$, "/", 0)
exitfunction CLOSETAG
else
exitfunction STARTTAG
endif
endif
endif
endfunction -1
function XML_ExtractAttributes( l$, attList REF as tAttribute[] )
qty = countString( l$, "=", 0 )
for k = 0 to qty-1
if k = 0
start = 0
lastStart = 0
else
start = instr( l$, QUOTE$ + " ", lastStart ) + 1
lastStart = start + 1
endif
attList[k].name = extractString( l$, " ", "=", start )
attList[k].value = extractString( l$, QUOTE$, QUOTE$, start )
next k
endfunction
OOPS!
I forgot to include this file in the rar. You will need these additional string functions too:
/*******************************************************************
Title: STRING FUNCTIONS
Author: Scraggle - aka Craig Bryan
Published - 17th May 2015
********************************************************************
Functions:
replaceString()
replaces one or more occurences of a tring with another if found inside the 'source' string
insertString()
inserts a string inside another at the requested position
rightFrom()
returns a string that is the right of the source string except for the number of characters specified by 'pos'
extractString()
returns whatever appears in the source string between the two strings passed to it
removeString()
returns the 'source' string without the 'remove' part in it
instr()
returns the position of a string inside another
countString()
returns the number of times one string appears within another
quote()
returns a string inside double quotes
padLeft()
inserts a number of characters to the left of the source string.
Handy for displaying a score for example:
456 becomes 00456
padRight()
Much like padLeft but to the right instead
CR$ - inserts a Carriage Return
CR$ - inserts a Line Feed
CRLF$ - inserts a Carriage Return and a line feed
TAB$ - inserts a TAB
QUOTE$ - inserts souble quotes
*/
#constant CR$ chr(13)
#constant LF$ chr(10)
#constant CRLF$ chr(13)+chr(10)
#constant TAB$ chr(9)
#constant QUOTE$ chr(34)
////////////////////////
//PAD LEFT
function padLeft( source as string, pad as string, maxSize as integer )
r$ = source
size = len(source)
if size>=maxSize then exitfunction source
pad$ = left(pad, 1)
for k = 1 to maxSize-size
insert$ = insert$ + pad$
next k
r$ = insertString( source, insert$, 0 )
endfunction r$
////////////////////////
//PAD RIGHT
function padRight( source as string, pad as string, maxSize as integer )
r$ = source
size = len(source)
if size>=maxSize then exitfunction source
pad$ = left(pad, 1)
for k = 1 to maxSize-size
insert$ = insert$ + pad$
next k
r$ = insertString( source, insert$, len(source) )
endfunction r$
////////////////////////
//QUOTE STRING
function quote( source$ )
q$ = QUOTE$+source$+QUOTE$
endfunction q$
////////////////////////
//REPLACE STRING
function replaceString( source as string, removeThis as string, insertThis as string, qty as integer )
r$=source
if qty < 1 then exitfunction source
found = countString(source, removeThis, 0)
if found = 0 then exitfunction source
if qty > found then qty = found
lastStart = 0
for k = 1 to qty
startPos = instr(r$, removeThis, lastStart)-1
r$ = removeString(r$, removeThis)
r$ = insertString(r$, insertThis, startPos )
next k
endfunction r$
////////////////////////
//INSERT STRING
function insertString( source as string, insert as string, pos as integer )
r$ = source
if pos < 0 then pos = 0
left$ = left(source, pos )
right$ = rightString( source, pos)
r$ = left$+insert+right$
endfunction r$
////////////////////////
//RIGHT FROM
function rightString( source as string, pos as integer )
r$ = mid(source, pos+1, -1)
endfunction r$
////////////////////////
//EXTRACT STRING
function extractString( source as string, left$ as string, right$ as string, start as integer )
r$ = source
leftPos = instr(source, left$, start) + 1
rightPos = instr(source, right$, leftPos+1)
r$ = mid(source, leftPos, rightPos-leftPos)
endfunction r$
////////////////////////
//REMOVE STRING
function removeString( source as string, remove as string )
r$=source
if instr(source, remove, 0) = -1
exitfunction source
endif
start = instr(source, remove, 0)
if start>-1
left$=left(source, start-1)
else
left$=""
endif
right$ = mid( source, start+len(remove), -1)
r$ = left$+right$
endfunction r$
///////////////////////////
// INSTR
function instr(source as string, search as string, start as integer )
if (start > len(source)-len(search)+1) or (start < 0)
exitfunction -1
endif
for k = start to len(source)-len(search)+1
if mid(source, k, len(search)) = search
exitfunction k
endif
next k
endfunction -1
//////////////////////////
// COUNT
function countString( source as string, search as string, start as integer )
if (start > len(source)-len(search)+1) or (start < 0)
exitfunction 0
endif
count = 0
for k = 0 to len(source)
if mid(source, k, len(search)) = search
inc count
endif
next k
endfunction count
AGK V2 user - Tier 1 (mostly)