//
// common.js -- Common JavaScript Functions
//
// by Ian Flanigan
// (C) 2001-2003, All rights reserved.
//

// Escape HTML in HTML
function escapeHTML(s)
{
  if (s == null)
    return "";

  s = s.replace(/</g,"&lt;");
  return s;
}

// Check for IE
function isIE()
{
  return navigator.userAgent.match(/MSIE/) ? true : false;
}

// Check for Mozilla
// FIXME: not right!
function isMozilla()
{
  return !isIE();
}

// isCrippledBrowser()
function isCrippledBrowser()
{
  if (!navigator.userAgent.match(/Mozilla\/5/)
      && !navigator.userAgent.match(/IE 6.0/))
    return true;
  return false;
}

// Returns a string containing n copies of an input string.
function copystr(str, n)
{
  var result = "";

  for (var i = 0; i < n; i++)
    {
      result += str;
    }

  return result;
}

// Returns true if thing is in array.
function array_in(thing, array)
{
  thing += "";

  for (var i = 0; i < array.length; i++)
    {
      if (array[i].toUpperCase() == thing.toUpperCase())
	return true;
    }
  return false;
}

// Indent HTML
//   This does a pretty bad job of indenting HTML, but it's better
//   than nothing.  Because IE does not preserve the original source
//   formatting, we have to try to "reconstitute" it.  Besides not
//   preserving the formatting, IE often doesn't even preserve the
//   tags correctly, so we have to correct for this, too.
//
//   spacing - number of spaces to indent each line
//   str - the HTML string to indent
//
//   NOTE: This function assumes, in some ways, that str comes from IE
//   and thus is not a completely general solution.  Try it, though,
//   it may work.
//
//   BUGS: Dose not parse comment tags correctly.  Should wrap at 80
//   columns.
//

// CONSTANTS
//   TAGS_BROKEN - IE doesn't put end tags after these (most of the time)
//   TAGS_QWERK_ONELINE - bodies follow the tags on the same line
//   TAGS_CONTENT - content tags -- not indented
//   TAGS_NOEND - tags that have no end tag
//
//   CONTEXT_AFTERNOTHING - beginning context
//   CONTEXT_AFTERTAG - after a tag
//   CONTEXT_AFTERCONTENT - after some content
//
var TAGS_BROKEN = ['LI','P'];
var TAGS_QWERK_ONELINE = ['LI','H1','H2','SCRIPT'];
var TAGS_CONTENT = ['A','I','B','TITLE','SPAN'];
var TAGS_NOEND = ['BR','HR','!--'];

var CONTEXT_AFTERNOTHING = -1;
var CONTEXT_AFTERTAG = 0;
var CONTEXT_AFTERCONTENT = 1;

function indentHTML(spacing, str)
{
  var spaces = copystr("&nbsp;", spacing);
  var result = '';
  var tagregex = new RegExp('&lt;(/?)([^> ]+)[^>]*>');
  var spaceregex = new RegExp("[ \t\r\n]*$");
  var stack = [];
  var context = CONTEXT_AFTERNOTHING;
  var match = null;

  while (match = tagregex.exec(str))
    {
      // Set up convenience variables
      var is_close = (match[1] ? true : false);
      var tag = match[2];
      var whole_tag = match[0];
      var top = stack[stack.length-1];

      var content = str.substr(0,match.index);
      content = content.replace(spaceregex,'');	// strip trailing spaces

      // Decide whether we need a newline befor the current content.
      // With non-content tags, we always output a newline before we
      // insert the tag.  So, usually we need another newline before
      // the content (except for one-line tags), if there is any.
      // This includes content-tags.
      //
      // So we want a newline if:
      //   o we are following a tag
      //   o and that tag is not a one-line tag
      //   o and we have some content or a content tag
      //
      if ((context == CONTEXT_AFTERTAG)
	  && !array_in(top, TAGS_QWERK_ONELINE)
	  && (content.length > 0 || array_in(tag, TAGS_CONTENT)))
	{
	  result += "<BR>";
	  result += copystr(spaces, stack.length);
	}

      result += content;	       // add content to result
      str = str.substr(match.index + whole_tag.length); // trim source

      // Set the context if we added content.  This way following
      // content knows that it is already in a content context and
      // does not need another newline.
      if (content.length > 0 || array_in(tag, TAGS_CONTENT))
	{
	  context = CONTEXT_AFTERCONTENT;
	}

      // If we're not going to split this tag, just add it to the
      // result and continue.
      if (array_in(tag, TAGS_CONTENT)) 
	{
	  result += whole_tag;
	  continue;
	}
      
      // If this is a close tag, try to find the start tag for it on the
      // stack.  In a well-formed document, it should be on the top.
      // However, IE does not always return well-formed documents, so we
      // have to look further for our twin.  Badly formed documents can
      // clear the stack and cause bad output.
      if (is_close)
	{
	  while (stack.length > 0)
	    {
	      var top = stack.pop();
	      if (top.toUpperCase() == tag.toUpperCase())
		break;
	    }
	}

      // If this is a known broken tag (meaning there is no end tag for
      // it), and the top of the stack has a broken tag, treat this one
      // as a close tag and add a simulated close tag to the output.
      if (!is_close
	  && array_in(top, TAGS_BROKEN)
	  && array_in(tag, TAGS_BROKEN))
	{
	  var old_top = stack.pop();

	  // Normally, insert a newline before the close tag.  Don't
	  // insert the newline if this is a one-line tag UNLESS we're
	  // coming after a tag anyway.  Note: This causes empty
	  // one-line tags to be split without the hack below.
	  if (!array_in(old_top, TAGS_QWERK_ONELINE)
	      || context == CONTEXT_AFTERTAG)
	    {
	      result += "<BR>";
	      result += copystr(spaces, stack.length);
	    }

	  // Add the tag to the result and set our context.
	  result += "&lt;/"+old_top+">";
	  context = CONTEXT_AFTERTAG;
	}

      // If we're not at the top of the document, and this is NOT a
      // close tag for a one-line tag in a content context, then
      // output a newline.  Note: This causes empty one-line tags to
      // be split without the hack below.
      if (context != CONTEXT_AFTERNOTHING
	  && !(is_close
	       && array_in(top, TAGS_QWERK_ONELINE)
	       && context == CONTEXT_AFTERCONTENT))
	{
	  result += "<BR>";
	  result += copystr(spaces, stack.length);
	}

      // Add the tag to the result.
      result += whole_tag;

      // Set the context -- cheat if we've got a one-line tag and
      // force a content context.  This prevents the one-line close
      // tag from being split if there is no actual content (as for
      // <SCRIPT> tags).
      context = (array_in(tag, TAGS_QWERK_ONELINE)
		 ? CONTEXT_AFTERCONTENT
		 : CONTEXT_AFTERTAG);

      // If this is an open tag (that has an end tag), push it on to
      // the stack.
      if (!is_close
	  && !array_in(tag, TAGS_NOEND))
	{
	  stack.push(tag);
	}
    }

  return result;
}


// Insert source of page back into page
//
//   id - the ID of the element into which the source is inserted
//
// Note: the element _must_ be parsed _before_ this function is called
// or nothing will happen as the element won't exist yet.  Likewise,
// if the page is not fully parsed, the HTML that shows up will be
// truncated.  It is best to use this function in an onLoad handler.
//
// Other Notes: In the innerHTML string, the <head> tag gobbles the
// whitespace after it, while </head> gobbles the whitespace before
// it.  Likewise <pre> gobbles an immediate newline, but leaves
// anything else alone.  There may be other oddities that I haven't
// found yet.  This function does nothing to correct these problems.
//
function insertDocumentSourceIntoElement(id)
{
  if (isCrippledBrowser())
    return;

  var s = "";
  for (var n = 0; n < document.childNodes.length; n++)
    {
      if (isIE())
	s += escapeHTML(document.childNodes[n].outerHTML);
      else
	{
	  s += escapeHTML(document.childNodes[n].innerHTML);
	}
    }

  if (isMozilla())
    {
      s = '&lt;html>' + s + '&lt;/html>';
    }

  if (true || isIE())
    {
      s = indentHTML(2, s);
    }


  var e = document.getElementById(id);
  if (e != null)
    e.innerHTML = s;
}

// Scroll content of an element
//
//   id - the ID of the element to scroll
//
// Note: this looks very weird for most element types, except <PRE>.
// And right now internal tags are not detected, so they'll cause
// spurrious bad formatting to happen.
//
function scrollElement(id)
{
  var e = document.getElementById(id);
  if (e == null)
    return;

  if (e.scrollLines == null)
    {				// initialize
      var s = e.innerHTML;
      e.scrollLines = s.split("\n");
      e.scrollTop = 0;
    }

  e.scrollTimerID = window.setInterval("scrollElementOneLine('"+id+"')", 400);
}

function scrollElementOneLine(id)
{
  var e = document.getElementById(id);
  if (e == null)
    return;

  var s = "";
  e.scrollTop++;
  if (e.scrollTop >= e.scrollLines.length)
    e.scrollTop = 0;

  var l = e.scrollTop;
  for (var i = 0; i < e.scrollLines.length; i++)
    {
      s += e.scrollLines[l] + "\n";
      l++;
      if (l >= e.scrollLines.length)
	l = 0;
    }

  e.innerHTML = s;
}

function fixModernizers()
{
  var links = document.links;
  var href_re = new RegExp('href="/','i');

  for (var i = 0; i < links.length; i++)
    {
      if (links[i].outerHTML)	// for IE
	{
	  var s = links[i].outerHTML.replace(href_re,
					     'href="http://www.lean.to/');
	  links[i].outerHTML = s;
	}
      else if (links[i].attributes.href.value.charAt(0) == '/')
	{
	  links[i].attributes.href.value = ( 'http://www.lean.to'
					     +links[i].attributes.href.value );
	}
    }
}

function standardOnload()
{
  insertDocumentSourceIntoElement('backgroundContent');
  fixModernizers();
}
