Module: CanvasQtiToLearnosityConverter

Defined in:
lib/canvas_qti_to_learnosity_converter.rb,
lib/canvas_qti_to_learnosity_converter/convert.rb,
lib/canvas_qti_to_learnosity_converter/version.rb

Defined Under Namespace

Classes: CanvasQtiQuiz, CanvasQtiQuizQuestion, CanvasQuestionTypeNotSupportedError, LearnosityActivity, LearnosityQuestion, Learnosityitem, MultipleAnswersLearnosityQuestion, MultipleChoiceLearnosityQuestion

Constant Summary collapse

LEARNOSITY_TYPE_MAP =
{
  "mcq" => MultipleChoiceLearnosityQuestion
}
VERSION =
"0.1.4"

Class Method Summary collapse

Class Method Details

.build_item_from_file(path, item_type = nil) ⇒ Object



67
68
69
70
71
72
73
74
# File 'lib/canvas_qti_to_learnosity_converter/convert.rb', line 67

def self.build_item_from_file(path, item_type = nil)
  file = File.new path
  file_val = JSON.parse(file.read)
  type = item_type || LEARNOSITY_TYPE_MAP[file_val["type"]]
  type.new(file_val)
ensure
  file.close
end

.build_quiz_from_file(path) ⇒ Object



59
60
61
62
63
64
65
# File 'lib/canvas_qti_to_learnosity_converter/convert.rb', line 59

def self.build_quiz_from_file(path)
  qti_file = File.new path
  qti_string = qti_file.read
  CanvasQtiQuiz.new(qti_string: qti_string)
ensure
  qti_file.close
end

.build_quiz_from_qti_string(qti_string) ⇒ Object



55
56
57
# File 'lib/canvas_qti_to_learnosity_converter/convert.rb', line 55

def self.build_quiz_from_qti_string(qti_string)
  CanvasQtiQuiz.new(qti_string: qti_string)
end

.convert(qti) ⇒ Object



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/canvas_qti_to_learnosity_converter/convert.rb', line 169

def self.convert(qti)
  quiz = CanvasQtiQuiz.new(qti_string: qti)
  items = quiz.css("item").map do |item|
    begin
      quiz_item = CanvasQtiQuizQuestion.new(qti_string: item.to_html)
      convert_item(quiz_item)
    rescue CanvasQuestionTypeNotSupportedError
      nil
    end
  end.compact

  assessment = quiz.css("assessment")
  {
    title: assessment.attribute("title").value,
    ident: assessment.attribute("ident").value,
    items: items,
  }
end

.convert_imscc_export(path) ⇒ Object



208
209
210
211
212
213
214
215
216
217
218
219
220
# File 'lib/canvas_qti_to_learnosity_converter/convert.rb', line 208

def self.convert_imscc_export(path)
  Zip::File.open(path) do |zip_file|
    entry = zip_file.find_entry("imsmanifest.xml")
    manifest = entry.get_input_stream.read
    parsed_manifest = Nokogiri.XML(manifest, &:noblanks)
    paths = imscc_quiz_paths(parsed_manifest)
    result = paths.map do |qti_path|
      qti = zip_file.find_entry(qti_path).get_input_stream.read
      to_native_types(convert(qti))
    end
    result
  end
end

.convert_item(qti_quiz) ⇒ Object



155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/canvas_qti_to_learnosity_converter/convert.rb', line 155

def self.convert_item(qti_quiz)
   type = extract_type(qti_quiz)
   case type
   when :multiple_choice_question
     convert_multiple_choice(qti_quiz)
   when :true_false_question
     convert_multiple_choice(qti_quiz)
   when :multiple_answers_question
     convert_multiple_answers(qti_quiz)
   else
     raise CanvasQuestionTypeNotSupportedError
   end
end

.convert_multiple_answers(item) ⇒ Object



144
145
146
147
148
149
150
151
152
153
# File 'lib/canvas_qti_to_learnosity_converter/convert.rb', line 144

def self.convert_multiple_answers(item)
 MultipleAnswersLearnosityQuestion.new({
   stimulus: extract_stimulus(item),
   options: extract_multiple_choice_options(item),
   multiple_responses: true,
   response_id: extract_response_id(item),
   type: "mcq",
   validation: extract_multiple_answers_validation(item),
 })
end

.convert_multiple_choice(item) ⇒ Object



133
134
135
136
137
138
139
140
141
142
# File 'lib/canvas_qti_to_learnosity_converter/convert.rb', line 133

def self.convert_multiple_choice(item)
  MultipleChoiceLearnosityQuestion.new({
    stimulus: extract_stimulus(item),
    options: extract_multiple_choice_options(item),
    multiple_responses: false,
    response_id: extract_response_id(item),
    type: "mcq",
    validation: extract_multiple_choice_validation(item),
  })
end

.convert_qti_file(path) ⇒ Object



188
189
190
191
192
193
194
195
# File 'lib/canvas_qti_to_learnosity_converter/convert.rb', line 188

def self.convert_qti_file(path)
  file = File.new(path)
  qti_string = file.read
  convert(qti_string)
ensure
  file.close
  file.unlink
end

.extract_mattext(mattext_node) ⇒ Object



82
83
84
# File 'lib/canvas_qti_to_learnosity_converter/convert.rb', line 82

def self.extract_mattext(mattext_node)
  mattext_node.content
end

.extract_multiple_answers_validation(item) ⇒ Object



124
125
126
127
128
129
130
131
# File 'lib/canvas_qti_to_learnosity_converter/convert.rb', line 124

def self.extract_multiple_answers_validation(item)
  correct_condition = item.css('item > resprocessing > respcondition[continue="No"] > conditionvar > and > varequal')
  alt_responses = correct_condition.map(&:text)
  {
    "scoring_type" => "partialMatch",
    "alt_responses" => alt_responses,
  }
end

.extract_multiple_choice_options(item) ⇒ Object



91
92
93
94
95
96
97
98
99
100
# File 'lib/canvas_qti_to_learnosity_converter/convert.rb', line 91

def self.extract_multiple_choice_options(item)
  choices = item.css("item > presentation > response_lid > render_choice > response_label")
  choices.map do |choice|
    ident = choice.attribute("ident").value
    {
      "value" => ident,
      "label" => extract_mattext(choice.css("material > mattext").first),
    }
  end
end

.extract_multiple_choice_validation(item) ⇒ Object



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/canvas_qti_to_learnosity_converter/convert.rb', line 106

def self.extract_multiple_choice_validation(item)
  resp_conditions = item.css("item > resprocessing > respcondition")
  correct_condition = resp_conditions.select do |condition|
    setvar = condition.css("setvar")
    setvar.length === 1 && setvar.text === "100"
  end.first


  # TODO check for more than 1 element
  correct_value = correct_condition.css("varequal").text
  {
    "scoring_type" => "exactMatch",
    "valid_response" => {
      "value" => [correct_value],
    },
  }
end

.extract_response_id(item) ⇒ Object



102
103
104
# File 'lib/canvas_qti_to_learnosity_converter/convert.rb', line 102

def self.extract_response_id(item)
  item.css("item > presentation > response_lid").attribute("ident").value
end

.extract_stimulus(item) ⇒ Object



86
87
88
89
# File 'lib/canvas_qti_to_learnosity_converter/convert.rb', line 86

def self.extract_stimulus(item)
  mattext = item.css("item > presentation > material > mattext").first
  extract_mattext(mattext)
end

.extract_type(item) ⇒ Object



76
77
78
79
80
# File 'lib/canvas_qti_to_learnosity_converter/convert.rb', line 76

def self.extract_type(item)
  item.css(%{ item > itemmetadata > qtimetadata >
    qtimetadatafield > fieldlabel:contains("question_type")})
    &.first&.next&.text&.to_sym
end

.imscc_quiz_paths(parsed_manifest) ⇒ Object



197
198
199
200
# File 'lib/canvas_qti_to_learnosity_converter/convert.rb', line 197

def self.imscc_quiz_paths(parsed_manifest)
  parsed_manifest.css("resources > resource[type='imsqti_xmlv1p2'] > file").
    map { |entry| entry.attribute("href").value }
end

.read_file(path) ⇒ Object



47
48
49
50
51
52
53
# File 'lib/canvas_qti_to_learnosity_converter/convert.rb', line 47

def self.read_file(path)
  file = File.new path
  file.read
ensure
  file.close
# Do we need to unlink?
end

.to_native_types(activity) ⇒ Object



202
203
204
205
206
# File 'lib/canvas_qti_to_learnosity_converter/convert.rb', line 202

def self.to_native_types(activity)
  clone = activity.clone
  clone[:items] = activity[:items].map(&:to_h)
  clone
end