
/*
 *
 * File:      $RCSfile: framework.js,v $
 * Version:   $Revision: 2.0 $
 * Revised:   $Date:  $
 *            $Author: mbm $
 *
 * Created:   2009-06-01
 * Author:    sectra:mbm
 * Project:   Web
 *
 *
 * Description
 *   Non-obtrusive page behaviors for Sectra web sites.
 *
 * Compatibility:
 *   IE      >= 5.5
 *   Mozilla >= 5.0
 *   Opera   >= 7.0
 *   Firefox >= 1.0
 *   Safari  >= 1.0
 *   Gecko   >= 1.0
 *
 * Dependencies:
 *   - jQuery
 *   - si_dommenu
 *
 * General comments:
 *   Based on previously used pg_layout.js. There are still tons of 'oldish'
 *   code that can can be further optimized using jQuery constructs. 
 *
 *   [c1]: When reloading a page in FF, it the scripts are thrown out of 
 *         context before the actual page, making event handlers still 
 *         attached to elements in the page to cause script errors on, for 
 *         example, mouse over. A test is therefore performed in each event 
 *         handler before calling functions in the script. 
 * 
 *         Also, is seems that the default (function call) context "leaves" 
 *         slightly before the scripts are removed from the window object, so 
 *         for testing for presence of a function in the window context to be 
 *         safe the function must be called on the window context as well.
 *
 *   Classes/global objects:
 *   - DETECT:          Browser detection
 *   - SiTools:         Utility functions
 *   - SiHtml:          Simple HTML code generation
 *   - SiJS:            Load scripts dynamically
 *   - SiFile:          Helper functions for reading external files
 *   - SiURL:           Helper functions for handling URLs and paths
 *   - SiDomHelp:       Helper functions for the browser DOM
 *   - Cookies:         Helper functions for handling cookies
 *   - SiTargetGroup:   Handling target group selection
 *   - SiFlash:         Flash Player instantiation
 *   - SiFlvPlayer:     FLV Player instantiation. 
 *   - SiMedia:         Media Player instantiation. 
 *   - SiMailLinks:     Decoding e-mail links
 *   - SiActiveLinks:   Creation of active links, i.e. hover effects, 'this document' links indicated as 'current'
 *   - SiFoldingItems:  Initialization of flowing lists and headings.
 *   - SiSwitchItems:   APA
 *   - SiInlineForms:   Handling of inline forms. An inline form is included in a page by a link 
 *   - SiInlineAjax:    Ajax
 *   - SiPageCaptions:  Stylized headigs by the means of replacing each element matching the 
 *   - SiSplash:        Splash (contact) "windows".
 *   - SiIFrame:        Helper for creating IFRAMEs.
 *   - SiMenu:          Menu system.
 *   - SiDocumentPopup: Makes links to documents (e.g. PDF files) to load content via the 
 *   - SiTrack:         Tracking code installation.
 *   - SiUnsupported:   Resolving unsupported features, i.e. making IE6 to behave
 *   - SiKeyboard:      Handling keyboard focus. 
 *   - SiMiniSearch:    Initialization of the minisearch field
 *   - SiSocialLinks:   Install Add-this widget. 
 *   - SiDebug:         Debug menu
 *   - SiLayout:        Main Layout class
 */

/* FIXA:

class . -> sisplashform

.folding 

*/


/*===========================================================================
 *
 * Configuration
 *
 */

SIF_CONFIG = {

  TARGET_GROUPS: {
    COOKIE_NAME:             'si_tgtgroup',
    DEFAULT_NAME:            'deptmgr'
  },

  ANALYTICS: {
    // Account ID, override for sub-sites:
    CODE:                    window._si_gacode,
    DOMAIN:                  'none',
    REWRITE: {
      FROM:                  [/.*\b(__sfsubmit_)?template=([^&]*)\b.*/,
                              /.*\/sfsubmit\.php$/],
      TO:                    ['$2',
                              null]
    }
  },

  ACTIVE_LINKS: {
    SELECTOR: {
      // Install active links on everything below...
      ROOT:                  '#banner, #message, #highlights, #minisearchform',
      // ...matching:
      ELEM:                  'A, INPUT, MAP'
    },
    // CSS and image properties:
    CURRENT_PAGE_CLASS:      'sipcurrentpage',
    HOVER_CLASS:             'sipbuttonhover',
    IMAGE: {
      DEFAULT_SUBSTR:        'default',
      ACTIVE_SUBSTR:         'active',
      HOVER_SUBSTR:          'hover'
    }
  },

  FOLDING_LISTS: {
    // Selector for lists to fold:
    SELECTOR:                'ul.folding',
    CANFOLD_CLASSNAME:       'canfold',
    CLOSED_CLASSNAME:        'closed'
  },
  
  FOLDING_PARAGRAPHS: {
    // Selector for H*-P:s to fold:
    SELECTOR:                'h2.folding, h3.folding, h4.folding',
    END_SELECTOR:            'img.leftmargin, div[id], .nofold',
    OPEN_CLASSNAME:          'open',
    BODY_CLASSNAME:          'foldingbody'
  },

  INLINE_FORMS: {
    SELECTOR:                'p.inlineform',
    ASSIGNED_CLASSNAME:      'inlineformassigned'
  },

  INLINE_AJAX: {
    SELECTOR:                '.inlineajax'
  },

  PAGE_CAPTIONS: {
    SELECTOR:                '#message > h1:not(.leftmargin), .eurocaps',
    HANDLER_URL:             '/common/swf/Text/Text.swf'
  },

  DOCUMENTPOPUP: {
    // Selector for links that should show up in a separate window:
    SELECTOR:                'A[href*=.pdf], A[href*=.tif], A:contains(.jpg)',
    // CSS for indication of those links:
    CLASSNAME:               'siplinkdecorationpopup',
    SCRIPT:                  '/common/php2/streamdown.php'
  },

  FLASH: {
    REQUIRE_VERSION:         8,
    PLAYER: {
      ID:                    'swfplayer',
      // CSS for flash controls:
      CLASSNAME:             'swf'
    },
    BLOCKER: {
      // CSS for flash blocker handling:
      CLASSNAME:             'flashblock',
      REPLACEMENT_CLASSNAME: 'noflash'
    }
  },

  MEDIA: {
    // Selector for media contaniers:
    SELECTOR:                'DIV.mediacontainer',
    PLAYER: {
      ID:                    'mediaCtrl',
      CLASSNAME:             'mediactrl',
      UI: {
        // Media contanier widgets:
        CLOSEBTN:            '/common/styles2/images/_fw_mediacontanier_close.gif',
        SETTINGSBTN:         '/common/styles2/images/_fw_mediacontanier_settings.gif',
        BGCOLOR:             '#2b2b2b'
      }
    },
    WMV: {
      PLAYER: {
        CLASSNAME:           'wmv mediactrl',
        STDBY_MSG:           'Loading Windows Media Player components...'
      }
    },
    FLV: {
      PLAYER: {
        CLASSNAME:           'flv swf mediactrl',
        URL:                 '/common/swf/FlvPlayer/main.swf',
        URL_2:               '/common/swf/FlvPlayer/main.100327.swf'
      }
    }
  },

  SPLASH: {
    // URL pattern for bringing up the splash window on page call:
    LOCATION_PATTERN:        /.*sipsplash=(.*)/,
    // Link selector for bringing up the splash window on link click:
    LINK_SELECTOR:           'A.splashform, A.splash, A[href*=.jpg], A[href*=.gif], A[href*=.png], AREA[href*=.jpg], AREA[href*=.png]',
    // Form button selector for commiting splash window:
    DONEBUTTON_SELECTOR:     'INPUT:submit[class*=done]',
    // Splash window UI CSS:
    UI: {
      CANVAS_CLASS:          'sipsplashcanvas',
      OUTSIDE_ID:            'sipsplashbg',
      BOX_CLASS:             'sipsplashbox'
    }
  },

  MINISEARCH: {
    // Inline search form 'default' text:
    INPUT_DEFAULT_VALUE:     'Find at sectra.com',
    // Inline search form 'default' style:
    INPUT_DEFAULT_CLASS:     'sipemptyinput',
    // Inline search form selector:
    SELECTOR:                '#minisearchform'
  },
  
  SOCIAL_LINKS: {
    SELECTOR:                '#addthis',
    ID:                      '49f9967e5e0b3bc5',
    SCRIPT_URL:              'http://s7.addthis.com/js/200/addthis_widget.js'
  },

  KBD_FOCUS: {
    // First content element in a page, skipping headers, menus and other fix stuff:
    PAGE_SELECTOR:           '#message H1',
    // First form in a form page:
    FORM_SELECTOR:           'FORM[id!=minisearchform][action] INPUT:text, FORM[id!=minisearchform][action] SELECT, FORM[id!=minisearchform][action] TEXTAREA'
  },

  UNSUPPORTED_FIXES: {
    // Finding PNG animals:
    PNG_SELECTOR:            'IMG.ie6fix'
  },

  // Detecting test sever:
  TEST_URL_PATTERN:          /(http:\/\/tarrekaise|http:\/\/tjidtjak|http:\/\/localhost|http:\/\/web-devel|http:\/\/edit)/
}


// #PRAGMA COMPACT SKIP START

// Configuration of the debug menu:
SIF_DEBUG = {
  ENABLE:       true,
  DREAMWEAVER:  ['C:\\progra~1\\Adobe\\Adobe Dreamweaver CS3\\Dreamweaver.exe', 'C:\\progra~1\\Macromedia\\Dreamweaver 8\\Dreamweaver.exe'],
  CODEEDITOR:   ['C:\\VStudio\\Common\\MSDev98\\Bin\\MSDEV.EXE', 'C:\\Program Files\\Microsoft Visual Studio 8\\Common7\\IDE\\devenv.exe', 'C:\\WINDOWS\\NOTEPAD.EXE', 'C:\\WINNT\\NOTEPAD.EXE'],
  EXPLORE:      ['explorer.exe'],
  SITE_ROOT:    'W:\\Sites\\www.sectra.se\\Version_6\\root'
}

// #PRAGMA COMPACT SKIP END



/*===========================================================================
 *
 * Browser detection 
 *   Note: prefer capability test to browser detection!
 *
 */

DETECT = {};
DETECT.is_opera     = (navigator.userAgent.indexOf('Opera')!=-1)?true:false;
DETECT.is_chrome    = (navigator.userAgent.indexOf('Chrome')!=-1)?true:false;
DETECT.is_safari    = ((navigator.userAgent.indexOf('Safari')!=-1)?true:false) && !DETECT.is_chrome;
DETECT.is_webkit    = (navigator.userAgent.indexOf('KHTML')!=-1)?true:false;
DETECT.is_ie        = ((navigator.appName=='Microsoft Internet Explorer')?true:false) && !DETECT.is_opera;
DETECT.is_gecko     = (navigator.product=='Gecko') && !DETECT.is_safari;
DETECT.versionStr   = navigator.userAgent.replace(/.*(MSIE|Opera|Firefox|Netscape|Safari|Mozilla)[^\d\.]+([\d\.]*).*/i, '$2');
DETECT.majorVersion = parseFloat(DETECT.versionStr.replace(/(\d*).*/, '$1'));
DETECT.version      = parseFloat(DETECT.versionStr.replace(/(\d*)\.?(\d*)/, '$1.$2'));
DETECT.is_ie6       = DETECT.is_ie && DETECT.majorVersion >= 6;
DETECT.is_ieLt6     = DETECT.is_ie && !DETECT.is_ie6;
DETECT.is_ie7       = DETECT.is_ie && DETECT.majorVersion >= 7;
DETECT.is_ieLt7     = DETECT.is_ie && !DETECT.is_ie7;
DETECT.is_ie8       = DETECT.is_ie && DETECT.majorVersion >= 8;
DETECT.is_ieLt8     = DETECT.is_ie && !DETECT.is_ie8;

if(DETECT.is_gecko)
{
  if(navigator.userAgent.indexOf('Netscape')==-1)
  {
    DETECT.is_ns   = false;
    DETECT.is_moz  = true;
  }
  else
  {
    DETECT.is_ns   = ((navigator.appName=='Netscape')?true:false) && !DETECT.is_opera  && !DETECT.is_safari;
    DETECT.is_moz  = false;
  }
}



/*===========================================================================
 *
 * Class script
 *
 */

(function()
{
  var isFn = function(fn) {return(typeof fn == 'function');};
  Class = function(){};
  Class.create = function(template)
  {
    var k = function(magic){
      // call construct only if there's no magic cookie
      if (magic != isFn && isFn(this.construct))
        this.construct.apply(this, arguments);
    };
    k.prototype = new this(isFn); // use our private method as magic cookie
    for (key in template)
      (function(fn, sfn){ // create a closure
         k.prototype[key] = !isFn(fn) || !isFn(sfn) ?
           fn
         : // add _super method
          function()
          {
            this._super = sfn;
            return(fn.apply(this, arguments));
          };
       })(template[key], k.prototype[key]);
    k.prototype.constructor = k;
    k.extend = this.extend || this.create;
    k.prototype.toString = function(){return(this.ToString.apply(this, arguments));};
    return(k);
  };
})();



/*
$.fn.delay = function(milliseconds, callback)
{
    // Run a dummy animation:
    return(this.animate({dummy: 0}, milliseconds, callback));
}
*/


$.fn.unwrap = function () 
{
  return(this.each(function()
         {
           $(this.childNodes).insertBefore(this); 
         }).remove());
};


/*===========================================================================
 *
 * Static class SiTools
 *   Utility functions
 *
 */

SiTools = {

  /*
   * mkId()
   *   Converts an arbitrary string to something usabale as an ID
   *
   * In:
   *   value: string to convert
   *
   * Returns:
   *   Propposed ID
   */

  mkId: function(value)
  {
    return(value.replace(/[^a-z0-9_]+/ig, ''));
  },


  /*
   * mkArray()
   *   Wraps the given value in an array, if not already an array.
   *
   * In:
   *   value: value to make sure it is an array
   *
   * Returns:
   *   Array containing the given value, or the given array
   */

  mkArray: function(value)
  {
    if(value!=null && value.sort==null)
    {
      var arr = new Array();
      arr.push(value);
      return(arr);
    }
    else
      return(value);
  },


  /*
   * getLength()
   *   Gets the "length" of the given "thing", or 0 if the "thing" does not have a length. 
   *
   * In:
   *   thing: Object that might have a length
   *
   * Returns:
   *   Length of the "thing"
   */

  getLength: function(thing)
  {
    if(thing==null || thing.length==null)
      return(0);
    else
      return(thing.length);
  },


  /*
   * narrowIndex()
   *   Returns a value corresopinding the the given index, or if the index is out of bounds, the closet value. 
   *
   * In:
   *   indexable: array to index
   *   index:     index
   *
   * Returns:
   *   A value from the array
   *
   */

  narrowIndex: function(indexable, index)
  {
    if(indexable==null || indexable.length==0)
      return(null);
    else if(index>=indexable.length)
      return(indexable[indexable.length-1]);
    else if(index == null || index < 0)
      return(indexable[0]);
    else
      return(indexable[index]);
  },


  /*
   * contains(), starts(), ends()
   *   Tests if an string or array contains, starts or ends a given value.
   *
   * In:
   *   haystack: object to scan
   *   needle:   value to scan for
   *
   * Returns:
   *   True if needle was found in haystack, at beginning or end, respectively.
   *
   */

  contains: function(haystack, needle)
  {
    if(haystack == null || needle == null)
      return(false);
    else if(haystack.indexOf)
      return(haystack.indexOf(needle) != -1);
    else
    {
      for(var i=0; haystack!=null && i<haystack.length; i++)
      {
        if(haystack[i]==needle)
          return(true);
      }
      return(false);
    }
  },

  starts: function(haystack, needle)
  {
    if(haystack == null || needle == null)
      return(false);
    else if(haystack.indexOf)
      return(haystack.substr(0, needle.length) == needle);
    else
      return(haystack[0] == needle);
  },

  ends: function(haystack, needle)
  {
    if(haystack == null || needle == null)
      return(false);
    else if(haystack.indexOf)
      return(haystack.substr(haystack.length - needle.length) == needle);
    else
      return(haystack[haystack.length-1] == needle);
  }

}



/*===========================================================================
 *
 * class SiHtml
 *   Simple HTML code generation
 *
 */

SiHtml = Class.create({

  /*
   * construct()
   *   Constructs ad initializes an SiHtml object. If given a tagName; the object corressponds to an HTML tag, if not; the object corressponds to a HTML document snippet.
   *
   * In:
   *   tagName:    (optional) tag name
   *   attributes: (optional) attributes associative array
   *
   * Returns:
   *   n/a
   */

  construct: function(tagName, attributes)
  {
    this.tagName = tagName;
    this.attributes = new Array();
    this.childNodes = new Array();
    this.addAttributes(attributes);
  },


  /*
   * addAttributes()
   *   Add attribuets given in an associative array
   *
   * In:
   *   Attributes structure
   *
   * Returns:
   *   n/a
   */

  addAttributes: function(attributes)
  {
    for(var name in attributes)
      this.addAttribute(name == 'className' ? 'class' : name, attributes[name]);
  },


  /*
   * addAttribute()
   *   Add a single attribute/value
   *
   * In:
   *   name:         (optional) name of attribute
   *   value:        (optional) value of attribute
   *   defaultValue: (optional) valuie to use if value was empty
   *
   * Returns:
   *   n/a
   */

  addAttribute: function(name, value, defaultValue)
  {
    if(!value && defaultValue)
      value = defaultValue;
    if(value && value.replace)
      value = value.replace(/"/g, '&quot;');
    if(!name)
      name = value;
    if(value)
      this.attributes.push(name + '="' + value + '"');
  },


  /*
   * addChild()
   *   Add a child node
   *
   * In:
   *   value: child nod to add, string or SiHtml object
   *
   * Returns:
   *   n/a
   */

  addChild: function(value)
  {
    this.childNodes.push(value);
  },


  /*
   * ToString()
   *   Converts the object to a (HTML) string
   *
   * Returns:
   *   HTML code
   */

  ToString: function()
  {
    var html = '';
    if(this.tagName)
    {
      html += '<' + this.tagName;
      if(this.attributes.length > 0)   
        html += ' \t' + this.attributes.join('\n\t');
    }
    if(this.childNodes.length > 0)
    {
      if(this.tagName)
        html += '>\n'
      html += this.childNodes.join('');
      if(this.tagName)
        html += '</' + this.tagName + '>\n';
    }
    else if(this.tagName)
      html += ' />\n';
    return(html);
  }
  
});


/*===========================================================================
 *
 * class SiJS
 *   Load scripts dynamically
 *
 *   Original idea/source at 
 *   http://lyncd.com/2009/03/better-google-analytics-javascript/
 *
 */

SiJS = Class.create({
  construct: function()
  {
    this.script = null;
    this.onload = null;
  },

  load: function(url)
  {

    var script = document.createElement('script');
    script.src = url;
    script.type = 'text/javascript';

    if(this.onload)
    {
      script._si_loadkilroy = false;
      var self = this;

      // DOM Compatible:
      script.onload = function () 
      {
        if (!script._si_loadkilroy)
        {
          script._si_loadkilroy = true;
          if(self.onload)
            self.onload();
        }
      };

      // IE:
      script.onreadystatechange = function() 
      {
        if (('loaded' === script.readyState || 'complete' === script.readyState) && !script._si_loadkilroy)
        {
          script._si_loadkilroy = true;
          if(self.onload)
            self.onload();
        }
      };
    }
    document.getElementsByTagName('head')[0].appendChild(script);

  }
});


/*===========================================================================
 *
 * static class SiFile
 *   Helper functions for reading external files
 *   Only IE supported
 */

SiFile = {

  _httpRequest: null,

  _createHttpRequest: function()
  {
    if(SiFile._httpRequest==null)
    {
      try 
      {
        SiFile._httpRequest = new ActiveXObject("Microsoft.XMLHTTP");
      }
      catch(e){} // ActiveX disabled?
    }
    return(SiFile._httpRequest);
  },


  read: function(url)
  {
    var rq = SiFile._createHttpRequest();
    if(rq!=null)
    {
      try
      {
        SiFile._httpRequest.open("GET", url, false);
        SiFile._httpRequest.send();
        if(SiFile._httpRequest.status == 0 || SiFile._httpRequest.status == 200) 
          return(SiFile._httpRequest.responseText);
        else
          return(null);
      }
      catch(e){} // Ignore errors
    }
    return(null);
  }
}


/*===========================================================================
 *
 * static class SiURL
 *   Helper functions for handling URLs and paths
 *
 */

SiURL = {
  
  /*
   * strip()
   *   Strips hash bookmarks and default document names from a path
   *
   * In:
   *   path: URL or path to strip
   *
   * Returns:
   *   URL or path stripped
   */

  strip: function(path)
  {
    return(path!=null ? path.replace(/#.*$/, '').replace(/index\.\w+/i, '') : null);
  },


  /*
   * mkLocal()
   *   Returns the papth of an URL
   *
   * In:
   *   url: URL to convert
   *
   * Returns:
   *   Path (from the server document root)
   */

  mkLocal: function(url)
  {
    return(url!=null ? url.replace(/^[^:]*:\/\/[^\/]*/, '') : null);
  },


  /*
   * getCurrentPath()
   *   Returns the path of the current document.
   *
   * Returns:
   *   Path of the current document
   */

  getCurrentPath: function()
  {
    return(SiURL.mkLocal(SiURL.strip(window.location.href)));
  },


  /*
   * isTestServer()
   *   Tests if an URL is located on a testing server or not
   *
   * In:
   *   url: (optional) URL to test, default is the current document's URL
   *
   * Returns:
   *   true:  testing server
   *   false: public server
   */

  isTestServer: function(url)
  {  
    url = url!=null ? url : window.location.href;
    return(url.match(SIF_CONFIG.TEST_URL_PATTERN)!=null);
  },


  /*
   * isCurrent()
   *   Tests if an URL mathces the current document
   *
   * In:
   *   url: URL to test
   *
   * Returns:
   *   true:  The given URL corresponds to the current document
   *   false: The given URL does not correspond to the current document
   */

  isCurrent: function(url)
  {
    return(SiURL.mkLocal(url) == SiURL.getCurrentPath());
  },
  

  /*
   * isDynamic()
   *   Tests if an URL refers to a dynamic page (server script)
   *
   * In:
   *   url: URL to test
   *
   * Returns:
   *   true:  The given URL corresponds to a dynamic page (server script)
   *   false: The given URL corresponds to a static page
   */

  isDynamic: function(url)
  {
    return(url.indexOf('.php')!=-1 
        || url.indexOf('.asp')!=-1 
        || url.indexOf('/cgi-bin/')!=-1);
  },


  paramsToAssoc: function(params, defaultKey)
  {
    // Skip optional path:
    params = params.split('?');
    params = params.length > 1 ? params[1] : params[0];

    // Use incrementing numeric default key if nothing else specified:
    if(!defaultKey)
      defaultKey = 0;
    
    var fragments = params.split('&');
    assoc = new Object();
    for(var i = 0; i < fragments.length; i++)
    {
      var kvp = fragments[i].split('=');
      // If no key, use default key:
      if(kvp.length==1)
      {
        assoc[defaultKey] = kvp[0];
        // Increment if numeric default key:
        if(!isNaN(defaultKey))
          defaultKey++;
      }
      else
        assoc[kvp[0]] = kvp[1];
    }
    return(assoc);
  },
  

  assocToParams: function(assoc)
  {
    // Convert parameter assoc to URL-style parameters:
    var sequence = new Array();
    for(var name in assoc)
      sequence.push(name + '=' + assoc[name]);
    return(sequence.join('&'));
  }

}



/*===========================================================================
 *
 * static class SiDomHelp
 *   Helper functions for the browser DOM
 *
 */

SiDomHelp = {

  UUID: [],
  
  uniqueId: function(template)
  {
    template = template || '_sidomhelp';
    if(SiDomHelp.UUID[template] == null)
    {
      SiDomHelp.UUID[template] = 0;
      return(template);
    }
    else
      return(template + '_' + ++SiDomHelp.UUID[template]);
  },

  /*
   * mkElementId()
   *   Makes sure an element has an ID, and returns that ID
   *
   * In:
   *   element: element
   *
   * Returns:
   *   element ID
   * 
   * Postcondition:
   *   If not previously assigned an ID, the element now has one
   */

  mkElementId: function(element)
  {
    if(!element.id)
      element.id = SiDomHelp.uniqueId();
    return(element.id);
  },


  /*
   * isClass()
   *   Tests for a class
   *
   * In:
   *   element:   element to test
   *   className: class name to test for
   *
   * Returns:
   *   true if the element has the class
   */

  isClass: function(element, className)
  {
    return(element!=null && element.className!=null && element.className.indexOf(className)!=-1);
  },


  /*
   * getOuterHTML()
   *   Returns the outer HTML of an element, or at least a fair guess of what it might be if the DOM does not support the .outerHTML property
   *
   * In:
   *   element: DOM element
   *
   * Returns:
   *   outerHTML, or a guess, of the given element.
   */

  getOuterHTML: function(element)
  {
    if(element.outerHTML != null)
      return(element.outerHTML);
    else
    {
      var html = '<' + element.tagName;
      for(var i = 0; i < element.attributes.length; i++)
        html += ' ' + element.attributes[i].nodeName + '="' + element.attributes[i].nodeValue + '"';
      html += '>' + element.innerHTML + '</' + element.tagName + '>';
      return(html);
    }
  },
  
  
  /*
   * topWindow()
   *   Returns the topmost window (frame) that is loaded from the current site; 
   *   there may still be encapsulating windows from other sites.
   *
   * In:
   *   win: (optional) current window
   *
   * Returns:
   *   The topmost window (frame) that is loaded from the same site as 'win'.
   */

  topWindow: function(win)
  {
    var top = win != null ? win : window;
    try
    {
      /* Traverse up in the window hierarchy; accessing
       * top.parent.location.href throws an exception if top.parent is loaded 
       * from another site,leaving top referring to the topmost page of the
       * current site.
       */
      while(top.parent != top && top.parent.location.href != null)
        top = top.parent;
    }
    catch(e){}
    return(top);
  },


  /*
   * isTtopWindow()
   *   Tests if the current window (frame) is the topmost window that is 
   *   loaded from the current site.
   *
   * In:
   *   win: (optional) current window
   *
   * Returns:
   *   True if 'win' is the topmost window (frame) that is loaded from the 
   *   same site. 
   */

  isTopWindow: function(win)
  {
    var win = win != null ? win : window;
    return(SiDomHelp.topWindow(win) == win);
  },


  /*
   * _getAbsolutePosition()
   *   (Internal)
   *   Calculates the absolute position if an element
   */

  _getAbsolutePosition: function(element)
  {
    var offset={x: 0, y: 0};

    while(element!=null && element.tagName!=null && element.style!=null)
    {
      if((element.style!=null) && (element.style.position=='absolute'))
      {
        // In NS6, this might be '0pt' although set to '0px'!
        offset.x+=1*element.style.left.replace(/\w+$/, '');
        offset.y+=1*element.style.top.replace(/\w+$/, '');
        
        if(element.parentNode!=null)
          element=element.parentNode;
        else
          element=element.parentElement;
      }
      else
      {
        offset.x+=element.offsetLeft;
        offset.y+=element.offsetTop;

        if(element.offsetParent!=null)
          element=element.offsetParent;
        else if(element.parentNode!=null)
          element=element.parentNode;
        else
          element=element.parentElement;
      }
    }
    return(offset);
  },


  /*
   * getAbsoluteLeft()
   *   Returns the absolute position of a relatively or absolutely positioned object.
   *
   * In:
   *   element: DOM object
   *
   * Returns:
   *   the left of the object (px), or 0 (zero) if not available
   */

  getAbsoluteLeft: function(element)
  {
    return(SiDomHelp._getAbsolutePosition(element).x);
  },


  /*
   * getAbsoluteTop()
   *   Returns the absolute position of a relatively or absolutely positioned object.
   *
   * In:
   *   element: DOM object
   *
   * Returns:
   *   the top of the object (px), or 0 (zero) if not available
   */

  getAbsoluteTop: function(element)
  {
    return(SiDomHelp._getAbsolutePosition(element).y);
  },


  /*
   * getAbsolutePoint()
   *   Returns the absolute position of a relatively or absolutely positioned object.
   *
   * In:
   *   element: DOM object
   *
   * Returns:
   *   the position of the object (px/px), or 0/0 (zero) if not available
   *     - x: 
   *     - y: 
   */

  getAbsolutePoint: function(element)
  {
    return(SiDomHelp._getAbsolutePosition(element));
  },


  /*
   * _getAbsoluteDimension()
   *   (Internal)
   *   Calculates the absolute dimension if an element
   */

  _getAbsoluteDimension: function(element)
  {
    var offset={width: 0, height: 0};

    if(element!=null && element.style!=null)
    {
      if(false && element.tagName=='DIV') 
        offset={width:  1*element.style.width.replace(/\w+$/, ''), 
                height: 1*element.style.height.replace(/\w+$/, '')};
      else if(element.tagName!='TBODY')
        offset={width:  element.offsetWidth, 
                height: element.offsetHeight};
    }

    return(offset);
  },


  /*
   * getAbsoluteWidth()
   *   Returns the absolute dimension of a relatively or absolutely positioned object.
   *
   * In:
   *   element: DOM object
   *
   * Returns:
   *   the width of the object (px), or 0 (zero) if not available
   */

  getAbsoluteWidth: function(element)
  {
    return(SiDomHelp._getAbsoluteDimension(element).width);
  },


  /*
   * getAbsoluteHeight()
   *   Returns the absolute dimension of a relatively or absolutely positioned object.
   *
   * In:
   *   element: DOM object
   *
   * Returns:
   *   the height of the object (px), or 0 (zero) if not available
   */

  getAbsoluteHeight: function(element)
  {
    return(SiDomHelp._getAbsoluteDimension(element).height);
  },


  /*
   * getAbsoluteSize()
   *   Returns the absolute dimension of a relatively or absolutely positioned object.
   *
   * In:
   *   element: DOM object
   *
   * Returns:
   *   the size of the object (px/px), or 0/0 (zero) if not available
   *     - width: 
   *     - height: 
   */

  getAbsoluteSize: function(element)
  {
    return(SiDomHelp._getAbsoluteDimension(element));
  },


  /*
   * getStyle()
   *   Returns the named style value of an element.
   *
   * In:
   *   element: DOM object
   *   name:    name of the style attribute (camelCase name style)
   *
   * Returns:
   *   the style value, or null if no mathing style was found. For dimension
   *   values, the unit is stripped off and the (numeric) value is returned 
   *   as an integer.
   */

  getStyle: function(element, name)
  {
    var value;
    if(element.currentStyle!=null)
      value = element.currentStyle[name];
    else
    {
      value = document.defaultView.getComputedStyle(element,'');
      if(value)
        value = value.getPropertyValue(name.replace(/([A-Z])/g, '-$1').toLowerCase());
    }
    if(value!=null)
    {
      var unit;
      if(value.replace!=null)
      {
        unit = value.replace(/.*([a-z]{2})$/, '$1');
        value = value.replace(/(\d+)[a-z]+$/, '$1');
      }
      if(!isNaN(value))
      {
        value = parseInt(value);
        if(unit=='pt')
          value *= 1.3;
        return(value);
      }
      else
        return(value);
    }
    else
      return(null);
  },
  
  
  /*
   * rgbColorToHex()
   *   Coverts the given rgb color value to a "#hex-triplet". 
   *
   *   The color style attribute is standardwise defined as 
   *   "rgb(r, g, b)", but IE6 and Opera does not adhere but returns a hex-
   *   triplet. Either way, getDocumentScroller() tries to return a hex-
   *   triplet. 
   *
   * Returns:
   *   Color as hex-triplet.
   *
   * Example:
   *   var c = SiDomHelp.rgbColorToHex(SiDomHelp.getStyle(element, 'color'));
   */

  rgbColorToHex: function(color)
  {
    if(!color)
      color = 0;
    if(typeof(color)=='string')
    {
      if(color.indexOf('rgb')!=-1)
      {
        // Keep digits only and split:
        var fragments = color.replace(/[^\d ]/g, '').split(' ');
        color = '#' + parseInt(fragments[0]).toString(16) + parseInt(fragments[1]).toString(16) + parseInt(fragments[2]).toString(16);
      }
      else if(color.charAt(0)!='#')
        color = parseInt(color);
    }
    else if(!isNaN(color))
      color = '#' + color.toString(16);
    return(color);
  },


  /*
   * getDocumentScroller()
   *   Returns the document 'scrolling' element
   *
   * Returns:
   *   DOM element performing scrolling of the document
   */

  getDocumentScroller: function()
  {
    return(window.document.body.parentNode); // i.e. window.document.html
  },


  /*
   * scrollIntoView()
   *   Makes a node visible within the document viewport, scrolling the document if needed
   *
   * In:
   *   node:      Node to scroll
   *   scroller: (optional) Scrolling element, default getDocumentScroller()
   *
   * Returns:
   *   n/a
   */

  scrollIntoView: function(node, scroller)
  {
    scroller   = scroller != null ? scroller : SiDomHelp.getDocumentScroller();
    var top    = SiDomHelp.getAbsoluteTop(node);
    var height = SiDomHelp.getAbsoluteHeight(node);
    if(top < scroller.scrollTop || height >= scroller.clientHeight)
      scroller.scrollTop = top;
    else if(top + height > scroller.clientHeight + scroller.scrollTop)
      scroller.scrollTop = top + height - scroller.clientHeight;
  },

  getAncestorEditedByMe: function(node)
  {
    for(; node!=null; node = node.parentNode)
    {
      if(node._si_edit_kilroy!=null)
        return(node);
    }
    return(null);
  },
  
  
  isEditedByMe: function(node)
  {
    return(SiDomHelp.getAncestorEditedByMe(node)!=null);
  }
}



/*===========================================================================
 *
 * static class Cookies
 *   Helper functions for handling cookies
 *
 */

SiCookies = {
  
  /*
   * set()
   *   Set a cookie
   *
   * In:
   *   name:       name of cookie
   *   value:      value of cookie, or null to remove the cookie
   *   persistent: true to create a persistent cookie
   *
   * Returns:
   *   n/a
   *
   * Postcondition:
   *   Cookie created or updated
   */
  
  set: function(name, value, persistent)
  {
    name=SiTools.mkId(name);
    if(value!==null)
    {
      var expires;
      if(persistent)
        expires='expires=' + (new Date((new Date()).getTime() + 86400 * 30 * 1000)).toGMTString() + '; ';
      else
        expires='';
      document.cookie = name + '=' + escape(value) + '; ' + expires + 'path=/';
    }
    else
      SiCookies.remove(name);
  },


  /*
   * remove()
   *   Remove a cookie
   *
   * In:
   *   name:             name of cookie to remove
   *   _allowDirtyName: (internal)
   *
   * Returns:
   *   n/a
   *
   * Postcondition:
   *   Cookie removed, if previously present
   */

  remove: function(name, _allowDirtyName)
  {
    if(!_allowDirtyName)
      name=SiTools.mkId(name);
    document.cookie = name + '=; expires=Mon, 1 Jan 1990 00:00:00 UTC; path=/';
  },


  /*
   * removeAll()
   *   Remove all cookies (accessible in the domain)
   *
   * Returns:
   *   n/a
   *
   * Postcondition:
   *   All cookies accessible in the domain removed
   */

  removeAll: function()
  {
    var cookies = document.cookie.split('; ');
    var count = 0;
    for (var i=0; i < cookies.length; i++)
    {
      if(cookies[i])
      {
        var crumb = cookies[i].split('=');
        SiCookies.remove(crumb[0], true);
        count++;
      }
    }
    return(count);
  },


  /*
   * get()
   *   Get a cookie
   *
   * In:
   *   name: name of cookie
   *
   * Returns:
   *   Value of the cookie if present, null otherwise
   */

  get: function(name)
  {
    name=SiTools.mkId(name);
    var cookie = document.cookie.split('; ');
    for (var i=0; i < cookie.length; i++)
    {
      var crumb = cookie[i].split('=');
      if (name == crumb[0])
      {
        var rc = unescape(crumb[1]);
        if(rc=='null')
          rc=null;
        return(rc);
      }
    }
    return(null);
  }
}



/*===========================================================================
 *
 * static class Target groups
 *   Helper functions for handling target groups
 *
 */

SiTargetGroup = {

  /*
   * get()
   *   Get the current target group name
   *
   * Returns:
   *   Name of the current target group, or null if not set
   */

  get: function()
  {
    return(SiCookies.get(SIF_CONFIG.TARGET_GROUPS.COOKIE_NAME));
  },


  /*
   * set()
   *   Set the name of the current target group
   *
   * In:
   *   value: new target group name, or null to reset 
   *
   * Returns:
   *   n/a
   *
   * Postcondition:
   *   Target group cookie created, updated or removed.
   */

  set: function(value)
  {
    return(SiCookies.set(SIF_CONFIG.TARGET_GROUPS.COOKIE_NAME, value));
  }
  
}  



/*===========================================================================
 *
 * static class Flash content
 *   Flash Player instantiation
 *
 */

SiFlash = {

  /*
   * getVersion()
   *   Returns the version of the installed Flash player plugin, or tests the version against a given value. 
   *
   * In:
   *   requireMajor: (optional) Version to test against.
   *
   * Returns:
   *   w/o requireMajor: the Flash Player version
   *   w requireMajor:   null if the Flash Player version was lower than required
   */

  getVersion: function(requireMajor)
  {
    var swfVersion = null;

    if(requireMajor!=null)
      requireMajor = '.' + requireMajor;
    else
      requireMajor = '';

    if(navigator.plugins && navigator.mimeTypes.length)
    {
      var plug = navigator.plugins['Shockwave Flash'];
      if(plug && plug.description)
        swfVersion = plug.description.replace(/([a-zA-Z]|\s)+/, '').replace(/(\s+r|\s+b[0-9]+)/, '.').split('.');
      if(swfVersion && requireMajor!='' && requireMajor>swfVersion[0])
        swfVersion = null;
    }
    else
    { 
      var axo = null;
      try
      {
        axo = new ActiveXObject('ShockwaveFlash.ShockwaveFlash' + requireMajor);
      }
      catch(e){}
      if(axo != null)
      {
        try
        {
          swfVersion = [6,0,21];
          axo.AllowScriptAccess = 'always';
          swfVersion = null;
          swfVersion = axo.GetVariable('$version').split(' ')[1].split(',');
        }
        catch(e){}
      }
    }
    return(swfVersion);
  },


  /*
   * createHtml()
   *   Creates Flash Player instantiation HTML code
   *
   * In:
   *   (see corresponding arguments in SiFlash.create)
   *
   * Returns:
   *   HTML code
   */

  createHtml: function(URL, 
                       variables, 
                       styles, 
                       baseURL, 
                       wmode,
                       bgcolor,
                       width, 
                       height, 
                       ctrlId,
                       ctrlClass)
  {
    // Set default values:
    if(!bgcolor)
      bgcolor = '#ffffff';
    if(wmode===false)
      wmode = null;
    else if(!wmode)
      wmode = 'opaque';
    if(!ctrlId)
      ctrlId = SiDomHelp.uniqueId(SIF_CONFIG.FLASH.PLAYER.ID);
    if(!ctrlClass)
      ctrlClass = SIF_CONFIG.FLASH.PLAYER.CLASSNAME;

    // Convert args to arrays:
    URL       = SiTools.mkArray(URL);
    variables = SiTools.mkArray(variables);
    styles    = SiTools.mkArray(styles);
    baseURL   = SiTools.mkArray(baseURL);
    wmode     = SiTools.mkArray(wmode);
    bgcolor   = SiTools.mkArray(bgcolor);
    width     = SiTools.mkArray(width);
    height    = SiTools.mkArray(height);
    ctrlId    = SiTools.mkArray(ctrlId);
    ctrlClass = SiTools.mkArray(ctrlClass);

    // Generated HTML control code:
    var html  = new SiHtml();

    // Nrof controls to create:
    var count = Math.max(Math.max(SiTools.getLength(ctrlId), SiTools.getLength(URL)), 
                         Math.max(SiTools.getLength(variables), SiTools.getLength(styles)));

    // For each control to create:
    for(var i=0; i<count; i++)
    {
      var nURL       = SiTools.narrowIndex(URL,       i);
      var nStyles    = SiTools.narrowIndex(styles,    i);
      var nBaseURL   = SiTools.narrowIndex(baseURL,   i);
      var nWmode     = SiTools.narrowIndex(wmode,     i);
      var nBgcolor   = SiTools.narrowIndex(bgcolor,   i);
      var nWidth     = SiTools.narrowIndex(width,     i);
      var nHeight    = SiTools.narrowIndex(height,    i);
      var nCtrlId    = SiTools.narrowIndex(ctrlId,    i);
      var nCtrlClass = SiTools.narrowIndex(ctrlClass, i);

      // Get the flashvars string, it can be passed as an URL encoded string or 
      // as an associative array:
      var varSet    = SiTools.narrowIndex(variables, i);
      var varString = '';
      if(varSet!=null && varSet.indexOf==null)
      {
        for(var name in varSet)
        {
          if(varString!='')
            varString += '&';
          varString += name + '=' +   varSet[name];
        }
      }
      else
        varString = varSet;

      // Get hold of the base URL:    
      if(nBaseURL=='@')
        nBaseURL = nURL.replace(/[^\/]*$/, '');
      else if(!nBaseURL)
        nBaseURL = window.location.href.replace(/[^\/]*$/, '');

      if(nStyles==null)
        nStyles = '';
      if(nStyles.indexOf('width:')==-1)
        nStyles += ';width:100%';
      var objHtml = new SiHtml('object', {className: nCtrlClass,
                                          type:      'application/x-shockwave-flash',
                                          style:     nStyles,
                                          width:     nWidth,
                                          height:    nHeight,
                                          id:        nCtrlId});
      if(!DETECT.is_ie)
        objHtml.addAttribute('data', nURL);
        
      if(wmode)
        objHtml.addChild(new SiHtml('param', {name: 'wmode', value: nWmode}));
        
      objHtml.addChild(new SiHtml('param', {name: 'movie',     value: nURL}));
      objHtml.addChild(new SiHtml('param', {name: 'bgcolor',   value: nBgcolor}));
      objHtml.addChild(new SiHtml('param', {name: 'FlashVars', value: varString || ''}));
      objHtml.addChild(new SiHtml('param', {name: 'base',      value: nBaseURL  || ''}));
      objHtml.addChild(new SiHtml('param', {name: 'quality',   value: 'high'}));
      objHtml.addChild(new SiHtml('param', {name: 'autoStart', value: '-1'}));

      var embed = new SiHtml('embed', {className:   nCtrlClass,
                                       quality:     'high',
                                       type:        'application/x-shockwave-flash',
                                       bgcolor:     nBgcolor,
                                       src:         nURL,
                                       style:       nStyles,
                                       width:       nWidth,
                                       height:      nHeight,
                                       id:          nCtrlId,
                                       name:        nCtrlId,
                                       flashvars:   varString || '',
                                       base:        baseURL || '',
                                       pluginspage: 'http://www.macromedia.com/go/getflashplayer'});
      if(wmode)
        embed.addAttribute('wmode', nWmode);
      objHtml.addChild(embed);
      html.addChild(objHtml);
    }
    return(html.toString());
  },


  /*
   * SiFlash.testBlocker()
   *   Tests the given container, previously populated with flash content via 
   *   SiFlash.create(), for the Flash Blocker pattern, that is, absence of 
   *   OBJECT tags. If the Flash Blocker has taken control of the container, 
   *   reinstall the default (static) content saved by SiFlash.create(). 
   *
   * In:
   *   containerID: ID of the container presumaby containing Flash instantiation code
   *
   * Returns:
   *   n/a
   * 
   * Postcondition:
   *   If the Flash Blocker was found; the original HTML is restored
   */

  testBlocker: function(containerID)
  {
    var container = window.document.getElementById(containerID);
    if(container==null)
      return;
    if(container.getElementsByTagName('OBJECT').length!=0)
      return;

    if(container.si_blockHtml)
    {
      var blocker = container.getElementsByTagName('DIV')[0];
      if(blocker!=null)
      {
        blocker.style.marginTop = blocker.style.marginRight = blocker.style.marginBottom = blocker.style.marginLeft = '0px';
        blocker.style.width = '100%';
        blocker.innerHTML = container.si_blockHtml;
      }
      // else´: we're not sure what kind of creature eaten our flash; just leave it as is!
    }
    else if(container._si_alternateHtml)
      container.innerHTML = container._si_alternateHtml;
    else if(container._si_origHtml)
      container.innerHTML = container._si_origHtml;
  },


  /*
   * injectHtml()
   *   Inserts Flash Player instantiation HTML code in the given container, and issues a test for presence of the Flash Blocker. 
   *
   * In:
   *   html:        Flash Player instantiation HTML code
   *   containerID: Element to populate
   *
   * Returns:
   *   true:  container element was populated
   *   false: container element was not found 
   * 
   * Postcondition:
   *   The container element is populated with the given Flash Player instantiation HTML code. If the Flash Blocker is found after yielding, the original HTML code is restored.
   */

  injectHtml: function(html, containerID)
  {
    if(SiFlash.getVersion(SIF_CONFIG.FLASH.REQUIRE_VERSION)==null)
      return(null);

    // Replace taget's inner objects (possibly ststic image(s)):
    var container;
    if(containerID==null)
      containerID = 'banner';
    if(containerID.tagName!=null)
      container = containerID;
    else
      container = window.document.getElementById(containerID);

    if(container!=null)
    {
      containerID = SiDomHelp.mkElementId(container);

      // Yield and test for Flash block:
      setTimeout('SiFlash.testBlocker("' + containerID + '")', 100);

      // Save original (static) HTML:
      container._si_origHtml = container.innerHTML;

      // Save original (static) HTML to display for flashblock:
      container.si_blockHtml = '';
      for(var i=0; i<container.childNodes.length; i++)
      {
        var node = container.childNodes[i];
        if(SiDomHelp.isClass(node, SIF_CONFIG.FLASH.BLOCKER.CLASSNAME))
          container.si_blockHtml += SiDomHelp.getOuterHTML(node).replace(/flashblock/g, SIF_CONFIG.FLASH.BLOCKER.REPLACEMENT_CLASSNAME);
      }
      container.innerHTML='';
      var wrapper = document.createElement('span');
      wrapper.style.width='100%';
      wrapper.style.height='100%';
      wrapper.innerHTML = html;
      container.appendChild(wrapper);
      container._si_edit_kilroy = 'SiFlash';
      return(true);
    }
    else
      return(false);
  },


  /*
   * SiFlash.create()
   *   Writes code to the document to load an SWF movie, circumvents the "grey 
   *   box of death" or "Click to activate and use this control"-misfeature (aka
   *   "EOLAS problem") in IE. 
   *
   *   This approach avoids the layout flicker of the otherwise neather approach 
   *   with scripted "cut/paste-replace" <object> tags included in the HTML page.
   *
   *   Called by inline-script during document load (containerID==null) or after 
   *   load from an event handler (containerID!=null).
   *
   *   See http://msdn.microsoft.com/library/default.asp?url=/workshop/author/dhtml/overview/activating_activex.asp.
   *
   * In:
   *   URL:         URL of the Flash movie to load
   *   variables:   Variables to pass to the Flash movie. Variables can be 
   *                passed as an URL encoded string or as an associative array.
   *   styles:      (optional) Styles to assign to the new Flash control. If 
   *                no 'width:' parameter is found, a width:100% will be 
   *                appended to the style.
   *   containerID: (optional) ID of element to receive the Flash control HTML
   *   baseURL:     (optional) Base URL for relative paths in the Flash movie
   "                   URL: Given URL is the base
   *                   "@": Path of the Flash movie is the base
   *                   Default: Path of the current document is the base
   *   wmode:       (optional) Window mode, default is 'opaque'. Other options 
   *                   are 'window' and 'transparent'. 
   *   bgcolor:     (optional) Background color, default is #ffffff
   *   width:       (optional) Width of the new Flash control
   *   height:      (optional) Height of the new Flash control
   *   ctrlId:      ID of the new Flash control
   *   ctrlClass:   Class name of the new Flash control, default is 'swf'
   *
   * One or more of the attributes id, URL, vars and styles may be arrays to 
   * create multiple controls.
   *   
   *
   * Pre condition: 
   *   Document is loading (use containerID==null)
   *   Document is loaded (use containerID!=null)
   *
   * Post condition: 
   *   The container element is populated with Flash Player instantiation HTML code. If the Flash Blocker is found after yielding, the original HTML code is restored.
   */

  create: function(URL, 
                   variables, 
                   styles, 
                   containerID, 
                   baseURL, 
                   wmode,
                   bgcolor,
                   width, 
                   height, 
                   ctrlId,
                   ctrlClass)
  {
    if(!SiFlash._init_kilroy)
    {
      $('div.noflash *, div.flashblock *').show();
      SiFlash._init_kilroy = true;
    }
    return(SiFlash.injectHtml(SiFlash.createHtml(URL, 
                                                 variables, 
                                                 styles, 
                                                 baseURL, 
                                                 wmode,
                                                 bgcolor,
                                                 width, 
                                                 height, 
                                                 ctrlId,
                                                 ctrlClass), 
                              containerID));
  },

  /*
   * killAll()
   *   Kills all flash player controls on the page, if any.
   */

  killAll: function()
  {
    var count=0;
    $('OBJECT[type*=shockwave-flash]').each(function() // APA: 'EMBED'??
    {
      var container = this.parentNode.parentNode;
      if(container._si_origHtml)
        container.innerHTML = container._si_origHtml;
      else
        container.innerHTML = '';
      count++;
    });
    return(count);
  },


  callback: function(value)
  {
    SiTrack.footprint(value);
  }
}


/*===========================================================================
 *
 * static class SiFlvPlayer
 *   FLV Player instantiation. 
 */

SiFlvPlayer = {

  compatibility: 0,
  
  /*
   * create():
   *   Create a FLV player instance
   * 
   * In:
   *   - params:      parameters to the FLV player; this may be either a 
   *                  string with a path to a .xml configuration file or a 
   *                  .flv or .swf file to play, or an associative array with 
   *                  the following fields:
   *                    config: .xml configuration file 
   *                    video:  .flv or .swf file to play
   *                    start:  act in .xml file to start with or frame# in 
   *                            video.
   *                    duration: hint about length of video file (used if no 
   *                            meta data is supplied in the file itself)
   *
   *                  If 'video' and 'config' is both specified, the basic 
   *                  configuraton is loaded from 'config', 'video' is played 
   *                  while any playlist in 'config' is ingored.
   *
   *                  If a string is supplied, it may be a URL-encoded string 
   *                  with names corresponding to the fields in the 
   *                  associative array above. 
   *
   *   - styles:      Optional CSS styles, default is 'height:500px'.
   *
   *   - containerID: Optional target ID for the FLV player, default is 
   *                  'banner'.
   *
   */
   
  create: function(params, styles, containerID)
  {
    if(typeof(params)=='string')
      params = SiURL.paramsToAssoc(params, 'config');
    if(SiTools.ends(params.config, '.xml'))
    {
      // Build config path from target group + default group name:
      var cfgByGroup = new Array(SiTargetGroup.get(), SIF_CONFIG.TARGET_GROUPS.DEFAULT_NAME);
      for(var i = 0; i < cfgByGroup.length; i++)
        cfgByGroup[i] = params.config.replace(/%targetgroup%/, cfgByGroup[i]);
      params.config = cfgByGroup.join(';');
    }
    else
    {
      params.video = params.config;
      delete params.config;
    }
    if(params.start)
      params.start = params.start.replace(/[#&\?]+/, '');
    else
      delete params.start;
    params = SiURL.assocToParams(params);

    
    if(!styles)
      styles = 'height:500px';

    if(!containerID)
      containerID = 'banner';

    var player = SIF_CONFIG.MEDIA.FLV.PLAYER.URL;
    if(SiFlvPlayer.compatibility == 1)
      player = SIF_CONFIG.MEDIA.FLV.PLAYER.URL_2;
    SiFlash.create(player,                                         // URL
                   params,                                         // variables
                   styles,                                         // styles
                   containerID,                                    // containerID
                   '@',                                            // baseURL
                   null,                                           // wmode
                   null,                                           // bgcolor
                   null,                                           // width
                   null,                                           // height
                   SiDomHelp.uniqueId(SIF_CONFIG.MEDIA.PLAYER.ID), // ctrlId
                   SIF_CONFIG.MEDIA.FLV.PLAYER.CLASSNAME);         // ctrlClass
  }

}


/*===========================================================================
 *
 * static class SiMedia
 *   Media Player instantiation. 
 *   All media links contained in "media containers" are made to open an inline
 *   Media Player when clicked upon. Only one (1) Media Player can be 
 *   simultaneously open in a document; any already opened playes are closed 
 *   when clicking other media links. 
 *
 *   If Flash Player is installed and not blocked, videos will be played using 
 *   the "FLV player". Otherwise videos will be played usng the system media 
 *   player.
 */

SiMedia = {

  /*
   * install()
   *   Creates inline media players for all media links in the DOM.
   *
   * Returns:
   *   n/a
   */

  install: function()
  {
    $(SIF_CONFIG.MEDIA.SELECTOR).each(function(){SiMedia._containerInit(this);});
  },


  /*
   * reset()
   *   Resets (the currenty active media) control.
   *
   * Returns:
   *   n/a
   *
   * Post condition: 
   *   If a media player was open, it is closed.
   */

  reset: function()
  {
    $('.' + SIF_CONFIG.MEDIA.PLAYER.CLASSNAME).each(function()
    {
      var ctrl = this;
      
      // Try to stop the movie first:
      try
      {
        ctrl.Pause();
        ctrl.URL = null;
      }
      catch(e){}
      
      // Find the container:
      while(ctrl!=null && ctrl._si_origHtml==null)
        ctrl = ctrl.parentNode;

      // Kill the player + yield before reinstalling original HTML code to avoid lockups in IE:
      ctrl.innerHTML = '<br/><br /><br /><br />';
      setTimeout(function()
      {
        ctrl.innerHTML = ctrl._si_origHtml;
        SiMedia._containerInit(ctrl);
      }, 10);
    });
  },


  /*
   * _launch()
   *   Launches a media player called from a media link.
   */

  _launch: function(type, URL, container)
  {
    SiMedia.reset();
    
    var prefixHtml = new SiHtml('a', {style: 'display: inline; float: right; margin: -40px 0px 30px 0px; background: none',
                                      href: '#',
                                      onclick: 'SiMedia.reset(this);return false;'});
    prefixHtml.addChild(new SiHtml('img', {style: 'margin: 0px', src: SIF_CONFIG.MEDIA.PLAYER.UI.CLOSEBTN}));
    prefixHtml = prefixHtml.ToString();

    //APA:+ '<a style="display: inline; float: right; margin: -40px 0px 40px 0px" href="#" onclick="sip_configMediaControl(this);return false;"><img src="' + SIF_CONFIG.MEDIA.PLAYER.UI.SETTINGSBTN + '" /></a>'
    var ctrlId  = SiDomHelp.uniqueId(SIF_CONFIG.MEDIA.PLAYER.ID)
    var wmvHtml = SiMedia._createHtml(URL, ctrlId);
    var flvHtml = SiFlash.createHtml(SIF_CONFIG.MEDIA.FLV.PLAYER.URL,             // URL
                                        'video=' + URL.replace(/\.wmv$/, '.flv'), // variables
                                        null,                                     // styles
                                        null,                                     // baseURL
                                        null,                                     // wmode
                                        SIF_CONFIG.MEDIA.PLAYER.UI.BGCOLOR,       // bgcolor
                                        null,                                     // width
                                        null,                                     // height
                                        ctrlId,                                   // ctrlId
                                        SIF_CONFIG.MEDIA.FLV.PLAYER.CLASSNAME);   // ctrlClass
    container._si_alternateHtml = prefixHtml + wmvHtml;
    if(container._si_origHtml==null)
      container._si_origHtml = container.innerHTML;
    if(flvHtml)
      SiFlash.injectHtml(prefixHtml + flvHtml, container);
    else
      container.innerHTML = prefixHtml + wmvHtml;
    container._si_edit_kilroy = 'SiMedia';
    SiDomHelp.scrollIntoView(container.parentNode);
  },


  /*
   * createHtml()
   *  Creates Media Player instantiation HTML code
   */

  _createHtml: function(URL, ctrlId)
  {
    var style = 'background: #000000 url(/common/images/_fw_mediawaitscreen.jpg) no-repeat center center';

    var obj = new SiHtml('object', {className: SIF_CONFIG.MEDIA.WMV.PLAYER.CLASSNAME, 
                                    id:        ctrlId, 
                                    classid:   'CLSID:22D6F312-B0F6-11D0-94AB-0080C74C7E95', 
                                    standby:   SIF_CONFIG.MEDIA.WMV.PLAYER.STDBY_MSG, 
                                    type:      'application/x-oleobject',
                                    style:     style});
    obj.addChild(new SiHtml('param', {name: 'FileName',          value: URL}));
    obj.addChild(new SiHtml('param', {name: 'autostart',         value: 'true'}));
    obj.addChild(new SiHtml('param', {name: 'EnableContextMenu', value: 'true'}));
    obj.addChild(new SiHtml('param', {name: 'ShowControls',      value: 'true'}));
    obj.addChild(new SiHtml('param', {name: 'ShowStatusBar',     value: 'false'}));
    obj.addChild(new SiHtml('param', {name: 'ShowDisplay',       value: 'false'}));
    obj.addChild(new SiHtml('embed', {className:         SIF_CONFIG.MEDIA.WMV.PLAYER.CLASSNAME,
                                      type:              'application/x-mplayer2',
                                      src:               URL,
                                      name:              ctrlId,
                                      id:                ctrlId,
                                      EnableContextMenu: '1',
                                      ShowControls:      '1',
                                      ShowStatusBar:     '0',
                                      ShowDisplay:       '0',
                                      autostart:         '1',
                                      style:             style}));

    return(obj.ToString());
  },


  /*
   * _containerInit()
   *   Initializes a media container
   */

  _containerInit: function(container)
  {
    $(container).find('A').each(function()
    {
      var fileType = this.href.replace(/.*\.(\w+)$/, '$1');
      switch(fileType)
      {
        case 'wmv':
        case 'flv':
          //Note: use native click handler as jq won't let us set this twice (?):
          this.onclick = function()
          {
            SiMedia._launch(fileType, this.href, this.parentNode);
            return(false);
          };
          break;
      }
    });
  }
}



/*===========================================================================
 *
 * static class SiMailLinks
 *   Decoding e-mail links
 *
 */

SiMailLinks = {

  /*
   * install()
   *   Decodes the link and display name of all e-mail links in the DOM
   *
   * Returns:
   *   n/a
   * 
   * Postcondition:
   *   Encoded e-mail links are decoded.
   */

  install: function()
  {
    $('A[href*=/ilmail/?], A[href*=/mailto/?]').each(function()
    {
      this.href      = SiMailLinks._decode(this.href);
      this.innerHTML = SiMailLinks._decode(this.innerHTML);
      this._si_edit_kilroy = 'SiMailLinks';
    });
  },


  /*
   * _decode()
   *   Decodes the link and display of a e-mail link.
   */

  _decode: function(value)
  {
    return(value.replace(/.*\/mailto[^\?]*\?/, 'mailto:')
                .replace(/.*\/ilmail[^\?]*\?/, 'mailto:')
                .replace(/\{at?\}/g, '@')
                .replace(/\(at?\)/g, '@')
                .replace(/\/at?\//g, '@')
                .replace(/\{d(ot)?\}/g, '.')
                .replace(/\(d(ot)?\)/g, '.')
                .replace(/\/d(ot)?\//g, '.')
                .replace(/@removethis/, ''));
  }
}



/*===========================================================================
 *
 * static class SiActiveLinks
 *   Creation of active links, i.e. hover effects, 'this document' links indicated as 'current'
 *
 */

SiActiveLinks = {

  /*
   * install()
   *   Makes links in the DOM 'active', installs hovecr scripts etc.
   *
   * Returns:
   *   n/a
   * 
   * Postcondition:
   *   Hover scripts installed, 'this document' links indicated as 'current'
   */

  install: function()
  {
    $(SIF_CONFIG.ACTIVE_LINKS.SELECTOR.ROOT).find(SIF_CONFIG.ACTIVE_LINKS.SELECTOR.ELEM).each(function(){SiActiveLinks.attachElem(this)});
  },


  /*
   * attachElem()
   *   Attaches hover scripts to the given element (link)
   */

  attachElem: function(linkElm, setAsActive)
  {
    var href = linkElm.getAttribute('href', 2);
    if(href != null && href.charAt(0)=='#')
      return;
    href = SiURL.mkLocal(SiURL.strip(href));

    var params = SiActiveLinks._getElemParams(linkElm);
    if(!params)
      return;
      
    // This is a link to the current page; set 'current' properties:  
    // if(href != null && href != '' && href.charAt(0) != '#' && SiURL.isCurrent(href))
    if(href != null && href != '' && SiURL.isCurrent(href))
    {
      SiActiveLinks._setImage(params.imageElm, params.imageUrl.replace(new RegExp('\\b' + SIF_CONFIG.ACTIVE_LINKS.IMAGE.DEFAULT_SUBSTR + '\\b'), SIF_CONFIG.ACTIVE_LINKS.IMAGE.ACTIVE_SUBSTR));
      $(linkElm).addClass(SIF_CONFIG.ACTIVE_LINKS.CURRENT_PAGE_CLASS);
      if(linkElm!=params.imageElm)
        $(params.imageElm).addClass(SIF_CONFIG.ACTIVE_LINKS.CURRENT_PAGE_CLASS);
    } 
    // Set up hover scripts:
    else if(params.imageElm)
      SiActiveLinks._assignEvents(linkElm, params.imageElm, params.imageUrl, setAsActive);
  },


  detachElem: function(linkElm)
  {
    $(linkElm).unbind('mouseover', null);
    $(linkElm).unbind('mouseout', null);
    linkElm._si_hoverkilroy = false;
    var params = SiActiveLinks._getElemParams(linkElm);
    if(params)
    {
      SiActiveLinks._setImage(params.imageElm, null);
      $(params.imageElm).removeClass(SIF_CONFIG.ACTIVE_LINKS.HOVER_CLASS);
    }
  },

    // Find a related element containing an image:
  _getElemParams: function(linkElm)
  {
    var imageElm = linkElm;
    var imageUrl = imageElm.src;
    if(!imageUrl)
      imageUrl = $(imageElm).css('background-image');
    if(imageUrl=='none')
    {
      imageElm = $(linkElm).find('IMG').get(0);
      if(imageElm)
        imageUrl = imageElm.src;
    }
    if(imageUrl=='none')
    {
      imageElm = linkElm.parentNode;
      imageUrl = $(imageElm).css('background-image');
    }
    if(imageUrl=='none')
    {
      imageElm = $(linkElm.parentNode).find('IMG').get(0);
      if(imageElm)
        imageUrl = imageElm.src;
    }
    if(imageUrl=='none')
      imageElm = null;

    if(imageUrl && ((imageUrl.indexOf(SIF_CONFIG.ACTIVE_LINKS.IMAGE.DEFAULT_SUBSTR)!=-1) || imageUrl.indexOf(SIF_CONFIG.ACTIVE_LINKS.IMAGE.HOVER_SUBSTR)!=-1 || imageUrl.indexOf(SIF_CONFIG.ACTIVE_LINKS.IMAGE.ACTIVE_SUBSTR)!=-1))
      return({
        imageElm: imageElm,
        imageUrl: imageUrl
      });
    else
      return(null);
  },

  /*
   * _setImage()
   *   Sets the (foreground or background) image of an element
   */

  _setImage: function(element, url)
  {
    if(element==null)
      return;
    if(element.tagName=='IMG' || (element.tagName=='INPUT' && element.src!=null && element.src!=''))
    {
      // Trap image load errors; reset to default image if error is called:
      if(!element.onerror)
      {
        element.onerror = function()
        {
          // Avoid posting events to itself in case of error loading default image:
          var re = new RegExp('\\b(' + SIF_CONFIG.ACTIVE_LINKS.IMAGE.HOVER_SUBSTR + '|' + SIF_CONFIG.ACTIVE_LINKS.IMAGE.ACTIVE_SUBSTR + ')\\b');
          if(element.src.match(re))
            element.src = element.src.replace(re, SIF_CONFIG.ACTIVE_LINKS.IMAGE.DEFAULT_SUBSTR);
        }
      }
      if(!url)
        url = 'about:blank';
      element.src=url;
    }
    else
    {
      if(!url)
        url = '';
      element.style.backgroundImage=url;
    }
  },


  /*
   * _assignEvents()
   *   Assigns image switching mouse event hanndlers to a link
   */

  _assignEvents: function(linkElm, imageElm, imageUrl, setAsActive)
  {
    if(linkElm._si_hoverkilroy)
      return;      
    linkElm._si_hoverkilroy = true;

    var hoverImg = imageUrl.replace(new RegExp('\\b' + SIF_CONFIG.ACTIVE_LINKS.IMAGE.DEFAULT_SUBSTR + '\\b'), SIF_CONFIG.ACTIVE_LINKS.IMAGE.HOVER_SUBSTR);
    SiActiveLinks._cache(hoverImg);

    var whenOver = function()
    {
      // [c1]
      if(window.SiActiveLinks._setImage!=null)
        window.SiActiveLinks._setImage(imageElm, hoverImg);
      if(true)
        $(imageElm).addClass(SIF_CONFIG.ACTIVE_LINKS.HOVER_CLASS);
    };

    var whenOut = function()
    {
      // [c1]
      if(window.SiActiveLinks._setImage!=null)
        window.SiActiveLinks._setImage(imageElm, imageUrl);
      if(true)
        $(imageElm).removeClass(SIF_CONFIG.ACTIVE_LINKS.HOVER_CLASS);
    };

    $(linkElm).bind('mouseover', whenOver);
    $(linkElm).bind('mouseout', whenOut);
    
    if(setAsActive)
      whenOver();
  },


  /*
   * _cache()
   *   Caches the given inage
   */

  _cache: function(imageUrl)
  {
    if(SiActiveLinks._imageCache==null)
      SiActiveLinks._imageCache = new Array();
    imageUrl = imageUrl.replace(/url\(["']*([^"']*)["']*\)/i, '$1');
    for(var i=0; i<SiActiveLinks._imageCache.length; i++)
    {
      if(SiActiveLinks._imageCache[i].src==imageUrl)
        return;
    }
    var img = new Image();
    img.src = imageUrl;
    SiActiveLinks._imageCache.push(img);
  }

}


/*===========================================================================
 *
 * static class SiFoldingItems
 *   Initialization of flowing lists and headings.
 *
 */

SiFoldingItems = {

  install: function()
  {
    // Handling of folding lists:
    
    // Set up the folding behaviour:
    //APA: var current = SiURL.getCurrentPath();
    
    // Get all (possibly foldable) <LI> nodes in the folding list:
    $(SIF_CONFIG.FOLDING_LISTS.SELECTOR + ' li')
      
      // Assign a click hanlder on each of them; we need to assign event handlers also on unfoldable leaf nodes to trap click events on those instead of the ancestors:
      .click(function(event) 
      {
        var self = $(this);
        // If the clicked <LI> node is foldable (has nested lists) and the element hit was not a link; toggle the folding state:
        if(self.has('ul').length && event.target.tagName != 'A')
        {
          // Toggle the nested content:
          self.children('ul').slideToggle('fast');
          
          SiActiveLinks.detachElem(this);

          // Toggle the folding indicator:
          if(!self.hasClass(SIF_CONFIG.FOLDING_LISTS.CLOSED_CLASSNAME))
            self.addClass(SIF_CONFIG.FOLDING_LISTS.CLOSED_CLASSNAME);
          else
            self.removeClass(SIF_CONFIG.FOLDING_LISTS.CLOSED_CLASSNAME);
          
          SiActiveLinks.attachElem(this, true);
        }
        // A click event on an <A> element should bubble, not otherwise:
        return(event.target.tagName=='A');
      })
      
      // Set style on nodes that can fold:
      .has('ul').addClass(SIF_CONFIG.FOLDING_LISTS.CANFOLD_CLASSNAME)   
      
      // Initialize the folding icons for folded nodes (with hidden content):
      .addClass(SIF_CONFIG.FOLDING_LISTS.CLOSED_CLASSNAME)

      // Initialize the content of folded nodes to be hidden:
      .children('ul').hide();
      

    /* Now, open the branch to the nodes(s) containing the current URL. We 
     * could have done this by not folding them in the first place above by 
     * some propeller head code like:
     *
     *  .children('ul:not(:has(a[href=' + current + ']))').hide()
     *  .parent().addClass(SIF_CONFIG.FOLDING_LISTS.CLOSED_CLASSNAME);
     *
     * but then we would not have handeled variations of links/URL that are 
     * to be considered equal.
     */

    // Get the nodes to unfold:
    var current = SiURL.getCurrentPath();
    var nodesInPath = [];
    $(SIF_CONFIG.FOLDING_LISTS.SELECTOR + ' li a').each(function()
    {
      if(SiURL.mkLocal(SiURL.strip(this.href)) == current)
        nodesInPath.push(this.parentNode);
    });
    
    // ...and unfold:
    $(nodesInPath).parentsUntil(SIF_CONFIG.FOLDING_LISTS.SELECTOR)
      .removeClass(SIF_CONFIG.FOLDING_LISTS.CLOSED_CLASSNAME)
      .show('ul');


    // Handling of speed search form attached to a folding list:
    $(SIF_CONFIG.FOLDING_LISTS.SELECTOR).prev('form').find('input[type=text]')
      .keyup(function()
      {
        // Clear search input status color:
        $(this).removeClass('foldingfound');
        $(this).removeClass('foldingnotfound');

        SIF_CONFIG.FOLDING_LISTS.searchMatchUrl = null;
        
        // Get the folding list:
        var list = $(this.form).next();

        
        // Clear highlights in the list:
        //UTIL: unwrapKeep()
        list.find('.highlight').each(function()
        {
          // Get the heading text node:
          var head = this.previousSibling;
          if(head == null || head.nodeType == 1)
          {
            // ...or create one if n/a:
            head = document.createTextNode();
            this.parentNode.insertBefore(head, this);
          }
          // Append value to heading text node:
          head.nodeValue += this.innerHTML;
          
          // If there is a taiking text node:
          var tail = this.nextSibling;
          if(tail != null && tail.nodeType == 3)
          {
            // ...also append value of that one to heading text node:
            head.nodeValue += tail.nodeValue;
            this.parentNode.removeChild(tail);
          }
          // Remove old element:
          this.parentNode.removeChild(this);
        });
        

        // Skip searches < 3 chrs long
        if(this.value.length >= 3)
        {
          // Create a fuzzy search re:
          var searchRe = new RegExp('(' + this.value.replace(/[^\w]+/g, '').replace(/(.)/g, '$1[^\\w]*') + ')', 'gi');
          
          // Get all the foldable <LI>s:
          var foldable = list.find('li:has(ul)');
          var matching = []; // Set of matching nodes
          var scrap = [];    // 

          // Search through foldable AND their direct decendant <A>s:
          foldable.add(foldable.find('> a')).each(function()
          {
            // Search through all child nodes...
            for(var node = this.firstChild; node != null; node = node.nextSibling)
            {
              //...which are text nodes and matches the search re:
              if(node.nodeType == 3 && node.nodeValue.match(searchRe))
              {
                // Add to the matching set:
                if(this.tagName == 'LI')
                  matching.push(this);
                else
                {
                  matching.push(this.parentNode);
                  if(!SIF_CONFIG.FOLDING_LISTS.searchMatchUrl)
                    SIF_CONFIG.FOLDING_LISTS.searchMatchUrl = this.href;
                }
                  

                // Insert highlight code:
                $(node).before(node.nodeValue.replace(searchRe, '<span class="highlight">$1</span>'));
                // We'll trash the node later: 
                scrap.push(node);
              }
            }
          })
          
          // Trash nodes replaced with highlight code:
          $(scrap).remove();

          // We'll unfold all parents of matching nodes, up to the list root:
          matching = $(matching).parentsUntil(SIF_CONFIG.FOLDING_LISTS.SELECTOR);
          
          // Now, fold all foldable nodes except the ones to have unfolded:
          foldable.not(matching)
            .has('ul').addClass(SIF_CONFIG.FOLDING_LISTS.CLOSED_CLASSNAME)
            .children('ul').slideUp('fast');

          // And then unfold to reveal the matches:
          matching
            .has('ul').removeClass(SIF_CONFIG.FOLDING_LISTS.CLOSED_CLASSNAME)
            .children('ul').slideDown('fast');
            
          // Last, update search input status color:
          if(matching.length > 0)
            $(this).addClass('foldingfound');
          else
            $(this).addClass('foldingnotfound');
        }
      });
      

    $(SIF_CONFIG.FOLDING_LISTS.SELECTOR).prev('form').submit(function()
    {
      if(SIF_CONFIG.FOLDING_LISTS.searchMatchUrl)
        window.location.href = SIF_CONFIG.FOLDING_LISTS.searchMatchUrl;
      return(false);
    });




    // Handling of folding paragraphs:

    $(SIF_CONFIG.FOLDING_PARAGRAPHS.SELECTOR)
      .each(function() 
      {
        /*
         * Get the level of the leading heading (or of not heading, empty 
         * string). We'll use this value to check where the content following 
         * the heading ends.
         */
        var level = this.tagName.replace(/[a-z]*/i, '');
        
        // The folding header is not normally an 'active' link, force it to be:
        SiActiveLinks.attachElem(this);

        // Click handler on the leading heading:
        $(this).click(function() 
        {
          // Turn off all active-link behavior, making the default CSS take effect:
          SiActiveLinks.detachElem(this);
          
          // For everything after the heading:
          $(this).nextAll().each(function()
          {
            // Check if still in p:s following the leading heading?
            var l = this.tagName.replace(/[a-z]*/i, '');
            if((l && l<=level) || $(this).is(SIF_CONFIG.FOLDING_PARAGRAPHS.END_SELECTOR))
              return(false);

	          $(this).slideToggle('fast', function()
	          {
	            // After toggling, trig onload event on contained IFRAMEs to force scripted layout:
	            $(this).find('IFRAME').each(function()
	            {
	              SiIFrame.layout(this);
	            });
	          });
          });
          if(!$(this).hasClass(SIF_CONFIG.FOLDING_PARAGRAPHS.OPEN_CLASSNAME))
            $(this).addClass(SIF_CONFIG.FOLDING_PARAGRAPHS.OPEN_CLASSNAME);
          else
            $(this).removeClass(SIF_CONFIG.FOLDING_PARAGRAPHS.OPEN_CLASSNAME);

          // Attach the active-link behavior again, now making use of the new assigned CSS:
          SiActiveLinks.attachElem(this, true);

	        return(false);
	      });
  	    
	      $(this).nextAll().each(function()
        {
          // Check if still in p:s following the leading heading?
          var l = this.tagName.replace(/[a-z]*/i, '');
          if((l && l<=level) || $(this).is(SIF_CONFIG.FOLDING_PARAGRAPHS.END_SELECTOR))
            return(false);
  	       
	        // Initialize content:
	        $(this).hide().addClass(SIF_CONFIG.FOLDING_PARAGRAPHS.BODY_CLASSNAME + level);
  	    });
	    });
	    
	    //YYY: $('tr.folding th').
  }

}


/*===========================================================================
 *
 * static class SiSwitchItems
 *   APA
 *
 */


SiSwitchItems = {

  install: function()
  {
    // Handling of folding lists:

    $('#switch div') //APA! SIF_CONFIG.FOLDING_LISTS.SELECTOR
      .mouseenter(function(){
        $(this).find('IMG[src*=closed]').each(function() {
          $(this).attr('src', $(this).attr('src').replace(/closed/, 'open'));
        });
        $(this).children().stop(true, true);
        $(this).children('p').fadeOut('fast');
        $(this).children('ul').slideDown('normal');
      })
      .mouseleave(function(){
        $(this).find('IMG[src*=open]').each(function() {
          $(this).attr('src', $(this).attr('src').replace(/open/, 'closed'));
        });
        $(this).removeClass('open');
        $(this).children('ul').slideUp('fast');
        $(this).children('p').fadeIn('fast');
      })
      .each(function()
      {
        $(this).find('p').css('position', 'absolute');
        $(this).find('ul').slideUp(0);
        $(this).children('p').show();
      });
  }

}


/*===========================================================================
 *
 * static class SiInlineForms
 *   Handling of inline forms. An inline form is included in a page by a link 
 *   to a page (typically containing a form or other "device" that possibly 
 *   reloads the page) contained in a paragraph with the INLINE_FORMS.SELECTOR 
 *   class. The paragraph content will be replaces by an IFRAME containing the 
 *   form page. Event handler assures the IFRAME is resized to fit the page, 
 *   w/o the need for scrollbars.
 *
 */

SiInlineForms = {
  
  install: function()
  {
    $(SIF_CONFIG.INLINE_FORMS.SELECTOR).each(function(){
      var html = SiIFrame.createHtml($(this).find('a').attr('href'), null, 'inlineform ' + this.className);
      if(html)
      {
        $(this).html(html);
        $(this).addClass(SIF_CONFIG.INLINE_FORMS.ASSIGNED_CLASSNAME);
      }
    });
  }
}


/*===========================================================================
 *
 * static class SiInlineAjax
 *
 */

SiInlineAjax = {
  
  install: function()
  {
    $(SIF_CONFIG.INLINE_AJAX.SELECTOR).each(function(){

      // Get the URL to load:
      var href = $(this).find('a').attr('href');
      if(href)
      {
        var self = $(this);
        
        // Add progress indicator to 'self': 
        self.html($('<img src="/common/styles2/images/_fw_loading_64x64.gif" style="display: block; margin: 0px auto 0px auto" />')
          // Use the progress indicator onload handler to init ajax load. This 
          // way we make sure the progress indicator really shows, otherwise it 
          // might be blocked in the browser socket pool by the ajax calls. 
          .load(function()
          {
            $.ajax({
              type:    "GET",
              url:     href,
              success: function(msg)
              {
                // Have a temporary container to parse the received html code:
                var container = $('<div></div>').html(msg);
                // Move all (new) child nodes off the container in front of the 'self' element:
                container.children().insertBefore(self);
                // Throw away the temporary container as well as 'self' containing the progress indicator:
                container.remove();
                self.remove();
              }
            });
          }));
      }
    });
  }
}


/*===========================================================================
 *
 * static class SiPageCaptions
 *   Simplified sIFR - Stylized headigs by the means of replacing each 
 *   element matching the PAGE_CAPTIONS.SELECTOR with a text rendering 
 *   Flash control.
 *
 */

SiPageCaptions = {

  install: function()
  {
    if(SiDomHelp.isTopWindow())
    {
      $(SIF_CONFIG.PAGE_CAPTIONS.SELECTOR).each(function(){
        SiPageCaptions.mkCaption(this);
      });
    }
  },
  
  mkCaption: function(elem, width, height)
  {
    if(!SiDomHelp.isEditedByMe(elem))
    {
      if(!width)
        width  = elem.offsetWidth - SiDomHelp.getStyle(elem, 'paddingLeft') - SiDomHelp.getStyle(elem, 'paddingRight');
      if(!height)
        height = elem.offsetHeight - SiDomHelp.getStyle(elem, 'paddingTop') - SiDomHelp.getStyle(elem, 'paddingBottom');
      if(height>5)
      {
        var link   = $(elem).find('A[href]').attr('href');
        var anchor = $(elem).find('A[name]').attr('name');
        // Format the text to fit the flash
        var text   = elem.innerHTML.replace(/<br[^>]*>\s*/ig, '{BR /}') // Clean up <br>:s (Flash is picky), (temporarily) make them {br /}
                                   .replace(/<(\/?)em>/ig, '{$1I}')     // <em> -> {i}
                                   .replace(/<(\/?)strong>/ig, '{$1B}') // <strong> -> {b}
                                   .replace(/<[^>]+>/ig, '')            // remove all other tags
                                   .replace(/\{([^\}]+)\}/ig, '<$1>')   // retore tags {xx} to <xx>
                                   .replace(/&nbsp;/g, ' ')             // nbsp to space
                                   .replace(/\s+/g, ' ')                // Skip redundant whitespace
                                   .replace(/^\s*(.*)\s*$/g, '$1');     // Trim whitespace

        //APA: text = escape(text);
        text = text.replace(/&/, '%26');
                                   
        // Convert all HTML tags to uppercase (elem differs between browsers, IE makes them upper, FF & co lower).
        var tags = text.match(/<([^>]+)>/g);
        if(tags!=null)
        {
          for(var i = 0; i < tags.length; i++)
            text = text.replace(new RegExp(tags[i]), tags[i].toUpperCase());
        }
        
        if(text)
        {
          // Create flash:
          SiFlash.injectHtml(
            '<!-- ' + text + ' -->'
            + (anchor ? '<a name="' + anchor + '"></a>' : '')
            + SiFlash.createHtml(SIF_CONFIG.PAGE_CAPTIONS.HANDLER_URL, // URL
                                 'text='        + text
                               + '&width='      + width
                               + '&height='     + height
                               + '&font='       + SiDomHelp.getStyle(elem, 'fontFamily')
                               + '&size='       + SiDomHelp.getStyle(elem, 'fontSize')
                               + '&style='      + SiDomHelp.getStyle(elem, 'fontStyle')
                               + '&variant='    + SiDomHelp.getStyle(elem, 'fontVariant')
                               + '&weight='     + SiDomHelp.getStyle(elem, 'fontWeight')
                               + '&decoration=' + SiDomHelp.getStyle(elem, 'textDecoration')
                               + '&align='      + SiDomHelp.getStyle(elem, 'textAlign')
                               + '&indent='     + SiDomHelp.getStyle(elem, 'textIndent')
                               + '&lineHeight=' + SiDomHelp.getStyle(elem, 'lineHeight')
                               + '&transform='  + SiDomHelp.getStyle(elem, 'textTransform')
                               + '&color='      + SiDomHelp.rgbColorToHex(SiDomHelp.getStyle(elem, 'color'))
                               + (link? '&url=' + link : ''),          // variables
                                 null,                                 // styles
                                 null,                                 // baseURL
                                 'transparent',                        // wmode
                                 null,                                 // bgcolor
                                 width,                                // width
                                 height),                              // height
                             elem);
          elem._si_edit_kilroy = 'SiPageCaptions';
        }
      }
    }
  }

}



/*===========================================================================
 *
 * static class SiSplash
 *   Splash (contact) "windows".
 *
 *   Splash windows are used to allow the user to fill in various forms 
 *   without leaving the current web page. 
 *
 *   Splash windows can be displayed either by the user clicking on a link 
 *   with the "splash class", or requested via the document URL using a 
 *   "splash argument". This is typically used when entering the site from a 
 *   campaign banner or similar. When the user is has completed the form, a 
 *   (unique) cookie, the name based on the form URL, is set to remember this 
 *   form as ‘done’. This will prevent the form from showing again via the 
 *   document URL. Note that the cookie is set regardless of if the splash 
 *   form was displayed as a result of the user clicking a link or if the 
 *   splash form was requested via the document URL. Also note that the user 
 *   can always get to the splash form via a link, even if the ‘done’ cookie 
 *   is set.
 *
 *   The splash window calls up the corresponding form page in an <IFRAME> 
 *   with an additional argument “framed”. The form should be displayed 
 *   without page decorations when called this way. This allows for nice 
 *   degrading of splash link clicks, and also allows a user to open the 
 *   form in a separate window or tab (Shift+click, Ctrl+click), as the form 
 *   in these cases can be displayed with page decorations. 
 *
 *   Splash windows are modal but cancelable. When displayed, the rest of the 
 *   document is dimmed and "disabled". If the user clicks outside the splash 
 *   window, the splash window is closed and the document reappears. 
 *
 *   Only one (1) splash window can be simultaneously displayed.
 */

SiSplash = {

  _id:     null,
  _canvas: null,
  _anchor: null,


  /*
   * install()
   *   - For the topmost window: Installs splash script popup on all splash links
   *   - For child (splash) window: Attahces splash close handler to 'done' form button(s)
   *
   * Returns:
   *   n/a
   * 
   * Postcondition:
   *   - Topmost window: event handler installed on all splash links
   *   - For child (splash) window: event handler installed on 'done' form button(s)
   */

  install: function()
  {
    if(SiDomHelp.isTopWindow())
    {
      // For top window: launch splash form for all splashform links:
      $(SIF_CONFIG.SPLASH.LINK_SELECTOR).click(function(event)
      {
        if(!event.ctrlKey && !event.shiftKey)
          return(!SiSplash.show(this.href, null /*this*/));
        else
          return(true);
      });
      
      SiSplash.show();
    }
    else
    {
      // For child window (i.e. splash form iframe): handle 'done' form button:
      $(SIF_CONFIG.SPLASH.DONEBUTTON_SELECTOR).click(function(event)
      {
        SiSplash.done(); 
        return(false);
      });
    }
  },


  /*
   * show()
   *   Creates splash popup if requested via the document URL, if not previously 'done'.
   *
   * show(splashUrl, anchorElement)
   *   Shows splash popup
   *
   * In:
   *   splashUrl:     URL to the splash document, default form handler prefix will be inserted if not present.
   *   anchorElement: anchor element to open the splash relative
   *
   * Returns:
   *   n/a
   * 
   * Postcondition:
   *   Splash window is opened
   */

  show: function(splashUrl, anchorElement)
  {
    if(SiSplash._canvas!=null)
      return;
      
    var forceOpen = false;
    if(splashUrl==null)
    {
      var winUrl=window.location.href;
      if(winUrl.match(SIF_CONFIG.SPLASH.LOCATION_PATTERN))
        splashUrl=winUrl.replace(SIF_CONFIG.SPLASH.LOCATION_PATTERN, '$1');
    }
    else
      forceOpen = true;

    if(splashUrl!=null)
    {
      splashUrl = SiURL.mkLocal(splashUrl);
      var splashID = 'splash_' + splashUrl;    
      if(forceOpen || !SiCookies.get(splashID))
      {
        SiSplash._createCanvas(splashUrl, anchorElement);
        SiSplash._id = splashID;
      }
    }
    return(true);
  },


  /*
   * done()
   *   Confirms that a the current splash window is 'done'; stores the 'done' flag in an persistent cookie and closes the window. 
   *
   * Returns:
   *   n/a
   * 
   * Postcondition:
   *   'Done' cookie stored and splash window closed.
   */

  done: function()
  {
    var topWin = SiDomHelp.topWindow();
    if(window==topWin)
    {
      SiCookies.set(SiSplash._id, 'kilroy', true);
      SiSplash.close();
    }
    else if(topWin.SiSplash!=null)
      topWin.SiSplash.done();
    else
      alert('Warning: Page called out of context');
  },


  /*
   * close()
   *   Closes the current splash window.
   *
   * Returns:
   *   n/a
   *
   * Postcondition:
   *   Splash window closed.
   */

  close: function()
  {
    var topWin = SiDomHelp.topWindow();
    if(window==topWin)
    {
      if(SiSplash._canvas!=null)
      {
        SiSplash._canvas.style.visibility='hidden';
        SiSplash._canvas.parentNode.removeChild(SiSplash._canvas);
        SiSplash._canvas = null;
      }
    }
    else if(topWin.SiSplash!=null)
      topWin.SiSplash.close();
    else
      alert('Warning: Page called out of context');
  },


  /*
   * _layout()
   *   Layouts the splash window accordig to the size if the hosted document
   */

  _layout: function(show)
  {
    if(SiSplash._canvas==null)
      return;

    var box    = SiSplash._canvas.childNodes[SiSplash._canvas.childNodes.length-1];
    var frame  = SiSplash._canvas.getElementsByTagName('IFRAME')[0];

    SiIFrame.layout(frame);

    var docWidth  = box.offsetWidth;
    var docHeight = box.offsetHeight;

    var scroller = SiDomHelp.getDocumentScroller();
    if(SiSplash._anchor!=null && box!=null)
    {
      var anchorPos = SiDomHelp.getAbsolutePoint(SiSplash._anchor);
      var top;
      if(anchorPos.y - scroller.scrollTop > scroller.clientHeight/2)
        top = anchorPos.y - docHeight;
      else
        top = anchorPos.y;
      box.style.top = Math.max(scroller.scrollTop + 10, Math.min(top, scroller.scrollTop + scroller.clientHeight - docHeight)) + 'px';
      var cw = scroller.clientWidth - 64;
      if(anchorPos.x + docWidth > cw)
        box.style.left = Math.max(0, cw - docWidth) + 'px';
      else
        box.style.left = anchorPos.x + 'px';
    }
    else
    {
      box.style.top = (scroller.clientHeight - docHeight)/2 + scroller.scrollTop + 'px';
      box.style.left = (scroller.clientWidth - docWidth)/2 + scroller.scrollLeft + 'px';
    }
  },


  /*
   * _createCanvas()
   *   Creates the splash canvas with splash window decorations and hosting <iframe>.
   */

  _createCanvas: function(splashUrl, anchorElement)
  {
    SiSplash._anchor = anchorElement;
    if(SiSplash._canvas!=null)
      SiSplash._canvas.innerHTML = '';
    else
      SiSplash._canvas = document.createElement('DIV');
    var canvas = SiSplash._canvas;
    
    canvas.className=SIF_CONFIG.SPLASH.UI.CANVAS_CLASS;
    canvas.onclick=function(event)
    {
      var element;
      if(window.event!=null) // Typically IE
        element=window.event.srcElement;
      else // Typically W3C compliant
        element=event.target;
      if(element.id==SIF_CONFIG.SPLASH.UI.OUTSIDE_ID)
        SiSplash.close();
    }

    var cover = document.createElement('DIV');
    cover.id = SIF_CONFIG.SPLASH.UI.OUTSIDE_ID;
    cover.style.height = SiDomHelp.getDocumentScroller().scrollHeight + 'px';
    canvas.appendChild(cover);

    var box = document.createElement('TABLE');
    box.className = SIF_CONFIG.SPLASH.UI.BOX_CLASS;
    var container;
    for(var r=0; r<3; r++)
    {
      var row = box.insertRow(-1);
      for(var c=0; c<3; c++)
      {
        var cell = row.insertCell(-1);
        cell.className = 'r' + r + 'c' + c;
        if(r==1 && c==1)
          container = cell; // Yes, this is where the content is going!
        else if(r==0 && c==2)
          cell.onclick = SiSplash.close; // Yes, this is where the content is going!
      }
    }

    if(splashUrl!=null)
    {
      var fileType = splashUrl.replace(/.*\.(\w+)($|\?)/, '$1');
      switch(fileType)
      {
        case 'htm':
        case 'html':
        case 'txt':
        case 'php':
          container.innerHTML = SiIFrame.createHtml(splashUrl, null, 'splashform', 'SiSplash._layout');
          break;
        case 'flv':
          SiFlash.create(SIF_CONFIG.MEDIA.FLV.PLAYER.URL,                // URL
                         'video=' + splashUrl,                           // variables
                         'width:640;height:480',                         // styles
                         container,                                      // containerID
                         '@',                                            // baseURL
                         null,                                           // wmode
                         null,                                           // bgcolor
                         null,                                           // width
                         null,                                           // height
                         SiDomHelp.uniqueId(SIF_CONFIG.MEDIA.PLAYER.ID), // ctrlId
                         SIF_CONFIG.MEDIA.FLV.PLAYER.CLASSNAME);         // ctrlClass
          break;
        case 'jpg':
        case 'png':
        case 'gif':
          container.innerHTML = '<img src="' + splashUrl + '" onload="SiSplash._layout()" />';
          break;
        default:
          container.innerHTML = 'Unknown media type "' + fileType + '" in "' + splashUrl + '"';
          break;
      }
    }
    canvas.appendChild(box);
    
    window.document.body.appendChild(canvas);
    SiSplash._layout(false);
  }
}


/*===========================================================================
 *
 * static class SiIFrame
 *   Helper for creating IFRAMEs.
 *
 */

SiIFrame = {

  
  getDocument: function(frame)
  {
    if(frame.contentDocument!=null)
      return(frame.contentDocument);
    else if(frame.contentWindow!=null)
      return(frame.contentWindow.document);
    else
      return(null);
  },

  getWindow: function(frame)
  {
    if(frame.contentWindow!=null)
      return(frame.contentWindow);
    else if(frame.contentDocument!=null)
      win = frame.contentDocument.defaultView;
    else
      return(null);
  },

  layout: function(frame)
  {
    try
    {
      var doc = SiIFrame.getDocument(frame);
      var win = SiIFrame.getWindow(frame);
      if(doc!=null && doc.body!=null)
      {
        if(doc.body.parentNode.scrollWidth > 64)
          frame.style.width = doc.body.parentNode.scrollWidth + 'px';
        if(doc.body.parentNode.scrollHeight > 64)
          frame.style.height = doc.body.parentNode.scrollHeight + 'px';
        
      }
    }
    catch(e){};
  },

  createHtml: function(src, layoutName, className, layoutHandler)
  {
    if(src)
    {
      if(src.indexOf('?')==-1)
        src += '?siiframe';
      layoutName    = '&layout=' + (layoutName || 'framed');
      className     = '&class=framed' + (className ? (' ' + className) : '');
      layoutHandler = layoutHandler || 'SiIFrame.layout';
      return('<iframe name="__si_framed" onload="' + layoutHandler + '(this)" scrolling="no" frameborder="0" src="' + src + layoutName + className + '" />');
    }
    else
      return(null);
  }
  
}

/*===========================================================================
 *
 * static class SiMenu
 *   Menu system.
 */

SiMenu = {
  install: function()
  {
    // First get the menu definition file, use the path from the first menu link to find the correct script:
    $('#toc a').eq(0).each(function()
    {
      var script = new SiJS();
      script.onload = SiMenu._install2;
      // Substitute file name with script name:
      script.load(this.href.replace(/\w+\.\w+$/, 'entries.js'));
    });
  },
  
  _install2: function()
  {
    // Seciondly load the menu script itself:
    var script = new SiJS();
    script.onload = function()
    {
      $('#toc').each(function()
      {
        if(window.sim_init!=null)
          sim_init();
      });
    }
    script.load('/common/scripts/si_dommenu.js');
  }

}



/*===========================================================================
 *
 * static class SiDocumentPopup
 *   Makes links to documents (e.g. PDF files) to load content via the 
 *   streamdown script (setting an attachement HTTP header which makes them escape the browser).
 *
 */

SiDocumentPopup = {

  /*
   * install()
   *   - Installs click handler on all document links
   *   - Adds "document popup" class to all document links, i.e. shows a "popup" icon next to the links. 
   *
   * Returns:
   *   n/a
   * 
   * Postcondition:
   *   Click hanlders installed in all document liks
   */

  install: function()
  {
    $(SIF_CONFIG.DOCUMENTPOPUP.SELECTOR).click(SiDocumentPopup._popup)
                                        .addClass(SIF_CONFIG.DOCUMENTPOPUP.CLASSNAME);
  },


  /*
   * _popup()
   *   Called when user clicks a document link. 
   *   Cancels the click and launches loading via the 'streamdown' script.
   */

  _popup: function()
  {
    /* Get the file title from:
     *   a) the link's or link's parent's title attribute
     *   b) first title ot alt attribute of the children
     */
    var title = this.title || this.parentNode.title;
    if(!title)
      title = $(this).find('*[title]').attr('title') || $(this).find('*[alt]').attr('alt');
    if(title)
      title = '&filename=' + encodeURI(title) + '.' + this.href.replace(/.*\.([^.]*)$/, '$1');
    else
      title = '';
    window.location.href = SIF_CONFIG.DOCUMENTPOPUP.SCRIPT + '?file=' + this.href + title;
    return(false);
  }
}



/*===========================================================================
 *
 * static class SiTrack
 *   Tracking code installation.
 *
 */

SiTrack = {

  /*
   * install()
   *   Installs Google Analytics tracking scripts
   *
   * Returns:
   *   n/a
   * 
   * Postcondition:
   *   Analytics code linked to the document
   */

  install: function()
  {
    if(!SiURL.isTestServer())
    {
      var script = new SiJS();
      script.onload = SiTrack.initiate;
      script.load((("https:" == document.location.protocol) ? "https://ssl." : "http://www.") + 'google-analytics.com/ga.js');
    }
  },


  _rewrite: function(path)
  {
    var from = SIF_CONFIG.ANALYTICS.REWRITE.FROM;
    var to   = SIF_CONFIG.ANALYTICS.REWRITE.TO;

    for(var i=0; i<Math.min(from.length, to.length); i++)
      path = path.replace(from[i], to[i]);
    return(path);
  },
    


  /*
   * initiate()
   *   Call Google Analytics tracking
   *
   * Returns:
   *   n/a
   * 
   * Postcondition:
   *   The page is tracked.
   */

  initiate: function()
  {
    if(!SiURL.isTestServer() && SIF_CONFIG.ANALYTICS.CODE != null && window._gat!=null)
    {
      try
      {
        SiTrack._pageTracker = _gat._getTracker(SIF_CONFIG.ANALYTICS.CODE);
        SiTrack._pageTracker._setDomainName(SIF_CONFIG.ANALYTICS.DOMAIN);
        SiTrack._pageTracker._setAllowLinker(true);
        SiTrack._pageTracker._initData();
        
        var t = SiTrack._rewrite(window.location.pathname + window.location.search);
        if(t)
          SiTrack._pageTracker._trackPageview();
        
        for(var i = 0; SiTrack._queue != null && i < SiTrack._queue.length; i++)
          SiTrack._pageTracker._trackPageview(SiTrack._queue[i]);
        SiTrack._queue = null;
      }
      catch(e){}
    }
  },


  /*
   * footprint()
   *   Make a custom footprint in Analytics.
   *
   * In:
   *   value: Footprint value.
   *
   * Returns:
   *   n/a
   * 
   * Postcondition:
   *   The footprint is tracked.
   */

  footprint: function(value)
  {
    var value = SiTrack._rewrite(value);
    if(value)
    {
      if(!SiURL.isTestServer() && SiTrack._pageTracker!=null)
      {
        try
        {
          SiTrack._pageTracker._trackPageview(value);
        }
        catch(e){}
      }
      else
      {
        if(SiTrack._queue==null)
          SiTrack._queue = new Array();
        SiTrack._queue.push(value);
      }
    }
  }
}



/*===========================================================================
 *
 * static class SiUnsupported
 *   Resolving unsupported features, i.e. making IE6 to behave
 *
 */

SiUnsupported = {

  /*
   * static class Ie6Css
   * Handles selectors not supported in IE6
   */

  Ie6Css: {

    /*
     * _apply2elem()
     *   Applies styles to an element.
     *
     * In: 
     *   - element: element to apply styles to
     *   - styles:  styles to apply as an array
     */

    _apply2elem: function(element, styles)
    {
      for(var si=0; si<styles.length && styles[si] && styles[si]!=' '; si++)
      {
        var fragments = styles[si].split(':');
        // fragments[0]: attribute name
        // fragments[1]: attribute value

        // Get the scripted name of the attribute:
        fragments[0] = fragments[0].split('-');
        var name = fragments[0][0];
        for(var ni = 1; ni < fragments[0].length; ni++)
          name += fragments[0][ni].substring(0, 1).toUpperCase() + fragments[0][ni].substring(1);

        switch(name)
        {
          case 'content':
            element.innerHTML = sip_trim(fragments[1], true);
            break;
          case 'margin':
          case 'padding':
          case 'border':
            var values = fragments[1].split(' ');
            element.style[name + 'Top']    = values[0];
            element.style[name + 'Right']  = values[1]||values[0];
            element.style[name + 'Bottom'] = values[2]||values[0];
            element.style[name + 'Left']   = values[3]||values[1]||values[0];
            break;
          default:
            // Anything else, this is a chance so first look if it exists at all:
            try
            {
              if(element.style[name]!=null)
                element.style[name] = fragments[1];
            }
            catch(e){}
            break;
        }
      }
    },


    /*
     * apply()
     *   Initializes the IE6 css selector patch.
     */

    apply: function()
    {
      // Load the css:
      var cssUrl = $('LINK[href*=.css]').attr('href');
      if(!cssUrl)
        return;
      var cssText = SiFile.read(cssUrl);
      if(!cssText)
        return;
    
      // "Precompile" the css:
      cssText=cssText.replace(/[\s]+/g, ' ')                      // Skip newlines
                     .replace(/\/\*.*?\*\//g, '')                 // Skip comments
                     .replace(/{/g, ';')                          // Join all selectors & attributes with a ';'
                     .replace(/[\s]*([\+\:\,\;\}]+)[\s]*/g, '$1') // Trim redundant whitespaces

      // Split css text into rules array with selector + attributes in each slot:
      var rules=cssText.split('}');
      for(var ri = 0; ri < rules.length; ri++)
      {
        // Split selector(s) and attributes from each other:
        var fragments  = rules[ri].split(';');
        var selectors  = fragments[0].split(',');
        var attributes = fragments.slice(1);
        
        for(var si = 0; si < selectors.length; si++)
        {
          /*
           * Test if the selector is of interest for the "magic" handling 
           * (contains '+' or '>') and is not a pseudo-class which jQ will not be 
           * able to select from the (current) DOM or refers to a state (which we 
           * don't have any):
           */
          if(selectors[si].match(/[\+>]|:[a-z]+/) && !selectors[si].match(/:(link|visited|active|hover|after)/))
          {
            // Find mathing elements to apply the style atttributes onto:
            $(selectors[si]).each(function()
            {
              SiUnsupported.Ie6Css._apply2elem(this, attributes);
            });
          }
        }
      }
    }
  },


  /*
   * install()
   *   Installs handling of unsupported features on related objects in the DOM
   *
   * Returns:
   *   n/a
   * 
   * Postcondition:
   *   Unsupported features installed
   */

  install: function()
  {
    if(DETECT.is_ieLt7)
    {
      // IE6 css selector patch:
      SiUnsupported.Ie6Css.apply();
      
      $(SIF_CONFIG.UNSUPPORTED_FIXES.PNG_SELECTOR).each(function()
      {
        // Create a substitute element:
        var substitute = document.createElement('span');
        substitute.innerHTML = '&nbsp;';
        this.insertAdjacentElement('afterEnd', substitute);

        // Copy the current style to the div to keep the complete positioning:
        for(var name in this.currentStyle)
          substitute.style[name]=this.currentStyle[name];

        // Get implicit size correct:
        if(!substitute.style.width || substitute.style.width=='auto')
          substitute.style.width = this.offsetWidth;
        if(!substitute.style.height || substitute.style.height=='auto')
          substitute.style.height = this.offsetHeight;

        // Set the magic alpha filter:
        substitute.style.filter='progid:DXImageTransform.Microsoft.AlphaImageLoader(src="' + this.src + '", sizingMethod="scale")';

        // Hide the original image (don't remove it as it still might be referred to by other scripts):
        this.style.display='none';
      });
    }
  }
}




/*===========================================================================
 *
 * static class SiKeyboard
 *   Handling keyboard focus. 
 *   1) If the page contains a form, focus the first input field
 *   2) Otherwise forcus the first heading, thus giving a nice tab order to 
 *      links and stuff in the page.
 */

SiKeyboard = {

  install: function()
  {
    try
    {
      // Set keyboard focus I(II):
      $(SIF_CONFIG.KBD_FOCUS.PAGE_SELECTOR).eq(0).each(function()
      {
        if(document.createRange!=null)
        {
          // DOM compliant, set selection to the H1:
          var range = window.getSelection();
          range.collapse(this, 0);
        }
        else
        {
          /* IE: set the tabIndex attribute on the H1 and focus it. A negative 
           * tabIndex will make the element to remain unfocusable but still 
           * respond to the focus() method:
           */
          try
          {
            this.tabIndex = -1;
            this.focus();
          }
          catch(e){}
        }
      });

      // Set keyboard focus II(II). Focus first form field in the document:
      $(SIF_CONFIG.KBD_FOCUS.FORM_SELECTOR).eq(0).select();
    }
    catch(e){}
  }
 
}


/*===========================================================================
 *
 * static class SiMiniSearch
 *   Initialization of the minisearch field
 *   1) Applying idle style and help text
 *   2) Handler to navigate to search page when submitting an empty query
 *   3) Creating keyboard shortcut
 */

SiMiniSearch = {
  
  install: function()
  {
    /*
     * Set up defualt text & style in the minisearch field:
     */
    $(SIF_CONFIG.MINISEARCH.SELECTOR + ' input:first')
      .attr('value', SIF_CONFIG.MINISEARCH.INPUT_DEFAULT_VALUE)
      .addClass(SIF_CONFIG.MINISEARCH.INPUT_DEFAULT_CLASS)
      .focus(function()
      {
        if(this.value == SIF_CONFIG.MINISEARCH.INPUT_DEFAULT_VALUE)
        {
          this.value = '';
          $(this).removeClass(SIF_CONFIG.MINISEARCH.INPUT_DEFAULT_CLASS);
        }
      })
      .blur(function()
      {
        if(this.value == '' || this.value == SIF_CONFIG.MINISEARCH.INPUT_DEFAULT_VALUE)
        {
          this.value = SIF_CONFIG.MINISEARCH.INPUT_DEFAULT_VALUE;
          $(this).addClass(SIF_CONFIG.MINISEARCH.INPUT_DEFAULT_CLASS);
        }
      });
    
    $(SIF_CONFIG.MINISEARCH.SELECTOR).submit(function()
    {
      var value = $('input:first').val();
      if(value != '' && value != SIF_CONFIG.MINISEARCH.INPUT_DEFAULT_VALUE) 
        return true; 
      else 
        window.location='/cgi-bin/mnogo'; 
      return false;
    });
    
    // Make Ctrl+Shift+F focus the minisearch field:
    $(document).keydown(function(event)
    {
      if(event.keyCode==70 && event.ctrlKey && event.shiftKey) // Ctrl+Shift+F
        $(SIF_CONFIG.MINISEARCH.SELECTOR + ' input:first').focus();
    });
  }
}



/*===========================================================================
 *
 * static class SiSocialLinks
 *   Install Add-this widget. 
 *
 */

SiSocialLinks = {
  
  install: function()
  {
    /*
     * Add-this widget:
     * See also: [IE7 patch 8]
     */
    window.addthis_pub = SIF_CONFIG.SOCIAL_LINKS.ID;
    var script = new SiJS();
    script.load(SIF_CONFIG.SOCIAL_LINKS.SCRIPT_URL);
    $(SIF_CONFIG.SOCIAL_LINKS.SELECTOR)
      .click(function()
      {
        window.focus();
        addthis_open(this, '', '[URL]', '[TITLE]');
        return(false);
      })
      .mouseout(function()
      {
        addthis_close();
      });
  }

}

/*===========================================================================
 *
 * static class SiDebug
 *   Debug menu
 *
 */

// #PRAGMA COMPACT SKIP START

SiDebug = {


  /*
   * install()
   *   Creates the debug menu if the page is loaded from a test server
   *
   * Returns:
   *   n/a
   * 
   * Postcondition:
   *   Debug menu possibly created
   */

  install: function()
  {
    if(!SIF_DEBUG.ENABLE)
      return;

    if(!SiURL.isTestServer())
      return;

    if(SiDomHelp.topWindow()._si_debugkilroy==true)
      return;
    SiDomHelp.topWindow()._si_debugkilroy=true;

    var menu = document.createElement('div');
    menu.style.position='absolute';
    menu.style.left='0px';
    menu.style.top='0px';
    menu.style.width='140px';
    menu.style.margin='0px';
    menu.style.padding='0px';
    menu.style.zIndex='100';
    menu.style.textAlign='left';

    menu.onmouseover=function(){
      this.style.clip='rect(auto auto auto auto)';
    }
    
    menu.onmouseout=function(){
      this.style.clip='rect(0px 20px 20px 0px)';
    }
    
    menu.onmouseout();

    var html = '<img style="margin: 0px" src="/common/styles2/images/_fw_authoringmenuico.gif" width="20" height="20" />';
    html += '<div style="text-align: left; border: 4px double #2060a0; background: #ffffff; padding: 2px 4px 2px 4px; margin: 0px; font: normal 9px Arial, Helvetica, sans-serif">';
    html += SiDebug._link('<span style="float: right; font-weight: bold; font-size: 7pt; background-color: #ffffff; margin: 1px; padding: 0px 2px"><a href="/admin/guide/stb/page_popup.html">?</a></span>Authoring menu', null, 'font-weight: bold; font-size: 8pt; padding: 0px 4px; color: #ffffff; background-color: #2060a0; margin-bottom: 4px; ');
    html += SiDebug._link('Edit file in Dreamweaver', 'SiDebug._command(null, \'DREAMWEAVER\')');
    html += SiDebug._link('Edit file in Code Editor', 'SiDebug._command(null, \'CODEEDITOR\')');
    html += SiDebug._link('Explore work folder', 'SiDebug._command(null, \'EXPLORE\')');
    html += SiDebug._link('Copy work location', 'SiDebug._command(null, \'COPY\')');
    html += SiDebug._link('---');
    html += SiDebug._link('Start debugger', 'javascript:debugger');
    html += SiDebug._link('List cookies', 'SiDebug._command(null, \'LISTCOOKIES\')');
    html += SiDebug._link('Remove cookies', 'SiDebug._command(null, \'RMCOOKIES\')');
    html += SiDebug._link('Kill SWF Ctrls', 'SiDebug._command(null, \'KILLSWF\')');
    html += SiDebug._link('---');
    html += SiDebug._link('Open at www', window.location.href.replace(/http:\/\/[^\/]*(.*)/, 'http://www.sectra.se$1'));
    html += SiDebug._link('Open at web-devel', window.location.href.replace(/http:\/\/[^\/]*(.*)/, 'http://web-devel.sectra.se$1'));
    html += SiDebug._link('Open at edit', window.location.href.replace(/http:\/\/[^\/]*(.*)/, 'http://edit.sectra.se$1'));
    html += SiDebug._link('Open at localhost', window.location.href.replace(/http:\/\/[^\/]*(.*)/, 'http://localhost$1'));
    html += SiDebug._link('Open at tarrekaise', window.location.href.replace(/http:\/\/[^\/]*(.*)/, 'http://tarrekaise.sectra.se:8089$1'));
    html += SiDebug._link('---');
    html += SiDebug._link('EDIT', 'http://edit.sectra.se/admin/composer/edit.php');
    html += SiDebug._link('---');
    html += SiDebug._link('Group reset', '/common/php2/targetgroup.php?reset');
    html += SiDebug._link('Group set all', '/common/php2/targetgroup.php?all');
    html += SiDebug._link('Group show / edit', '/common/php2/targetgroup.php?show');
    html += SiDebug._link('Show group CSS', '/common/php2/targetgroup.php?css');
    html += SiDebug._link('Show group JS', '/common/php2/targetgroup.php?js');
    html += SiDebug._link('---');
    html += SiDebug._link('Browser:<br/>&nbsp;&nbsp;<b>' + navigator.userAgent + '</b>');
    html += '</div>';
    menu.innerHTML = html;
    window.document.body.appendChild(menu);
  },


  /*
   * _link()
   *   Returns code for a debug menu link
   */

  _link: function(caption, action, style)
  {
    if(!style)
      style='';
    style = '; display: block; margin: 0px; padding: 0px; ' + style;
    if(caption.charAt(0)=='-')
      return('<hr size="1" color="#2060a0" noshade="1" width="80" />');
    else if(action)
    {
      if(action.indexOf('(')!=-1)
        return('<a style="color: #203060' + style + '" href="#" onclick="' + action + '; return false">&bull;&nbsp;' + caption + '</a>');
      else
        return('<a style="color: #203060' + style + '" href="' + action + '">&bull;&nbsp;' + caption + '</a>');
    }
    else
      return('<span style="color: #000000' + style + '">' + caption + '</span>');
  },


  /*
   * _command()
   *   Runs a debug menu command
   */

  _command: function(url, tool)
  {
    if(url==null)
      url = SiURL.getCurrentPath();
    if(url.charAt(url.length-1)=='/')
      url += 'index.html';

    switch(tool)
    {
      case null:
        tool = 'DREAMWEAVER';
        break;
    }

    switch(tool)
    {
      case 'KILLSWF':
        var count = SiFlash.killAll();
        if(count)
          alert('Killed ' + count + ' SWF controls')
        else
          alert('No SWF controls to kill')
        break;
      case 'COPY':
        window.clipboardData.setData('Text', SIF_DEBUG.SITE_ROOT + url.replace(/\//g, '\\'));
        break;
      case 'LISTCOOKIES':
      case 'RMCOOKIES':
        var cookies = document.cookie.split('; ');
        var msg='';
        for (var i=0; i < cookies.length; i++)
        {
          if(cookies[i])
          {
            var crumb = cookies[i].split('=');
            msg += '- ' + crumb[0] + ':\n      "' + crumb[1] + '"\n';
          }
        }
        if(msg)
        {
          if(tool=='RMCOOKIES')
          {
            if(confirm('Remove the cookies at ' + window.location.hostname + '?\n' + msg))
              alert('Removed ' + SiCookies.removeAll() + ' cookies');
          }
          else   
            alert('Cookies at ' + window.location.hostname + ':\n' + msg);
        }
        else
          alert('No cookies at ' + window.location.hostname + '.');
        break;
      case 'EXPLORE':
        url = url.replace(/\/[^\/]*\.[^\/]*$/, '/').replace(/\//g, '\\');
      default:
        SiDebug._edit(url, tool);
        break;
    }
  },


  /*
   * _edit()
   *   Tries to start an editor
   */

  _edit: function(url, editor)
  {
    var errMsg;
    for(var i=0; i<SIF_DEBUG[editor].length; i++)
    {
      try
      {
        var wsh = new ActiveXObject('WScript.Shell');
        wsh.Exec(SIF_DEBUG[editor][i] + ' "' + SIF_DEBUG.SITE_ROOT + url.replace(/\//g, '\\') + '"');
        return;
      }
      catch(e)
      {
        errMsg = e.message;
      }
    }
    if(confirm('Could not start ' + editor + ': ' + errMsg + '\n\nDo you want to show help?'))
      window.open('/admin/guide/install_and_configure.html#Configurewebbrowsers');
  }
}

// #PRAGMA COMPACT SKIP END


/*===========================================================================
 *
 * static class SiLayout
 *   Main Layout class
 *
 */

SiLayout = {

  /*
   * initImmediate()
   *   Installations to run immediate.
   */

  initImmediate: function()
  {
    // Run once:
    if(SiLayout._init_immediate_kilroy)
      return;
    SiLayout._init_immediate_kilroy = true;

    // Escape from fames:
    if(!SiDomHelp.isTopWindow() && !$('body').hasClass('framed'))
    {
      window.top.location.replace(window.location.href);
      return;
    }

    // Call document defined scripts:
    if(window.whenDocumentLoaded!=null)
      whenDocumentLoaded();

    // Debug menu:
    SiDebug.install();

    // Handling of splashforms:
    SiSplash.install();

    // Mail links:
    SiMailLinks.install();

    // Handling of folding items:
    SiFoldingItems.install();
	  
    // Handling of switch items:
    SiSwitchItems.install();

    // Menu system:
    SiMenu.install();

    // Make IE6 behave:
    SiUnsupported.install();

    // Keyboard focus:
    SiKeyboard.install();

    // Minisearch field:
    SiMiniSearch.install();

    // Add-this widget:
    //TODO: Wait for Add-this to fix the IE problems of October'09
    SiSocialLinks.install();

    // Handling of inline forms:
    SiInlineForms.install();
    
    // Stylized headigs:
    SiPageCaptions.install();

    // Create active links:
    SiActiveLinks.install();
    
    // Document popups:
    SiDocumentPopup.install();

    // Handling of media files:
    SiMedia.install();
    
    // Handling of ajax:
    SiInlineAjax.install();

    // Call document defined scripts:
    if(window.whenDocumentScripted!=null)
      whenDocumentScripted();
  },


  /*
   * initDeferred()
   *   Installations to run deferred.
   */

  initDeferred: function()
  {
    // Google analytics:
    SiTrack.install();

    // Call document defined scripts:
    if(window.whenDocumentComplete!=null)
      whenDocumentComplete();  
  }  
}


/*===========================================================================
 *
 * "Main"
 *
 */

/*
if(DETECT.is_ie)
{
  window.document.onpropertychange = function()
  {
    if('onpropertychange' == window.event.propertyName)
    {
      var f = function()
      {
        if(window.document.body == null)
          setTimeout(f, 1);
        else
          SiLayout.initImmediate();
      };
      setTimeout(f, 1);
    }
  }
}
*/

// Run only for browsers that has a DOM:
if(window.document.getElementsByTagName!=null)
{
  $(document).ready(SiLayout.initImmediate);
  $(window).load(SiLayout.initDeferred);
}

// EoF
