Understanding AS3 and XML

When you are about to design a Flash application using dynamic content, such as an image gallery or a tile based game, XML is one of the best ways to handle external content because it’s very easy to create, understand, read, and update.

AS3 now uses a system called ECMAScript for XML (E4X) that will do most of the dirty job, allowing us to focus on the application we are goingo to write.

From Wikipedia: ECMAScript for XML (E4X) is a programming language extension that adds native XML support to ECMAScript (which includes ActionScript, DMDScript, JavaScript, JScript). The goal is to provide an alternative to DOM interfaces that uses a simpler syntax for accessing XML documents. It also offers a new way of making XML visible. Before the release of E4X, XML was always accessed at an object level. E4X instead treats XML as a primitive (like characters, integers, and booleans). This implies faster access, better support, and acceptance as a building block (data structure) of a program.

So let’s start playing with it, and let me show you the one of the worst XML you can find… a collection of Sokoban levels (guess why?)



  Fly
  Simple Microban-style levels.
  bsimpson_910@hotmail.com
  
    
         ####
         #  #
        ##  ####
      ###.$.$. #
      #  $.$.$ ##
      #  .$@$.  #
      ## $.$.$  #
       # .$.$.###
       ####  ##
          #  #
          ####
    
    
          ####
          #  #
        ###  ####
      ### .$.$. #
      #   $.$.$ ###
      #   .$#$.   #
      ### $.$.$   #
        # .$.$. ###
        ####  ###
           # @#
           ####
    
    
           ####
          ##  ##
          #    ###
      #####$.$.$ ###
      #   $.#.#.$  ##
      #    $.@.$    #
      ##  $.#.#.$   #
       ### $.$.$#####
         ###    #
           ##  ##
            ####
    
  

This is one of the worst XML you can find because it has an unknown number of nodes (I don’t know how many levels has a level collection) and some nodes with multiple attributes.

But you’ll see parsing it with AS3 will be very easy anyway.

Let’s see how to read the xml file:

package {
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.net.URLRequest;
	import flash.net.URLLoader;
	public class sokoxml extends Sprite {
		public function sokoxml() {
			var loader:URLLoader=new URLLoader;
			var req:URLRequest=new URLRequest("sokobanxml.xml");
			loader.addEventListener(Event.COMPLETE,on_xml_completed);
			loader.load(req);
		}
		public function on_xml_completed(event:Event):void {
			var xml_to_parse:XML=new XML(event.target.data);
			trace(xml_to_parse)
		}
	}
}

This would outputs the entire XML file in the same way it was created. Let’ see how does it work:

Lines 8-9: Setting the XML file to load

Line 10: Adding a listener to check if the XML has been completely loaded

Line 11: Loading the file

Line 14: Transforming the file in a XML variable. Not it’s ready to be parsed

The first things we want to look for in an XML file are elements. The elements method lists the elements of an XML object.

package {
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.net.URLRequest;
	import flash.net.URLLoader;
	public class sokoxml extends Sprite {
		public function sokoxml() {
			var loader:URLLoader=new URLLoader  ;
			var req:URLRequest=new URLRequest("sokobanxml.xml");
			loader.addEventListener(Event.COMPLETE,on_xml_completed);
			loader.load(req);
		}
		public function on_xml_completed(event:Event):void {
			var xml_to_parse:XML=new XML(event.target.data);
			for each (var item:XML in xml_to_parse.elements()) {
				trace(item.name()+": "+item+"\n--------------");
			}
		}
	}
}

With the for each loop we can parse all elements even if we do not know how many of them we have in our XML file. The output is pretty similar to the original file because elements method does not care if an element has children.

Title: Fly
--------------
Description: Simple Microban-style levels.
--------------
Email: bsimpson_910@hotmail.com
--------------
LevelCollection: 
  
    ####
    #  #
    ##  ####
    ###.$.$. #
    #  $.$.$ ##
    #  .$@$.  #
    ## $.$.$  #
    # .$.$.###
    ####  ##
    #  #
    ####
  
  
    ####
    #  #
    ###  ####
    ### .$.$. #
    #   $.$.$ ###
    #   .$#$.   #
    ### $.$.$   #
    # .$.$. ###
    ####  ###
    # @#
    ####
  
  
    ####
    ##  ##
    #    ###
    #####$.$.$ ###
    #   $.#.#.$  ##
    #    $.@.$    #
    ##  $.#.#.$   #
    ### $.$.$#####
    ###    #
    ##  ##
    ####
  

--------------

So the first thing to do is to create a recursive function, because we don’t know if a child of an element has its own children, that can have children, and so on. That’s why I am approaching this problem with recursive functions.

package {
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.net.URLRequest;
	import flash.net.URLLoader;
	public class sokoxml extends Sprite {
		public function sokoxml() {
			var loader:URLLoader=new URLLoader  ;
			var req:URLRequest=new URLRequest("sokobanxml.xml");
			loader.addEventListener(Event.COMPLETE,on_xml_completed);
			loader.load(req);
		}
		public function on_xml_completed(event:Event):void {
			var xml_to_parse:XML=new XML(event.target.data);
			for each (var item:XML in xml_to_parse.elements()) {
				recursive(item);
			}
		}
		public function recursive(xml:XML) {
			trace("NAME: "+xml.name());
			trace("VALUE: "+xml.children());
			trace("-----------------");
		}
	}
}

As you can see, the result does not change…

NAME: Title
VALUE: Fly
-----------------
NAME: Description
VALUE: Simple Microban-style levels.
-----------------
NAME: Email
VALUE: bsimpson_910@hotmail.com
-----------------
NAME: LevelCollection
VALUE: 
  ####
  #  #
  ##  ####
  ###.$.$. #
  #  $.$.$ ##
  #  .$@$.  #
  ## $.$.$  #
  # .$.$.###
  ####  ##
  #  #
  ####


  ####
  #  #
  ###  ####
  ### .$.$. #
  #   $.$.$ ###
  #   .$#$.   #
  ### $.$.$   #
  # .$.$. ###
  ####  ###
  # @#
  ####


  ####
  ##  ##
  #    ###
  #####$.$.$ ###
  #   $.#.#.$  ##
  #    $.@.$    #
  ##  $.#.#.$   #
  ### $.$.$#####
  ###    #
  ##  ##
  ####

-----------------

Now it’s time to scan each element and see if it has children. The children method lists the children of the XML object in the sequence in which they appear. So, now for each element I am scanning for children, and for each child (if any), for children of that child, and so on.

package {
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.net.URLRequest;
	import flash.net.URLLoader;
	public class sokoxml extends Sprite {
		public function sokoxml() {
			var loader:URLLoader=new URLLoader  ;
			var req:URLRequest=new URLRequest("sokobanxml.xml");
			loader.addEventListener(Event.COMPLETE,on_xml_completed);
			loader.load(req);
		}
		public function on_xml_completed(event:Event):void {
			var xml_to_parse:XML=new XML(event.target.data);
			for each (var item:XML in xml_to_parse.elements()) {
				recursive(item);
			}
		}
		public function recursive(xml:XML) {
			trace("NAME: "+xml.name());
			if (xml.children()==xml) {
				trace("VALUE: "+xml.children());
				trace("-----------------");
			} else {
				trace("This node has children:");
				for each (var item:XML in xml.children()) {
					recursive(item);
				}
			}
		}
	}
}

Now the XML has been completely parsed, I can see if a node has children and list them properly

NAME: Title
VALUE: Fly
-----------------
NAME: Description
VALUE: Simple Microban-style levels.
-----------------
NAME: Email
VALUE: bsimpson_910@hotmail.com
-----------------
NAME: LevelCollection
This node has children:
NAME: Level
This node has children:
NAME: L
VALUE: ####
-----------------
NAME: L
VALUE: #  #
-----------------
NAME: L
VALUE: ##  ####
-----------------
NAME: L
VALUE: ###.$.$. #
-----------------
NAME: L
VALUE: #  $.$.$ ##
-----------------
NAME: L
VALUE: #  .$@$.  #
-----------------
NAME: L
VALUE: ## $.$.$  #
-----------------
NAME: L
VALUE: # .$.$.###
-----------------
NAME: L
VALUE: ####  ##
-----------------
NAME: L
VALUE: #  #
-----------------
NAME: L
VALUE: ####
-----------------
NAME: Level
This node has children:
NAME: L
VALUE: ####
-----------------
NAME: L
VALUE: #  #
-----------------
NAME: L
VALUE: ###  ####
-----------------
NAME: L
VALUE: ### .$.$. #
-----------------
NAME: L
VALUE: #   $.$.$ ###
-----------------
NAME: L
VALUE: #   .$#$.   #
-----------------
NAME: L
VALUE: ### $.$.$   #
-----------------
NAME: L
VALUE: # .$.$. ###
-----------------
NAME: L
VALUE: ####  ###
-----------------
NAME: L
VALUE: # @#
-----------------
NAME: L
VALUE: ####
-----------------
NAME: Level
This node has children:
NAME: L
VALUE: ####
-----------------
NAME: L
VALUE: ##  ##
-----------------
NAME: L
VALUE: #    ###
-----------------
NAME: L
VALUE: #####$.$.$ ###
-----------------
NAME: L
VALUE: #   $.#.#.$  ##
-----------------
NAME: L
VALUE: #    $.@.$    #
-----------------
NAME: L
VALUE: ##  $.#.#.$   #
-----------------
NAME: L
VALUE: ### $.$.$#####
-----------------
NAME: L
VALUE: ###    #
-----------------
NAME: L
VALUE: ##  ##
-----------------
NAME: L
VALUE: ####
-----------------

The last thing I have to do is checking for attributes. An element can have one or more attributes, or can have no attributes. Also, I don’t know their names and values. That’s when attribute method comes and saves me. attribute returns a list of attribute values for the given XML object.

package {
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.net.URLRequest;
	import flash.net.URLLoader;
	public class sokoxml extends Sprite {
		public function sokoxml() {
			var loader:URLLoader=new URLLoader  ;
			var req:URLRequest=new URLRequest("sokobanxml.xml");
			loader.addEventListener(Event.COMPLETE,on_xml_completed);
			loader.load(req);
		}
		public function on_xml_completed(event:Event):void {
			var xml_to_parse:XML=new XML(event.target.data);
			for each (var item:XML in xml_to_parse.elements()) {
				recursive(item);
			}
		}
		public function recursive(xml:XML) {
			trace("NAME: "+xml.name());
			for each (var att:XML in xml.attributes()) {
				trace("Attribute: "+att.name()+" = "+att);
			}
			if (xml.children()==xml) {
				trace("VALUE: "+xml.children());
				trace("-----------------");
			} else {
				trace("This node has children:");
				for each (var item:XML in xml.children()) {
					recursive(item);
				}
			}
		}
	}
}

And finally the XML has been fully parsed, attributes included

NAME: Title
VALUE: Fly
-----------------
NAME: Description
VALUE: Simple Microban-style levels.
-----------------
NAME: Email
VALUE: bsimpson_910@hotmail.com
-----------------
NAME: LevelCollection
Attribute: Copyright = Tim LeFevre
Attribute: MaxWidth = 15
Attribute: MaxHeight = 11
This node has children:
NAME: Level
Attribute: Id = The Fly_1
Attribute: Width = 11
Attribute: Height = 11
This node has children:
NAME: L
VALUE: ####
-----------------
NAME: L
VALUE: #  #
-----------------
NAME: L
VALUE: ##  ####
-----------------
NAME: L
VALUE: ###.$.$. #
-----------------
NAME: L
VALUE: #  $.$.$ ##
-----------------
NAME: L
VALUE: #  .$@$.  #
-----------------
NAME: L
VALUE: ## $.$.$  #
-----------------
NAME: L
VALUE: # .$.$.###
-----------------
NAME: L
VALUE: ####  ##
-----------------
NAME: L
VALUE: #  #
-----------------
NAME: L
VALUE: ####
-----------------
NAME: Level
Attribute: Id = The Fly_2
Attribute: Width = 13
Attribute: Height = 11
This node has children:
NAME: L
VALUE: ####
-----------------
NAME: L
VALUE: #  #
-----------------
NAME: L
VALUE: ###  ####
-----------------
NAME: L
VALUE: ### .$.$. #
-----------------
NAME: L
VALUE: #   $.$.$ ###
-----------------
NAME: L
VALUE: #   .$#$.   #
-----------------
NAME: L
VALUE: ### $.$.$   #
-----------------
NAME: L
VALUE: # .$.$. ###
-----------------
NAME: L
VALUE: ####  ###
-----------------
NAME: L
VALUE: # @#
-----------------
NAME: L
VALUE: ####
-----------------
NAME: Level
Attribute: Id = The Fly_3
Attribute: Width = 15
Attribute: Height = 11
Attribute: Copyright = 1106 1087 Jordi Doménech
This node has children:
NAME: L
VALUE: ####
-----------------
NAME: L
VALUE: ##  ##
-----------------
NAME: L
VALUE: #    ###
-----------------
NAME: L
VALUE: #####$.$.$ ###
-----------------
NAME: L
VALUE: #   $.#.#.$  ##
-----------------
NAME: L
VALUE: #    $.@.$    #
-----------------
NAME: L
VALUE: ##  $.#.#.$   #
-----------------
NAME: L
VALUE: ### $.$.$#####
-----------------
NAME: L
VALUE: ###    #
-----------------
NAME: L
VALUE: ##  ##
-----------------
NAME: L
VALUE: ####
-----------------

This was the hardest situation, when you don’t exactly know the structure of your XML tree.

But a good PROgrammer always knows the structure of the data he’s playing with, so next time we’ll see how to parse this sokoban level in order to draw it on the screen.

Get the most popular Phaser 3 book

Through 202 pages, 32 source code examples and an Android Studio project you will learn how to build cross platform HTML5 games and create a complete game along the way.

Get the book

215 GAME PROTOTYPES EXPLAINED WITH SOURCE CODE
// 1+2=3
// 100 rounds
// 10000000
// 2 Cars
// 2048
// A Blocky Christmas
// A Jumping Block
// A Life of Logic
// Angry Birds
// Angry Birds Space
// Artillery
// Astro-PANIC!
// Avoider
// Back to Square One
// Ball Game
// Ball vs Ball
// Ball: Revamped
// Balloon Invasion
// BallPusher
// Ballz
// Bar Balance
// Bejeweled
// Biggification
// Block it
// Blockage
// Bloons
// Boids
// Bombuzal
// Boom Dots
// Bouncing Ball
// Bouncing Ball 2
// Bouncy Light
// BoxHead
// Breakout
// Bricks
// Bubble Chaos
// Bubbles 2
// Card Game
// Castle Ramble
// Chronotron
// Circle Chain
// Circle Path
// Circle Race
// Circular endless runner
// Cirplosion
// CLOCKS - The Game
// Color Hit
// Color Jump
// ColorFill
// Columns
// Concentration
// Crossy Road
// Crush the Castle
// Cube Jump
// CubesOut
// Dash N Blast
// Dashy Panda
// Deflection
// Diamond Digger Saga
// Don't touch the spikes
// Dots
// Down The Mountain
// Drag and Match
// Draw Game
// Drop Wizard
// DROP'd
// Dudeski
// Dungeon Raid
// Educational Game
// Elasticity
// Endless Runner
// Erase Box
// Eskiv
// Farm Heroes Saga
// Filler
// Flappy Bird
// Fling
// Flipping Legend
// Floaty Light
// Fuse Ballz
// GearTaker
// Gem Sweeper
// Globe
// Goat Rider
// Gold Miner
// Grindstone
// GuessNext
// Helicopter
// Hero Emblems
// Hero Slide
// Hexagonal Tiles
// HookPod
// Hop Hop Hop Underwater
// Horizontal Endless Runner
// Hundreds
// Hungry Hero
// Hurry it's Christmas
// InkTd
// Iromeku
// Jet Set Willy
// Jigsaw Game
// Knife Hit
// Knightfall
// Legends of Runeterra
// Lep's World
// Line Rider
// Lumines
// Magick
// MagOrMin
// Mass Attack
// Math Game
// Maze
// Meeblings
// Memdot
// Metro Siberia Underground
// Mike Dangers
// Mikey Hooks
// Nano War
// Nodes
// o:anquan
// One Button Game
// One Tap RPG
// Ononmin
// Pacco
// Perfect Square!
// Perfectionism
// Phyballs
// Pixel Purge
// PixelField
// Planet Revenge
// Plants Vs Zombies
// Platform
// Platform game
// Plus+Plus
// Pocket Snap
// Poker
// Pool
// Pop the Lock
// Pop to Save
// Poux
// Pudi
// Pumpkin Story
// Puppet Bird
// Pyramids of Ra
// qomp
// Quick Switch
// Racing
// Radical
// Rebuild Chile
// Renju
// Rise Above
// Risky Road
// Roguelike
// Roly Poly
// Run Around
// Rush Hour
// SameGame
// SamePhysics
// Save the Totem
// Security
// Serious Scramblers
// Shrink it
// Sling
// Slingy
// Snowflakes
// Sokoban
// Space Checkers
// Space is Key
// Spellfall
// Spinny Gun
// Splitter
// Spring Ninja
// Sproing
// Stabilize!
// Stack
// Stairs
// Stick Hero
// String Avoider
// Stringy
// Sudoku
// Super Mario Bros
// Surfingers
// Survival Horror
// Talesworth Adventure
// Tetris
// The Impossible Line
// The Moops - Combos of Joy
// The Next Arrow
// Threes
// Tic Tac Toe
// Timberman
// Tiny Wings
// Tipsy Tower
// Toony
// Totem Destroyer
// Tower Defense
// Trick Shot
// Tunnelball
// Turn
// Turnellio
// TwinSpin
// vvvvvv
// Warp Shift
// Way of an Idea
// Whack a Creep
// Wheel of Fortune
// Where's my Water
// Wish Upon a Star
// Word Game
// Wordle
// Worms
// Yanga
// Yeah Bunny
// Zhed
// zNumbers