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. ----------------------------------------------------------------------------- ''' # -*- 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)