XML as the API

 

Chris Lovett
Microsoft Corporation

October 16, 2000

Download Test.exe.

Contents

First Attempt
XSL/T to the Rescue
Conclusion

Have you ever used Office Web Components? I recently had a very interesting experience— and I want to make a big deal about it, because I think it perfectly illustrates the difference between XML and object-oriented APIs.

I was writing some Active Server Pages (ASP) code to chart our team's bug-fix progress. We have a SQL Server database that contains all the bugs, to whom they are assigned, when they were opened, resolved, or closed, and their current status.

Our SQL Server database doesn't retain enough history to generate this exact graph, so I also had to create an XML file that could act as a cache for storing the results of a daily query made against the database. From the XML file, I generated the graph. This XML file is also a ListEditor file so that it can be directly edited with other information that does not come from the database, such as the target bug fix goal numbers (the black line in the chart above).

The XML file contains items that look like this:

   <item>
      <id>32</id><date>8/31/2000</date>
      <target>146</target><active>167</active>
      <dev>69</dev><pm>19</pm><test>1</test><ue>58</ue>
      <resolved>484</resolved>
      <rdev>15</rdev><rpm>36</rpm><rtest>396</rtest><rue>0</rue>
      <in>33</in><out>36</out>
   </item>

First Attempt

I read up on the Office 2000 Web Chart component API documented in MSOWCVBA.CHM, and I discovered that you can build charts from multiple input sources, including arrays, strings, recordsets, and spreadsheets. I looked at the samples and decided that arrays would be the easiest.

I then wrote a bunch of ASP code that generated the client-side JScript code for building the chart from these arrays. The ASP code looked something like this:

<object id=ChartSpace1 classid=CLSID:0002E500-0000-0000-C000-000000000046 style="width:100%;height:350"></object>

<script language=vbs>
Sub Window_OnLoad()
' The colors that I will use for the different chart series.
Dim colors(10)
colors(0) = "black"
colors(1) = "blue"
colors(2) = "red"
colors(3) = "green"
colors(4) = "magenta"
colors(5) = "orange"
colors(6) = "brown"
colors(7) = "gray"
colors(8) = "purple"
colors(9) = "yellow"
colors(10) = "lightgreen"

ChartSpace1.Charts.Add
Set c = ChartSpace1.Constants

' Create array of strings that describes each series.
<% var xpath = "//Schema/ElementType/element[@view = '']"
   var nodes = doc.selectNodes(xpath);%>
Dim series(<%=nodes.length%>)
<%
var series = new Array()
var node = nodes.nextNode();
i = 0
while (node != null) {
    var name = node.getAttribute("type");
    if (name != "id" && name != "date") {
        series[i] = name;
%>
series(<%=i%>) = "<%=name%>"
<%    i++;
    }
    node = nodes.nextNode();
}%>

' Now get the names of each category.
Dim dates()
<% nodes = doc.selectNodes("//item/date");%>
Dim categories(<%=nodes.length%>)
<%
k = 0
var node = nodes.nextNode();
while (node != null) {
    d = node.text.substr(0,node.text.length-5)
%>  categories(<%=k%>) = "<%=d%>"
<%
    k = k + 1
    node = nodes.nextNode();
}%>

' Now get the values for each series.
<%
var values = new Array();
for (j = 0; j < i; j++)
{
    name = series[j];
    nodes = doc.selectNodes("//item[date]");%>
Dim values(<%=k%>)
<%
    x = 0
    first = true
    var node = nodes.nextNode();
    while (node != null) {
        var child = node.selectSingleNode(name);
        if (child && child.text != "") {
            values[x] = parseInt(child.text);%>
values(<%=x%>) = <%=child.text%>
<%
            first = false;
        } else if (first) {
            values[x] = 0;%>
values(<%=x%>) = 0
<%      }
        x = x + 1;
        node = nodes.nextNode();
    } %>
ChartSpace1.Charts(0).SeriesCollection.Add
set chartseries = ChartSpace1.Charts(0).SeriesCollection(<%=j%>)
chartseries.Caption = series(<%=j%>)
chartseries.Line.Color = colors(<%=j%>)
chartseries.SetData c.chDimCategories, c.chDataLiteral, categories
chartseries.SetData c.chDimValues, c.chDataLiteral, values
<% } %>

ChartSpace1.Charts(0).HasLegend = True
ChartSpace1.Charts(0).Type = c.chChartTypeLine
ChartSpace1.Charts(0).Axes(c.chAxisPositionLeft).MajorUnit = 25

End Sub
</script>

I didn't particularly like this solution, because the ASP code was a nightmare to maintain. I changed it so that it would generate an HTML page that downloaded the XML to the client and built the chart entirely on the client side. This also took some of the load off my Web server.

Everything was moving along just fine until I hit a brick wall in the charting API itself. If you've played with Excel charts much, you may have run into their cool combination charts—which can display two vertical axes with a different scale on the same chart. If you look closely at the above chart, this is exactly what I wanted to do. I set up the chart so that the lines use the left axis, which goes up to 700; the bars use the right axis, which goes up to 160. It turns out that you cannot build such a chart using the charting API in Office 2000 Web components. I was stumped. I sent an e-mail to the Office guys to see whether they had some sort of workaround.

The Office team showed me a nifty little XmlData property on the ChartSpace object, which returns a complete XML format for the chart I had built. All I had to do was to tweak that XML a bit to insert the different scale on the right axis, set the XmlData property with the fixed version of the XML, and voilà, the right axis was fixed!

The XML format looks like this:

<xml xmlns:x="urn:schemas-microsoft-com:office:excel">
    <x:WebChart>
        <x:OWCVersion>9.0.0.2710</x:OWCVersion>
        <x:Width>20664</x:Width>
        <x:Height>9260</x:Height>
        <x:Chart>
            <x:PlotArea>
                <x:Interior>
                    <x:Color>#F0F0F0</x:Color>
                </x:Interior>
                <x:Graph>
                    <x:Type>Line</x:Type>
                    <x:SubType>Standard</x:SubType>
                    <x:Series>
                        <x:Line>
                            <x:Color>............</x:Color>
                        </x:Line>
                        <x:Caption>
                            <x:DataSource>-1</x:DataSource>
                            <x:Data>............</x:Data>
                        </x:Caption>
                        <x:Index>2</x:Index>
                        <x:Category>
                            <x:DataSource>-1</x:DataSource>
                            <x:Data>............</x:Data>
                        </x:Category>
                        <x:Value>
                            <x:DataSource>-1</x:DataSource>
                            <x:Data>............</x:Data>
                        </x:Value>
                    </x:Series>
...

Note As of this writing, the Office team does not commit to supporting this XML format, as it may change.

XSL/T to the Rescue

I put two and two together and decided I didn't need all that code after all. I needed only a simple XSL style sheet, built from the XML representation of the chart that I really wanted. The XSL style sheet could insert the numbers that I wanted from my little XML file.

The client-side code became quite clean:

<HTML>
<object id=ChartSpace1 classid=CLSID:0002E500-0000-0000-C000-000000000046
        style="width:100%;height:350"></object>
<xml id="XmlDoc" src="daily.xml"></xml>
<xml id="Xsl" src="chart.xsl"></xml>
<script for=window event=onload>
    ChartSpace1.XMLData = XmlDoc.transformNode(Xsl.XMLDocument);
</script>
</HTML>

The result is the following:

File
Description
Test.asp The HTML page that displays the chart. You can view the chart if you have Internet Explorer 5.x and Office 2000 Web Components installed.
Daily.xml The XML file that contains the bug history.
Chart.xsl The XSL file that builds the chart in XML format.
Tableview.xsl The XSL file that builds the raw data HTML table view.
Mycss.css A cascading style sheet that defines the look of the HTML page.

Conclusion

What just happened here? A tightly bound, object-oriented API for manipulating charts was bigger and richer than my application required.

The high-powered charting API hit a brick wall. The feature for implementing different scales on multiple axes was not exposed. Developing high-powered, tightly coupled APIs is expensive. It takes time and effort, and the APIs are hard to test. Imagine having to test all the call sequences over scores of classes and hundreds of methods. It's almost impossible, which is why important features like this get cut.

I didn't need to animate the chart. I just needed a simple chart built from some simple XML—that wouldn't change much over time, except for the data.

Fortunately, the application exposed the loosely coupled, low-tech XML format. This XML format rips all the API stuff out of the way and gives you raw access to every piece of data that makes up the real chart. It enables you to reach into the guts of the chart and tweak whatever you need to.

The tradeoff is that the granularity is now much coarser. You cannot make the line colors animate in different colors every millisecond. You cannot make the line width pulse to the rhythm of a heartbeat. It turns out, however, that this coarser granularity is just perfect for most Web-based applications of the Office Web Chart component.

This is a perfect example of how a loosely coupled, coarse-grained XML API can also result in a deeper integration between components (in this case, the Office Web Chart Component and my ASP application), because the object-oriented interfaces that could get in the way have been bypassed. This assumes that your component's XML format is well designed, and that all the capabilities are exposed in that XML format.

The XML interface to your component should also be a lot cheaper to build, because it doesn't have the call sequence test matrix explosion. It simply accepts the XML in one specific schema—and if the XML doesn't validate against that schema, it is rejected. You have to test lots of different XML inputs, but that is still less work than testing all the possible call sequences through a rich API.

Don't get me wrong. I love objects and I love rich APIs—because I'm a programmer, after all. But there is now a paradigm shift in which, for many cases, a rich API is overkill—and customers are demanding simplicity paired with deeper integration between components. XML is the answer to this problem.

Chris Lovett is a program manager for Microsoft's XML team.