Before discovering http://quizlet.com/, the following program was developed for running custom quizzes to help with studying for college courses. The program is not very advanced, but it works reasonably well for what it was designed to do. If the program were developed further, it would need greater capabilities than it currently has and would require a secondary system for actually creating the quizzes (currently, they are hand-typed). Quiz Me could be a starting point for anyone who wishes to actually write a program such as this and inspire others to write much better programs than what this recipe currently offers.
Quizes are stored as XML files and must be parsed and processed for use within the Quiz Me application. BankParser acts as a ContentHandler when used in parsing one of these XML files. It builds a tree of _Nodes, extracts attributes and textual data as needed, and validates the structure of the quiz bank data stream. _Nodes act as an abstract representation of XML elements and have the capability of reconstructing the relevant part of the XML that they encapsulate. Several classes follow that inherit from the _Node class and specify an attribute that node is expected to contain. The parse function takes a filename and returns a TestBank root object if parsing and validation were successful at this stage of a quiz's loading sequence.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | ####################
# source/testbank.py
####################
from xml.sax import parse as _parse
from xml.sax.handler import ContentHandler as _ContentHandler
################################################################################
class BankParser(_ContentHandler):
def __init__(self):
super().__init__()
self.context = []
def startElement(self, name, attrs):
if name == 'testbank':
# Validate
assert len(self.context) == 0
# Specific
name = attrs.getValue('name')
self.context.append(TestBank(name))
elif name == 'chapter':
# Validate
assert isinstance(self.context[-1], TestBank)
# Specific
name = attrs.getValue('name')
self.context.append(Chapter(name))
elif name == 'section':
# Validate
assert isinstance(self.context[-1], Chapter)
# Specific
name = attrs.getValue('name')
self.context.append(Section(name))
elif name == 'category':
# Validate
assert isinstance(self.context[-1], Section)
# Specific
kind = attrs.getValue('type')
assert kind in ('multiple_choice', 'true_or_false', 'matching')
self.context.append(Category(kind))
elif name == 'fact':
# Validate
assert isinstance(self.context[-1], Category)
# Specific
if self.context[-1].attr == 'multiple_choice':
kind = attrs.getValue('type')
self.context.append(Fact(kind))
else:
self.context.append(Fact())
elif name == 'question':
# Validate
assert isinstance(self.context[-1], Fact)
# Specific
self.context.append(Question())
elif name == 'answer':
# Validate
assert isinstance(self.context[-1], Fact)
# Specific
self.context.append(Answer())
else:
# Something is wrong with this document.
raise ValueError(name)
def characters(self, content):
self.context[-1].add_text(content)
def endElement(self, name):
node = self.context.pop()
if name == 'testbank':
self.TESTBANK = node
else:
self.context[-1].add_child(node)
################################################################################
class _Node:
def __init__(self, attr=None):
self.attr = attr
self.text = ''
self.children = []
def __repr__(self):
name = self.__class__.__name__.lower()
if self.attr is None:
attr = ''
else:
attr = ' {}="{}"'.format(self.ATTR_NAME, self.attr)
cache = '<{}{}>'.format(name, attr)
for child in self.children:
lines = repr(child).split('\n')
lines = map(lambda line: ' ' + line, lines)
cache += '\n' + '\n'.join(lines)
cache += self.text if self.ATTR_NAME is None else '\n'
cache += '</{}>'.format(name)
return cache
def add_text(self, content):
self.text += content
def add_child(self, node):
self.children.append(node)
################################################################################
class TestBank(_Node): ATTR_NAME = 'name'
class Chapter(_Node): ATTR_NAME = 'name'
class Section(_Node): ATTR_NAME = 'name'
class Category(_Node): ATTR_NAME = 'type'
class Fact(_Node): ATTR_NAME = 'type'
class Question(_Node): ATTR_NAME = None
class Answer(_Node): ATTR_NAME = None
################################################################################
def parse(filename):
parser = BankParser()
_parse(filename, parser)
return parser.TESTBANK
|