summaryrefslogtreecommitdiff
path: root/macros/latex/contrib/webquiz/scripts/webquiz_xml.py
blob: b1db71a2dabb33cac28e72e132befb4352382fdf (plain)
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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
r'''
-----------------------------------------------------------------------------
    webquiz_xml | xml reader for reading the xml file generated by tex4ht
-----------------------------------------------------------------------------

    Copyright (C) Andrew Mathas and Donald Taylor, University of Sydney

    Distributed under the terms of the GNU General Public License (GPL)
                  http://www.gnu.org/licenses/

    This file is part of the Math_quiz system.

    <Andrew.Mathas@sydney.edu.au>
    <Donald.Taylor@sydney.edu.au>
-----------------------------------------------------------------------------
'''

# -*- encoding: utf-8 -*-

import xml.sax

# imports of webquiz code
import webquiz_util

# ---------------------------------------------------------------------------------------
def ReadWebQuizXmlFile(quizfile, defaults):
    r'''
    Set up, call and then return the xml parser
    for the quiz web page
    '''
    parser = xml.sax.make_parser()
    quiz = QuizHandler(defaults)
    parser.setContentHandler(quiz)
    parser.setErrorHandler(quiz)
    parser.setDTDHandler(quiz) # as far as I can see this does nothing...
    parser.parse(quizfile)
    parser.close()
    return quiz


# ---------------------------------------------------------------------------------------
class Data(object):
    r'''
    A wrapper object class that holds the data for the different
    components of the quiz.
    '''
    def __init__(self, **args):
        '''
        Accepts key-value pairs, each of which is stored as an attribute
        '''
        self._items = args.items()
        for key, val in args.items():
            setattr(self, key, val)


class QuizHandler(xml.sax.ContentHandler):
    """
        The content handler gives the xml tags to `startElement`, which
        initialises the webquiz tags, and then `endElement` attaches the
        content of each webquiz tag tothe appropriate part of `self`. Any end
        tag that is ot special to webquiz has its contents appended to
        `self.text`. Any tag that contains `DeFaUlT` is set to the system
        default using the `defaults` dictionary.
    """

    def __init__(self, defaults):
        self.defaults = defaults

        # arrays for the different quiz components
        self.discussion_list = []
        self.question_list = []
        self.quiz_index = []

        # these will contain the link and meta elements from the xml file but they
        # are ignored by webquiz.py
        self.link_list = []
        self.meta_list = []

        # to add mathjs when an eval comparison is used
        self.mathjs = False

        # the following tags have defaults set by `defaults`
        self.setting_tags = [
               'department',
               'department_url',
               'institution',
               'institution_url',
               'language',
               'theme',
        ]
        # quiz data
        for tag in self.setting_tags:
            setattr(self, tag, defaults[tag])
        self.breadcrumb = ''
        self.text = ''
        self.after_text = ''
        self.title = ''
        self.unit_code = ''
        self.unit_name = ''

        # keep track of current tags for debugging...
        self.current_tags=[]

    def webquiz_debug(self, msg):
        r'''
            Customised debugging message for the xml module
        '''
        webquiz_util.webquiz_debug(self.defaults.debugging, 'xml: '+msg)

    def webquiz_error(self, msg, err=None):
        r'''
            Customised error message for the xml module
        '''
        webquiz_util.webquiz_error(self.defaults.debugging, 'xml: '+msg, err)

    def set_default_attribute(self, key, value):
        ''' Set the attribute `key` of self, using the default value if
        `value=='DeFaUlT'`.
        '''
        if value.strip() == 'DeFaUlT':
            setattr(self, key, self.defaults[key])
        else:
            setattr(self, key, value)
        self.webquiz_debug('Just set "{}" equal to "{}" from "{}"'.format(key, getattr(self, key), value))

    #---- start of start elements --------------------------------------------
    def startElement(self, tag, attributes):
        '''
            At the start of each webquiz xml tag we need to pull out the
            attributes and place
        '''
        self.webquiz_debug('Starting tag for '+tag)
        self.current_tags.append(tag)

        if hasattr(self, 'start_'+tag):
            getattr(self, 'start_'+tag)(attributes)

        elif tag in ['department', 'institution', 'uni']:
            for key in attributes.keys():
                self.set_default_attribute(tag, attributes.get(key))

    def start_webquiz(self, attributes):
        r'''
        Start element for tag="webquiz". Initialise the quix and extract
        and process the list of attributes.
        '''
        for key in attributes.keys():
            self.set_default_attribute(key, attributes.get(key))

        # convert the following attibutes to booleans
        for key in ['debugging', 'hide_side_menu', 'one_page', 'pst2pdf', 'random_order']:
            setattr(self, key, getattr(self, key)=='true')

        setattr(self, 'language', self.language.lower())
        setattr(self, 'theme', self.theme.lower())

        # set debugging mode from the latex file...from this point on
        self.defaults.debugging = self.defaults.debugging or self.debugging

    def start_link(self, attributes):
        r'''
        Start element for tag="link"
        '''
        self.link_list.append({key: attributes.get(key) for key in attributes.keys()})

    def start_meta(self, attributes):
        r'''
        Start element for tag="meta"
        '''
        self.meta_list.append({key: attributes.get(key) for key in attributes.keys()})

    def start_breadcrumb(self, attributes):
        r'''
        Start element for tag="breadcrumb"
        '''
        self.set_default_attribute('breadcrumbs', attributes.get('breadcrumbs'))

    def start_unit_name(self, attributes):
        r'''
        Start element for tag="unit_name"
        '''
        self.set_default_attribute('unit_url', attributes.get('url'))
        self.quizzes_url = attributes.get('quizzes_url')
        if self.quizzes_url == 'DeFaUlT':
            self.quizzes_url = self.unit_url + '/Quizzes'

    def start_discussion(self, attributes):
        r'''
        Start element for tag="discussion"
        '''
        discussion = Data(heading = '',
                          short_heading = '',
                          text = ''  # The text of the discussion
        )
        self.discussion_list.append(discussion)

    def start_question(self, attributes):
        r'''
        Start element for tag="question"
        '''
        self.question_list.append(
            Data(text = '',      # The text of the question
                type = None,    # input, or single or multiple choice
                after_text = '' # text at end of question
            )
        )

    def start_answer(self, attributes):
        r'''
        Process the different question types, items choice and feedback
        '''
        if self.question_list[-1].type != None:
            self.webquiz_error('question {} has too many question types: {} and input'.format(
                    len(self.question_list)+1, self.question_list[-1].type)
            )
        self.question_list[-1].type = 'input'
        self.question_list[-1].answer = ''
        self.question_list[-1].feedback_right = ''
        self.question_list[-1].feedback_wrong = ''
        self.question_list[-1].text += self.text
        self.text = ''

        self.question_list[-1].comparison = attributes.get('comparison')
        self.question_list[-1].prompt = attributes.get('prompt')=='true'
        if self.question_list[-1].comparison in ['complex', 'number']:
            self.mathjs = True

    def start_choice(self, attributes):
        r'''
        Start element for tag="choice"
        '''
        if self.question_list[-1].type != None:
            self.webquiz_error('question {} has too many question types: {} and choice'.format(
                    len(self.question_list)+1, self.question_list[-1].type)
            )
        self.question_list[-1].type = attributes.get('type')
        self.question_list[-1].columns = int(attributes.get('columns'))
        self.question_list[-1].items = []
        self.question_list[-1].correct = 0
        self.question_list[-1].text += self.text
        self.text = ''

    def start_item(self, attributes):
        r'''
        Start element for tag="item"
        '''
        self.question_list[-1].items.append(
                Data(correct= attributes.get('correct'),
                     symbol=attributes.get('symbol'),
                     feedback='',
                     text=''
                    )
        )
        if attributes.get('correct')=='true':
            self.question_list[-1].correct += 1

    def start_index_item(self, attributes):
        r'''
        Finally look after the index file
        '''
        self.quiz_index.append(Data(
                prompt=attributes.get('prompt')=='true',
                url=attributes.get('url'),
                title=''
            )
        )

    def start_when(self, attributes):
        r'''
        start element for tag="when"
        '''
        if self.text.strip() != '':
            self.question_list[-1].after_text += ' '+self.text.strip()
            self.webquiz_debug('After_text is now {}'.format(self.question_list[-1].after_text))
            self.text = ''
        self.current_tags[-1] = 'feedback_'+attributes.get('type')

    #---- end of start elements ---------------------------------------------

    def endElement(self, tag):
        self.webquiz_debug('ending tag for {} (should be {})'.format(tag, self.current_tags[-1])) 

        reset_text = True
        if hasattr(self, 'end_'+tag):
            getattr(self, 'end_'+tag)()
            self.text = ''

        elif tag in self.setting_tags:
            self.set_default_attribute(tag, self.text)
            self.text = ''

        elif tag in ['heading', 'short_heading']:
            setattr(self.discussion_list[-1], tag, self.text.strip())

        elif tag in ['breadcrumb', 'title', 'unit_code', 'unit_name']:
            setattr(self, tag, self.text.strip())

        else:
            # self.text lives to be used another day
            reset_text = False

        if reset_text:
            self.text = ''

        # remove the last tag from the tag list
        self.current_tags.pop()

    #---- start of the end elements ------------------------------------------

    def end_answer(self):
        r'''
        Process end tag when tag="answer"
        '''
        self.question_list[-1].answer = self.text.strip()

    def end_discussion(self):
        r'''
        Process end tag when tag="discussion"
        '''
        self.discussion_list[-1].text = self.text.strip()

    def end_item(self):
        r'''
        Process end tag when tag="item"
        '''
        self.question_list[-1].items[-1].text = self.text.strip()

    def end_feedback(self):
        r'''
        Process end tag when tag="feedback"
        '''
        self.question_list[-1].items[-1].feedback = self.text.strip()

    def end_question(self):
        r'''
        Process end tag when tag="question"
        '''
        # first some error checking
        if self.question_list[-1].type == None:
                self.webquiz_error('Question {} does not have an \\answer or choice environment'.format(
                              len(self.question_list)+1))

        elif hasattr(self.question_list[-1], 'items'):
            if len(self.question_list[-1].items)==0:
                self.webquiz_error('question {} has no multiple choice items'.format(
                              len(self.question_list)+1))

            if self.question_list[-1].type=='single' and self.question_list[-1].correct!=1:
                self.webquiz_error('question {} is single-choice but has {} correct answers'.format(
                                len(self.question_list)+1,
                                self.question_list[-1].correct
                             )
                )
        elif not hasattr(self.question_list[-1], 'answer') or self.question_list[-1].answer=='':
            self.webquiz_error('question {} does have not an \answer or multiple choice'.format(
                          len(self.question_list)+1))

        if self.text.strip() != '':
            self.question_list[-1].after_text += ' '+self.text.strip()

    def end_index_item(self):
        r'''
        Process end tag when tag="index_item"
        '''
        self.quiz_index[-1].title = self.text.strip().replace('\n',' ').replace('\r',' ')

    def end_when(self):
        r'''
        Process end tag when tag="index_item"
        '''
        self.webquiz_debug('WHEN: Adding text to '+self.current_tags[-1])
        setattr(self.question_list[-1], self.current_tags[-1], self.text.strip())

    #---- end of end elements -----------------------------------------------

    def characters(self, text):  #data,start,length):
        r'''
        Append everything to `self.text`
        '''
        self.text += text

    def error(self, e):
        self.webquiz_error('unknown error', e)

    def fatalError(self, e):
        self.webquiz_error('unknown fatal error', e)