[ Index ]

PHP Cross Reference of Moodle 1.9.3 [Build 15-Oct-2008]

title

Body

[close]

/mod/hotpot/ -> hotpot-full.js (source)

   1  <!--
   2  // PLEASE NOTE that this version is more recent than the incorrectly
   3  // numbered v6.1, dated 2003.11.17. From now on, version numbers will
   4  // follow those of Hot Potatoes.
   5  /* hot-potatoes.js (v6.0.4.0 - 2005.02.18)
   6   * =======================================
   7   * by Gordon Bateson, February 2003
   8   * Copyright (c) 2003 Gordon Bateson. All Rights Reserved.
   9   *
  10   * You are hereby granted a royalty free license to use or modify this
  11   * software provided that this copyright notice appears on all copies.
  12   *
  13   * This software is provided "AS IS" without a warranty of any kind.
  14   *
  15   * Documentation and downloads may be available from:
  16   * http://www.kanazawa-gu.ac.jp/~gordon/research/hot-potatoes/
  17   */
  18  // This JavaScript library modifies the SendResults and StartUp functions
  19  // used by hotpot v5 and v6, so that more (or less!) details about the
  20  // student can be input, and more details of a quiz's questions and answers
  21  // can be submitted to the server when the quiz is finished
  22  // If the arrays below (Login, DB, JBC, ...) are set BEFORE calling this
  23  // script, they will NOT be overwritten. Any array that is not set, will
  24  // use the defaults below. This is useful if you want to use different
  25  // settings for different quizzes.
  26  // ************
  27  //  Login Screen
  28  // ************
  29  if (window.Login==null) {
  30      Login = new Array();
  31      Login[0] = true;    // Show prompt for user name
  32                  // This can also be a string of user names ...
  33                  // Login[0] = "Guest,Peter,Paul,Mary,Webmaster";
  34                  // or an array of user names (and on-screen texts) (and passwords) ...
  35                  // Login[0] = new Array("Guest", "001,Peter,xxxx", "002,Paul,yyyy", "003,Mary,zzzz", "Webmaster");
  36                  // and can also be  written as ...
  37                  // Login[0] = new Array(
  38                  //    new Array("Guest"),
  39                  //    new Array("001", "Peter", "xxxx"),
  40                  //    new Array("002", "Paul", "yyyy"),
  41                  //    new Array("003", "Mary", "zzzz"),
  42                  //    new Array("Webmaster")
  43                  // );
  44      Login[1] = true;    // Show prompt for student's UserID
  45                  // If there is no password prompt (i.e. Logon[3] is false), this value
  46                  // will be checked against the password information, if any, in Login[0]
  47      Login[2] = false;    // Show prompt for student's email
  48      Login[3] = false;    // Show prompt for quiz password, and check this value against
  49                  // the password information, if any, in Login[0]
  50                  // This can also be a string required to start the quiz ...
  51                  // Login[3] = "password";
  52      Login[4] = true;    // Show prompt for the cookie expiry date
  53                  // If false, cookies expire at the end of the current session
  54      Login[5] = "guest,webmaster"
  55                  // guest user names (case insensitive) ...
  56                  // Login[5] = "guest,webmaster";
  57                  // These users do NOT need to fill in other login fields
  58                  // and their quiz results are NOT added to the database
  59      // the Login prompts and error messages
  60      // are defined in the MSG array (see below)
  61  }
  62  // *********
  63  //  Database (for use with BFormMail)
  64  // *********
  65  if (window.DB==null) {
  66      DB = new Array();
  67      DB[0] = true; // append form fields to database on server
  68              // If you are NOT using BFormMail's database feature,
  69              // set DB[0]=false, and you can then safely ignore DB[1 to 5]
  70      DB[1] = "/home/gordon/public_html/cgi/hot-potatoes-data";
  71              // append_db folder path (no trailing slash)
  72              // Can be either an absolute path  e.g. "/home/gordon/public_html/cgi/hot-potatoes-data"
  73              // or a relative (to CGI bin) path  e.g. "hot-potatoes-data"
  74      DB[2] = "hot-potatoes";
  75              // append_db file name (no extension)
  76              // If left blank, the quiz file name, without extension, will be used
  77              // i.e. each quiz will have its results stored in a different file.
  78              // If filled in, this file will store the results for ALL quizzes.
  79              // Database files and folders must be set up BEFORE running the quiz
  80              // must have appropriate access privileges (on Unix, use "chmod 666").
  81      DB[3] = ""; // append_db extension (if left blank, ".txt" will be used)
  82      DB[4] = ""; // db_fields (if left blank, ALL quiz fields will be sent)
  83      DB[5] = ""; // db_delimiter (if left blank, tab will be used)
  84      DB[6] = "REMOTE_ADDR,HTTP_USER_AGENT";
  85              // env_report ('REMOTE_ADDR','HTTP_USER_AGENT' and a few others)
  86      // for a complete description of these fields are, see ...
  87      // http://www.infosheet.com/stuff/BFormMail.readme
  88      // Switches DB[7] and DB[8] force the settings in the ResultForm
  89      // In v5 and v6 quizzes, these settings wil be override those in the original quiz
  90      // If the quiz results are to be sent to an LMS (via the "store" form)
  91      // then switches DB[7] and DB[8] are not used
  92      DB[7] = '';    // URL of form processing script
  93              // e.g. http://www.kanazawa-gu.ac.jp/~gordon/cgi/bformmail.cgi
  94      DB[8] = '';    // email address to which results should be sent
  95              // e.g. gordon@kanazawa-gu.ac.jp
  96  }
  97  // By default the quiz's question's scores will be returned.
  98  // If you want more detailed information, set the flags below:
  99  // ********
 100  //  JBC
 101  // ********
 102  if (window.JBC==null) {
 103      JBC = new Array();
 104      JBC[0] = true;    // show separator line between answers on email
 105      JBC[1] = true;    // show number of attempts to answer question
 106      JBC[2] = true;    // show question texts
 107      JBC[3] = true;    // show right answer(s)
 108      JBC[4] = true;    // show wrong answer(s)
 109      JBC[5] = true;    // show ignored answer(s)
 110      JBC[6] = false;    // show answer as text (false) or number (true)
 111  }
 112  // JBC quizzes use the global variables 'I' and 'Status'
 113  // I : an array of JBC_QUESTIONs (one for each question)
 114  // JBC_QUESTION :
 115  //    [0] : question text
 116  //    [1] : array of JBC_ANSWERs (one for each answer)
 117  //    [2] : single/multi flag
 118  //        0 : single answer (using 'button')
 119  //        1 : multiple answers (using 'checkbox')
 120  // JBC_ANSWER :
 121  //    [0] : answer text
 122  //    [1] : answer feedback
 123  //    [2] : correct answer flag
 124  //        0 : this is NOT the correct answer
 125  //        1 : this is the correct answer
 126  // Status : an array of JBC_QUESTION_STATUSes
 127  // JBC_QUESTION_STATUS:
 128  //    [0] : correctly answered yet flag
 129  //        0 : this question has NOT been correctly answered
 130  //        1 : this question has been correctly answered
 131  //    [1] : array of JBC_ANSWER_STATUSes (one for each answer)
 132  //        '0' : initial value
 133  //        'R' : single answer question was answered 'R'ight
 134  //        'W' : single answer question was answered 'W'rong
 135  //        'C' : multiple answer question's checkbox was 'C'hecked
 136  //        'U' : multiple answer question's checkbox was 'U'nchecked
 137  //    [2] : number of times this question has been wrongly answered
 138  //    [3] : score (out of 1) for this question (maybe undefined on HP<5.5)
 139  //        0 : not correct yet
 140  //        0<[3]<1 : correct but only after [2] wrong attempts
 141  //        1 : correct first time (bravo!)
 142  //    N.B. score = (numberOfAnswers - numberofWrongTries) / numberOfAnswers
 143  // ********
 144  //  JCloze
 145  // ********
 146  if (window.JCloze==null) {
 147      JCloze = new Array();
 148      JCloze[0] = true;    // show separator line between answers on email
 149      JCloze[1] = true;    // show student's correct answer
 150      JCloze[2] = true;    // show other correct answer(s), if any
 151      JCloze[3] = true;    // show wrong answer(s), if any (NOT available for v5)
 152      JCloze[4] = false;    // show number of hints + checks (legacy field, replaced by [7]+[9])
 153      JCloze[5] = false;    // show if clue was asked for or not (legacy field, replaced by [8])
 154      JCloze[6] = true;    // show clue
 155      JCloze[7] = true;    // show number of hints (=next letter requests)
 156      JCloze[8] = true;    // show number of clues
 157      JCloze[9] = true;    // show number of checks
 158  }
 159  // JCloze quizzes use the global variables 'I' and 'State'
 160  // I : array of JCLOZE_ANSWERs
 161  // JCLOZE_ANSWER :
 162  //    [0] : (unused)
 163  //    [1] : array of JCLOZE_ANSWER_TEXTs
 164  //    [2] : clue for this answer
 165  // JCLOZE_ANSWER_TEXT :
 166  //    [0] : array (seems unnecessary, just the text would be enough?)
 167  //        [0] : text of possible answer
 168  // State : array of JCLOZE_ANSWER_STATEs
 169  // JCLOZE_ANSWER_STATE (v5) :
 170  //    [0] : clue asked for or not
 171  //    [1] : number of hints (show next letter) and penalties ('check' an incorrect answer)
 172  //    [2] : length of answer matched
 173  //    [3] : score for this item
 174  //    [4] : already answered correctly
 175  //    [5] : answer entered in text box (right or not)
 176  // JCLOZE_ANSWER_STATE (v6)
 177  //    this.ClueGiven = false;
 178  //    this.HintsAndChecks = 0;
 179  //    this.MatchedAnswerLength = 0;
 180  //    this.ItemScore = 0;
 181  //    this.AnsweredCorrectly = false;
 182  //    this.Guesses = new Array(); last guess is correct answer
 183  // ********
 184  //  JCross
 185  // ********
 186  if (window.JCross==null) {
 187      JCross = new Array();
 188      JCross[0] = true;    // show separator line between answers on email
 189      JCross[1] = true;    // show number of penalties (hints or checks before complete)
 190      JCross[2] = true;    // show number of letters
 191      JCross[3] = true;    // show correct answers
 192      JCross[4] = true;    // show clues
 193      JCross[5] = true;    // show wrong answers
 194      JCross[6] = true;    // show if clue was asked for or not
 195      JCross[7] = true;    // show number of hints (=next letter requests)
 196      JCross[8] = true;    // show number of checks
 197      // there are no "ignored" answers for JCross quizzes
 198  }
 199  // JCross quizzes use the following global variables:
 200  //     L : letters (of correct answers)
 201  //     C : clue numbers (CL in v6)
 202  //     G : guesses
 203  // 'L', 'C' ('CL') and 'G' are all 2-dimensional arrays (rows x cols)
 204  //
 205  // v5 quizzes additionally use the following single-dimension arrays
 206  //     A : clues for across (horizontal) words
 207  //     D : clues for down (vertical) words
 208  // N.B. form is only sent when all answers are correct so
 209  // you can't find out what 'wrong' answers were entered
 210  // ********
 211  //  JMatch
 212  // ********
 213  if (window.JMatch==null) {
 214      JMatch = new Array();
 215      JMatch[0] = true;    // show separator line between answers on email
 216      JMatch[1] = false;    // show number of penalties (= total number of checks)
 217      JMatch[2] = true;    // show LHS texts (the question)
 218      JMatch[3] = true;    // show correct answers
 219      JMatch[4] = true;    // show wrong answers
 220      JMatch[5] = true;    // show checks (per match) [empty or unchanged RHS are not counted]
 221      // JMatch has no "clue" or "hint" buttons
 222      // there cannot be any "ignored" answers
 223  }
 224  // v5 JMatch quizzes use the global variables 'I' and 'Status' (and 'RItems')
 225  // v6 JMatch quizzes use only 'Status'
 226  // v6+ JMatch quizzes use 'F' and 'D' (see below)
 227  // I : an array of JMATCH_PAIRs (one for each pair)
 228  // JMATCH_PAIR :
 229  //    [0] : LHS text
 230  //    [1] : RHS text
 231  //    [2] : fixed (=not jumbled) flag
 232  //        0 : not fixed
 233  //        1 : fixed
 234  //    [3] : index in drop down list selection
 235  // Status : an array of JMATCH_PAIR_STATUSes
 236  // JMATCH_PAIR_STATUS:
 237  //    [0] : correctly matched yet flag
 238  //        0 : this pair has NOT been correctly matched
 239  //        1 : this pair has been correctly matched
 240  //    [1] : number of times this item has been wrongly matched
 241  //     v6 quizzes only
 242  //    [2] : id of original SELECT element containing possible matches
 243  //    Note that after matching, this SELECT is removed, so don't try looking for it :-)
 244  // v6+ JMatch quizzes use the global variables 'F' and 'D'
 245  // F : array of JMATCH_FIXED_ITEMs
 246  // JMATCH_FIXED_ITEM:
 247  //    [0] : text
 248  //    [1] : tag
 249  // D : array of JMATCH_DRAGGABLE_ITEMs
 250  // JMATCH_DRAGGABLE_ITEM
 251  //    [0] : text
 252  //    [1] : tag of the F item to which it SHOULD be dragged
 253  //    [2] : tag of the F item to which it was dragged (initally 0)
 254  // N.B. form is only sent when all answers are correct so
 255  // you can't find out what 'wrong' answers were entered
 256  // ********
 257  //  JMix
 258  // ********
 259  if (window.JMix==null) {
 260      JMix = new Array();
 261      JMix[0] = true;        // show separator line between answers on email
 262      JMix[1] = false;    // show number of wrong guesses (replaced by JMix[5])
 263      JMix[2] = true;        // show right answer
 264      JMix[3] = true;        // show wrong answer, if any
 265      JMix[4] = false;    // show answer as text (false) or number (true)
 266      JMix[5] = true;        // show number of checks
 267      JMix[6] = true;        // show number of hints (=show next word)
 268  }
 269  // JMix quizzes use the global variables
 270  // 'Segments', 'GuessSequence' and 'Penalties'
 271  // Segments : array of JMix_QUESTIONs
 272  // JMix_QUESTION:
 273  //    [0] : text
 274  //    [1] : order in sequence
 275  //    [2] : used flag
 276  // GuessSequence : array of 'order in sequence' numbers
 277  // Penalties : number of incorrect guesses
 278  // ********
 279  //  JQuiz
 280  // ********
 281  if (window.JQuiz==null) {
 282      JQuiz = new Array();
 283      JQuiz[0] = true;    // show separator line between answers on email
 284      JQuiz[1] = true;    // show question text
 285      JQuiz[2] = true;    // show student's correct answer(s)
 286      JQuiz[3] = false;    // show wrong and ignored answer(s) (legacy field superceded by [8] & [9])
 287      JQuiz[4] = true;    // show number of hints requested
 288      JQuiz[5] = false;    // show number of checks of incorrect answers (legacy field superceded by [12])
 289      // HP6 v6 quizzes only
 290      JQuiz[6] = false;    // show answer value (false) or A,B,C... index (true)
 291      JQuiz[7] = false;    // show all students answers
 292      JQuiz[8] = true;    // show student's wrong answers
 293      JQuiz[9] = true;    // show ignored answers (not relevant for multi-select questions)
 294      JQuiz[10] = true;    // show score weightings
 295      JQuiz[11] = true;    // show question type
 296      JQuiz[12] = true;    // show number of checks (if true, then JQuiz[5] of will be ignored)
 297      JQuiz[13] = true;    // show number of times ShowAnswer button was pressed (usually 0 or 1)
 298  }
 299  // v5 JQuiz quizzes use the global variables 'I' and 'Status'
 300  // I : array of JQUIZ_ANSWERs
 301  // JQUIZ_ANSWER :
 302  //    [0] : question text
 303  //    [1] : array of JQUIZ_ANSWER_TEXTs (one for each answer)
 304  // JQUIZ_ANSWER_TEXT :
 305  //    [0] : array (seems unnecessary, just the text would be enough?)
 306  //        [0] : text of possible answer
 307  // Status : array of JQUIZ_ANSWER_STATEs
 308  // JQUIZ_ANSWER_STATE :
 309  //    [0] : question done or not
 310  //    [1] : number of wrong checks
 311  //    [2] : number of hints asked for
 312  //    [3] : student's answer
 313  //    [4] : score for this question
 314  // v6 JQuiz quizzes use the global variables 'I' and 'State'
 315  // I : array of JQUIZ_QUESTIONs
 316  // JQUIZ_QUESTION :
 317  //    [0] : weighting
 318  //    [1] : ?? (always set to '')
 319  //    [2] : question type
 320  //        '0'=multiple-choice, '1'=short-answer, '2'=hybrid, '3'=multi-select
 321  //    [3] : array of JQUIZ_ANSWERSs (one for each possible answer)
 322  // JQUIZ_ANSWER :
 323  //    [0] : answer value
 324  //    [1] : feedback text
 325  //    [2] : correct answer flag (1=a correct answer, 0=a wrong answer)
 326  //    [3] : weighted score (as percentage) if correct
 327  //    [4] : flag (usually set to 1, but for hybrid answers that are not
 328  //        to be included in multiple choice options, it is set to 0)
 329  // State : array of JQUIZ_QUESTION_STATEs
 330  // JQUIZ_QUESTION_STATE :
 331  //    [0] : score (-1 shows not done yet)
 332  //    [1] : array showing on which number try each JQUIZ_ANSWER was selected
 333  //    [2] : number of attempts at this question
 334  //    [3] : total of weighted scores of correct answers that were selected
 335  //        i.e. each time a correct answer is selected,
 336  //        its JQUIZ_ANSWER[3] weighting is added to this total
 337  //        so when all the correct answers have been selected, this will be 100
 338  //    [4] : penalties incurred for hints (score is set to zero if >= 1)
 339  //    [5] :     - for multiple choice, short-answer and hybrid questions, this is a
 340  //        comma-delimited list showing order in which answers were chosen
 341  //        - for multi-select fields, this is bar-delimted ('|') list of settings
 342  //        showing whether each checkbox was selected ('Y') on not ('N') when the
 343  //        'Check' button was clicked. The final item in the list will be the
 344  //        settings for the correct answer.
 345  // N.B. JBC, JMatch(v5) and JQuiz(v5) all use global variables 'I' and 'Status'
 346  //    JBC : I[0].length==3 && !window.RItems
 347  //     JQuiz(v5) : I[0].length==2
 348  //    JMatch(v5) : I[0].length==4 && window.RItems
 349  // N.B. JCloze(v5+6) and JQuiz(v6) both use global variables 'I' and 'State'
 350  //    JCloze (v5) : I[0].length==3 && State[0].Guesses==null
 351  //    JCloze (v6) : I[0].length==3 && State[0].Guesses!=null
 352  //     JQuiz  (v6) : I[0].length==4
 353  // **********
 354  //  Rhubarb
 355  // **********
 356  if (window.Rhubarb==null) {
 357      Rhubarb = new Array();
 358      Rhubarb[0] = true;  // show correct words (so far)
 359      Rhubarb[1] = true;  // show correct words as count (true) or list (false)
 360      Rhubarb[2] = true;  // show wrong words
 361      Rhubarb[3] = false; // show wrong words as count (true) or list (false)
 362      Rhubarb[4] = false; // show ignored words (not implemented yet)
 363      Rhubarb[5] = true;  // show hints
 364  }
 365  // **********
 366  //  Sequitur
 367  // **********
 368  if (window.Sequitur==null) {
 369      Sequitur = new Array();
 370      Sequitur[0] = true;  // show count of correct button clicks
 371      Sequitur[1] = true;  // show count of wrong button clicks
 372  }
 373  // **********
 374  //  Messages
 375  // **********
 376  if (window.MSG==null) {
 377      MSG = new Array();
 378      // Login prompts
 379      MSG[0] = 'Name';
 380      MSG[1] = 'ID';
 381      MSG[2] = 'Email';
 382      MSG[3] = 'Password';
 383      MSG[4] = 'Cookies';
 384      // Login buttons
 385      MSG[5] = 'Start the Quiz';
 386      MSG[6] = 'Cancel';
 387      // Cookie menu options (only used if Login[4] is true)
 388      MSG[7] = 'keep for this session only';
 389      MSG[8] = 'keep for one day';
 390      MSG[9] = 'keep for one month';
 391      MSG[10] = 'do NOT keep cookies';
 392      // Login error messages
 393      MSG[11] = 'Sorry, you were unable to login. Please try again later.';
 394      MSG[12] = 'Please fill in all the information.';
 395      MSG[13] = 'Incorrect Password. Please try again.';
 396      MSG[14] = 'Incorrect ID. Please try again.';
 397      MSG[15] = 'Email address does not appear to be valid.';
 398      // day and month names (used in Start_Time and End_Time)
 399      MSG[16] = new Array('Sun','Mon','Tue','Wed','Thu','Fri','Sat');
 400      MSG[17] = new Array('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec');
 401      // enable popups
 402      MSG[18] = 'Please enable pop-up windows on your browser.';
 403      // browser specific instuctions on how to enable popup windows
 404      var n = navigator;
 405      var s = n.userAgent.toLowerCase();
 406      if (n.appName=='Netscape' && s.indexOf('gecko')>=0) {
 407          // Netscape 6 and 7
 408          MSG[18] += '\n\n' + 'Edit->Preferences, ' + (s.indexOf('mac')>=0 ? 'Advanced->Scripts & Plugins' : 'Privacy & Security->Popup Window Controls');
 409      } else if (s.indexOf('safari')>=0) {
 410          // Safari
 411          MSG[18] += '\n\n' + 'on Safari menu, uncheck "Block Pop-Up Windows"';
 412      } else if (s.indexOf('firebird')>=0) {
 413          // Firebird
 414          MSG[18] += '\n\n' + 'Preferences->Web Features, uncheck "Block Pop-Up Windows"';
 415      } else if (s.indexOf('msie 6')>=0) {
 416          // IE 6 (WinXP.SP2)
 417          MSG[18] += '\n\n' + 'Tools->Pop-up Blocker->Turn Off Pop-up Blocker';
 418      }
 419  }
 420  //if (window.FEEDBACK==null) {
 421  //    FEEDBACK = new Array();
 422  //    FEEDBACK[0] = ''; // url of feedback page/script
 423  //    FEEDBACK[1] = ''; // array of array('teachername', 'value');
 424  //    FEEDBACK[2] = ''; // 'student name' [formmail only]
 425  //    FEEDBACK[3] = ''; // 'email@somewhere.com>' [formmail only]
 426  //    FEEDBACK[4] = ''; // window width
 427  //    FEEDBACK[5] = ''; // window height
 428  //    FEEDBACK[6] = ''; // 'Send a message to teacher' [prompt/button text]
 429  //    FEEDBACK[7] = ''; // 'Title'
 430  //    FEEDBACK[8] = ''; // 'Teacher'
 431  //    FEEDBACK[8] = ''; // 'Message'
 432  //    FEEDBACK[10] = ''; // 'Close this window' [formmail only]
 433  //}
 434  // **********
 435  //  HP array
 436  // **********
 437  HP = new Array();
 438  for (var i=0; i<=8; i++) {
 439      HP[i] = new Array();
 440  }
 441  // indexes for the HP array (makes the code further down easier to read)
 442  _score   = 0;
 443  _weight  = 1;
 444  _correct = 2;
 445  _wrong   = 3;
 446  _unused  = 4;
 447  _hints   = 5;
 448  _clues   = 6;
 449  _checks  = 7;
 450  _guesses = 8;
 451  // *************
 452  //  Server Fields
 453  // *************
 454  if (window.ServerFields==null) {
 455      ServerFields = new Array();
 456      // these fields will be added to the ResultForm and submitted to the CGI script on the server.
 457      // 'Sort', 'return_link_title', 'return_link_url' and 'print_blank_fields' are useful for formmail
 458      // override the HP setting of sort fields (forces ALL fields to be displayed)
 459      ServerFields[0] = new Array('sort', '');
 460      // add link to close pop-up results window
 461      ServerFields[1] = new Array('return_link_title', 'Close this window');
 462      ServerFields[2] = new Array('return_link_url', 'javascript:self.close()');
 463      // make sure zero values are printed
 464      ServerFields[3] = new Array('print_blank_fields', 'yes');
 465      // you can also set other fields for your customized CGI script
 466      // e.g. adding a server defined start time (instead of a client defined start time)
 467      // ServerFields[4] = new Array('serverStartTime', '<?php echo date("Y-m-d H:i:s") ?>');
 468  }
 469  // *********************
 470  //      Login screen
 471  //  (not required by LMS)
 472  // *********************
 473  function QuizLogin(LoginPrompt) {
 474      if (!is_LMS() && (Login[0] || Login[1] || Login[2] || Login[3])) {
 475          var html = ''
 476              + '<html>'
 477              + '<head></head>'
 478              + '<body bgColor="#cccccc" onLoad="opener.setFocus(self)">'
 479              + '<form onSubmit="'
 480              +     'self.ok=true;'
 481              +     'self.expiry=null;'
 482          ;
 483          if (Login[4]) { // cookie expiry
 484              html += "opener.checkOK(self,'CookieExpiry');";
 485          }
 486          if (Login[0]) { // user name
 487              html += "opener.checkOK(self,'UserName');";
 488          }
 489          if (Login[1]) { // user ID
 490              html += "opener.checkOK(self,'UserID');";
 491          }
 492          if (Login[2]) { // user email
 493              html += "opener.checkOK(self,'UserEmail');";
 494          }
 495          if (Login[3]) { // quiz password
 496              html += "opener.checkOK(self,'Password');";
 497          }
 498          html +=      'if(ok){'
 499              +         'opener.StartQuiz();'
 500              +         'self.close();'
 501              +      '}else{'
 502              +         'if(isNaN(self.tries))self.tries=0;'
 503              +         'self.tries++;'
 504              +         'if(self.tries<3){'
 505              +            'opener.setFocus(self);'
 506              +         '}else{'
 507              +             "alert(opener.MSG[11]);"
 508              +             'opener.goBack();'
 509              +             'self.close();'
 510              +         '}'
 511              +      '}'
 512              +      'return false;'
 513              + '">'
 514          ;
 515          html += '<table>'
 516              +     '<caption>' + LoginPrompt + '</caption>';
 517          ;
 518          if (Login[0]) { // user name
 519              var v = getCookie(self, 'UserName');
 520              html += '<tr>'
 521                  +    '<th align=right nowrap>' + MSG[0] + ' :</th>'
 522                  +    '<td>'
 523              ;
 524              if (typeof(Login[0])=='boolean') { // text box
 525                  html += '<input type=text name=UserName value="' + v + '">';
 526              } else { // drop down menu of names
 527                  // pattern to match commas and white space
 528                  var comma = (window.RegExp) ? new RegExp('\\s*,\\s*') : ',';
 529                  // convert list of names to array, if necessary
 530                  if (typeof(Login[0])=='string') {
 531                      Login[0] = Login[0].split(comma);
 532                  }
 533                  html += '<select name=UserName size=1>'
 534                      + '<option value=""></option>'
 535                  ;
 536                  for(var i=0; i<Login[0].length; i++) {
 537                      // convert name details to array if nececount_cary
 538                      if (typeof(Login[0][i])=='string') {
 539                          Login[0][i] = Login[0][i].split(comma);
 540                      }
 541                      html += makeOption(Login[0][i][0], v, Login[0][i][1]);
 542                  }
 543                  html += '</select>';
 544              }
 545              html +=     '</td>'
 546                  + '</tr>'
 547              ;
 548          }
 549          if (Login[1]) { // user ID
 550              var v = getCookie(self, 'UserID');
 551              html += '<tr><th align=right nowrap>' + MSG[1] + ' :</th><td><input type=text name=UserID value="' + v + '"></td></tr>';
 552          }
 553          if (Login[2]) { // user email
 554              var v = getCookie(self, 'UserEmail');
 555              html += '<tr><th align=right nowrap>' + MSG[2] +' :</th><td><input type=text name=UserEmail value="' + v + '"></td></tr>';
 556          }
 557          if (Login[3]) { // quiz password
 558              var v = getCookie(self, 'Password');
 559              html += '<tr><th align=right nowrap>' + MSG[3] + ' :</th><td><input type=password name=Password value="' + v + '"></td></tr>';
 560          }
 561          if (Login[4]) { // cookie lifespan
 562              var v = getCookie(self, 'CookieExpiry');
 563              html += '<tr>'
 564                  +     '<th align=right nowrap>' + MSG[4] + ' :</th>'
 565                  +     '<td>'
 566                  +        '<select name="CookieExpiry" size=1>'
 567                  +             makeOption('session', v, MSG[7])
 568                  +             makeOption('day', v, MSG[8])
 569                  +             makeOption('month', v, MSG[9])
 570                  +             makeOption('never', v, MSG[10])
 571                  +         '</select>'
 572                  +     '</td>'
 573                  + '</tr>'
 574              ;
 575          }
 576          html +=     '<tr>'
 577              +        '<th>&nbsp;</th>'
 578              +        '<td nowrap>'
 579              +            '<input type=submit value="' + MSG[5] + '"> '
 580              +             '<input type=button value="' + MSG[6] + '" onClick="opener.goBack();self.close();">'
 581              +        '</td>'
 582              +    '</tr>'
 583              + '</table></form></body></html>'
 584          ;
 585          // set height of Login Window
 586          var m = navigator.userAgent.indexOf('Mac')>=0;
 587          var h = (m ? 80 : 100);
 588          for (var i=0; i<5; i++) h += (Login[i] ? (m ? 20 : 25) : 0);
 589          // open up a new window
 590          if (!openWindow('', '', (m ? 320 : 300), h, 'RESIZABLE', html)) {
 591              alert(MSG[18]); // unable to open popup window
 592          }
 593      } else { // no Login required
 594          window.UserName = window.UserID = window.UserEmail = window.Password = '';
 595          window.StartQuiz();
 596      }
 597      return true;
 598  }
 599  function makeOption(value, v, txt) {
 600      return '<option value="' + value + '"' + (value==v ? ' SELECTED' : '') + '>' + (txt ? txt : value) + '</option>';
 601  }
 602  function setFocus(w) {
 603      w.focus(); // bring window to the front
 604      var obj = w.document.forms[0].elements;
 605      for(var i=0; i<obj.length; i++) {
 606          var v = getValue(w, i);
 607          if (v=='' || obj[i].type=='submit') {
 608              obj[i].focus();
 609              break;
 610          }
 611      }
 612  }
 613  function checkOK(w, n){
 614      var v = getValue(w, n, true);
 615      if (v || (n!='UserName' && isGuest())) {
 616          if (n=='CookieExpiry') setCookieExpiry(w, v);
 617          setCookie(self, n, v, w.expiry);
 618          if (n!='CookieExpiry') eval('self.' + n + '=v');
 619      } else {
 620          if (w.ok) alert(MSG[12]);
 621          w.ok = false;
 622      }
 623  }
 624  function getValue(w, n, flag) {
 625      var obj = w.document.forms[0].elements[n];
 626      var TYPE = obj.type.toUpperCase(); // required for ns4 (win)
 627      if (obj.options && TYPE.indexOf('SELECT')>=0){
 628          var v = obj.options[obj.selectedIndex].value;
 629      } else {
 630          var v = obj.value;
 631      }
 632      if (flag) {
 633          var msg = '';
 634          if (n=='Password' || (n=='UserID' && !Login[3])) {
 635              var pwd = getPassword(w);
 636              if (pwd && v!=pwd) msg = MSG[n=='Password' ? 13 : 14];
 637          }
 638          if (n=='UserEmail' && window.RegExp) {
 639              var r = '(\\w|-)+';
 640              r = r + '(\\.' + r + ')';
 641              r = new RegExp('^(' + r + '*)@(' + r + '+)$');
 642              if (v.match(r)==null) msg = MSG[15];
 643          }
 644          if (msg) {
 645              obj.value = v = '';
 646              if (w.ok) alert(msg);
 647              w.ok = false;
 648          }
 649      }
 650      return v;
 651  }
 652  function getPassword(w) {
 653      var pwd = '';
 654      if (Login[3] && typeof(Login[3])=='string') {
 655          pwd = Login[3];
 656      } else if ((Login[3] || Login[1]) && typeof(Login[0])=='object') {
 657          var username = getValue(w, 'UserName');
 658          for(var i=0; i<Login[0].length; i++) {
 659              if (username==Login[0][i][0]) {
 660                  pwd = Login[0][i][2];
 661                  break;
 662              }
 663          }
 664      }
 665      return pwd;
 666  }
 667  function setCookieExpiry(w, v) {
 668      if (v=='never'){
 669          w.expiry = new Date('Thu, 01-Jan-70 00:00:01 GMT');
 670      } else if (v=='day' || v=='month') {
 671          var ms = (v=='month' ? 31 : 1) * 60 * 60 * 24 * 1000;
 672          w.expiry = new Date((new Date()).getTime() + ms);
 673      }
 674  }
 675  function setCookie(w, name, value, expires, path, domain, secure) {
 676      if (name) w.document.cookie = ''
 677          + 'HP_' + name + "=" + escape(value)
 678          + (expires ? "; expires=" + expires.toGMTString() : "")
 679          + (path ? "; path=" + path : "")
 680          + (domain ? "; domain=" + domain : "")
 681          + (secure ? "; secure" : "")
 682      ;
 683  }
 684  function getCookie(w, n) {
 685      var c = w.document.cookie;
 686      var i = c.indexOf('HP_' + n + '=');
 687      var j = (i<0) ? -1 : c.indexOf(';', (i += n.length + 4));
 688      return (i<0) ? '' : unescape(c.substring(i, ((j<0) ? c.length : j)));
 689  }
 690  function goBack(w) {
 691      if (w==null) w = self; // default
 692      if (w.history.length) w.history.back();
 693  }
 694  function openWindow(url, name, width, height, attributes, html) {
 695      // set height, width and attributes
 696      if (window.screen && width && height) {
 697          var W = screen.availWidth;
 698          var H = screen.availHeight;
 699          width = Math.min(width, W);
 700          height = Math.min(height, H);
 701          attributes = ''
 702              + (attributes ? (attributes+',') : '')
 703              + 'WIDTH='+width+',HEIGHT='+height
 704          ;
 705      }
 706      // create global hpWindows object, if necessary
 707      if (!window.hpWindows) window.hpWindows = new Array();
 708      // initialize window object
 709      var w = null;
 710      // has a window with this name been opened before?
 711      if (name && hpWindows[name]) {
 712          // http://www.webreference.com/js/tutorial1/exist.html
 713          if (hpWindows[name].open && !hpWindows[name].closed) {
 714              w = hpWindows[name];
 715              w.focus();
 716          } else {
 717              hpWindows[name] = null;
 718          }
 719      }
 720      // check window is not already open
 721      if (w==null) {
 722          // workaround for "Access is denied" errors in IE when offline
 723          // based on an idea seen at http://www.devshed.com/Client_Side/JavaScript/Mini_FAQ
 724          var ie_offline = (document.all && location.protocol=='file:');
 725          // try and open the new window
 726          w = window.open((ie_offline ? '' : url), name, attributes);
 727          // check window opened OK (user may have prevented popups)
 728          if (w) {
 729              // center the window
 730              if (window.screen && width && height) {
 731                  w.moveTo((W-width)/2, (H-height)/2);
 732              }
 733              // add content, if required
 734              if (html) {
 735                  with (w.document) {
 736                      clear();
 737                      open();
 738                      write(html);
 739                      close();
 740                  }
 741              } else if (url && ie_offline) {
 742                  w.location = url;
 743              }
 744              if (name) hpWindows[name] = w;
 745          }
 746      }
 747      return w;
 748  }
 749  // *********************
 750  //  Send results by email
 751  //  (not required by LMS)
 752  // *********************
 753  function SendAllResults(Score) {
 754      // check this quiz is not generated by a LMS
 755      if (!is_LMS()) {
 756          // add flat file database details to the results form
 757          AddDatabaseDetailsToResultForm();
 758          // add student details to the results form
 759          AddStudentDetailsToResultForm();
 760          // add question details to the results form
 761          AddQuestionDetailsToResultForm();
 762          // add server fields, if any, to results form
 763          AddServerFieldsToResultForm();
 764          // change "method" of form, because "get" only allows 512 byts of data
 765          ResultForm = replaceLast('method="get"', 'method="post"', ResultForm);
 766          // create results window and form
 767          var w = openWindow('', '', 500, 400, 'RESIZABLE,SCROLLBARS,LOCATION', ResultForm);
 768          // check window opened OK (user may have prevented popups)
 769          if (w) {
 770              // get shortcut to form object
 771              var form = w.document.forms[0];
 772              // update some important field values
 773              form.Score.value = Score + '%';
 774              form.realname.value = UserName;
 775              form.Start_Time.value = getTime(Start_Time);
 776              form.End_Time.value = getTime();
 777              // force email subject and Exercise title
 778              form.subject.value = document.title;
 779              form. Exercise.value = document.title;
 780              // update DB fields, if required
 781              if (DB[0] && !isGuest()) set_db_fields(form);
 782              if (DB[7]) form.action = DB[7];
 783              if (DB[8]) form.recipient.value = DB[8];
 784              // if this is a Netscape browser, check if the referer will be set OK
 785              if (navigator.appName=='Netscape' && (location.protocol=='file:' || navigator.userAgent.indexOf('Netscape6')>=0)) {
 786                  // ns4 and ns7 set referer to 'file:// ...' when running a quiz offline
 787                  // ns6.2 (at least) always sets referer to 'about:blank'
 788                  // Netscape's setting of referer can cause BFormMail
 789                  // to reject the form, so encode the form data as a URL
 790                  var url = form.action;
 791                  var obj = form.elements;
 792                  for (var i=0; i<obj.length; i++) {
 793                      var  v = escape(obj[i].value);
 794                      v = v.replace((new RegExp('\\+', 'g')), '%2B');
 795                      url += (i==0 ? '?' : '&') + obj[i].name + '=' + v;
 796                  }
 797                  w.location.href = url;
 798              } else { // browser can POST form ok
 799                  form.submit();
 800              }
 801          } else { // unable to open popup window
 802              alert(MSG[18]);
 803          }
 804      } // end if LMS
 805  }
 806  function isGuest() {
 807      // check username is not a "guest" user
 808      var flag = false;
 809      var n = getCookie(self, 'UserName').toLowerCase();
 810      if (n) {
 811          // convert list of user names to array, if necessary
 812          if(typeof(Login[5])=='string') {
 813              Login[5] = Login[5].split(',');
 814          }
 815          for(var i=0; i<Login[5].length; i++) {
 816              if (n==Login[5][i].toLowerCase()) {
 817                  flag = true;
 818                  break;
 819              }
 820          }
 821      }
 822      return flag;
 823  }
 824  function set_db_fields(form) {
 825      // update list of DB fields, if required
 826      if (DB[4]=='' && window.RegExp) {
 827          // add administration fields
 828          var db_fields = ''
 829              + 'subject,realname'
 830              + (Login[1] ? ',ID' : '')
 831              + (Login[2] ? ',email' : '')
 832              + (Login[3] ? ',password' : '')
 833              + ',Score,Start_Time,End_Time'
 834          ;
 835          // add answer fields (except separators)
 836          var r = new RegExp('^[^_]+_q\\d\\d_\\w+$');
 837          for(var i=0; i<form.elements.length; i++) {
 838              var n = form.elements[i].name;
 839              if (r.test(n)) db_fields += ',' + n;
 840          }
 841          form.db_fields.value = db_fields;
 842      }
 843      // make sure delimiter is set (NS6+ requires this be set here, not any earlier)
 844      form.db_delimiter.value = (DB[5] ? DB[5] : '\t');
 845  }
 846  function AddStudentDetailsToResultForm() {
 847      var sDetails = '';
 848      if (Login[0]) { // user name
 849          // use 'realname' instead of a separate 'Name' field
 850          // sDetails += hpHiddenField('Name', window.UserName);
 851      }
 852      if (Login[1]) { // user ID
 853          sDetails += hpHiddenField('ID', window.UserID);
 854      }
 855      if (Login[2]) { // user email
 856          sDetails += hpHiddenField('email', window.UserEmail);
 857      }
 858      if (sDetails && window.RegExp) {
 859          // insert sDetails before '<input...Score...></input>'
 860          var r = new RegExp('<input[^>]*Score[^>]*><\\/input>', 'i');
 861          var m = r.exec(ResultForm);
 862          if (m) {
 863              ResultForm = ResultForm.replace(m[0], sDetails + m[0] + makeSeparator('Time_'));
 864              sDetails = '';
 865          }
 866      }
 867      if (Login[3]) { // quiz password
 868          sDetails += hpHiddenField('Password', window.Password);
 869          ResultForm = replaceLast('</form>', sDetails + '</form>', ResultForm);
 870      }
 871  }
 872  function AddQuestionDetailsToResultForm() {
 873      var qDetails = GetQuestionDetails();
 874      if (qDetails) {
 875          // insert qDetails before the final </form> tag in the ResultForm
 876          ResultForm = replaceLast('</form>', qDetails + '</form>', ResultForm);
 877      }
 878  }
 879  function AddDatabaseDetailsToResultForm() {
 880      if (window.DB && DB[0] && !isGuest()) {
 881          var dbDetails = '';
 882          var folder = DB[1];
 883          if (folder && folder.charAt(folder.length-1)!='/') folder += '/';
 884          var file = DB[2];
 885          if (file=='') {
 886              file = location.href;
 887              file= file.substring(file.lastIndexOf('/')+1);
 888              var i = file.indexOf('?');
 889              if (i >= 0) file = file.substring(0, i);
 890              var i = file.lastIndexOf('.');
 891              if (i >= 0) file = file.substring(0, i);
 892          }
 893          var ext = (DB[3] ? DB[3] : 'txt');
 894          if (ext.charAt(0)!='.') ext = '.' + ext;
 895          dbDetails += hpHiddenField('append_db', folder + file + ext);
 896          dbDetails += hpHiddenField('db_fields', DB[4]);
 897          dbDetails += hpHiddenField('db_delimiter', ''); // NS6+ requires this be set later
 898          if (DB[6]) dbDetails += hpHiddenField('env_report', DB[6]);
 899          // insert dbDetails before the final </form> tag in the ResultForm
 900          ResultForm = replaceLast('</form>', dbDetails + '</form>', ResultForm);
 901      }
 902  }
 903  function AddServerFieldsToResultForm() {
 904      if (window.ServerFields) {
 905          var s = ''; // input tags for s(erver fields)
 906          for (var i=0; i<ServerFields.length; i++) {
 907              if (ServerFields[i][0] && window.RegExp) {
 908                  // remove previous field value, if any
 909                  var r = new RegExp('<input[^>]*name\\s*=\\s*["\']\\s*' + ServerFields[i][0] + '[^>]*>(\\s*<\\/input>)?', 'i');
 910                  if (r.test(ResultForm)) {
 911                      ResultForm = ResultForm.replace(r, '');
 912                  }
 913              }
 914              if (ServerFields[i][1]) {
 915                  s += hpHiddenField(ServerFields[i][0], ServerFields[i][1]);
 916              }
 917          } // end for
 918          if (s) ResultForm = replaceLast('</form>', s + '</form>', ResultForm);
 919      }
 920  }
 921  function replaceLast(a, b, c) {
 922      // replace last occurrence of 'a' in 'c' with 'b'
 923      var l = a.length;
 924      var i = c.lastIndexOf(a);
 925      return (i<0 || l==0) ? c : (c.substring(0, i) + b + c.substring(i+l));
 926  }
 927  // *************************
 928  //  Extract question details
 929  // *************************
 930  function GetQuestionDetails() {
 931      var hp = hpVersion();
 932      var t = hpQuizType();
 933      var v = hpQuizVersion();
 934      return    (t==1) ? GetJbcQuestionDetails(hp, v) :
 935          (t==2) ? GetJClozeQuestionDetails(hp, v) :
 936          (t==3) ? GetJCrossQuestionDetails(hp, v) :
 937          (t==4) ? GetJMatchQuestionDetails(hp, v) :
 938          (t==5) ? GetJMixQuestionDetails(hp, v) :
 939          (t==6) ? GetJQuizQuestionDetails(hp, v) :
 940          (t==7) ? GetRhubarbDetails(hp, v) :
 941          (t==8) ? GetSequiturDetails(hp, v) : '';
 942  }
 943  function GetJbcQuestionDetails(hp, v) {
 944      qDetails = '';
 945      // check the quiz version
 946      if (hp==5 || hp==6) {
 947          // get question details
 948          for(var q=0; q<I.length; q++) {
 949              // initialize strings to hold answer details
 950              var aDetails = new Array();
 951              aDetails[0] = new Array(); // right
 952              aDetails[1] = new Array(); // wrong
 953              aDetails[2] = new Array(); // ignored
 954              // get answer details
 955              for(var a=0; a<I[q][1].length; a++) {
 956                  var i = (Status[q][1][a]=='R') ? 0 : (Status[q][1][a]=='0') ? 2 : 1;
 957                  aDetails[i][aDetails[i].length] = (JBC[6] ? a : I[q][1][a][0]);
 958              }
 959              // format 'Q' (a padded, two-digit version of 'q')
 960              var Q = getQ('JBC', q);
 961              // add separator, if required
 962              if (JBC[0]) qDetails += makeSeparator(Q);
 963              if (JBC[1]) { // number of attempts to answer question
 964                  qDetails += hpHiddenField(Q+'attempts', Status[q][2] + (Status[q][0]==1 ? 1 : 0));
 965              }
 966              if (JBC[2]) { // question text
 967                  qDetails += hpHiddenField(Q+'text', I[q][0]);
 968              }
 969              if (JBC[3] && (DB[0] || aDetails[0].length>0)) { // right
 970                  qDetails += hpHiddenField(Q+'right', aDetails[0]);
 971              }
 972              if (JBC[4] && (DB[0] || aDetails[1].length>0)) { // wrong
 973                  qDetails += hpHiddenField(Q+'wrong', aDetails[1]);
 974              }
 975              if (JBC[5] && (DB[0] || aDetails[2].length>0)) { // ignored
 976                  qDetails += hpHiddenField(Q+'ignored', aDetails[2]);
 977              }
 978              // calculate score for this question, if required (for HP version < 5.5)
 979              if (isNaN(Status[q][3])) {
 980                  var a1 = Status[q][1].length; // answers
 981                  var a2 = Status[q][2]; // attempts
 982                  Status[q][3] =  (a1<1 || a1<(a2-1)) ? 0 : ((a1 - (a2-1)) / a1);
 983              }
 984              // add 'score' for this question
 985              qDetails += hpHiddenField(Q+'score', Math.floor(Status[q][3]*100)+'%');
 986          } // end for
 987      }
 988      return qDetails;
 989  }
 990  function GetJClozeQuestionDetails(hp, v) {
 991      var qDetails = '';
 992      // check the quiz version
 993      if (hp==5 || hp==6) {
 994          var r = hpRottmeier();
 995          if (parseInt(r)==2) { // Rottmeier Find-It 3a+3b
 996              qDetails += hpHiddenField('JCloze_penalties', window.TotWrongChoices);
 997          }
 998          // get details for each question
 999          var q_max = (r==0) ? State.length :  GapList.length; // could use I.length for both
1000          for (var q=0; q<q_max; q++) {
1001              // format 'Q' (a padded, two-digit version of 'q')
1002              var Q = getQ('JCloze', q);
1003              // add separator, if required
1004              if (JCloze[0]) qDetails += makeSeparator(Q);
1005              // score (as %)
1006              var x = (hp==5) ? State[q][3] : (r==0) ? State[q].ItemScore : GapList[q][1].Score;
1007              qDetails += hpHiddenField(Q+'score', Math.floor(x*100)+'%');
1008              var correct = (HP[_correct][q] ? HP[_correct][q] : '');
1009              if (JCloze[1]) { // student's correct answer
1010                  qDetails += hpHiddenField(Q+'correct', correct);
1011              }
1012              if (JCloze[2]) { // ignored answers
1013                  var x = new Array();
1014                  if (r!=2.1) { // exclude Find-It 3a
1015                      for (var i=0, ii=0; i<I[q][1].length; i++) {
1016                          var s = I[q][1][i][0];
1017                          if (typeof(s)=='string' && s!='') {
1018                              if (s.toUpperCase() == correct.toUpperCase()) {
1019                                  var is_ignored = false;
1020                              } else {
1021                                  // DropDown 2.4
1022                                  var is_ignored = true;
1023                                  var iii_max = HP[_wrong][q] ? HP[_wrong][q].length : 0;
1024                                  for (var iii=0; iii<iii_max; iii++) {
1025                                      if (s.toUpperCase() == HP[_wrong][q][iii].toUpperCase()) {
1026                                          var is_ignored = false;
1027                                      }
1028                                  }
1029                              }
1030                              if (is_ignored) {
1031                                  x[ii++] = s;
1032                              }
1033                          }
1034                      }
1035                  }
1036                  qDetails += hpHiddenField(Q+'ignored', x);
1037              }
1038              if (JCloze[3]) {
1039                  var x = (HP[_wrong][q] ? HP[_wrong][q] : '');
1040                  qDetails += hpHiddenField(Q+'wrong', x);
1041              }
1042              if (JCloze[4]) { // number of penalties (Hints + Checks)
1043                  var x = (hp==5) ? State[q][1] : (r==0) ? State[q].HintsAndChecks : (r==1) ?  GapList[q][1].NumOfTrials : (r==2.2) ?  GapList[q][1].HintsAndChecks : 0;
1044                  qDetails += hpHiddenField(Q+'penalties', x);
1045              }
1046              if (JCloze[5]) { // clue shown?
1047                  var x = (hp==5) ? State[q][0] : (r==0) ? State[q].ClueGiven: (r==1) ? GapList[q][1].ClueAskedFor : false;
1048                  qDetails += hpHiddenField(Q+'clue_shown', (x ? 'YES' : 'NO'));
1049              }
1050              if (JCloze[6]) { // clue text
1051                  qDetails += hpHiddenField(Q+'clue_text', I[q][2]);
1052              }
1053              if (JCloze[7]) { // number of hints
1054                  var x = (HP[_hints][q] ? HP[_hints][q] : 0);
1055                  qDetails += hpHiddenField(Q+'hints', x);
1056              }
1057              if (JCloze[8]) { // number of clues
1058                  var x = HP[_clues][q] ? HP[_clues][q] : 0;
1059                  qDetails += hpHiddenField(Q+'clues', x);
1060              }
1061              if (JCloze[9]) { // number of checks (including the final one for the correct answer)
1062                  var x = (HP[_checks][q] ? HP[_checks][q] : 0);
1063                  qDetails += hpHiddenField(Q+'checks', x);
1064              }
1065          } // end for
1066      }
1067      return qDetails;
1068  }
1069  function GetJCrossQuestionDetails(hp, v) {
1070      var qDetails = '';
1071      // check the quiz version
1072      if (hp==5 || hp==6) {
1073          // inialize letter count
1074          var letters = 0;
1075          // get details for each question
1076          for (var row=0; row<L.length; row++) {
1077              for (var col=0; col<L[row].length; col++) {
1078                  // increment letter count, if required
1079                  if (L[row][col]) letters++;
1080                  // show answers and clues, if required
1081                  var q = (hp==5) ? C[row][col] : CL[row][col];
1082                  if (q) {
1083                      for (var i=0; i<2; i++) { // 0==across, 1==down
1084                          var AD = (i==0) ? 'A' : 'D';
1085                          var acrossdown = (i==0) ? 'across' : 'down';
1086  
1087                          var clue = (hp==5) ? eval(AD+'['+q+']') : GetJCrossClue('Clue_'+AD+'_'+q);
1088                          if (clue) {
1089                              // format 'Q' (a padded, two-digit version of 'q')
1090                              var Q = getQ('JCross', q) + acrossdown + '_'; // e.g. JCross_01_across_
1091  
1092                              if (JCross[0]) {
1093                                  qDetails += makeSeparator(Q);
1094                              }
1095                              if (JCross[5]) {
1096                                  var x = (HP[_correct][AD] && HP[_correct][AD][q]) ? HP[_correct][AD][q] : '';
1097                                  qDetails += hpHiddenField(Q+'correct', x);
1098                              }
1099                              if (JCross[4]) qDetails += hpHiddenField(Q+'clue', clue);
1100                              if (JCross[5]) {
1101                                  var x = (HP[_wrong][AD] && HP[_wrong][AD][q]) ? HP[_wrong][AD][q] : '';
1102                                  qDetails += hpHiddenField(Q+'wrong', x);
1103                              }
1104                              if (JCross[6]) {
1105                                  var x = HP[_clues][q] ? HP[_clues][q] : 0;
1106                                  qDetails += hpHiddenField(Q+'clues', x);
1107                              }
1108                              if (JCross[7]) {
1109                                  var x = (HP[_hints][AD] && HP[_hints][AD][q]) ? HP[_hints][AD][q] : 0;
1110                                  qDetails += hpHiddenField(Q+'hints', x);
1111                              }
1112                              if (JCross[8]) {
1113                                  var x = (HP[_checks][AD] && HP[_checks][AD][q]) ? HP[_checks][AD][q] : '';
1114                                  qDetails += hpHiddenField(Q+'checks', x);
1115                              }
1116                          } // end for i
1117                      } // end if clue
1118                  } // end if q
1119              } // end for col
1120          } // end for row
1121          if (JCross[2]) { // show number of letters
1122              qDetails = hpHiddenField('JCross_letters', letters) + qDetails;
1123          }
1124          if (JCross[1]) { // show penalties
1125              var x = (window.Penalties) ? Penalties : 0;
1126              qDetails = hpHiddenField('JCross_penalties', x) + qDetails;
1127          }
1128      }
1129      return qDetails;
1130  }
1131  function GetJCrossClue(id) {
1132      var obj = (document.getElementById) ? document.getElementById(id) : null;
1133      return (obj) ? GetTextFromNodeN(obj, 'Clue') : '';
1134  }
1135  function GetJCrossWord(a, r, c, goDown) {
1136      // a is a 2-dimensional array of letters, r is a row number, c is a column number
1137      var s = '';
1138      while (r<a.length && c<a[r].length && a[r][c]) {
1139          s += a[r][c];
1140          if (goDown) {
1141              r++;
1142          } else {
1143              c++;
1144          }
1145      }
1146      return s;
1147  }
1148  function GetJMatchText(q, className) {
1149      var obj = (document.getElementById) ? document.getElementById('Questions') : null;
1150      return (obj) ? GetTextFromNodeN(obj, className, q) : '';
1151  }
1152  function GetJMatchRHS(v, q, getCorrect) {
1153      var rhs = '';
1154      if (v==5.1 || v==6.1) { // Drag-and-drop
1155          var max_i = (window.F && window.D) ? D.length : 0;
1156          for (var i=0; i<max_i; i++) {
1157              if (F[q][1]==D[i][getCorrect ? 1 : 2]) break;
1158          }
1159          if (i<max_i) rhs = D[i][0];
1160      } else if (v==5 || v==6) { // drop-down list of options
1161          var obj=document.getElementById(Status[q][2]);
1162          if (obj) { // not correct yet
1163              if (getCorrect) {
1164                  var k = GetKeyFromSelect(obj);
1165                  var i_max = obj.options.length;
1166                  for (var i=0; i<i_max; i++) {
1167                      if (obj.options[i].value==k) break;
1168                  }
1169                  if (i>=i_max) i = 0; // shouldn't happen
1170              } else {
1171                  // get current guess, if any
1172                  var i = obj.selectedIndex;
1173              }
1174              if (i) rhs = obj.options[i].innerHTML;
1175          } else { // correct
1176              rhs = GetJMatchText(q, 'RightItem');
1177          }
1178      }
1179      return rhs;
1180  }
1181  function GetJMixQuestionDetails(hp, v) {
1182      qDetails = '';
1183      // check the quiz version
1184      if (hp==5 || hp==6) {
1185          var q = 0; // question number
1186          // format 'Q' (a padded, two-digit version of 'q')
1187          var Q = getQ('JMix', q);
1188          // add separator, if required
1189          if (JMix[0]) qDetails += makeSeparator(Q);
1190          // add 'score' for this question
1191          var score = HP[_correct]==null ? 0 : ((Segments.length-Penalties)/Segments.length);
1192          qDetails += hpHiddenField(Q+'score', Math.floor(score*100)+'%');
1193          if (JMix[1]) { // number of wrong guesses
1194              qDetails += hpHiddenField(Q+'wrongGuesses', Penalties);
1195          }
1196          if (JMix[2]) { // right answer
1197              var x = (HP[_correct][q]) ? HP[_correct][q] : '';
1198              qDetails += hpHiddenField(Q+'correct', x);
1199          }
1200          if (JMix[3]) { // wrong answer(s)
1201              var x = (HP[_wrong][q]) ? HP[_wrong][q] : '';
1202              qDetails += hpHiddenField(Q+'wrong', x);
1203          }
1204          if (JMix[5]) { // checks
1205              var x = (HP[_checks][q]) ? HP[_checks][q] : 0;
1206              qDetails += hpHiddenField(Q+'checks', x);
1207          }
1208          if (JMix[6]) { // hints
1209              var x = (HP[_hints][q]) ? HP[_hints][q] : 0;
1210              qDetails += hpHiddenField(Q+'hints', x);
1211          }
1212      }
1213      return qDetails;
1214  }
1215  function GetJMixSequence(indexes) {
1216      var s = new Array();
1217      for (var i=0; i<indexes.length; i++) {
1218          s[i] = JMix[4] ? indexes[i] : GetJMixSegmentText(indexes[i]);
1219      }
1220      return s;
1221  }
1222  function GetJMixSegmentText(index){
1223      var i_max = Segments.length;
1224      for (var i=0; i<i_max; i++) {
1225          if (Segments[i][1] == index) break;
1226      }
1227      return (i<i_max) ? Segments[i][0] : '';
1228  }
1229  function GetJQuizQuestionDetails(hp, v) {
1230      var qDetails = '';
1231      // HP5.5 uses "Status" for v5 and v6 JMatch quizzes (HP6 uses "State")
1232      // var hp =  (window.Status) ? 5 : (window.State) ? 6 : 0;
1233      // check the quiz version
1234      if (hp==5 || hp==6) {
1235          // get details for each question
1236          var max_q = (hp==5) ? Status.length : State.length;
1237          for (var q=0; q<max_q; q++) {
1238              // skip this question if it was not used (HP6 v6 only)
1239              if (hp==6 && !State[q]) continue;
1240              // format 'Q' (a padded, two-digit version of 'q')
1241              var Q = getQ('JQuiz', q);
1242              // add separator
1243              if (JQuiz[0]) qDetails += makeSeparator(Q);
1244              if (hp==6 && JQuiz[11]) { // question type
1245                  var x = parseInt(I[q][2]);
1246                  x = (x==0) ? 'multiple-choice' : (x==1) ? 'short-answer' : (x==2) ? 'hybrid' : (x==3) ? 'multi-select' : 'n/a';
1247                  qDetails += hpHiddenField(Q+'type', x);
1248              }
1249              // score (as %)
1250              var x = (hp==5) ? Status[q][4]*10 : I[q][0]*State[q][0];
1251              if (x<0) x = 0;
1252              qDetails += hpHiddenField(Q+'score', Math.floor(x)+'%');
1253              if (hp==6 && JQuiz[10]) { // weighting
1254                  qDetails += hpHiddenField(Q+'weighting', I[q][0]);
1255              }
1256              if (JQuiz[1]) { // question text
1257                  var x = (hp==5) ? I[q][0] : (document.getElementById) ? GetTextFromNodeN(document.getElementById('Q_'+q), 'QuestionText') : '';
1258                  qDetails += hpHiddenField(Q+'question', x);
1259              }
1260              if (JQuiz[2]) { // student's correct answers
1261                  var x = (HP[_correct][q]) ? HP[_correct][q] : '';
1262                  qDetails += hpHiddenField(Q+'correct', x);
1263              }
1264              if (JQuiz[3]) { // ignored and wrong answers
1265                  var x = (hp==5) ? new Array() : GetJQuizAnswerDetails(q, 1);
1266                  if (hp==5) {
1267                      for (var i=0; i<I[q][1].length; i++) {
1268                          var correct = HP[_correct][q] ? HP[_correct][q] : '';
1269                          if (I[q][1][i][0] && I[q][1][i][0].toUpperCase()!=correct.toUpperCase()) {
1270                              x[x.length] = I[q][1][i][0];
1271                          }
1272                      }
1273                  }
1274                  if (DB[0] || x) qDetails += hpHiddenField(Q+'other', x);
1275              }
1276              if (hp==6 && JQuiz[7]) { // all selected answers
1277                  var x = GetJQuizAnswerDetails(q, 0);
1278                  qDetails += hpHiddenField(Q+'selected', x);
1279              }
1280              if (JQuiz[8]) { // wrong answers
1281                  var x = (HP[_wrong][q]) ? HP[_wrong][q] : '';
1282                  qDetails += hpHiddenField(Q+'wrong', x);
1283              }
1284              if (hp==6 && JQuiz[9]) { // ignored answers
1285                  var x = GetJQuizAnswerDetails(q, 4);
1286                  qDetails += hpHiddenField(Q+'ignored', x);
1287              }
1288              if (JQuiz[4]) { // number of hints
1289                  var x = (HP[_hints][q]) ? HP[_hints][q] : 0;
1290                  qDetails += hpHiddenField(Q+'hints', x);
1291              }
1292              if (JQuiz[5] || JQuiz[12]) { // number of checks
1293                  if (JQuiz[12]) { // strictly checks only
1294                      var x = (HP[_checks][q]) ? HP[_checks][q] : 0;
1295                  } else { // checks (+ hints in HP6)
1296                      var x = (hp==5) ? Status[q][1] : (State[q][2]-1);
1297                  }
1298                  qDetails += hpHiddenField(Q+'checks', x);
1299              }
1300              if (JQuiz[13]) { // ShowAnswer button
1301                  var x = (HP[_clues][q]) ? HP[_clues][q] : 0;
1302                  qDetails += hpHiddenField(Q+'clues', x);
1303              }
1304          } // end for
1305      } // end if
1306      return qDetails;
1307  }
1308  function GetTextFromNodeN(obj, className, n) {
1309      // returns the text under the nth node of obj with the target class name
1310      var txt = '';
1311      if (obj && className) {
1312          if (typeof(n)=='undefined') {
1313              n = 0;
1314          }
1315          var nodes = GetNodesByClassName(obj, className);
1316          if (n<nodes.length) {
1317              txt += GetTextFromNode(nodes[n]);
1318          }
1319      }
1320      return txt;
1321  }
1322  function GetNodesByClassName(obj, className) {
1323      // returns an array of nodes with the target classname
1324      var nodes = new Array();
1325      if (obj) {
1326          if (className && obj.className==className) {
1327              nodes.push(obj);
1328          } else if (obj.childNodes) {
1329              for (var i=0; i<obj.childNodes.length; i++) {
1330                  nodes = nodes.concat(GetNodesByClassName(obj.childNodes[i], className));
1331              }
1332          }
1333      }
1334      return nodes;
1335  }
1336  function GetTextFromNode(obj) {
1337      // return text in (and under) a single DOM node
1338      var txt = '';
1339      if (obj) {
1340          if (obj.nodeType==3) {
1341              txt = obj.nodeValue + ' ';
1342          }
1343          if (obj.childNodes) {
1344              for (var i=0; i<obj.childNodes.length; i++) {
1345                  txt += GetTextFromNode(obj.childNodes[i]);
1346              }
1347          }
1348      }
1349      return txt;
1350  }
1351  function GetJQuizAnswerDetails(q, flag) {
1352      // flag : the type of information required about the student's answers
1353      //    0 : all student's answers
1354      //    1 : student's wrong and ignored answers
1355      //    2 : student's correct answers
1356      //    3 : student's wrong answers
1357      //    4 : ignored answers
1358      var x = State[q][5]; //Sequence of answers chosen by number
1359      if (I[q][2]=='3') { // multi-select
1360          if (flag==4) {
1361              var x = new Array();
1362          } else {
1363              // get required part of 'x' and convert to array
1364              if (x.charAt(0)=='|') {
1365                  // HP 6.0 and 6.1 (always has leading bar)
1366                  var i = x.lastIndexOf('|');
1367                  var x = x.substring((flag==2 ? (i+1) : 1), ((flag==0 || flag==2) ? x.length : i)).split('|');
1368              } else {
1369                  // HP 6.2 (no leading delimiter)
1370                  var i = x.lastIndexOf(' | ');
1371                  var x = x.substring((flag==2 ? (i+3) : 0), ((flag==0 || flag==2) ? x.length : i)).split(' | ');
1372              }
1373          }
1374          for (var i=0; i<x.length; i++) {
1375              var a = new Array();
1376              for (var ii=0; ii<x[i].length; ii++) {
1377                  if (x[i].charAt(ii)=='Y') {
1378                      var s = JQuiz[6] ? String.fromCharCode(97+ii) : I[q][3][ii][0];
1379                      if (s && s.replace && window.RegExp) {
1380                          s = s.replace(new RegExp('\\+', 'g'), '&#43;');
1381                      }
1382                      a.push(s);
1383                  }
1384              }
1385              x[i] = a.join('+');
1386          }
1387      } else if (x) { // multiple-choice, short-answer and hybrid
1388          if (x.charAt(x.length-1)==',') {
1389              // HP 6.0 and 6.1 (always has trailing comma)
1390              x = x.substring(0, x.length-1).split(',');
1391          } else {
1392              // HP 6.2 (short answer also contains student entered text)
1393              x = x.split(' | ');
1394          }
1395          if (flag) {
1396              var a = new Array();
1397              if (flag==1 || flag==2 || flag==3) {
1398                  for (var i=0; i<x.length; i++) {
1399                      var is_correct = false;
1400                      if (x[i].length==1) { // single letter
1401                          var ii = x[i].charCodeAt(0) - 65;
1402                          if (I[q][3] && I[q][3][ii] && I[q][3][ii][2]) {
1403                              var is_correct = true;
1404                          }
1405                      }
1406                      if (is_correct) {
1407                          if (flag==2) {
1408                              a.push(x[i]);
1409                          }
1410                      } else {
1411                          if (flag==1 || flag==3) {
1412                              a.push(x[i]);
1413                          }
1414                      }
1415                  }
1416              }
1417              if (flag==1) {
1418                  x = a;
1419                  a = new Array();
1420              }
1421              if (flag==1 || flag==4) {
1422                  for (var i=0; i<I[q][3].length; i++) {
1423                      var s = String.fromCharCode(65+i);
1424                      for (var ii=0; ii<x.length; ii++) {
1425                          if (x[ii]==s) break;
1426                      }
1427                      if (ii==x.length) a.push(s);
1428                  }
1429              }
1430              x = a;
1431          }
1432          // convert answer indexes to values, if required
1433          if (JQuiz[6]==false) {
1434              for (var i=0; i<x.length; i++) {
1435                  if (x[i].length==1) { // single letter
1436                      var ii = x[i].charCodeAt(0) - 65;
1437                      if (I[q][3] && I[q][3][ii]) {
1438                          x[i] = I[q][3][ii][0];
1439                      }
1440                  }
1441              }
1442          }
1443      } else {
1444          x = new Array();
1445      }
1446      return x;
1447  }
1448  function GetRhubarbDetails(v) {
1449      qDetails = '';
1450      if (v==6) {
1451          var q = 0; // always zero
1452          var Q = getQ('Rhubarb', q);
1453          if (document.title) { // use quiz title as question name
1454              qDetails += hpHiddenField(Q+'name', document.title);
1455          }
1456          if (Rhubarb[0]) { // correct words
1457              var x = (HP[_correct][q]) ? HP[_correct][q] : '';
1458              if (Rhubarb[1]) { // count of correct words
1459                  for (var i=0,ii=0; i<x.length; i++) {
1460                      if (x[i]) ii++;
1461                  }
1462                  x = ii;
1463              }
1464              qDetails += hpHiddenField(Q+'correct', x);
1465          }
1466          if (Rhubarb[2]) { // wrong words
1467              var x = (HP[_wrong][q]) ? HP[_wrong][q] : '';
1468              if (Rhubarb[3]) { // count of wrong words
1469                  x = x.length;
1470              }
1471              qDetails += hpHiddenField(Q+'wrong', x);
1472          }
1473          if (Rhubarb[4]) { // ignored
1474              var x = '';
1475              qDetails += hpHiddenField(Q+'ignored', x);
1476          }
1477          if (Rhubarb[5]) { // hints
1478              var x = (HP[_hints][q]) ? HP[_hints][q] : '';
1479              qDetails += hpHiddenField(Q+'hints', x);
1480          }
1481      }
1482      return qDetails;
1483  }
1484  function GetSequiturDetails(v) {
1485      qDetails = '';
1486      if (v==6) {
1487          var q = 0; // always zero
1488          var Q = getQ('Sequitur', q);
1489          if (document.title) { // use quiz title as question name
1490              qDetails += hpHiddenField(Q+'name', document.title);
1491          }
1492          if (Sequitur[0]) { // number of correct buttons chosen
1493              var x = (HP[_correct][q]) ? HP[_correct][q] : '';
1494              qDetails += hpHiddenField(Q+'correct', x);
1495          }
1496          if (Sequitur[1]) { // number of wrong buttons chosen
1497              var x = (HP[_wrong][q]) ? HP[_wrong][q] : '';
1498              qDetails += hpHiddenField(Q+'wrong', x);
1499          }
1500      }
1501      return qDetails;
1502  }
1503  // *********************
1504  //    click event handlers
1505  // *********************
1506  function hpClick(x, args) {
1507      // x is the button type
1508      // args is either empty, a single argument, or an array of arguments
1509      var btn = (x==1) ? 'Hint' : (x==2) ? 'Clue' : (x==3) ? 'Check' : (x==4)  ? 'Enter' : '';
1510      if (btn) {
1511          // convert args to array, if necessary
1512          var t = typeof(args);
1513          if (t=='object') {
1514              // do nothing (args is already an array)
1515          } else if (t=='undefined') {
1516              args = new Array();
1517          } else {
1518              args = new Array(''+args);
1519          }
1520          // call handler for this kind of button
1521          var x = eval('hpClick'+btn+'('+hpVersion()+','+hpQuizType()+','+hpQuizVersion()+',args);');
1522      }
1523  }
1524  function hpClickHint(hp, t, v, args) {
1525      if (t==2 || t==5 || t==6 || t==7) { // JCloze, JMix, JQuiz, Rhubarb
1526          var q = args[0]; // clue/question number
1527          if (!HP[_hints][q]) HP[_hints][q] = 0;
1528          HP[_hints][q]++;
1529      }
1530      if (t==3) { // JCross
1531          if (v==6 || v==5) {
1532              var q = args[0]; // clue/question number
1533              var AD = args[1]; // direction ('A' or 'D')
1534              if (!HP[_hints][AD]) HP[_hints][AD] = new Array();
1535              if (!HP[_hints][AD][q]) HP[_hints][AD][q] = 0;
1536              HP[_hints][AD][q]++;
1537          }
1538      }
1539      return true;
1540  }
1541  function hpClickClue(hp, t, v, args) {
1542      if (t==2 || t==3 || t==6) { // JCloze or JCross, or JQuiz (ShowAnswer button)
1543          var q = args[0]; // clue/question number
1544          if (!HP[_clues][q]) HP[_clues][q] = 0;
1545          HP[_clues][q]++;
1546      }
1547      return true;
1548  }
1549  function hpClickCheck(hp, t, v, args) {
1550      if (t==2) { // JCloze
1551      if (v==5 || v==6) {
1552              var r = hpRottmeier();
1553              var already_correct = 'true';
1554              if (r==0) {
1555                  already_correct = (hp==5) ? 'State[i][4]==1' : 'State[i].AnsweredCorrectly==true';
1556              } else if (r==1) { // DropDown
1557                  already_correct = 'GapList[i][1].GapLocked==true';
1558              } else if (r==2.1) { // Find-It 3a
1559                  already_correct = 'GapList[i][1].ErrorFound==true';
1560              } else if (r==2.2) { // Find-It 3b
1561                  already_correct = 'GapList[i][1].GapSolved==true';
1562              }
1563              var i_max = I.length;
1564              for (var i=0; i<i_max; i++) {
1565                  if (eval(already_correct)) continue;
1566                  var g = '';
1567                  if (r==0 || r==2.2) {
1568                      g = GetGapValue(i);
1569                  } else if (r==1) { // DropDown
1570                      if (hp==5) {
1571                          g = eval('document.Cloze.Gap'+i+'.value');
1572                      } else if (hp==6) {
1573                          var ii = Get_SelectedDropValue(i);
1574                          if (isNaN(ii) || ii<0) { // 'null' || -1
1575                              g = ''; // no guess yet
1576                          } else {
1577                              if (window.MakeIndividualDropdowns) {
1578                                  var is_wrong = (ii!=0);
1579                                  g = I[i][1][ii][0];
1580                              } else {
1581                                  var is_wrong = (ii!=i);
1582                                  g = I[ii][1][0][0];
1583                              }
1584                          }
1585                      }
1586                  } else if (r==2.1 && i==args[0]) { // Find-It 3a
1587                      g = I[i][1][0][0];
1588                  }
1589                  if (g) {
1590                      if (!HP[_checks][i]) HP[_checks][i] = 0;
1591                      HP[_checks][i]++;
1592                      if (!HP[_guesses][i]) HP[_guesses][i] = new Array();
1593                      var ii = HP[_guesses][i].length;
1594                      // is this a new guess at this gap?
1595                      if (ii==0 || g!=HP[_guesses][i][ii-1]) {
1596                          HP[_guesses][i][ii] = g;
1597                          if (r==1) {
1598                              // Rottmeier DropDown 2.4
1599                              // do nothing
1600                          } else {
1601                              var G = g.toUpperCase();
1602                              var ii_max = I[i][1].length;
1603                              for (var ii=0; ii<ii_max; ii++) {
1604                                  if (window.CaseSensitive) {
1605                                      if (g==I[i][1][ii][0]) break;
1606                                  } else {
1607                                      if (G==I[i][1][ii][0].toUpperCase()) break;
1608                                  }
1609                              }
1610                              var is_wrong = (ii==ii_max);
1611                          }
1612                          if (is_wrong) { // guess is wrong
1613                              if (!HP[_wrong][i]) HP[_wrong][i] = new Array();
1614                              var ii_max = HP[_wrong][i].length;
1615                              for (var ii=0; ii<ii_max; ii++) {
1616                                  if (HP[_wrong][i][ii]==g) break;
1617                              }
1618                              if (ii==ii_max) {
1619                                  HP[_wrong][i][ii] = g;
1620                              }
1621                          } else { // guess is correct
1622                              HP[_correct][i] = g;
1623                          }
1624                      }
1625                  }
1626              }
1627          }
1628      }
1629      if (t==3) { // JCross
1630          if (v==5 || v==6) {
1631              var q = args[0]; // clue/question number
1632              for (var row=0; row<L.length; row++) {
1633                  for (var col=0; col<L[row].length; col++) {
1634                      var q = (v==5) ? C[row][col] : CL[row][col];
1635                      if (q) {
1636                          hpClickCheckJCrossV5V6(hp, v, 'A', q, row, col);
1637                          hpClickCheckJCrossV5V6(hp, v, 'D', q, row, col);
1638                      }
1639                  }
1640              }
1641          }
1642      }
1643      if (t==4) { // JMatch
1644          var a = new Array();
1645          var extra = ''; // extra js code to eval(uate)
1646          var guess = ''; // js code to eval(uate) guess
1647          var correct = ''; // js code to eval(uate) correct answer
1648          if (window.D && window.F) {
1649              // drag-and-drop, i.e. v5+ and v6+ (HP5 and HP6)
1650              a = F;
1651              guess = 'GetJMatchRHS(v,i)';
1652              correct = 'GetJMatchRHS(v,i,true)';
1653          } else  if (window.GetKeyFromSelect) {
1654              // HP6 v6
1655              a = Status;
1656              guess = 'GetJMatchRHS(v,i)';
1657              correct = 'GetJMatchRHS(v,i,true)';
1658          } else if (window.GetAnswer) {
1659              // HP5 v6,v5
1660              a = I;
1661              guess = "(I[i][2]==0||I[i][0]=='')?'':GetAnswer(i)";
1662              correct = 'I[i][3])';
1663          } else if (window.Draggables) {
1664              // HP5 v4
1665              a = Draggables;
1666              s = "Draggables[i].correct=='1'";
1667          } else if (window.CorrectAnswers) {
1668              // HP5 v3
1669              a = CorrectAnswers;
1670              guess = 'document.QuizForm.elements[i*2].selectedIndex';
1671              correct = 'CorrectAnswers[i]';
1672          }
1673          for (var i=0; i<a.length; i++) {
1674              // check this match has not already been finished
1675              if (!HP[_correct][i]) {
1676                  // do extra setup, if necessary
1677                  if (extra) eval(extra);
1678                  // get the guess, if any
1679                  var g = ''+eval(guess);
1680                  if (g) {
1681                      // is the guess correct?
1682                      if (g==eval(correct)) {
1683                          HP[_correct][i] = g;
1684                      } else { // wrong answer
1685                          // initialize wrong guess array if necessary
1686                          if (!HP[_wrong][i]) HP[_wrong][i] = new Array();
1687                          // check to see if the guess is already in the guess array
1688                          var i_max = HP[_wrong][i].length;
1689                          for (var ii=0; ii<i_max; ii++) {
1690                              if (HP[_wrong][i][ii]==g) break;
1691                          }
1692                          // add the guess if it was not found
1693                          if (ii==i_max) {
1694                              HP[_wrong][i][ii]=g;
1695                          } else {
1696                              g = null; // this is not a new answer
1697                          }
1698                      }
1699                      // increment checks for this question, if necessary
1700                      if (g) {
1701                          if (!HP[_checks][i]) HP[_checks][i] = 0;
1702                          HP[_checks][i]++;
1703                      }
1704                  }
1705              }
1706          }
1707      } // end if JMatch
1708      if (t==5) { // JMix
1709          // get question number (always 0)
1710          var q = args[0];
1711          // check question has not already been answered correctly
1712          if (!HP[_correct][q]) {
1713              // match current guess against possible correct answers
1714              var a_max = Answers.length;
1715              for (var a=0; a<a_max; a++) {
1716                  var i_max = Answers[a].length;
1717                  for (var i=0; i<i_max; i++) {
1718                      if (Answers[a][i] != GuessSequence[i]) break;
1719                  }
1720                  if (i==i_max) break; // correct answer was found
1721              }
1722              // at this point, (a==a_max) means guess is wrong
1723              // get array of segment texts in this g(uess)
1724              var g = GetJMixSequence(GuessSequence);
1725              // convert g(uess) array and to a s(tring)
1726              var s = '';
1727              var i_max = g.length;
1728              for (var i=0; i<i_max; i++) {
1729                  g[i] = trim(g[i]);
1730                  if (g[i]!='') {
1731                      s += (s=='' ? '' : '+') +  g[i];
1732                  }
1733              }
1734              if (s) {
1735                  if (a==a_max) { // wrong
1736                      if (!HP[_wrong][q]) HP[_wrong][q] = new Array();
1737                      var i = HP[_wrong][q].length;
1738                      HP[_wrong][q][i] = s;
1739                  } else { // correct
1740                      HP[_correct][q] = s;
1741                  }
1742                  // increment checks for this question
1743                  if (!HP[_checks][q]) HP[_checks][q] = 0;
1744                  HP[_checks][q]++;
1745              }
1746          }
1747      }
1748      if (t==6) { // JQuiz
1749          if (hp==5 || hp==6) {
1750              var q = args[0]; // clue/question number
1751              if (hp==5) {
1752                  if (v==5) {
1753                      var g = TrimString(eval('BottomFrame.document.QForm' + q + '.Guess').value);
1754                  } else if (v==6) {
1755                      var g = TrimString(eval('document.QForm.Guess').value);
1756                  }
1757              } else  { // HP 6
1758                  var g = args[1];
1759              }
1760              // increment check count
1761              if (!HP[_checks][q]) HP[_checks][q] = 0;
1762              HP[_checks][q]++;
1763              if (g) {
1764                  var G = g.toUpperCase(); // used for shortanswer only
1765                  var correct_answer = ''; // used for multiselect only
1766                  // set index of answer array in I (the question array)
1767                  var ans = (hp==5) ? 1 : 3;
1768                  var i_max = I[q][ans].length;
1769                  for (var i=0; i<i_max; i++) {
1770                      // is this a (possible) correct answer?
1771                      if (hp==5 || (hp==6 && I[q][ans][i][2])) {
1772                          if (hp==6 && I[q][2]==3) { // multiselect
1773                              correct_answer += (correct_answer  ? '&#43;' : '') + I[q][ans][i][0];
1774                          } else { // multichoice, shortanswer
1775                              if (window.CaseSensitive) {
1776                                  if (g==I[q][ans][i][0]) break;
1777                              } else {
1778                                  if (G==I[q][ans][i][0].toUpperCase()) break;
1779                              }
1780                          }
1781                      }
1782                  }
1783                  if (i==i_max && g!=correct_answer) { // wrong
1784                      if (!HP[_wrong][q]) HP[_wrong][q] = new Array();
1785                      var i_max = HP[_wrong][q].length;
1786                      for (var i=0; i<i_max; i++) {
1787                          if (HP[_wrong][q][i]==g) break;
1788                      }
1789                      if (i==i_max) HP[_wrong][q][i] = g;
1790                  } else {
1791                      HP[_correct][q] = g;
1792                  }
1793              }
1794          }
1795      }
1796      if (t==7) { // Rhubarb
1797          if (hp==6) {
1798              var q = 0; // question number (always zero)
1799              var g = args[0]; // InputWord from CheckGuess()
1800              if (g) {
1801                  var G = g.toUpperCase();
1802                  var i_max = Words.length;
1803                  for (var i=0; i<i_max; i++) {
1804                      if (G==Words[i].toUpperCase()) break;
1805                  }
1806                  if (i<i_max) { // correct
1807                      if (!HP[_correct][q]) HP[_correct][q] = new Array();
1808                      HP[_correct][q][i] = g;
1809                  } else { // wrong
1810                      if (!HP[_wrong][q]) HP[_wrong][q] = new Array();
1811                      var i_max = HP[_wrong][q].length;
1812                      for (var i=0; i<i_max; i++) {
1813                          if (G==HP[_wrong][q][i].toUpperCase()) break;
1814                      }
1815                      if (i==i_max) HP[_wrong][q][i] = g;
1816                  }
1817              }
1818          }
1819      }
1820      if (t==8) { // Sequitur
1821          if (hp==6) {
1822              var q = 0; // question number (always zero)
1823              if (CurrentCorrect==args[0]) { // correct button chosen
1824                  if (!HP[_correct][q]) HP[_correct][q] = 0;
1825                  HP[_correct][q]++;
1826              } else {
1827                  if (!HP[_wrong][q]) HP[_wrong][q] = 0;
1828                  HP[_wrong][q]++;
1829              }
1830          }
1831      }
1832      //return true;
1833  }
1834  function hpClickCheckJCrossV5V6(hp, v, AD, q, row, col) {
1835      // v is the version of Hot Potatoes
1836      // AD is the direction ('A' or 'D')
1837      // make sure HP[_checks] and HP[_correct] are initialized
1838      if (!HP[_checks][AD]) HP[_checks][AD] = new Array();
1839      if (!HP[_correct][AD]) HP[_correct][AD] = new Array();
1840      // get clue, if any
1841      var clue = (hp==5) ? eval('window.'+AD) : GetJCrossClue('Clue_'+AD+'_' + q);
1842      // is this a question that has not been answered correctly yet?
1843      if (clue && !HP[_correct][AD][q]) {
1844          var check = false;
1845          var guess = GetJCrossWord(G, row, col, (AD=='D'));
1846          var correct = GetJCrossWord(L, row, col, (AD=='D'));
1847          if (guess==correct) {
1848              HP[_correct][AD][q] = correct;
1849              check = true;
1850          } else if (guess) {
1851              // make sure HP[_wrong] is initialized
1852              if (!HP[_wrong][AD]) HP[_wrong][AD] = new Array();
1853              if (!HP[_wrong][AD][q]) HP[_wrong][AD][q] = new Array();
1854              // check this guess has not been entered before
1855              var i_max = HP[_wrong][AD][q].length;
1856              for (var i=0; i<i_max; i++) {
1857                  if (HP[_wrong][AD][q]==guess) break;
1858              }
1859              // add the guess if it has not been entered before
1860              if (i>=i_max) {
1861                  HP[_wrong][AD][q][i] = guess;
1862                  check = true;
1863              }
1864          }
1865          // update HP[_checks], if necessary
1866          if (check) {
1867              if (!HP[_checks][AD]) HP[_checks][AD] = new Array();
1868              if (!HP[_checks][AD][q]) HP[_checks][AD][q] = 0;
1869              HP[_checks][AD][q]++;
1870          }
1871      }
1872  }
1873  function hpClickEnter(hp, t, v, args) {
1874      if (t==3) { // JCross
1875          var q = args[0]; // clue/question number
1876          if (!HP[_enter][q]) HP[_enter][q] = 0;
1877          HP[_enter][q]++;
1878      }
1879      return true;
1880  }
1881  function GetJMatchQuestionDetails(hp, v) {
1882      var qDetails = '';
1883      // HP5.5 uses "I" for v5 and v6 JMatch quizzes
1884      // var hp5 = (window.I) ? true : false;
1885      // check the quiz version
1886      if (hp==5 || hp==6) {
1887          if (JMatch[1] && v==6.1) { // attempts
1888              qDetails += hpHiddenField('JMatch_attempts', Penalties+1);
1889          }
1890          // get number of questions
1891          var max_q = (hp==5 || v==6) ? Status.length : F.length;
1892          // get details for each question
1893          for (var q=0; q<max_q; q++) {
1894              // format 'Q' (a padded, two-digit version of 'q')
1895              var Q = getQ('JMatch', q);
1896              // add separator, if required
1897              if (JMatch[0] && (JMatch[1] || JMatch[2] || JMatch[3])) {
1898                  qDetails += makeSeparator(Q);
1899              }
1900              if (JMatch[1] && (hp==5 || v==6)) { // attempts
1901                  qDetails += hpHiddenField(Q+'attempts', Status[q][1]);
1902              }
1903              if (JMatch[2]) { // LHS text (the question)
1904                  var x = (v==5) ? I[q][0] : (v==6) ? GetJMatchText(q, 'LeftItem') : F[q][0];
1905                  qDetails += hpHiddenField(Q+'lhs', x);
1906              }
1907              if (JMatch[3]) { // correct answer (if any)
1908                  var x = HP[_correct][q] ? HP[_correct][q] : '';
1909                  qDetails += hpHiddenField(Q+'correct', x);
1910              }
1911              if (JMatch[4]) { // wrong answers (if any)
1912                  var x = HP[_wrong][q] ? HP[_wrong][q] : '';
1913                  qDetails += hpHiddenField(Q+'wrong', x);
1914              }
1915              if (JMatch[5]) { // checks
1916                  var x = HP[_checks][q] ? HP[_checks][q] : 0;
1917                  qDetails += hpHiddenField(Q+'checks', x);
1918              }
1919          } // end for
1920      }
1921      return qDetails;
1922  }
1923  // *********************
1924  //   library functions
1925  // *********************
1926  function pad(i, l) {
1927      var s = (i+'');
1928      while (s.length<l) s = '0' + s;
1929      return s;
1930  }
1931  function getQ(section, q) {
1932      // Q is a padded, two-digit version of the question number, 'q', prefixed by 'section'
1933      return section + '_q' + (q<9 ? '0' : '') + (q+1) + '_';
1934  }
1935  function makeSeparator(Q) {
1936      return     is_LMS() ? '' : hpHiddenField(Q.substring(0, Q.length-1), '---------------------------------');
1937  }
1938  function hpHiddenField(name, value, comma, forceHTML) {
1939      var field = '';
1940      var t = typeof(value);
1941      if (t=='string') {
1942          value = encode_entities(value);
1943      } else if (t=='object') { // array
1944          var values = value;
1945          var i_max = values.length;
1946          value = '';
1947          if (comma==null) comma = ',';
1948          for (var i=0; i<i_max; i++) {
1949              values[i] = trim(values[i]);
1950              if (values[i]!=null && values[i]!='') {
1951                  value += (i==0 ? '' : comma) +  encode_entities(values[i]);
1952              }
1953          }
1954      }
1955      if (is_LMS() && !forceHTML) {
1956          if (value && value.indexOf && value.indexOf('<')>=0 && value.indexOf('>')>=0) {
1957              value = '<![CDATA[' + value + ']]>';
1958          }
1959          field = '<field><fieldname>' + name + '</fieldname><fielddata>' + value + '</fielddata></field>';
1960      } else {
1961          field = '<input type=hidden name="' + name + '" value="' + value + '">';
1962      }
1963      return field;
1964  }
1965  function trim(s) {
1966      if (s==null) s = '';
1967      var i = 0;
1968      var ii = s.length;
1969      while (i<ii && s.charAt(i)==' ') {
1970          i++;
1971      }
1972      while (ii>i && s.charAt(ii-1)==' ') {
1973          ii--;
1974      }
1975      return s.substring(i, ii);
1976  }
1977  function encode_entities(s_in) {
1978      var i_max = (s_in) ? s_in.length : 0;
1979      var s_out = '';
1980      for (var i=0; i<i_max; i++) {
1981          var c = s_in.charCodeAt(i);
1982          // 34 : double quote .......["] &amp;
1983          // 38 : single quote .......['] &apos;
1984          // 43 : plus sign ..........[+]
1985          // 44 : comma ..............[,]
1986          // 60 : left angle bracket .[<] &lt;
1987          // 62 : right angle bracket [>] &gt;
1988          // >=128 multibyte character
1989          s_out += (c==43 || c==44 || c>=128) ? ('&#x' + pad(c.toString(16), 4) + ';') : s_in.charAt(i);
1990      }
1991      return s_out;
1992  }
1993  // *********************
1994  //    initialization
1995  //      functions
1996  // *********************
1997  function getTime(obj) {
1998      obj = obj ? obj : new Date();
1999      // get year, month and day
2000      //    for an LMS : yyyy-mm-dd
2001      //    for email  : DayName MonthName dd yyyy
2002      var s = is_LMS() ?
2003          obj.getFullYear() + '-' + pad(obj.getMonth()+1, 2) + '-' + pad(obj.getDate(), 2) :
2004          MSG[16][obj.getDay()] + ' ' + MSG[17][obj.getMonth()] + ' ' + pad(obj.getDate(), 2) + ' ' + obj.getFullYear()
2005      ;
2006      // get hours, minutes and seconds (hh:mm:ss)
2007      s += ' ' + pad(obj.getHours(), 2) + ':' + pad(obj.getMinutes(), 2) + ':' + pad(obj.getSeconds(), 2);
2008      // get time difference
2009      //    for an LMS : +xxxx
2010      //    for email  : GMT+xxxx
2011      var x = obj.getTimezoneOffset(); // e.g. -540
2012      if (!isNaN(x)) {
2013          s += ' ' + (is_LMS() ? '' : 'GMT') + (x<0 ? '+' : '-');
2014          x = Math.abs(x);
2015          s += pad(parseInt(x/60), 2) + pad(x - (parseInt(x/60)*60), 2);
2016      }
2017      return s;
2018  }
2019  function getFunc(fn) {
2020      if (typeof(fn)=='string') {
2021          fn = eval('window.' + fn);
2022      }
2023      return (typeof(fn)=='function') ? fn : null;
2024  }
2025  function getFuncCode(fn, extraCode, anchorCode, beforeAnchor) {
2026      var s = '';
2027      var obj = getFunc(fn);
2028      if (obj) {
2029          s = obj.toString();
2030          var i1 = s.indexOf('{')+1;
2031          var i2 = s.lastIndexOf('}');
2032          if (i1>0 && i1<i2) {
2033              s = s.substring(i1, i2);
2034          }
2035      }
2036      if (extraCode) {
2037          if (anchorCode) {
2038              if (beforeAnchor) {
2039                  s = replaceLast(anchorCode, extraCode + anchorCode, s);
2040              } else {
2041                  s = replaceLast(anchorCode, anchorCode + extraCode, s);
2042              }
2043          } else {
2044              if (beforeAnchor) {
2045                  s = extraCode + s;
2046              } else {
2047                  s = s + extraCode;
2048              }
2049          }
2050      }
2051      return s;
2052  }
2053  function getArgsStr(args, addQuotes) {
2054      // make s(tring) version of function args array
2055      var s = '';
2056      var i_max = args.length;
2057      for (var i=0; i<i_max; i++) {
2058          if (addQuotes) {
2059              s += '"' + args[i] + '",';
2060          } else {
2061              if (s) {
2062                  s += ',';
2063              }
2064              s += args[i];
2065          }
2066      }
2067      return s;
2068  }
2069  function getFuncArgs(fn, flag) {
2070      // flag==0 : return args as string
2071      // flag==1 ; return args as array
2072      var i = 0;
2073      var a = new Array();
2074      var obj = getFunc(fn);
2075      if (obj) {
2076          var s = obj.toString();
2077          var i1 = s.indexOf('(') + 1;
2078          var i2 = s.indexOf(')', i1);
2079          // add args to a(rray)
2080          while (i1>0 && i1<i2) {
2081              var i3 = s.indexOf(',', i1); // next comma
2082              if (i3<0 || i3>i2) i3 = i2;
2083              a[i++] = trim(s.substring(i1, i3));
2084              i1 = i3+1;
2085          }
2086      }
2087      return flag ? a : getArgsStr(a);
2088  }
2089  function getPrompt(fn) {
2090      // the LoginPrompt is the text string in the first prompt(...) statement
2091      //    v5 : in the StartUp function
2092      //    v6 : in the GetUserName function
2093      // Note: netscape uses double-quote as delimiter, others use single quote
2094      var s = getFuncCode(fn);
2095      var i1 = s.indexOf('prompt') + 8;
2096      var i2 = s.indexOf(s.charAt(i1-1), i1);
2097      var p = (i1>=8 && i2>i1) ? s.substring(i1, i2) : '';
2098      // make sure browser has decoded the unicode prompt properly
2099      // this check is mainly for ns4, but there may be others
2100      if (window.RegExp) {
2101          var r = new RegExp('u([0-9A-F]{4})');
2102          var m = r.exec(p);
2103          while (m) {
2104              p = p.replace(m[0], '&#' + parseInt(m[1], 16) + ';');
2105              m = r.exec(p);
2106          }
2107      }
2108      return p;
2109  }
2110  function getStartUpCode(fn) {
2111      // the main initialization code comes from the StartUp function
2112      //    v5 : the code before "UserName", if any,
2113      //         and the code after the 2nd subsequent '}'
2114      //    v6 : the code before and after 'GetUserName();'
2115      //         i.e. all the code except the call to 'GetUserName();'
2116      var s = getFuncCode(fn);
2117      var i1 = s.indexOf('GetUserName();');
2118      if (i1>=0) { // v6
2119          var i2 = i1 + 14;
2120      } else { // v5
2121          var i1 = s.indexOf('UserName');
2122          var i2 = s.indexOf('}', s.indexOf('}', i1+8)+1)+1;
2123      }
2124      return (0<i1 && i1<i2) ? s.substring(0, i1) + s.substring(i2) : '';
2125  }
2126  function is_LMS() {
2127      if (!window.hpCheckedForm) {
2128          window.hpCheckedForm = true;
2129          window.hpFoundForm = hpFindForm('store') ? true : false;
2130      }
2131      return hpFoundForm;
2132  }
2133  function hpFeedback() {
2134      if (FEEDBACK[0]) {
2135          var url = '';
2136          var html = '';
2137          if (FEEDBACK[1] && FEEDBACK[2]) { // formmail
2138              html += '<html><body>'
2139                  + '<form action="' + FEEDBACK[0] + '" method="POST">'
2140                  + '<table border="0">'
2141                  + '<tr><th valign="top" align="right">' + FEEDBACK[7] + ':</th><td>' + document.title + '</td></tr>'
2142                  + '<tr><th valign="top" align="right">' + FEEDBACK[8] + ': </th><td>'
2143              ;
2144              if (typeof(FEEDBACK[1])=='string') {
2145                  html += FEEDBACK[1] + hpHiddenField('recipient', FEEDBACK[1], ',', true);
2146              } else if (typeof(FEEDBACK[1])=='object') {
2147                  var i_max = FEEDBACK[1].length;
2148                  if (i_max==1) { // one teacher
2149                      html += FEEDBACK[1][0][0] + hpHiddenField('recipient', FEEDBACK[1][0][0]+' &lt;'+FEEDBACK[1][0][1]+'&gt;', ',', true);
2150                  } else if (i_max>1) { // several teachers
2151                      html += '<select name="recipient">';
2152                      for (var i=0; i<i_max; i++) {
2153                          html += '<option value="'+FEEDBACK[1][i][1]+'">' + FEEDBACK[1][i][0] + '</option>';
2154                      }
2155                      html += '</select>';
2156                  }
2157              }
2158              html += '</td></tr>'
2159                  +    '<tr><th valign="top" align="right">' + FEEDBACK[9] + ':</th>'
2160                  +    '<td><TEXTAREA name="message" rows="10" cols="40"></TEXTAREA></td></tr>'
2161                  +    '<tr><td>&nbsp;</td><td><input type="submit" value="' + FEEDBACK[6] + '">'
2162                  +     hpHiddenField('realname', FEEDBACK[2], ',', true)
2163                  +     hpHiddenField('email', FEEDBACK[3], ',', true)
2164                  +     hpHiddenField('subject', document.title, ',', true)
2165                  +     hpHiddenField('title', document.title, ',', true)
2166                  +     hpHiddenField('return_link_title', FEEDBACK[10], ',', true)
2167                  +     hpHiddenField('return_link_url', 'javascript:self.close()', ',', true)
2168                  +    '</td></tr></table></form></body></html>'
2169              ;
2170          } else if (FEEDBACK[1]) { // url only
2171              if (typeof(FEEDBACK[1])=='object') {
2172                  var i_max = FEEDBACK[1].length;
2173                  if (i_max>1) { // several teachers
2174                      html += '<html><body>'
2175                          + '<form action="' + FEEDBACK[0] + '" method="POST" onsubmit="this.action+=this.recipient.options[this.recipient.selectedIndex].value">'
2176                          + '<table border="0">'
2177                          + '<tr><th valign="top" align="right">' + FEEDBACK[7] + ':</th><td>' + document.title + '</td></tr>'
2178                          + '<tr><th valign="top" align="right">' + FEEDBACK[8] + ': </th><td>'
2179                      ;
2180                      html += '<select name="recipient">';
2181                      for (var i=0; i<i_max; i++) {
2182                          html += '<option value="'+FEEDBACK[1][i][1]+'">' + FEEDBACK[1][i][0] + '</option>';
2183                      }
2184                      html += '</select>';
2185                      html += '</td></tr>'
2186                          +    '<tr><td>&nbsp;</td><td><input type="submit" value="' + FEEDBACK[6] + '">'
2187                          +    '</td></tr></table></form></body></html>'
2188                      ;
2189                  } else if (i_max==1) { // one teacher
2190                      url = FEEDBACK[0] + FEEDBACK[1][0][1];
2191                  }
2192              } else if (typeof(FEEDBACK[1])=='string') {
2193                  url = FEEDBACK[0] + FEEDBACK[1];
2194              }
2195          } else {
2196              url = FEEDBACK[0];
2197          }
2198          if (url || html) {
2199              var w = openWindow(url, 'feedback', FEEDBACK[4], FEEDBACK[5], 'RESIZABLE,SCROLLBARS', html);
2200              if (!w) {
2201                   // unable to open popup window
2202                  alert(MSG[18]);
2203              }
2204          }
2205      }
2206  }
2207  // ********************
2208  //    intercept clicks
2209  // ********************
2210  function hpNewFunction(f, a, s) {
2211      if (window.C && C.safari) {
2212          if (f=='CheckAnswers') {
2213              if (s.indexOf('TotalChars-State[i].HintsAndChecks/')>=0) {
2214                  // special fix for "CheckAnswers" in JCloze
2215                  s = s.replace(/TotalChars-State\[i\]\.HintsAndChecks/g, '(TotalChars-State[i].HintsAndChecks)');
2216              }
2217              if (s.indexOf('TotalChars-GapList[x][1].HintsAndChecks/')>=0) {
2218                  // special fix for "CheckAnswers" in JCloze (Find-It)
2219                  s = s.replace(/TotalChars-GapList\[x\]\[1\]\.HintsAndChecks/g, '(TotalChars-GapList[x][1].HintsAndChecks)');
2220              }
2221              if (s.indexOf('CorrectLetters-Penalties/')>=0) {
2222                  // special fix for "CheckAnswers" in JMatch
2223                  s = s.replace(/CorrectLetters-Penalties/g, '(CorrectLetters-Penalties)');
2224              }
2225              if (s.indexOf('TotCorrectChoices-Penalties/')>=0) {
2226                  // special fix for "CheckAnswers" in JMix (v6)
2227                  s = s.replace(/TotCorrectChoices-Penalties/g, '(TotCorrectChoices-Penalties)');
2228              }
2229              if (s.indexOf('TotalCorrect-Penalties/')>=0) {
2230                  // special fix for "CheckAnswers" in JMix (v6+) Drag-and_Drop
2231                  s = s.replace(/TotalCorrect-Penalties/g, '(TotalCorrect-Penalties)');
2232              }
2233          }
2234          if (s.indexOf('replace(\\[')>=0) {
2235              s = s.replace(/\\\[/g, '/\\[');
2236              s = s.replace(/\\\]/g, '\\]/g');
2237          }
2238          if (s.indexOf('for (i')>=0 || s.indexOf('for (x')>=0) {
2239              s = s.replace(/for \(/g, 'for (var ');
2240          }
2241          eval('window.' + f + '=function(' + getArgsStr(a) + '){' + s + '}');
2242      } else {
2243          eval('window.' + f + '=new Function(' + getArgsStr(a, true) + 's);');
2244      }
2245  }
2246  function hpInterceptFeedback() {
2247      // modify the function which writes feedback
2248      //     v6: ShowMessage(Feedback)
2249      //        but Rhubarb prints score in other functions, so use 'CheckFinished'
2250      //    v5: WriteFeedback(Feedback)
2251      //    v4: WriteFeedback(Stuff)
2252      //    v3: WriteFeedback(Feedback) [except JMatch]
2253      //    v3: CheckAnswer()           [JMatch only]
2254      var f = '';
2255      if (window.CheckWord) { // Rhubarb
2256          f = 'CheckFinished';
2257          window.FEEDBACK = null;
2258      } else if (window.ShowText) { // Sequitur
2259          f = 'CheckAnswer';
2260          window.FEEDBACK = null;
2261      } else { // JBC, JCloze, JCross, JMatch, JMix, JQuiz
2262          f = window.ShowMessage ? 'ShowMessage' : window.WriteFeedback ? 'WriteFeedback' : 'CheckAnswer';
2263      }
2264      if (f) {
2265          var s = getFuncCode(f) + 'Finish();';
2266          var a = getFuncArgs(f, true);
2267          if (a[0] && window.FEEDBACK && FEEDBACK[0]) {
2268              s = a[0] + "+='<br /><br />" + '<a href="javascript:hpFeedback();">' + FEEDBACK[6] + "</A>';" + s;
2269          }
2270          hpNewFunction(f, a, s);
2271      }
2272  }
2273  function hpInterceptHints() {
2274      // modify the function which shows hints
2275      //    JBC:    none
2276      //    JCloze  v3-v6: ShowHint()
2277      //    JCross  v3: Cheat(), v4: ShowHint(), v5-v6[HP5]: ShowHint(Across,x,y,BoxName), v6[HP6]: ShowHint(Across,ClueNum,x,y,BoxId)
2278      //    JMatch: none
2279      //    JMix    v5-v6: CheckAnswer(CheckType=1)
2280      //    JQuiz   v3: CheckAnswer(ShowHint=true, QNum), v4: CheckAnswer(ShowHint=true), v5-v6[HP5]: CheckAnswer(ShowHint=true,QNum), v6[HP6]: ShowHint(QNum)
2281      var x = ''; // extra code, if any
2282      if (window.Cheat) {
2283          // JCross v3 ?
2284      } else if (window.ShowHint) {
2285          var f = 'ShowHint';
2286          var a = getFuncArgs(f, true);
2287          if (a.length==0) {
2288              if (window.FindCurrent) {
2289                  // JCloze v3-v6
2290                  x = 'var q=window.Locked?-1:FindCurrent();if(q>=0&&GetHint(q))hpClick(1,q);';
2291              } else {
2292                  // JCross v4
2293                  // work out which box would have a hint added
2294                  // work out which question that box is part of using GridMap and WinLetters
2295              }
2296          } else if (a[0]=='Across') {
2297              if (a[1]=='ClueNum') {
2298                  // JCross v6 [HP6]
2299                  x = "var args=new Array(ClueNum,Across?'A':'D');hpClick(1,args);";
2300              } else if (a[1]=='x' && a[2]=='y') {
2301                  // JCross v5-v6 [HP5]
2302                  x = "var args=new Array(C[x][y],Across?'A':'D');hpClick(1,args);";
2303              }
2304          } else if (a[0]=='QNum') {
2305              // JQuiz v6[HP6]
2306              x = 'hpClick(1,QNum);';
2307          }
2308      } else if (window.Hint) {
2309          // Rhubarb
2310          var f = 'Hint';
2311          var a = getFuncArgs(f, true);
2312          x = 'hpClick(1,0);'; // question number is always zero
2313  
2314      } else if (window.CheckAnswer) {
2315          var f = 'CheckAnswer';
2316          var a = getFuncArgs(f, true);
2317          if (a[0]=='ShowHint') {
2318              if (a[1]=='QNum') {
2319                  // JQuiz v3, v5-v6[HP5]
2320                  x = 'if(ShowHint)hpClick(1,QNum);';
2321              } else {
2322                  // JQuiz v4
2323                  x = 'if(ShowHint)hpClick(1,QNum-1);'; // QNum is a global variable
2324              }
2325          } else if (a[0]=='CheckType') {
2326              // JMix v5-v6
2327              x = 'if(CheckType==1)hpClick(1,0);'; // question number is always 0;
2328          }
2329      }
2330      // add the e(x)tra code, if any, to the start of the hint (f)unction
2331      if (x) {
2332          var s = getFuncCode(f, x, '', true);
2333          hpNewFunction(f, a, s);
2334      }
2335  }
2336  function hpInterceptClues() {
2337      // modify the function which shows clues (or ShowAnswers in JQuiz)
2338      //    JBC:    none
2339      //    JCloze  v3-v6: ShowClue(ItemNum)
2340      //    JCross  v3-v4: ShowClue(ClueNum), v5-v6: ShowClue(ClueNum,x,y)
2341      //    JMatch  none
2342      //    JMix    none
2343      //    JQuiz   ShowAnswers(QNum)
2344      var x = ''; // extra code, if any
2345      if (window.ShowClue) {
2346          var f = 'ShowClue';
2347          var a = getFuncArgs(f, true);
2348          if (a[0]=='ItemNum') {
2349              // JCloze (v3-v6)
2350              x = 'if(!window.Locked)hpClick(2,ItemNum);'; // v6 [HP6] uses window.Locked
2351          } else if (a[0]=='ClueNum') {
2352              if (a[1]=='x' && a[2]=='y') {
2353                  if (window.A && window.D) {
2354                      // JCross v5-v6 [HP5]
2355                      x = 'if(A[ClueNum]||D[ClueNum])hpClick(2,ClueNum);';
2356                  } else if (document.getElementById) {
2357                      // JCross v6 [HP6]
2358                      x = "if(document.getElementById('clue_' + ClueNum)||document.getElementById('Clue_D_' + ClueNum))hpClick(2,ClueNum);";
2359                  }
2360              } else {
2361                  if (window.AClues && window.DClues) {
2362                      // JCross v3-v4
2363                      x = 'if(AClues[ClueNum]||DClues[ClueNum])hpClick(2,ClueNum);';
2364                  }
2365              }
2366          }
2367      }
2368      // JQuiz: there is no "ShowClue" function but there is a "ShowAnswer" function
2369      if (window.ShowAnswers) {
2370          var f = 'ShowAnswers';
2371          var a = getFuncArgs(f, true);
2372          if (window.State) {
2373              if (window.ShowMessage) {
2374                  // JQuiz v6 [HP6]
2375                  x = 'if(State[QNum][0]<1)hpClick(2,QNum);';
2376              } else if (window.WriteFeedback) {
2377                  // JQuiz v3-v4
2378                  x = 'if(State[QNum-1][0]<1)hpClick(2,QNum-1);';
2379              }
2380          } else if (window.Status) {
2381              // JQuiz v5-v6 [HP5]
2382              x = 'if(Status[QNum][0]<0)hpClick(5,QNum);';
2383          }
2384      }
2385      // add the e(x)tra code, if any, to the start of the clue (f)unction
2386      if (x) {
2387          var s = getFuncCode(f, x, '', true);
2388          var s = getFuncCode(f, '', '', true);
2389          hpNewFunction(f, a, s);
2390      }
2391  }
2392  function hpInterceptChecks() {
2393      // modify the function which handles checks
2394      //    JBC:    none
2395      //    JCloze  CheckAnswers()
2396      //        NB: Rottmeier Find-It 3a: CheckText(GapState,GapId)
2397      //    JCross  none
2398      //    JMatch  HP5 v3, v5, v6: CheckAnswer(), HP5 v4: CheckResults(), HP6: CheckAnswers()
2399      //    JMix    CheckAnswer(CheckType)
2400      //    JQuiz
2401      //        HP5: CheckAnswer(ShowHint, QNum)
2402      //        HP6: CheckMCAnswer, CheckMultiSelAnswer, CheckShortAnswer
2403      //    Rhubarb  CheckWord(InputWord)
2404      //    Sequitur CheckAnswer(Chosen, Btn)
2405      // HP6 JQuiz has three "Check Answer" functions
2406      var f = new Array('CheckMCAnswer', 'CheckMultiSelAnswer', 'CheckShortAnswer');
2407      for (var i=0; i<f.length; i++) {
2408          if (eval('window.' + f[i])) {
2409              var a = getFuncArgs(f[i], true);
2410              var x = "";
2411              if (f[i]=='CheckMCAnswer') {
2412                  x += "var args=new Array(QNum,I[QNum][3][ANum][0]);";
2413              } else if (f[i]=='CheckShortAnswer') {
2414                  x += ""
2415                  + "var obj=document.getElementById('Q_'+QNum+'_Guess');"
2416                  + "var args=new Array(QNum,obj.value);"
2417                  ;
2418              } else if (f[i]=='CheckMultiSelAnswer') {
2419                  x += ""
2420                  + "var g='';"
2421                  + "for (var ANum=0; ANum<I[QNum][3].length; ANum++){"
2422                  +     "var obj=document.getElementById('Q_'+QNum+'_'+ANum+'_Chk');"
2423                  +     "if (obj.checked)g+=(g?'&#43;':'')+I[QNum][3][ANum][0];"
2424                  + "}"
2425                  + "var args=new Array(QNum,g);"
2426                  ;
2427              }
2428              if (x) {
2429                  x = "if(!Finished&&State[QNum].length&&State[QNum][0]<0){" + x + "hpClick(3,args)}";
2430                  var s = getFuncCode(f[i], x, '', true);
2431                  hpNewFunction(f[i], a, s);
2432              }
2433          }
2434      }
2435      var f = ''; // function name
2436      var x = ''; // extra code, if any
2437      if (window.CheckAnswer) {
2438          f = 'CheckAnswer';
2439          var a = getFuncArgs(f, true);
2440          if (a[0]=='ShowHint') {
2441              if (a[1]=='QNum') {
2442                  // JQuiz v3, v5-v6[HP5]
2443                  x = 'if(!ShowHint&&Status[QNum][0]<1)hpClick(3,QNum);';
2444              } else {
2445                  // JQuiz v4
2446                  x = 'if(!ShowHint&&State[QNum-1][0]<1)hpClick(3,QNum-1);'; // QNum is a global variable
2447              }
2448          } else if (a[0]=='CheckType') {
2449              // JMix v5-v6
2450              x = 'if(CheckType==0)hpClick(3,0);'; // question number is always 0;
2451          } else if (a[0]=='Chosen') {
2452              // Sequitur
2453              x = 'if (!(CurrentNumber==TotalSegments||AllDone||Btn.innerHTML==IncorrectIndicator))hpClick(3,Chosen);';
2454          }
2455      } else if (window.CheckWord) {
2456          f = 'CheckWord';
2457          var a = getFuncArgs(f, true);
2458          if (a[0]=='InputWord') {
2459              // Rhubarb
2460              x = 'if(!window.AllDone)hpClick(3,InputWord);';
2461          }
2462      } else if (window.CheckText && !window.CheckAnswers) { // Rottmeier Find-It (3a)
2463          f = 'CheckText';
2464          var a = getFuncArgs(f, true);
2465          if ((a[0]=='bool' && a[1]=='item') || (a[0]=='GapState' && a[1]=='GapId')) {
2466              x = 'if(!window.Finished&&'+a[0]+')hpClick(3,'+a[1]+');';
2467          }
2468      }
2469      if (f) {
2470          var s = getFuncCode(f, x, '', true);
2471          hpNewFunction(f, a, s);
2472      }
2473      // JMatch has three possible check functions, depending on the version
2474      // (NB: other quiz types also have these functions)
2475      var f = new Array('CheckAnswers', 'CheckAnswer', 'CheckResults');
2476      for (var i=0; i<f.length; i++) {
2477          if (eval('window.' + f[i])) {
2478              var a = getFuncArgs(f[i], true);
2479              if (a.length==0) {
2480                  var s = getFuncCode(f[i], "hpClick(3);", '', true);
2481                  hpNewFunction(f[i], a, s);
2482                  break; // out of the loop
2483              }
2484          }
2485      }
2486  }
2487  // ***************
2488  //  fix IE5 and NS6
2489  // ***************
2490  // add Array.push if required (allows v6 quizzes to run on ie5win)
2491  if (Array.prototype && Array.prototype.push==null) {
2492      Array.prototype.push = new Function("x", "this[this.length]=x");
2493  }
2494  // add attachEvent function, if required (allows HP5 v6 quizzes to run on ie5mac)
2495  //     NOTE: to allow v6 quizzes on ie5mac, the following code
2496  //     needs to be inserted BEFORE the Hot Potatoes javascript
2497  if (window.attachEvent==null) {
2498      window.attachEvent = new Function('evt', 'fn', 'eval("window."+evt+"="+fn)');
2499  }
2500  if (document.attachEvent==null) {
2501      document.attachEvent = new Function('evt', 'fn', 'eval("document."+evt+"="+fn)');
2502  }
2503  // fix the ShowMessage function for NS6
2504  // by removing calls to a button's "focus()" method
2505  if (navigator.userAgent.indexOf("Netscape6")>=0 && window.ShowMessage) {
2506      var s = ShowMessage.toString();
2507      var r = new RegExp('document\\.getElementById\\((\'|")FeedbackOKButton(\'|")\\)\\.focus\\(\\);', 'gi');
2508      s = s.substring(s.indexOf('{')+1, s.lastIndexOf('}')).replace(r, '');
2509      window.ShowMessage = new Function('Feedback', s);
2510  }
2511  // ns6.0 (in JMix at least) has an error in the FocusAButton function too
2512  // this could be fixed as follows ...
2513  //if (window.FocusAButton) {
2514  //    window.FocusAButton = new Function('return true');
2515  //}
2516  // however, ns6.0 then crashes completely when the mouse moves over a link, so don't bother
2517  // Hot Potatoes quiz sniffing
2518  // === v3 ===
2519  // JBC uses "QuizForm", which contains elements called "Q*_**" (* and ** start at 1)
2520  // JCloze uses "Cloze" form
2521  // JCross uses "Crossword" form
2522  // JMatch uses "QuizForm", which contains elements called "1,2,3..x" and "x+1,x+2...",
2523  //     and "CheckForm" form, which contains an element called "ScoreBox"
2524  //     it is also the only HP quiz type to use an array called "CorrectAnswers"
2525  // JQuiz uses "QForm*" forms (* starts at 1), which each contain an element called "Guess"
2526  // === v4 ===
2527  // JBC uses "QForm" form in "QuestionDiv", which contains elements called "FB* (* starts at 0)"
2528  // JCloze uses "Cloze" form in "QuestionDiv"
2529  // JCross uses "Crossword" form in "CWDiv"
2530  // JMatch uses "ExCheck" form in "TitleDiv"
2531  // (no JMix in hp4)
2532  // JQuiz uses "QForm" form in "QuestionDiv", which contains an element called "Answer"
2533  // === v5 ===
2534  // JBC uses "QForm" form, which contains elements called "FB_*_**" (* and ** start at 0)
2535  // JCloze uses "Cloze" form
2536  // JCross writes out "AnswerForm" from a variable called "GetAnswerOpener"
2537  //    HP5.3: uses "AnswerForm" in "BottomFrame"
2538  //    HP5.5: uses "AnswerForm" in "TopFrame", but it is only there when an answer is being input
2539  // JMatch uses "QForm" form, which contains elements called "sel*" (which disappear by the time the quiz is finished)
2540  // JMix uses "ButtonForm"
2541  // JQuiz uses "QForm*" and "Buttons*" (one for each question)
2542  // === v6 ===
2543  // JBC uses "QForm" form (elements have no name or id)
2544  // JCloze uses "Cloze" form (elements have no name or id)
2545  // JCross does not use any forms,
2546  //    HP5: has "GridDiv" in "MainDiv"
2547  //    HP6: has "Clues" table in "MainDiv"
2548  // JMatch has "MatchDiv" in "MainDiv"
2549  //    HP5: uses "QForm" form, which contains elements called "sel*"
2550  //     HP6: uses "QForm" form, which contains elements called "s*_**"
2551  // JMix does not use any forms, but has "SegmentDiv" in "MainDiv"
2552  // JQuiz
2553  //    HP5: uses "QForm" form, which contains an element called "Guess"
2554  //    HP6: has "Questions" ordered list in "MainDiv"
2555  // === v6+ ===
2556  // JMatch has DIVs called "D*" and  "F*" (* starts at 0)
2557  // JMix has DIVs called "D*" and  "Drop*" (* starts at 0)
2558  // useful sniffing tools (Cut and Paste to browser address box)
2559  //javascript:var s="";var x=new quiz_obj();for(X in x)s+=","+X+"="+x[X];alert(s.substring(1));
2560  //javascript:var s="";var x=document.layers;for(var i=0;i<x.length;i++)s+=","+x[i].name;alert(s.substring(1))
2561  //javascript:var s="";var x=document.forms;for(var i=0;i<x.length;i++)s+=","+x[i].id;alert(s.substring(1))
2562  //javascript:var s="";var x=document.forms;for(var i=0;i<x.length;i++)s+=","+x[i].name;alert(s.substring(1))
2563  //javascript:var s="";var x=document.forms.QForm.elements;for(var i=0;i<x.length;i++)s+=","+x[i].id;alert(s.substring(1))
2564  //javascript:var s="";var x=document.forms.QForm.elements;for(var i=0;i<x.length;i++)s+=","+x[i].name;alert(s.substring(1))
2565  function hpDetectQuiz() {
2566      // "sniff" (=detect) the quiz's type and intended browser version
2567      // and cache the values in a global variable called "quiz"
2568      // Hot Potatoes version
2569      //    5 : HP5.3 (mac) or HP5.5 (win)
2570      //    6 : HP6.0 (mac) or HP6.0 (win)
2571      // intended browser version
2572      //    3   : ns3, ie3 (frames)
2573      //    4   : ns4, ie4 (cross browser dhtml)
2574      //    5   : ie5 (frames, send results via CGI)
2575      //    6   : ie6, op7, gecko (w3 standards)
2576      //    6.1 : "drag and drop" versions of JMatch and JMix v6
2577      // quiz type
2578      //     0 : unknown
2579      //    1 : jbc
2580      //    2 : jcloze
2581      //    3 : jcross
2582      //    4 : jmatch
2583      //    5 : jmix
2584      //    6 : jquiz
2585      //    7 : rhubarb (TexToys)
2586      //    8 : Sequitur (TexToys)
2587      // rottmeier quiz type
2588      //    1 : drop-down (JCloze)
2589      //    2 : find-it (JCloze)
2590      // shortcut to window object
2591      var w = window;
2592      // create the global "quiz" object, if necessary
2593      if (!w.quiz) w.quiz = new Object();
2594      // Hot Potatoes version
2595      //     HP6 v6:    Client()
2596      //     HP5 v4-v5: BrowserCheck()
2597      //        v3:    WinStringToMac() [JCloze, JCross, JQuiz]
2598      //        v3:    winrightchar     [JBC, JMatch]
2599      //        v3:    DownTime()       [JBC, JCloze, JQuiz]
2600      if (!quiz.hp) {
2601          quiz.hp = (w.Client) ? 6 : (w.BrowserCheck) ? 5 : (w.WinStringToMac || w.winrightchar) ? 5 : -1;
2602      }
2603      // check the version and type are not already set
2604      if (!quiz.v || !quiz.t) {
2605          // initialize version and type
2606          var v = 0;
2607          var t = 0;
2608          // set shortcuts to DOM objects
2609          var d = document;
2610          var f = d.forms;
2611          if (f.QuizForm && f.CheckForm && w.CorrectAnswers) {
2612              v = 3;
2613              t = 4; // jmatch
2614          } else if (w.FeedbackFrame && w.CodeFrame) {
2615               v = 3;
2616              f = CodeFrame.document.forms;
2617              t = (f.QuizForm) ? 1 : (f.Cloze) ? 2 : (f.Crossword) ? 3 : (f.QForm1) ? 6 : 0;
2618          } else if (w.DynLayer) {
2619              v = 4;
2620              if (d.layers) {
2621                  // for NS4, adjust "f" to point to a forms object in a layer
2622                  var lyr = d.QuestionDiv || d.CWDiv || d.TitleDiv || null;
2623                  if (lyr) f = lyr.document.forms;
2624              }
2625              t = (f.QForm && f.QForm.FB0) ? 1 : (f.Cloze) ? 2 : (f.Crossword) ? 3 : (f.ExCheck) ? 4 : (f.QForm && f.QForm.Answer) ? 6 : 0;
2626          } else if (w.TopFrame && w.BottomFrame) {
2627              v = 5;
2628              f = BottomFrame.document.forms;
2629              t = (f.QForm && f.QForm.elements[0].name.substring(0,3)=='FB_') ? 1 : (f.Cloze) ? 2 : (w.GetAnswerOpener && GetAnswerOpener.indexOf('AnswerForm')>=0) ? 3 : (f.QForm && w.RItems) ? 4 : (f.ButtonForm) ? 5 : (f.QForm0 && f.Buttons0) ? 6 : 0;
2630          } else if (hpObj(d, 'MainDiv')) {
2631              v = 6;
2632              var obj = (f.QForm) ? f.QForm.elements : null;
2633              t = (obj && obj.length>0 && obj[0].id=='') ? 1 : (f.Cloze) ? 2 : (hpObj(d, 'GridDiv') || hpObj(d, 'Clues')) ? 3 : hpObj(d, 'MatchDiv') ? 4 : hpObj(d, 'SegmentDiv') ? 5 : ((f.QForm && f.QForm.Guess) || hpObj(d, 'Questions')) ? 6 : 0;
2634          } else if (hpObj(d, 'D0')) {
2635              v = 6.1; // drag and drop (HP5 and HP6)
2636              t = (hpObj(d, 'F0')) ? 4 : (hpObj(d, 'Drop0')) ? 5 : 0;
2637          } else if (w.Words && f.Rhubarb) {
2638              v = 6;
2639              t = 7; // rhubarb (TexToys)
2640          } else if (w.Segments && hpObj(d, 'Story')) {
2641              v = 6;
2642              t = 8; // sequitur (TexToys)
2643          }
2644          quiz.v = v; // intended browser version
2645          quiz.t = t; // quiz type
2646      }
2647  }
2648  function hpRottmeier() {
2649      hpDetectQuiz();
2650      if (typeof(quiz.r)=='undefined') { // first-time only
2651          quiz.r = 0;
2652          if (quiz.t==2) { // JCloze
2653              if (quiz.hp==5) { // HP5
2654                  // ??
2655              } else if (quiz.hp==6) { // HP6
2656                  if (window.Create_StateArray) { // Rottmeier
2657                      var obj = new Create_StateArray();
2658                      if (typeof(obj.GapLocked)=='boolean') {
2659                          quiz.r = 1; // drop-down (v2.4)
2660                      } else if (typeof(obj.ErrorFound)=='boolean') {
2661                          if (typeof(obj.GapSolved)!='boolean') {
2662                              quiz.r = 2.1; // find-it (v3.1a)
2663                          } else {
2664                              quiz.r = 2.2; // find-it (v3.1b)
2665                          }
2666                      }
2667                      obj = null; // prevents memory leakage on some versions of IE
2668                  }
2669              }
2670          }
2671      }
2672      return quiz.r;
2673  }
2674  function hpVersion() {
2675      hpDetectQuiz();
2676      return quiz.hp;
2677  }
2678  function hpQuizType() {
2679      hpDetectQuiz();
2680      return quiz.t;
2681  }
2682  function hpQuizVersion() {
2683      hpDetectQuiz();
2684      return quiz.v;
2685  }
2686  function hpScoreEngine(score_i, a, s, aa, ss, count_c, count_i) {
2687      // calculate the score for the quiz so far
2688      // score_i : amount by which to increment "score"
2689      // a  : outer array
2690      // s  : condition, if any, on outer array (=a)
2691      //      if true, the score will be incremented by "score_i"
2692      // aa : inner array, if any
2693      // ss : condition, if any, on inner array (=aa)
2694      // count_c : condition, if any, on which "count" is to be incremented
2695      // count_i : amount by which to increment "count"
2696      // "a" and "aa" may be passed as arrays or strings containing the name of an array
2697      // "s" and "ss" are strings containing an expression to be eval(uated)
2698      // "score_i", "count_i" and "count_c" strings containing an expression to be eval(uated)
2699      var score = 0;
2700      var count = 0;
2701      // set default condition to increment "count", and amount by which to increment the count
2702      if (count_c==null) count_c = "true";
2703      if (count_i==null) count_i = "1";
2704      // set length of outer array. if any
2705      var l = (typeof(a)=="string") ? eval(a + ".length") : a ? a.length : 0;
2706      // loop through outer array
2707      for (var i=0; i<l; i++) {
2708          if (s==null && ss==null) {
2709              score += eval(score_i);
2710              if (eval(count_c)) count += eval(count_i);
2711          } else if (s) {
2712              score += eval(s) ? eval(score_i) : 0;
2713              if (eval(count_c)) count += eval(count_i);
2714          } else if (ss) {
2715              // set length of inner array, if any
2716              var ll = (typeof(aa)=="string") ? eval(aa + ".length") : aa ? aa.length : 0;
2717              // loop through inner array. checking inner condition
2718              for (var ii=0; ii<ll; ii++) {
2719                  score += eval(ss) ? eval(score_i) : 0;
2720                  if (eval(count_c)) count += eval(count_i);
2721              }
2722          }
2723      }
2724      if (count) {
2725          // get p(enalties) for JCross and JMatch (and JMix ?)
2726          if (window.Penalties) {
2727              score -= (Penalties - (hpFinished() ? 0 : 1));
2728          }
2729          // adjust count for Find-It 3a and 3b
2730          if (window.TotWrongChoices) {
2731              if (window.CheckText && !window.CheckAnswers) { // Find-It 3a
2732                  // this seems a little odd, but will replicate behavior of CalculateScore()
2733                  count = score + TotWrongChoices;
2734              } else {
2735                  count += TotWrongChoices;
2736              }
2737          }
2738          score = Math.floor(100*score/count);
2739          if (score<0) { // just in case
2740              score = 0;
2741          }
2742      }
2743      return score;
2744  }
2745  function hpScore() {
2746      var x = ''; // score
2747      var hp = hpVersion();
2748      var t = hpQuizType();
2749      var v = hpQuizVersion();
2750      if (t==1) { // jbc
2751          if (v==3) x = hpScoreEngine(1, DoneStatus, "i>0 && a[i]=='0'"); // doesn't work
2752          else if (v==4) x = hpScoreEngine(1, DoneStatus, "a[i]==0");    // doesn't work
2753          else if (v==5 || v==6) x = hpScoreEngine("a[i][3]", Status, "a[i][3]");
2754      } else if (t==2) { // jcloze
2755          if (v==3 || v==4) x = hpScoreEngine("a[i]", Scores);
2756          else if (hp==5) x = hpScoreEngine("a[i][3]", State); // v==5 && v==6
2757          else if (hp==6) {
2758              var r = hpRottmeier();
2759              if (r==0) x = x = hpScoreEngine("a[i].ItemScore", State);
2760              else if (r==1) x = hpScoreEngine("a[i][1].Score", GapList, "a[i][1].GapLocked"); // dropdown
2761              else if (r==2.1) x = hpScoreEngine(1, GapList, "a[i][1].ErrorFound"); // Find-It 3a
2762              else if (r==2.2) x = hpScoreEngine("a[i][1].Score", GapList, "a[i][1].GapSolved"); // Find-It 3b
2763          }
2764      } else if (t==3) { // jcross
2765          if (v==3) x = hpScoreEngine(1, CorrectAnswers, "document.QuizForm.elements[i*2].selectedIndex==a[i]");
2766          else if (v==4) x = hpScoreEngine(1, WinLetters, "ConvertCase(GetBoxValue(i),1).charAt(0)==a[i].charAt(0)");
2767          else if (v==5 || v==6) {
2768              if (window.CaseSensitive) { // HP 6.2
2769                  x = hpScoreEngine(1, L, "", "L[i]", "L[i][ii] && L[i][ii]==G[i][ii]", "L[i][ii]");
2770              } else {
2771                  x = hpScoreEngine(1, L, "", "L[i]", "L[i][ii] && L[i][ii].toUpperCase()==G[i][ii].toUpperCase()", "L[i][ii]");
2772              }
2773          }
2774      } else if (t==4) { // jmatch
2775          if (v==3) x = hpScoreEngine(1, CorrectAnswers, "document.QuizForm.elements[i*2].selectedIndex==a[i]");
2776          else if (v==4) x = hpScoreEngine(1, Draggables, "a[i].correct=='1'");
2777          else if (v==5) x = hpScoreEngine(1, I, "I[i][2]<1 && I[i][0].length>0 && Status[i][0]==1");
2778          else if (v==6) x = hpScoreEngine(1, Status, "Status[i][0]==1");
2779          else if (v==5.1 || v==6.1) x = hpScoreEngine(1, D, "D[i][2]==D[i][1] && D[i][2]>0", "", "", "i<F.length");
2780      } else if (t==5) { // jmix
2781          // there was no v3 or v4 of JMix
2782          if (v==5 || v==6 || v==6.1) x = Math.floor(100*(Segments.length-Penalties)/Segments.length);
2783      } else if (t==6) { // jquiz
2784          if (hp==5) {
2785              if (v==3 || v==4) x = hpScoreEngine("a[i][4]/10", State, "a[i][0]==1");
2786              else if (v==5 || v==6) x = hpScoreEngine("a[i][4]/10", Status, "a[i][0]==1", "", "", "true", "1");
2787          } else if (hp==6) {
2788              if (v==6) x = hpScoreEngine("I[i][0]*a[i][0]", State, "a[i]&&a[i][0]>=0", "", "", "a[i]", "I[i][0]");
2789          }
2790      } else if (t==7) { // rhubarb
2791          if (v==6) {
2792              x = Math.floor(100*Correct/TotalWords);
2793          }
2794      } else if (t==8) { // sequitur
2795          if (v==6) {
2796              var myTotalPoints = TotalPoints - (hpFinished() ? 0 : (OptionsThisQ-1));
2797              x = Math.floor(100*ScoredPoints/myTotalPoints);
2798          }
2799      }
2800      return x; // result
2801  }
2802  function hpFinishedEngine(a, s, aa, ss) {
2803      // determine whether or not all quistions in a quiz are finished
2804      // a  : outer array
2805      // s  : condition, if any, on outer array
2806      //      if true for any element in "a", the quiz is NOT finished
2807      // aa : inner array, if any
2808      // ss : condition, if any, on inner array
2809      //      if true for any element in "aa", the quiz is NOT finished
2810      // the arrays "a" and "aa" may be passed as arrays or strings to be eval(uated)
2811      // the conditions "s" and "ss" are specified as strings to be eval(uated)
2812      // assume a positive result
2813      var x = true;
2814      // set length of outer array. if any
2815      var l = (typeof(a)=="string") ? eval(a + ".length") : a ? a.length : 0;
2816      // loop through outer array
2817      for (var i=0; i<l; i++) {
2818          // do outer condition, if any
2819          if (s && eval(s)) x = false;
2820          // set length of inner array, if any
2821          var ll = (typeof(aa)=="string") ? eval(aa + ".length") : aa ? aa.length : 0;
2822          // loop through inner array. checking inner condition
2823          for (var ii=0; ii<ll; ii++) {
2824              if (ss && eval(ss)) x = false;
2825          }
2826      }
2827      return x;
2828  }
2829  function hpTimedOut() {
2830      // v5 uses "min" and "sec"
2831      // v6 uses Seconds
2832      return (typeof(self.Seconds)=='number' && Seconds==0) || (typeof(self.min)=='number' && min==0 && typeof(self.sec)=='number' && sec==0);
2833  }
2834  function hpFinished() {
2835      // assume false result
2836      var x = false;
2837      var hp = hpVersion();
2838      var t = hpQuizType();
2839      var v = hpQuizVersion();
2840      if (t==1) { // jbc
2841          if (v==3) x = hpFinishedEngine(DoneStatus, "i>0 && a[i]=='0'");
2842          else if (v==4) x = hpFinishedEngine(DoneStatus, "a[i]==0");
2843          else if (v==5 || v==6) x = hpFinishedEngine(Status, "a[i][0]==0");
2844      } else if (t==2) { // jcloze
2845          var r = hpRottmeier();
2846          if (r==1) x = hpFinishedEngine(GapList, "a[i][1].GapLocked==false"); // drop-down
2847          else if (r==2.1) x = hpFinishedEngine(GapList, "a[i][1].ErrorFound==false"); // find-it 3a
2848          else if (r==2.2) x = hpFinishedEngine(GapList, "a[i][1].GapSolved==false"); // find-it 3b
2849          else if (v==3 || v==4 || v==5 || v==6) x = hpFinishedEngine(I, "CheckAnswer(i)==-1");
2850          // also:   else if (v==5 || v==6) x = hpFinishedEngine(State, "a[i][4]!=1")
2851      } else if (t==3) { // jcross
2852          if (v==3) x = hpFinishedEngine(document.Crossword.elements, "ConvertCase(is.mac?unescape(MacStringToWin(a[i].value)):a[i].value,1)!=Letters[i]");
2853          else if (v==4) x = hpFinishedEngine(WinLetters, "ConvertCase(GetBoxValue(i),1).charAt(0) != a[i].charAt(0)");
2854          else if (v==5 || v==6) {
2855              if (window.CaseSensitive) { // 6.2
2856                  x = hpFinishedEngine(L, "", "L[i]", "L[i][ii] && L[i][ii]!=G[i][ii]");
2857              } else {
2858                  x = hpFinishedEngine(L, "", "L[i]", "L[i][ii] && L[i][ii].toUpperCase()!=G[i][ii].toUpperCase()");
2859              }
2860          }
2861      } else if (t==4) { // jmatch
2862          if (v==3) x = hpFinishedEngine(CorrectAnswers, "document.QuizForm.elements[i*2].selectedIndex != a[i]");
2863          else if (v==4) x = hpFinishedEngine(Draggables, "a[i].correct!='1'");
2864          else if (v==5) x = hpFinishedEngine(I, "I[i][2]<1 && I[i][0].length>0 && Status[i][0]<1 && GetAnswer(i)!=I[i][3]");
2865          else if (v==6) x = hpFinishedEngine(Status, "Status[i][0]<1");
2866          else if (v==5.1 || v==6.1) x = hpFinishedEngine(D, "", F, "F[ii][1]==D[i][1]&&D[i][1]!=D[i][2]");
2867      } else if (t==5) { // jmix
2868          // there was no v3 or v4 of JMix
2869          if (v==5 || v==6 || v==6.1) x = !hpFinishedEngine(Answers, "a[i].join(',')=='" + GuessSequence.join(',') + "'");
2870      } else if (t==6) { // jquiz
2871          if (v==3 || v==4) x = hpFinishedEngine(State, "a[i][0]==0");
2872          else if (v==5 || v==6) {
2873              if (hp==5) x = hpFinishedEngine(Status, "a[i][0]<1");
2874              else if (hp==6) x = hpFinishedEngine(State, "a[i] && a[i][0]<0");
2875          }
2876      } else if (t==7) { // rhubarb
2877          if (v==6) x = hpFinishedEngine(DoneList, "a[i]==1");
2878      } else if (t==8) { // sequitur
2879          if (v==6) x = (CurrentNumber==TotalSegments || AllDone);
2880      }
2881      return x;
2882  }
2883  function hpObj(d, id) {
2884      return d.getElementById ? d.getElementById(id) : d.all ? d.all[id] : d[id];
2885  }
2886  function GetViewportHeight() {
2887      if (window.innerHeight) {
2888          return innerHeight;
2889      } else {
2890          if (hpIsStrict()) {
2891              return document.documentElement.clientHeight;
2892          } else {
2893              return document.body.clientHeight;
2894          }
2895      }
2896  }
2897  function hpIsStrict() {
2898      if (!window.hpStrictIsSet) {
2899          window.hpStrictIsSet = true;
2900          window.hpStrict = false;
2901          var s = document.compatMode;
2902          if (s && s=="CSS1Compat") { // ie6
2903              window.hpStrict = true;
2904          } else {
2905              var obj = document.doctype;
2906              if (obj) {
2907                  var s = obj.systemId || obj.name; // n6 || ie5mac
2908                  if (s && s.indexOf("strict.dtd") >= 0) {
2909                      window.hpStrict = true;
2910                  }
2911              }
2912          }
2913      }
2914      return window.hpStrict;
2915  }
2916  // **************
2917  //  initialization
2918  // **************
2919  hpInterceptFeedback();
2920  hpInterceptHints();
2921  hpInterceptClues();
2922  hpInterceptChecks();
2923  function hpFindForm(formname, w) {
2924      if (w==null) w = self;
2925      var f = w.document.forms[formname];
2926      if (f==null && w.frames) {
2927          for (var i=0; i<w.frames.length; i++) {
2928                  f = hpFindForm(formname, w.frames[i]);
2929                  if (f) break;
2930          }
2931      }
2932      return f;
2933  }
2934  function Finish(quizstatus) {
2935      var mark = hpScore();
2936      window.hpForm = hpFindForm('store');
2937      if (hpForm) { // LMS
2938          hpForm.starttime.value = getTime(Start_Time);
2939          hpForm.endtime.value = getTime();
2940          hpForm.mark.value = mark;
2941          hpForm.detail.value = '<?xml version="1.0"?><hpjsresult><fields>'+GetQuestionDetails()+'</fields></hpjsresult>';
2942          if (hpForm.status) {
2943              if (!quizstatus) {
2944                  // 4=completed, 3=abandoned, 2=timed-out or 1=in-progress
2945                  quizstatus = hpFinished() ? 4 : hpTimedOut() ? 2 : 1;
2946              }
2947              hpForm.status.value = quizstatus;
2948          }
2949          if (!window.hpQuizResultsSent) {
2950              if (hpForm.status && quizstatus==4) {
2951                  window.hpQuizResultsSent = true;
2952              }
2953              if (quizstatus==4) { // completed
2954                  // wait 2 seconds for student to see feedback
2955                  setTimeout("hpForm.submit();", 2000);
2956              } else {
2957                  hpForm.submit();
2958              }
2959          }
2960      } else if (hpFinished()) {
2961          SendAllResults(mark);
2962      }
2963  }
2964  // create form to send results
2965  if (DB[7] && DB[8] && !is_LMS()) {
2966      ResultForm = ''
2967          + '<html><body>'
2968          + '<form name="Results" action="" method="post" enctype="x-www-form-encoded">'
2969          +     hpHiddenField('recipient', '')
2970          +     hpHiddenField('subject', '')
2971          +     hpHiddenField('Exercise', '')
2972          +     hpHiddenField('realname', '')
2973          +     hpHiddenField('Score', '')
2974          +     hpHiddenField('Start_Time', '')
2975          +     hpHiddenField('End_Time', '')
2976          +     hpHiddenField('title', 'Thanks!')
2977          + '</form>'
2978          + '</body></html>'
2979      ;
2980  }
2981  // reassign the StartUp function
2982  var p = getPrompt(window.GetUserName || window.StartUp);
2983  var c = getStartUpCode(window.StartUp);
2984  if (p && c) {
2985      if (window.C && C.safari) {
2986          eval('window.StartUp=function(){QuizLogin("' + p + '")}');
2987          eval('window.StartQuiz=function(){' + c + '}');
2988      } else {
2989          window.StartUp = new Function('QuizLogin("' + p + '")');
2990          window.StartQuiz = new Function(c);
2991      }
2992      // "QuizLogin" finshes by calling "StartQuiz"
2993  }
2994  // reassign the SendResults function
2995  window.SendResults = SendAllResults;
2996  // set start time
2997  var Start_Time = new Date();
2998  //-->


Generated: Wed Jan 14 11:33:29 2009 Cross-referenced by PHPXref 0.7