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) + "/")