:Wright State University Lake Campus/2019-5/QuizSoftware codes
9 May 2019 (UTC)
editmakeExam.py
edit##This code must be run in a folder that contains:
## 1. ./bank (contains .tex files for all bank wikiquizzes)
## 2. ./bank/images (images contain all the image files.
## 3. ./input/xxx.csv (where one or more csv files are situated)
## 4. ./output (where the four xxx.tex files will be created)
## 5. ./output/images (I currently requre to copies of the images)
##The extra images file (in output) is not required for this program to run,
## but is required to run the output xxx.tex files that are created. Also
## created in the output file is an empty ./output/xxx directory where
## the pdf files created by LaTex can be placed (I use Miktex)
##
##Also in the directory that holds this program (program.py) is another
## program called template.py. Use template.py to create the xxx.csv files
## used to create the xxx.csv files used by program.py.
import os, sys, csv, re, time, copy, shutil, random#(re not used yet)
global debugmode
debugmode=False
Nversions=6
Nversions=int(input("Nversions: "))
statement='%statement\n'
statement+='Though posted on Wikiversity, this document was created without '
statement+='wikitex using Python to write LaTeX markup. With a bit more development '
statement+='it will be possible for users to download and use software that will '
statement+='permit them to create, modify, and print their own versions of this document.\n'
statement+='% insert more here...\\\\\n'
##This code opens a testfile (e.g. anyTest.csv) and
##verifies that it is properly formatted. It checks that:
##1. The testName is in the 01 cell of the csv file
##2. The number of questions in the bank in the 00 file is correct
##3. Each available question has a top row integral question number
## If possible it fixes the error and creates anyTestUPGRADE.csv
####Code requires ./bank/ and ./input/template/ directories
## getInfo returns testName
## getCheckData returns csvData (what might be on test)
class Question:
def __init__(self):
self.quizName =""
self.number=0 #
self.quizType = ""
self.QA = [] #question and answer
self.Q=[] #question only
self.A=[] #answer only
self.Nrenditions = 1
class Quiz:
def __init__(self):
self.quizName =""
self.quizType = ""
self.questions = []#Question() clas list
self.Nquestions=0#number of questions
class CsvInfo:
def __init__(self):
self.comments=[]#col 1a
self.quizNames=[]#col 2b
self.selections=[]#col 3c (Sel)
self.Nquestions=[]#col 4d (available)
self.types=[]# t,n,c col 4e
self.choices=[] #list starts at 5f
def whatis(string, x):
print(string+' value=',repr(x),type(x))
return string+' value='+repr(x)+repr(type(x))
def getFromChoices(first,second,num2pick): #select num2pick items
random.shuffle(first)#list of integers first priority
random.shuffle(second)#list of integers second priority
allofem=(first+second)
Nmax=min(len(allofem),num2pick)#Don't pick more than available
selected=allofem[0:Nmax]
random.shuffle(selected)
return selected
def getTestName():
if debugmode: #set testName to sample and return
return "s_ample"
availableTests=[]
for item in os.listdir('./input'):
if item.endswith(".csv"):
availableTests.append(item[:-4])
print("availableTests: ", availableTests)
testName=input('enter testName ')
if testName not in availableTests:
testName="s_ample"
print("testName taken to be",testName)
if testName not in availableTests:
print(testName, " not available in ./input")
sys.exit("testName not available in ./input")
return testName
def getCsv(csvInfo,testName):
string=os.path.join("./input",testName+'.csv')
with open(string,newline='') as fin:
reader = csv.reader(fin)
csvData=list(reader)
#rows & columns indexed as follows
cvsRows=len(csvData)#number of rows: index as nrow
cvsCols=len(csvData[0])#num cols: index as ncol
#print('csvData dimensions: Nrow,Ncol',cvsRows,cvsCols)
commentColA=[]#0a comments used
quizNameColB=[]#1b quiznames list
selectionColC=[]#2c num Sel=S=selected for test
NavailableColD=[]#3d num questions in quiz (bank)
typeColE=[]#4e quizType list
choiceListF=[]#5f lists len(NQues) w/ 0,1,or 2
##The first row is different:
##0A=number of questions on test
##0B=testName
##0C="Sel" (either 0,1,2 where "blank" means 0
##0D=number of questions in the bank
##0E="t" (type)
##0F,0G,...=1,2,...
for nrow in range(0,cvsRows):
commentColA.append(csvData[nrow][0])#0a
quizNameColB.append(csvData[nrow][1]) #1b
selectionColC.append(csvData[nrow][2]) #2c
NavailableColD.append(csvData[nrow][3]) #3d
typeColE.append(csvData[nrow][4]) #4e
choiceListF.append(csvData[nrow][5:cvsCols])
csvInfo.comments=commentColA
csvInfo.quizNames=quizNameColB
csvInfo.selections=selectionColC
csvInfo.Nquestions=NavailableColD
csvInfo.types=typeColE
csvInfo.choices=choiceListF
return [csvInfo,cvsRows,csvData] #ends getCvs
def prefixUnderscore(string):
string=string.replace('_','\\_')
return string
def findLatexWhole(pre,string,post):
#returns list of indices between the pre and post
#tags. Whole includes the tags
mylist=[]
tag=pre+r'(.*?)'+post
matches=re.finditer(tag,string,re.S)
for item in matches:
mylist.append([item.start(),item.end()])
#mylist is a lists of two element lists (start/st
#the two element list is the start/stop point in string
#mylist is as long as there are instances of the tag
return mylist #ends findLatexWhole
def findLatexBetween(pre,strIn,post):
#returns list of indeces between the pre and post
#tags. Between excludes the tags
mylist=[]
tag=pre+r'(.*?)'+post
matches=re.finditer(tag,strIn,re.S)
for item in matches:
#To get "between" we subtract out the pre and post
#part of the tag. We need to count the backslashes
#in order to compensate for the extra \ in the \\ escape
n1=item.start()+len(pre)-pre.count(r'\\')
n2=item.end()-len(post)+post.count(r'\\')#as before:
#mylist is a lists of two element lists (start/st
#the two element list is the start/stop point in string
#mylist is as long as there are instances of the tag
mylist.append([n1,n2])
return mylist #ends findLatexBetween
def QA2QandA(string):
#Define 4 patterns:
beginStr=r'\\begin{choices}'
endStr=r'\\end{choices}'
CoStr=r'\\CorrectChoice '
chStr=r'\\choice '
#get Q=question
n=string.find('\\begin{choices}')
Q=string[0:n]
#get A=string of answers
#newStr defines the new search parameter as 1st answer
pat=CoStr+'|'+chStr+'|'+endStr+'(.*?)'#fails-not greedy
iterations=re.finditer(pat,string,re.S)
nList=[0]
A=[]
for thing in iterations:
nList.append(thing.start())
#for i in range(len(nList)): #crashes as expected
for i in range(1,len(nList)-1):
A.append(string[nList[i]:nList[i+1]])
return [Q,A]
def makeQuiz(path,quizName):
strIn=""
with open(path,'r') as fin:
for line in fin:
strIn+=line
########### we build Quiz() etc from strIn
quiz=Quiz()
quiz.quizName=quizName
x=findLatexBetween(r'{\\quiztype}{',strIn,'}')
quizType=strIn[x[0][0]:x[0][1]]
quiz.quizType=quizType
x=findLatexBetween(r'\\begin{questions}', #List of index pairs
strIn,r'\\end{questions}') #(start,stop)
firstBatch=strIn[x[0][0]:x[0][1]] #not sure I need x and y
y=findLatexWhole(r'\\question ',firstBatch, r'\\end{choices}')
quiz.Nquestions=len(y)#=number of questions in quiz
for i in range(quiz.Nquestions):
question=Question()
question.QA=[]#I don't know why, but code ran without this
question.Q=[]#ditto
question.A=[]#ditto
question.quizName=quizName
question.number=i+1
question.quizType=quizType
#Now we search firstBatch for the i-th QA
#(where QA is the "Question&Answers" string)
questionAndAnswer=firstBatch[y[i][0]:y[i][1]]
question.QA.append(questionAndAnswer)
[Q0,A0]=QA2QandA(questionAndAnswer)
question.Q.append(Q0)
question.A.append(A0)
#more needs to be added if numerical
#Also need questionQ an question.A
quiz.questions.append(question)
if question.quizType=="numerical":
z=findLatexBetween(r'\\section{Renditions}',
strIn,r'\\section{Attribution}')
renditionsAll=strIn[z[0][0]:z[0][1]]
w=findLatexWhole(r'\\begin{questions}',renditionsAll,
r'\\end{questions}')
for i in range(quiz.Nquestions):
#reopen each question from quiz
question=quiz.questions[i]
renditionsThis=renditionsAll[w[i][0]:w[i][1]]
v=findLatexWhole(r'\\question ',renditionsThis,
r'\\end{choices}')
question.Nrenditions=len(v)
for j in range(len(v)):
QAj=renditionsThis[v[j][0]:v[j][1]]
######### fix pagebreak bug ################
QAj=QAj.replace('\\pagebreak',' ')
question.QA.append(QAj)
question.Q.append(QAj)
question.A.append(QAj)
return quiz #ends MakeQuiz
def startLatex(titleTxt,quizType,statement,timeStamp): #this starts the Latex markup
titleTex=titleTxt.replace('_','\\_')
string=r'''%PREAMBLE
\newcommand{\quiztype}{'''+quizType+r'''}
\newif\ifkey\documentclass[11pt,twoside]{exam}
\RequirePackage{amssymb, amsfonts, amsmath, latexsym, verbatim,
xspace, setspace,datetime,tikz, pgflibraryplotmarks, hyperref}
\usepackage[left=.4in, right=.4in, bottom=.9in, top=.7in]{geometry}
\usepackage{endnotes, multicol,textgreek,graphicx} \singlespacing
\parindent 0ex \hypersetup{ colorlinks=true, urlcolor=blue}
\pagestyle{headandfoot}
\runningheader{'''+titleTex+r'''}{\thepage\ of \numpages}{'''\
+timeStamp+r'''}
\footer{}
{\LARGE{The next page might contain more answer choices for this question}}{}
% BEGIN DOCUMENT
\begin{document}\title{'''+titleTex+r'''}
\author{\includegraphics[width=0.10\textwidth]
{images/666px-Wikiversity-logo-en.png}\\
The LaTex code that creates this quiz is released to the Public Domain\\
Attribution for each question is documented in the Appendix\\
\url{https://bitbucket.org/Guy_vandegrift/qbwiki/wiki/Home}\\
\url{https://en.wikiversity.org/wiki/Quizbank} \\
'''+quizType+r''' quiz '''+timeStamp+\
r'''}
\maketitle
'''+statement+"\n\\tableofcontents\n"
return string
def AllStudyLatex(testName,statement,timeStamp,bnkQuizzes):
bigStrAll=startLatex(testName+": All","mixed",statement,timeStamp)
bigStrStudy=startLatex(testName+":Study","mixed",statement,timeStamp)
NbnkQuiz=len(bnkQuizzes)
for i in range(NbnkQuiz):
quiz=bnkQuizzes[i]
quizNameLatex=prefixUnderscore(quiz.quizName)
bigStrAll+=r'\section{'+quizNameLatex\
+r'}\keytrue\printanswers'
bigStrStudy+=r'\section{'+quizNameLatex\
+r'}\keytrue\printanswers'
#print zeroth renditions
bigStrAll+='\n\\begin{questions}\n'
bigStrStudy+='\n\\begin{questions}\n'
for j in range(quiz.Nquestions):
thisQA=quiz.questions[j].QA
bigStrAll+=thisQA[0]
bigStrStudy+=thisQA[0]
bigStrAll+='\n\\end{questions}\n'
bigStrStudy+='\n\\end{questions}\n'
if(quiz.questions[j].quizType)=='numerical':
bigStrAll+='\n \\subsection{Renditions}'
for j in range(quiz.Nquestions): #iterates questions not renditions
bigStrAll+='\n\\subsubsection*{'+quizNameLatex+' Q'+str(j+1)+'}'
thisQA=quiz.questions[j].QA
bigStrAll+='\n\\begin{questions}\n'
for k in range(1,len(thisQA)): #A null loop if conceptual
bigStrAll+=thisQA[k]
bigStrAll+='\n\\end{questions}\n'
bigStrAll+='\n\\section{Attribution}\\theendnotes\n\\end{document}'
bigStrStudy+='\n\\section{Attribution}\\theendnotes\n\\end{document}'
#Make output
path2=os.path.join('.\output',timeName)
if not os.path.exists(path2):
os.makedirs(path2)
pathAll=os.path.join('.\output',timeName+'All.tex')
with open(pathAll,'w') as latexOut:
latexOut.write(bigStrAll)
pathStudy=os.path.join('.\output',timeName+'Study.tex')
with open(pathStudy,'w') as latexOut:
latexOut.write(bigStrStudy)
return
def checkcsvData(csvData, bnkQuizzes):
csvChecks=True
csvNew=copy.deepcopy(csvData)
NquestionsMax=0 #will grow to the most questions on any wikiquiz
NquestionsOnTest=0 #This will sum up the selections column 3c
NquestionsInBnk=0#number questions in (local) bank
#skip the top row of csvDat for now:
Nquizzes=len(bnkQuizzes)
for i in range(Nquizzes):
#col 1a comments. No need to check (except for top row).
#col 2b testname
thing=bnkQuizzes[i].quizName
ss=csvData[i+1][1]
if repr(thing)!=repr(ss):
print(thing+', '+ss+',quizname mismatch fatal error')
sys.exit()
csvChecks=False
#col 3c Sel=number selected for test. No need to check.
NquestionsOnTest+=int(csvData[i+1][2])
#col 4d number questions available from bank
thing=bnkQuizzes[i].Nquestions
NquestionsInBnk+=thing
if thing>NquestionsMax:
NquestionsMax=thing
ss=csvData[i+1][3]
if str(thing)!=str(ss):
print('thing', repr(thing), 'ss', repr(ss))
print(str(thing),ss,'available questions mismatch')
csvNew[i+1][3]=thing
csvChecks=False
#col 5e t=type
thing=bnkQuizzes[i].quizType
ss=csvData[i+1][4]
if thing=='conceptual':
thing='c'
if thing=='numerical':
thing='n'
if thing!=ss:
print(thing,ss,'quiztype mismatch')
csvNew[i+1][4]=str(thing)
csvChecks=False
#col 6f 1 (2,3,...) were obtained from the ss=spreadsheet
#Now do the first row: Now thing has to be calculated
#col 1a thing is the number of questions on the Test
ss=csvData[0][0] #col 1a has index 0
thing=NquestionsOnTest
if str(thing)!=str(ss):
print(repr(thing),repr(ss),'Questions on Test mismatch')
csvNew[0][0]=str(thing)
csvChecks=False
#ss=csvData[0][1] #col 2b is testName index 1
ss=testName #here we disable the testName check
thing=testName
if str(thing)!=str(ss):
print(thing,ss,'TestName mismatch')
csvNew[0][1]=thing
csvChecks=False
ss=csvData[0][2] #col 3c Sel selection label index 2
thing='Sel'
if str(thing)!=str(ss):
print(thing,ss,'Sel=selection col header not convention')
csvNew[0][2]=thing
csvChecks=False
ss=csvData[0][3]
thing=str(NquestionsInBnk)
if str(thing)!=str(ss):
print(thing,ss,'questions in (local) bank mismatch')
csvNew[0][3]=str(thing)
csvChecks=False
ss=csvData[0][4] #col 5e is 't' type
thing='t'
if str(thing)!=str(ss):
print(thing,ss,"needs 't' in top of column")
csvNew[0][4]=thing
csvChecks=False
ss=csvData[0][5:len(csvData[0])]
problemLabels=map(str,list(range(1,NquestionsMax+1)))
thing=list(problemLabels)
if str(thing)!=repr(ss)and str(thing)!=repr(ss[:-1]):
print(thing,ss,'problemLabels mismatch')
for n in range(0,NquestionsMax):
csvNew[0][5+n]=str(n+1)
csvChecks=False
print('csvChecks =',csvChecks)
## if csvChecks:
## print('csv file seems correct')
## else:
## print('csv file needs UPGRADE - upgrading now')
## upgradePath="./input/"+testName+'UPGRADE.csv'
## with open(upgradePath,'w',newline='') as fout:
## wr=csv.writer(fout,delimiter=',')
## for row in csvNew:
## wr.writerow(row)
return csvChecks
def instructorLatex(testName,statement,timeStamp,bnkQuizzes,csvInfo):
bigStr=startLatex(testName+": Instructor","mixed",statement,timeStamp)
bigStr+=r'\keytrue\printanswers'
choiceList=[]
for i in range(len(bnkQuizzes)):
bigStr+=r'%New bank wikiquiz\\'
bigStr+='\n'
quiz=bnkQuizzes[i]
quizName=quiz.quizName
#must upgrade quizName to Latex format:
quizName=quizName.replace('_','\\_')
Nquestions=quiz.Nquestions
num2pick_str=csvInfo.selections[i+1]
bigStr+=r'\subsection{'+num2pick_str
bigStr+=' of '+ str(Nquestions) +' questions from '+quizName+'}\n'
first=[]
second=[]
#choiceSel is the decision made regarding each question
#in the wikiquiz: 0, 1, 2, or if outide range:''
choiceSel=csvInfo.choices[i+1]
#convert this list of "integer strings" to a string:
bigStr+='\nSel: '
bigStr+=', '.join(choiceSel)+r'.\\'
iMax=len(choiceSel)
for j in range(iMax):#note that the list starts at 1 not 0
if choiceSel[j]=='1':
first.append(int(j+1))
if choiceSel[j]=='2':
second.append(int(j+1))
#randChoice selects randomly from this:
randChoice=getFromChoices(first,second,int(num2pick_str))
randChoice=sorted(randChoice)
#create a printable string to show the random selection:
bigStr+='\nRandom: '
bigStr+=', '.join(map(str,randChoice))+r'.\\'+'\n'
#choiceList is a list of lists:
choiceList.append(randChoice)
#all versions use these same choices and choice list saves
#Review: i indexed the quizzes, so we let j index the questions
#Begin fix to avoid void question gag
if len(randChoice)!=0:
bigStr+='\n'+'\\begin{questions}\n'
for j in range(len(randChoice)):
jQ=randChoice[j]
#print('randomChoice=jQ',jQ,'bnkquiz#=i',i)
#recall that humans count questions beginning at 1
#but python starts at zero: jQ goes to jQ-1
bigStr+=quiz.questions[jQ-1].QA[0]
bigStr+='\n\\end{questions}\n'
#End fix to avoid void gquestion tag
bigStr+='\n\\section{Attribution}\n'
bigStr+='Some attributions are located elsewhere.'
bigStr+=r'''\endnote{The current system neglects to repeat the
attributions for the multipe renditions}'''
bigStr+='\n\\theendnotes\n\\end{document}'
pathInstructor=os.path.join('.\output',timeName+'Instructor.tex')
with open(pathInstructor,'w') as latexOut:
latexOut.write(bigStr)
return choiceList
def choiceList2string(choiceList,bnkQuizzes): #wherewasi
#returns random selections from the choiceList in randomized order
testQAlist=[]
for i in range(len(bnkQuizzes)):
quiz_i=bnkQuizzes[i]
for j in range(len(choiceList[i])):
choice_j=choiceList[i][j]
jPy=choice_j-1#Python iterates from zero
#choice_j is an integer
question=quiz_i.questions[jPy]
if question.quizType=="numerical":
NR=question.Nrenditions
nR=random.choice(range(1,NR))
testQAlist.append(question.QA[nR])
if question.quizType=="conceptual":
Q=question.Q[0]
Araw=question.A[0]
Ashuffled=question.A[0]
random.shuffle(Ashuffled)
string=Q+'\\begin{choices}'
for answer in Ashuffled:
string+=answer
string+='\\end{choices}\n'
#testQAlist.append(Q+Ashuffled)
testQAlist.append(string)
random.shuffle(testQAlist)
bigStr=''
for item in testQAlist:
bigStr+=item+'\n'
return bigStr
def testLatex(testName,statement,timeStamp,bnkQuizzes,choiceList,
nVersions):
questionList=[]
for n in range(len(bnkQuizzes)):
quiz=bnkQuizzes[n]
string=quiz.quizName+' ('+str(quiz.Nquestions)
string+=' '+quiz.quizType+') choices: '
string+=', '.join(map(str,choiceList[n]))
#print(string)
for quesNumStr in choiceList[n]:
#pyQuesIndex gets us the question we want:
pyQuesIndex=int(quesNumStr)-1
question=quiz.questions[pyQuesIndex]
questionList.append(question)
bigStr=startLatex(testName+": Test","mixed",statement,timeStamp)
#startLatex fixes the prefixUnderscore, but henceforth we need:
testNameLatex=prefixUnderscore(testName)
bigStr+='text before first subsection\n'
for nver in range(nVersions):
#noanswers part:
bigStr+='\\cleardoublepage\\subsection{V'+str(nver+1)+'}\n'
bigStr+='\\noprintanswers\\keyfalse\n'
bigStr+='\\begin{questions}\n'
testQAlist= choiceList2string(choiceList,bnkQuizzes)
bigStr+=testQAlist
bigStr+='\\end{questions}\n'
##KEY part
bigStr+='\\cleardoublepage\\subsubsection{KEY V'+str(nver+1)+'}\n'
bigStr+='\\printanswers\\keyfalse\n'
bigStr+='\\begin{questions}\n'
bigStr+=testQAlist
bigStr+='\\end{questions}\n'
bigStr+='\n\\section{Attribution}\n'
bigStr+='Some attributions are located elsewhere.'
bigStr+=r'''\endnote{The current system neglects to repeat the
attributions for the multipe renditions}'''
bigStr+='\n\\theendnotes\n\\end{document}'
pathTest=os.path.join('.\output',timeName+'Test.tex')
with open(pathTest,'w') as latexOut:
latexOut.write(bigStr)
return
#################### Program starts here ##########################
timeStamp=str(int(time.time()*100))
if debugmode==True:
testName='s_ample'
timeStamp='0000000' #and testName will be set to sample
path2empty=os.path.join('.\output','0000000-sample')
if os.path.exists(path2empty):
shutil.rmtree(path2empty)
## This last line was an attempt to fix a mysterious failure. I need
## to remove path2empty because it is created later.
else:
testName=getTestName()
##timeStamp=str(int(time.time()*100))
timeName = timeStamp+'-'+testName
path='./bank/'+testName+'.csv'
csvInfo=CsvInfo()
[csvInfo,cvsRows,csvData]=getCsv(csvInfo,testName)
#bnkQuizNames is a list of quznames in the bank
bnkQuizNames=csvInfo.quizNames[1:cvsRows]
bnkQuizzes=[]#bnkQuizzes is a list of objects of class Quiz()
for name in bnkQuizNames:
if os. path. isfile('./bank/'+name+'.tex'):
bnkQuizzes.append(makeQuiz('./bank/'+name+'.tex',name))
else:
print(name+'is not a quiz in the bank')
csvChecks=checkcsvData(csvData, bnkQuizzes)
AllStudyLatex(testName,statement,timeStamp,bnkQuizzes)
choiceList=instructorLatex(testName,statement,timeStamp,bnkQuizzes,csvInfo)
testLatex(testName,statement,timeStamp,bnkQuizzes,choiceList,
Nversions)
NumDismantle.py
edit## NumDismantle dismantles a numerical wikiquiz.
import os, sys, csv, re, time, copy, shutil, random#(re not used yet)
## These can be moved into NumDismantleAux using this:
##from NumDismantleAux import whatis, Question, Quiz, prefixUnderscore, findLatexBetween,\
## findLatexWhole, numericListGet, QA2QandA, makeQuiz, bankQuizHeader, bankQuizFooter,\
## printQuiz
def whatis(string, x):
print(string+' value=',repr(x),type(x))
return string+' value='+repr(x)+repr(type(x))
###################################################### ENDS whatis ###
class Question:
def __init__(self):
self.quizName =""
self.number=0 #
self.quizType = ""
self.QA = [] #question and answer
self.Q=[] #question only (flawed for numericals?)
self.A=[] #answer only (flawed for numericals?)
self.Nrenditions = 1
###################################################### ends Question##
class Quiz:
def __init__(self):
self.quizName =""
self.quizType = ""
self.questions = []#Question() clas list
self.Nquestions=0#number of questions
#############################################################ends Quiz
def prefixUnderscore(string):
string=string.replace('_','\\_')
return string
############################################## ends prefix underscore####
def findLatexBetween(pre,strIn,post):
#returns list of indeces between the pre and post
#tags. Between excludes the tags
mylist=[]
tag=pre+r'(.*?)'+post
matches=re.finditer(tag,strIn,re.S)
for item in matches:
#To get "between" we subtract out the pre and post
#part of the tag. We need to count the backslashes
#in order to compensate for the extra \ in the \\ escape
n1=item.start()+len(pre)-pre.count(r'\\')
n2=item.end()-len(post)+post.count(r'\\')#as before:
#mylist is a lists of two element lists (start/st
#the two element list is the start/stop point in string
#mylist is as long as there are instances of the tag
mylist.append([n1,n2])
return mylist
#################################################ends findLatexBetween
def findLatexWhole(pre,string,post):
#returns list of indices between the pre and post
#tags. Whole includes the tags
mylist=[]
tag=pre+r'(.*?)'+post
matches=re.finditer(tag,string,re.S)
for item in matches:
mylist.append([item.start(),item.end()])
#mylist is a lists of two element lists (start/st
#the two element list is the start/stop point in string
#mylist is as long as there are instances of the tag
return mylist
################################################ ends findLatexWhole ###
def numericListGet(defaultQuiz,debugmode):
Nnumerical=Nconceptual=Nunknown=0
numericList=[]
for item in os.listdir('./bank'):
if item.endswith(".tex"):
quizType=''
sout=item
with open('./bank/'+item,'r') as fin:
strIn=fin.read()
x=findLatexBetween(r'{\\quiztype}{',strIn,'}')
quizType=strIn[x[0][0]:x[0][1]]
if quizType=="numerical":
sout='n:'+sout
Nnumerical+=1
numericList.append(item[:-4])
elif quizType=="conceptual":
sout='c:'+sout
Nconceptual+=1
else:
sout='?: '
Nunknown+=1
print('\ncounts: ',Nnumerical,'n ', Nconceptual,'c ',Nunknown,'?')
numericList.append("Default")
if debugmode==1:
quizName=defaultQuiz
else:
print(numericList)
quizName=input("Enter quiz: ")
if quizName=="Default":
quizName=defaultQuiz
return quizName
################################################### ends numericListGet##
def QA2QandA(string):
#Define 4 patterns:
beginStr=r'\\begin{choices}'
endStr=r'\\end{choices}'
CoStr=r'\\CorrectChoice '
chStr=r'\\choice '
#get Q=question
n=string.find('\\begin{choices}')
Q=string[0:n]
#print('\nbeginQ\n',Q,'\endQ\n')#diagnostic
#get A=string of answers
#newStr defines the new search parameter as 1st answer
pat=CoStr+'|'+chStr+'|'+endStr+'(.*?)'#fails-not greedy
iterations=re.finditer(pat,string,re.S)
nList=[0]
A=[]
for thing in iterations:
nList.append(thing.start())
#for i in range(len(nList)): #crashes as expected
for i in range(1,len(nList)-1):
A.append(string[nList[i]:nList[i+1]])
return [Q,A]
#######################################################end QA2QandA ###
def makeQuiz(path,quizName):
#This function has a flaw regarding questions/answers being different
#for the first rendition. It's ok as long as the first rendtion is
#not used. See the last few lines of makeQuiz.
strIn=""
with open(path,'r') as fin:
for line in fin:
strIn+=line
########### we build Quiz() etc from strIn
quiz=Quiz()
quiz.quizName=quizName
x=findLatexBetween(r'{\\quiztype}{',strIn,'}')
quizType=strIn[x[0][0]:x[0][1]]
quiz.quizType=quizType
x=findLatexBetween(r'\\begin{questions}', #List of index pairs
strIn,r'\\end{questions}') #(start,stop)
firstBatch=strIn[x[0][0]:x[0][1]] #not sure I need x and y
y=findLatexWhole(r'\\question ',firstBatch, r'\\end{choices}')
quiz.Nquestions=len(y)#=number of questions in quiz
for i in range(quiz.Nquestions):
question=Question()
question.QA=[]#I don't know why, but code ran without this
question.Q=[]#ditto
question.A=[]#ditto
question.quizName=quizName
question.number=i+1
question.quizType=quizType
#Now we search firstBatch for the i-th QA
#(where QA is the "Question&Answers" string)
questionAndAnswer=firstBatch[y[i][0]:y[i][1]]
question.QA.append(questionAndAnswer)
[Q0,A0]=QA2QandA(questionAndAnswer)
question.Q.append(Q0)
question.A.append(A0)
#more needs to be added if numerical
#Also need questionQ an question.A
quiz.questions.append(question)
if question.quizType=="numerical":
z=findLatexBetween(r'\\section{Renditions}',
strIn,r'\\section{Attribution}')
renditionsAll=strIn[z[0][0]:z[0][1]]
w=findLatexWhole(r'\\begin{questions}',renditionsAll,
r'\\end{questions}')
for i in range(quiz.Nquestions):
#reopen each question from quiz
question=quiz.questions[i]
renditionsThis=renditionsAll[w[i][0]:w[i][1]]
v=findLatexWhole(r'\\question ',renditionsThis,
r'\\end{choices}')
question.Nrenditions=len(v)
for j in range(len(v)):
QAj=renditionsThis[v[j][0]:v[j][1]]
QAj=QAj.replace('\\pagebreak',' ')
question.QA.append(QAj)
#here is where the problem is.
question.Q.append(QAj)
question.A.append(QAj)
return quiz
#################################################ends MakeQuiz
def bankQuizHeader(quizName):
#requires prefixUnderscore
sHeader =r'''
\newcommand{\quiztype}{numerical}%[[Category:QB/numerical]]
%%%%% PREAMBLE%%%%%%%%%%%%
\newif\ifkey %estabkishes Boolean ifkey to turn on and off endnotes
\documentclass[11pt]{exam}
\RequirePackage{amssymb, amsfonts, amsmath, latexsym, verbatim,
xspace, setspace,datetime}
\RequirePackage{tikz, pgflibraryplotmarks, hyperref}
\usepackage[left=.5in, right=.5in, bottom=.5in, top=.75in]{geometry}
\usepackage{endnotes, multicol,textgreek} %
\usepackage{graphicx} %
\singlespacing %OR \onehalfspacing OR \doublespacing
\parindent 0ex % Turns off paragraph indentation
\hypersetup{ colorlinks=true, urlcolor=blue}
% BEGIN DOCUMENT
\begin{document}'''+'\n'
sHeader+='\\title{'+prefixUnderscore(quizName)+'}\n'
sHeader+=r'''
\author{The LaTex code that creates this quiz is released to the Public Domain\\
Attribution for each question is documented in the Appendix}
\maketitle
\begin{center}
\includegraphics[width=0.15\textwidth]{images/666px-Wikiversity-logo-en.png}
\\For more information visit:\\
\footnotesize{
\url{https://en.wikiversity.org/wiki/Quizbank}\\
\url{https://bitbucket.org/Guy_vandegrift/qbwiki/wiki/Home}\\
\url{https://en.wikiversity.org/wiki/special:permalink/1828921}}
\end{center}
\begin{frame}{}
\begin{multicols}{3}\tableofcontents\end{multicols}
\end{frame}
\pagebreak\section{Quiz}\keytrue\printanswers'''
return sHeader
################################################### ends bankQuizHeader
def bankQuizFooter():
sFooter=r'''\pagebreak\section{Attribution}\theendnotes
\end{document}'''
return sFooter
############################################### ends bankQuizFooter ########
def printQuiz(quizName,thisQuiz):
theseQuestions=thisQuiz.questions
folderName=timeStamp+'-'+quizName
# first we make one folder with all questions
sAll=''
for thisQuestion in theseQuestions:
sAll+='\n\\subsection{}\n\\begin{questions}\n'
for rendition in thisQuestion.QA:
sAll+=rendition
sAll+='\n\\end{questions}\n'
path2=os.path.join('.\output',folderName)
if not os.path.exists(path2):
os.makedirs(path2)
pathAll=os.path.join('.\output',folderName+'_All.tex')
with open(pathAll,'w') as fout:
fout.write(bankQuizHeader(quizName))
fout.write(sAll)
fout.write(bankQuizFooter())
# next we make folders for each question
questionCount=countJumps[0] #iterates questioncount
for thisQuestion in theseQuestions:
sThis='\n\\subsection{}\n\\begin{questions}\n'
for rendition in thisQuestion.QA:
sThis+='\n'+rendition
sThis+='\n\\end{questions}\n'
sCount='_'+str(questionCount).zfill(3)+'.tex'
pathThis=os.path.join('.\output',folderName+sCount)
with open(pathThis,'w') as fout:
fout.write(bankQuizHeader(quizName))
fout.write(sThis)
fout.write(bankQuizFooter())
questionCount+=countJumps[1]
############################################### ends bankQuizFooter ########
#
## code starts here# ###########
#defaultQuiz='SelectAll'
defaultQuiz='a07_energy_cart'
countJumps=(1,1) #(first, jump) in problem numbers
timeStamp=str(int(time.time()*100))
debugmode=0 #0 if not debugging
#1: Use defaultQuiz. Don't ask in numericListGet
quizName=numericListGet(defaultQuiz,debugmode)
thisQuiz=makeQuiz('./bank/'+quizName+'.tex',quizName)
printQuiz(quizName,thisQuiz)
print('Done')
MakeExcelTemplate.py
edit##Reads the tex file in ./bank, counts the questions and
##creates ./input/excelAll.cvs that can be saved as an excel
##file and used to create collections of quizzes for selection
##into exams.
##Code requires ./bank and ./input/template directories
import os, sys, csv, re #(re not used yet)
global DIAGNOSTIC
DIAGNOSTIC=True
def getNames(): #returns [quizNames,testName])
testName="template"
quizNames=[]
availableTests=[]
texset=set()
#collect set of tex files in '/bank'
for item in os.listdir('./bank'):
if item.endswith('.tex'):
texset.add(item[:-4])
quizNames.append(item[:-4])
print('Updating template.csv in ./input')
print(len(quizNames),' bankquizzes are available for',testName)
return [quizNames,testName]
#end def getNames
def getQuizInfo(quizName):
#returns the quiztype and number of questions
path2=os.path.join('./bank',quizName+".tex")
with open(path2,'r') as fin:
quizText=fin.read()
#get quizType (spelled quiztype in latex docs)
start=quizText.find(r'\quiztype')
str2search=quizText[start+10:start+25]
if str2search.find("conceptual")==1:
quiztype="conceptual"
elif str2search.find("numerical")==1:
quiztype="numerical"
else:
quiztype="????????????????"
print('unknown quiztype in getQuizInfo')
sys.exit()
#get numberOfQuestions (aka Nquestions)
start=quizText.find(r'\begin{questions}')
stop=quizText.find(r'\end{questions}',start)
str2search=quizText[start:stop]
seeking=r'\question '
numberOfQuestions=count_occurances(str2search,seeking)
return[quiztype, numberOfQuestions]
# end def getQuizInfo
def find_all(a_str, sub): #this is a generator?
# x=list(find_all('spam spam spam spam', 'spam'))
# print(x) yields: [0, 5, 10, 15]
start = 0
while True:
start = a_str.find(sub, start)
if start == -1: return
yield start
start += len(sub)
# use start += 1 to find overlapping matches
# end def find_all
def count_occurances(a_str, sub):
# count how often "sub" exists in "a_str"
return len( list( find_all(a_str, sub) ) )
# end count_occurances
###################### START PROGRAM ########################
[quizNames,testName]=getNames()
for quizName in quizNames:
ss=[]#start spreadsheet
row=[]
#Row 1 colums: A=number questions in Test, B=TestName,
#C=Sel column: user-selected number to be on test
#D=local banksize E=quizType, F=first question
row.append("=sum(C2:C"+str(1+len(quizNames))+")") #A
row.append("x-name") #testName #B
row.append("Sel")#Select number questions test #C
row.append("=sum(D2:D"+str(1+len(quizNames))+")") #D
row.append('t') #quiz type #E
row.append(1) #first question #F
string="=if(max(g2:g"+str(1+len(quizNames))\
+")>0, F1+1,0)"#logical if
row.append(string)#allows fillright of top row #G
ss.append(row)
bankQuestionCount=0
for quizName in quizNames:
[quizType, Nquestions]=getQuizInfo(quizName)
bankQuestionCount+=Nquestions
row=[]
row.append(" ") #blank workspace #A
row.append(quizName) #B
row.append(1) #C
row.append(Nquestions) #D
if quizType=="conceptual": #E
row.append("c")
else:
row.append("n")
for j in range(Nquestions): #F
row.append("1")
ss.append(row)
path2='./input/template/'+"templateXLXS"+'.csv'
with open(path2,'w',newline='') as fout:
wr=csv.writer(fout,delimiter=',')
for row in ss:
wr.writerow(row)
print("The bank currently holds",bankQuestionCount,"questions")
print("Template update complete")
input('press enter to exit')
cleanup.py
edit#places all files into folder that starts with the same name
import os, shutil#(re not used yet)
def whatis(string, x):
print(string+' value=',repr(x),type(x))
return string+' value='+repr(x)+repr(type(x))
excluded=('images','archive','cleanup.py')
itemList=[]
for item in os.listdir():
if item not in excluded:
itemList.append(item)
folderList=[]
print('\n*folders:')
for item in filter(os.path.isdir, os.listdir(os.getcwd())):
if item not in excluded:
folderList.append(item)
for folder in folderList:
print('\n*',folder,len(folder))
for file in itemList:
if file[:len(folder)]==folder and len(file)>len(folder):
shutil.move(file,folder)
numMake.py
edit##NumMake finds a folder beginning that ends with _NUM in
##.\input and creates a numerical quiz with the same name in .\output
import os, time, sys, re
##from numMakeAux import whatis, prefixUnderscore, setup, makeHeader,\
## makeFooter
##
##from numMakeAux import bankQuizPrint
################################### Recently moved from AUX file
def whatis(string, x):
print(string+' value=',repr(x),type(x))
return string+' value='+repr(x)+repr(type(x))
################################################# end whatis
def prefixUnderscore(string):
string=string.replace('_','\\_')
return string
################################################# end prefixUnderscore
def setup(debug,quizNameDefault):
timeStamp=str(int(time.time()*100))
#1: used default NumFolder and default comment
if debug==1:
quizName=quizNameDefault
numFolder=quizName+'_NUM'
timeStamp='0000'
else:
numFolderList=[]
#Search for *NUM files in input
for item in os.listdir('../input'):
if item.endswith("_NUM"):
numFolderList.append(item)
print(numFolderList)
numFolder=input("select folder_NUM: ")
quizName=numFolder[:-4]
return [quizName,numFolder,timeStamp]
################################################# end setup
def makeHeader(quizName):
#need to remove underscores in quizName
quizname=prefixUnderscore(quizName)
sHeader=r'\newcommand{\quizname}{'
sHeader+=quizname+'}'
sHeader+=r'''
%This numerical quiz was modified by merging in questions with numMake.py
\newcommand{\quiztype}{numerical}%[[Category:QB/numerical]]
%%%%% PREAMBLE %%%%%%%
\newif\ifkey
%%%%% ifkey turns on/off endnotes
\documentclass[11pt]{exam}
\RequirePackage{amssymb, amsfonts, amsmath, latexsym, verbatim,
xspace, setspace,datetime}
\RequirePackage{tikz, pgflibraryplotmarks, hyperref}
\usepackage[left=.5in, right=.5in, bottom=.5in, top=.75in]{geometry}
\usepackage{endnotes, multicol} %try this }
\usepackage{graphicx}
\singlespacing %OR \onehalfspacing OR \doublespacing
\parindent 0ex % Turns off paragraph indentation
\hypersetup{ colorlinks=true, urlcolor=blue}
%%%% BEGIN DOCUMENT
\begin{document}
\title{\quizname}
\author{The LaTex code that creates this quiz is released to the Public Domain\\
Attribution for each question is documented in the Appendix}
\maketitle
\begin{center}
\includegraphics[width=0.15\textwidth]{images/666px-Wikiversity-logo-en.png}
\\Latex markup at\\
\footnotesize{
\url{https://en.wikiversity.org/wiki/Quizbank}\\
\url{https://bitbucket.org/Guy_vandegrift/qbwiki/wiki/Home}\\
\url{https://en.wikiversity.org/wiki/special:permalink/1936879}}
\end{center}
\begin{frame}{}
\begin{multicols}{3}\tableofcontents\end{multicols}
\end{frame}
\pagebreak\section{Quiz}
\keytrue\printanswers
'''
return sHeader
################################################# end makeHeader
def makeFooter():
return r'''\section{Attribution}\endnote{end}\theendnotes
\end{document}'''
################################################# end makeFooter
##########################################################################
################################################### will be last
def bankQuizPrint(timeStamp,quizName,sLatex):
cwd=os.getcwd()
path2QuizSoftware=os.path.dirname(cwd)#goes up
path2output=os.path.join(path2QuizSoftware,'output')
#This is the path to the timestamped folder
path2new= os.path.join(path2output,timeStamp+'_'+quizName)
if not os.path.exists(path2new):
os.makedirs(path2new)
#Write two identical Latex files with different names
pathTest=os.path.join(path2output,timeStamp+'_'+quizName+'.tex')
pathPost=os.path.join(path2new,quizName+'.tex')
with open(pathTest,'w') as fout:
fout.write(sLatex)
with open(pathPost,'w') as fout:
fout.write(sLatex)
return
################################### Begin new procedures
def getProbs():
path2probs=[]
path1=os.getcwd() #path to numerical
path2=os.path.dirname(path1) #path to QuizSoftwre
path3=os.path.join(path2,'input')
for item in os.listdir(path3):
#There should be only one *_NUM folder, but 2B safe:
if item.endswith('_NUM'):
path4=os.path.join(path3,item)
for item in os.listdir(path4):
p2prob=os.path.join(path4,item)
path2probs.append(p2prob)
return path2probs
def findLatexWhole(pre,string,post):
#returns list of indices between the pre and post
#tags. Whole includes the tags
mylist=[]
tag=pre+r'(.*?)'+post
matches=re.finditer(tag,string,re.S)
for item in matches:
mylist.append([item.start(),item.end()])
#mylist is a lists of two element lists (start/st
#the two element list is the start/stop point in string
#mylist is as long as there are instances of the tag
return mylist
def printZeroRend():
sLatex='\n\\begin{questions}\n'
path2probs=getProbs()
Nprob=len(path2probs)
#allRend=[]
for nprob in range(Nprob):
path2=path2probs[nprob]
with open(path2,'r') as fin:
strIn=''
for line in fin:
strIn+=line
x=findLatexWhole(r'\\question', #List of index pairs
strIn,r'\\end{choices}') #(start,stop)
problem=strIn[x[0][0]:x[0][1]]
sLatex+='\n'+problem+'\n'
sLatex+='\n\\end{questions}\n'
return(sLatex)
def printOtherRend():
sLatex='\\newpage\\section{Renditions}\n'
path2probs=getProbs()
Nprob=len(path2probs)
for nprob in range(0,Nprob):
sLatex+='\n\\subsection{}%'+ str(nprob)+'\n'
sLatex+='\\begin{questions}\n'
path2=path2probs[nprob]
with open(path2,'r') as fin:
strIn=''
for line in fin:
strIn+=line
x=findLatexWhole(r'\\question', #List of index pairs
strIn,r'\\end{choices}') #(start,stop)
#find number of renditions:
Nrend=len(x)
for nrend in range(1,Nrend):
rendition=strIn[x[nrend][0]:x[nrend][1]]
print('\nrendition\n', rendition)
#strIn+='\n'+rendition+'\n'
sLatex+='\n'+rendition+'\n'
#sLatex+=strIn
#sLatex+=strIn #problem
sLatex+='\\end{questions}\n'
return sLatex
############### start program #############################################
debug=0
quizNameDefault='a07_energy_cart'
[quizName,numFolder,timeStamp]=setup(debug,quizNameDefault)
sLatex=makeHeader(quizName)
sLatex+='\n'
sLatex+=printZeroRend()
sLatex+='\n'
sLatex+=printOtherRend()
sLatex+=makeFooter()
bankQuizPrint(timeStamp,quizName,sLatex)
print('done')
copyAndRename.py
edit##Copy and rename this file so that newName.py will create
##newName.tex, which is a latex file with 20 renditions of
##the same numerical question with random variables. This
##program must reside in the same program as DoNotEdit.py.
##newName.tex will not run properly without being in the
##same directory as the "images" folder.
import os, csv, re, time, shutil, random, sys, math
from DoNotEdit import roundSigFig, QuesVar, createFootnote,\
startLatex, makeAnswers, finishLatex
random.seed() #needed to make random number different each time the code opens
def finishLatex():
#Creates the latex .tex file
firstRendition=True
[questionString,prefix2question,answer2question,
units4answer,insertImage,
author,attribution,about]=writeQA(firstRendition)
bs=startLatex(author,attribution, about)+'\n'+questionString
firstRendition=False
bs+='\n\\keytrue\n'
for count in range(19):
bs+='\n\\question\n'
[questionString,prefix2question,answer2question,
units4answer,insertImage,
author,attribution,about]=writeQA(firstRendition)
bs+=questionString
bs+='\n\\end{questions}\n\\theendnotes\\end{document}'
with open(os.path.basename(sys.argv[0][:-3])+'.tex','w') as fout:
fout.write(bs)
with open(os.path.basename(sys.argv[0][:-3])+'.txt','w') as fout:
fout.write(bs)
############################################################################
########## AUTHOR OF NEW QUESTION STARTS HERE ############
############################################################################
def writeQA(firstRendition):
#Step 1: Fill in author, attribution, and short explanation
author=r'''OpenStax College University Physics'''
attribution=r'''CC-BY copyright information available at \\
\url{https://cnx.org/contents/1Q9uMg\_a@12.3:Gofkr9Oy@20/Preface}'''
about=r'''We are grateful to David Marasco and Annie Chase
of Foothill College Physics. Their effort greatly facilitated
this attempt to turn the odd problems in OpenStax
University physics into a question bank that is also
an open educational resource'''
#Step 2: To insert image, change False to True, add width and image name:
insertImage=[False, 0.3,
'Roller_coaster_energy_conservation.png']
# The "images" folder must contain this image file
#Step 3: Declare variables/image using the QuesVar class:
# Reserved variable names:
# firstRendition questionString insertImage QuesVar
# prefix2answer answer2question units2answer
# offByFactors detractorsOffBy insertImage
# All QuesVar entities: *.t (text) and *.v (value)
x1= QuesVar(1, 2.1, 3, firstRendition)#first,min,max
x2= QuesVar(2, 4.1, 5, firstRendition)#
x3= QuesVar(3, 5.1, 6, firstRendition)#
if insertImage[0]: #This inserts image, but only if requested
questionString='\\includegraphics[width='+str(insertImage[1])+\
'\\textwidth]{images/'+insertImage[2]+r'}\\'+'\n'
#the standard is for the first line to be the image
else:
questionString='' #Initializes question if no image is needed
#Step 4: Write the question *.t is text, *.v is variable
questionString+='''Alice has '''+x1.t+''' apples, '''\
+x2.t+''' oranges and '''\
+x3.t+''' pears. How many pieces of fruit does she have?'''
#Step 5: Solve the problem, defining (non-magic) variables as needed:
x=x1.v + x2.v # apples plus oranges
y=x+x3.v # add the pears
#Step 6: Use magic words to state answer, units (if needed)
prefix2answer=""# Or set to "" (empty string)
answer2question = y
units2answer =" pieces" #Blank to "" if dimensionless
#Step 7 (optional): Adjust the wrong answers (detractors)
#offByFactors is usually True: causing the RATIO of two consecutive
#detractors to equal "detractorsOffBy"
offByFactors=True
detractorsOffBy=1.1
#If offByFactors is False, the DIFFERENCE between two consecutive
#detractors to equal "detractorsOffBy"
#########################################################################
############ There is no need to edit beyond this point ######
#########################################################################
questionString+=createFootnote(author,attribution)
questionString+=makeAnswers(prefix2answer, answer2question,
units2answer,detractorsOffBy,offByFactors)
return[questionString,prefix2answer,answer2question,
units2answer,insertImage,author,attribution,about]
finishLatex() #This last dedented line is the program!
DoNotEdit.py
editimport os, csv, re, time, shutil, random, sys, math
def whatis(string, x):
print(string+' value=',repr(x),type(x))
return string+' value='+repr(x)+repr(type(x))
def roundSigFig(x, sigFig):
#rounds positive or negative x to sigFig figures
from math import log10, floor
return round(x, sigFig-int(floor(log10(abs(x))))-1)
class QuesVar:
#creates random input variables x.v(t) is number(text)
def __init__(self, first, low, high, firstRendition):
sigFigQues=3
self.precision=3 #Number of digits to round
self.first=first
# see if either low or high are floating
oneFloats=isinstance(low,float) or isinstance(high,float)
if firstRendition:
temp=first #temp is the local variable for self.v
self.v=temp #v=value
else: #it is not the first question and may or may not float
if oneFloats: #select a random floating value
temp=random.uniform(low,high)
temp=roundSigFig(temp,sigFigQues)
self.v=temp
else:
temp=random.randint(low,high)
temp=roundSigFig(temp,sigFigQues)
self.v=temp
#tempV=self.v and is used to create self.t (text version
if abs(temp) > .01 and abs(temp) < 99:
self.t=str(temp)
else:
formatStatement='{0:1.'+str(sigFigQues)+'E}'
self.t=formatStatement.format(temp)
def createFootnote(author,attribution):
s='\n\\ifkey\\endnote{Question licensed by '
s+=author+' under Creative Commons '+attribution
s+=r'}\else{}\fi'
return s
def makeAnswers(prefix2answer, answer2question,
units2answer,detractorsOffBy,offByFactors):
sigFigAns=2
s4mat="{:."+str(sigFigAns)+"E}" #string format
offBy=detractorsOffBy #separation between answers
nCorrect=random.randint(0,4)
s='\n\\begin{choices}\n'
for n in range(5):
if n==nCorrect:
s+='\\CorrectChoice '+prefix2answer
s+=s4mat.format(answer2question)
s+=units2answer+'\n'
else:
s+='\\choice '+prefix2answer
if not offByFactors:
error=offBy*(n-nCorrect)
thisAnswer=answer2question+error
s+=s4mat.format(thisAnswer)
s+=units2answer+'\n'
if offByFactors:
error=offBy**(n-nCorrect)
thisAnswer=answer2question*error
s+=s4mat.format(thisAnswer)
s+=units2answer+'\n'
s+='\\end{choices}\n'
return s
def finishLatex():
#Creates the latex .tex file
firstRendition=True
[questionString,prefix2question,answer2question,
units4answer,insertImage,
author,attribution,about]=writeQA(firstRendition)
bs=startLatex(author,attribution, about)+'\n'+questionString
firstRendition=False
bs+='\n'
for count in range(19):
bs+='\n\\question\n'
[questionString,prefix2question,answer2question,
units4answer,insertImage,
author,attribution,about]=writeQA(firstRendition)
bs+=questionString
bs+='\n\\end{questions}\n\\theendnotes\\end{document}'
with open(os.path.basename(sys.argv[0][:-3])+'.tex','w') as fout:
fout.write(bs)
with open(os.path.basename(sys.argv[0][:-3])+'.txt','w') as fout:
fout.write(bs)
def startLatex(author, attribution, about):
nameQuestion=os.path.basename(sys.argv[0][:-3])
bs=r'\newcommand{\nameQuestion}{'+nameQuestion+'}\n'
bs+=r'''\newcommand{\quiztype}{numerical}
\newif\ifkey \keytrue \documentclass[11pt]{exam}
\RequirePackage{amssymb, amsfonts, amsmath, latexsym, verbatim,xspace, setspace,datetime}
\RequirePackage{tikz, pgflibraryplotmarks, hyperref,textcomp}
\usepackage[left=.5in, right=.5in, bottom=.5in, top=.75in]{geometry}
\usepackage{endnotes, multicol,textgreek} \usepackage{graphicx}
\singlespacing \parindent 0ex \hypersetup{ colorlinks=true, urlcolor=blue}
\begin{document}\title{\nameQuestion}
\author{Question author: '''+author+r''' \\
''' + 'License: ' + attribution+r'''\\
LaTex code that generate this question is released to the Public Domain}
''' + r'''\maketitle\begin{center}
\includegraphics[width=0.15\textwidth]{images/666px-Wikiversity-logo-en.png}
\\For more information visit:\\
\footnotesize{
\url{https://en.wikiversity.org/wiki/Quizbank}\\
\url{https://bitbucket.org/Guy_vandegrift/qbwiki/wiki/Home}}
\end{center}'''
bs+='\n'+about+'\n'
bs+='\\printanswers\\begin{questions}\n\\question'
return bs
def makeAnswers(prefix2answer, answer2question,
units2answer,detractorsOffBy,offByFactors):
sigFigAns=2
s4mat="{:."+str(sigFigAns)+"E}" #string format
offBy=detractorsOffBy #separation between answers
nCorrect=random.randint(0,4)
s='\n\\begin{choices}\n'
for n in range(5):
if n==nCorrect:
s+='\\CorrectChoice '+prefix2answer
s+=s4mat.format(answer2question)
s+=units2answer+'\n'
else:
s+='\\choice '+prefix2answer
if not offByFactors:
error=offBy*(n-nCorrect)
thisAnswer=answer2question+error
s+=s4mat.format(thisAnswer)
s+=units2answer+'\n'
if offByFactors:
error=offBy**(n-nCorrect)
thisAnswer=answer2question*error
s+=s4mat.format(thisAnswer)
s+=units2answer+'\n'
s+='\\end{choices}\n'
return s