Hack 85 Import ASC Files as XML

figs/expert.gif figs/hack85.gif

XML class for basic file loading. Then parse the text out of the single resulting XML node.

XML is a well-known format for sending and receiving data to and from the Flash Player. When you want to load a smaller amount of data (or unstructured data), the LoadVars class is another popular route. XML is common in web design and web-related technologies but many non-web applications use proprietary data formats not supported by the Flash Player. Two common options for loading nonstandard data into Flash are to convert the data into XML or ampersand-delimited text files (for LoadVars). Both approaches force you to reformat the data every time you update the source data, making for an inefficient workflow and possibly introducing errors during conversion.

A better route is to import the source application's original file format if possible. Here we import the ASC file format, which is common to many applications.

ASC Files

The ASC file format consists of any sequential ASCII file structured as a series of lines of space-delimited data, each line being terminated with a carriage return (or other delimiter), such as:

data data data data data<CR>

data data data data data<CR>

...

data data data data data<CR>

ASC files and ASC-style files are typically used as a general file format that is easy to read by other applications. Many 3D applications support plug-ins that allow them to import and export ASC files, although fewer have this native capability.

ASC files are easy to hand-edit in any text editor because:

  • The data file is simple ASCII.

  • The data does not contain any nonprintable characters or escape sequences (a.k.a. control codes).

  • The data is ordered in a regular table format that is easy for humans to read.

This format should not be confused with the Flash-specific ASC (ActionScript Communications) file format used by Flash Communication Server MX.

This hack uses an ASC file created by 3D Studio Max. As you can see, it is essentially a table of data:

Ambient light color: Red= 0.0 Green= 0.0 Blue= 0.0



Named object: "Torus01"

Tri-mesh, Vertices: 100     Faces: 200

Vertex list:

Vertex 0:  X:-0.4     Y: 0.0     Z:61.1

Vertex 1:  X:-0.4     Y:-11.8     Z:57.3

...

Vertex 98:  X:-27.5     Y:19.0     Z:38.5

Vertex 99:  X:-33.4     Y:11.8     Z:46.6

Face list:

Face 0:    A:0 B:11 C:10 AB:0 BC:1 CA:1

Smoothing: 1 

Face 1:    A:0 B:1 C:11 AB:1 BC:1 CA:0

Smoothing: 1 

...

Face 198:    A:99 B:0 C:9 AB:0 BC:1 CA:1

Smoothing: 1 

Face 199:    A:99 B:90 C:0 AB:1 BC:1 CA:0

Smoothing: 1

The data forms a 3D torus (doughnut), as seen in Figure 10-12.

Figure 10-12. The torus shape in 3D Studio Max
figs/flhk_1012.gif


The problem is how to load our ASC file into Flash. One of the easiest (and downright sneaky) ways is to trick Flash into thinking the file is an XML file. The fact that the file isn't really XML causes Flash to assume that the file contains only one node. This single node is a string containing the entire contents of the file, which we can parse using standard string-handling (rather than XML node-parsing) functions.

Import and Parse the ASC File

The functions handleIncoming( ) and readXMLstart( ) in the following listing cause Flash to load the ASC file as an XML file and place the contents of the file into a string, my3dFile, if the load is successful.

We know that each line of the ASC file contains related data and ends with a carriage return ("\n" or Key.ENTER in Flash). To split the ASC file into separate lines of data, we simply search through the file looking for each occurrence of "\n" (we don't use String.split( ) because we want to loop through the array anyway to look for the data of interest, as discussed later). The readASC( ) function separates the ASC file into lines and stores them in an array named shape:

handleIncoming = function (success) {

  if (success) {

    readXMLstart( );

  }

};

readXMLstart = function ( ) {

  my3dFile = xObj.firstChild.nodeValue;

  readASC( );

};

readASC = function ( ) {

  var oIndex:Number = 0;

  var rIndex:Number = 0;

  var elem:String = "";

  while (rIndex != -1) {

    rIndex = my3dFile.indexOf("\n", oIndex);

    elem = my3dFile.substring(oIndex, rIndex);

    // Skip any lines that are padding (less than 30 characters)

    if (elem.length > 30) {

      shape[shape.length] = elem;

    }

    oIndex = rIndex+1;

  }

};

var xObj:XML = new XML( );

var my3dFile:String = new String( );

var shape:Array = new Array( );

xObj.onLoad = handleIncoming;

xObj.load("torus.asc");

The String.substring( ) method searches my3dFile and extracts the characters between the start of the first unsearched character position, oIndex ("old index"), and the position of the next "\n", rIndex ("return index"), as a line of data:

rIndex = my3dFile.indexOf("\n", oIndex);

elem = my3dFile.substring(oIndex, rIndex);

The value of rIndex is found by looking for the next "\n" via the String.indexOf( ) method. The code knows when the end of the ASC file is reached because String.indexOf( ) returns -1 when the search string cannot be found.

Some lines of the ASC file are not of interest to us (i.e., they contain data other than the raw point and line data we are looking for), and we can get rid of these by checking for some identifying characteristic before we treat the current line as valid. In our case, if the line length is greater than 30 characters, the currently found line is assumed to be valid data and stored as the next element in array shape:

if (elem.length > 30) {

  shape[shape.length] = elem;

}

Figure 10-13 shows the first 10 elements of the shape array as created by the code presented. We'll filter out anything that isn't vertex data momentarily.

Figure 10-13. The first 10 elements of the shape array
figs/flhk_1013.gif


Parse Each Line in an ASC File

Normally, we don't want to store the individual lines but rather the relevant data contained within them. Given that the ASC file in question represents a 3D shape, we want to extract the vertex point data (x, y, z) of this shape.

A typical data line that contains 3D point data in our imported file looks like:

Vertex 98:  X:-27.5     Y:19.0     Z:38.5

From this line, we can deduce that:

  • All lines that contain vertex data in the ASC file are at least 30 characters long and start with "Vertex."

  • Within these lines, the x data is preceded by "X:" (similarly for the y and z data).

We can add these rules to the code as follows. We can determine whether the current line contains vertex data by looking for a "V" as the first character of the current line:

if (elem.substring(0, 1) == "V") {

If this is the case, we search through the line for the offset of "X:", "Y:", and "Z:":

posX = elem.indexOf("X:", 0);

posY = elem.indexOf("Y:", posX);

posZ = elem.indexOf("Z:", posY);

These offsets are the same for all lines starting with "Vertex" in the example file, but that is not guaranteed, so we have to search for the start positions rather than assuming them.

We use these offsets to extract the x, y, and z coordinates. We add 2 to the starting offset to account for the letter and colon, such as "X:".

pX = Number(elem.slice(posX + 2, posX + 6));

pY = Number(elem.slice(posY + 2, posY + 6));

pZ = Number(elem.slice(posZ + 2, posZ + 6));

Finally, we can add the (x, y, z) point as structured Flash data that can be used by ActionScript to build the 3D shape [Hack #37] . Here, I add the 3D point data as properties to the shape array, as shown in Figure 10-14.

Figure 10-14. The 3D data points stored as x, y, and z properties of each shape element
figs/flhk_1014.gif


shape[sPointer] = new Object( );

shape[sPointer].x = pX;

shape[sPointer].y = pY;

shape[sPointer].z = pZ;

The full code to extract the (x, y, z) point data from our 3D Studio Max ASC file is:

handleIncoming = function (success) {

  if (success) {

    readXMLstart( );

  }

};

readXMLstart = function ( ) {

  my3dFile = xObj.firstChild.nodeValue;

  readASC( );

};

readASC = function ( ) {

  var oIndex:Number = 0;

  var rIndex:Number = 0;

  var elem:String = "";

  var posX:Number = 0;

  var posY:Number = 0;

  var posZ:Number = 0;

  while (rIndex != -1) {

    rIndex = my3dFile.indexOf("\n", oIndex);

    elem = my3dFile.substring(oIndex, rIndex);

    if (elem.length > 30) {

      if (elem.substring(0, 1) == "V") {

        posX = elem.indexOf("X:", 0);

        posY = elem.indexOf("Y:", posX);

        posZ = elem.indexOf("Z:", posY);

        pX = Number(elem.slice(posX + 2, posX + 6));

        pY = Number(elem.slice(posY + 2, posY + 6));

        pZ = Number(elem.slice(posZ + 2, posZ + 6));

        shape[sPointer] = new Object( );

        shape[sPointer].x = pX;

        shape[sPointer].y = pY;

        shape[sPointer].z = pZ;

        sPointer++;

      }

    }

    oIndex = rIndex+1;

  }

};

var xObj:XML = new XML( );

var my3dFile:String = new String( );

var shape:Array = new Array( );

var sPointer:Number = 0;

xObj.onLoad = handleIncoming;

xObj.load("torus.asc");

You can see Edwin Heijmen's version of this file in the Experiments section at http://www.poeticterror.com. The full version uses the 3D point data to create an interactive 3D wireframe viewer, as shown in Figure 10-15.

Figure 10-15. Wireframe data imported from an ASC file into Flash
figs/flhk_1015.gif


Final Thoughts

As Flash becomes more of a general web multimedia platform, you are increasingly likely to want to import data into a SWF from nonstandard or non-web-based applications. Even if this data is not XML, you can use the XML class to load the data (Flash doesn't care whether a file you ask it to treat as XML is actually XML). This is one of the better ways to import structured text files that are formatted in a way that Flash does not understand, because you gain many of the features of the XML class, such as notification via the onLoad event that the file is loaded.

Furthermore, the raw ASC file format makes it particularly easy to import data not intended for Flash (such as 3D point data or database recordsets) into a running SWF, so there may be little gain in converting to native Flash formats such as XML.

Avoid the temptation to always convert data into XML. Doing so unnecessarily adds steps to the production workflow if the source application updates the data, however infrequently.

?Edwin Heijmen