// Connection library: connect.js   beta version 0.6  21 may 2000
//
// Copyright (C) 2000 Jean-Hugues Rety and Robert Kendall
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published 
// by the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public 
// License along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
//
// THIS IS NOT AN OFFICIAL RELEASE BUT A CURRENT WORKING VERSION !!
//
// This library provides JavaScript tools for 
// hypertext literary writing in HTML.
//
// Important: this version is a prototype, functionalities may me 
// modified in future versions.
//
// Documentation: http://www.wordcircuits.com/connect
// e-mail: connection@wordcircuits.com
//

//
// Parameters.
//

// The variable _histCookieName_ below specifies the names of the 
// cookies (long histories may be splitted into several cookies) 
// that contain the historical list of visited nodes.
// In this version, the history can occupy up to 19 cookies with 
// Netscape navigator.
// (note: with very long histories, page loading can become really slow!!). 
// UNFORTUNATELY: Internet Explorer cannot store more than one long cookie!!!
// Each node list recorded into a single cookie can be approximately 
// 4000 char long.
// For an average of 8 char long filenames, this makes a litle bit
// more than 400 nodes per cookie.
// For long hypertexts, use of aliases (see documentation) is recomended.
// This way, the history requires much less space and an history of 
// about 1400 nodes can be stored into one single cookie.

if (typeof(histCookieName) == "undefined")
 var histCookieName = "Hist";

// History is optimized for file names with extension .filesExtension
if (typeof(filesExtension) == "undefined")
 var filesExtension = "html";

 var h_filesExtComp = "." + filesExtension;
 // var h_filesExtComp = "".

// output window.
if (typeof(outputWin) == "undefined")
 var outputWin = window;

// Name of the base directory of the hypertext. 
// To be used if the hypertext's nodes are placed into several directories.
// If one single directory: value is "".
if (typeof(baseDirectory) == "undefined")
 {//var baseDirectory = "";
   var baseDirectory = h_thisDirectory();
 }
if (baseDirectory == "") baseDirectory = "/";

var h_relativeDir = h_relativeDirectory();

// The function executeAfterLoading, if specified, 
// is executed after the library is loaded.
if (typeof(executeAfterLoading) == "undefined")
  var executeAfterLoading = new Function("");
 
// color of links (anchors):
// (if null, then uses the color defined by the navigator or the A.link style)
if (typeof(linkColorDefault) == "undefined")
 var linkColorDefault = null;

// What follows is used to bracket links:
if (typeof(linkPrefixDefault) == "undefined")
 var linkPrefixDefault = "";
if (typeof(linkSuffixDefault) == "undefined")
 var linkSuffixDefault = "";

//color of text written with h_Write: 
if (typeof(annotatedTextColorDefault) == "undefined")
  var annotatedTextColorDefault = null;
// var annotatedTextColorDefault = document.fgColor;
//var annotatedTextColorDefault = document.vlinkColor;

// popup's management

if (typeof(popupTextDefault) == "undefined")
  var popupTextDefault = null;

// shall titles be poped up?
if (typeof(titlePopup) == "undefined")
  var titlePopup = false;

// default text color 
if (typeof(popupTextColorDefault) == "undefined")
  var popupTextColorDefault = null;

// default foreground color 
if (typeof(popupBgColorDefault) == "undefined")
  var popupBgColorDefault = '#FFFFCC';

// default attribute (this is the attribute of a <TABLE> tag) 
if (typeof(popupAttDefault) == "undefined")
  var popupAttDefault = "";

// status bar management

// display on what window? 
if (typeof(statusWin) == "undefined")
 var statusWin = outputWin;

// default value for status Text
if (typeof(statusTextDefault) == "undefined")
  var statusTextDefault = null;

// shall titles be displayed in the status bar?
if (typeof(titleStatus) == "undefined")
  var titleStatus = false;

// Dead links management:

// color of dead links:

if (typeof(deadLinkColor) == "undefined")
//var deadLinkColor = null;
var deadLinkColor = document.fgColor;
//var deadLinkColor = document.linkColor;
//var deadLinkColor = document.vlinkColor;

if (typeof(deadLinkPopup) == "undefined")
  var deadLinkPopup = false;

if (typeof(deadLinkStatus) == "undefined")
  var deadLinkStatus = false;

// appearance of dead links.
// (only used when no popup or status text is to be displayed with the dead link)!!
if (typeof(deadLinkStyle) == "undefined")
 var deadLinkStyle = "text"; // appears as normal text
//var deadLinkStyle = "link"; // appears as links


// The array aliasesArray may contain short identifiers of nodes 
// to be used in the history.
if (typeof(aliasArray) == "undefined")
    aliasArray = new Array ();

// Reverse aliases matching array.
if (typeof(aliasReverseArray) == "undefined")
    aliasReverseArray = new Array ();


// The array titleArray may contain "titles" to be displayed with nodes.
if (typeof(titleArray) == "undefined")
    titleArray = new Array ();

//Not used for the moment
//if (typeof(popupAttArray) == "undefined")
//    popupAttArray = new Array ();

// CSS style definitions
// links and annotated text appearance can also be controled with CSS:

if (typeof(annotatedTextCSS) == "undefined")
  var annotatedTextCSS = "text-decoration: none";

if (typeof(linkCSS) == "undefined")
  var linkCSS = "";

//
// Initializations
//

Array.prototype.expanded=false;

// Maximum length of cookies
var h_maxCookieLength = 4010 - histCookieName.length;

// Window where the connect file is loaded (= this window)
if (typeof(connectWin) == 'undefined')
  var connectWin = window;

// the history !!
var h_nbCookies = Number(h_cookieVal (histCookieName + 'Nb'));
if (h_nbCookies <1)
  h_nbCookies = 1;
if (h_nbCookies >19)
  h_nbCookies = 19;

var h_hist = histValue ();

var h_currHist = h_cookieVal (histCookieName + h_nbCookies.toString() );

var h_histEmpty = (h_hist == "" || h_hist == "*");


// If noHistUpdate=true the history is not updated with the current node.
if (typeof(noHistUpdate) == "undefined")
  addHist();
else
  if (noHistUpdate != true)
    addHist();

// Variables below are below permit to modify parameters "on the fly".
var popupText = popupTextDefault;

var statusText = statusTextDefault;

var popupBgColor = popupBgColorDefault;

var popupAtt = popupAttDefault;

var popupTextColor = popupTextColorDefault;

var linkClass = "link";

var linkColor = linkColorDefault;

var linkPrefix = linkPrefixDefault;

var linkSuffix = linkSuffixDefault;

var annotatedTextColor = annotatedTextColorDefault;

// Internal variable
var h_popnb = 1;

// Internal cookie manipulation functions.


function h_cookieVal (cname) 
// Returns the value of the cookie _cname_.
{

  function h_cookieVal2 (offset) 
  {
  var end = document.cookie.indexOf (";", offset);
  if (end == -1)
    end = document.cookie.length;
  return (document.cookie.substring(offset, end));
  }

var prefix = cname + "=";
var prefix_len = prefix.length;
var cookie_len = document.cookie.length;
var i = 0;
while (i < cookie_len ) 
  {
  var j = i + prefix_len;
  if (document.cookie.substring(i, j) == prefix)
    return h_cookieVal2 (j);
  i = document.cookie.indexOf(" ", i) + 1;
  if (i == 0) break; 
  }
return "";
}

function h_setCookie (cname, value) 
// Sets the cookie _cname_ with value _value_.
{
var arg = h_setCookie.arguments;
var arg_len = h_setCookie.arguments.length;
var expires = (arg_len > 2) ? arg[2] : null;
var path = (arg_len > 3) ? arg[3] : null;
var domain = (arg_len > 4) ? arg[4] : null;
var secure = (arg_len > 5) ? arg[5] : false;
document.cookie = cname + "=" +  value +
   ((expires == null) ? "" : ("; expires=" + expires.toGMTString())) +
   ((path == null) ? "" : ("; path=" + path)) +
   ((domain == null) ? "" : ("; domain=" + domain)) +
   ((secure == true) ? "; secure" : "");
}

function h_deleteCookie (cname) 
// Deletes the cookie _cname_.
{
var expires = new Date ();
expires.setTime (expires.getTime() - 1000000000);  
document.cookie = cname + "=" + "CookieDeleted" + "; expires=" + expires.toGMTString();
}


// Other internal functions


function h_thisFile ()
// Returns the file name of the outputWin window location
{
var url = outputWin.location.href;
var i = url.lastIndexOf (baseDirectory);
return (url.substring (i + baseDirectory.length, url.length));
}

function h_thisDirectory ()
// Returns an URL that corresponds the current directory.
{
var url = outputWin.location.href;
return (url.substring (0,url.lastIndexOf("/")));
}

function h_relativeDirectory ()
// Returns the path between the base directory and the current directory.
{
var url = outputWin.location.href;
return (url.substring (url.lastIndexOf(baseDirectory)+baseDirectory.length +1,url.lastIndexOf("/") +1));
}


function h_show(object) 
// Sets the visibility attribute to visible
{ 
if (outputWin.document.layers && outputWin.document.layers[object]) 
  outputWin.document.layers[object].visibility = 'visible'; 
else if (outputWin.document.all) 
  {
  outputWin.document.all[object].style.visibility = 'visible'; 
  outputWin.document.all[object].style.zIndex = 100;
  }
}

function h_hide(object) 
// Sets the visibility attribute to hidden
{ 
if (outputWin.document.layers && outputWin.document.layers[object]) 
  outputWin.document.layers[object].visibility = 'hidden'; 
else if (outputWin.document.all) 
  outputWin.document.all[object].style.visibility = 'hidden'; 
}

function h_member (ident, str)
// Returns true if _ident_ belongs to the string _str_.
{
return (str.indexOf(ident) != -1);
}


function h_expand (args)
// Returns an array that contains a list of atomic arguments.
{
if (typeof(args.expanded) != 'undefined' && args.expanded == true)
  return(args);
var ind=0;
var argArray = new Array ();
for (var i=0 ; i<args.length ; i++)
  {
  if (args[i]!=null &&  typeof (args[i].sort) == "function")
    {
    var tmpArray = h_expand(args[i]); 
    for (var j=0 ; j<tmpArray.length ; j++)
      argArray[ind++]=tmpArray[j];
    }
  else argArray[ind++]=args[i];
  }
argArray.expanded=true;
return (argArray);
}


function h_makeArg (args, fromInd)
// Returns an array that contains a list of atomic arguments.
{
var ind=0;
var argArray = new Array ();
for (var i=fromInd ; i<args.length ; i++)
  {
  if (args[i]!=null && typeof (args[i].sort) == "function")
    {
    var tmpArray = h_expand(args[i]); 
    for (var j=0 ; j<tmpArray.length ; j++)
      argArray[ind++]=tmpArray[j];
    }
  else argArray[ind++]=args[i];
  }
argArray.expanded=true;
return (argArray);
}

// Functions for identifiers manipulation


function makeIdent (lfile)
// Produces the identifier to be used in the history.
{
if (lfile.substring(lfile.lastIndexOf('/'),lfile.length).indexOf('.') == -1)
  lfile += h_filesExtComp;
if (lfile.indexOf(':/') != -1) 
  {if (lfile.indexOf(baseDirectory) != -1) 
     var file = lfile.substring(lfile.lastIndexOf(baseDirectory) + baseDirectory.length + 1, lfile.length);
   else 
     var file = lfile;
  }
else 
 {if (lfile.charAt(0) == '/') 
   var file = lfile.substring(1,lfile.length);
 else
   var file = h_relativeDir + lfile;}
if (typeof(aliasArray[file]) != "undefined")
  return (aliasArray[file]);
//else
var dot = file.lastIndexOf ('.');
if (dot == -1) return (file + '?');
var suffix = file.substring (dot +1, file.length);
var prefix = file.substring (0, dot);
if (suffix == filesExtension) return (prefix);
//if (suffix == "htm")  return (prefix + '/');
return (file + '?');
}

function makeFileName (ident)
// Returns the file name corresponding to _ident_.
{
if (typeof(aliasReverseArray[ident]) != "undefined")
  return (aliasReverseArray[ident]);
//else
var end = ident.length -1;
var type_ext = ident.charAt (end);
if (type_ext == "?") return (ident.substring(0, end));
//if (type_ext == "/") return (ident.substring(0, end) + ".htm");
return (ident + "." + filesExtension);
}


// Functions for history manipulation


function setCurrHist (value)
// Sets the history to value _value_.
{
var cname = histCookieName + h_nbCookies.toString();
var exp_date = new Date (); 
exp_date.setTime(exp_date.getTime() + 31536000000); 
   //(+ 24 * 60 * 60 * 1000 * 365) = 1 year after
h_setCookie (cname, value, exp_date);  
return false;
}

function histValue ()
// Returns the value of the history.
{
var hist = "*";
for (var i = 1; i <= h_nbCookies; i++)
  hist = hist + h_cookieVal ( histCookieName + i.toString() );
return hist;
}

function histArray ()
// Returns an array containing the history (array of file names).
{
h = new Array ();
var ind = 0;
var i = 0;
var j = h_hist.indexOf ('*', i+1);
while (j != -1)
  {
  h[ind] = makeFileName ( h_hist.substring( i+1, j));
  ind++;
  i = j;
  j = h_hist.indexOf ('*', i+1);
  }
return (h);
}


// End user functions for text or node (or whatever) specification.
// These functions can be used for conditions specification.


function select (args)
{
var arg = h_expand(select.arguments);
var mode = arg[0];
if (mode == "random") 
  {var arg2 = h_makeArg(arg, 1);  
  return (random (arg2));
  }
if (mode == "oldest")
  {var arg2 = h_makeArg(arg, 1);
   return (oldest(arg2));
   }
if (mode == "filteredRandom")
  {var arg2 = h_makeArg(arg, 3);
   return (filteredRandom (arg[1], arg[2], arg2));
  }
if (mode == "filteredFirst")
  {var arg2 = h_makeArg(arg, 3);
   return (filteredFirst (arg[1], arg[2], arg2));
  }
if (mode == "filteredOldest")
  {var arg2 = h_makeArg(arg, 3);
   return (filteredOldest (arg[1], arg[2], arg2));
  }
if (mode == "conditionalRandom")
  {var arg2 = h_makeArg(arg, 1);
   return (conditionalRandom (arg2));
  }
if (mode == "conditionalFirst")
  {var arg2 = h_makeArg(arg, 1);
   return (conditionalFirst (arg2));
  }
}

function filter(condFunct, items)
// returns an array that contains the items such that condFunct(item) is true.
{
var arg = h_expand(filter.arguments);
var arg_len = arg.length;
var arg_condFunct = arg[0];
nv = new Array ();
var nbnv = 0;
for (var i = 1 ; i < arg_len ; i++)
  if (arg_condFunct(arg[i])) 
    nv[nbnv++] = arg[i];
return(nv);
}


function conditionalFilter(itemsAndConds)
// returns an array that contains the items such that condFunct(item) is true.
{
var arg = h_expand(conditionalFilter.arguments);
var arg_len = arg.length;
nv = new Array ();
var nbnv = 0;
for (var i = 0 ; i < arg_len ; i += 2)
  if (arg[i+1]) 
    nv[nbnv++] = arg[i];
return(nv);
}


function nRandom (nbi,items)
// Randomly selects n items in _items_. Returns an array. 
{
var arg = h_expand(nRandom.arguments);
var arg_len = arg.length;
var nb = arg[0];
if (nb > arg_len-1)
  nb = arg_len-1;
var randArray = new Array();
var resultArray = new Array();
var ind = 0;
for (var i=0;i<arg_len-1;i++) 
  randArray[i]=i;
for (var i=0;i<nb;i++)
  {
  var select = Math.floor (Math.random () * (randArray.length) );
  if (select == randArray.length) select--;
  resultArray[ind]=arg[randArray[select]+1];
  ind++;
  randArray[select]=randArray[randArray.length-1];
  randArray.length--
  }
return (resultArray);
}


function random (items)
// Randomly selects an item in _items_. 
{
var arg = h_expand(random.arguments);
var arg_len = arg.length;
var select = Math.floor (Math.random () * (arg_len) );
if (select == arg_len) select = arg_len - 1;
return (arg[select]);
}

function oldest (nodes)
// Returns the oldest node in _nodes_
{
var arg = h_expand(oldest.arguments);
var arg_len = arg.length;
var oldest_val = h_hist.length;
var oldest_node = null;
for (var i=0 ; i<arg_len ; i++)   
  {
  var ident = makeIdent(arg[i]);
  var j = h_hist.lastIndexOf('*' + ident + '*')
    if (j < oldest_val)
      {
      oldest_val = j;
      oldest_node = arg[i];
      } 
  }
return (oldest_node);
}


function filteredRandom (condFunct, otherwiseItem, items)
// condFunct is a JavaScript function.
// Randomly selects an item _itemi_  such that _condFunct(itemi)_ holds.
// If no condition is satisfied then returns _otherwiseItem_.
{
var arg = h_expand (filteredRandom.arguments);
var condFunct = arg[0];
var arg2 = h_makeArg(arg,2);
var arr = filter(condFunct, arg2);
if (arr.length ==0 ) return (arg[1]);
else
  {
  var select = Math.ceil (Math.random () * (arr.length) );
  if (select == 0) select = 1;
  return (arr[select -1]);
  }
}


function filteredFirst (condFunct, otherwiseItem, items)
// condFunct is a JavaScript function.
// Returns the first item _itemi_ such that _condFunct(itemi)_ holds.
// If no condition is satisfied then returns _otherwiseItem_.
{
var arg = h_expand( filteredFirst.arguments);
var arg_condFunct = arg[0];
var arg_len = arg.length;
for (var i=2 ; i<arg_len ; i++)
  if ( arg_condFunct (arg[i]) )
    return (arg[i]);
return (arg[1]);
}


function filteredOldest (condFunct, otherwiseNode, nodes)
// condFunct is a JavaScript function.
// Returns the oldest (wrt its last visit) node that satisfies condFunct.
// (By default: non visited nodes are oldest than all other nodes.)
// If no node satisfies condFunct, then returns otherwiseNode.
{
var arg = h_expand(filteredOldest.arguments);
var condFunct = arg[0];
var arg2 = h_makeArg (filteredOldest.arguments, 2);
arr = filter(condFunct, arg2);
if (arr.length ==0 ) return (arg[1]);
else
  {
  var oldest_val = h_hist.length;
  var oldest_node = null;
  for (var i=0 ; i<arr.length ; i++)   
   {
   var ident = makeIdent(arr[i]);
   var j = h_hist.lastIndexOf('*' + ident + '*')
     if (j < oldest_val)
       {
       oldest_val = j;
       oldest_node = arr[i];
       }
   }
   return (oldest_node);
  }
}



function conditionalRandom (otherwiseItem, itemsAndConds)
// Randomly selects an item _itemi_  such that _condi_ holds.
// If no condition is satisfied then returns _otherwiseItem_.
{
var arg = h_expand(conditionalRandom.arguments);
var arg_len = arg.length;
nv = new Array ();
var nbnv = 0;
for (var i = 1 ; i < arg_len ; i=i+2)
  if (arg[i+1]) 
    {
    nv[nbnv] = i;
    nbnv++;
    }
if (nbnv ==0) return (arg[0]);
else
  {
  var select = Math.ceil (Math.random () * (nbnv) );
  if (select == 0) select = 1;
  return (arg[ nv[select -1] ]);
  }
}


function conditionalFirst (otherwiseItem, itemsAndConds)
// _itemsAndConds_ is of the form:
//     item1, cond1, item2, cond2, item3, cond3,...
// items can be file names or text, or whatever.
// Returns the first item _itemi_ such that _condi_ holds.
// If no condition is satisfied then returns _otherwiseItem_.
{
var arg = h_expand(conditionalFirst.arguments);
var arg_len = arg.length;
for (var i=1 ; i<arg_len ; i=i+2)
  if ( arg[i+1] )
    return (arg[i]);
return (arg[0]);
}



// End user functions for node specification.
// These functions can also be used inside conditions.


function hist (rank)
// Returns the node (file name) element of rank _rank_ in the history.
// If _rank_ = 0, Hist(rank) returns the current node's file name. 
// If _rank_ is negative, the rank is counted in reverse from the 
// last element in the history.
{
if (rank > 0) 
  {
  var end = h_hist.length-1;
  var j = 0;
  var k = h_hist.indexOf ('*', j +1);
  for (var i=rank ; i>1 ; i--)
    {
    if (j >= end) break;
    j = k;
    k = h_hist.indexOf ('*', j +1);
    }
  if (j >= end) return (null);
  var ident = h_hist.substring (j+1, k);
  return (makeFileName (ident) );
  }
if (rank <= 0) 
  {
  var j = h_hist.length -1;
  var k = h_hist.lastIndexOf ('*', j-1);
  for (var i=rank ; i<0 ; i++)
    {
    if (j <= 0) break;
    j = k;
    k = h_hist.lastIndexOf ('*', j-1);
    }
  if (j <= 0) return (null);
  var ident = h_hist.substring (k+1, j);
  return (makeFileName (ident) );
  }
}

function histRank (node)
// Returns an array containing the indices where the node
// _node_ occurs in the history.
{
var ident = makeIdent (node);
indarray = new Array ();
var i = 0;  var j = 0; var ind = 1;
var k = h_hist.indexOf ('*', j +1);
while (k != -1)
  {
  if (h_hist.substring (j+1, k) == ident)
    {
    indarray[i] = ind;
    i++;
    }
  j = k;
  k = h_hist.indexOf ('*', j +1);
  ind++;
  }
return (indarray);
}

function earliest (nodes)
// Returns the node (file name) in _nodes_ that was visited first.
// Returns null if none of _nodes_ were visited.
{
var arg = h_expand(earliest.arguments);
var arg_len = arg.length;
var max_val = h_hist.length;
var max_node = null;
for (var i=0 ; i<arg_len ; i++)
  {   
  var ident = makeIdent (arg[i]);
  var j = h_hist.indexOf ('*' + ident + '*');
  if (j != -1 && j < max_val)
    {
    max_val = j;
    max_node = arg[i];
    }
  }
return (max_node);
}

function latest (nodes)
// Returns the node (file name) in _nodes_ that was visited last.
// Returns null if none of _nodes_ were visited.
{
var arg = h_expand (latest.arguments);
var arg_len = arg.length;
var max_val = -1;
var max_node = null;
for (var i=0 ; i<arg_len ; i++)   
  {
  var ident = makeIdent(arg[i]);
  var j = h_hist.lastIndexOf('*' + ident + '*')
    if (j > max_val)
      {
      max_val = j;
      max_node = arg[i];
      }
  }
return (max_node);
}


// Other end user functions for condition building.


function allVisited (nodes)
// Returns true if all nodes in _nodes_ belong to the history.
{
var arg = h_expand (allVisited.arguments);
var arg_len = arg.length;
for (var i=0 ; i<arg_len ; i++)
  if ( ! h_member('*' + makeIdent(arg[i]) + '*', h_hist) ) 
    return (false);
return (true);
}

function someVisited (nodes)
// Returns true if at least one node in _nodes_ belongs to the history.
{
var arg = h_expand (someVisited.arguments);
var arg_len = arg.length;
for (var i=0 ; i<arg_len ; i++)
  if ( h_member('*' + makeIdent (arg[i]) + '*', h_hist) ) 
    return (true);
return (false);
}

function visited (node)
{
return ( allVisited(node) );
}

function notVisited (node)
{
return ( ! someVisited (node) );
}

function visitedFrom (node)
// Returns true if _node_ was visited immediately after the current node.
// if more than 1 arguments, tests whether _node_ was visited  
// immediately after one (at least) of the other arguments.
{
var arg = h_expand (visitedFrom.arguments);
var arg_len = arg.length;
var offset = "*" + makeIdent (node) + "*";
if (arg_len == 1)
  {
  offset = "*" + makeIdent ( h_thisFile() ) + offset;
  return ( h_member (offset, h_hist) );
  }
else
  {
  for (var i=1 ; i<arg_len ; i++)
    {
    var offset2 = "*" + makeIdent (arg[i]) + offset;
    if ( h_member(offset2, h_hist) ) return (true);
    }
  return (false);
  }
}

function visitedNotFrom (node)
// Boolean function.
// Returns true if _node_ was visited immediately after a node not in _nodes_.
// Returns false otherwise. i.e., if every time _node_ was visited,
// it was visited from one node in _nodes_.
// Notice that if _node_ was not visited then the function returns false. 
{
var arg = h_expand (visitedNotFrom.arguments);
var arg_len = arg.length;
var arg_list = "*";
if (arg_len == 1)
  {
  arg[1] = h_thisFile();
  arg_len =2;
  }
for (var i=1 ; i<arg_len ; i++)
  arg_list = arg_list + makeIdent(arg[i]) + "*";
var offset = "*" + makeIdent (arg[0]) + "*";
var indsup = h_hist.indexOf("*");
indsup = h_hist.indexOf(offset, indsup +1);
while (indsup != -1)
  {
  var indinf = h_hist.lastIndexOf("*", indsup -1);
  if ( ! h_member (h_hist.substring(indinf, indsup +1), arg_list))
    return (true);
  indsup = h_hist.indexOf(offset, indsup +1);
  }
return (false);
}

function visitedOnlyFrom(filename, args)
{
var arg = h_expand (visitedOnlyFrom.arguments);
return ( visitedFrom(arg) && ! visitedNotFrom(arg));
}

function allLinked (frameArg, nodes)
// Returns true if all nodes in _nodes_ are linked above in the 
// frame _frameArg_. _frameArg_ can be omitted (default = current window).
{
var arg = h_expand(allLinked.arguments);
if (typeof (arg[0].location) == "undefined")
  {var frame = outputWin;
   var ind = 0;
  }
else
  {var frame = arg[0];
   var ind = 1;
  }
var arg_len = arg.length;
var nb_links = frame.document.links.length;
var l_list = "*";
for (var i=0 ; i<nb_links ; i++)
  l_list = l_list + makeIdent(frame.document.links[i].toString()) + "*";
for (var i=ind ; i<arg_len ; i++)
  if ( ! h_member('*' + makeIdent (arg[i]) + '*', l_list) ) 
    return (false);
return (true);
}

function someLinked (frameArg, nodes)
// Returns true if at least one nodes in _nodes_ is linked above 
// in the frame. _frameArg_ can be omitted.
{
var arg = h_expand(someLinked.arguments);
if (typeof (arg[0].location) == "undefined")
  {var frame = outputWin;
   var ind = 0;
  }
else
  {var frame = arg[0];
   var ind = 1;
  }
var arg_len = arg.length;
var nb_links = frame.document.links.length;
var l_list = "*";
for (var i=0 ; i<nb_links ; i++) 
  l_list = l_list + makeIdent(frame.document.links[i].toString()) + "*";
for (var i=ind ; i<arg_len ; i++)
  if ( h_member('*' + makeIdent(arg[i]) + '*', l_list) ) 
    return (true);
return (false);
}

function linked (node)
{
return ( allLinked (node) );
}

function notLinked (node)
{
return ( ! someLinked (node ));
}

function notVisitedAndNotLinked (node)
{
return ( ! (someVisited (node) || someLinked (node) ) );
}

function allLinksFollowed (frameArg)
// Returns true if all links in the document belong to the history.
{
if (allLinksFollowed.arguments.length == 0)
  var frame = outputWin;
else
  var frame = frameArg;
var nb_links = frame.document.links.length;
for (var i=0 ; i<nb_links ; i++)
  {
  var ident = makeIdent (frame.document.links[i].toString());
  if ( ! h_member('*' + ident + '*', h_hist) ) 
    return (false);
  }
return (true);
}

function nbVisits (nodes)
// Returns how many times the nodes _nodes_ were already visited.
// if no argument, then returns the number of nodes already visited, 
// i.e., the length of the history.
{
var arg = h_expand(nbVisits.arguments);
var arg_len = arg.length;
var nb_visits = 0;
if (arg_len == 0)
  {
  var i = h_hist.indexOf ('*');
  i = h_hist.indexOf ('*', i+1);
  while ( i != -1)
    {
    i = h_hist.indexOf ('*', i+1);
    nb_visits++;
    }
  }
else
  {
  for (var j=0 ; j<arg_len ; j++)
    {
    var ident = makeIdent (arg[j]);
    var i = h_hist.indexOf ('*' + ident + '*');
    while (i != -1)
      {
      nb_visits++;
      i = h_hist.indexOf ('*' + ident + '*',i+1);
      }
    }
  }
return (nb_visits);
}
        
function nbVisited (nodes)
// Returns how many nodes in _nodes_ were visited yet.
// if called with no argument, then returns the number of 
// different nodes already visited.
{
var arg = h_expand(nbVisited.arguments);
var arg_len = arg.length;
var nbnodes = 0;
if (arg_len == 0)
  {
  var i = 0;
  while ( i < h_hist.length)
    {
    var next = h_hist.indexOf ('*', i+1);
    if (next == -1) break;
    var h_ident = h_hist.substring (i, next +1);
    if (h_hist.indexOf(h_ident) == i)
      nbnodes++;
    i = next;
    }
  }
else
  {
  for (var i=0 ; i<arg_len ; i++)
    {
    if (typeof (arg[i]) == "string") {
      var ident = makeIdent (arg[i]);
      if (h_member('*' + ident + '*', h_hist)) 
        nbnodes++;
      }
    else
      nbnodes = nbnodes + arg[i].nbNodesVisited();
    }
  }
return (nbnodes);
}

function age(node)
// Returns the "age" of a node.
{
  var ident = makeIdent(node);
  var i = h_hist.lastIndexOf('*' + ident + '*');
  if (i != -1) i += ident.length + 1;
  var age = -1;
  while (i != -1)
    {
    i = h_hist.indexOf('*',i+1);
    age++;
    }
  return (age);
}

function nbLinks (frameArg)
// Returns the number of links in the document.
{
if (nbLinks.arguments.length == 0)
  var frame = outputWin;
else
  var frame = frameArg;
return (frame.document.links.length);
}

function nbLinksNotFollowed (frameArg)
// returns how many links in the document lead to non-visited nodes.
{
if (nbLinksNotFollowed.arguments.length == 0)
  var frame = outputWin;
else
  var frame = frameArg;
var nb_links = frame.document.links.length;
var nb = 0;
for (var i=0 ; i<nb_links ; i++)
  {
  var ident = makeIdent (frame.document.links[i].toString());
  if ( ! h_member('*' + ident + '*', h_hist) ) 
    nb++;
  }
return (nb);
}


// End-user functions for history munipulation


function initHist (node)
// Initialises the history with the current node 
// (or with _node_ if specified).
{
h_setCookie (histCookieName + "Nb","1");
h_nbCookies = 1;
if (initHist.arguments.length == 0)
  h_currHist = makeIdent( h_thisFile() ) + '*';  
else
  h_currHist = makeIdent(node) + '*';  
setCurrHist (h_currHist);
h_hist = "*" + h_currHist;
}

function addHist (node)
// Adds the current node (or the node _node_ if specified) to the history.
{
if (addHist.arguments.length == 0)
  ident = makeIdent( h_thisFile() ) + '*';
else
  ident = makeIdent(node) + '*';
h_hist = h_hist + ident;
if (h_currHist.length + ident.length > h_maxCookieLength)
  {
  h_nbCookies = h_nbCookies + 1;
  if (h_nbCookies > 19)
    alert('maximum history length exceeded: the history may be truncated and result in unexpected behavior');
  h_setCookie(histCookieName + 'Nb',h_nbCookies.toString());
  h_currHist = ident;
  }
else
  h_currHist = h_currHist + ident;
setCurrHist (h_currHist);
}

function deleteHist ()
// Deletes the history.
{
for (var i=1; i <= h_nbCookies ; i++)
  h_deleteCookie (histCookieName + i.toString());
h_setCookie (histCookieName + "Nb","1");
h_nbCookies = 1;
h_currHist = "";
h_hist = "*";
}

function histEmpty ()
// Returns true if the history is empty.
{
return (h_histEmpty);
}


// End-user functions for writing and linking


function condJSCode (yesJavaScriptCode, cond, noJavaScriptCode)
// Conditional JavaScript code.
// _yesJavaScriptCode_ is  executed if _cond_ holds.
// _noJavaScriptCode_, if specified,  is executed if _cond_ does not hold.
{
if (cond)
  h_write("<SCRIPT type='text/javascript'>" +  yesJavaScriptCode + "</SCRIPT>");
else
  if ( typeof(noJavaScriptCode) == "string" )
    h_write("<SCRIPT type='text/javascript'>" + noJavaScriptCode + "</SCRIPT>");
}

function display (yesText, cond, noText)
// Displays conditional text, 
// possibly with popup or/and status text if specified
// (in this case, the text is displayed as a link with void target
// and color _annotatedTextColor_).
{
if (display.arguments.length == 1 || cond)
  h_display (yesText);
else
  if ( typeof(noText) == "string" )
    h_display (noText);

annotatedTextColor = annotatedTextColorDefault;
popupText = popupTextDefault;
statusText = statusTextDefault;
popupBgColor = popupBgColorDefault;
popupAtt = popupAttDefault;
popuptextColor = popupTextColorDefault;
linkPrefix = linkPrefixDefault;
linkSuffix = linkSuffixDefault;
linkClass = "link";
}

function h_display (text)
// Displays text, possibly with popup or/and status text if specified.
{
if (popupText == null && statusText == null)
  h_write (text);
else
  {
  if (statusText == null)
    statusText = " ";
  linkColor = annotatedTextColor;
  linkClass = "annotatedText";
  h_link("javascript:void(0)", text);
  linkColor = linkColorDefault;
  }
}


function h_write (text)
// Writes text on the outputWin window
{
outputWin.document.write (text);
}


function h_link (node, text)
// Creates a link to file _node_ with text _text_.
{
 function h_makeUrl(nnode)
 {if (nnode == 'javascript:void(0)')
    return (nnode);
  if (nnode.substring(nnode.lastIndexOf('/'),nnode.length).indexOf('.') == -1)
    nnode += h_filesExtComp;
  if (nnode.charAt(0) == '/')
   if (baseDirectory != '/') 
     return(baseDirectory + nnode);
   else
     return(nnode.substring(1,nnode.length));
  else
    return(h_thisDirectory() + '/' + nnode);
 }

if (node == null)
  {
  if (deadLinkPopup == false) 
    popupText = null;
  else
    if (popupText == null && titlePopup == true)
      if (typeof(titleArray['deadLink']) != "undefined")
        popupText = titleArray['deadLink'];
  if (deadLinkStatus == false)
    statusText = null;
  else
    if (statusText == null && titleStatus == true)
      if (typeof(titleArray['deadLink']) != "undefined")
        statusText = titleArray['deadLink'];
  if (popupText == null && statusText == null)
    {
    if (deadLinkStyle == "link")
      {var Btag = "<U>"; var Etag = "</U>";}
    else
      {var Btag = ""; var Etag = "";}
  if (deadLinkColor != null)
    {
    var begin_font_tag = '<FONT color="' + deadLinkColor + '">';
    var end_font_tag = "</FONT>";
    }
  else
    {
    var begin_font_tag = "";
    var end_font_tag = "";
    }
    h_write (begin_font_tag + Btag + text + Etag + end_font_tag);
    return (true);
    }
  node = "javascript:void(0)";
  linkColor = deadLinkColor;
  if (deadLinkStyle != "link")
    linkClass = "text";
  }
else
  {
  if (popupText == null && titlePopup == true)
    if (typeof(titleArray[node]) != "undefined")
      popupText = titleArray[node];
  if (statusText == null && titleStatus == true)
    if (typeof(titleArray[node]) != "undefined")
      statusText = titleArray[node];
  }

if (linkColor != null)
  {
  var begin_font_tag = '<FONT color="' + linkColor + '">';
  var end_font_tag = "</FONT>";
  }
else
  {
  var begin_font_tag = "";
  var end_font_tag = "";
  }

h_write (linkPrefix + '<A href=' + h_makeUrl(node));
if (linkClass != null)
  h_write(" class='" + linkClass + "'");
if (statusText != null || popupText != null)
  h_write(" onMouseOver='");
if (statusText != null)
  h_write("statusWin.status=" + '"' + statusText + '";');
if (popupText != null)
  h_write("h_show(" + '"pop' + h_popnb.toString() + '"' + ");");
if (statusText != null || popupText != null)
  h_write(" return true' onMouseOut='");
if (statusText != null)
  h_write('statusWin.status="";');
if (popupText != null)
  h_write ("h_hide(" + '"pop' + h_popnb.toString() + '"' + ");");
if (statusText != null || popupText != null)
  h_write(" return true'");
h_write (">" + begin_font_tag + text + end_font_tag + '</A>' + linkSuffix);
if (popupText != null)
  {
if (popupTextColor != null)
  {
  var begin_font_popup = '<FONT color="' + popupTextColor + '">';
  var end_font_popup = "</FONT>";
  }
else
  {
  var begin_font_popup = "";
  var end_font_popup = "";
  }
  h_write("<SPAN id=" + '"pop' + h_popnb.toString() + '"' + " CLASS='popup'><TABLE " + popupAtt + " bgcolor='" + popupBgColor + "'><TR><TD>" + begin_font_popup + popupText + end_font_popup + "</TD></TR></TABLE></SPAN>");  
  h_popnb++;
  }
linkColor = linkColorDefault;
linkClass = "link";
return (true);
}    

function link(node, text, cond, noNode, noText)
// If called with exactly 2 arguments then creates a link.
// If called with more than 2 arguments, then creates a 
// link to node _node_ with text _text_ if _cond_ holds.
// If _cond_ does not hold then, if _noText_ is specified 
// then creates a link to _noNode_ with text _noText_
// else, creates a link to _noNode_ with text _text_.
{
var arg_len = link.arguments.length;
if (arg_len == 2)
  h_link (node, text);
if (arg_len > 2)
  {
  if (cond)
    h_link (node, text);
  else 
    {
    if ( typeof(noText) != "undefined" )
      text = noText;
    if ( typeof(noNode) !="undefined" )
      h_link (noNode, text);
    }
  }

linkColor = linkColorDefault;
popupText = popupTextDefault;
statusText = statusTextDefault;
popupBgColor = popupBgColorDefault;
popupAtt = popupAttDefault;
popupTextColor = popupTextColorDefault;
linkPrefix = linkPrefixDefault;
linkSuffix = linkSuffixDefault;
linkClass = "link";
}


//
// Style definitions
//

// Style A.link: 
h_write("<style type='text/css'>A.link {" + linkCSS + "}</style>");

// Style A.annotatedText:
h_write("<style type='text/css'>A.annotatedText {" + annotatedTextCSS + "}</style>");

// Style A.text:
var textCSS = "text-decoration: none";
h_write("<style type='text/css'>A.text {" + textCSS + "}</style>");

// Style SPAN.popup
var popupCSS = "position: absolute; visibility: hidden;";
h_write("<style type='text/css'>SPAN.popup {"+ popupCSS + "}</style>");

executeAfterLoading();
























