"Sliced bread was the culmination of a century of technological innovation."
-- http://www.engineerguy.com/comm/4263.htm
Information, experience and learning of Steven Pothoven -- usually technology related.
Monday, November 27, 2006
Why is sliced bread such a great thing?
Wednesday, November 08, 2006
Using XML, XPath, and XSLT with JavaScript
XML
First off is the constructor which determined if we will be using IE (ActiveX) functions or W3C standard functions for XML manipulation and either stores the XML DOM returned as theresponseXML
from an XMLHttpRequest
(XHR) or creates an empty DOM object for later manipulation.
// // XML // function XML(xmlDom) { this.isIE = window.ActiveXObject; if (xmlDom != null) { this.xmlDom = xmlDom; } else { // create an empty document if (this.isIE) { Try.these ( function() { axDom = new ActiveXObject("MSXML2.DOMDocument.5.0"); }, function() { axDom = new ActiveXObject("MSXML2.DOMDocument.4.0"); }, function() { axDom = new ActiveXObject("MSXML2.DOMDocument.3.0"); }, function() { axDom = new ActiveXObject("MSXML2.DOMDocument"); }, function() { axDom = new ActiveXObject("Microsoft.XmlDom"); } ); this.xmlDom = axDom; } else { this.xmlDom = document.implementation.createDocument("", "", null); } } };In case you're not getting the XML data from an XHR response, I also provided a
load
function to load an XML file from an URL.
// load // // Loads an XML file from an URL XML.prototype.load = function(url) { this.xmlDom.async = false; this.xmlDom.load(url); };Next, I want to be able to find a single node in the XML DOM based on an XPath. The
getNode
function will do that for me.
// getNode // // get a single node from the XML DOM using the XPath XML.prototype.getNode = function(xpath) { if (this.isIE) { var result = this.xmlDom.selectSingleNode(xpath); } else { var evaluator = new XPathEvaluator(); var result = evaluator.evaluate(xpath, this.xmlDom, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null); } return result; };More often, I don't need the actual DOM node object, but the value of that node, so I provide the
getNodeValue
a function to get the value of a node.
// getNodeValue // // get the value of a the element specified by the XPath in the XML DOM XML.prototype.getNodeValue = function(xpath) { var value = null; try { var node = this.getNode(xpath); if (this.isIE && node) { value = node.text; } else if (!this.isIE && node.singleNodeValue) { value = node.singleNodeValue.textContent; } } catch (e) {} return value; };If you're just looking for a particular node/value, the previous functions are fine, but more often you need a group of node or node values (ex. all the book titles). So, the full JavaScript file also contains corresponding functions to get multiple nodes (
getNodes
) and node values as an array (getNodeValues
).
At some point, you'll probably want to access this XML DOM as XML data (string), so we need a function to serialize the XML from the DOM to a String. The getNodeAsXml
function will do that for you. If you pass in the root XPath ("/") it will serialize the complete XML file. Also, the included prettyPrintXml
function can be used to escape the HTML characters.
// getNodeAsXml // // get the XML contents of a node specified by the XPath XML.prototype.getNodeAsXml = function(xpath) { var str = null; var aNode = this.getNode(xpath); try { if (this.isIE) { str = aNode.xml; } else { var serializer = new XMLSerializer(); str = serializer.serializeToString(aNode.singleNodeValue); } } catch (e) { str = "ERROR: No such node in XML"; } return str; };So far, we've only read the XML data we received. But we may also want to change the contents of the XML. So, here are the functions necessary to change existing node values (
updateNodeValue
), add new nodes/values (insertNode
), and delete existing nodes (removeNode
).
Note: the XPaths used for node insertions cannot be overly complex as I wanted to keep this library simple.
// updateNodeValue // // update a specific element value in the XML DOM XML.prototype.updateNodeValue = function(xpath, newvalue) { var node = this.getNode(xpath); var changeMade = false; newvalue = newvalue.trim(); if (this.isIE && node) { if (node.text != newvalue) { node.text = newvalue; changeMade = true; } } else if (!this.isIE && node.singleNodeValue) { if (node.singleNodeValue.textContent != newvalue) { node.singleNodeValue.textContent = newvalue; changeMade = true; } } else { if (newvalue.length > 0) { this.insertNode(xpath); changeMade = this.updateNodeValue(xpath, newvalue); } } return changeMade; }; // insertNode // // insert a new element (node) into the XML document based on the XPath XML.prototype.insertNode = function(xpath) { var xpathComponents = xpath.split("/"); var newChildName = xpathComponents.last(); var parentPath = xpath.substr(0, xpath.length - newChildName.length - 1); var qualifierLoc = newChildName.indexOf("["); // remove qualifier for node being added if (qualifierLoc != -1) { newChildName = newChildName.substr(0, qualifierLoc); } var node = this.getNode(parentPath); var newChild = null; if (this.isIE && node) { newChild = this.xmlDom.createElement(newChildName); node.appendChild(newChild); } else if ((!this.isIE) && node.singleNodeValue) { newChild = this.xmlDom.createElement(newChildName); node.singleNodeValue.appendChild(newChild); } else { // add the parent, then re-try to add this child var parentNode = this.insertNode(parentPath); newChild = this.xmlDom.createElement(newChildName); parentNode.appendChild(newChild); } return newChild; }; // removeNode // // remove an element (node) from the XML document based on the xpath XML.prototype.removeNode = function(xpath) { var node = this.getNode(xpath); var changed = false; if (this.isIE && node) { node.parentNode.removeChild(node); changed = true; } else if ((!this.isIE) && node.singleNodeValue) { node.singleNodeValue.parentNode.removeChild(node.singleNodeValue); changed = true; } return changed; };You should now be able to fully read and manipulate XML using XPaths. For any significant reformatting or processing of the XML, you'll probably want to utilize XSLT. So, next I'll provide a simple XSLT class. It will preload (into a DOM object) an XSL file in its constructor. This provides rapid transformations of new XML data passed in to it's
transform
function (including parameters).
First, the constructor which requires an URL pointing to the XSL file to be used for transformations. It does not accept a DOM object like the XML class did as it is assumed that the XSL (layout information) is more-or-less static and stored in a file whereas the XML data would most likely be dynamic and received via an XHR request.
// // XSLT Processor // function XSLT(xslUrl) { this.isIE = window.ActiveXObject; if (this.isIE) { var xslDom = new ActiveXObject("MSXML2.FreeThreadedDOMDocument"); xslDom.async = false; xslDom.load(xslUrl); if (xslDom.parseError.errorCode != 0) { var strErrMsg = "Problem Parsing Style Sheet:\n" + " Error #: " + xslDom.parseError.errorCode + "\n" + " Description: " + xslDom.parseError.reason + "\n" + " In file: " + xslDom.parseError.url + "\n" + " Line #: " + xslDom.parseError.line + "\n" + " Character # in line: " + xslDom.parseError.linepos + "\n" + " Character # in file: " + xslDom.parseError.filepos + "\n" + " Source line: " + xslDom.parseError.srcText; alert(strErrMsg); return false; } var xslTemplate = new ActiveXObject("MSXML2.XSLTemplate"); xslTemplate.stylesheet = xslDom; this.xslProcessor = xslTemplate.createProcessor(); } else { var xslDom = document.implementation.createDocument("", "", null); xslDom.async = false; xslDom.load(xslUrl); this.xslProcessor = new XSLTProcessor(); this.xslProcessor.importStylesheet(xslDom); } };Finally, the
transform
function will preform the XSL transformation and return the result. It accepts parameters to be passed to the XSL as an associative array of parameter name and value pairs.
// transform // // Transform an XML document XSLT.prototype.transform = function(xml, params) { // set stylesheet parameters for (var param in params) { if (typeof params[param] != 'function') { if (this.isIE) { this.xslProcessor.addParameter(param, params[param]); } else { this.xslProcessor.setParameter(null, param, params[param]); } } } if (this.isIE) { this.xslProcessor.input = xml.xmlDom; this.xslProcessor.transform(); var output = this.xslProcessor.output; } else { var resultDOM = this.xslProcessor.transformToDocument(xml.xmlDom); var serializer = new XMLSerializer(); var output = serializer.serializeToString(resultDOM); } return output; };You should now be able to easily utilize XML and XSL on your web pages. An example usage of this XML/XSLT library would be to first build an XSLT object during the page initialization (onload).
xslProcessor = new XSLT(xslUrl);Then, when XML responses are received from an AJAX request, process them. For example,
// get the XML data try { var xmlData = new XML(request.responseXML); } catch (e) { alert(request.responseText); } // get the username out of the XML var userName = xmlData.getNodeValue('//userName'); // transform the XML to some HTML content var newData = xslProcessor.transform(xmlData, {'param':'value'}); document.getElementById('someDiv').innerHTML = newData;Update (February 27, 2007): I previously had a function in my XML class to generate valid HTML for the XML, but I've now modified it to use Oliver Becker's XML to HTML Verbatim Formatter with Syntax Highlighting stylesheet to format the XML. If that is not available, it will default to simply serialize the XML and convert the special characters. The code for this looks like:
/ Define a class variable which can be used to apply a style sheet to // the XML for format it to HTML XML.htmlFormatter = new XSLT("xsl/xmlverbatim.xsl"); // toHTML // // Transform the XML into formatted HTML // XML.prototype.toHTML = function() { var html = null; if (XML.htmlFormatter) { html = XML.htmlFormatter.transform(this); } else { html = this.getNodeAsXml('/'); if (html != null) { html = html.replace(/&/g, "&"); html = html.replace(/</g, "<"); html = html.replace(/>/g, "><br/>"); } } return html; }As before, the complete package is available to download Update (March 7, 2007): I have updated this package to utilize Google's AJAXSLT XSL-T implementation when a native JavaScript XSLT function is not available. I need to test it more before releasing it however.
Thursday, October 26, 2006
Cross-browser inline SVG solved
Below is a screenshot my test application now. The XML used as sample data and the XSLT to product the SVG data was provided earlier.

(Note: I was also able to add some linking between the SVG and HTML so that as you mouse over each bar it will be highlighted in the SVG and simultaneously highlighted the corresponding row in the HTML table.)
In order to make this work I used the
embed
tag for IE and the object
tag for all other browsers (tested with Firefox and Opera). My HTML has a div
named svgDiv which will dynamically be given the SVG to render. For Firefox I can create my object
with the dynamic SVG in one step, but for Internet Explorer, I first have to embed a static, blank SVG (as mentioned earlier) and then replace the blank SVG with the dynamic SVG. However, I determined that this could not be done all in one JavaScript function. I found that if I programatically add the embedded blank SVG, and them immediately replace the blank SVG with the dynamic one, it would fail, but having something like an alert
message between adding the blank SVG and replacing it with the dynamic SVG would work. The only explanation I could reason for this is that the actual addition of the embed
element is not completed by the browser until the JavaScript reaches a pause in processing (ie. not flushed/committed), and that the need to create the alert
popup causes the changes to be committed. (does anyone know how to make the browser commit the changes?). My solution to this problem was to add the blank SVG when the page is loaded (if IE) so that it will be available for me to replace when I'm ready.When the page is loaded, if the browser is IE I call this function to add the blank SVG:
// embedBlankSVG
//
// add an <embed> tag to display a blank static SVG
// this is needed for IE to replace with dynamic content later
SVG.prototype.embedBlankSVG = function(width, height) {
var svgEmbed = document.createElement('embed');
svgEmbed.setAttribute('codebase', 'http://www.adobe.com/svg/viewer/install/');
svgEmbed.setAttribute('classid', 'clsid:78156a80-c6a1-4bbf-8e6a-3cd390eeb4e2');
svgEmbed.setAttribute('pluginspage', 'http://www.adobe.com/svg/viewer/install/');
svgEmbed.setAttribute('src', 'blank.svg');
svgEmbed.setAttribute('width', width);
svgEmbed.setAttribute('height', height);
svgEmbed.setAttribute('type', 'image/svg+xml');
svgEmbed.setAttribute('id', 'embeddedSVG');
this.svgDiv.appendChild(svgEmbed);
};
Then, whenever I want to render a new SVG, I invoke this function:
// render
//
// render the dynamic SVG in the page
SVG.prototype.render = function() {
var str = this.toString();
if (this.isIE) {
// For IE, we use an <embed> tab
// build replacement SVG document
var newSvgElement = document.createElement("svg");
newSvgElement.setAttribute("xmlns", "http://www.w3.org/2000/svg");
newSvgElement.setAttribute('xmlns:xlink', "http://www.w3.org/1999/xlink");
newSvgElement.setAttribute("width", this.width);
newSvgElement.setAttribute("height", this.height);
newSvgElement.innerHTML = str;
// replace blank SVG with new SVG
domtodom(newSvgElement, 'embeddedSVG');
} else {
this.clearPage();
// For all others (mainly Mozilla based), we use an <object> tag
// with the SVG data added inline
var svgObject = document.createElement('object');
svgObject.setAttribute('name', 'svgObj');
svgObject.setAttribute('codebase', 'http://www.adobe.com/svg/viewer/install/');
svgObject.setAttribute('classid', 'clsid:78156a80-c6a1-4bbf-8e6a-3cd390eeb4e2');
svgObject.setAttribute('data', 'data:image/svg+xml,'+ str);
svgObject.setAttribute('width', '850');
svgObject.setAttribute('height', '350');
svgObject.setAttribute('type', 'image/svg+xml');
this.svgDiv.appendChild(svgObject);
}
}
where the
toString
function produces SVG data from XML data (whether read from a file or returned in an XMLHttpRequest
response) and the domtodom
function comes from Chris Bayes' domtodom.jsThis was tested in Firefox 2.0, IE 7.0, and Opera 9.02 (though the table highlighting isn't currently working in Opera. It says I have a Security Error: attempted to read protected variable).
Help requested
I would like to be able to correspondingly highlight bars in the graph when I mouse over the table rows, but every attempt to add the:
<script type="text/ecmascript">
<![CDATA[
...
]]>
</script>
to the SVG data (as described here) immediately causes Firefox to crash.
Updated 12/07/2006 per comment below
The comment below asked what the
clearPage
function does. Since I can't format it nicely in the comments section, I decided to update the post with it. It doesn't really do anything too profound which is why I didn't post it originally.
// clear out any old displayed data from the page
SVG.prototype.clearPage = function() {
this.svgDiv.innerHTML = '';
this.tableDiv.innerHTML = '';
if (this.isIE) {
this.embedBlankSVG(this.svgWidth, this.svgHeight);
}
}
Additionally, while I'm updating this posting anyway, I was able to add JavaScript to my SVG through the XSLT (see Help Requested section) by adding it like this in the XSL:
<xsl:element name="script">
<xsl:attribute name="type">
<xsl:value-of select="'text/ecmascript'" />
</xsl:attribute>
<xsl:text>
<![CDATA[
...
]]>
</xsl:text>
</xsl:element>
I haven't gotten it to do everything that I want yet, but at least the browser no longer immediately crashes.
Tuesday, October 24, 2006
Scalable Vector Graphics (SVG) Intro
- What is SVG (and what is is not)?
- How do you use SVG?
- Why should you use SVG?
Thursday, October 19, 2006
IE7, any real improvement?
I got an email this morning from eWEEK highlighting Jim Rapoza's glowing review of IE7. One one hand, he acknowledges that IE7 hasn't made any revolutionary step forward in the browser space, but has merely caught up as he says,
While we wouldn't yet call IE 7 one of the best browsers available today, Microsoft has greatly closed the distance between its browser and those of its competitors. Version 7 catches IE up with now-common browser features...
but after summarizing the key user interface and security enhancements, he makes this statement,
On the standards side, IE 7 does a much better job than previous versions of the product at supporting key standards, such as Cascading Style Sheets support and JavaScript. IE 7 still doesn't have perfect standards support, but it is much better than previous versions of the browser.
That statement puzzles me since according to this analysis which summarizes web browser standards support, I see very little improvement in standards support except in CSS 2.1 & 3 Basic Selectors.
To me, it sounds like Microsoft has merely put a new glossy finish on a rotting infrastructure (a whitewashed tomb so to speak) which is really disappointing to me, since after listening to this interview of the IE7 team by the Ajaxians last month, I was more optimistic about standards support.
Thursday, October 05, 2006
Using Cookies in JavaScript
Whether you like them or not, cookies are an easy way store simple user specific information across web pages and sessions. Here are the basic JavaScript methods to create (and update), read, or delete (CRUD) cookies with JavaScript.
function createCookie(name, value, days) {
if (days) {
var date = new Date();
date.setTime(date.getTime()+(days*24*60*60*1000));
var expires = "; expires="+date.toGMTString();
}
else {
var expires = ";";
}
document.cookie = name + "=" + value + expires + "; path=/";
}
function readCookie(name) {
var nameEQ = name + "=";
var cookies = document.cookie.split(';');
for(var i = 0; i < cookies.length;i++) {
var cookie = cookies[i];
while (cookie.charAt(0) == ' ') {
cookie = cookie.substring(1, cookie.length);
}
if (cookie.indexOf(nameEQ) == 0) {
return cookie.substring(nameEQ.length, cookie.length);
}
}
return null;
}
function eraseCookie(name) {
createCookie(name, "", -1);
}
Detailed explanation of code how this code works is available here. This script was originally written by Scott Andrew.
Add/remove options to/from a select list
// addSelectOption
//
// Add the single select option to the selection list with the id specified
//
function addSelectOption(selectId, value, display) {
if (display == null) {
display = value;
}
var anOption = document.createElement('option');
anOption.value = value;
anOption.innerHTML = display;
document.getElementById(selectId).appendChild(anOption);
return anOption;
}
// removeSelectOption
//
// Remove the option with the specified value from the list of options
// in the selection list with the id specified
//
function removeSelectOption(selectId, value) {
var select = document.getElementById(selectId);
var kids = select.childNodes;
var numkids = kids.length;
for (var i = 0; i < numkids; i++) {
if (kids[i].value == value) {
select.removeChild(kids[i]);
break;
}
}
}
Wednesday, October 04, 2006
Move options up and down select lists

In my previous post I showed how to move options in a select list from one list to another with JavaScript. Once you've moved them, you may want to reorder them if order is important. So, now I'll show how to do that.
First, define your control in HTML to look something like what is shown to the right where you have up and down buttons which will be used to move selected elements up and down in the list.
Your HTML will look something like:
<select id="orderedList" multiple="multiple"></select>
<img src="moveup.gif" alt="Move Up" onclick="moveOptionsUp('orderedList')" />
<img src="movedown.gif" alt="Move Down" onclick="moveOptionsDown('orderedList')" />
As you can see, I've added two image which when you click on them invoke the
moveOptionsUp
and moveOptionsDown
function. The JavaScript for these functions looks like:
// moveOptionsUp
//
// move the selected options up one location in the select list
//
function moveOptionsUp(selectId) {
var selectList = document.getElementById(selectId);
var selectOptions = selectList.getElementsByTagName('option');
for (var i = 1; i < selectOptions.length; i++) {
var opt = selectOptions[i];
if (opt.selected) {
selectList.removeChild(opt);
selectList.insertBefore(opt, selectOptions[i - 1]);
}
}
}
and
// moveOptionsDown
//
// move the selected options down one location in the select list
//
function moveOptionsDown(selectId) {
var selectList = document.getElementById(selectId);
var selectOptions = selectList.getElementsByTagName('option');
for (var i = selectOptions.length - 2; i >= 0; i--) {
var opt = selectOptions[i];
if (opt.selected) {
var nextOpt = selectOptions[i + 1];
opt = selectList.removeChild(opt);
nextOpt = selectList.replaceChild(opt, nextOpt);
selectList.insertBefore(nextOpt, opt);
}
}
}
Unfortunately there's no
insertAfter
function for a node, so the moveOptionsDown
function is a little funky.
Move options across select lists

Perhaps you've seen controls like what is shown to the right where you have some list of items in one selection list and you can move them in and out of another by pressing the '<' and '>' buttons and you want to know how to do it.
Here's one way. First, define your select lists and buttons similarly to that shown below. In my case, I fill in the select options with AJAX, so I have no options defined in the HTML, but if you generated the HTML on the server, then you'll have options in the HTML as well.
<select id="available" size="10" multiple="multiple"></select>
<input type="button" value=">"
onclick="moveOptionsAcross($('available'), $('selected'))" />></input>
<input type="button" value="<"
onclick="moveOptionsAcross($('selected'), $('available'))" /><</input>
<select id="selected" size="10" multiple="multiple"></select>
Now include the following JavaScript code:
// moveOptionsAcross
//
// Move selected options from one select list to another
//
function moveOptionsAcross(fromSelectList, toSelectList) {
var selectOptions = fromSelectList.getElementsByTagName('option');
for (var i = 0; i < selectOptions.length; i++) {
var opt = selectOptions[i];
if (opt.selected) {
fromSelectList.removeChild(opt);
toSelectList.appendChild(opt);
// originally, this loop decremented from length to 0 so that you
// wouldn't have to worry about adjusting the index. However, then
// moving multiple options resulted in the order being reversed from when
// was in the original selection list which can be confusing to the user.
// So now, the index is adjusted to make sure we don't skip an option.
i--;
}
}
}
You should now be able to select options in one list and press the appropriate button to move them to the other list.
Checkbox as Radio Buttons
function singleSelectCheckbox(checkbox) {
var aGroup = checkbox.parentNode;
var allInputs = aGroup.getElementsByTagName("input");
for (var i = 0; i < allInputs.length; i++) {
if (allInputs[i] == checkbox) {
continue;
} else {
allInputs[i].checked = false;
}
}
}
This assumes you've placed the checkboxes to work as a radio button group in some sort of containing HTML element (a
td
or span
or something like that).Your HTML code would look something like:
<span>
<input onclick="singleSelectCheckbox(this)" type="checkbox">Option 1</input>
<input onclick="singleSelectCheckbox(this)" type="checkbox">Option 2</input>
<input onclick="singleSelectCheckbox(this)" type="checkbox">Option 3</input>
</span>
Wednesday, September 27, 2006
The move from Netgear MP101 to Xbox Media Center
Thursday, September 21, 2006
Creating Mashups
ProgrammableWeb showcases the latest mashup sites. For each site showcased, they provide a list of the mashup APIs used. They also have an API dashboard which provides a comprehensive database of API resources available to you as a developer and they have lots of tutorials and articles.
At the time of this writting, there are 1001 mashup sites showcased.
Using IE in Linux
To use IE in Linux, you have a few options.
- Use WINE (Windows Emulator)
- Use a virtual machine like VMWARE
- Connect remotely to a Windows machine with something like VNC, NX, or Citrix.
- WINE
- pros
- free, good performance and minimal memory requirements
- cons
- can be hard to setup, may not always work due to emulation problems (ex. APIs not implmented in WINE)
- VMWARE
- pros
- fully supported, descent performance with sufficient hardware
- cons
- not free, resource intensive, takes longer to startup
- Remote connection
- pros
- real windows machine
- cons
- requires a second machine, may have network latency
To address the "hard to setup" aspect of getting IE to run in WINE, I've used winetools before which works well, but it's really meant to set up a whole lot more than just IE. I've recently run across IEs4Linux which is a more straightforward method to simply set up IE in Linux under Wine.
Speeding up Linux
This article posses 7 Ways to Speed Up Your Linux Desktop which in a nutshell are:
- Use a Minimalistic Window Manager/Desktop Environment
- Customizing GNOME, KDE, or Xfce
- Get More RAM
- Kill Unneeded Processes
- Remove Virtual Terminals
- Use “Small” Applications
- Remove Start-Up Services
Web Design Trends
- Web Desktop Style
- Silver Orange Style
- Apple Style
- Microsoft Style
- Magazine Style
- Rounded Plain Style
- Web 2.0 Design Style
- Adobe / Macromedia Style
Web Metrics
This article has a nice sumary of some metrics like pageviews and reach and how redesigning some populate sites to use newer techniques like AJAX and RSS would significantly change their current metrics. It doesn't offer a solution for a good metric, but demonstrates the need for one.
In the aforementioned article, it has a link for specifically measuring AJAX applications with Analytics which is useful information.
Wednesday, September 20, 2006
No phishing allowed
- 208.67.222.222
- 208.67.220.220
This service will also correct obvious typos in your URL which can be handy too.
They also claim to be a faster DNS because the have "a really big, smart cache" and are "geographically distributed", however I don't see how accessing their regional server can be faster than the local DNS on your ISP network, but I'm currently using this service and it's plenty fast.
Wednesday, August 09, 2006
Tracking satellites
Similarly, in my earlier post on using XPlanet to generate current satellite wallpaper I discuss how to build a dynamic wallpaper which shows satellite tracking over the current earth image (with current cloud cover) but it also requires fetching the TLEs.
However, now it appears I can drop STSPLUS in favor of an Ajax enabled online version which has lots of nice features and utilizes Google maps for the mapping.
Wednesday, July 12, 2006
Get request parameters through JavaScript
function getParameter ( queryString, parameterName ) {
// Add "=" to the parameter name (i.e. parameterName=value)
var parameterName = parameterName + "=";
if ( queryString.length > 0 ) {
// Find the beginning of the string
begin = queryString.indexOf ( parameterName );
// If the parameter name is not found, skip it, otherwise return the value
if ( begin != -1 ) {
// Add the length (integer) to the beginning
begin += parameterName.length;
// Multiple parameters are separated by the "&" sign
end = queryString.indexOf ( "&" , begin );
if ( end == -1 ) {
end = queryString.length
}
// Return the string
return unescape ( queryString.substring ( begin, end ) );
}
// Return "null" if no parameter has been found
return "null";
}
}
The
queryString
is obtained via var queryString = window.top.location.search.substring(1);
Thursday, July 06, 2006
Follow up on inline SVG
I had built my XSLT to render the SVG as described in the article, Render dynamic graphs in SVG and my mouseover coloring events was modeled after the article, Add interactivity to your SVG, which were both articles from developerWorks.
So, my generated code for a bar in my graph looked like:
<svg:rect x="35" y="272.88" height="57.12" width="109" style="fill:rgb(49,0,0)">However, after taking the advice from Martin Honnen (SVG Developers Yahoo! Group) my generated code for the same bar in the graph looks like:
<set attributeName="fill" from="rgb(49,0,0)" to="red" begin="mouseover" end="mouseout" />
</svg:rect>
<svg:rect x="35" y="272.88" height="57.12" width="109" fill="rgb(49,0,0)" onmouseover="evt.target.setAttribute('fill', 'red');" onmouseout="evt.target.setAttribute('fill','rgb(49,0,0)');"/>The result is that the correct colorings are now displayed when rendered inline in IE, and the mouseover events work correctly in Firefox.
The only thing still not working is the mouseover events when rendered inline in IE.
Martin Honnen further suggested to start with a blank SVG placed in the page with the
embed
tag, and then access it to manipulate it with the getSVGDocument
method (which should work in IE, Firefox 1.5+, and Opera 9) to add the content. However, my attempts at this method have not been successful to date.
IBM's Death Spiral
"[IBM] has entered a death spiral of under-bidding and then under-delivering."
"decades no longer matter to publicly traded American companies. All that really matter are fiscal quarters."
I'm tempted to add my own personal editorial on the subject, but I don't want to put my job in jeopardy.
Wednesday, June 28, 2006
Help with dynamic inline SVG
In a previous post I presented some methods to embed SVG inline after generating it from XML data. However, there's still a problem, particularly in Internet Explorer, which I discovered when I tried to add some event handling to the SVG. Let me walk through the problem.
First, we start out with some sample XML data like:
<?xml version="1.0" ?>Which I run through my XSLT and get the following SVG (notice the mouseover events):
<SrmData>
<data>
<display_name>srmdb2</display_name>
<date>2006-04-30</date>
<processor_busy>19.04</processor_busy>
</data>
<data>
<display_name>srmxml05</display_name>
<date>2006-04-30</date>
<processor_busy>37.68</processor_busy>
</data>
<data>
<display_name>srmdev1</display_name>
<date>2006-04-30</date>
<processor_busy>26.6</processor_busy>
</data>
<data>
<display_name>srmxml01</display_name>
<date>2006-04-30</date>
<processor_busy>42.74</processor_busy>
</data>
<data>
<display_name>srmweb2</display_name>
<date>2006-04-30</date>
<processor_busy>1.66</processor_busy>
</data>
<data>
<display_name>srmweb1</display_name>
<date>2006-04-30</date>
<processor_busy>5.61</processor_busy>
</data>
<data>
<display_name>srmxml02</display_name>
<date>2006-04-30</date>
<processor_busy>22.74</processor_busy>
</data>
</SrmData>
<svg:svg baseProfile="full" width="850" height="350" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">If I embed the above SVG from a static file (via
<svg:g>
<!--Now Draw the main X and Y axis-->
<svg:g style="stroke-width:3; stroke:black">
<!--X Axis-->
<svg:path d="M 30 330 L 830 330 Z" />
<!--Y Axis-->
<svg:path d="M 30 30 L 30 330 Z" />
</svg:g>
<svg:g style="fill:none; stroke:#B0B0B0; stroke-width:2; stroke-dasharray:2 4;text-anchor:end; font-size:12px; stroke-width:5; stroke:black ">
<!--Add dotted line at 50-->
<svg:path d="M 30 30 L 830 30 Z" />
<svg:path d="M 30 165 L 830165 Z" />
<!--Add Y-axis labels-->
<svg:text style="fill:black; stroke:none" x="20" y="30">
100 </svg:text>
<svg:text style="fill:black; stroke:none" x="20" y="165">
50 </svg:text>
<svg:text style="fill:black; stroke:none" x="20" y="330">
0 </svg:text>
</svg:g>
<svg:rect x="35" y="272.88" height="57.12" width="109" style="fill:rgb(49,0,0)">
<set attributeName="fill" from="rgb(49,0,0)" to="red" begin="mouseover" end="mouseout" />
</svg:rect>
<svg:g>
<svg:text style="fill:black; stroke:none" x="35" y="347" rotate="45" font-size="8">
srmdb2</svg:text>
</svg:g>
<svg:rect x="149" y="216.96" height="113.03999999999999" width="109" style="fill:rgb(96,0,0)">
<set attributeName="fill" from="rgb(96,0,0)" to="red" begin="mouseover" end="mouseout" />
</svg:rect>
<svg:g>
<svg:text style="fill:black; stroke:none" x="149" y="347" rotate="45" font-size="8">
srmxml05</svg:text>
</svg:g>
<svg:rect x="263" y="250.2" height="79.80000000000001" width="109" style="fill:rgb(68,0,0)">
<set attributeName="fill" from="rgb(68,0,0)" to="red" begin="mouseover" end="mouseout" />
</svg:rect>
<svg:g>
<svg:text style="fill:black; stroke:none" x="263" y="347" rotate="45" font-size="8">
srmdev1</svg:text>
</svg:g>
<svg:rect x="377" y="201.78" height="128.22" width="109" style="fill:rgb(109,0,0)">
<set attributeName="fill" from="rgb(109,0,0)" to="red" begin="mouseover" end="mouseout" />
</svg:rect>
<svg:g>
<svg:text style="fill:black; stroke:none" x="377" y="347" rotate="45" font-size="8">
srmxml01</svg:text>
</svg:g>
<svg:rect x="491" y="325.02" height="4.9799999999999995" width="109" style="fill:rgb(4,0,0)">
<set attributeName="fill" from="rgb(4,0,0)" to="red" begin="mouseover" end="mouseout" />
</svg:rect>
<svg:g>
<svg:text style="fill:black; stroke:none" x="491" y="347" rotate="45" font-size="8">
srmweb2</svg:text>
</svg:g>
<svg:rect x="605" y="313.17" height="16.830000000000002" width="109" style="fill:rgb(14,0,0)">
<set attributeName="fill" from="rgb(14,0,0)" to="red" begin="mouseover" end="mouseout" />
</svg:rect>
<svg:g>
<svg:text style="fill:black; stroke:none" x="605" y="347" rotate="45" font-size="8">
srmweb1</svg:text>
</svg:g>
<svg:rect x="719" y="261.78" height="68.22" width="109" style="fill:rgb(58,0,0)">
<set attributeName="fill" from="rgb(58,0,0)" to="red" begin="mouseover" end="mouseout" />
</svg:rect>
<svg:g>
<svg:text style="fill:black; stroke:none" x="719" y="347" rotate="45" font-size="8">
srmxml02</svg:text>
</svg:g>
</svg:g>
</svg:svg>
<embed src="myfile.svg">
), it is rendered correctly in IE as:
The problem is that when it is rendered dynmically inline in Internet Explorer, as described in my previous post, it looks like:

There is one interesting difference in how IE handles this. When using the
embed
tag, if you right click on the image, the pop-up context menu will look like this:

Meanwhile, on the Firefox side of things, regardless of whether you
embed
a static SVG file, put the SVG directly inline (if you're using XHTML), or put it in an object
tag, it always looks the same:
As an alternative method, I tried PlotKit which can generate dynamic SVG graphs and charts. So, I built an HTML table from my XML data and told PlotKit to use that and in both IE and Firefox it worked correctly to give me a graph such as:

I found this note in one of his samples:
Note: With SVG, the most compatible way is NOT to put in SVG tags, but create the SVG element with Javascript. Therefore, instead of adding
<svg>
tag, we just have a <div>
container and create the SVG element using SVGRenderer.SVG().And looking at the PlotKit code I can see that his library is dynamically building the SVG using JavaScript DOM APIs to add each element.
Well, since I want to use XSLT to generate my SVG from XML and not write my own version of the PlotKit library to build my SVGs, I tried a hybrid approach. I used the DOM APIs to create a temporary DOM element. I assigned my generated SVG to that element's innerHTML in order to have the browser build a DOM tree out of my SVG. I then created an
svg
DOM element, and appended the first child DOM element of my SVG DOM (the first g
element) to the svg
DOM element and appened the svg
DOM element to the document. Which I would think would be theoretically the same thing, but to no avail.Does anyone out there know how to fix this? How can I make Internet Explorer render my dynamic inline SVG correctly in order to get proper coloring and event handling?
Update: See follow-up
Monday, June 26, 2006
Saturday, June 03, 2006
Compressing HTML, CSS, and JavaScript files
.htaccess
file to compress your HTML, CSS, and JavaScript files. To do this, add these lines to your .htaccess
file:
AddHandler application/x-httpd-php .css .html .js
php_value auto_prepend_file /path/to/gzip-page.php
php_flag zlib.output_compression On
where the path is specific to your installation.
The
gzip-page.php
is needed to define the content-type for the compressed data so that they are not assumed to be PHP files. The code should look something like:
<?php
$pathinfo = pathinfo($PHP_SELF);
$extension = $pathinfo['extension'];
switch ($extension) {
case "css" :
header("Content-type: text/css");
break;
case "html" :
header("Content-type: text/html");
break;
case "js" :
header("Content-type: text/javascript");
break;
default :
break;
}
?>
Now all your HTML, CSS, and JavaScript content will be compressed if the browser supports compressed data (which most do). For Ajax applications this is most useful since you'll probably have a decent amount of JavaScript code to transmit and potentially a fair amount of CSS as well.
Thursday, June 01, 2006
SVG in IE and Firefox
PLUGINSPAGE
attribute of the embed
tag (see http://developer.apple.com/quicktime/compatibility.html for a nice explanation of the technique), but I'm using the object
tag (which is recommended since it complies with the W3C HTML specifications while embed does not and it allows the data to be added inline). Unfortunately, adding the codebase
attribute of http://www.adobe.com/svg/viewer/install/
(the Adobe SVG Viewer install site) and the classid
attribute of clsid:78156a80-c6a1-4bbf-8e6a-3cd390eeb4e2
(the Adobe SVG Viewer ActiveX control id) didn't do what I had hoped for.I haven't figured out how to make IE prompt for the plugin yet, but I did find this page which provides an alternative method to check for IE and alternate using the
object
or the embed
(in his case or inline SVG in my example) rather than using the isASVInstalled()
method I used.In his example, Spartanicus uses the MS "uplevel revealed" conditional comment to define a CSS type as:
.svg{display:none}
*>.svg{display:inline}
Then his HTML looks like:
<!--[if IE]><embed src="img/butterfly_vector.svg" height="120" width="170"><![endif]-->
<object data="img/butterfly_vector.svg" type="image/svg+xml" class="svg" height="120" width="170"/>
Where the class of
svg
in the object
version will causes it not to display if not in Internet Explorer.It's an interesting technique, but in my case of inline generated SVG where I can't use the
embed
tag since I have no src
so I'd have to insert the SVG inline for IE and then also have the wrapped object version. Unfortunately, the non-object version would still display the data in the SVG XML even though it would not be an SVG graphic which would look terrible. So, it doesn't really help me in this case, but I thought it was interesting.
Saturday, May 27, 2006
Inline dynamic SVG from XML with Ajax
Problem: retrieve report data in XML format from a SOA application and generate an HTML textual representation of the report data (ex. a table) as well as a graphical representation (ex. bar graph, pie chart, etc) on the client side using Ajax.
Solution: ok, there's plenty of information online regarding generating the XMLHTTPRequest to fetch the XML data and render HTML, so I won't go into that here other than to say I utilized the prototype.js package to make my life a little simpler. Also, to make my XML manipulation easier cross-browser, I utilized the zXML package from Nickolas C. Zakas (co-author of Professional Ajax which is where I read about it). The one caveat regarding using the zXML package, is that I had to use it to parse the XML from the XMLHTTPRequest.responseText instead of using the pre-parsed XMLHTTPRequest.responseXML like so:
currentReportDOM = zXmlDom.createDocument();
currentReportDOM.async = false;
currentReportDOM.loadXML(request.responseText);
Ok, so I have the report XML data in a DOM object in the variable named currentReportDOM. For this example I chose to format the XML into SVG using XSLT. You could of course do direct manipulation of the DOM object in Javascript if you'd prefer. Nothing spectacular in the XSLT file so I won't show it's contents here, but I apply the XSLT as shown:
// first convert the XML to SVG using XSLT
var xslDom = zXmlDom.createDocument();
xslDom.async = false;
xslDom.load("chart.xsl");
var str = zXslt.transformToText(currentReportDOM.documentElement, xslDom);
Now, the str variable contains the SVG XML as a string, and here's where the difficulties start. If this was XHTML in Firefox, I could just paste in the SVG data in some div like:
$("reportSVGDiv").innerHTML = str;
However, since this needs to work cross-browser, and Internet Explorer doesn't handle
.xhtml
files, this is a .html
file and when you do that in HTML Firefox no longer renders it as an SVG, but just as plain XML, and Internet Explorer doesn't know what to do with it either since it doesn't support SVG natively.Most instructions for embedding inline SVG in an HTML page for Internet Explorer instruct you to use either an
embed
, an object
, or an iframe
and set the source as the SVG file. This works great as along as your SVG data is actually in a file and the file ends with either a .svg
or a .svgz
(compressed) extension. If it was in a file, I could add an object to my page dynamically with the SVG file like:
var svgObject = document.createElement('object');
svgObject.setAttribute('type', 'image/svg+xml');
svgObject.setAttribute('data', 'svgdata.svg');
$("reportSVGDiv").appendChild(svgObject);
And it would be rendered. However, in this case, the data was received as raw XML from a server and rendered to SVG by the browser. Theoretically, you could add the data to the object inline like this:
var svgObject = document.createElement('object');
svgObject.setAttribute('type', 'image/svg+xml');
svgObject.setAttribute('data', 'data:image/svg+xml,'+ str);
$("reportSVGDiv").appendChild(svgObject);
However, since you're no longer referencing a file with a
.svg
or .svgz
extension, the IE MIME type handling -- which depends on file extensions, not the specified MIME type (image/svg+xml
) -- won't recognize it as an SVG.How to get it working in Internet Explorer:
To get Internet Explorer working you first have to install the Adobe SVG Viewer plugin so that IE can render SVGs. Then you have to define the
svg
namespace in your HTML header like:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
Then in the
head
section add the following lines to associate the Adobe SVG viewer with the svg
namespace (since Internet Explorer bases MIME types off of file extensions and we have no file in this case).
<object id="AdobeSVG" classid="clsid:78156a80-c6a1-4bbf-8e6a-3cd390eeb4e2"></object>
<?import namespace="svg" implementation="#AdobeSVG"?>
Now, as long as the SVG data you generate is tagged with the svg namespace (ex. <svg:svg>...</svg:svg>) then placing the SVG XML data in the innerHTML of some div will actually work! Additionally, you don't want the SVG embedded in an object tag in IE since Adobe SVG Viewer always disables scripting when it determines that the SVG file is embedded using the OBJECT tag.
How to get it working in Firefox:
Ironically, even though Firefox supports SVG natively (in XHTML) you can't just stick it in the HTML without embedding it in an
object
(or embed
, or iframe
). So, we have to build the object
and add the SVG data inline as I demonstrated earlier, but I'll give the complete example now:
// remove any old charts
while ($("reportSVGDiv").hasChildNodes()) {
$("reportSVGDiv").removeChild($("reportSVGDiv").firstChild);
}
var svgObject = document.createElement('object');
svgObject.setAttribute('name', 'svg');
svgObject.setAttribute('codebase', 'http://www.adobe.com/svg/viewer/install/');
svgObject.setAttribute('classid', 'clsid:78156a80-c6a1-4bbf-8e6a-3cd390eeb4e2');
svgObject.setAttribute('data', 'data:image/svg+xml,'+ str);
svgObject.setAttribute('width', '1050');
svgObject.setAttribute('height', '550');
svgObject.setAttribute('type', 'image/svg+xml');
$("reportSVGDiv").appendChild(svgObject);
How to get it working cross-browser:
The last piece in the puzzle is how to determine when we're in IE with the Adobe SVG viewer installed, and when we're not. To do that, I use a method to check for the Adobe SVG viewer:
// isASVInstalled
//
// utililty function to check for Adobe SVG viewer
//
function isASVInstalled() {
try {
var asv = new ActiveXObject("Adobe.SVGCtl");
return true;
}
catch(e){ }
return false;
}
Putting it all together:
With all the pieces in place, my final Javascript function to render the SVG inline with dynamic XML data received via Ajax from an SOA looks like:
// toSVG
//
// convert the report XML data to a SVG (scalable vector graphic) image
//
function toSVG() {
// first convert the XML to SVG using XSLT
var xslDom = zXmlDom.createDocument();
xslDom.async = false;
xslDom.load("chart.xsl");
var str = zXslt.transformToText(currentReportDOM.documentElement, xslDom);
// check if we're using the AdobeSVG viewer (Internet Explorer)
if (isASVInstalled() ) {
$("reportSVGDiv").innerHTML = straw;
} else {
// otherwise, we're assuming firebug/Mozilla which can render the SVG directly
// if this was true XHTML instead of HTML, then firebug would also render it directly
// using the inheriting above, however, since it has to be HTML to make IA happy,
// we then have to wrap the SVG in an
I hope this helps someone and saves them from the aggravation I encounted trying to get this to work!
Blog renamed
When I originally named it, I picked pansophy because I wanted a word for knowledge or wisdom that started with 'P' for some alliteration with my name. So, looking up knowledge or wisdom in a thesaurus I found:
pansophy
\Pan"so*phy\, n. [Pan- + Gr. ? wisdom, ? wise: cf. F. pansophie.] Universal wisdom; esp., a system of universal knowledge proposed by Comenius (1592 -- 1671), a Moravian educator.
However, every time I look at it, it looks too much like pansy. So, I'm renaming the blog to Pothoven Post to sound more like a newspaper, but still with the alliteration.
Thursday, April 20, 2006
Ajax: HTML vs XML responses
I voiced a comment regarding my appreciation for the direction used by Rico where XML is always returned, though it may contain an HTML snippet such as:
<ajax-response>
<response type="element" id="personInfo">
[valid XHTML here]
</response>
</ajax-response>
where the type="element" id="personInfo" indicates to Rico to replace the innerHTML of the element with id personInfo with the contents of the response.
Or alternatively:
<ajax-response>
<response type="object" id="formLetterUpdater">
[valid XML data here]
</response>
</ajax-response>
The type="object" id="formLetterUpdater" calls a JavaScript object registered under the name matching the id of formLetterUpdater. That Javascript would then process the contents of the response as XML to extract whatever data is necessary.
For either mechanism, multiple "response" clauses can be returned in a single "ajax-response" allowing multiple sections of a web page to be updated from a single AJAX action if necessary.
My apprehension about this method is that the server side processing has too much knowledge about how the data will be used (by dictating the ids). Of course, that might be able to be handled by having the AJAX response send which ids it wants data for.
Of these two mechanisms from Rico, I think the XML data mechanism has the most flexibility, as it describes the data in XML and the UI can extract what it needs and format it correctly whether by coding manual JavaScript DOM processing as indicated with the formLetterUpdater above, or by utilizing XSLT on the browser to format it as desired. Using XSLT provides multiple advantages for formatting and re-formatting the data for the user without the need to communicate with the server over and over to format the HTML. For example, switching themes, languages, or switching from a table to a graph.
Tuesday, April 18, 2006
Wrapping long strings with XSL 1.0
<!-- recursive section of splitString... don't call directly -->
<xsl:template name="firstString">
<xsl:param name="string" />
<xsl:param name="length" />
<xsl:param name="currentpos" />
<xsl:param name="prefix" />
<xsl:param name="hangingPrefix" />
<xsl:param name="suffix" />
<xsl:param name="hangingSuffix" />
<xsl:param name="padding" />
<xsl:choose>
<xsl:when test="substring($string, $currentpos, 1) = ' '">
<xsl:value-of select="substring($string, 1, $currentpos - 1)" />
<xsl:value-of select="$suffix" />
<xsl:value-of select="$newline" />
<xsl:call-template name="splitString">
<xsl:with-param name="string" select="substring($string, $currentpos + 1)" />
<xsl:with-param name="length" select="$length" />
<xsl:with-param name="prefix" select="$hangingPrefix" />
<xsl:with-param name="hangingPrefix" select="$hangingPrefix" />
<xsl:with-param name="suffix" select="$hangingSuffix" />
<xsl:with-param name="hangingSuffix" select="$hangingSuffix" />
<xsl:with-param name="padding" select="$padding" />
</xsl:call-template>
</xsl:when>
<xsl:when test="substring($string, $currentpos, 1) = '/'">
<xsl:value-of select="substring($string, 1, $currentpos)" />
<xsl:value-of select="$suffix" />
<xsl:value-of select="$newline" />
<xsl:call-template name="splitString">
<xsl:with-param name="string" select="substring($string, $currentpos + 1)" />
<xsl:with-param name="length" select="$length" />
<xsl:with-param name="prefix" select="$hangingPrefix" />
<xsl:with-param name="hangingPrefix" select="$hangingPrefix" />
<xsl:with-param name="suffix" select="$hangingSuffix" />
<xsl:with-param name="hangingSuffix" select="$hangingSuffix" />
<xsl:with-param name="padding" select="$padding" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="firstString">
<xsl:with-param name="string" select="$string" />
<xsl:with-param name="length" select="$length" />
<xsl:with-param name="currentpos" select="$currentpos - 1" />
<xsl:with-param name="prefix" select="$prefix" />
<xsl:with-param name="hangingPrefix" select="$hangingPrefix" />
<xsl:with-param name="suffix" select="$suffix" />
<xsl:with-param name="hangingSuffix" select="$hangingSuffix" />
<xsl:with-param name="padding" select="$padding" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- split a string to a specified size.
Note: there is no newline at the end of the last line to allow additions to the end of the line
string: string to split
length: max length string can be per line
prefix: optional string to prepend to the first line
hangingPrefix: optional string to prepend subsequent lines with
suffix: optional string to append to the end of the first line
hangingSuffix: optional string to append to subsequent lines
padding: optional string to pad the last line of a splt string with to allow extra text to be added after it
-->
<xsl:template name="splitString">
<xsl:param name="string" />
<xsl:param name="length" select="$maxLineLength" />
<xsl:param name="prefix" />
<xsl:param name="hangingPrefix" select="$prefix" />
<xsl:param name="suffix" />
<xsl:param name="hangingSuffix" select="$suffix" />
<xsl:param name="padding" />
<xsl:choose>
<xsl:when test="(string-length($prefix) +
string-length($string) +
string-length($suffix)) > $length">
<xsl:variable name="currentpos" select="$length - string-length($suffix)" />
<xsl:choose>
<xsl:when test="contains(substring($string, 1, $currentpos), ' ') or
contains(substring($string, 1, $currentpos), '/')">
<xsl:call-template name="firstString">
<xsl:with-param name="string" select="concat($prefix, $string)" />
<xsl:with-param name="length" select="$length" />
<xsl:with-param name="currentpos" select="$currentpos" />
<xsl:with-param name="prefix" select="$prefix" />
<xsl:with-param name="hangingPrefix" select="$hangingPrefix" />
<xsl:with-param name="suffix" select="$suffix" />
<xsl:with-param name="hangingSuffix" select="$hangingSuffix" />
<xsl:with-param name="padding" select="$padding" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<!-- no line split character within maximum line length -->
<xsl:value-of select="$prefix" />
<xsl:value-of select="substring($string, 1, $currentpos - string-length($prefix))" />
<xsl:value-of select="$suffix" />
<xsl:value-of select="$newline" />
<xsl:call-template name="splitString">
<xsl:with-param name="string" select="substring($string, $currentpos + 1 - string-length($prefix))" />
<xsl:with-param name="length" select="$length" />
<xsl:with-param name="prefix" select="$hangingPrefix" />
<xsl:with-param name="hangingPrefix" select="$hangingPrefix" />
<xsl:with-param name="suffix" select="$hangingSuffix" />
<xsl:with-param name="hangingSuffix" select="$hangingSuffix" />
<xsl:with-param name="padding" select="$padding" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<!-- less than $length chars left, just write them -->
<xsl:otherwise>
<xsl:value-of select="$prefix" />
<xsl:value-of select="$string" />
<xsl:value-of select="$suffix" />
<xsl:value-of select="substring($padding, 1, ($length - string-length($prefix) - string-length($string) - string-length($suffix))) " />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Sorting groups in XSL
The referenced web page shows an example of getting a group of contacts and sorting it by surname. That's all well and good if your only computing the group once and processing it straight-forward as shown. But what if the XML data is large so calculating the group is costly (in terms of memory and CPU) and you need to use it multiple times and the order is important because you're using it within the context of processing some larger processing loop (ex. 10 at a time associated with another value)?
For an example, let's just say you need to generate a report that has a cover sheet listing all the unique surnames in your file, then separate statistics for each surname such as how many people share that surname. This is a very contrived example, but at least it will demonstrate what I mean.
Using the example in the referenced web page, we could build a list of unique sorted surnames to use in various places in our XSL via:
<xsl:key name="contacts-by-surname" match="contact" use="surname"/>
<xsl:variable name="surnames" select="contact[count(. | key('contacts-by-surname', surname)[1]) = 1]/surname"/>
<xsl:template match="/">
<xsl:call-template name="sortList">
<xsl:with-param name="list" select="$surnames"/>
</xsl:call-template>
<!-- list is now sorted to use whenever necessary -->
<xsl:for-each select="$surnames">
[process sorted surname]
</xsl:for-each>
</xsl:template>
<!-- sort list by the value of its current node -->
<xsl:template name="sortList">
<xsl:param name="list"/>
<xsl:for-each select="$list">
<xsl:sort select="." />
</xsl:for-each>
</xsl:template>
The call to sortList will sort it for all subsequent uses.
So, to see it in action, here is our sample data:
<records>
<contact id="0001">
<title>Mr</title>
<forename>John</forename>
<surname>Smith</surname>
</contact>
<contact id="0002">
<title>Dr</title>
<forename>Amy</forename>
<surname>Jones</surname>
</contact>
<contact id="0003">
<title>Mr</title>
<forename>John</forename>
<surname>Doe</surname>
</contact>
<contact id="0004">
<title>Mr</title>
<forename>Jack</forename>
<surname>Smith</surname>
</contact>
<contact id="0005">
<title>Mr</title>
<forename>Mike</forename>
<surname>Jordan</surname>
</contact>
<contact id="0006">
<title>Mr</title>
<forename>Jim</forename>
<surname>Johnson</surname>
</contact>
<contact id="0007">
<title>Mr</title>
<forename>Randy</forename>
<surname>Jones</surname>
</contact>
<contact id="0008">
<title>Mr</title>
<forename>Bobby</forename>
<surname>Jones</surname>
</contact>
<contact id="0009">
<title>Mr</title>
<forename>Glenn</forename>
<surname>Smith</surname>
</contact>
<contact id="0010">
<title>Mr</title>
<forename>Bill</forename>
<surname>McDonnald</surname>
</contact>
</records>
Here is the complete style sheet:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" indent="no" omit-xml-declaration="yes" />
<xsl:variable name="newline">
<!-- Hex 10 is a line feed, and hex 13 is a carriage return -->
<xsl:value-of select="' '" />
</xsl:variable>
<!-- Remove all whitespace from the source XML so that it is not echoed into the new report -->
<xsl:strip-space elements="//*" />
<xsl:key name="contacts-by-surname" match="contact" use="surname" />
<xsl:variable name="surnames" select="//contact[count(. | key('contacts-by-surname', surname)[1]) = 1]/surname" />
<xsl:template match="/">
<xsl:text>Full list of surnames:</xsl:text>
<xsl:value-of select="$newline" />
<xsl:for-each select="//surname">
<xsl:value-of select="." />
<xsl:value-of select="' '" />
</xsl:for-each>
<xsl:value-of select="$newline" />
<xsl:value-of select="$newline" />
<xsl:text>List of unique surnames:</xsl:text>
<xsl:value-of select="$newline" />
<xsl:for-each select="$surnames">
<xsl:value-of select="." />
<xsl:value-of select="' '" />
</xsl:for-each>
<xsl:value-of select="$newline" />
<xsl:value-of select="$newline" />
<xsl:call-template name="sortList">
<xsl:with-param name="list" select="$surnames" />
</xsl:call-template>
<!-- list is now sorted to use whenever necessary -->
<xsl:text>List of unique surnames (sorted):</xsl:text>
<xsl:value-of select="$newline" />
<xsl:for-each select="$surnames">
<xsl:value-of select="." />
<xsl:value-of select="' '" />
</xsl:for-each>
<xsl:value-of select="$newline" />
<xsl:value-of select="$newline" />
<xsl:text>People per surname:</xsl:text>
<xsl:value-of select="$newline" />
<xsl:for-each select="$surnames">
<xsl:variable name="surname" select="."/>
<xsl:value-of select="$surname" />
<xsl:value-of select="' ('" />
<xsl:value-of select="count(//contact[surname = $surname])" />
<xsl:value-of select="'):'" />
<xsl:value-of select="$newline" />
<xsl:for-each select="//contact[surname = $surname]">
<xsl:sort select="forname"/>
<xsl:value-of select="' '" />
<xsl:value-of select="forename" />
<xsl:value-of select="$newline" />
</xsl:for-each>
<xsl:value-of select="$newline" />
</xsl:for-each>
</xsl:template>
<!-- sort list by the value of its current node -->
<xsl:template name="sortList">
<xsl:param name="list" />
<xsl:for-each select="$list">
<xsl:sort select="." />
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
And here is the report:
Full list of surnames:
Smith Jones Doe Smith Jordan Johnson Jones Jones Smith McDonnald
List of unique surnames:
Smith Jones Doe Jordan Johnson McDonnald
List of unique surnames (sorted):
Doe Johnson Jones Jordan McDonnald Smith
People per surname:
Doe (1):
John
Johnson (1):
Jim
Jones (3):
Amy
Randy
Bobby
Jordan (1):
Mike
McDonnald (1):
Bill
Smith (3):
John
Jack
Glenn
Friday, February 24, 2006
Mastering Ajax
3 part series on Mastering AJAX:
http://www-128.ibm.com/developerworks/web/library/wa-ajaxintro1.html
http://www-128.ibm.com/developerworks/web/library/wa-ajaxintro2/
http://www-128.ibm.com/developerworks/java/library/wa-ajaxintro3/index.html
2 part series on using SOAP with AJAX
http://www-128.ibm.com/developerworks/webservices/library/ws-wsajax/
http://www-128.ibm.com/developerworks/webservices/library/ws-wsajax2/
See the round-up of 30 AJAX tutorials for more useful information.
I'm also in the process of reading Ajax in Action which is very well written.
Monday, January 30, 2006
Enabling JSP in Tomcat
For this example, I'm setting it up on a SuSE Linux system so your directory structure may vary.
- Create a JSP directory in your tomcat webapps directory. In this case, the default webapps directory for Tomcat is
/srv/www/tomcat5/base/webapps/
so I make ajsp
directory there. - Under the
jsp
directory I create the basic webapp directory structure and create a directory named/srv/www/tomcat5/base/webapps/jsp/WEB-INF
- In the
WEB-INF
directory, I create a basic deployment descriptor namedweb.xml
which looks like:
<?xml version="1.0" encoding="ISO-8859-1"?>
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/j2ee/dtds/web-app_2_3.dtd">
<web-app>
<display-name>My JSPs<display-name>
</web-app> - In the tomcat configuration directory
/etc/tomcat5/base/Catalina/localhost
add the context path for the JSPs in a file namedjsp.xml
that looks like:
<Context path="/jsp" docBase="jsp"
debug="0" reloadable="true" /> - Restart Tomcat (
rctomcat5 restart
) - Create some sample JSP file in the jsp directory (
/srv/www/tomcat5/base/webapps/jsp
such as:
<html>
<head>
<title>Hello World</title>
</head>
<body>
<h1><%= "Hello World" %><h1>
</body>
</html> - Make sure it's working via
http://localhost:8080/jsp/index.jsp
- I run Tomcat under Apache via mod_jk so that I can utilize the SSL layer of Apache. So, to make the JSP transparently accessible from Apache, I update the
/etc/apache2/conf.d/jk.conf
and add the following lines:
# Add the jsp directory
Alias /jsp "/srv/www/tomcat5/base/webapps/jsp"
<Directory "/srv/www/tomcat5/base/webapps/jsp">
Options Indexes FollowSymLinks
allow from all
</Directory>
JkMount /jsp ajp13
JkMount /jsp/* ajp13
<Location "/jsp/WEB-INF/">
AllowOverride None
deny from all
</Location> - After restarting Apache (
rcapache2 restart
) I can then reference the JSPs directly from Apache, such ashttp://localhost/jsp/index.jsp
Multimedia setup for SuSE 10
http://www.thejemreport.com/mambo/content/view/178/42/
Additionally, adding some of the extra Yast software sources from http://www.suseforums.net/index.php?showtopic=18575 or http://www.opensuse.org/Additional_YaST_Package_Repositories#External_YaST_Repositories is useful.
Current Earth image wallpaper
- Install xplanet RPM if not already installed.
- I made a
.xplanet
directory in my home directory in which I copied the config file from/usr/share/xplanet/config
- In the
[earth]
section of the config file, I added the lines:
map=/home/username/.xplanet/earthmap4k.png
bump_map=/home/username/.xplanet/earthbump4k.png
bump_scale=5
cloud_map=/home/pothoven/.xplanet/clouds.jpg - To get the dynamic cloud map, I downloaded the
download_clouds.pl
Perl script from http://xplanet.sourceforge.net/clouds.php to my .xplanet directory. Note: you may wish to change this line in the script to suite your preferences:
my $MaxDownloadFrequencyHours = 1; - I then added this line to my crontab
0 * * * * cd /home/username/.xplanet; perl download_clouds.pl > /dev/null
to update the cloud information once an hour - To get the topography and enahanced coloring maps in my config, they can be purchased from J.H.T.'s Planetary Pixel Emporium (though a Google search for
earthmap4k
may reveal other sources). - If you want to track a satelite, such as the Hubble telescope or International Space Station (ISS), download the lastest tracking data with either:
wget -O science.tle http://www.celestrak.com/NORAD/elements/science.txt
for scientific satelites (Hubble) or:
wget -O stations.tle http://celestrak.com/NORAD/elements/stations.txt
for space stations (ISS). In the resulting science.tle file, you will see a section with the header "HST" (Hubble space telescope), place those lines in a file namedhubble.tle
which will then look like:
HST
1 20580U 90037B 06008.34262361 .00000587 00000-0 33114-4 0 5098
2 20580 28.4688 11.8178 0003636 285.4875 74.5312 14.99943058661070
If you also want to track the IST, also add the "ISS (ZARYA)" lines from thestations.tle
file. These lines will look something like:
ISS (ZARYA)
1 25544U 98067A 06009.46434028 .00013978 00000-0 10043-3 0 1177
2 25544 51.6445 283.5661 0010748 133.7206 217.3373 15.73896465408147
Then, create a file namedhubble
in your.xplanet
directory that contains:
20580 "" image=hubble.png transparent={0,0,0} trail={orbit,-10,0,5} color=green
for tracking the Hubble and/or:
25544 "" image=iss.png transparent={0,0,0} trail={orbit,-10,0,5} color=green
for tracking the space station. Finally, add this line to the[earth]
clause of the config:
satellite_file=/home/username/.xplanet/hubble - Finally, set it up in KDE. right click on the background.
- Select
Configure Desktop
. - Click on
Background
in the left hand panel. - In the Background group box, I have
No picture
selected.
- Click on
Advanced Options
. - I have
Use the following program for drawing the background
checked. - Select XPlanet in the list.
- Click on Modify.
- My
Command
entry is:
xplanet --config /home/username/.xplanet/config
--geometry %xx%y --num_times 1 --output %f.jpg
--latitude 27.96 --longitude -82.48 --radius 60 &&
mv %f.jpg %f
(the latitude and longitude is for where I live -- adjust to suit) - My
Preview cmd
entry is the same as above. - I have my
Refresh time
set to 5 minutes if I'm tracking satellites, and 30 minutes if not. - Enjoy a dynamic updating picture of the earth such as:
Update - December 19, 2007
First off, I want to point out that XPlanet is an option for Windows as well. See XPlanet on Microsoft Windows. You can use the same Perl script to download the latest cloud cover image using ActivePerl. When you run XPlanet use the
-fork
option to have it periodically update the background.Secondly, the instructions above were for KDE, but if you use Gnome, here are the instructions to get XPlanet to work with Gnome2.