// "Random quote" script, by Michelangelo Grigni.
// Tested in firefox, mozilla, ie6.
//
// This does the following:
//  1. fetches and parses an XML quote data (specified by quoteFile)
//  2. finds document elements with ids "author" and "quote"
//  3. picks a random quote, and displays it in those elements.
// If you want to show more than just a first random quote, bind
// "randQuote()" as an action somewhere.

// Try to start a request for the URL, calling back to the HANDLER.
// Returns either the request object, or false if we cannot do it.
function startRequest(url, handler)
{
 // mozilla, ie7:
 var req = window.XMLHttpRequest && new window.XMLHttpRequest();
 // ie5, ie6:
 if (!req && window.ActiveXObject) {
   req = new ActiveXObject("Msxml2.XMLHTTP");
   if (!req) req = new ActiveXObject("Microsoft.XMLHTTP");
 }
 if (!req) return false;
 try {
  // For debugging, we give each request a unique name:
  if (!startRequest.counter) startRequest.counter = 0;
  // IE6 bug: cannot set this field
  //req.name = "[" + url + ":" + (startRequest.counter++) + "]";
  // Bind handler in a closure, passing it the request object:
  req.onreadystatechange = function(){handler(req);};
  req.open("GET", url, true); // open it
  req.send(""); // send it
 } catch (e) {
  alert("startRequest failed! " + e);
  return false;
 }
 return req;
}

// entities of current XML document, possibly used by xml2dom
var entities;

// Our list of (author,quote) pairs, XML objects parsed from quoteFile.
// This is a global variable, initialized once by myQuoteHandler, and then
// read by randQuote() each time we want to display a quote.
var pairs = new Array();
function addPair(x,y) { pairs[pairs.length] = new Array(x,y); } 
function myQuoteHandler(req)
{
  var state = req.readyState;
  if (req.readyState != 4) return; // not done yet
  if (req.status != 200) { // done, but not ok
    //alert(req.name + " failed: " + req.statusText);
    alert("request failed: " + req.statusText);
    return;
  }
  // Done and ok, so parse the XML into (author,quote) pairs:
  var xml = req.responseXML;
  entities = xml.doctype.entities;
  var authors = req.responseXML.getElementsByTagName('author');
  for (var i=0; i<authors.length; ++i) {
    var quotes = authors[i].getElementsByTagName('quote');
    for (var j=0; j<quotes.length; ++j)
      addPair(authors[i], quotes[j]);
  }
  if (pairs.length == 0) {
    alert("error: could not parse the quote database!");
  }
  // Done parsing, now try to show our first random quote.
  randQuote();
}

// Now start things going, reading data from quoteFile (an URL).
var quoteFile = quoteFile || "quotes.xml"; // a default value
startRequest(quoteFile, myQuoteHandler);

// xml2dom creates a DOM clone of the given XML structure.
// We translate the XML 'comment' tag specially.
// NOTE: importNode from XML (with familiar tags like P, BR, etc.)
// does not work as we might have hoped.
function xml2dom(node, is_first, is_last)
{
 var type=node.nodeType;
 // 3: text
 if (type==3) {
   // Reduce multiple whitespace chars to a single space,
   // which only seems necessary in IE6.  But "\s" seems to
   // match &nbsp; in mozilla, so be careful of that.
   var text = node.data.replace(/[ \t\r\n]+/g, ' ');
   if (is_first) text = text.replace(/^\s/, '');
   if (is_last) text = text.replace(/\s$/, '');
   return document.createTextNode(text);
 }
 // 8: comment
 if (type==8) return document.createComment(node.data);
 // 5: entity reference (IE6 parser does not expand them?)
  if (type==5) {
   // alert("expanding entityref " + node.nodeName);
   // document.createEntityReference(node.data) fails in HTML docs
   return xml2dom(entities.getNamedItem(node.nodeName));
 }
 // 2: DocumentFragment, or 6: Entity.
 if (type==2 || type==6) {
   var ret = document.createDocumentFragment();
   var kids = node.childNodes;
   for (i=0; i<kids.length; ++i)
    ret.appendChild(xml2dom(kids[i], (i==0), (i+1==kids.length)));
   return ret;   
 }
 // element
 if (type==1) {
  var tag = node.tagName, kids = node.childNodes, attr=node.attributes, i;
  var ret = document.createElement(tag=='comment'? 'span' : tag);
  for (i=0; i<attr.length; ++i) ret.setAttribute(attr[i].name, attr[i].value);
  for (i=0; i<kids.length; ++i) 
   ret.appendChild(xml2dom(kids[i], (i==0), (i+1==kids.length)));
  if (tag != 'comment') return ret;
  // Make "comment" contents initially hidden, revealed by a button.
  var closedmark = attr["closedmark"];
  return hiddenItem(ret, closedmark && closedmark.value);
 }
 alert("xml2dom: unexpected nodeType " + type + " " + node.nodeTypeString);
 return null;
}

// Given a DOM node, return a "hidden button" version of it.
function hiddenItem(node, closedtext)
{
 closedtext = (closedtext || '+');
 var hid = document.createElement('div');
 hid.appendChild(document.createElement('br'));
 hid.appendChild(node);
 hid.appendChild(document.createElement('br'));
 hid.style.display = 'none';
 var anc = document.createElement('a');
 anc.href = 'javascript:toggleDisplay()'; // just for the user
 anc.title = 'comment';
 anc.innerHTML = closedtext;
 anc.onclick = function() { toggleDisplay(hid, anc, closedtext); return false; }
 var ret = document.createElement('span');
 ret.appendChild(document.createTextNode('['));
 ret.appendChild(anc);
 ret.appendChild(hid);
 ret.appendChild(document.createTextNode(']'));
 return ret;
}
  
// Toggle the display of a given DOM element.
function toggleDisplay(node, anchor, closedmark) {
  var s = node.style;
  if (s.display == 'none') {
    s.display = 'inline';
    anchor.innerHTML = '&lt;close&gt;'; // '&mdash;'
  } else {
    s.display = 'none';
    anchor.innerHTML = closedmark;
  }
}

// Reduce a date string to the year ("1999.12.31" to "1999")
// by truncating at the first dot.
function date2year(d)
{
  if (!d) return d;
  var dot = d.indexOf('.');
  if (dot < 0) return d;
  return d.substring(0,dot);
}


// Pick a random (author,quote) pair, and display it in the document.
// Returns an undefined value (same as void(null)), so it may be
// conveniently used in href='javascript:randQuote()'.
function randQuote() {
  if (pairs.length==0) return; // probably the parsing failed
  // Pick a random pair:
  var rand = Math.round(Math.random()*pairs.length-0.5);
  var pair = pairs[rand];
  var text = pair[1].childNodes;
  var q = document.getElementById("rand:quote");
  if (!q) return;
  q.innerHTML = ''; // clear it
  for (var i=0; i < text.length; ++i) {
   q.appendChild(xml2dom(text[i]));
  }
  // If available, append "[attribution]" after quote.
  var attr = pair[1].getAttribute('attrib');
  if (attr) {
   q.appendChild(document.createElement('br'));
   q.appendChild(document.createTextNode("[" + attr + "]"));
  }
  var a = document.getElementById("rand:author");
  if (!a) {
   // alert('Could not find the author element!');
   return;
  }
  //alert(a.nodeType + ' ' + a.tagName + ' ' + a.childNodes[0].data);
  a.innerHTML = ''; // clear it
  // Build up text: "Author Name (DOB to DOD)"
  var text = pair[0].getAttribute('name');
  var dob = date2year(pair[0].getAttribute('dob'));
  var dod = date2year(pair[0].getAttribute('dod'));
  if (dob) text += " (" + dob + " to " + (dod? dod : "?") + ")";
  a.appendChild(document.createTextNode(text));
  //  Add each author comment (direct children only).
  var comments = pair[0].getElementsByTagName('comment');
  for (var i=0; i < comments.length; ++i) {
    if (comments[i].parentNode == pair[0]) {
      a.appendChild(document.createTextNode(' '));
      a.appendChild(xml2dom(comments[i]));
    }
  }
  return;
}

// eof
