%% -*-TeX-*-
 % ###################################################################
 % 
 %  FILE: "grades.sty"
 %                                    created: 1998-11-16 09:11:27 
 %                                last update: 2001-01-23 16:41:48 
 %  Author: Frédéric Boulanger
 %  E-mail: Frederic.Boulanger@supelec.fr
 %    mail: Supélec - Service Informatique
 %          Plateau de Moulon, 3 rue Joliot-Curie, F-91192 Gif-sur-Yvette cedex
 %     www: http://wwwsi.supelec.fr/fb/
 %     
 %  checksum = "29091 505 1897 18339"
 %  
 %  Description: 
 % 
 %  History
 % 
 %  modified   by  rev reason
 %  ---------- --- --- -----------
 %  1998-11-16 FBO 1.0 original
 % ###################################################################
 %%
% Grades file format:
% +Total scale  +Question scale  + Q1 scale  + Q2 scale + ... +
% !Name         !First name      ! Q1 grade  ! Q2 grade ! ... !
% <Total scale> is the maximum grade for the whole exam. <Question 
% scale> is the maximum grade for each question. <Qx> scale is the 
% grade for question x for a student that got <Question scale> for 
% question x.
% For instance:
% +20   +10   +2  +5  +7 +
% means that grades will be between 0 and 20, each question will get 
% between 0 and 10. 10 for question 1 contributes for 2 points, 10 
% for question 2 contributes for 5 points and 10 for question 3 
% contributes for 7 points. So a student who gets 5, 10 and 6 for the 
% questions will get (5*2)/10 = 1 point for the first question, 
% (10*5)/10 = 5 points for the second question, and (6*7)/10 = 4.2 
% points for the last question. This makes 7.2 over 14 points, which 
% is converted to 14.57 over 20, rounded to 15/20.
%
\newcount\grades@mean       % Integer part of grades mean
\newcount\grades@meandec    % Fractional part of grades mean
\newcount\grades@gradestot  % Sum of all grades
\newcount\grades@ngrades    % Number of grades (or students)
\newcount\grades@tmp        % Temporary count
\newcount\grades@tmpi       % Loop index count
\newcount\grades@total      % Current grade (total of question grades)
\newcount\grades@scaletot   % Total scale = sum of the question scales
\newcount\grades@nquest     % Number of questions

\newif\if@grades@grade      % True if we are processing grades (version scale)
\newif\if@grades@onlyshow   % True if we only show global grades

\newread\grades@datain      % Input file with scale and grades
\newwrite\grades@dataout    % Output LaTeX file (tabular environment)
\newwrite\grades@infoout    % Output info file (#grades,min,max,mean)

% Public interface
%
\def\GradesNum{\the\grades@ngrades}  % Number of grades, or students

\def\GradesMean#1{%                  % mean, #1 is the decimal point
  \the\grades@mean #1\the\grades@meandec
}

\def\GradesMin{\grades@min}    % Lower grade seen in data file
\def\GradesMax{\grades@max}    % Higher grade seen in data file

\def\TotalScale{\the\grades@scaletot} % Total scale (sum of scales)
\def\QuestionScale{\grades@questionscale} % Scale for each question
\def\GlobalScale{\grades@globalscale} % Global scale for final grades
\def\CurrentGrade{\the\grades@total}  % Grade that has just been computed

% Private stuff
%
\def\grades@protectdiacritics{ % Use to protect 8 bit chars
  \let\grades@protect@orig\protect
  \set@display@protect
}
\def\grades@restoreprotect{    % Restore meaning of \protect
  \let\protect\grades@protect@orig
}

\def\grades@calcmean{  % Compute integer and fractional parts of mean
  \grades@mean=\grades@gradestot     % Int part = total/# grades
  \divide\grades@mean by \the\grades@ngrades
  \grades@meandec=\grades@gradestot
  \grades@tmp=\grades@mean
  \multiply\grades@tmp by \the\grades@ngrades
  \advance\grades@meandec by -\the\grades@tmp % remainder of prev. div.
  \multiply\grades@meandec by 100    % Dec part has 2 digits
  \grades@tmp=\the\grades@ngrades
  \divide\grades@tmp by 2            % Add half divisor to round up .5
  \advance\grades@meandec by \grades@tmp
  \divide\grades@meandec by \the\grades@ngrades\relax
}

\def\grades@makedefaults{ % Define default behavior if user didn't define one
  \ifx\GradesMissingErrorMsg\undefined % Missing data file
    \def\GradesMissingErrorMsg##1{%
		  ^^J^^J ** Sorry: couldn't open file ##1\space **^^J^^J%
		}
  \fi
  \ifx\GradesTabularEnv\undefined  % Name of the tabular like environment
    \def\GradesTabularEnv{tabular}
  \fi
  \ifx\GradesFirstCol\undefined  % Format of first column (Name)
    \def\GradesFirstCol{|l|}
  \fi
  \ifx\GradesSecondCol\undefined % Format of 2nd column (First name)
    \def\GradesSecondCol{l||}
  \fi
  \ifx\GradesScaleCol\undefined % Format of multicolumn{2} for Q scale
    \def\GradesScaleCol{|l||}
  \fi
  \ifx\GradesQuestCol\undefined % Format of question columns
    \def\GradesQuestCol{c|}
  \fi
  \ifx\GradesTotalCol\undefined % Format of last column (Grade/Total)
    \def\GradesTotalCol{|c|}
  \fi
  \ifx\GradesHeader\undefined % First two columns of first row
    \def\GradesHeader{Name & First name}
  \fi
  \ifx\GradesQuestLabel\undefined % Build question label from its number
    \def\GradesQuestLabel##1{Q##1}
  \fi
  \ifx\GradesTotalLabel\undefined % Last column of first row
    \def\GradesTotalLabel{Grade/\GlobalScale}
  \fi
  \ifx\GradesScaleHead\undefined % multicolumn{2} of 2nd row
    \def\GradesScaleHead{Question scale: \QuestionScale}
  \fi
  \ifx\GradesScaleTail\undefined % Last column of 2nd row (after scales)
    \def\GradesScaleTail{Total: \TotalScale}
  \fi
  \ifx\GradesExtraCols\undefined % Extra cols (format added to \GradesTotalCol)
    \def\GradesExtraCols{}
  \fi
  \ifx\GradesExtraFirstCols\undefined  % Extra cols for first line
    \def\GradesExtraFirstCols{}
  \fi
  \ifx\GradesExtraSecondCols\undefined % Extra cols for 2nd line
    \def\GradesExtraSecondCols{}
  \fi
  \ifx\ScaleShow\undefined % How to show question scales
    \def\ScaleShow##1{##1}
  \fi
  \ifx\GradesShow\undefined % How to show question grades
    \def\GradesShow##1{##1}
  \fi
}

\def\grades@nofile#1{ % Couldn't open data file
  \message{\GradesMissingErrorMsg{#1}}
  \fi
  \end{document}
}
\def\grades@parseline{  % Parse a line of the data file
  \grades@newline       % Setup + and !, empty list of grades
  \grades@curline\relax % Let + and ! do their job
}
\def\grades@processline{  % Process a line once it is parsed
  \if@grades@grade        % If it is a line of grades
    \grades@processgrades %   make a grade
  \else                   % else
    \grades@processscale  %   build a scale
  \fi
}

\def\grades@mainloop{      % Loop until end of data file
  \ifeof\grades@datain
    \let\grades@nextiter=\relax
  \else
    \let\grades@nextiter=\grades@mainbody
  \fi
  \grades@nextiter
}

\def\grades@maketabular{  % Build the \begin{tabular} line with proper format
  \grades@protectdiacritics % Protect 8 bit char in head line
	\if@grades@onlyshow
	  \edef\grades@tabhead{% 
	    \string\begin\string{\GradesTabularEnv\string}%
	    \string{\GradesFirstCol\GradesSecondCol%
			\GradesTotalCol\string}
	  }
	\else
	  \edef\grades@tabhead{% 
	    \string\begin\string{\GradesTabularEnv\string}%
	    \string{\GradesFirstCol\GradesSecondCol%
			*\string{\the\grades@nquest\string}\string{\GradesQuestCol\string}%
			\GradesTotalCol\string}
	  }
	\fi
  \immediate\write\grades@dataout{\grades@tabhead}
  \immediate\write\grades@dataout{\string\hline}
  \grades@restoreprotect
}

\def\grades@makefirstrow{  % Build first row of tabular
  \grades@protectdiacritics
  \edef\grades@tabhead{\GradesHeader}
	\if@grades@onlyshow
	\else
		\grades@makeqlabls     % add question labels to first row
	\fi
  \edef\grades@tabhead{\grades@tabhead & \GradesTotalLabel
	  \GradesExtraFirstCols\string\\ \string\hline
	}
  \immediate\write\grades@dataout{\grades@tabhead}
  \grades@restoreprotect
}

\def\grades@makeqlabls{    % Question label part of first row
  \grades@tmpi=0
  \loop                    % Build list of question labels
    \ifnum\the\grades@tmpi < \the\grades@nquest
    \advance\grades@tmpi 1
    \edef\grades@tabhead{%
		  \grades@tabhead & \GradesQuestLabel{\the\grades@tmpi}%
		}
  \repeat
}

\def\grades@makescalerow{ % Build 2nd row of tabular (scales)
  \grades@protectdiacritics
  \edef\grades@tabhead{
    \string\multicolumn\string{2\string}\string{\GradesScaleCol\string}%
    \string{\GradesScaleHead\string}
  }
  \immediate\write\grades@dataout{\grades@tabhead}
  \def\grades@tabhead{}
	\grades@makeqscals      % build list of question scales
  \edef\grades@tabhead{%
	  \grades@tabhead & \GradesScaleTail \GradesExtraSecondCols
	}
  \immediate\write\grades@dataout{\grades@tabhead}
  \grades@restoreprotect
}

\def\grades@makeqscals{   % Question scales part of 2nd row
  \grades@tmpi=0
  \loop                   % Build list of scales
    \ifnum\the\grades@tmpi < \the\grades@nquest
    \advance\grades@tmpi 1
    \edef\grades@tabhead{%  Scales are in \Bi, \Bii, \Biii, \Biv ...
		  \grades@tabhead
			& \ScaleShow{\csname B\romannumeral\the\grades@tmpi\endcsname}%
		}
  \repeat
}

\def\grades@isend{  % Look for end of line in \grades@ntok
  \if\grades@ntok\space % \space after ! or + ends a line
    \let\grades@nextop=\relax
  \else
    \ifx\grades@ntok\relax % \relax after ! or + also ends a line
      \let\grades@nextop=\relax
    \else
      \let\grades@nextop=\grades@instok
    \fi
  \fi
  \grades@nextop
}

\def\grades@buildscale#1.{ % Define B<n> as the scale for the nth question
  \advance\grades@nquest 1
  \expandafter\xdef\csname B\romannumeral\the\grades@nquest\endcsname{#1}
  \advance\grades@scaletot #1 % Adjust total scale
}

\def\grades@calcgrade#1.{ % Compute the grade for next question
  \advance\grades@tmpi 1 % step quest number
  \grades@tmp=#1         % grade for this question
  \edef\grades@scale{\csname B\romannumeral\the\grades@tmpi\endcsname}
  \multiply\grades@tmp by \grades@scale % This is the grade
  \advance\grades@total\the\grades@tmp  % Add it to the total
}

\def\grades@buildline#1.{&\GradesShow{#1}} % Add question grade to current line

\newcount\grades@exclam  % Orignal catcode of !
\newcount\grades@plus    % Original catcode of +
\newcount\grades@comma   % Original catcode of ,
\newcount\grades@space   % Original catcode of space

\def\grades@init{ % Initialize before reading a new data file
	\grades@exclam=\catcode`\! % Save original catcodes
	\grades@plus=\catcode`\+
	\grades@comma=\catcode`\,
	\grades@space=\catcode`\ \relax
  \def\grades@grades{}   % There are no grades yet
  \def\grades@scales{}   % There is no scale yet
  \def\grades@mtiline{}  % To check for an empty line
  \@grades@gradefalse    % We should see a scale before seeing grades
  \grades@gradestot=0    % Initialize counters
  \grades@ngrades=0
  \grades@total=0
  \grades@scaletot=0
  \grades@nquest=0
	\grades@makedefaults   % Define default behaviors
}

%%%%
{\catcode`\!=13       % These chars do all the job of parsing a line
\catcode`\+=13
\gdef\grades@newline{ % Setup for a new line (scale or grade)
  \let!=\grades@getname % First item of a grade line is the name
	\let+=\grades@gettot  % First item of scale line is the global scale
	\gdef\grades@grades{}
}
\gdef\grades@mainbody{ % Body of main loop
	\catcode`\!=13       % Make these two active
	\catcode`\+=13 
	\def!{\string!}      % But they should not expand for the moment
  \def+{\string+}
  \ifx\grades@curline\grades@mtiline
  \else
    \ifx\grades@curline\par
    \else              % If the line is not empty
      \grades@parseline   % parse it
      \grades@processline % process the data gathered during parse
		\fi
  \fi
  \grades@protectdiacritics
  \read\grades@datain to\grades@curline % read next line
  \grades@restoreprotect
  \grades@mainloop     % iterate main loop
}
}% End of group with ! and + active

{\catcode`\,=13  % We build lists of ,xxx. items
\gdef\grades@neutralcomma#1.{\noexpand,#1.} % Do not expand while building
\gdef\grades@processscale{ % Process the scale line
  \grades@nquest=0         % No scales yet, total is 0
  \grades@scaletot=0
  \catcode`\,=13           % Make , build the scale
  \let,=\grades@buildscale
  \grades@scales           % , will expand and build the scale
  \grades@maketabular      % Begin a tabular environment
  \grades@makefirstrow     % Build the head line
	\if@grades@onlyshow
  	\immediate\write\grades@dataout{\string\hline}
	\else
  	\grades@makescalerow     % Build the scale line
  	\grades@protectdiacritics
  	\immediate\write\grades@dataout{\string\\ \string\hline\string\hline}
	\fi
	\ifx\GradesHeadHook\undefined
	\else
  	\immediate\write\grades@dataout{\GradesHeadHook}
	\fi
  \grades@restoreprotect
  \def\grades@min{\grades@globalscale}
  \edef\grades@max{0}   % Initialize min and max grades
}
\gdef\grades@processgrades{ % Process a grade line
  \edef\grades@endtabline{\string\\ \string\hline}
  \catcode`\,=13
  \grades@protectdiacritics
	\if@grades@onlyshow
  	\immediate\write\grades@dataout{\grades@name & \grades@fname}
	\else
  	\let,=\grades@buildline % Make , build a line of grades
  	\immediate\write\grades@dataout{\grades@name & \grades@fname \grades@grades}
	\fi
  \grades@total=0           % Total grade is 0
  \grades@tmpi=0            % Question number is 0
  \let,=\grades@calcgrade   % Make , compute the total grade
  \grades@grades            % , will expand and compute the grade
  \multiply\grades@total by \grades@globalscale % Adjust between
  \divide\grades@total by \the\grades@scaletot  % total and global scale
  \grades@tmp=\grades@questionscale % Add half Q. scale to round 0.5 up
  \divide\grades@tmp by 2
  \advance\grades@total\the\grades@tmp
  \divide\grades@total by \grades@questionscale % We have a grade
  \advance\grades@gradestot\the\grades@total  % Add it to the grand total
  \advance\grades@ngrades 1                   % There is one more grade
  \immediate\write\grades@dataout{&\the\grades@total\GradesExtraCols\grades@endtabline}
  \grades@restoreprotect
  \ifnum\the\grades@total < \grades@min % Adjust min grade
    \edef\grades@min{\the\grades@total}
  \fi
  \ifnum\the\grades@total > \grades@max % Adjust max grade
    \edef\grades@max{\the\grades@total}
  \fi
  \ifx\GradesLogWrite\undefined % If we must log grades to a file
  \else
    \grades@protectdiacritics
    \immediate\write\GradesLogWrite{% Write name, first name and grade
			  \GradesLogStart
				\grades@name\GradesLogSep\grades@fname\GradesLogSep\the\grades@total
				\GradesLogEnd
			}
    \grades@restoreprotect
  \fi
	\ifx\GradesHook\undefined  % If we must call a hook for each grade
	\else
		\GradesHook              % call it!
	\fi
}
}% End of group with ',' active

{\catcode`\ =13\let =\space
\catcode`\!=13\catcode`\+=13
\catcode`\,=13
\gdef\grades@getname#1!{ % Meaning of ! to read the name
  \catcode`\ =13\def {}%   remove trailing spaces (others must be escaped)
  \grades@protectdiacritics
  \xdef\grades@name{#1}
  \grades@restoreprotect
  \@grades@gradetrue
  \let!=\grades@getfname!% Next thing to read is first name
}
\gdef\grades@getfname#1!{% Meaning of ! to read the first name
  \catcode`\ =13\def {}%   Remove trailing space (others must be escaped)
  \grades@protectdiacritics
  \xdef\grades@fname{#1}%
  \grades@restoreprotect
  \let!=\grades@getgrade!% Next thing to read is a grade
}
\gdef\grades@getgrade#1!{% Meaning of ! to read a grade
  \newgrade{#1}%         % add grade to the list
	\let\grades@instok=!%  % Insert ! if we are not at EOL
	\futurelet\grades@ntok\grades@isend% Check for end of line
}
\gdef\newgrade#1{% Add a new grade to the list
  \catcode`\,=13\catcode`\ =13\def {}%
	\let,=\grades@neutralcomma % do not expand ',' while we build the list
  \xdef\grades@grades{\grades@grades,#1.}%
}
\gdef\grades@gettot#1+{% Meaning of + to read global scale
  \catcode`\ =13\def {}%  Remove trailing spaces
	\xdef\grades@globalscale{#1}%
	\let+=\getscale+%     Next thing to read is question scale
}
\gdef\getscale#1+{%     % Meaning of + to read question scale
  \catcode`\ =13\def {}%  Remove trailing spaces
	\xdef\grades@questionscale{#1}%
	\let+=\getscales+%   Next thing to read is list of scales
}
\gdef\getscales#1+{% Meaning of + to read a scale
  \newscale{#1}%   % Add scale to the list
	\let\grades@instok=+% Insert + if we are not at EOL
	\futurelet\grades@ntok\grades@isend% Check for end of line
}
\gdef\newscale#1{% Add a scale to the list
  \catcode`\,=13\catcode`\ =13\def {}%
	\let,=\grades@neutralcomma % Do not expand ',' while building the list
  \xdef\grades@scales{\grades@scales,#1.}%
}
}% End of group with !, +, ',' and space active

\def\grades@finish{ % Restore catcodes after reading data file
	\catcode`\!=\the\grades@exclam
	\catcode`\+=\the\grades@plus
	\catcode`\,=\the\grades@comma
	\catcode`\ =\the\grades@space
	\@grades@onlyshowfalse
	\grades@calcmean
}

{\catcode`\#=12\gdef\grades@hash{#}} % Used to write a # to a file

% Main public macro: read a data file and produce a tabular
\newcommand{\readgrades}[2][grd]{ % read file #2.grd and write LaTeX to #2.tex
	\grades@init       % Initialize our stuff
  \openin\grades@datain=#2.#1\relax % Open data file
  \ifeof\grades@datain  % If open failed -> error
    \grades@nofile{#2.#1}
  \else              % else open LaTeX file
    \immediate\openout\grades@dataout=#2.tex\relax
		\def\grades@curline{}
    \grades@mainloop % Let's go...
  \fi
  \closein\grades@datain    % Close data file
  \grades@protectdiacritics % Put and end to the tabular environment
  \immediate\write\grades@dataout{\string\end\string{\GradesTabularEnv\string}}
  \grades@restoreprotect
  \immediate\closeout\grades@dataout\relax % Close LaTeX file
	\grades@finish            % Restore catcodes
	\immediate\openout\grades@infoout=#2.inf\relax
	\immediate\write\grades@infoout{%
	  \string\def\string\GradesNum\string{\GradesNum\string}%
	}
	\immediate\write\grades@infoout{%
	  \string\def\string\GradesMin\string{\GradesMin\string}%
	}
	\immediate\write\grades@infoout{%
	  \string\def\string\GradesMax\string{\GradesMax\string}%
	}
	\immediate\write\grades@infoout{%
	  \string\def\string\GradesMean\grades@hash1\string{%
		\the\grades@mean \grades@hash1\string\relax\the\grades@meandec\string}%
	}
	\immediate\closeout\grades@infoout
}

\newcommand{\showgrades}[2][grd]{ % Same as \readgrades,
	\def\GradesTotalCol{c|}         % but show only final grades.
	\@grades@onlyshowtrue
	\readgrades[#1]{#2}
}
