Welcome, guest | Sign In | My Account | Store | Cart

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.

Python, 120 lines
  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