Showing posts with label XML. Show all posts
Showing posts with label XML. Show all posts

ActionScript, E4X, and White Space

english mobile

I've been reading XML into ActionScript 3 for many years, and I still haven't solved all the issues with it. Given that my searches for solutions to these issues turn up so little, I decided to share the solutions I have found, especially when it comes to white space.

I think at least part of the reason that these problems are so hard to solve is that the XML documentation for many of the properties and methods we need to use is frustratingly circular. So let me start by laying out what some of these do in practice.

ignoreWhiteSpace

I think ignoreWhiteSpace is the property we first try to use to correct some of the issues with E4X. This property will, in fact, fix issues that are caused by having "extra" children that you weren't expecting to be parsing when you use normal formatting, like new lines and tabs, in your XML. However, it then causes other white spaces that might have been pretty important to disappear.

An alternative to using ignoreWhiteSpace is to use xmlVar.elements() instead of xmlVar.children(). This works relatively well unless you need to access something that's not an element, like a comment, or you need to pass the XML to Adobe code (like new DataProvider()) that for some reason is not equipped to handle the vagaries of their own E4X implementation.

prettyPrinting

We're all familiar with the problem where the default XML parsing adds even more formatting than we included in the original XML file, so that

<root>
 <p>The quick brown fox jumped over the
 <b>lazy</b> dog.</p>
</root>
becomes
<root>
 <p>The quick brown fox jumped over the 
  <b>lazy</b> 
 dog.</p>
</root>

I'm sure there are some versions of Flash where setting prettyPrinting to false fixes this problem, but in the version of the Flash player we target, setting this property has no effect. The good news is that TextField, the Flash Label component, and the Flex MX Label component support the condenseWhite property, which gets rid of this issue for text you want to display. I guess if you're using a spark label, the skin will need to use something that supports condenseWhite if you need this.

Wrap in CDATA

One of the reasons XML parsing is so thorny is that we're using the XML to store external content, often HTML-formatted. CDATA does work to preserve HTML formatting, but it has its own issues. One issue is that there's no real documentation about how to get the text out of CDATA.

Funny story--I was once on a Flex project where it turned out I needed to wrap the entire XML package I was building in CDATA. Try though I might, I couldn't find anything that told me how to get at the "stuff" inside the CDATA. With deadline fast approaching, I "hacked" it by giving the text to a Spark label (which did know how to do this), then reading the text out again and casting it to XML.

Some months later, I ultimately discovered that String(myNode) will retrieve the contents of a node, whether its contents are wrapped in CDATA or not. Unless the contents are not wrapped in CDATA but contain HTML formatting anyway. This is something that is quite likely to happen on my team, as the people who produce the XML see no reason they shoulld start wrapping everything in CDATA just because we're using ActionScript 3 now.

In addition, even when things are wrapped in CDATA when they should be, you have to dump your text in one line into CDATA because it faithfully reproduces all those \n's and \t's, and you wind up with a bunch of junk nodes if you have to cast the contents back to XML. This results in XML that isn't readable, and readability is one reason to use XML in the first place.

hasComplexContent

So, if you're getting XML and you know that sometimes certain nodes will contain HTML and sometimes that HTML will be wrapped in CDATA and sometimes it won't, what do you do? One thing you can do is create a function that checks for hasComplexContent and calls toString() if it returns false and children().toXMLString() if it returns true.

This works well, but one issue with it is you have to have access to this function wherever you're parsing XML. And since you can't really extend the XML Class, due to its dependence on static methods and properties, that means you either need to use a static method yourself (not my favorite thing) or you need to violate DRY, unless your application lends itself to only parsing XML nodes in exactly one place.

normalize()

This is probably my favorite method, because it gets rid of all the weird child nodes that contain "\n\t\t". This means that I can now parse formatted XML and not have to allow for weird extra junk in there. More importantly, I can pass that XML to "dumber" code, such as the Flash DataProvider constructor, and it works. It also does not take out the spaces around inline HTML tags, so you can normalize() the XML to make for easy parsing that you should get from ignoreWhiteSpace (but don't, due to the removed spaces), then you can call String(myXMLNode) on the node whether it has complex content or not, and it will work in a TextField with condenseWhite set to true. FTW!

Comparing XML nodes of the same name with E4X

english mobile

I recently found a situation that the Flex E4X documentation didn't cover, and I couldn't find an example for ActionScript 3 or JavaScript that covered it. Essentially, I had XML in the data property of an itemRenderer that looked something like this:

<question id="1>
<selected>
1
</selected>
<selected>
5
</selected>

</question>

Essentially, my itemRenderer has five buttons (A-E), any or all of which can be selected. If the user clicks one of the buttons and it is already selected, it should be deselected. Or, in other words, if there is already an element in the data XML's "selected" collection with the same value as what the user clicked (assuming A=1 and E=5), then that element should be deleted.

I had a challenge, though, in that I didn't want to use a for loop. I was using XML, and E4X should be able to handle this easily. The problem I had was that every E4X example I found that referenced the contents of the nodes of an XML node presumed that there would only be one of each node of the same node. So if my XML structure had been

<question id="1>
<selected>
1
</selected>

</question>

then life would have been easy. I could have used data.(selected==userSelection) and away I'd go. But the problem when you have multiple nodes named "selected" is that data.selected returns an XMLList that contains all of the nodes. Hence, it cannot be equal to any single number, even though when there is only one node it works fine.

So, after much research, I came up with something like this:

myXMLNode:XML="<selected>"+userSelection+"</selected>";
if (data.selected.contains(myXMLNode){
//loop through and delete any nodes with that value
} else {
//add a node of that value
}

I wasn't really happy with this, and I posted to the Flex Coders mailing list. Tracy Spratt suggested that I use the text() method of the XML object, so I wound up with something like this in my final logic:

if (data.selected.(text()==userSelection).length()==0){
//add the selected node to the data object
data.prependChild(myXMLNode);
} else {
//remove the selected node from the data object
for (i=data.selected.length()-1; i>=0; i--){
if (data.selected[i] == userSelection){
delete data.selected[i];
}

I hope this will help someone else solve this problem without having to waste as much time as I spent on this!

Problems with HTML within XML

english mobile

Around a month ago, I promised more fun and exciting posts about passing XML through ExternalInterface from Authorware to be used within Flex. The specific task I was trying to accomplish is taking a series of xml nodes and displaying their contents on screen using a repeater control. The contents could be images, text, or swfs.

The problem I had was with the text. It seems that Flex is very persnickety about its XML formatting, so each XML node must be indented and on a new line. This is fine for ordinary XML, since who cares how it looks to Flex, but when you have line breaks and tabs added around your <b > tags, that is a problem!

The irritating thing is that the problem occurs not once, but twice! The first time is when the XML gets passed through ExternalInterface. I did eventually find a way to defeat that one, but since it also happens again when you pass the string to the XML() object to make the data source for the repeater, that didn't help much. Flex essentially ignores CDATA tags put into the XML nodes and formats the data anyway, so that didn't help either.

What I eventually wound up doing is replacing the "<" characters in embedded html with "^", then replacing that in the final step before rendering. I'm thinking now that I probably also could have gone with a separate text file for each bit of HTML text and told the Flex file to read that in and display it. However, at the time I didn't know how to get a non-air Flex file to read directly from the hard drive, so it didn't occur to me.

Updated September 18, 2008:
I think this was caused by the prettyPrinting and prettyIndent settings on the global XML object itself. I think that if I'd set prettyPrinting to false, I could have avoided the hassle. One day I may even revisit that component...I'll let you know how it turns out.

Showing item number in ItemRenderer

english mobile

I asked a question several weeks ago on the FlexCoders forum on how to get the item number of an item that had been clicked in a Repeater. None of the answers that I got seemed to help and it wasn't that critical, so I let it drop. In my current project, it is critical, because I have to show that number on the screen.

Luckily, I am using XML. E4X XML, like Authorware icons, is very "self-aware." In Authorware, you can get from any place in the program to any other place using the numeric values implicit in the parent/child relationship. Similarly, an E4X XML node "knows" what its child number is relative to the parent node. So displaying the node number on the screen was as simple as {data.childIndex + 1}.

Some days Flex is really cool :-).

Updated 12-3-08:
I've posted a simple example that shows how to display the line number in a List based control with either an XML or ListCollectionView source.