import sys from string import Template import re # types = ["multichoice", "truefalse", "shortanswer" , "matching", # "cloze", "essay", "numerical", "description"] # These are the possible # question types in moodle (for reference) FORMAT_VERSION = 2 FORMAT_TYPE = 'markdown' if FORMAT_VERSION == 2: import parse_format2 questions_from_lines = parse_format2.questions_from_lines else: import parse_format1 questions_from_lines = parse_format1.questions_from_lines from Question import Question from Question import QuestionType TERM = '201710' QUIZ_QUESTION_FILENAME = Template('./quizzes-${TERM}/Quiz${QUIZ_NUMBER}.txt') QUIZ_XML_FILENAME = Template('./quizzes-xml/Quiz${QUIZ_NUMBER}.xml') XML_HEADER = Template(""" $$course/Quizzes-${TERM}/$$Quiz ${QUIZ_NUMBER}/ """) XML_TRAILER = Template(""" """) XML_QUESTION = Template(""" $QUESTION_NUMBER_FOR_SORTING """) MATCHING_SUBQUESTION = Template(""" ${LEFT_HAND_SIDE_OF_MATCH} ${RIGHT_HAND_SIDE_OF_MATCH} """) def xml_from_txt(quiz_number, term=TERM): lines = read_questions(quiz_number, term) questions = questions_from_lines(lines) write_xml(quiz_number, questions, term) for question in questions: print(question) def read_questions(quiz_number, term=TERM): filename = QUIZ_QUESTION_FILENAME.substitute(QUIZ_NUMBER=quiz_number, TERM=term) with open(filename, 'r') as f: lines = f.read() return lines def write_xml(quiz_number, questions, term=TERM): filename = QUIZ_XML_FILENAME.substitute(QUIZ_NUMBER=quiz_number) with open(filename, 'w') as xml_file: write_xml_header(xml_file, quiz_number, term) for k in range(len(questions)): write_xml_question(questions[k], k, xml_file) write_xml_answers(questions[k], xml_file) write_xml_trailer(xml_file) def write_xml_header(xml_file, quiz_number, term): xml_file.write(XML_HEADER.substitute(QUIZ_NUMBER=quiz_number, TERM=term)) def write_xml_question(question, question_number, xml_file): """ :type question: Question """ # WARNING: More than 9,999 questions destroys the sorting. number_for_sorting = '{:0>4}'.format(question_number) question_type = question.question_type.name xml = XML_QUESTION.substitute(QUESTION_TYPE=question_type, QUESTION_NUMBER_FOR_SORTING=number_for_sorting, FORMAT_TYPE=FORMAT_TYPE, QUESTION_TEXT=question.question_text) xml_file.write(xml) def write_xml_answers(question, xml_file): """ :type question: Question """ if question.question_type == QuestionType.description: return if question.question_type == QuestionType.essay: pass def write_xml_trailer(xml_file): xml_file.write(XML_TRAILER.substitute()) # xml_from_txt(2) def txt2xml(ifi, ofi, category): ifi = open(ifi) ofi = open(ofi, 'w') ofi.write("\n\n") ofi.write("\n\n") ofi.write(" " + category + "\n\n\n") ctr = 0 question = {"text": "", "type": "", "answers": []} # Answers are a dictionary consisting of text and the amount of credit # given for this answer for li in ifi: print(li) li = li.strip() if li == "": continue mark = marktype(li.split(".")[0]) li = ".".join(li.split(".")[1:]) li = li.strip() if mark == 0: # We don't know what this line is print(li, ctr) i = input( "We don't know what " + li + " is. Is this a problem (y/n)?:") if i == 'y': sys.exit(0) else: continue if mark == 1: # We have a new question # print the old question ctr += 1 ctr = xmlout(ofi, question, ctr) # Make a new question with the given question text question = {"text": li, "type": "", "answers": []} if mark == 2: # We have a description # print the old question ctr += 1 ctr = xmlout(ofi, question, ctr) # Make a new question with the given question text question = {"text": li, "type": "description", "answers": []} if mark == 3: # We have a short answer answer # create the new answer # assign it 100% credit question["type"] = "shortanswer" question["answers"].append({"text": li, "credit": 100}) if mark == 4: # We have a t/f or multiple choice answer # create the new answer question["answers"].append({"text": li, "credit": 0}) # if it is marked with ~, assign it 100% credit if "~" in li: question["answers"][-1]["credit"] = 100 question[ "answers"][-1]["text"] = question["answers"][-1]["text"].replace("~", "") if mark == 6: # matching question/answer pair matchpair = li.split("->") if len(matchpair) == 1: assert(" -> " in li) a = matchpair[0].strip() else: q = matchpair[0].strip() a = matchpair[1].strip() question["type"] = "matching" if not "pairs" in question: question["pairs"] = [] question["pairs"].append({"question": q, "answer": a}) # print the last question ctr += 1 ctr = xmlout(ofi, question, ctr) ofi.write("") ofi.close() ifi.close() def gettype(q): if q["type"] != "": return if len(q["answers"]) == 0: q["type"] = "essay" q["answers"] = [{"text": "", "credit": 0}] # this is a true false question elif len([a for a in q["answers"] if a["text"] in ["True", "False", "true", "false"]]) == len(q["answers"]): for a in range(len(q["answers"])): q["answers"][a]["text"] = q["answers"][a]["text"].lower() q["type"] = "truefalse" else: q["type"] = "multichoice" def marktype(s): if s == "": return 0 if isint(s): return 1 if s == "D": return 2 if s == "ANS": return 3 if s in "abcdefghijklmnopqrstuvwxyz": return 4 if s == "FEED": return 5 if s == "M": return 6 return 0 def isint(a): try: int(a) return True except: return False def matchingout(ofi, question, ctr): for pair in question["pairs"]: ofi.write("\n") ofi.write("\n") ofi.write("\n") ofi.write("\n") ofi.write("\n") ofi.write("\n") def xmlout(ofi, question, ctr): if question["text"] == "": return ctr - 1 gettype(question) ofi.write("\n") ofi.write( "\n") ofi.write("\n") ofi.write( "\n\n") if question["type"] == "matching": matchingout(ofi, question, ctr) ofi.write("false\n") else: # Generally want to shuffle the answers because most people write the correct answers first. # You can override later in the XML or online. ofi.write("true\n") # handle multichoice case when checkboxes are desired anscount = 0 if question["type"] == "multichoice": for a in question["answers"]: if a["credit"] > 0: anscount += 1 if anscount > 1: # need checkboxes ofi.write('false') break anscount = max(anscount, 1) for a in question["answers"]: ofi.write( "\n") ofi.write("\n") ofi.write("\n") ofi.write("\n") return ctr # if question_type == QuestionType.truefalse: # prefix = 'True or False: ' # elif question_type == QuestionType.yesno: # prefix = 'Yes or No: ' # for k in range(len(raw_answers)): # if not raw_answers[k].strip().startswith(prefix): # raw_answers[k] = prefix + '\n' + raw_answers[k] def make_quiz(quiz_number, term='201630'): txt = './quizzes-' + term + '/Quiz' + str(quiz_number) + '.txt' xml = './quizzes-xml/Quiz' + str(quiz_number) + '.xml' category = ('$course/Quizzes-' + term + '/$Quiz ' + str(quiz_number) + '/') txt2xml(txt, xml, category) def format1_to_format2(quiz_number, term=TERM): lines = read_questions(quiz_number, term) lines = re.sub(r'^D\.', 'Q.', lines, flags=re.MULTILINE) lines = re.sub(r'^[0-9]+\.', 'Q.', lines, flags=re.MULTILINE) lines = re.sub(r'^[a-z]\.', 'A.', lines, flags=re.MULTILINE) lines = re.sub(r'^ANS\.', 'A.', lines, flags=re.MULTILINE) lines = re.sub(r'^M\.', 'A.', lines, flags=re.MULTILINE) return lines lines = format1_to_format2(1, '201710') print(lines) # make_quiz(1) # for q in range(8, 9): # txt2xml("./quizzes txt/Quiz" + str(q) + ".txt", # "./quizzes xml/Quiz" + str(q) + ".xml", "$course/Quizzes-201630/$Quiz " + str(q) + "/")