canvas-lms/gems/plugins/qti_exporter/lib/qti/calculated_interaction.rb

143 lines
5.4 KiB
Ruby

#
# Copyright (C) 2011 - present Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
require 'cgi'
module Qti
class CalculatedInteraction < AssessmentItemConverter
def initialize(opts)
super(opts)
@question[:answers] = []
@question[:variables] = []
@question[:question_type] = 'calculated_question'
end
def parse_question_data
imported_formula = @doc.at_css('calculated formula')
@question[:imported_formula] = CGI.unescape(imported_formula.text) if imported_formula
get_calculated_property('answer_tolerance')
if @question[:answer_tolerance] && !@question[:answer_tolerance].to_s.match(/[^\d\.]/)
@question[:answer_tolerance] = @question[:answer_tolerance].to_f
end
get_calculated_property('unit_points_percent')
@question[:unit_points_percent] = @question[:unit_points_percent].to_f if @question[:unit_points_percent]
get_calculated_property('unit_value')
get_calculated_property('unit_required', true)
get_calculated_property('unit_case_sensitive', true)
get_calculated_property('partial_credit_points_percent')
@question[:partial_credit_points_percent] = @question[:partial_credit_points_percent].to_f if @question[:partial_credit_points_percent]
get_calculated_property('partial_credit_tolerance')
@question[:partial_credit_tolerance] = @question[:partial_credit_tolerance].to_f if @question[:partial_credit_tolerance]
get_variables()
get_answer_sets()
get_feedback()
get_formulas()
if !@question[:answer_tolerance] && tolerance = get_node_att(@doc, 'instructureMetadata instructureField[name=formula_tolerance]', 'value')
@question[:answer_tolerance] = tolerance
end
if !@question[:formula_decimal_places] && precision = get_node_att(@doc, 'instructureMetadata instructureField[name=formula_precision]', 'value')
@question[:formula_decimal_places] = precision.to_i
end
apply_d2l_fixes if @flavor == Qti::Flavors::D2L
if @question[:formulas]&.empty? && @question[:imported_formula]
@question[:formulas] << {:formula => @question[:imported_formula]}
end
@question
end
def get_calculated_property(prop_name, is_true_false=false)
@question[:"#{prop_name}"] = @doc.at_css("calculated #{prop_name}").text if @doc.at_css("calculated #{prop_name}")
if is_true_false and @question[:"#{prop_name}"]
@question[:"#{prop_name}"] = @question[:"#{prop_name}"] == 'true' ? true : false
end
end
def get_variables
@doc.css('calculated vars var').each do |v|
var = {}
@question[:variables] << var
var[:name] = v['name']
var[:scale] = v['scale'].to_i
var[:min] = v.at_css('min').text.to_f if v.at_css('min')
var[:max] = v.at_css('max').text.to_f if v.at_css('max')
end
end
def get_answer_sets
@doc.css('calculated var_sets var_set').each do |vs|
set = {:variables=>[], :weight=>100}
set[:id] = vs['ident'].presence || unique_local_id
@question[:answers] << set
set[:answer] = vs.at_css('answer').text.to_f if vs.at_css('answer')
vs.css('var').each do |v|
var = {}
set[:variables] << var
var[:name] = v['name']
var[:value] = v.text.to_f
end
end
end
def get_formulas
@question[:formulas] = []
if formulas_node = @doc.at_css('formulas')
@question[:formula_decimal_places] = formulas_node['decimal_places'].to_i
formulas_node.css('formula').each do |f_node|
formula = {}
formula[:formula] = f_node.text
@question[:formulas] << formula
end
end
@question[:formulas]
end
def apply_d2l_fixes
@question[:variables].each do |v|
v_name = v[:name]
# substitute {var} for [var]
@question[:question_text].gsub!("{#{v_name}}", "[#{v_name}]") if @question[:question_text]
# substitute {var} for var
@question[:imported_formula].gsub!("{#{v_name}}", "#{v_name}") if @question[:imported_formula]
end
if @question[:imported_formula]
method_substitutions = {"sqr" => "sqrt", "Factorial" => "fact", "exp" => "e"}
method_substitutions.each do |orig_method, new_method|
@question[:imported_formula].gsub!("#{orig_method}(", "#{new_method}(")
end
end
if @question[:variables].count == 1
# is this secretly a simple numeric question in disguise
var = @question[:variables].first
if (var[:min] == var[:max]) && (@question[:imported_formula] == var[:name]) # yup the formula for the answer is "x" and there's only one possible value
[:variables, :formulas, :imported_formula, :formula_decimal_places, :answer_tolerance].each{|k| @question.delete(k)}
@question[:question_type] = 'numerical_question'
@question[:answers] = [
{:weight => 100, :id => unique_local_id, :text => 'answer_text',
:numerical_answer_type => "exact_answer", :exact => var[:min]}
]
end
end
end
end
end