Angular and XML? No Problem!

This entry is part 7 of 9 in the series: AngularJS Learning Series

The Finished Product

JSON is a native object to AngularJS so working solely with JSON examples is easy. In the real world, however, you’ll probably need to deal with other types of data sources, like XML. Before I get too wordy, let’s start with what you’re really here for…the example:

[iframe src=”http://embed.plnkr.co/Hpx8GPHt0IqKADj8K1Qc” width=”100%” height=”365px”]

The Explanation

While the finished product looks like the result of 15 minutes of coding, I have to admit that it took me significantly longer to figure out. If you search the Angular Developer Guide or API for “XML” you’re going to come up woefully absent of useful results. Googling won’t get you much further. As frustration began to set in, I started thinking about what made loading an XML file more difficult than loading a JSON file. The answer is: not much!

Bridging the Language Gap

Angular likes JSON, and can you blame it? Angular is a JavaScript toolset and JSON is JavaScript Object Notation, a native brethren. XML, on the other hand, is tag-based markup…a foreign language to Angular. What I needed was a way to make Angular understand what XML was trying to communicate.

The key to overcoming any language gap is to have a translator. If there were a way to translate XML to JSON before handing it to the application then I could eliminate the language gap, and the problem.

A quick search for xml converters turned up multiple results. I chose Abdulla Abdurakhmano’s X2JS library because it was the first one I came across, it was lightweight, and it was pretty easy to use.

The code required to perform the translation is short and simple. Provide the library with XML, tell it which method to use, and receive JSON in return:

function(xml) {
 var x2js = new X2JS();
 var json = x2js.xml_str2json( xml );
 return json;
}

Where to Stick It

Now that I had a solution to the language gap I needed to decide where to implement it. Angular’s $http service conveniently applies transformation functions for both requests and responses. These functions apply transformations to data when it is sent and received, and they can be overridden.

Using the X2JS conversion code as a transformResponse function it was simple to get the $http service to read in XML and return JSON to the application, effectively creating the translator I needed. The results look something like this:

angular.module('myApp.service',[]).
    factory('DataSource', ['$http',function($http){
       return {
           get: function(callback){
                $http.get(
                    'data.xml',
                    {transformResponse:function(data) {
                    	// convert the data to JSON and provide
                    	// it to the success function below
						var x2js = new X2JS();
						var json = x2js.xml_str2json( data );
						return json;
						}
					}
                ).
                success(function(data, status) {
					// send the converted data back
					// to the callback function
                    callback(data);
                })
           }
       }
    }]);
    
angular.module('myApp',['myApp.service']);

var AppController = function($scope,DataSource) {
    
    //This is the callback function
    setData = function(data) {
        $scope.dataSet = data;
    }
        
    DataSource.get(setData);
    
}

That’s really all there is to it! The example above will retrieve an xml file named “data.xml”, convert it to JSON, and assign the results to $scope.dataSet.

The Caveat

XML comes in all shapes and sizes. Some use attributes, others don’t, and many use a combination of both. Some XML files use a proper header, others are just fragments. Your application will likely require tweaking to get the data into the shape that your application requires. Always read the docs, read the comments, and experiment.

The example at the beginning of this post is a great starting point. Fork it and experiment with different libraries or XML formats.

Keep on Coding!

XSLT Gotcha When Combining Attribute Sets

I made my first foray into XSLT territory recently. Quick definition from W3Schools:

“XSL stands for EXtensible Stylesheet Language, and is a style sheet language for XML documents. XSLT stands for XSL Transformations.”

Overall the experience was a good one. It was very easy to figure out how to display an XML document in an HTML report format. It was also very easy to style the report using attribute sets. Essentially, attribute sets allow you to define styles for different elements in your document. Elements use “use-attribute-sets” to define the sets that should be applied. For example:

<xsl:attribute-set name="bodystyle">
    <xsl:attribute name="bgcolor">
        #cccccc
    </xsl:attribute>
</xsl:attribute-set>

<xsl:template match="report">
    <html>
        <body xsl:use-attribute-sets="bodystyle">
            <xsl:apply-templates select="headerData"/>
            <xsl:apply-templates select="reportData"/>
        </body>
    </html>
</xsl:template>

The code above would make the body background color #cccccc. Works great. But what if I wanted to set a property that isn’t an attribute of the element. What if I want to set the font color, for example. There is no “color” attribute for body. In this case, the style attribute can be used:

<xsl:attribute-set name="defaultfontcolor">
    <xsl:attribute name="style">
        color:#111;
    </xsl:attribute>
</xsl:attribute-set>

<xsl:template match="report">
    <html>
        <body xsl:use-attribute-sets="defaultfontcolor">
            <xsl:apply-templates select="headerData"/>
            <xsl:apply-templates select="reportData"/>
        </body>
    </html>
</xsl:template>

So far, so good. The cool thing about attribute sets is that they can be combined, too. Let’s say I want to combine the two examples above and set the body color and font color at the same time. I could define both in the same attribute set, but I want to use the font color elsewhere without the bgcolor. This can be done easily:


<xsl:attribute-set name="bodystyle">
    <xsl:attribute name="bgcolor">
        #cccccc
    </xsl:attribute>
</xsl:attribute-set>

<xsl:attribute-set name="defaultfontcolor">
    <xsl:attribute name="style">
        color:#111;
    </xsl:attribute>
</xsl:attribute-set>

<xsl:template match="report">
    <html>
        <body xsl:use-attribute-sets="bodystyle defaultfontcolor">
            <xsl:apply-templates select="headerData"/>
            <xsl:apply-templates select="reportData"/>
        </body>
    </html>
</xsl:template>

As you can see in the body tag I’ve listed both attribute sets, separated by a space. This works great!

Now here’s the gotcha. The last attribute set in the list will override previous style definitions of the same attribute. Normally, this isn’t a problem…as long as you’re not using the style attribute in more than one set. While the style attribute can define more than one element property, “style” itself is the attribute. Therefore, if you try to combine two attribute sets that both define “style”, only the last one will be implemented, regardless of whether they define different properties.

For example, although the code below defines the same styles as the one above, only the defaultfontcolor attribute set will be implemented. This is because both sets define the same attribute: “style”.


<xsl:attribute-set name="bodystyle">
    <xsl:attribute name="style">
        background: #ccc;
    </xsl:attribute>
</xsl:attribute-set>

<xsl:attribute-set name="defaultfontcolor">
    <xsl:attribute name="style">
        color:#111;
    </xsl:attribute>
</xsl:attribute-set>

<xsl:template match="report">
    <html>
        <body xsl:use-attribute-sets="bodystyle defaultfontcolor">
            <xsl:apply-templates select="headerData"/>
            <xsl:apply-templates select="reportData"/>
        </body>
    </html>
</xsl:template>

It doesn’t matter that one defines background and the other defines color. It only matters that they both set the “style” attribute. And because defaultfontcolor is last in the use-attribute-sets list, its definition is used over bodystyle.