Exemplo n.º 1
0
def prepare(element_html, data):
    element = lxml.html.fragment_fromstring(element_html)
    required_attribs = ['file-names']
    optional_attribs = []
    pl.check_attribs(element, required_attribs, optional_attribs)

    if '_required_file_names' not in data['params']:
        data['params']['_required_file_names'] = []
    file_names = get_file_names_as_array(pl.get_string_attrib(element, 'file-names'))
    data['params']['_required_file_names'].extend(file_names)
Exemplo n.º 2
0
def prepare(element_html, data):
    element = lxml.html.fragment_fromstring(element_html)
    required_attribs = ['answers-name']
    optional_attribs = ['weight', 'correct-answer', 'label', 'suffix', 'display']
    pl.check_attribs(element, required_attribs, optional_attribs)
    name = pl.get_string_attrib(element, 'answers-name')

    correct_answer = pl.get_integer_attrib(element, 'correct-answer', None)
    if correct_answer is not None:
        if name in data['correct_answers']:
            raise Exception('duplicate correct_answers variable name: %s' % name)
        data['correct_answers'][name] = correct_answer
Exemplo n.º 3
0
def prepare(element_html, data):
    element = lxml.html.fragment_fromstring(element_html)
    required_attribs = ['file-name']
    optional_attribs = ['ace-mode', 'ace-theme', 'editor-config-function', 'source-file-name']
    pl.check_attribs(element, required_attribs, optional_attribs)
    source_file_name = pl.get_string_attrib(element, 'source-file-name', None)

    if '_required_file_names' not in data['params']:
        data['params']['_required_file_names'] = []
    data['params']['_required_file_names'].append(pl.get_string_attrib(element, 'file-name'))

    if source_file_name is not None:
        if element.text is not None and not str(element.text).isspace():
            raise Exception('Existing code cannot be added inside html element when "source-file-name" attribute is used.')
Exemplo n.º 4
0
def prepare(element_html, data):
    element = lxml.html.fragment_fromstring(element_html)
    required_attribs = ['answers-name']
    optional_attribs = ['weight', 'correct-answer', 'variables', 'label', 'display', 'allow-complex', 'imaginary-unit-for-display']
    pl.check_attribs(element, required_attribs, optional_attribs)
    name = pl.get_string_attrib(element, 'answers-name')

    correct_answer = pl.get_string_attrib(element, 'correct-answer', None)
    if correct_answer is not None:
        if name in data['correct-answers']:
            raise Exception('duplicate correct-answers variable name: %s' % name)
        data['correct-answers'][name] = correct_answer

    imaginary_unit = pl.get_string_attrib(element, 'imaginary-unit-for-display', 'i')
    if not (imaginary_unit == 'i' or imaginary_unit == 'j'):
        raise Exception('imaginary-unit-for-display must be either i or j')
Exemplo n.º 5
0
def prepare(element_html, data):
    element = lxml.html.fragment_fromstring(element_html)
    required_attribs = [
        'answer_name',          # key for 'submitted_answers' and 'true_answers'
    ]
    optional_attribs = [
        'body-position',        # [x, y, z]
        'body-orientation',     # [x, y, z, w] or [roll, pitch, yaw] or rotation matrix (3x3 ndarray) or exponential coordinates [wx, wy, wz]
        'camera-position',      # [x, y, z] - camera is z up and points at origin of space frame
        'body-cantranslate',         # true (default) or false
        'body-canrotate',         # true (default) or false
        'camera-canmove',       # true (default) or false
        'body-pose-format',       # 'rpy' (default), 'quaternion', 'matrix', 'axisangle'
        'answer-pose-format',     # 'rpy' (default), 'quaternion', 'matrix', 'axisangle'
        'text-pose-format',    # 'matrix' (default), 'quaternion', 'homogeneous'
        'show-pose-in-question',            # true (default) or false
        'show-pose-in-correct-answer',      # true (default) or false
        'show-pose-in-submitted-answer',    # true (default) or false
        'tol-translation',      # 0.5 (default : float > 0)
        'tol-rotation',          # 5 (default : float > 0)
        'grade'                 # true (default) or false
    ]
    pl.check_attribs(element, required_attribs=required_attribs, optional_attribs=optional_attribs)
Exemplo n.º 6
0
def prepare(element_html, data):
    element = lxml.html.fragment_fromstring(element_html)
    required_attribs = ['answers-name']
    optional_attribs = [
        'weight', 'correct-answer', 'variables', 'label', 'display',
        'allow-complex', 'imaginary-unit-for-display', 'size', 'show-help-text'
    ]
    pl.check_attribs(element, required_attribs, optional_attribs)
    name = pl.get_string_attrib(element, 'answers-name')

    correct_answer = pl.get_string_attrib(element, 'correct-answer',
                                          CORRECT_ANSWER_DEFAULT)
    if correct_answer is not None:
        if name in data['correct-answers']:
            raise Exception('duplicate correct-answers variable name: %s' %
                            name)
        data['correct-answers'][name] = correct_answer

    imaginary_unit = pl.get_string_attrib(element,
                                          'imaginary-unit-for-display',
                                          IMAGINARY_UNIT_FOR_DISPLAY_DEFAULT)
    if not (imaginary_unit == 'i' or imaginary_unit == 'j'):
        raise Exception('imaginary-unit-for-display must be either i or j')
Exemplo n.º 7
0
def prepare(element_html, data):
    element = lxml.html.fragment_fromstring(element_html)
    required_attribs = ['file-name']
    optional_attribs = [
        'ace-mode', 'ace-theme', 'editor-config-function', 'source-file-name',
        'min-lines', 'max-lines', 'auto-resize', 'preview', 'focus'
    ]
    pl.check_attribs(element, required_attribs, optional_attribs)
    source_file_name = pl.get_string_attrib(element, 'source-file-name',
                                            SOURCE_FILE_NAME_DEFAULT)

    file_name = pl.get_string_attrib(element, 'file-name')
    if '_required_file_names' not in data['params']:
        data['params']['_required_file_names'] = []
    elif file_name in data['params']['_required_file_names']:
        raise Exception(
            'There is more than one file editor with the same file name.')
    data['params']['_required_file_names'].append(file_name)

    if source_file_name is not None:
        if element.text is not None and not str(element.text).isspace():
            raise Exception(
                'Existing code cannot be added inside html element when "source-file-name" attribute is used.'
            )
Exemplo n.º 8
0
def prepare(element_html, data):
    element = lxml.html.fragment_fromstring(element_html)
    required_attribs = ['answers-name']
    optional_attribs = [
        'weight', 'correct-answer', 'label', 'suffix', 'display', 'size',
        'show-help-text', 'base', 'allow-blank', 'blank-value'
    ]

    pl.check_attribs(element, required_attribs, optional_attribs)
    name = pl.get_string_attrib(element, 'answers-name')
    base = pl.get_integer_attrib(element, 'base', BASE_DEFAULT)

    if base != 0 and (base < 2 or base > 36):
        raise Exception('Base must be either 0, or between 2 and 36')

    correct_answer = pl.get_string_attrib(element, 'correct-answer',
                                          CORRECT_ANSWER_DEFAULT)
    if correct_answer is not None:
        if name in data['correct_answers']:
            raise Exception('duplicate correct_answers variable name: %s' %
                            name)
        # Test conversion, but leave as string so proper value is shown on answer panel
        data['correct_answers'][name] = correct_answer

    if correct_answer is None:
        correct_answer = pl.from_json(data['correct_answers'].get(name, None))
    if correct_answer is not None:
        try:
            if not isinstance(correct_answer, int):
                correct_answer = int(correct_answer, base)
        except Exception:
            raise Exception('correct answer is not a valid input: %s' % name)
        if correct_answer > 2**53 - 1 or correct_answer < -((2**53) - 1):
            raise Exception(
                'correct answer must be between -9007199254740991 and +9007199254740991 (that is, between -(2^53 - 1) and +(2^53 - 1)).'
            )
Exemplo n.º 9
0
    def prepare_tag(html_tags, index, group=None):
        if html_tags.tag != 'pl-answer':
            raise Exception('Any html tags nested inside <pl-order-blocks> must be <pl-answer> or <pl-block-group>. \
                Any html tags nested inside <pl-block-group> must be <pl-answer>')

        if grading_method == 'external':
            pl.check_attribs(html_tags, required_attribs=[], optional_attribs=['correct'])
        elif grading_method == 'unordered':
            pl.check_attribs(html_tags, required_attribs=[], optional_attribs=['correct', 'indent'])
        elif grading_method in ['ranking', 'ordered']:
            pl.check_attribs(html_tags, required_attribs=[], optional_attribs=['correct', 'ranking', 'indent'])
        elif grading_method == 'dag':
            pl.check_attribs(html_tags, required_attribs=[], optional_attribs=['correct', 'tag', 'depends', 'comment', 'indent'])

        is_correct = pl.get_boolean_attrib(html_tags, 'correct', PL_ANSWER_CORRECT_DEFAULT)
        answer_indent = pl.get_integer_attrib(html_tags, 'indent', None)
        inner_html = pl.inner_html(html_tags)
        ranking = pl.get_integer_attrib(html_tags, 'ranking', -1)

        tag = pl.get_string_attrib(html_tags, 'tag', None)
        if grading_method == 'ranking':
            tag = str(index)
        depends = pl.get_string_attrib(html_tags, 'depends', '')
        depends = depends.strip().split(',') if depends else []

        if check_indentation is False and answer_indent is not None:
            raise Exception('<pl-answer> should not specify indentation if indentation is disabled.')

        answer_data_dict = {'inner_html': inner_html,
                            'indent': answer_indent,
                            'ranking': ranking,
                            'index': index,
                            'tag': tag,          # set by HTML with DAG grader, set internally for ranking grader
                            'depends': depends,  # only used with DAG grader
                            'group': group       # only used with DAG grader
                            }
        if is_correct:
            correct_answers.append(answer_data_dict)
        else:
            incorrect_answers.append(answer_data_dict)
Exemplo n.º 10
0
def render(element_html, data):
    element = lxml.html.fragment_fromstring(element_html)
    digits = pl.get_integer_attrib(element, 'digits', 2)
    show_matlab = pl.get_boolean_attrib(element, 'show-matlab', True)
    show_mathematica = pl.get_boolean_attrib(element, 'show-mathematica', True)
    show_python = pl.get_boolean_attrib(element, 'show-python', True)
    default_tab = pl.get_string_attrib(element, 'default-tab', 'matlab')

    tab_list = ['matlab', 'mathematica', 'python']
    if default_tab not in tab_list:
        raise Exception(f'invalid default-tab: {default_tab}')

    # Setting the default tab
    displayed_tab = [show_matlab, show_mathematica, show_python]
    if not any(displayed_tab):
        raise Exception('All tabs have been hidden from display. At least one tab must be shown.')

    default_tab_index = tab_list.index(default_tab)
    # If not displayed, make first visible tab the default
    if not displayed_tab[default_tab_index]:
        first_display = displayed_tab.index(True)
        default_tab = tab_list[first_display]
    default_tab_index = tab_list.index(default_tab)

    # Active tab should be the default tab
    default_tab_list = [False, False, False]
    default_tab_list[default_tab_index] = True
    [active_tab_matlab, active_tab_mathematica, active_tab_python] = default_tab_list

    # Process parameter data
    matlab_data = ''
    mathematica_data = ''
    python_data = 'import numpy as np\n\n'
    for child in element:
        if child.tag == 'variable':
            # Raise exception if variable does not have a name
            pl.check_attribs(child, required_attribs=['params-name'], optional_attribs=['comment', 'digits'])

            # Get name of variable
            var_name = pl.get_string_attrib(child, 'params-name')

            # Get value of variable, raising exception if variable does not exist
            var_data = data['params'].get(var_name, None)
            if var_data is None:
                raise Exception('No value in data["params"] for variable %s in pl-variable-output element' % var_name)

            # If the variable is in a format generated by pl.to_json, convert it
            # back to a standard type (otherwise, do nothing)
            var_data = pl.from_json(var_data)

            # Get comment, if it exists
            var_matlab_comment = ''
            var_mathematica_comment = ''
            var_python_comment = ''
            if pl.has_attrib(child, 'comment'):
                var_comment = pl.get_string_attrib(child, 'comment')
                var_matlab_comment = f' % {var_comment}'
                var_mathematica_comment = f' (* {var_comment} *)'
                var_python_comment = f' # {var_comment}'

            # Get digit for child, if it exists
            if not pl.has_attrib(child, 'digits'):
                var_digits = digits
            else:
                var_digits = pl.get_string_attrib(child, 'digits')

            # Assembling Python array formatting
            if np.isscalar(var_data):
                prefix = ''
                suffix = ''
            else:
                # Wrap the variable in an ndarray (if it's already one, this does nothing)
                var_data = np.array(var_data)
                # Check shape of variable
                if var_data.ndim > 2:
                    raise Exception('Value in data["params"] for variable %s in pl-variable-output element must be a scalar, a vector, or a 2D array' % var_name)
                # Create prefix/suffix so python string is np.array( ... )
                prefix = 'np.array('
                suffix = ')'

            # Mathematica reserved letters: C D E I K N O
            mathematica_reserved = ['C', 'D', 'E', 'I', 'K', 'N', 'O']
            if pl.inner_html(child) in mathematica_reserved:
                mathematica_suffix = 'm'
            else:
                mathematica_suffix = ''

            # Create string for matlab and python format
            var_name_disp = pl.inner_html(child)
            var_matlab_data = pl.string_from_numpy(var_data, language='matlab', digits=var_digits)
            var_mathematica = pl.string_from_numpy(var_data, language='mathematica', digits=var_digits)
            var_python_data = pl.string_from_numpy(var_data, language='python', digits=var_digits)

            matlab_data += f'{var_name_disp} = {var_matlab_data};{var_matlab_comment}\n'
            mathematica_data += f'{var_name_disp}{mathematica_suffix} = {var_mathematica};{var_mathematica_comment}\n'
            python_data += f'{var_name_disp} = {prefix}{var_python_data}{suffix}{var_python_comment}\n'

    html_params = {
        'active_tab_matlab': active_tab_matlab,
        'active_tab_mathematica': active_tab_mathematica,
        'active_tab_python': active_tab_python,
        'show_matlab': show_matlab,
        'show_mathematica': show_mathematica,
        'show_python': show_python,
        'matlab_data': matlab_data,
        'mathematica_data': mathematica_data,
        'python_data': python_data,
        'uuid': pl.get_uuid()
    }

    with open('pl-variable-output.mustache', 'r', encoding='utf-8') as f:
        html = chevron.render(f, html_params).strip()

    return html
Exemplo n.º 11
0
def prepare(element_html, data):
    element = lxml.html.fragment_fromstring(element_html)
    pl.check_attribs(
        element,
        required_attribs=['params-name'],
        optional_attribs=['text', 'no-highlight', 'prefix', 'suffix'])
Exemplo n.º 12
0
def parse(element_html, data):
    element = lxml.html.fragment_fromstring(element_html)
    answer_name = pl.get_string_attrib(element, 'answers-name')

    temp = answer_name
    temp += '-input'
    # the answer_name textfields that raw-submitted-answer reads from
    # have '-input' appended to their name attribute

    student_answer_temp = ''
    if temp in data['raw_submitted_answers']:
        student_answer_temp = data['raw_submitted_answers'][temp]

    if student_answer_temp is None:
        data['format_errors'][answer_name] = 'NULL was submitted as an answer.'
        return
    elif student_answer_temp == '':
        data['format_errors'][answer_name] = 'No answer was submitted.'
        return

    student_answer = []
    student_answer_indent = []
    grading_mode = pl.get_string_attrib(element, 'grading-method',
                                        GRADING_METHOD_DEFAULT)

    student_answer_ranking = ['Question grading_mode is not "ranking"']

    student_answer_temp = json.loads(student_answer_temp)

    student_answer = student_answer_temp['answers']
    student_answer_indent = student_answer_temp['answer_indent']

    if grading_mode.lower() == 'ranking':
        student_answer_ranking = []
        pl_drag_drop_element = lxml.html.fragment_fromstring(element_html)
        for answer in student_answer:
            e = pl_drag_drop_element.xpath(f'.//pl-answer[text()="{answer}"]')
            is_correct = pl.get_boolean_attrib(
                e[0], 'correct',
                PL_ANSWER_CORRECT_DEFAULT)  # default correctness to True
            if is_correct:
                ranking = pl.get_integer_attrib(e[0], 'ranking', 0)
            else:
                ranking = -1  # wrong answers have no ranking
            student_answer_ranking.append(ranking)

    if pl.get_string_attrib(element, 'grading-method',
                            'ordered') == 'external':
        for html_tags in element:
            if html_tags.tag == 'pl-answer':
                pl.check_attribs(html_tags,
                                 required_attribs=[],
                                 optional_attribs=[])
        file_name = pl.get_string_attrib(element, 'file-name',
                                         FILE_NAME_DEFAULT)

        answer_code = ''
        for index, answer in enumerate(student_answer):
            indent = int(student_answer_indent[index])
            answer_code += ('    ' * indent) + answer + '\n'

        if len(answer_code) == 0:
            data['format_errors']['_files'] = 'The submitted file was empty.'
        else:
            data['submitted_answers']['_files'] = [{
                'name':
                file_name,
                'contents':
                base64.b64encode(answer_code.encode('utf-8')).decode('utf-8')
            }]

    data['submitted_answers'][answer_name] = {
        'student_submission_ordering': student_answer_ranking,
        'student_raw_submission': student_answer,
        'student_answer_indent': student_answer_indent
    }
    if temp in data['submitted_answers']:
        del data['submitted_answers'][temp]
Exemplo n.º 13
0
def prepare(element_html, data):
    element = lxml.html.fragment_fromstring(element_html)
    pl.check_attribs(element, required_attribs=[], optional_attribs=['engine', 'params-name-matrix', 'weights', 'weights-digits', 'weights-presentation-type', 'params-name-labels'])
Exemplo n.º 14
0
def prepare(element_html, data):
    element = lxml.html.fragment_fromstring(element_html)
    required_attribs = ['answers-name']
    optional_attribs = ['weight', 'label', 'comparison', 'rtol', 'atol', 'digits', 'allow-complex']
    pl.check_attribs(element, required_attribs, optional_attribs)
Exemplo n.º 15
0
def prepare(element_html, data):
    element = lxml.html.fragment_fromstring(element_html)
    required_attribs = []
    optional_attribs = []
    pl.check_attribs(element, required_attribs, optional_attribs)
Exemplo n.º 16
0
def prepare(element_html, data):
    element = lxml.html.fragment_fromstring(element_html)
    required_attribs = ['params-name']
    optional_attribs = ['digits', 'presentation-type']
    pl.check_attribs(element, required_attribs, optional_attribs)
Exemplo n.º 17
0
def prepare(element_html, data):
    element = lxml.html.fragment_fromstring(element_html)
    pl.check_attribs(element,
                     required_attribs=['file-name'],
                     optional_attribs=['type', 'directory', 'label'])
Exemplo n.º 18
0
def prepare(element_html, data):
    element = lxml.html.fragment_fromstring(element_html)
    check_attributes_rec(element)

    w_button = None

    prev = not pl.get_boolean_attrib(element, 'gradable',
                                     defaults.element_defaults['gradable'])

    load_extensions(data)

    # Some preparation for elements with grading componenet
    if not prev:
        name = pl.get_string_attrib(element, 'answers-name', None)
        if name is None:
            raise Exception(
                'answers-name is required if gradable mode is enabled')

        n_id = 0
        n_control_elements = 0

        answer_child = None
        initial_child = None

        for child in element:
            # Get all the objects in pl-drawing-answer
            if child.tag == 'pl-drawing-answer':
                if answer_child is not None:
                    raise Exception(
                        'You should have only one pl-drawing-answer inside a pl-drawing.'
                    )
                draw_error_box = pl.get_boolean_attrib(
                    child, 'draw-error-box',
                    defaults.element_defaults['draw-error-box'])
                answer_child = child
            # Get all the objects in pl-drawing-initial
            if child.tag == 'pl-drawing-initial':
                if initial_child is not None:
                    raise Exception(
                        'You should have only one pl-drawing-initial inside a pl-drawing.'
                    )
                initial_child = child
            # Get the width of the vector defined in the pl-drawing-button for pl-vector
            if child.tag == 'pl-controls':
                n_control_elements += 1
                for groups in child:
                    if groups.tag == 'pl-controls-group':
                        for buttons in groups:
                            if buttons.tag == 'pl-drawing-button':
                                type_name = buttons.attrib.get('type', None)
                                if type_name == 'pl-arc-vector-CCW':
                                    type_name = 'pl-arc-vector'
                                elif type_name == 'pl-arc-vector-CW':
                                    type_name = 'pl-arc-vector'
                                type_attribs = elements.get_attributes(
                                    type_name)
                                if elements.should_validate_attributes(
                                        type_name):
                                    pl.check_attribs(
                                        buttons,
                                        required_attribs=['type'],
                                        optional_attribs=type_attribs)
                                if buttons.attrib['type'] == 'pl-vector':
                                    if 'width' in buttons.attrib:
                                        w_button = buttons.attrib['width']
                                    else:
                                        w_button = None

        if answer_child is None:
            raise Exception(
                'You do not have any "pl-drawing-answer" inside pl-drawing where gradable=True. You should either specify the "pl-drawing-answer" if you want to grade objects, or make gradable=False'
            )

        # Generate these in order so that answer elements are displayed on top of initial elements
        init = None
        if initial_child is not None:
            init, n_id = render_drawing_items(initial_child, n_id)
        ans, n_id = render_drawing_items(answer_child, n_id)

        # Makes sure that all objects in pl-drawing-answer are graded
        # and all the objects in pl-drawing--initial are not graded

        for obj in ans:
            obj['graded'] = True
            obj['drawErrorBox'] = draw_error_box
            if 'objectDrawErrorBox' in obj:
                if obj['objectDrawErrorBox'] is not None:
                    obj['drawErrorBox'] = obj['objectDrawErrorBox']
            # Check to see if consistent width for pl-vector is used for correct answer
            # and submitted answers that are added using the buttons
            if obj['gradingName'] == 'vector':
                if (w_button is None and obj['width']
                        == defaults.drawing_defaults['force-width']
                    ) or obj['width'] == float(w_button):
                    continue
                else:
                    raise Exception(
                        'Width is not consistent! pl-vector in pl-drawing-answers needs to have the same width of pl-vector in pl-drawing-button.'
                    )

        # Combines all the objects in pl-drawing-answers and pl-drawing-initial
        # and saves in correct_answers
        if init is not None:
            for obj in init:
                obj['graded'] = False
            data['correct_answers'][name] = union_drawing_items(init, ans)
        else:
            data['correct_answers'][name] = ans
Exemplo n.º 19
0
def prepare(element_html, data):
    element = lxml.html.fragment_fromstring(element_html)
    pl.check_attribs(element, required_attribs=['file-name'], optional_attribs=['type', 'directory', 'label', 'force-download'])
Exemplo n.º 20
0
def prepare(element_html, data):
    element = lxml.html.fragment_fromstring(element_html)
    required_attribs = ['answers-name']
    optional_attribs = ['weight', 'number-answers', 'fixed-order', 'inline']
    pl.check_attribs(element, required_attribs, optional_attribs)
    name = pl.get_string_attrib(element, 'answers-name')

    correct_answers = []
    incorrect_answers = []
    index = 0
    for child in element:
        if child.tag in ['pl-answer', 'pl_answer']:
            pl.check_attribs(child, required_attribs=[], optional_attribs=['correct'])
            correct = pl.get_boolean_attrib(child, 'correct', False)
            child_html = pl.inner_html(child)
            answer_tuple = (index, correct, child_html)
            if correct:
                correct_answers.append(answer_tuple)
            else:
                incorrect_answers.append(answer_tuple)
            index += 1

    len_correct = len(correct_answers)
    len_incorrect = len(incorrect_answers)
    len_total = len_correct + len_incorrect

    if len_correct < 1:
        raise Exception('pl-multiple-choice element must have at least one correct answer')

    number_answers = pl.get_integer_attrib(element, 'number-answers', len_total)

    number_answers = max(1, min(1 + len_incorrect, number_answers))
    number_correct = 1
    number_incorrect = number_answers - number_correct
    if not (0 <= number_incorrect <= len_incorrect):
        raise Exception('INTERNAL ERROR: number_incorrect: (%d, %d, %d)' % (number_incorrect, len_incorrect, number_answers))

    sampled_correct = random.sample(correct_answers, number_correct)
    sampled_incorrect = random.sample(incorrect_answers, number_incorrect)

    sampled_answers = sampled_correct + sampled_incorrect
    random.shuffle(sampled_answers)

    fixed_order = pl.get_boolean_attrib(element, 'fixed-order', False)
    if fixed_order:
        # we can't simply skip the shuffle because we already broke the original
        # order by separating into correct/incorrect lists
        sampled_answers.sort(key=lambda a: a[0])  # sort by stored original index

    display_answers = []
    correct_answer = None
    for (i, (index, correct, html)) in enumerate(sampled_answers):
        keyed_answer = {'key': chr(ord('a') + i), 'html': html}
        display_answers.append(keyed_answer)
        if correct:
            correct_answer = keyed_answer

    if name in data['params']:
        raise Exception('duplicate params variable name: %s' % name)
    if name in data['correct_answers']:
        raise Exception('duplicate correct_answers variable name: %s' % name)
    data['params'][name] = display_answers
    data['correct_answers'][name] = correct_answer
Exemplo n.º 21
0
def prepare(element_html, data):
    element = lxml.html.fragment_fromstring(element_html)

    required_attribs = ['answers-name']
    optional_attribs = [
        'fixed-order', 'number-statements', 'number-options',
        'none-of-the-above', 'blank', 'counter-type'
    ]
    pl.check_attribs(element, required_attribs, optional_attribs)
    name = pl.get_string_attrib(element, 'answers-name')
    options, statements = categorize_matches(element, data)

    # Choose and randomize the options and statements. Each can be in a fixed order.
    fixed_statements_order = pl.get_boolean_attrib(
        element, 'fixed-order', FIXED_STATEMENTS_ORDER_DEFAULT)
    number_statements = pl.get_integer_attrib(element, 'number-statements',
                                              len(statements))
    number_options = pl.get_integer_attrib(element, 'number-options',
                                           len(options))
    nota = pl.get_boolean_attrib(element, 'none-of-the-above', NOTA_DEFAULT)

    # Organize the list of statements to use.
    if fixed_statements_order:
        if number_statements < len(statements):
            # Take a random sampling, but maintain the original order of the statements.
            indices = random.sample(range(len(statements)), number_statements)
            statements = [statements[i] for i in sorted(indices)]
        # Otherwise, just use all the statements as-is.
    else:
        # Shuffle or sample the statements.
        if number_statements < len(statements):
            statements = random.sample(statements, number_statements)
        else:
            random.shuffle(statements)

    # Organize the list of options to use.
    # First, select all the options associated with the chosen statements.
    needed_options_keys = set((s['match'] for s in statements))
    needed_options, distractors = partition(
        options, lambda opt: opt['name'] in needed_options_keys)

    if len(needed_options) < number_options:
        # The limit is set above the # of options needed to match the chosen statements.
        # Add distractor options; and None of the Above if needed.
        more_needed = number_options - len(needed_options)
        if more_needed >= len(distractors):
            # Add all distractors.
            needed_options.extend(distractors)
            # Add NOTA if that's still not enough.
            if more_needed > len(distractors):
                nota = True
        else:
            # Add a sample of the distractors.
            needed_options.extend(random.sample(distractors, more_needed))
        options = needed_options
        random.shuffle(options)

    elif len(needed_options) > number_options:
        # The limit is set below the # of options needed.
        # Add None of the Above to compensate.
        options = random.sample(needed_options, number_options)
        nota = True
    else:
        # The number of needed options matches the total options.
        options = needed_options
        random.shuffle(options)

    if nota:
        options.append({
            'index': len(options),
            'name': '__nota__',
            'html': 'None of the above'
        })

    # Build the options to display to the student.
    chosen_option_names = []
    display_options = []
    for (i, opt) in enumerate(options):
        keyed_option = {'key': opt['name'], 'html': opt['html']}
        display_options.append(keyed_option)
        chosen_option_names.append(opt['name'])

    # Build the statements to display to the student.
    display_statements = []
    correct_matches = []
    for (i, statement) in enumerate(statements):
        # Check if the matched option was removed from the display_options to make room for
        # none-of-the-above option.
        if nota and statement['match'] not in chosen_option_names:
            match_index = len(options) - 1
        else:
            match_index = chosen_option_names.index(statement['match'])

        keyed_statement = {
            'key': str(i),
            'html': statement['html'],
            'match': statement['match']
        }
        display_statements.append(keyed_statement)
        correct_matches.append(match_index)

    data['params'][name] = (display_statements, display_options)
    data['correct_answers'][name] = correct_matches
Exemplo n.º 22
0
def prepare(element_html, data):
    element = lxml.html.fragment_fromstring(element_html)
    required_attribs = []
    optional_attribs = ['question', 'submission', 'answer']
    pl.check_attribs(element, required_attribs, optional_attribs)
Exemplo n.º 23
0
def prepare(element_html, data):
    element = lxml.html.fragment_fromstring(element_html)
    required_attribs = ['answers-name']
    optional_attribs = ['weight', 'label', 'comparison', 'rtol', 'atol', 'digits', 'allow-complex']
    pl.check_attribs(element, required_attribs, optional_attribs)
Exemplo n.º 24
0
def prepare(element_html, data):
    if not use_pl_variable_score:
        return

    element = lxml.html.fragment_fromstring(element_html)
    pl.check_attribs(element, required_attribs=['answers-name'], optional_attribs=[])
Exemplo n.º 25
0
def prepare(element_html, data):
    element = lxml.html.fragment_fromstring(element_html)
    answer_name = pl.get_string_attrib(element, 'answers-name')

    required_attribs = ['answers-name']
    optional_attribs = [
        'source-blocks-order', 'grading-method', 'indentation',
        'source-header', 'solution-header', 'file-name', 'solution-placement',
        'max-incorrect', 'min-incorrect', 'weight', 'inline', 'max-indent',
        'feedback', 'partial-credit'
    ]

    pl.check_attribs(element,
                     required_attribs=required_attribs,
                     optional_attribs=optional_attribs)

    check_indentation = pl.get_boolean_attrib(element, 'indentation',
                                              INDENTION_DEFAULT)
    grading_method = pl.get_string_attrib(element, 'grading-method',
                                          GRADING_METHOD_DEFAULT)
    feedback_type = pl.get_string_attrib(element, 'feedback', FEEDBACK_DEFAULT)

    if grading_method in ['dag', 'ranking']:
        partial_credit_type = pl.get_string_attrib(element, 'partial-credit',
                                                   'lcs')
        if partial_credit_type not in ['none', 'lcs']:
            raise Exception('partial credit type "' + partial_credit_type +
                            '" is not available with the "' + grading_method +
                            '" grading-method.')
    elif pl.get_string_attrib(element, 'partial-credit', None) is not None:
        raise Exception(
            'You may only specify partial credit options in the DAG and ranking grading modes.'
        )

    accepted_grading_method = [
        'ordered', 'unordered', 'ranking', 'dag', 'external'
    ]
    if grading_method not in accepted_grading_method:
        raise Exception(
            'The grading-method attribute must be one of the following: ' +
            ', '.join(accepted_grading_method))

    if (grading_method not in ['dag', 'ranking'] and feedback_type != 'none') or \
       (grading_method in ['dag', 'ranking'] and feedback_type not in ['none', 'first-wrong']):
        raise Exception('feedback type "' + feedback_type +
                        '" is not available with the "' + grading_method +
                        '" grading-method.')

    correct_answers = []
    incorrect_answers = []

    def prepare_tag(html_tags,
                    index,
                    group_info={
                        'tag': None,
                        'depends': None
                    }):
        if html_tags.tag != 'pl-answer':
            raise Exception(
                'Any html tags nested inside <pl-order-blocks> must be <pl-answer> or <pl-block-group>. \
                Any html tags nested inside <pl-block-group> must be <pl-answer>'
            )

        if grading_method == 'external':
            pl.check_attribs(html_tags,
                             required_attribs=[],
                             optional_attribs=['correct'])
        elif grading_method in ['unordered', 'ordered']:
            pl.check_attribs(html_tags,
                             required_attribs=[],
                             optional_attribs=['correct', 'indent'])
        elif grading_method == 'ranking':
            pl.check_attribs(html_tags,
                             required_attribs=[],
                             optional_attribs=['correct', 'ranking', 'indent'])
        elif grading_method == 'dag':
            pl.check_attribs(html_tags,
                             required_attribs=[],
                             optional_attribs=[
                                 'correct', 'tag', 'depends', 'comment',
                                 'indent'
                             ])

        is_correct = pl.get_boolean_attrib(html_tags, 'correct',
                                           PL_ANSWER_CORRECT_DEFAULT)
        answer_indent = pl.get_integer_attrib(html_tags, 'indent', None)
        inner_html = pl.inner_html(html_tags)
        ranking = pl.get_integer_attrib(html_tags, 'ranking', -1)

        tag, depends = get_graph_info(html_tags)
        if grading_method == 'ranking':
            tag = str(index)

        if check_indentation is False and answer_indent is not None:
            raise Exception(
                '<pl-answer> should not specify indentation if indentation is disabled.'
            )

        answer_data_dict = {
            'inner_html': inner_html,
            'indent': answer_indent,
            'ranking': ranking,
            'index': index,
            'tag':
            tag,  # set by HTML with DAG grader, set internally for ranking grader
            'depends': depends,  # only used with DAG grader
            'group_info': group_info  # only used with DAG grader
        }
        if is_correct:
            correct_answers.append(answer_data_dict)
        else:
            incorrect_answers.append(answer_data_dict)

    index = 0
    for html_tags in element:  # iterate through the html tags inside pl-order-blocks
        if html_tags.tag is etree.Comment:
            continue
        elif html_tags.tag == 'pl-block-group':
            if grading_method != 'dag':
                raise Exception(
                    'Block groups only supported in the "dag" grading mode.')

            group_tag, group_depends = get_graph_info(html_tags)
            for grouped_tag in html_tags:
                if html_tags.tag is etree.Comment:
                    continue
                else:
                    prepare_tag(grouped_tag, index, {
                        'tag': group_tag,
                        'depends': group_depends
                    })
                    index += 1
        else:
            prepare_tag(html_tags, index)
            index += 1

    if grading_method != 'external' and len(correct_answers) == 0:
        raise Exception(
            'There are no correct answers specified for this question.')

    all_incorrect_answers = len(incorrect_answers)
    max_incorrect = pl.get_integer_attrib(element, 'max-incorrect',
                                          all_incorrect_answers)
    min_incorrect = pl.get_integer_attrib(element, 'min-incorrect',
                                          all_incorrect_answers)

    if min_incorrect > len(incorrect_answers) or max_incorrect > len(
            incorrect_answers):
        raise Exception(
            'The min-incorrect or max-incorrect attribute may not exceed the number of incorrect <pl-answers>.'
        )
    if min_incorrect > max_incorrect:
        raise Exception(
            'The attribute min-incorrect must be smaller than max-incorrect.')

    incorrect_answers_count = random.randint(min_incorrect, max_incorrect)

    sampled_correct_answers = correct_answers
    sampled_incorrect_answers = random.sample(incorrect_answers,
                                              incorrect_answers_count)

    mcq_options = sampled_correct_answers + sampled_incorrect_answers

    source_blocks_order = pl.get_string_attrib(element, 'source-blocks-order',
                                               SOURCE_BLOCKS_ORDER_DEFAULT)
    if source_blocks_order == 'random':
        random.shuffle(mcq_options)
    elif source_blocks_order == 'ordered':
        mcq_options.sort(key=lambda a: a['index'])
    else:
        raise Exception(
            'The specified option for the "source-blocks-order" attribute is invalid.'
        )

    for option in mcq_options:
        option['uuid'] = pl.get_uuid()

    data['params'][answer_name] = mcq_options
    data['correct_answers'][answer_name] = correct_answers

    # if the order of the blocks in the HTML is a correct solution, leave it unchanged, but if it
    # isn't we need to change it into a solution before displaying it as such
    data_copy = deepcopy(data)
    data_copy['submitted_answers'] = {answer_name: correct_answers}
    data_copy['partial_scores'] = {}
    grade(element_html, data_copy)
    if data_copy['partial_scores'][answer_name]['score'] != 1:
        data['correct_answers'][answer_name] = solve_problem(
            correct_answers, grading_method)
def prepare(element_html, data):
    element = lxml.html.fragment_fromstring(element_html)
    required_attribs = []
    optional_attribs = []
    pl.check_attribs(element, required_attribs, optional_attribs)
Exemplo n.º 27
0
def prepare(element_html, data):
    element = lxml.html.fragment_fromstring(element_html)
    required_attribs = ['answers-name']
    optional_attribs = ['weight', 'number-answers', 'fixed-order', 'inline']
    pl.check_attribs(element, required_attribs, optional_attribs)
    name = pl.get_string_attrib(element, 'answers-name')

    correct_answers = []
    incorrect_answers = []
    index = 0
    for child in element:
        if child.tag in ['pl-answer', 'pl_answer']:
            pl.check_attribs(child, required_attribs=[], optional_attribs=['correct'])
            correct = pl.get_boolean_attrib(child, 'correct', False)
            child_html = pl.inner_html(child)
            answer_tuple = (index, correct, child_html)
            if correct:
                correct_answers.append(answer_tuple)
            else:
                incorrect_answers.append(answer_tuple)
            index += 1

    len_correct = len(correct_answers)
    len_incorrect = len(incorrect_answers)
    len_total = len_correct + len_incorrect

    if len_correct < 1:
        raise Exception('pl-multiple-choice element must have at least one correct answer')

    number_answers = pl.get_integer_attrib(element, 'number-answers', len_total)

    number_answers = max(1, min(1 + len_incorrect, number_answers))
    number_correct = 1
    number_incorrect = number_answers - number_correct
    if not (0 <= number_incorrect <= len_incorrect):
        raise Exception('INTERNAL ERROR: number_incorrect: (%d, %d, %d)' % (number_incorrect, len_incorrect, number_answers))

    sampled_correct = random.sample(correct_answers, number_correct)
    sampled_incorrect = random.sample(incorrect_answers, number_incorrect)

    sampled_answers = sampled_correct + sampled_incorrect
    random.shuffle(sampled_answers)

    fixed_order = pl.get_boolean_attrib(element, 'fixed-order', False)
    if fixed_order:
        # we can't simply skip the shuffle because we already broke the original
        # order by separating into correct/incorrect lists
        sampled_answers.sort(key=lambda a: a[0])  # sort by stored original index

    display_answers = []
    correct_answer = None
    for (i, (index, correct, html)) in enumerate(sampled_answers):
        keyed_answer = {'key': chr(ord('a') + i), 'html': html}
        display_answers.append(keyed_answer)
        if correct:
            correct_answer = keyed_answer

    if name in data['params']:
        raise Exception('duplicate params variable name: %s' % name)
    if name in data['correct_answers']:
        raise Exception('duplicate correct_answers variable name: %s' % name)
    data['params'][name] = display_answers
    data['correct_answers'][name] = correct_answer
Exemplo n.º 28
0
def prepare(element_html, element_index, data):
    element = lxml.html.fragment_fromstring(element_html)
    pl.check_attribs(element,
                     required_attribs=['file_name'],
                     optional_attribs=['width', 'type', 'directory'])
    return data
Exemplo n.º 29
0
def render(element_html, data):
    element = lxml.html.fragment_fromstring(element_html)
    digits = pl.get_integer_attrib(element, 'digits', DIGITS_DEFAULT)

    matlab_data = ''
    python_data = 'import numpy as np\n\n'
    for child in element:
        if child.tag == 'variable':
            # Raise exception of variable does not have a name
            pl.check_attribs(child,
                             required_attribs=['params-name'],
                             optional_attribs=[])

            # Get name of variable
            var_name = pl.get_string_attrib(child, 'params-name')

            # Get value of variable, raising exception if variable does not exist
            var_data = data['params'].get(var_name, None)
            if var_data is None:
                raise Exception(
                    'No value in data["params"] for variable %s in pl-matrix-output element'
                    % var_name)

            # If the variable is in a format generated by pl.to_json, convert it
            # back to a standard type (otherwise, do nothing)
            var_data = pl.from_json(var_data)

            if np.isscalar(var_data):
                prefix = ''
                suffix = ''
            else:
                # Wrap the variable in an ndarray (if it's already one, this does nothing)
                var_data = np.array(var_data)
                # Check shape of variable
                if var_data.ndim != 2:
                    raise Exception(
                        'Value in data["params"] for variable %s in pl-matrix-output element must be a scalar or a 2D array'
                        % var_name)
                # Create prefix/suffix so python string is np.array( ... )
                prefix = 'np.array('
                suffix = ')'

            # Create string for matlab and python format
            matlab_data += pl.inner_html(
                child) + ' = ' + pl.string_from_2darray(
                    var_data, language='matlab', digits=digits) + ';\n'
            python_data += pl.inner_html(
                child) + ' = ' + prefix + pl.string_from_2darray(
                    var_data, language='python', digits=digits) + suffix + '\n'

    html_params = {
        'default_is_matlab': True,
        'matlab_data': matlab_data,
        'python_data': python_data,
        'uuid': pl.get_uuid()
    }

    with open('pl-matrix-output.mustache', 'r', encoding='utf-8') as f:
        html = chevron.render(f, html_params).strip()

    return html
Exemplo n.º 30
0
def parse(element_html, data):
    element = lxml.html.fragment_fromstring(element_html)
    answer_name = pl.get_string_attrib(element, 'answers-name')

    answer_raw_name = answer_name + '-input'
    student_answer = None

    if answer_raw_name in data['raw_submitted_answers']:
        student_answer = data['raw_submitted_answers'][answer_raw_name]

    student_answer = json.loads(student_answer)
    if student_answer is None or student_answer == []:
        data['format_errors'][answer_name] = 'No answer was submitted.'
        return

    grading_mode = pl.get_string_attrib(element, 'grading-method',
                                        GRADING_METHOD_DEFAULT)
    correct_answers = data['correct_answers'][answer_name]

    if grading_mode == 'ranking':
        for answer in student_answer:
            search = next((item for item in correct_answers
                           if item['inner_html'] == answer['inner_html']),
                          None)
            answer['ranking'] = search[
                'ranking'] if search is not None else -1  # wrong answers have no ranking
    elif grading_mode == 'dag':
        for answer in student_answer:
            search = next((item for item in correct_answers
                           if item['inner_html'] == answer['inner_html']),
                          None)
            answer['tag'] = search['tag'] if search is not None else None

    if pl.get_string_attrib(element, 'grading-method',
                            'ordered') == 'external':
        for html_tags in element:
            if html_tags.tag == 'pl-answer':
                pl.check_attribs(html_tags,
                                 required_attribs=[],
                                 optional_attribs=[])
        file_name = pl.get_string_attrib(element, 'file-name',
                                         FILE_NAME_DEFAULT)

        answer_code = ''
        for index, answer in enumerate(student_answer):
            indent = int(answer['indent'])
            answer_code += ('    ' * indent) + answer['inner_html'] + '\n'

        if len(answer_code) == 0:
            data['format_errors']['_files'] = 'The submitted file was empty.'
        else:
            data['submitted_answers']['_files'] = [{
                'name':
                file_name,
                'contents':
                base64.b64encode(answer_code.encode('utf-8')).decode('utf-8')
            }]

    data['submitted_answers'][answer_name] = student_answer
    if answer_raw_name in data['submitted_answers']:
        del data['submitted_answers'][answer_raw_name]
Exemplo n.º 31
0
def render(element_html, data):
    element = lxml.html.fragment_fromstring(element_html)
    digits = pl.get_integer_attrib(element, 'digits', DIGITS_DEFAULT)
    show_matlab = pl.get_boolean_attrib(element, 'show-matlab',
                                        SHOW_MATLAB_DEFAULT)
    show_mathematica = pl.get_boolean_attrib(element, 'show-mathematica',
                                             SHOW_MATHEMATICA_DEFAULT)
    show_python = pl.get_boolean_attrib(element, 'show-python',
                                        SHOW_PYTHON_DEFAULT)
    default_tab = pl.get_string_attrib(element, 'default-tab',
                                       DEFAULT_TAB_DEFAULT)

    tab_list = ['matlab', 'mathematica', 'python']
    if default_tab not in tab_list:
        raise Exception(f'invalid default-tab: {default_tab}')

    # Setting the default tab
    displayed_tab = [show_matlab, show_mathematica, show_python]
    if not any(displayed_tab):
        raise Exception(
            'All tabs have been hidden from display. At least one tab must be shown.'
        )

    default_tab_index = tab_list.index(default_tab)
    # If not displayed, make first visible tab the default
    if not displayed_tab[default_tab_index]:
        first_display = displayed_tab.index(True)
        default_tab = tab_list[first_display]
    default_tab_index = tab_list.index(default_tab)

    # Active tab should be the default tab
    default_tab_list = [False, False, False]
    default_tab_list[default_tab_index] = True
    [active_tab_matlab, active_tab_mathematica,
     active_tab_python] = default_tab_list

    # Process parameter data
    matlab_data = ''
    mathematica_data = ''
    python_data = 'import numpy as np\n\n'
    for child in element:
        if child.tag == 'variable':
            # Raise exception if variable does not have a name
            pl.check_attribs(child,
                             required_attribs=['params-name'],
                             optional_attribs=['comment', 'digits'])

            # Get name of variable
            var_name = pl.get_string_attrib(child, 'params-name')

            # Get value of variable, raising exception if variable does not exist
            var_data = data['params'].get(var_name, None)
            if var_data is None:
                raise Exception(
                    'No value in data["params"] for variable %s in pl-variable-output element'
                    % var_name)

            # If the variable is in a format generated by pl.to_json, convert it
            # back to a standard type (otherwise, do nothing)
            var_data = pl.from_json(var_data)

            # Get comment, if it exists
            var_matlab_comment = ''
            var_mathematica_comment = ''
            var_python_comment = ''
            if pl.has_attrib(child, 'comment'):
                var_comment = pl.get_string_attrib(child, 'comment')
                var_matlab_comment = f' % {var_comment}'
                var_mathematica_comment = f' (* {var_comment} *)'
                var_python_comment = f' # {var_comment}'

            # Get digit for child, if it exists
            if not pl.has_attrib(child, 'digits'):
                var_digits = digits
            else:
                var_digits = pl.get_string_attrib(child, 'digits')

            # Assembling Python array formatting
            if np.isscalar(var_data):
                prefix = ''
                suffix = ''
            else:
                # Wrap the variable in an ndarray (if it's already one, this does nothing)
                var_data = np.array(var_data)
                # Check shape of variable
                if var_data.ndim > 2:
                    raise Exception(
                        'Value in data["params"] for variable %s in pl-variable-output element must be a scalar, a vector, or a 2D array'
                        % var_name)
                # Create prefix/suffix so python string is np.array( ... )
                prefix = 'np.array('
                suffix = ')'

            # Mathematica reserved letters: C D E I K N O
            mathematica_reserved = ['C', 'D', 'E', 'I', 'K', 'N', 'O']
            if pl.inner_html(child) in mathematica_reserved:
                mathematica_suffix = 'm'
            else:
                mathematica_suffix = ''

            # Create string for matlab and python format
            var_name_disp = pl.inner_html(child)
            var_matlab_data = pl.string_from_numpy(var_data,
                                                   language='matlab',
                                                   digits=var_digits)
            var_mathematica = pl.string_from_numpy(var_data,
                                                   language='mathematica',
                                                   digits=var_digits)
            var_python_data = pl.string_from_numpy(var_data,
                                                   language='python',
                                                   digits=var_digits)

            matlab_data += f'{var_name_disp} = {var_matlab_data};{var_matlab_comment}\n'
            mathematica_data += f'{var_name_disp}{mathematica_suffix} = {var_mathematica};{var_mathematica_comment}\n'
            python_data += f'{var_name_disp} = {prefix}{var_python_data}{suffix}{var_python_comment}\n'

    html_params = {
        'active_tab_matlab': active_tab_matlab,
        'active_tab_mathematica': active_tab_mathematica,
        'active_tab_python': active_tab_python,
        'show_matlab': show_matlab,
        'show_mathematica': show_mathematica,
        'show_python': show_python,
        'matlab_data': matlab_data,
        'mathematica_data': mathematica_data,
        'python_data': python_data,
        'uuid': pl.get_uuid()
    }

    with open('pl-variable-output.mustache', 'r', encoding='utf-8') as f:
        html = chevron.render(f, html_params).strip()

    return html
Exemplo n.º 32
0
def prepare(element_html, data):
    element = lxml.html.fragment_fromstring(element_html)
    required_attribs = ['answers-name']
    optional_attribs = ['weight', 'number-answers', 'fixed-order', 'inline', 'hide-letter-keys',
                        'none-of-the-above', 'none-of-the-above-feedback', 'all-of-the-above', 'all-of-the-above-feedback',
                        'external-json', 'external-json-correct-key', 'external-json-incorrect-key']
    pl.check_attribs(element, required_attribs, optional_attribs)
    name = pl.get_string_attrib(element, 'answers-name')

    correct_answers, incorrect_answers = categorize_options(element, data)

    len_correct = len(correct_answers)
    len_incorrect = len(incorrect_answers)
    len_total = len_correct + len_incorrect

    enable_nota = pl.get_boolean_attrib(element, 'none-of-the-above', NONE_OF_THE_ABOVE_DEFAULT)
    enable_aota = pl.get_boolean_attrib(element, 'all-of-the-above', ALL_OF_THE_ABOVE_DEFAULT)

    nota_correct = False
    aota_correct = False
    if enable_nota or enable_aota:
        prob_space = len_correct + enable_nota + enable_aota
        rand_int = random.randint(1, prob_space)
        # Either 'None of the above' or 'All of the above' is correct
        # with probability 1/(number_correct + enable-nota + enable-aota).
        # However, if len_correct is 0, nota_correct is guaranteed to be True.
        # Thus, if no correct option is provided, 'None of the above' will always
        # be correct, and 'All of the above' always incorrect
        nota_correct = enable_nota and (rand_int == 1 or len_correct == 0)
        # 'All of the above' will always be correct when no incorrect option is
        # provided, while still never both True
        aota_correct = enable_aota and (rand_int == 2 or len_incorrect == 0) and not nota_correct

    if len_correct < 1 and not enable_nota:
        # This means the code needs to handle the special case when len_correct == 0
        raise Exception('pl-multiple-choice element must have at least 1 correct answer or set none-of-the-above')

    if enable_aota and len_correct < 2:
        # To prevent confusion on the client side
        raise Exception('pl-multiple-choice element must have at least 2 correct answers when all-of-the-above is set')

    # 1. Pick the choice(s) to display
    number_answers = pl.get_integer_attrib(element, 'number-answers', None)
    # determine if user provides number-answers
    set_num_answers = True
    if number_answers is None:
        set_num_answers = False
        number_answers = len_total + enable_nota + enable_aota
    # figure out how many choice(s) to choose from the *provided* choices,
    # excluding 'none-of-the-above' and 'all-of-the-above'
    number_answers -= (enable_nota + enable_aota)

    expected_num_answers = number_answers

    if enable_aota:
        # min number if 'All of the above' is correct
        number_answers = min(len_correct, number_answers)
        # raise exception when the *provided* number-answers can't be satisfied
        if set_num_answers and number_answers < expected_num_answers:
            raise Exception(f'Not enough correct choices for all-of-the-above. Need {expected_num_answers - number_answers} more')
    if enable_nota:
        # if nota correct
        number_answers = min(len_incorrect, number_answers)
        # raise exception when the *provided* number-answers can't be satisfied
        if set_num_answers and number_answers < expected_num_answers:
            raise Exception(f'Not enough incorrect choices for none-of-the-above. Need {expected_num_answers - number_answers} more')
    # this is the case for
    # - 'All of the above' is incorrect
    # - 'None of the above' is incorrect
    # - nota and aota disabled
    number_answers = min(min(1, len_correct) + len_incorrect, number_answers)

    if aota_correct:
        # when 'All of the above' is correct, we choose all from correct
        # and none from incorrect
        number_correct = number_answers
        number_incorrect = 0
    elif nota_correct:
        # when 'None of the above' is correct, we choose all from incorrect
        # and none from correct
        number_correct = 0
        number_incorrect = number_answers
    else:
        # PROOF: by the above probability, if len_correct == 0, then nota_correct
        # conversely; if not nota_correct, then len_correct != 0. Since len_correct
        # is none negative, this means len_correct >= 1.
        number_correct = 1
        number_incorrect = max(0, number_answers - number_correct)

    if not (0 <= number_incorrect <= len_incorrect):
        raise Exception('INTERNAL ERROR: number_incorrect: (%d, %d, %d)' % (number_incorrect, len_incorrect, number_answers))

    # 2. Sample correct and incorrect choices
    sampled_correct = random.sample(correct_answers, number_correct)
    sampled_incorrect = random.sample(incorrect_answers, number_incorrect)

    sampled_answers = sampled_correct + sampled_incorrect
    random.shuffle(sampled_answers)

    # 3. Modify sampled choices
    fixed_order = pl.get_boolean_attrib(element, 'fixed-order', FIXED_ORDER_DEFAULT)
    if fixed_order:
        # we can't simply skip the shuffle because we already broke the original
        # order by separating into correct/incorrect lists
        sampled_answers.sort(key=lambda a: a[0])  # sort by stored original index

    inline = pl.get_boolean_attrib(element, 'inline', INLINE_DEFAULT)
    if enable_aota:
        if inline:
            aota_text = 'All of these'
        else:
            aota_text = 'All of the above'
        # Add 'All of the above' option after shuffling
        aota_feedback = pl.get_string_attrib(element, 'all-of-the-above-feedback', FEEDBACK_DEFAULT)
        sampled_answers.append((len_total, aota_correct, aota_text, aota_feedback))

    if enable_nota:
        if inline:
            nota_text = 'None of these'
        else:
            nota_text = 'None of the above'
        # Add 'None of the above' option after shuffling
        nota_feedback = pl.get_string_attrib(element, 'none-of-the-above-feedback', FEEDBACK_DEFAULT)
        sampled_answers.append((len_total + 1, nota_correct, nota_text, nota_feedback))

    # 4. Write to data
    # Because 'All of the above' is below all the correct choice(s) when it's
    # true, the variable correct_answer will save it as correct, and
    # overwriting previous choice(s)
    display_answers = []
    correct_answer = None
    for (i, (index, correct, html, feedback)) in enumerate(sampled_answers):
        keyed_answer = {'key': pl.index2key(i), 'html': html, 'feedback': feedback}
        display_answers.append(keyed_answer)
        if correct:
            correct_answer = keyed_answer

    if name in data['params']:
        raise Exception('duplicate params variable name: %s' % name)
    if name in data['correct_answers']:
        raise Exception('duplicate correct_answers variable name: %s' % name)
    data['params'][name] = display_answers
    data['correct_answers'][name] = correct_answer
def prepare(element_html, data):
    element = lxml.html.fragment_fromstring(element_html)
    pl.check_attribs(element, [], [])
Exemplo n.º 34
0
def prepare(element_html, data):
    element = lxml.html.fragment_fromstring(element_html)

    pl.check_attribs(element,
                     required_attribs=['answers-name'],
                     optional_attribs=[
                         'source-blocks-order', 'grading-method',
                         'indentation', 'source-header', 'solution-header',
                         'file-name', 'solution-placement', 'max-incorrect',
                         'min-incorrect', 'weight'
                     ])

    answer_name = pl.get_string_attrib(element, 'answers-name')

    mcq_options = []
    html_ordering = []
    correct_answers = []
    correct_answers_indent = []
    correct_answers_ranking = []
    incorrect_answers = []

    check_indentation = pl.get_boolean_attrib(element, 'indentation',
                                              INDENTION_DEFAULT)
    grading_method = pl.get_string_attrib(element, 'grading-method',
                                          GRADING_METHOD_DEFAULT)
    accepted_grading_method = ['ordered', 'unordered', 'ranking', 'external']
    if grading_method not in accepted_grading_method:
        raise Exception(
            'The grading-method attribute must be one of the following: ' +
            accepted_grading_method)

    for html_tags in element:  # iterate through the tags inside pl-order-blocks, should be <pl-answer> tags
        if html_tags.tag == 'pl-answer':
            if grading_method == 'external':
                pl.check_attribs(html_tags,
                                 required_attribs=[],
                                 optional_attribs=['correct'])
            else:
                pl.check_attribs(
                    html_tags,
                    required_attribs=[],
                    optional_attribs=['correct', 'ranking', 'indent'])

            is_correct = pl.get_boolean_attrib(html_tags, 'correct',
                                               PL_ANSWER_CORRECT_DEFAULT)
            if check_indentation is False:
                try:
                    answer_indent = pl.get_string_attrib(html_tags, 'indent')
                except Exception:
                    answer_indent = -1
                else:
                    raise Exception(
                        '<pl-answer> should not specify indentation if indentation is disabled.'
                    )
            else:
                answer_indent = pl.get_integer_attrib(
                    html_tags, 'indent', PL_ANSWER_INDENT_DEFAULT
                )  # get answer indent, and default to -1 (indent level ignored)
            if is_correct is True:
                # add option to the correct answer array, along with the correct required indent
                if pl.get_string_attrib(html_tags, 'ranking', '') != '':
                    ranking = pl.get_string_attrib(html_tags, 'ranking')
                    try:
                        ranking = int(ranking) - 1
                    except ValueError:
                        raise Exception(
                            'Ranking specified in <pl-answer> is not a number.'
                        )
                    correct_answers_ranking.append(ranking)
                correct_answers.append(html_tags.text)
                correct_answers_indent.append(answer_indent)
            else:
                incorrect_answers.append(html_tags.text)
            html_ordering.append(html_tags.text)
        else:
            raise Exception(
                'Tags nested inside <pl-order-blocks> must be <pl-answer>.')

    if pl.get_string_attrib(element, 'grading-method', GRADING_METHOD_DEFAULT
                            ) != 'external' and len(correct_answers) == 0:
        raise Exception(
            'There are no correct answers specified for this question.')

    if (correct_answers_ranking != sorted(correct_answers_ranking)):
        # sort correct answers by indices specified in corect_answers_ranking
        correct_answers = [
            x for _, x in sorted(zip(correct_answers_ranking, correct_answers))
        ]

    min_incorrect = pl.get_integer_attrib(element, 'min-incorrect',
                                          MIN_INCORRECT_DEFAULT)
    max_incorrect = pl.get_integer_attrib(element, 'max-incorrect',
                                          MAX_INCORRECT_DEFAULT)

    if ((min_incorrect is None) & (max_incorrect is None)):
        mcq_options = correct_answers + incorrect_answers
    else:
        # Setting default for min-correct and checking for correct interval
        if min_incorrect is None:
            min_incorrect = 1
        else:
            if min_incorrect > len(incorrect_answers):
                raise Exception(
                    'min-incorrect must be less than or equal to the number of incorrect <pl-answers>.'
                )
        # Setting default for max-correct and checking for correct interval
        if max_incorrect is None:
            max_incorrect = len(incorrect_answers)
        else:
            if max_incorrect > len(incorrect_answers):
                raise Exception(
                    'max-incorrect must be less than or equal to the number of incorrect <pl-answers>.'
                )
        if min_incorrect > max_incorrect:
            raise Exception(
                'min-incorrect must be smaller than max-incorrect.')
        incorrect_answers_count = random.randint(min_incorrect, max_incorrect)
        mcq_options = correct_answers + random.sample(incorrect_answers,
                                                      incorrect_answers_count)

    source_blocks_order = pl.get_string_attrib(element, 'source-blocks-order',
                                               SOURCE_BLOCKS_ORDER_DEFAULT)

    if source_blocks_order == 'random':
        random.shuffle(mcq_options)
    elif source_blocks_order == 'ordered':
        mcq_options = html_ordering
    else:
        raise Exception(
            'the selected option for "source-blocks-order" does not exist.')

    data['params'][answer_name] = mcq_options
    data['correct_answers'][answer_name] = {
        'correct_answers': correct_answers,
        'correct_answers_indent': correct_answers_indent
    }
Exemplo n.º 35
0
def prepare(element_html, data):
    element = lxml.html.fragment_fromstring(element_html)

    required_attribs = ['answers-name']
    optional_attribs = ['weight', 'number-answers', 'min-correct', 'max-correct', 'fixed-order', 'inline', 'hide-answer-panel', 'hide-help-text', 'detailed-help-text', 'partial-credit', 'partial-credit-method', 'hide-letter-keys', 'hide-score-badge']

    pl.check_attribs(element, required_attribs, optional_attribs)
    name = pl.get_string_attrib(element, 'answers-name')

    partial_credit = pl.get_boolean_attrib(element, 'partial-credit', PARTIAL_CREDIT_DEFAULT)
    partial_credit_method = pl.get_string_attrib(element, 'partial-credit-method', None)
    if not partial_credit and partial_credit_method is not None:
        raise Exception('Cannot specify partial-credit-method if partial-credit is not enabled')

    correct_answers = []
    incorrect_answers = []
    index = 0
    for child in element:
        if child.tag in ['pl-answer', 'pl_answer']:
            pl.check_attribs(child, required_attribs=[], optional_attribs=['correct'])
            correct = pl.get_boolean_attrib(child, 'correct', False)
            child_html = pl.inner_html(child)
            answer_tuple = (index, correct, child_html)
            if correct:
                correct_answers.append(answer_tuple)
            else:
                incorrect_answers.append(answer_tuple)
            index += 1

    len_correct = len(correct_answers)
    len_incorrect = len(incorrect_answers)
    len_total = len_correct + len_incorrect

    if len_correct == 0:
        raise ValueError('At least one option must be true.')

    number_answers = pl.get_integer_attrib(element, 'number-answers', len_total)
    min_correct = pl.get_integer_attrib(element, 'min-correct', 1)
    max_correct = pl.get_integer_attrib(element, 'max-correct', len(correct_answers))

    if min_correct < 1:
        raise ValueError('The attribute min-correct is {:d} but must be at least 1'.format(min_correct))

    # FIXME: why enforce a maximum number of options?
    max_answers = 26  # will not display more than 26 checkbox answers

    number_answers = max(0, min(len_total, min(max_answers, number_answers)))
    min_correct = min(len_correct, min(number_answers, max(0, max(number_answers - len_incorrect, min_correct))))
    max_correct = min(len_correct, min(number_answers, max(min_correct, max_correct)))
    if not (0 <= min_correct <= max_correct <= len_correct):
        raise ValueError('INTERNAL ERROR: correct number: (%d, %d, %d, %d)' % (min_correct, max_correct, len_correct, len_incorrect))
    min_incorrect = number_answers - max_correct
    max_incorrect = number_answers - min_correct
    if not (0 <= min_incorrect <= max_incorrect <= len_incorrect):
        raise ValueError('INTERNAL ERROR: incorrect number: (%d, %d, %d, %d)' % (min_incorrect, max_incorrect, len_incorrect, len_correct))

    number_correct = random.randint(min_correct, max_correct)
    number_incorrect = number_answers - number_correct

    sampled_correct = random.sample(correct_answers, number_correct)
    sampled_incorrect = random.sample(incorrect_answers, number_incorrect)

    sampled_answers = sampled_correct + sampled_incorrect
    random.shuffle(sampled_answers)

    fixed_order = pl.get_boolean_attrib(element, 'fixed-order', FIXED_ORDER_DEFAULT)
    if fixed_order:
        # we can't simply skip the shuffle because we already broke the original
        # order by separating into correct/incorrect lists
        sampled_answers.sort(key=lambda a: a[0])  # sort by stored original index

    display_answers = []
    correct_answer_list = []
    for (i, (index, correct, html)) in enumerate(sampled_answers):
        keyed_answer = {'key': pl.index2key(i), 'html': html}
        display_answers.append(keyed_answer)
        if correct:
            correct_answer_list.append(keyed_answer)

    if name in data['params']:
        raise Exception('duplicate params variable name: %s' % name)
    if name in data['correct_answers']:
        raise Exception('duplicate correct_answers variable name: %s' % name)
    data['params'][name] = display_answers
    data['correct_answers'][name] = correct_answer_list
Exemplo n.º 36
0
def prepare(element_html, data):
    element = lxml.html.fragment_fromstring(element_html)
    required_attribs = ['script-name']
    optional_attribs = ['param-names', 'width', 'height']
    pl.check_attribs(element, required_attribs, optional_attribs)
    return data
Exemplo n.º 37
0
def prepare(element_html, data):
    element = lxml.html.fragment_fromstring(element_html)
    pl.check_attribs(element, required_attribs=['file-name'], optional_attribs=['width', 'type', 'directory'])
def prepare(element_html, element_index, data):
    element = lxml.html.fragment_fromstring(element_html)
    pl.check_attribs(element, required_attribs=[], optional_attribs=['digits'])
Exemplo n.º 39
0
def prepare(element_html, data):
    element = lxml.html.fragment_fromstring(element_html)
    required_attribs = []
    optional_attribs = ['digits', 'default-tab', 'show-matlab', 'show-mathematica', 'show-python']
    pl.check_attribs(element, required_attribs, optional_attribs)
Exemplo n.º 40
0
def get_objects(element, data):
    obj_list = []

    for child in element:
        is_stl = (child.tag in ['pl-threejs-stl', 'pl_threejs_stl'])
        is_txt = (child.tag in ['pl-threejs-txt', 'pl_threejs_txt'])
        if not (is_stl or is_txt):
            continue

        # Type-specific check and get (stl)
        if is_stl:
            # Attributes
            pl.check_attribs(child,
                             required_attribs=['file-name'],
                             optional_attribs=[
                                 'file-directory', 'frame', 'color',
                                 'position', 'orientation', 'format', 'scale',
                                 'opacity'
                             ])
            # - file-name (and file-directory)
            file_url = get_file_url(child, data)
            # - type
            object_type = 'stl'
            # - object
            specific = {'type': object_type, 'file_url': file_url}

        # Type-specific check and get (txt)
        if is_txt:
            # Attributes
            pl.check_attribs(child,
                             required_attribs=[],
                             optional_attribs=[
                                 'frame', 'color', 'position', 'orientation',
                                 'format', 'scale', 'opacity'
                             ])
            # - text
            text = pl.inner_html(child)
            # - type
            object_type = 'txt'
            # - object
            specific = {'type': object_type, 'text': text}

        # Common
        # - frame
        frame = pl.get_string_attrib(child, 'frame', 'body')
        if frame not in ['body', 'space']:
            raise Exception(
                '"frame" must be either "body" or "space": {:s}'.format(frame))
        if frame == 'body':
            default_color = '#e84a27'
            default_opacity = 0.7
        else:
            default_color = '#13294b'
            default_opacity = 0.4
        # - color
        color = pl.get_color_attrib(child, 'color', default_color)
        # - opacity
        opacity = pl.get_float_attrib(child, 'opacity', default_opacity)
        # - position
        p = pl.get_string_attrib(child, 'position', '[0, 0, 0]')
        try:
            position = np.array(json.loads(p), dtype=np.float64)
            if position.shape == (3, ):
                position = position.tolist()
            else:
                raise ValueError()
        except Exception:
            raise Exception(
                'attribute "position" must have format [x, y, z]: {:s}'.format(
                    p))
        # - orientation (and format)
        orientation = get_orientation(child, 'orientation', 'format')
        # - scale
        scale = pl.get_float_attrib(child, 'scale', 1.0)

        common = {
            'frame': frame,
            'color': color,
            'opacity': opacity,
            'position': position,
            'quaternion': orientation,
            'scale': scale
        }

        obj = {**specific, **common}
        obj_list.append(obj)

    return obj_list
Exemplo n.º 41
0
def get_objects(element, data):
    obj_list = []

    for child in element:
        is_stl = (child.tag in ['pl-threejs-stl', 'pl_threejs_stl'])
        is_txt = (child.tag in ['pl-threejs-txt', 'pl_threejs_txt'])
        if not (is_stl or is_txt):
            continue

        # Type-specific check and get (stl)
        if is_stl:
            # Attributes
            pl.check_attribs(child, required_attribs=['file-name'], optional_attribs=['file-directory', 'frame', 'color', 'position', 'orientation', 'format', 'scale', 'opacity'])
            # - file-name (and file-directory)
            file_url = get_file_url(child, data)
            # - type
            object_type = 'stl'
            # - object
            specific = {
                'type': object_type,
                'file_url': file_url
            }

        # Type-specific check and get (txt)
        if is_txt:
            # Attributes
            pl.check_attribs(child, required_attribs=[], optional_attribs=['frame', 'color', 'position', 'orientation', 'format', 'scale', 'opacity'])
            # - text
            text = pl.inner_html(child)
            # - type
            object_type = 'txt'
            # - object
            specific = {
                'type': object_type,
                'text': text
            }

        # Common
        # - frame
        frame = pl.get_string_attrib(child, 'frame', 'body')
        if frame not in ['body', 'space']:
            raise Exception('"frame" must be either "body" or "space": {:s}'.format(frame))
        if frame == 'body':
            default_color = '#e84a27'
            default_opacity = 0.7
        else:
            default_color = '#13294b'
            default_opacity = 0.4
        # - color
        color = pl.get_color_attrib(child, 'color', default_color)
        # - opacity
        opacity = pl.get_float_attrib(child, 'opacity', default_opacity)
        # - position
        p = pl.get_string_attrib(child, 'position', '[0, 0, 0]')
        try:
            position = np.array(json.loads(p), dtype=np.float64)
            if position.shape == (3,):
                position = position.tolist()
            else:
                raise ValueError()
        except Exception:
            raise Exception('attribute "position" must have format [x, y, z]: {:s}'.format(p))
        # - orientation (and format)
        orientation = get_orientation(child, 'orientation', 'format')
        # - scale
        scale = pl.get_float_attrib(child, 'scale', 1.0)

        common = {
            'frame': frame,
            'color': color,
            'opacity': opacity,
            'position': position,
            'quaternion': orientation,
            'scale': scale
        }

        obj = {**specific, **common}
        obj_list.append(obj)

    return obj_list
Exemplo n.º 42
0
def prepare(element_html, data):
    element = lxml.html.fragment_fromstring(element_html)
    answer_name = pl.get_string_attrib(element, 'answers-name')

    required_attribs = ['answers-name']
    optional_attribs = [
        'source-blocks-order', 'grading-method', 'indentation',
        'source-header', 'solution-header', 'file-name', 'solution-placement',
        'max-incorrect', 'min-incorrect', 'weight', 'inline', 'max-indent',
        'feedback'
    ]

    pl.check_attribs(element,
                     required_attribs=required_attribs,
                     optional_attribs=optional_attribs)

    check_indentation = pl.get_boolean_attrib(element, 'indentation',
                                              INDENTION_DEFAULT)
    grading_method = pl.get_string_attrib(element, 'grading-method',
                                          GRADING_METHOD_DEFAULT)
    feedback_type = pl.get_string_attrib(element, 'feedback', FEEDBACK_DEFAULT)

    accepted_grading_method = [
        'ordered', 'unordered', 'ranking', 'dag', 'external'
    ]
    if grading_method not in accepted_grading_method:
        raise Exception(
            'The grading-method attribute must be one of the following: ' +
            accepted_grading_method)

    if (grading_method != 'dag' and feedback_type != 'none') or \
       (grading_method == 'dag' and feedback_type not in ['none', 'first-wrong']):
        raise Exception('feedback type "' + feedback_type +
                        '" is not available with the "' + grading_method +
                        '" grading-method.')

    correct_answers = []
    incorrect_answers = []

    def prepare_tag(html_tags, index, group=None):
        if html_tags.tag != 'pl-answer':
            raise Exception(
                'Any html tags nested inside <pl-order-blocks> must be <pl-answer> or <pl-block-group>. \
                Any html tags nested inside <pl-block-group> must be <pl-answer>'
            )

        if grading_method == 'external':
            pl.check_attribs(html_tags,
                             required_attribs=[],
                             optional_attribs=['correct'])
        elif grading_method == 'unordered':
            pl.check_attribs(html_tags,
                             required_attribs=[],
                             optional_attribs=['correct', 'indent'])
        elif grading_method in ['ranking', 'ordered']:
            pl.check_attribs(html_tags,
                             required_attribs=[],
                             optional_attribs=['correct', 'ranking', 'indent'])
        elif grading_method == 'dag':
            pl.check_attribs(html_tags,
                             required_attribs=[],
                             optional_attribs=['correct', 'tag', 'depends'])

        is_correct = pl.get_boolean_attrib(html_tags, 'correct',
                                           PL_ANSWER_CORRECT_DEFAULT)
        answer_indent = pl.get_integer_attrib(html_tags, 'indent', None)
        inner_html = pl.inner_html(html_tags)
        ranking = pl.get_integer_attrib(html_tags, 'ranking', -1)

        tag = pl.get_string_attrib(html_tags, 'tag', None)
        depends = pl.get_string_attrib(html_tags, 'depends', '')
        depends = depends.strip().split(',') if depends else []

        if check_indentation is False and answer_indent is not None:
            raise Exception(
                '<pl-answer> should not specify indentation if indentation is disabled.'
            )

        answer_data_dict = {
            'inner_html': inner_html,
            'indent': answer_indent,
            'ranking': ranking,
            'index': index,
            'tag': tag,  # only used with DAG grader
            'depends': depends,  # only used with DAG grader
            'group': group  # only used with DAG grader
        }
        if is_correct:
            correct_answers.append(answer_data_dict)
        else:
            incorrect_answers.append(answer_data_dict)

    index = 0
    group_counter = 0
    for html_tags in element:  # iterate through the html tags inside pl-order-blocks
        if html_tags.tag is lxml.etree.Comment:
            continue
        elif html_tags.tag == 'pl-block-group':
            if grading_method != 'dag':
                raise Exception(
                    'Block groups only supported in the "dag" grading mode.')
            group_counter += 1
            for grouped_tag in html_tags:
                if html_tags.tag is lxml.etree.Comment:
                    continue
                else:
                    prepare_tag(grouped_tag, index, group_counter)
                    index += 1
        else:
            prepare_tag(html_tags, index)
            index += 1

    if pl.get_string_attrib(element, 'grading-method', GRADING_METHOD_DEFAULT
                            ) != 'external' and len(correct_answers) == 0:
        raise Exception(
            'There are no correct answers specified for this question.')

    all_incorrect_answers = len(incorrect_answers)
    max_incorrect = pl.get_integer_attrib(element, 'max-incorrect',
                                          all_incorrect_answers)
    min_incorrect = pl.get_integer_attrib(element, 'min-incorrect',
                                          all_incorrect_answers)

    if min_incorrect > len(incorrect_answers) or max_incorrect > len(
            incorrect_answers):
        raise Exception(
            'The min-incorrect or max-incorrect attribute may not exceed the number of incorrect <pl-answers>.'
        )
    if min_incorrect > max_incorrect:
        raise Exception(
            'The attribute min-incorrect must be smaller than max-incorrect.')

    incorrect_answers_count = random.randint(min_incorrect, max_incorrect)

    sampled_correct_answers = correct_answers
    sampled_incorrect_answers = random.sample(incorrect_answers,
                                              incorrect_answers_count)

    mcq_options = sampled_correct_answers + sampled_incorrect_answers

    source_blocks_order = pl.get_string_attrib(element, 'source-blocks-order',
                                               SOURCE_BLOCKS_ORDER_DEFAULT)
    if source_blocks_order == 'random':
        random.shuffle(mcq_options)
    elif source_blocks_order == 'ordered':
        mcq_options.sort(key=lambda a: a['index'])
    else:
        raise Exception(
            'The specified option for the "source-blocks-order" attribute is invalid.'
        )

    # data['params'][answer_name] = filter_keys_from_array(mcq_options, 'inner_html')
    for option in mcq_options:
        option['uuid'] = pl.get_uuid()

    data['params'][answer_name] = mcq_options
    data['correct_answers'][answer_name] = correct_answers
Exemplo n.º 43
0
def prepare(element_html, data):
    element = lxml.html.fragment_fromstring(element_html)
    required_attribs = ['script-name']
    optional_attribs = ['param-names', 'width', 'height']
    pl.check_attribs(element, required_attribs, optional_attribs)
    return data