<!--

// primitive browser sniffing
ns = (document.layers)? true:false
ie = (document.all)? true:false

// THE FOLLOWING VARIABLES AND FUNCTIONS GENERATE AND EVALUATE AN EXAM

var question = 0 ;				// number of the question selected
var recording = [ , ] ;			// question recording array
var recorded = 0 ;				// number of questions recorded
var recorder = false ;			// determines interface dependent output
var taped = "" ;				// recorded string

// function to open a recording window
var Recorder = null ;
function OpenRecorder() {
	if (Recorder == null || Recorder.closed) {
		Recorder = 	window.open ("","The_Recorder") ;
		Recorder.focus() }
	else alert('An old recorder window is open. Please close the old recorder before recording the new exam.')
}	// End of OpenRecorder

// exam question recording function
function recordit () {

	if ( numberset != 0 ) {
		recording[recorded] = Knitem[question];
		recorded ++ ;
		document.forms['TheQuestion'].Recorded.value = recorded }
	else alert ( 'No question is available for recording.' ) ;

} ;	// end createScript

// exam generating function
function playExam () {

	var convert = 0 ;
	var temp = ["",""] ;

	function tape ( tapestring ) {
		taped = taped + tapestring } ;
		
	function openscript ( source ) {
		if ( ie ) tape ( '<script type="text/javascript" ' + source + '>\r' ) ;
		if ( ns ) tape ( '&lt;script type="text/javascript" ' + source + '&gt;\r' ) } ;
		
	function closescript () {
		if ( ie ) tape ( '</script>\r' ) ;
		if ( ns ) tape ( '&lt;/script&gt;\r' ) } ;
			
	if ( recorded > 0 ) {
//	
	tape( '<html>\r<head>\r<title>' + document.forms["TheQuestion"].Headline.value + '</title>\r' ) ;
	openscript ('src="saq_engine.js"') ;
	closescript () ;
	openscript ('') ;
	tape( '\rvar examlength = ' + recording.length + ' ;' ) ;
	tape( '\rexam = true ;' ) ;
	tape( '\rvar excludeList = [ "-", "h", " " ] ;' ) ;
	tape( '\rvar mismatchlimit = 1 ;' ) ;
	tape( '\rvar acceptCourtesy = true ;' ) ;

	if ( document.forms["TheQuestion"].Case.checked ) tape( '\rvar caseInsensitive = true ;' )
	else tape( '\rvar caseInsensitive = false ;' ) ;

	if ( document.forms["TheQuestion"].Rules.checked ) tape( '\rvar byRules = true ;' )
	else tape( '\rvar byRules = false ;' ) ;

	if ( document.forms["TheQuestion"].Mismatch.checked ) tape( '\rvar allowMismatch = true ;' )
	else tape( '\rvar allowMismatch = false ;' ) ;

	tape( '\rvar sizelimit = ' + document.forms["TheQuestion"].Smallest.value + ' ;' ) ;
	tape( '\rvar CourtesyWords = [ ' + document.forms["TheQuestion"].Courtesy.value + ' ] ;\r' ) ;
	tape( '\rsubmitted = setTimeout("submitExam()",' + (document.forms["TheQuestion"].Minutes.value*60*1000) + ') ;\r' ) ;

	tape( 'var Register1 = ["",""] ;\r' )
	tape( 'var grades = ["",""] ;\r' )
	tape( '\rvar Register2 = [ ' ) ;
	for ( i = 0 ; i < recording.length ; i++ ) {
		tape( ' [' ) ;
		for ( j = 0 ; j < recording[i][1].length ; j++ ) {
			temp.length = 0 ;
			for ( convert = 0 ; convert < recording[i][1][j].length ; convert ++ )
				temp[convert] = recording[i][1][j].charCodeAt(convert) ;
			if ( j == recording[i][1].length-1 ) tape ( '"' + temp.join(",") + '"' )
			else tape ( '"' + temp.join(",") + '", ' ) ;
			} ; // end for
		if ( i == recording.length-1 ) tape ( ']' )
		else tape ( '],' ) ;
		} ; // end for
	tape( ' ] ;\r' ) ;
	tape( '\rfor ( i = 0 ; i < examlength ; i ++ ) Register1[i] = "" ;\r' ) ;
	tape( '\rfor ( i = 0 ; i < examlength ; i ++ ) grades[i] = "" ;\r' ) ;
	tape( '\rfunction Register( box ) {\r' ) ;
	
	tape( 'Register1[box-1] = eval ( "document.forms[' + '\'Exam\'].TheAnswer"' + ' + box + ".value" ) } ;\r' ) ;

	closescript () ;
	tape ( '</head>\r<body>\r' ) ;
	tape( '<H3>' + document.forms["TheQuestion"].Headline.value + '</H3>\r' ) ;
	tape( '<p>' + document.forms["TheQuestion"].Opening.value + '</p>\r' ) ;
	tape( '<form name="Exam">\r' ) ;
	tape( '<p><select name="Name" size="1">\r' ) ;
	tape( '<option value="Name 1">Name 1</option>\r' ) ;
	tape( '<option value="Name 2">Name 2</option>\r' ) ;
    tape( '<option value="Name 3">Name 3</option>\r' ) ;
    tape( '<option value="Campbell">Campbell</option>\r' ) ;
    tape( '<option value="Slomianka">Slomianka</option>\r' ) ;
	tape( '</select></p>\r' ) ;
	tape( '<table cellpadding="10">\r' ) ;
	for ( i = 0 ; i < recording.length ; i ++ ) {
		tape( '<tr>\r' ) ;
		tape( '<td><img src="' + recording[i][2] + '"></td>\r' ) ;
		tape( '<td><p>' + recording[i][0] + '</p>\r' ) ;
		tape( '<p><input type="text" name="TheAnswer' + (i+1) + '" size="40"></p>\r' ) ;
		tape( '<p><input type="button" name="Submitter' + (i+1) + '" value="I think that\'s it" onclick="Register(' + (i+1) + ');"></p></td>\r') ;
		tape( '</tr>\r\r' ) ;
		} ; // end for	
	tape( '</table>\r</form>\r' ) ;
	tape( '\r<form name="Result" method="post" action="henchman.asp"><input type="hidden" name="Submission"></form>\r' ) ;
	tape( '\r</body>\r</html>' ) ;
	OpenRecorder () ;
	Recorder.document.write(taped) ;
	Recorder.document.close()

	} else alert ( "You have not recorded any questions." ) ; // end if else
	
} ; // end playExam

// exam submission function
function submitExam () {

	var results = "" ;
	var nameindex = 0 ;
	var r = 0 ;
	
	Checkanswer() ;
	for ( r = 0 ; r < document.forms["Exam"].Name.options.length ; r ++ )
		if ( document.forms["Exam"].Name.options[r].selected ) nameindex = r ;
	results = document.forms["Exam"].Name.options[nameindex].value + "&" ;
	results = results + grades.join("&") + "&Score: " + numberright + "/" + examlength + "&" + (Math.round(numberright/examlength*100) + "&%") ;
	document.forms["Result"].Submission.value = results ;
	document.forms["Result"].submit() ;
	clearTimeout(submitted) ;

} ; // end submitExam 
		
// THE FOLLOWING VARIABLES AND FUNCTIONS CONTROL THE USERINTERFACE	

// general variables

var randomnumber = 0 ; 			// intermittent random number storage
var Qs = 0 ;					// number of available questions
var lastnorepeats = [ , ] ;		// norepeats array
var counttonorepeats = 0 ; 		// counting up to norepeats
var norepeats = 0 ;				// maximal norepeats-array size

// question properties

var Knitem = [ , ] ; 			// question properties array
var imagesource = " " ;			// question external source property
var answers = [ , ] ;			// question possible correct answers array property
var questiontext = " " ;		// question text property
var probability = 1 ;			// question drop value property

// performance tracking variables

var numberset = 0 ;				// number of set questions
var numberright = 0 ;			// number of right answers
var performance = 0 ;			// performance score in %
var nextone = false ;			// prevents logging correct answers more than once
var changed = false ;			// temporary evaluation rules restriction
var exam = false ;				// practice exam setting

// function displaying a new image in the question form
function showImage( newSource ) {
	if (ie || ns) document.images["image"].src = newSource ;
	if (!ns && !ie) document.getElementById("image").src = newSource ;
} ; // end showImage

// initialize questions
function Knit(questiontext, answers, filesource, probability) {
	Qs++ ;
	if ( filesource == "none" ) filesource = eval ( "'" + filesource + Qs + "'" ) ;
	Knitem[Qs] = [ questiontext, answers, filesource, probability] ;
	norepeats = Math.round( Qs * norepeatfrac ) ;
	if ( recorder ) document.forms['TheQuestion'].NumSet.value = Qs ;
} ; // end Knit

// reset form, counter variables and arrays
function Prepare() {
	// initialize variables
	nextone = false ;
	Qs = -1 ;
	lastnorepeats.length = 0 ;
	counttonorepeats = 0 ;
	Knit(" ", [" "], " ", " ", 0) ;
	numberset = 0 ;
	
	// initialize display
	document.forms['TheQuestion'].QuestionText.value = " " ;
	document.forms['TheQuestion'].TheAnswer.value = " " ;
	document.forms['TheQuestion'].NumSet.value = " " ;
	document.forms['TheQuestion'].Percent.value = " " ;
	if ( recorder ) document.forms['TheQuestion'].Recorded.value = recorded ;
	
} ; // end prepare

// function to set a questions in TheQuestion form of the page
function setaquestion () {

	question = 0 ;

	// check if one or more subject areas have been selected
	if ( Qs == 0 ) alert ('Select one or more subjects for the test!') ;

	// set a random question
	if ( Qs != 0 && stepper == 0 ) {
	
		nextone = true ;
		tryagain = true ;

		// select a question and test if it has been used recently
		while ( question == 0 ) {
			randomnumber = Math.random()* Qs ;
			question = Math.round(randomnumber) 
			for ( i = 0; i < lastnorepeats.length; i++ )
				if ( Knitem[question][1][0] == lastnorepeats[i] ) question = 0 ;
			if ( Math.random() > Math.abs(Knitem[question][3]) ) question = 0
			} ; // end while
		
		// update lastnorepeats array
		lastnorepeats[counttonorepeats] = Knitem[question][1][0] ;
		counttonorepeats++ ;
		if ( counttonorepeats == norepeats) counttonorepeats = 0 ;

		// temporary block of allowMismatch
		if ( Knitem[question][3] < 0 && allowMismatch == true ) {
			allowMismatch = false ;
			changed = true
			} ; // end if
		// reset allowMismatch
		if ( Knitem[question][3] >= 0 && allowMismatch == false && changed == true) {
			allowMismatch = true ;
			changed = false
			} ; // end if

		// increment the number of questions set
		numberset++ ;
		// display the question
		document.forms["TheQuestion"].QuestionText.value = Knitem[question][0] ;
		showImage(Knitem[question][2]) ;
		// reset answer
		document.forms["TheQuestion"].TheAnswer.value = ""
		} ; // end if
	
	// sequential stepping through all questions of the selected subjects
	if ( Qs != 0 && stepper != 0 && numberset < Qs ) {
		numberset ++ ;
		question = numberset ;
		nextone = true ;
		tryagain = true ;

		// temporary block of allowMismatch
		if ( Knitem[question][3] < 0 && allowMismatch == true ) {
			allowMismatch = false ;
			changed = true
			} ; // end if
		// reset allowMismatch
		if ( Knitem[question][3] >= 0 && allowMismatch == false && changed == true ) {
			allowMismatch = true ;
			changed = false
			} ; // end if

		// display the question
		document.forms["TheQuestion"].QuestionText.value = Knitem[question][0] ;
		showImage(Knitem[question][2]) ;
		// reset answer
		document.forms["TheQuestion"].TheAnswer.value = ""
		if ( recorder ) {
			document.forms["TheQuestion"].TheAnswer.value = Knitem[question][1].join() ;
			document.forms["TheQuestion"].Percent.value = numberset ;
			} ; // end if			
		} ; // end if
	
	if ( Qs != 0 && stepper != 0 && numberset == Qs && !recorder )
		document.forms["TheQuestion"].TheAnswer.value = "You have stepped through all questions" ;
	

} ; // end setaquestion

function score () {

	performance = Math.round(numberright / numberset * 100) ;
	document.forms["TheQuestion"].NumSet.value = numberset ;
	document.forms["TheQuestion"].Percent.value = performance
	
} ; // end score

// THE FOLLOWING FUNTIONS EVALUATE THE STUDENT RESPONSE

/* The following variables and functions analyse the student answer against the possible
right answers to the question */

var condensedAnswer = "" ;				// condensed student answer
var condensedCorrect = "" ;				// condensed correct answer
var answer = "" ;						// student answer to question
var wordlength = 0 ;					// number of characters in the submitted word
var truncatedAnswer = "" ;				// courtesy words removed from answer
var courteous = "false" ;				// indicates presence of a courtesy word
var tryagain = "true" ;					// a right answer has not yet been found

/* The following function stripps leading and tailing spaces from the student answer.
It also replaces multiple spaces bewteen words by a single space */

function strippSpaces ( word ) {

	var y = 0 ;						// counter variable
	var stripped = [ "" , "" ] ;	// word stripping array
	var strip = true ;	

	// replace all double spaces by single spaces
	while ( strip ) {
		if ( word.search("  ") != -1 ) word = word.replace("  "," ") ;
		if ( word.search("  ") == -1 ) strip = false } ;	

	// determine the number of remaining characters in the submitted word
	wordlength = word.length ;

	// convert the word string to an array
	for ( y = 0 ; y < wordlength ; y ++ ) stripped[y] = word.charAt(y) ;

	// remove tailing space from students answer
	if ( stripped[stripped.length-1] == " " ) stripped.length = stripped.length - 1 ;

	// remove tailing space
	stripped.reverse() ;
	if ( stripped[stripped.length-1] == " " ) stripped.length = stripped.length - 1 ;
	stripped.reverse() ;
  
	// return the array as a string
	return stripped.join("") ;
	
} ; // end strippSpaces

/* The following function strips "courtesy words" from the students answer. Words to be
considered "courtesy words" must be defined in the CourtesyWords array of this script.
Search for "Help! CourtesyWords" to locate the array and help on its use. */

function truncateCourtesy ( word ) {

	var z = 0 ;					// counter variable
	var lastwordstart = -1 ;	// locates the last space in the student answer
	var tempWord = "" ;
	var tempCourtesy = "" ;
	var lastword = "" ;

	courteous = false ;
	firstOfCourtesy = "" ;
    
	lastwordstart = word.lastIndexOf(" ") + 1 ;
	if ( lastwordstart != 0 ) {
		lastword = word.substr(lastwordstart) ;
		for ( z = 0 ; z < CourtesyWords.length ; z ++ )
			if (  lastword == CourtesyWords[z] ) {
				tempWord = word.substr(0,lastwordstart-1) ;
				firstOfCourtesy = CourtesyWords[z].charAt(0) ;
				courteous = true } ; // end if
     } ; // end if
  
	lastwordstart = word.lastIndexOf(" ") + 1 ;
	if ( lastwordstart != 0 && !courteous && byRules )  {
		lastword = word.substr(lastwordstart) ;
		for ( z = 0 ; z < CourtesyWords.length ; z ++ ) {
			tempCourtesy = condense(CourtesyWords[z]) ;
			if ( condense(lastword) == tempCourtesy && byRules ) {
				tempWord = word.substr(0,lastwordstart-1) ;
				firstOfCourtesy = CourtesyWords[z].charAt(0) ;
				courteous = true } ; // end if
			if ( justOneWrong(condense(lastword),tempCourtesy) && allowMismatch && tempCourtesy.length > sizelimit ) {
				tempWord = word.substr(0,lastwordstart-1) ;
				firstOfCourtesy = CourtesyWords[z].charAt(0) ;
				courteous = true } ; // end if			 
		} ; // end for
	} ; // end if

	if ( courteous && acceptCourtesy ) word = tempWord ;
	return word ;
	
} ; // end truncateCourtesy

/* The following function condenses the students answer to the minimum of characters
considered to represent the correct answer */

function condense ( word ) {

	var condensed = ["",""] ;			// word condensation array
	var m = 0 ;							// counter variable
	var n = 0 ;							// counter variable
	var endings = [ "e ", "ic ", "es ", "s " ] ;	// wordendings to strip
	
	word = word + " " ;
	for ( i = 0 ; i < endings.length ; i ++ )
		word = word.replace(endings[i]," ") ;
	
	// determine the number of characters in the submitted word
	wordlength = word.length ;
	
	// condense answer (strip double letters, final e, final es, middle h, spaces and dashes)
	for ( i = 0; i < wordlength; i++ ) {
		if ( i == 0 ) {
			includeLetter = true ;
			for ( n = 0; n < excludeList.length; n ++ )
				if ( word.charAt(i) == excludeList[n] ) includeLetter = false ;
			if ( includeLetter || word.charAt(i) == "h" ) {
				condensed[m] = word.charAt(i);
				m ++ } ; // end if
			} ; // end if
			
		if ( i != 0 ) {
			includeLetter = true ; 
			for ( n = 0; n < excludeList.length; n ++ ) {
				if ( word.charAt(i) == excludeList[n] ) includeLetter = false } ;
			if ( includeLetter ) {
				if ( m == 0 ) {
					condensed[m] = word.charAt(i) ;
					m ++ } ; // end if	
				if ( m != 0 && word.charAt(i) != condensed[m-1] ) {
					condensed[m] = word.charAt(i) ;
					m ++ } ; // end if
				} ; // end if
			} ; // end if
		} ; // end for

	// join the remaining characters into the condensed word and return it
	return condensed.join("") ;

} ; // end condense

/* The following function compares two strings and returns true if there is
only one mismatch or one transposition of characters between the string. */

function justOneWrong ( word, correct ) {

	var lengthDifference = 0 ;
	var stillOK = true ;
	var mismatches = 0 ;

	lengthDifference = word.length - correct.length ;
	if ( Math.abs(lengthDifference) > 1 ) stillOK = false ;
 
	if ( lengthDifference == 0 )
		for ( l = 0 ; l < word.length ; l ++ )
			if ( word.charAt(l) != correct.charAt(l) ) 
				if ( word.charAt(l) == correct.charAt(l+1) && word.charAt(l+1) == correct.charAt(l) ) l ++ 
				else mismatches ++ ;
	if ( mismatches > mismatchlimit ) stillOK = false ;	

	if ( stillOK && lengthDifference == 1 )
		for ( l = 0 ; l < correct.length ; l ++ )
			if ( word.charAt(l) != correct.charAt(l) )
				if ( word.substr( l + 1) != correct.substr(l) ) stillOK = false ;
           
	if ( stillOK && lengthDifference == -1 )
		for ( l = 0 ; l < word.length ; l ++ )
			if ( word.charAt(l) != correct.charAt(l) )
				if ( correct.substr( l + 1) != word.substr(l) ) stillOK = false ;

	return stillOK ;

} ; // end justOneWrong

/* The following functions analyse the student answer against the possible correct
answers. If necessary the function will use the function called condense to account for
possible spelling errors */

function analyse ( answer, correct ) {

	var i = 0 ;		// counter variable
	var x = 0 ;		// counter variable
	var k = 0 ;		// counter variable
	var tempcorrect = "" ;
		
	// check for a perfect match
	if ( answer == correct ) {
		tryagain = false ;
		if ( !exam ) alert ( "Your answer was correct. My most sincere congratulations!" ) ;
		} ; // end if

	// check for a case-insensitive match
	if ( tryagain && caseInsensitive ) {
		tempcorrect = correct.toLowerCase() ;
		if ( answer.toLowerCase() == tempcorrect && tryagain ) {
			tryagain = false ;
			if ( !exam ) alert ( "Your answer was correct. My most sincere congratulations!" ) } ; // end if
		} ; // end if
		
	// check for a case-insensitive match excluding courtesy words
	if ( tryagain && acceptCourtesy ) {	
		truncatedAnswer = truncateCourtesy( answer.toLowerCase() ) ;
		tempcorrect = correct.toLowerCase() ;
		if ( truncatedAnswer == tempcorrect ) {
			tryagain = false ;
			if ( !exam ) alert ( "A bit generous with words, but correct. Congratulations!" )
			} ; // end if
		if ( courteous && tryagain )
			if ( truncatedAnswer + firstOfCourtesy == tempcorrect ) {
				tryagain = false ;
				if ( !exam ) alert ( "A bit generous with words, but correct. Congratulations!" )
				} ; // end if
		} ; // end if
		
	// check for a case-insensitive imperfect match excluding a courtesy words
	// if present but WITHOUT accepting one missing, added or incorrect character
	if ( tryagain && byRules ) {
		tempcorrect = correct.toLowerCase() ;
		if ( truncateCourtesy(tempcorrect).length > sizelimit ) {
			condensedCorrect = condense ( tempcorrect ) ;
			if ( condense(truncatedAnswer) == condensedCorrect || condense(answer.toLowerCase()) == condensedCorrect ) {
				tryagain = false ;
				if ( !exam ) alert ( "Yeah, the correct answer is " + Knitem[question][1][0] + ", but your spelling and/or phrasing may be a bit off - improve it if necessary.") ;
				} ; // end if
			} ; // end if
		} ; // end if
    
	// check for a case-insensitive imperfect match ACCEPTING one missing, added or
	// incorrect character and excluding a courtesy word if present
	if ( tryagain && allowMismatch ) {
		tempcorrect = correct.toLowerCase() ;
		if ( truncateCourtesy(tempcorrect).length > sizelimit ) {
			condensedCorrect = condense (tempcorrect) ;
			if ( justOneWrong(condense(truncatedAnswer),condensedCorrect) || justOneWrong(condense(answer.toLowerCase()),condensedCorrect) ) {
				tryagain = false ;
				if ( !exam ) alert ( "Yeah, the correct answer is " + Knitem[question][1][0] + ", but your spelling is a bit off - improve it.") ;
				} ; // end if
			} ; // end if
		} ; // end if
	
} ;

function Checkanswer () {

	var q = 0 ;
	var listed = "";

	// read answer from the exam form
	if (! exam ) {
		if (! nextone ) alert ('Please set a new question before answering.') ;
		if ( nextone ) {
			answer = document.forms['TheQuestion'].TheAnswer.value ;
			answer = strippSpaces(answer) ;
			for ( optcount = 0 ; optcount < Knitem[question][1].length ; optcount ++ )
				if ( tryagain ) analyse ( answer, Knitem[question][1][optcount] ) ;
			if ( !tryagain ) {
				numberright ++ ;
				nextone = false
				} ; // end if
			} ; // end if
		}; // end if
		
	if ( exam ) {
		for ( q = 0 ; q < Register2.length ; q ++ ) {
			answer = Register1[q] ;
			answer = strippSpaces(answer) ;
			for ( optcount = 0 ; optcount < Register2[q].length ; optcount ++ )
				if ( tryagain ) analyse ( answer, eval ( 'String.fromCharCode(' + Register2[q][optcount] + ')') ) ;
			if ( !tryagain ) {
				numberright ++ ;
				grades[q] = "Q" + (q+1) + ": " + Register1[q] + " - right"
				} ; // end if
			if ( tryagain )	grades[q] = "Q" + (q+1) + ": " + Register1[q] + " - wrong" ;
			tryagain = true ;
			} ; // end for
		} ; // end if

	// alert the student to the fact that no matching right answer was found
	if ( tryagain && nextone && !exam ) {
		alert ("Sorry, but your answer is not correct. The correct answer is " + Knitem[question][1][0] ) ;
		nextone = false
		} ; // end if	
	
	if ( !exam ) score() ;

} ; // end of Checkanswer

// -->
