Exemple #1
0
    def run(self):
        """
            process the fillintheblank directive and generate html for output.
            :param self:
            :return: Nodes resulting from this directive.
            ...
            """

        super(FillInTheBlank, self).run()

        TEMPLATE_START = """
        <div class="%(divclass)s">
        <div data-component="fillintheblank" id="%(divid)s" %(optional)s>
            """

        TEMPLATE_END = """
        <script type="application/json">
            %(json)s
        </script>

        </div>
        </div>
            """

        addQuestionToDB(self)

        fitbNode = FITBNode(self.options, rawsource=self.block_text)
        fitbNode.source, fitbNode.line = self.state_machine.get_source_and_line(
            self.lineno
        )
        fitbNode.template_start = TEMPLATE_START
        fitbNode.template_end = TEMPLATE_END

        self.updateContent()

        self.state.nested_parse(self.content, self.content_offset, fitbNode)
        env = self.state.document.settings.env
        self.options["divclass"] = env.config.fitb_div_class

        # Expected _`structure`, with assigned variable names and transformations made:
        #
        # .. code-block::
        #   :number-lines:
        #
        #   fitbNode = FITBNode()
        #       Item 1 of problem text
        #       ...
        #       Item n of problem text
        #       feedback_bullet_list = bullet_list()  <-- The last element in fitbNode.
        #           feedback_list_item = list_item()   <-- Feedback for the first blank.
        #               feedback_field_list = field_list()
        #                   feedback_field = field()
        #                       feedback_field_name = field_name()  <-- Contains an answer.
        #                       feedback_field_body = field_body()  <-- Contains feedback for this answer.
        #                   feedback_field = field()  <-- Another answer/feedback pair.
        #           feedback_list_item = bullet_item()  <-- Feedback for the second blank.
        #               ...etc. ...
        #
        # This becomes a data structure:
        #
        # .. code-block::
        #   :number-lines:
        #
        #   self.feedbackArray = [
        #       [   # blankArray
        #           { # blankFeedbackDict: feedback 1
        #               "regex" : feedback_field_name   # (An answer, as a regex;
        #               "regexFlags" : "x"              # "i" if ``:casei:`` was specified, otherwise "".) OR
        #               "number" : [min, max]           # a range of correct numeric answers.
        #               "feedback": feedback_field_body (after being rendered as HTML)  # Provides feedback for this answer.
        #           },
        #           { # Feedback 2
        #               Same as above.
        #           }
        #       ],
        #       [  # Blank 2, same as above.
        #       ]
        #   ]
        #
        # ...and a transformed node structure:
        #
        # .. code-block::
        #   :number-lines:
        #
        #   fitbNode = FITBNode()
        #       Item 1 of problem text
        #       ...
        #       Item n of problem text
        #       FITBFeedbackNode(), which contains all the nodes in blank 1's feedback_field_body
        #       ...
        #       FITBFeedbackNode(), which contains all the nodes in blank n's feedback_field_body
        #
        self.assert_has_content()
        feedback_bullet_list = fitbNode.pop()
        if not isinstance(feedback_bullet_list, nodes.bullet_list):
            raise self.error(
                "On line {}, the last item in a fill-in-the-blank question must be a bulleted list.".format(
                    get_node_line(feedback_bullet_list)
                )
            )
        for feedback_list_item in feedback_bullet_list.children:
            assert isinstance(feedback_list_item, nodes.list_item)
            feedback_field_list = feedback_list_item[0]
            if len(feedback_list_item) != 1 or not isinstance(
                feedback_field_list, nodes.field_list
            ):
                raise self.error(
                    "On line {}, each list item in a fill-in-the-blank problems must contain only one item, a field list.".format(
                        get_node_line(feedback_list_item)
                    )
                )
            blankArray = []
            for feedback_field in feedback_field_list:
                assert isinstance(feedback_field, nodes.field)

                feedback_field_name = feedback_field[0]
                assert isinstance(feedback_field_name, nodes.field_name)
                feedback_field_name_raw = feedback_field_name.rawsource
                # See if this is a number, optinonally followed by a tolerance.
                try:
                    # Parse the number. In Python 3 syntax, this would be ``str_num, *list_tol = feedback_field_name_raw.split()``.
                    tmp = feedback_field_name_raw.split()
                    str_num = tmp[0]
                    list_tol = tmp[1:]
                    num = ast.literal_eval(str_num)
                    assert isinstance(num, Number)
                    # If no tolerance is given, use a tolarance of 0.
                    if len(list_tol) == 0:
                        tol = 0
                    else:
                        assert len(list_tol) == 1
                        tol = ast.literal_eval(list_tol[0])
                        assert isinstance(tol, Number)
                    # We have the number and a tolerance. Save that.
                    blankFeedbackDict = {"number": [num - tol, num + tol]}
                except (SyntaxError, ValueError, AssertionError):
                    # We can't parse this as a number, so assume it's a regex.
                    regex = (
                        # The given regex must match the entire string, from the beginning (which may be preceded by whitespaces) ...
                        r"^\s*"
                        +
                        # ... to the contents (where a single space in the provided pattern is treated as one or more whitespaces in the student's answer) ...
                        feedback_field_name.rawsource.replace(" ", r"\s+")
                        # ... to the end (also with optional spaces).
                        + r"\s*$"
                    )
                    blankFeedbackDict = {
                        "regex": regex,
                        "regexFlags": "i" if "casei" in self.options else "",
                    }
                    # Test out the regex to make sure it compiles without an error.
                    try:
                        re.compile(regex)
                    except Exception as ex:
                        raise self.error(
                            'Error when compiling regex "{}": {}.'.format(
                                regex, str(ex)
                            )
                        )
                blankArray.append(blankFeedbackDict)

                feedback_field_body = feedback_field[1]
                assert isinstance(feedback_field_body, nodes.field_body)
                # Append feedback for this answer to the end of the fitbNode.
                ffn = FITBFeedbackNode(
                    feedback_field_body.rawsource,
                    *feedback_field_body.children,
                    **feedback_field_body.attributes
                )
                ffn.blankFeedbackDict = blankFeedbackDict
                fitbNode += ffn

            # Add all the feedback for this blank to the feedbackArray.
            fitbNode.feedbackArray.append(blankArray)

        maybeAddToAssignment(self)

        return [fitbNode]
    def run(self):
        """
            process the fillintheblank directive and generate html for output.
            :param self:
            :return: Nodes resulting from this directive.
            ...
            """

        super(FillInTheBlank, self).run()

        TEMPLATE_START = '''
        <div class="%(divclass)s">
        <div data-component="fillintheblank" id="%(divid)s">
            '''

        TEMPLATE_END = '''
        <script type="application/json">
            %(json)s
        </script>

        </div>
        </div>
            '''

        addQuestionToDB(self)

        fitbNode = FITBNode(self.options, rawsource=self.block_text)
        fitbNode.source, fitbNode.line = self.state_machine.get_source_and_line(self.lineno)
        fitbNode.template_start = TEMPLATE_START
        fitbNode.template_end = TEMPLATE_END

        self.updateContent()

        self.state.nested_parse(self.content, self.content_offset, fitbNode)
        env = self.state.document.settings.env
        self.options['divclass'] = env.config.fitb_div_class

        # Expected _`structure`, with assigned variable names and transformations made:
        #
        # .. code-block::
        #   :number-lines:
        #
        #   fitbNode = FITBNode()
        #       Item 1 of problem text
        #       ...
        #       Item n of problem text
        #       feedback_bullet_list = bullet_list()  <-- The last element in fitbNode.
        #           feedback_list_item = list_item()   <-- Feedback for the first blank.
        #               feedback_field_list = field_list()
        #                   feedback_field = field()
        #                       feedback_field_name = field_name()  <-- Contains an answer.
        #                       feedback_field_body = field_body()  <-- Contains feedback for this answer.
        #                   feedback_field = field()  <-- Another answer/feedback pair.
        #           feedback_list_item = bullet_item()  <-- Feedback for the second blank.
        #               ...etc. ...
        #
        # This becomes a data structure:
        #
        # .. code-block::
        #   :number-lines:
        #
        #   self.feedbackArray = [
        #       [   # blankArray
        #           { # blankFeedbackDict: feedback 1
        #               "regex" : feedback_field_name   # (An answer, as a regex;
        #               "regexFlags" : "x"              # "i" if ``:casei:`` was specified, otherwise "".) OR
        #               "number" : [min, max]           # a range of correct numeric answers.
        #               "feedback": feedback_field_body (after being rendered as HTML)  # Provides feedback for this answer.
        #           },
        #           { # Feedback 2
        #               Same as above.
        #           }
        #       ],
        #       [  # Blank 2, same as above.
        #       ]
        #   ]
        #
        # ...and a transformed node structure:
        #
        # .. code-block::
        #   :number-lines:
        #
        #   fitbNode = FITBNode()
        #       Item 1 of problem text
        #       ...
        #       Item n of problem text
        #       FITBFeedbackNode(), which contains all the nodes in blank 1's feedback_field_body
        #       ...
        #       FITBFeedbackNode(), which contains all the nodes in blank n's feedback_field_body
        #
        self.assert_has_content()
        feedback_bullet_list = fitbNode.pop()
        if not isinstance(feedback_bullet_list, nodes.bullet_list):
            raise self.error('On line {}, the last item in a fill-in-the-blank question must be a bulleted list.'.format(get_node_line(feedback_bullet_list)))
        for feedback_list_item in feedback_bullet_list.children:
            assert isinstance(feedback_list_item, nodes.list_item)
            feedback_field_list = feedback_list_item[0]
            if len(feedback_list_item) != 1 or not isinstance(feedback_field_list, nodes.field_list):
                raise self.error('On line {}, each list item in a fill-in-the-blank problems must contain only one item, a field list.'.format(get_node_line(feedback_list_item)))
            blankArray = []
            for feedback_field in feedback_field_list:
                assert isinstance(feedback_field, nodes.field)

                feedback_field_name = feedback_field[0]
                assert isinstance(feedback_field_name, nodes.field_name)
                feedback_field_name_raw = feedback_field_name.rawsource
                # See if this is a number, optinonally followed by a tolerance.
                try:
                    # Parse the number. In Python 3 syntax, this would be ``str_num, *list_tol = feedback_field_name_raw.split()``.
                    tmp = feedback_field_name_raw.split()
                    str_num = tmp[0]
                    list_tol = tmp[1:]
                    num = ast.literal_eval(str_num)
                    assert isinstance(num, Number)
                    # If no tolerance is given, use a tolarance of 0.
                    if len(list_tol) == 0:
                        tol = 0
                    else:
                        assert len(list_tol) == 1
                        tol = ast.literal_eval(list_tol[0])
                        assert isinstance(tol, Number)
                    # We have the number and a tolerance. Save that.
                    blankFeedbackDict = {"number": [num - tol, num + tol]}
                except (SyntaxError, ValueError, AssertionError):
                    # We can't parse this as a number, so assume it's a regex.
                    blankFeedbackDict = {
                        'regex':
                            # The given regex must match the entire string, from the beginning (which may be preceeded by spaces) ...
                            '^ *' +
                            # ... to the contents (where a single space in the provided pattern is treated as one or more spaces in the student's anwer) ...
                            feedback_field_name.rawsource.replace(' ', ' +')
                            # ... to the end (also with optional whitespace).
                            + ' *$',
                        'regexFlags':
                            'i' if 'casei' in self.options else '',
                    }
                blankArray.append(blankFeedbackDict)

                feedback_field_body = feedback_field[1]
                assert isinstance(feedback_field_body, nodes.field_body)
                # Append feedback for this asnwer to the end of the fitbNode.
                ffn = FITBFeedbackNode(feedback_field_body.rawsource, *feedback_field_body.children, **feedback_field_body.attributes)
                ffn.blankFeedbackDict = blankFeedbackDict
                fitbNode += ffn

            # Add all the feedback for this blank to the feedbackArray.
            fitbNode.feedbackArray.append(blankArray)

        return [fitbNode]