User:Guy vandegrift/2019/QuizSoftware codes

From Wikiversity
Jump to navigation Jump to search

These three codes work together to make the tkinter GUI. Run makeTest.py

makeTest.py[edit]

  1 #adding selections
  2 
  3 #Unfortuantely I had to define global variables that are not fixed:
  4 global TESTNUMBERCOUNT, SELECTEDQUIZ,MAINX,MAINY,PREVX, PREVY,WRAPLENGTH
  5 #the following global variables are fixed:
  6 global csvInfo,quizList,courseName,lengthOfPreview, fontSize,\
  7         NtestCol, quizNameCol, preCol,inQuizCol,quesCol,bigCol
  8 #csvInfo contains instructions for selecting questions, the questions
  9 #are obtained by makeQuiz to retrieve each quiz in the bank
 10 
 11 import os, sys, csv, re, time, copy, shutil, random,time#(re not used yet)
 12 import tkinter as tk
 13 
 14 from functools import partial #allows calling with parameter
 15 from PIL import ImageTk,Image #needed for my png images
 16 
 17 #The following functions were created here and moved to makeTestAuz
 18 from makeTestAux import whatis, getCourse, makeQuiz,\
 19     findLatexWhole, findLatexBetween, QA2QandA, Question,\
 20     Quiz, CsvInfo, cleanFragment, mainWindowSize, \
 21     modifyQuote
 22 from makeExamPrt import print2output
 23 
 24 csvInfo=CsvInfo()
 25 
 26 #SET WINDOW SIZES FOR MAIN GRID AND QUESTION PREVIEW:
 27 MAINX,MAINY,PREVX,PREVY=980, 500, 60, 35
 28 WRAPLENGTH=400
 29 fontSize=11#sets font size for the GUI grid, not for the exams
 30 lengthOfPreview=150#terminates question length in quizCol
 31 #I gave the columns names in case this gets redesigned:
 32 (NtestCol,quizNameCol,preCol,imageCol,inQuizCol,quesCol)=(0,1,3,10,11,12)
 33 #   0          1         3      10,      11        12
 34 
 35 def pickQuizColor(iQ):
 36     #do not move into AUX b/c SELECTEDQUIZ is called
 37     global SELECTEDQUIZ
 38     if SELECTEDQUIZ==iQ:
 39         color="#D3D3D3"
 40     else:
 41         color="white"
 42     return color
 43 
 44 
 45 class Application(tk.Frame):  
 46     global TESTNUMBERCOUNT, SELECTEDQUIZ
 47     def __init__(self, root):
 48         # I don't really understand this stuff:
 49         tk.Frame.__init__(self, root)
 50         self.canvas = tk.Canvas(root, borderwidth=2,
 51                         relief="groove", background="#ffffff")
 52         self.frame = tk.Frame(self.canvas, background="#ffffff")
 53         self.vsb = tk.Scrollbar(root, orient="vertical",
 54                                 command=self.canvas.yview)
 55         self.hsb = tk.Scrollbar(root, orient="horizontal",
 56                                 command=self.canvas.xview)
 57         self.canvas.configure(yscrollcommand=self.vsb.set)
 58         self.canvas.configure(xscrollcommand=self.hsb.set)
 59         self.vsb.pack(side="right", fill="y")
 60         self.hsb.pack(side="bottom", fill="y")
 61         self.canvas.pack(side="left", fill="both", expand=True)
 62         self.canvas.create_window((4,4),window=self.frame,
 63           anchor="nw", tags="self.frame")#(*,*) was (4,4)
 64         self.frame.bind("<Configure>", self.onFrameConfigure)     
 65     def onFrameConfigure(self, event): 
 66         self.canvas.configure(scrollregion=self.canvas.bbox("all"))
 67         self.firstWidgets()
 68 
 69 ## This destroys the prview box and image
 70     def alsoDestroy(self):
 71         for item in self.frame.grid_slaves():
 72             if int(item.grid_info()["column"])==preCol:
 73                 item.destroy()  #grid_forget() also works
 74         #For some reason I need two seperate loops, or this crashes:
 75         for item in self.frame.grid_slaves():              
 76             if int(item.grid_info()["column"])==imageCol:
 77                 item.destroy()  #grid_forget() also works
 78         ## see also "if int(item.grid_info()["column"])==imageCol"
 79                         
 80                
 81     def questionPreview(self,iQ,iq,showPreview): 
 82     #WIDGET questionPreview !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 83         for item in self.frame.grid_slaves():
 84             if int(item.grid_info()["column"])==preCol:
 85                    item.destroy()  #grid_forget() also works                    
 86         quiz = makeQuiz('./bank/'+quizList[iQ]+'.tex',quizList[iQ])
 87         #here we get the questionQA for the preview  
 88         quote=quiz.questions[iq].QA[0]
 89         quote=modifyQuote(quote)
 90         headertext=str(iQ+1)+' '+quizList[iQ]+": "
 91         headertext+="Question "+str(iq+1)+"\n"
 92         quote=headertext+quote
 93         questionPreview=tk.Label(self.frame,
 94             width=PREVX,borderwidth=4,text=quote,
 95             wraplength=WRAPLENGTH,
 96             anchor="nw")      
 97         questionPreview.grid(row=1+iq, column=preCol)
 98         #destroy previous images in imageCol
 99         for item in self.frame.grid_slaves():
100             if int(item.grid_info()["column"])==imageCol:
101                 item.destroy()  #grid_forget() also works
102                 
103         #images4question is the list of images for question #iq
104         #If no images exist, it defaults to the alligator
105         images4question=quiz.questions[iq].imageList
106         #iImage counts the images for the i-th question
107         for iImage in range(len(images4question)):
108             imagePath="./bank/images/"+images4question[iImage]
109             questionImages=Image.open(imagePath)
110             questionImages=questionImages.resize((100, 100), Image.ANTIALIAS)
111             png1=ImageTk.PhotoImage(questionImages)
112 
113             #WIDGET firstImage !!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!
114             firstImage=tk.Label(self.frame,
115                                 image=png1,
116                                 height=100,
117                                 width=100,
118                                 bg="white")
119 
120             firstImage.image=png1
121             firstImage.grid(row=iq+1+iImage,column=imageCol)
122 
123     def firstWidgets(self):
124         global TESTNUMBERCOUNT, SELECTEDQUIZ
125         quizNumberCount=tk.IntVar()
126         testNumberCount=tk.IntVar()
127         testNumberCount.set(TESTNUMBERCOUNT)        
128         print_s="Print "+ courseName+'-'+testName
129      
130     #WIDGET printButt (top row) prints test
131         printButt=tk.Button(self.frame,
132             text=print_s,
133             fg="red", bg="white",font=('',fontSize),
134             command=self.master.destroy)
135         printButt.grid(sticky='w',row=0, column=quizNameCol)
136     
137         #List bank quizzes (spans 2 columns, after NtestCol)
138         #   iQ is an index for quizzes, iq indexes the questions
139         for iQ in range(len(quizList)):
140             q_number=csvInfo.Nquestions[iQ] #x is total # questions on quiz
141             quizname_s=str(iQ+1)+': '+quizList[iQ]+' ('+str(q_number)+')'
142     #WIDGETS pdfNum  prints quiznames w number questions          
143             pdfNum=tk.Button(self.frame,
144               text=quizname_s,  fg="green",                           
145               bg=pickQuizColor(iQ),
146               borderwidth="2",
147               relief="groove",
148               font=('',fontSize),
149               command=partial(self.selectQuiz, iQ, quizNumberCount))
150             pdfNum.grid(sticky="w", row=iQ+1, column=quizNameCol,
151                                                       columnspan=2)
152     def updateCsv(self,iQ,iq,onTest,quizNumberCount):
153         #called when the inQuizCol Checkbutton is hit
154         global TESTNUMBERCOUNT,NtestCol,SELECTEDQUIZ
155         if onTest.get():
156             csvInfo.choices[iQ][iq]="1"
157             old=quizNumberCount.get()
158             quizNumberCount.set(old+1)            
159             TESTNUMBERCOUNT+=1
160         else:
161             csvInfo.choices[iQ][iq]="0"
162             old=quizNumberCount.get()
163             quizNumberCount.set(old-1)
164             TESTNUMBERCOUNT=TESTNUMBERCOUNT-1       
165         #adding selections 190724!!!!!!!!!!!!!!!!!!!!!
166         csvInfo.selections[iQ]=str(quizNumberCount.get())
167 
168     #WIDGET testNumberCount #number questions on Test
169         Ntest=tk.Label(self.frame,
170            bg="white",
171            text=str(TESTNUMBERCOUNT),                      
172            font=('bold',fontSize+2))
173         Ntest.grid(row=0, column=NtestCol)
174 
175     def selectQuiz(self, iQ,quizNumberCount):
176         global SELECTEDQUIZ
177         SELECTEDQUIZ=iQ
178       #Opens the selected quiz and creates question Checkbutton
179         #Get quiznames from quizList and create a quiz for each:
180         quiz = makeQuiz('./bank/'+quizList[iQ]+'.tex',quizList[iQ])
181         #Destroy widgets in inQuizCol column:
182         for item in self.frame.grid_slaves():
183             if int(item.grid_info()["column"])==inQuizCol:
184                    item.destroy()  #grid_forget() also works
185         #Destroy widgets in preCol column: (does not seem to work)
186         for item in self.frame.grid_slaves():
187             if int(item.grid_info()["column"])==preCol:
188                    item.destroy()  #grid_forget() also works
189                   
190         #Count questions from each quiz that will be on test
191         quizNumberCount=tk.IntVar()
192         quizNumberCount.set(0)
193 ##        for iq in range(quiz.Nquestions):
194 ##            #rint('iQ,iq,choices',iQ,iq,csvInfo.choices[iQ][iq])
195 ##            
196     #WIDGET NfromQ counts num questions from selected quiz 1st row  
197         NfromQ=tk.Label(self.frame, bg="white", font=('',fontSize+2),
198             textvariable=quizNumberCount)
199         NfromQ.grid(row=0, column=inQuizCol)      
200         for iq in range(quiz.Nquestions):
201             onTest=tk.BooleanVar()       
202             onTest.set(csvInfo.choices[iQ][iq])                   
203             if csvInfo.choices[iQ][iq]=="1":
204                 temp=quizNumberCount.get()
205                 quizNumberCount.set(temp+1)
206                 
207     #WIDGET firstCol counts number questions from quiz 1st col
208             firstCol=tk.Label(self.frame,
209                 font=('',fontSize),bg="white",
210                  textvariable=quizNumberCount)
211             firstCol.grid(row=iQ+1, column=NtestCol)
212             
213     #WIDGETS  choicesWidget toggle question ON/OFF
214             choicesWidget=tk.Checkbutton(self.frame,
215                 text="ON", font=('',fontSize),                         
216                 background="green", foreground="green",
217                 variable=onTest,                                     
218                 indicatoron=False,                                                  
219                 command=partial(self.updateCsv, iQ, iq,
220                                         onTest, quizNumberCount)                                         )        
221             choicesWidget.grid(sticky='w' ,row=iq+1, column=inQuizCol)
222 
223         #rint('iQ, quizNumberCount',iQ,quizNumberCount.get())
224         
225         ## Create quesCol for each question in selected quiz
226         # first destroy widgets already present in quesCol
227         for item in self.frame.grid_slaves():
228             if int(item.grid_info()["column"])==quesCol:
229                    item.destroy()  #grid_forget() also works
230         # s is of form #:quizname (labeled iQ+1 so as to start at 1)
231         s=str(iQ+1)+": "+quizList[iQ]
232 
233     #WIDGET quesHead places quiz's name on top row
234         quesHead=tk.Button(self.frame,text=s,
235             borderwidth="2", bg="yellow",
236             font=('',fontSize), relief="groove",
237             command=self.alsoDestroy)
238         quesHead.grid(sticky='w',row=0,column=quesCol)
239         # The selected quiz has now been placed in the top row of quesCol
240         # Next we place short fagment from question in the other rows:        
241         questionList=quiz.questions
242 
243         for iq in range(quiz.Nquestions):          
244             fragment=quiz.questions[iq].Q[0]
245             fragment=cleanFragment(fragment, lengthOfPreview)
246           
247     #WIDGETS showPreview creates button with preview (fragment)
248             showPreview=tk.BooleanVar()
249 
250         #Widget quesBox    (see also Widget first Image below
251             quesBox=tk.Button(self.frame,
252                font=('',fontSize),
253                text=str(iQ+1)+'.'+str(iq+1)+': '+fragment,                          
254                 command = partial(self.questionPreview, iQ, iq,
255                                                       showPreview))
256             quesBox.grid(sticky="w", row=iq+1, column=quesCol)
257 
258 
259 ######################################  Add new functions here
260             
261 
262 #######################################################################
263 ## Program Starts here ## Program Starts here ## Program Starts here
264 #######################################################################
265 
266 if __name__ == "__main__":
267     global SELECTEDQUIZ
268     SELECTEDQUIZ=0
269         
270     timeStamp=str(int(time.time()*100))
271     [courseName,quizList]=getCourse()
272     testName=input('Enter testName for '+courseName+': ')
273     timeName = timeStamp+'-'+courseName+'-'+testName
274     #initialize csvInfo
275     for n in range(len(quizList)):
276         quiz = makeQuiz('./bank/'+quizList[n]+'.tex',quizList[n])
277         whatis('quizList',quizList[n])
278         csvInfo.comments.append(str(n+1))#very brief!
279         csvInfo.quizNames.append(quiz.quizName)
280         #selections = # questions for given quiz that appear on test
281         csvInfo.selections.append(str(0)) #string for export to csv
282         csvInfo.Nquestions.append(quiz.Nquestions)
283         csvInfo.types.append(quiz.quizType)
284         choiceList=[]
285         #choice is "1" if on test, else it is "0"
286         for i in range(quiz.Nquestions):
287             choiceList.append(str(0))
288         csvInfo.choices.append(choiceList)
289     TESTNUMBERCOUNT=0    
290     root=tk.Tk()
291     root.geometry(mainWindowSize(MAINX,MAINY))
292     Application(root).pack(side="top", fill="both", expand=True)
293     root.mainloop()
294 
295 
296     count=0
297     print('**************')
298     for iQ in range(len(quizList)):
299         print(csvInfo.quizNames[iQ])      
300         for iq in range(csvInfo.Nquestions[iQ]):
301             if csvInfo.choices[iQ][iq ]=="1":
302                 fragment=quiz.questions[iq].Q[0]
303                 fragment=cleanFragment(fragment, lengthOfPreview)              
304                 print('*',iq+1,fragment)
305                 count+=1
306     print('*****\n',count,"questions in test")
307 
308     bnkQuizzes=[]#bnkQuizzes is a list of objects of class Quiz()
309     for name in csvInfo.quizNames:
310         if os. path. isfile('./bank/'+name+'.tex'):
311             bnkQuizzes.append(makeQuiz('./bank/'+name+'.tex',name))
312         else:
313             print(name+'is not a quiz in the bank')
314     
315     
316     
317     print2output(csvInfo,courseName,testName,bnkQuizzes)
318 
319     input('done!')

makeTestAux.py[edit]

  1 import os, sys, csv, re, time, copy, shutil, random
  2                         
  3 class Question:                                          
  4     def __init__(self):
  5         self.quizName =""
  6         self.number=0 #
  7         self.quizType = ""
  8         self.QA = [] #question and answer
  9         self.Q=[] #question only
 10         self.A=[] #answer only
 11         self.Nrenditions = 1
 12         self.imageList=[]
 13 class Quiz:
 14     def __init__(self):
 15         self.quizName =""
 16         self.quizType = ""
 17         self.questions = []#Question() clas list
 18         self.Nquestions=0#number of questions
 19 
 20 
 21 class CsvInfo:  #formerly CsvInfo
 22     def __init__(self):
 23         self.comments=[]#formerly col 1a       
 24         self.quizNames=[]#formerly col 2b
 25         self.selections=[]#formerly col 3c (Sel)
 26         self.Nquestions=[]#formerly col 4d (available)
 27         self.types=[]# t,n,c formerlycol 4e 
 28         self.choices=[] #list formerly starts at 5f
 29 
 30 
 31 def whatis(string, x):
 32     print(string+' value=',repr(x),type(x))
 33     return string+' value='+repr(x)+repr(type(x))
 34 
 35 def getCourse():    #Gets courseName and quizList from a textfile
 36     availableCourses=[]
 37     for item in os.listdir('./input'):
 38         if item.endswith('.txt'):
 39             availableCourses.append(item[:-4])
 40     for i in range(len(availableCourses)):
 41         print(i+1,availableCourses[i])
 42     if len(availableCourses)==1:
 43         courseName=availableCourses[0]
 44     else:
 45         n=input("Enter integer for course: ")
 46         courseName=availableCourses[int(n)-1]
 47 
 48     path2course='./input/'+courseName+'.txt'
 49     lines = open(path2course).read().splitlines()
 50     #strip whitespaces
 51     for i in range(len(lines)):
 52         lines[i]=lines[i].strip()
 53     #remove empty lines
 54     quizList = list(filter(None, lines))
 55     return [courseName, quizList]
 56 
 57 def findLatexWhole(pre,string,post):
 58     #returns list of indices between the pre and post
 59     #tags.  Whole includes the tags
 60     mylist=[]
 61     tag=pre+r'(.*?)'+post
 62     matches=re.finditer(tag,string,re.S)
 63     for item in matches:
 64         mylist.append([item.start(),item.end()])
 65     #mylist is a lists of two element lists (start/st
 66     #the two element list is the start/stop point in string
 67     #mylist is as long as there are instances of the tag
 68     return mylist  #ends findLatexWhole
 69 def findLatexBetween(pre,strIn,post):
 70     #returns list of indeces between the pre and post
 71     #tags.  Between excludes the tags
 72     mylist=[]
 73     tag=pre+r'(.*?)'+post
 74     matches=re.finditer(tag,strIn,re.S)
 75     for item in matches:
 76     #To get "between" we subtract out the pre and post
 77     #part of the tag.  We need to count the backslashes
 78     #in order to compensate for the extra \ in the \\ escape
 79         n1=item.start()+len(pre)-pre.count(r'\\')
 80         n2=item.end()-len(post)+post.count(r'\\')#as before:
 81     #mylist is a lists of two element lists (start/st
 82     #the two element list is the start/stop point in string
 83     #mylist is as long as there are instances of the tag
 84         mylist.append([n1,n2])
 85     return mylist                        #ends findLatexBetween
 86 
 87 def QA2QandA(string): 
 88     #Define 4 patterns:
 89     beginStr=r'\\begin{choices}'
 90     endStr=r'\\end{choices}'
 91     CoStr=r'\\CorrectChoice '
 92     chStr=r'\\choice '
 93     #get Q=question
 94     n=string.find('\\begin{choices}')
 95     Q=string[0:n]
 96     #get A=string of answers
 97     #newStr defines the new search parameter as 1st answer
 98     pat=CoStr+'|'+chStr+'|'+endStr+'(.*?)'#fails-not greedy   
 99     iterations=re.finditer(pat,string,re.S)
100     nList=[0]
101     A=[]  
102     for thing in iterations:
103         nList.append(thing.start())
104     #for i in range(len(nList)): #crashes as expected
105     for i in range(1,len(nList)-1):
106         A.append(string[nList[i]:nList[i+1]])
107     return [Q,A]
108 
109 def makeQuiz(path,quizName):
110     strIn=""
111     with open(path,'r') as fin:
112       for line in fin:
113         strIn+=line
114         ########### we build Quiz() etc from strIn
115     quiz=Quiz()
116     quiz.quizName=quizName  
117     x=findLatexBetween(r'{\\quiztype}{',strIn,'}')
118     quizType=strIn[x[0][0]:x[0][1]]
119     quiz.quizType=quizType                      
120     x=findLatexBetween(r'\\begin{questions}',  #List of index pairs
121                     strIn,r'\\end{questions}') #(start,stop)
122     #julyadd firstBatch contains all the questions
123     firstBatch=strIn[x[0][0]:x[0][1]]    #not sure I need x and y    
124     y=findLatexWhole(r'\\question',firstBatch, r'\\end{choices}')
125     quiz.Nquestions=len(y)#=number of questions in quiz
126     for i in range(quiz.Nquestions):                
127         question=Question()
128         question.QA=[]#I don't know why, but code ran without this
129         question.Q=[]#ditto
130         question.A=[]#ditto
131         question.imageList=[]#julyadd 
132         question.quizName=quizName
133         question.number=i+1
134         question.quizType=quizType
135         #Now we search firstBatch for the i-th QA
136         #(where QA is the "Question&Answers" string)
137         questionAndAnswer=firstBatch[y[i][0]:y[i][1]]
138         question.QA.append(questionAndAnswer)
139         [Q0,A0]=QA2QandA(questionAndAnswer)
140         question.Q.append(Q0)
141         question.A.append(A0)
142 
143         #Find images for each question:
144         #Since I used x and y to find the questions, zIm will find images
145         #zIm is a list of 2-element lists that mark strings of form *.png
146         zIm=findLatexBetween(r'{images/',
147                 questionAndAnswer, r'}')
148         question.imageList=[]        
149         if zIm!=[]:  #if the image list is not empty
150             for ic in range(len(zIm)): #ic counts imates
151                 #rint('imageCount',ic)
152                 imageBatch=questionAndAnswer[zIm[ic][0]:zIm[ic][1]]
153                 question.imageList.append(imageBatch)
154         else:    #insert alligator if there are no images
155             imageBatch="lake-gator.png"
156             question.imageList.append(imageBatch)
157         quiz.questions.append(question)
158     if question.quizType=="numerical":
159         z=findLatexBetween(r'\\section{Renditions}',
160                   strIn,r'\\section{Attribution}')        
161         renditionsAll=strIn[z[0][0]:z[0][1]]
162         w=findLatexWhole(r'\\begin{questions}',renditionsAll,
163                          r'\\end{questions}')
164         for i in range(quiz.Nquestions):
165             #reopen each question from quiz
166             question=quiz.questions[i]
167             renditionsThis=renditionsAll[w[i][0]:w[i][1]]
168             v=findLatexWhole(r'\\question',renditionsThis,
169                              r'\\end{choices}')
170             question.Nrenditions=len(v)
171             for j in range(len(v)):
172                 QAj=renditionsThis[v[j][0]:v[j][1]]
173                  #########  fix pagebreak bug ################                             
174                 QAj=QAj.replace('\\pagebreak',' ')
175                 question.QA.append(QAj)
176                 question.Q.append(QAj)
177                 question.A.append(QAj)                
178     return quiz #ends MakeQuiz
179 
180 def cleanFragment(fragment, lengthOfPreview):
181     fragment=fragment[10:]
182     fragment=fragment.replace('\n','')
183     fragment=fragment.replace('\\newline ',' ')
184     fragment=re.sub(' +', ' ',fragment)
185     n1=fragment.find('\includegraphics')   
186     if n1>0:
187         n2=fragment.find('.png')
188         imageStuff=fragment[n1:n2+5]
189         fragment=fragment.replace(imageStuff,'')
190         fragment=fragment[:lengthOfPreview]
191     else:
192         fragment=fragment[:lengthOfPreview]
193     n3=fragment.find('ifkey')
194     if n3>0:
195         fragment=fragment[:n3-1]
196     return fragment
197 
198 def mainWindowSize(MAINX,MAINY):
199     return str(MAINX)+"x"+str(MAINY)
200 
201 def modifyQuote(quote):
202     #modifys the quiz.Q for p_rinting into the text widget
203     quote=quote.replace('\n', ' ')
204     quote=re.sub(' +', ' ',quote)
205     matches=findLatexWhole(r'\\ifkey',quote,r'\\fi')
206     if matches:
207         n1=matches[0][0]
208         n2=matches[0][1]
209         quote=quote[:n1]+quote[n2:]   
210     return quote  
211 #I think functions that call widget variables must be defined later(?)

makeTestPrt.py[edit]

  1 import os, sys, csv, re, time, copy, shutil, random, time#(re not used yet)
  2 #addcoursename add2out
  3 global csvInfo, courseName
  4 ##from makeTestAux import whatis, getCourse, makeQuiz,\
  5 ##    findLatexWhole, findLatexBetween, QA2QandA, Question,\
  6 ##    Quiz, CsvInfo, cleanFragment, mainWindowSize, \
  7 ##    modifyQuote
  8 
  9 from makeTestAux import whatis, getCourse, makeQuiz,\
 10     findLatexWhole, findLatexBetween, QA2QandA, Question,\
 11     Quiz, CsvInfo, cleanFragment, mainWindowSize, \
 12     modifyQuote
 13 
 14 def getCsv(csvInfo,testName):
 15     return [csvInfo,cvsRows,csvData] 
 16 
 17     
 18 def getStatement():
 19     statement='%statement\n'
 20     statement+='Though posted on Wikiversity, this document was created without '
 21     statement+='wikitex using Python to write LaTeX markup.  With a bit more development '
 22     statement+='it will be possible for users to download and use software that will '
 23     statement+='permit them to create, modify, and print their own versions of this document.\n'
 24     statement+='% insert more here...\\\\\n'
 25     return statement
 26 
 27 def getFromChoices(first,second,num2pick): #select num2pick items
 28     #I am not sure I need this.  I am not overselecting any more
 29     whatis('getFromChoices first',first)
 30     random.shuffle(first)#list of integers first priority
 31     random.shuffle(second)#list of integers second priority
 32     allofem=(first+second)
 33     Nmax=min(len(allofem),num2pick)#Don't pick more than available
 34     selected=allofem[0:Nmax]
 35     random.shuffle(selected)
 36     return selected
 37 
 38 def prefixUnderscore(string):
 39     #needed because Latex does not recobnize the underscore
 40     string=string.replace('_','\\_')
 41     return string    
 42 
 43 def startLatex(titleTxt,quizType,statement,timeStamp): #this starts the Latex markup
 44     titleTex=titleTxt.replace('_','\\_')
 45     string=r'''%PREAMBLE
 46 \newcommand{\quiztype}{'''+quizType+r'''}
 47 \newif\ifkey\documentclass[11pt,twoside]{exam}
 48 \RequirePackage{amssymb, amsfonts, amsmath, latexsym, verbatim,
 49 xspace, setspace,datetime,tikz, pgflibraryplotmarks, hyperref,
 50 textcomp}
 51 \usepackage[left=.4in, right=.4in, bottom=.9in, top=.7in]{geometry}
 52 \usepackage{endnotes, multicol,textgreek,graphicx} \singlespacing 
 53 \parindent 0ex \hypersetup{ colorlinks=true, urlcolor=blue}
 54 \pagestyle{headandfoot}
 55 \runningheader{'''+titleTex+r'''}{\thepage\ of \numpages}{'''\
 56 +timeStamp+r'''}
 57 \footer{}
 58 {\LARGE{The next page might contain more answer choices for this question}}{}
 59 % BEGIN DOCUMENT 
 60 \begin{document}\title{'''+titleTex+r'''}
 61 \author{\includegraphics[width=0.10\textwidth]
 62 {images/666px-Wikiversity-logo-en.png}\\
 63 The LaTex code that creates this quiz is released to the Public Domain\\
 64 Attribution for each question is documented in the Appendix\\
 65 \url{https://bitbucket.org/Guy_vandegrift/qbwiki/wiki/Home}\\
 66 \url{https://en.wikiversity.org/wiki/Quizbank} \\
 67 '''+quizType+r''' quiz '''+timeStamp+\
 68 r'''}
 69 \maketitle
 70 '''+statement+"\n\\tableofcontents\n"
 71     return string
 72 def AllStudyLatex(courseName,testName,
 73                   statement,timeStamp,bnkQuizzes):
 74     timeCourseTest = timeStamp+'-'+courseName+'-'+testName
 75     bigStrAll=startLatex(testName+": All","mixed",statement,timeStamp)
 76     bigStrStudy=startLatex(testName+":Study","mixed",statement,timeStamp)
 77     NbnkQuiz=len(bnkQuizzes)
 78     for i in range(NbnkQuiz):
 79         quiz=bnkQuizzes[i]
 80         quizNameLatex=prefixUnderscore(quiz.quizName)    
 81         bigStrAll+=r'\section{'+quizNameLatex\
 82             +r'}\keytrue\printanswers'
 83         bigStrStudy+=r'\section{'+quizNameLatex\
 84             +r'}\keytrue\printanswers'
 85         #print zeroth renditions
 86         bigStrAll+='\n\\begin{questions}\n'
 87         bigStrStudy+='\n\\begin{questions}\n' 
 88         for j in range(quiz.Nquestions):
 89             thisQA=quiz.questions[j].QA
 90             bigStrAll+=thisQA[0]
 91             bigStrStudy+=thisQA[0]
 92         bigStrAll+='\n\\end{questions}\n'
 93         bigStrStudy+='\n\\end{questions}\n'
 94         if(quiz.questions[j].quizType)=='numerical':
 95             bigStrAll+='\n \\subsection{Renditions}'     
 96             for j in range(quiz.Nquestions): #iterates questions not renditions 
 97                 bigStrAll+='\n\\subsubsection*{'+quizNameLatex+' Q'+str(j+1)+'}'
 98                 thisQA=quiz.questions[j].QA
 99                 bigStrAll+='\n\\begin{questions}\n'            
100                 for k in range(1,len(thisQA)): #A null loop if conceptual
101                     bigStrAll+=thisQA[k]
102                 bigStrAll+='\n\\end{questions}\n'
103     bigStrAll+='\n\\section{Attribution}\\theendnotes\n\\end{document}'
104     bigStrStudy+='\n\\section{Attribution}\\theendnotes\n\\end{document}'
105     #Make output
106     path2=os.path.join('.\output',timeCourseTest) 
107     if not os.path.exists(path2):
108         os.makedirs(path2)                  
109     pathAll=os.path.join('.\output',timeCourseTest+'-All.tex')
110     with open(pathAll,'w') as latexOut:
111         latexOut.write(bigStrAll)           
112     pathStudy=os.path.join('.\output',timeCourseTest+'-Study.tex')
113     with open(pathStudy,'w') as latexOut:
114         latexOut.write(bigStrStudy)
115     return
116 
117 
118 
119 def instructorLatex(courseName,testName,
120                     statement,timeStamp,bnkQuizzes,csvInfo):
121 
122     timeName = timeStamp+'-'+courseName+'-'+testName 
123     bigStr=startLatex(testName+": Instructor","mixed",statement,timeStamp)
124     bigStr+=r'\keytrue\printanswers'
125     choiceList=[]
126     for i in range(len(bnkQuizzes)):
127         bigStr+=r'%New bank wikiquiz\\'
128         bigStr+='\n'
129         quiz=bnkQuizzes[i]
130         quizName=quiz.quizName
131         #must upgrade quizName to Latex format:
132         quizName=quizName.replace('_','\\_')
133         Nquestions=quiz.Nquestions        
134         num2pick_str=csvInfo.selections[i]# in instructorLatex
135         bigStr+=r'\subsection{'+num2pick_str
136         bigStr+=' of '+ str(Nquestions) +' questions from '+quizName+'}\n'
137         first=[]
138         second=[]
139         #choiceSel is the decision made regarding each question
140         #in the wikiquiz: 0, 1, 2, or if outide range:''
141         choiceSel=csvInfo.choices[i] #this works   
142         #convert this list of "integer strings" to a string:
143         bigStr+='\nSel: '
144         bigStr+=', '.join(choiceSel)+r'.\\'
145         #The .join prints the elements separated by the string ', '        
146         iMax=len(choiceSel)
147         for j in range(iMax):#note that the list starts at 1 not 0
148             if choiceSel[j]=='1':
149                 first.append(int(j+1))
150             if choiceSel[j]=='2':
151                 second.append(int(j+1))
152         #randChoice selects randomly from this:      
153         randChoice=getFromChoices(first,second,int(num2pick_str))
154         randChoice=sorted(randChoice)
155         #create a printable string to show the random selection:
156         bigStr+='\nRandom: '
157         bigStr+=', '.join(map(str,randChoice))+r'.\\'+'\n'
158         #choiceList is a list of lists:
159         choiceList.append(randChoice)
160         #all versions use these same choices and choice list saves
161         #Review: i indexed the quizzes, so we let j index the questions
162 
163         #Begin fix to avoid void question gag
164         if len(randChoice)!=0:
165             bigStr+='\n'+'\\begin{questions}\n'
166             for j in range(len(randChoice)):            
167                 jQ=randChoice[j]
168                 #print('randomChoice=jQ',jQ,'bnkquiz#=i',i)
169                 #recall that humans count questions beginning at 1
170                 #but python starts at zero:  jQ goes to jQ-1
171                 bigStr+=quiz.questions[jQ-1].QA[0]
172             bigStr+='\n\\end{questions}\n'
173         #End fix to avoid void gquestion tag
174         
175     bigStr+='\n\\section{Attribution}\n'
176     bigStr+='Some attributions are located elsewhere.'
177     bigStr+=r'''\endnote{The current system neglects to repeat the
178 attributions for the multipe renditions}'''
179     bigStr+='\n\\theendnotes\n\\end{document}'     #add2out Instructor   
180     pathInstructor=os.path.join('.\output',timeName+'-Instructor.tex')
181     with open(pathInstructor,'w') as latexOut:
182         latexOut.write(bigStr)
183     return choiceList
184 
185 def choiceList2string(choiceList,bnkQuizzes): 
186     #returns random selections from the choiceList in randomized order
187     testQAlist=[]
188     for i in range(len(bnkQuizzes)):
189         quiz_i=bnkQuizzes[i]
190         for j in range(len(choiceList[i])):
191             choice_j=choiceList[i][j]         
192             jPy=choice_j-1#Python iterates from zero
193             #jPy=choice_j #try this #not yet resolved           
194             #choice_j is an integer
195             question=quiz_i.questions[jPy]
196             if question.quizType=="numerical":
197                 NR=question.Nrenditions
198                 nR=random.choice(range(1,NR))
199                 testQAlist.append(question.QA[nR])
200             if question.quizType=="conceptual":
201                 Q=question.Q[0]
202                 Araw=question.A[0]
203                 Ashuffled=question.A[0]
204                 random.shuffle(Ashuffled)
205                 string=Q+'\\begin{choices}'
206                 for answer in Ashuffled:
207                     string+=answer
208                 string+='\\end{choices}\n'
209                 #testQAlist.append(Q+Ashuffled)
210                 testQAlist.append(string)    
211     random.shuffle(testQAlist)
212     bigStr=''
213     for item in testQAlist:
214         bigStr+=item+'\n'
215     return bigStr
216 
217 def testLatex(courseName,testName,
218            statement,timeStamp,bnkQuizzes,choiceList,nVersions):
219     timeName = timeStamp+'-'+courseName+'-'+testName 
220     questionList=[]
221 
222     
223 ##    for n in range(len(bnkQuizzes)):
224 ##        quiz=bnkQuizzes[n]
225 ##        string=quiz.quizName+' ('+str(quiz.Nquestions)
226 ##        string+=' '+quiz.quizType+') choices: '
227 ##        string+=', '.join(map(str,choiceList[n]))
228 ##        for quesNumStr in choiceList[n]:
229 ##            #pyQuesIndex gets us the question we want:
230 ##
231 ##            
232 ##            pyQuesIndex=int(quesNumStr)+0#was -1
233 ##            # This has no effect on the test
234 ##            
235 ##            question=quiz.questions[pyQuesIndex]
236 ##            questionList.append(question)
237     bigStr=startLatex(testName+": Test","mixed",statement,timeStamp)
238     #startLatex fixes the prefixUnderscore, but henceforth we need:
239     testNameLatex=prefixUnderscore(testName)
240     bigStr+='text before first subsection\n'
241     print
242     for nver in range(nVersions):
243         #noanswers part:
244         bigStr+='\\cleardoublepage\\subsection{V'+str(nver+1)+'}\n'
245         bigStr+='\\noprintanswers\\keyfalse\n'
246         bigStr+='\\begin{questions}\n'
247         testQAlist= choiceList2string(choiceList,bnkQuizzes)
248         
249         #???????????????????????????????????????????????????????????
250 ##        def testLatex(testName,statement,timeStamp,bnkQuizzes,choiceList,
251 ##              nVersions):#we are in this function
252 
253         whatis('choiceList is empty',choiceList) #debug
254         
255         bigStr+=testQAlist       
256         bigStr+='\\end{questions}\n'
257 
258         ##KEY part
259         bigStr+='\\cleardoublepage\\subsubsection{KEY V'+str(nver+1)+'}\n'
260         bigStr+='\\printanswers\\keyfalse\n'
261         bigStr+='\\begin{questions}\n'
262         bigStr+=testQAlist         
263         bigStr+='\\end{questions}\n'    
264     bigStr+='\n\\section{Attribution}\n'
265     bigStr+='Some attributions are located elsewhere.'
266     bigStr+=r'''\endnote{The current system neglects to repeat the
267 attributions for the multipe renditions}'''
268     bigStr+='\n\\theendnotes\n\\end{document}'  
269     pathTest=os.path.join('.\output',timeName+'-Test.tex')  #path2out
270     with open(pathTest,'w') as latexOut:
271         latexOut.write(bigStr)
272     return 
273     
274     
275 ####################   Original program started here (now its a function) ##########################
276 def print2output(csvInfo, courseName, testName, bnkQuizzes):
277 
278 
279     timeStamp=str(int(time.time()*100))
280 
281     Nversions=int(input("Nversions: "))
282     #rint(Nversions,timeStamp)
283 
284     
285     print('in print2output, look at csvInfo using whatis')
286     whatis('comments',csvInfo.comments)
287     whatis('quizNames',csvInfo.quizNames)
288     whatis('selections',csvInfo.selections)
289     whatis('Nquestions',csvInfo.Nquestions)
290     whatis('types',csvInfo.types)
291     whatis('choices',csvInfo.choices)
292     
293     print('\n'+2*'The rest is not needed yet\n'+'\n')
294 
295     csvRows=len(csvInfo.quizNames)
296     bnkQuizNames=csvInfo.quizNames[1:csvRows]
297     statement=getStatement()
298     
299     AllStudyLatex(courseName,testName,
300                   statement,timeStamp,bnkQuizzes)
301 
302     choiceList=instructorLatex(courseName,testName,
303                                statement,timeStamp,bnkQuizzes,csvInfo)
304 
305     testLatex(courseName,testName,
306               statement,timeStamp,bnkQuizzes,choiceList,
307               Nversions)   
308     
309     os.startfile('.\output')