canvas_qti_to_learnosity_converter 1.0.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b08edcaa5b1202695605345f102ecd6999daf9da1e93afe4be7f3fac421e1607
4
- data.tar.gz: dac787cd08b5d12ba9f2612db7b7b05d5d8421d1ac49775790f3469af8fca511
3
+ metadata.gz: '052911e6b4ed78bd22f6f6f99af67f56e5aa42bb84f327cfe9da5090900ff3f8'
4
+ data.tar.gz: 8927eb57c5e1b4a75dd00b49775e9479573295c7b7ce1bb48565bf0763852ea7
5
5
  SHA512:
6
- metadata.gz: f6628bda0a17f0686f773cbf651fe1b19dd427d466440e43ba2ec3759e60d9f03d8f8b67544716f13239d776d6cfda201c1b427562d9e2aedf116e244b3ef239
7
- data.tar.gz: 278c0aed069d6b31d91088c764010c288ebefe4a818e4049d87b489ed0f1e38d796192bedb05afae538028165643a09dda55003eeab523737fceb23a5f66576b
6
+ metadata.gz: 9e435bae6d96255228cd1ccc74b1b98e96208f5962b860db9629d07d3de668539065cc5795a13f09bff7ace2b6b84f938ae449b5d0268788a0cf17b1a4a08646
7
+ data.tar.gz: ece35a13ae4485a6eb0518e472530426c121215720759058ebcda7ce0c2283ac9d94e72944dc392ee78987caaab85035d8b5f8173a10ee5453605ced1fda1e21
@@ -4,7 +4,30 @@ require "ostruct"
4
4
  require "zip"
5
5
  require "uri"
6
6
 
7
+ require "canvas_qti_to_learnosity_converter/questions/multiple_choice"
8
+ require "canvas_qti_to_learnosity_converter/questions/short_answer"
9
+ require "canvas_qti_to_learnosity_converter/questions/fill_the_blanks"
10
+ require "canvas_qti_to_learnosity_converter/questions/multiple_dropdowns"
11
+ require "canvas_qti_to_learnosity_converter/questions/matching"
12
+ require "canvas_qti_to_learnosity_converter/questions/essay"
13
+ require "canvas_qti_to_learnosity_converter/questions/file_upload"
14
+ require "canvas_qti_to_learnosity_converter/questions/text_only"
15
+ require "canvas_qti_to_learnosity_converter/questions/numerical"
16
+
7
17
  module CanvasQtiToLearnosityConverter
18
+ FEATURE_TYPES = [ :text_only_question ]
19
+ QUESTION_TYPES = [
20
+ :multiple_choice_question,
21
+ :true_false_question,
22
+ :multiple_answers_question,
23
+ :short_answer_question,
24
+ :fill_in_multiple_blanks_question,
25
+ :multiple_dropdowns_question,
26
+ :matching_question,
27
+ :essay_question,
28
+ :file_upload_question,
29
+ ]
30
+
8
31
  class CanvasQuestionTypeNotSupportedError < RuntimeError
9
32
  end
10
33
 
@@ -17,33 +40,6 @@ module CanvasQtiToLearnosityConverter
17
40
  end
18
41
  end
19
42
 
20
- class CanvasQtiQuizQuestion
21
- extend Forwardable
22
- def_delegators :@xml, :css
23
-
24
- def initialize(qti_string:)
25
- @xml = Nokogiri.XML(qti_string, &:noblanks)
26
- end
27
- end
28
-
29
- class LearnosityQuestion < OpenStruct
30
- end
31
-
32
- class MultipleChoiceLearnosityQuestion < LearnosityQuestion
33
- end
34
-
35
- class MultipleAnswersLearnosityQuestion < LearnosityQuestion
36
- end
37
-
38
- class Learnosityitem
39
- end
40
-
41
- class LearnosityActivity
42
- end
43
-
44
- LEARNOSITY_TYPE_MAP = {
45
- "mcq" => MultipleChoiceLearnosityQuestion
46
- }
47
43
 
48
44
  def self.read_file(path)
49
45
  file = File.new path
@@ -65,94 +61,6 @@ module CanvasQtiToLearnosityConverter
65
61
  qti_file.close
66
62
  end
67
63
 
68
- def self.build_item_from_file(path, item_type = nil)
69
- file = File.new path
70
- file_val = JSON.parse(file.read)
71
- type = item_type || LEARNOSITY_TYPE_MAP[file_val["type"]]
72
- type.new(file_val)
73
- ensure
74
- file.close
75
- end
76
-
77
- def self.extract_type(item)
78
- item.css(%{ item > itemmetadata > qtimetadata >
79
- qtimetadatafield > fieldlabel:contains("question_type")})
80
- &.first&.next&.text&.to_sym
81
- end
82
-
83
- def self.extract_mattext(mattext_node)
84
- mattext_node.content
85
- end
86
-
87
- def self.extract_stimulus(item)
88
- mattext = item.css("item > presentation > material > mattext").first
89
- extract_mattext(mattext)
90
- end
91
-
92
- def self.extract_multiple_choice_options(item)
93
- choices = item.css("item > presentation > response_lid > render_choice > response_label")
94
- choices.map do |choice|
95
- ident = choice.attribute("ident").value
96
- {
97
- "value" => ident,
98
- "label" => extract_mattext(choice.css("material > mattext").first),
99
- }
100
- end
101
- end
102
-
103
- def self.extract_response_id(item)
104
- item.css("item > presentation > response_lid").attribute("ident").value
105
- end
106
-
107
- def self.extract_multiple_choice_validation(item)
108
- resp_conditions = item.css("item > resprocessing > respcondition")
109
- correct_condition = resp_conditions.select do |condition|
110
- setvar = condition.css("setvar")
111
- setvar.length === 1 && setvar.text === "100"
112
- end.first
113
-
114
-
115
- # TODO check for more than 1 element
116
- correct_value = correct_condition.css("varequal").text
117
- {
118
- "scoring_type" => "exactMatch",
119
- "valid_response" => {
120
- "value" => [correct_value],
121
- },
122
- }
123
- end
124
-
125
- def self.extract_multiple_answers_validation(item)
126
- correct_condition = item.css('item > resprocessing > respcondition[continue="No"] > conditionvar > and > varequal')
127
- alt_responses = correct_condition.map(&:text)
128
- {
129
- "scoring_type" => "partialMatch",
130
- "alt_responses" => alt_responses,
131
- }
132
- end
133
-
134
- def self.convert_multiple_choice(item)
135
- MultipleChoiceLearnosityQuestion.new({
136
- stimulus: extract_stimulus(item),
137
- options: extract_multiple_choice_options(item),
138
- multiple_responses: false,
139
- response_id: extract_response_id(item),
140
- type: "mcq",
141
- validation: extract_multiple_choice_validation(item),
142
- })
143
- end
144
-
145
- def self.convert_multiple_answers(item)
146
- MultipleAnswersLearnosityQuestion.new({
147
- stimulus: extract_stimulus(item),
148
- options: extract_multiple_choice_options(item),
149
- multiple_responses: true,
150
- response_id: extract_response_id(item),
151
- type: "mcq",
152
- validation: extract_multiple_answers_validation(item),
153
- })
154
- end
155
-
156
64
  def self.add_files_to_assets(assets, path, text)
157
65
  text.scan(/%24IMS-CC-FILEBASE%24\/([^"]+)/).flatten.each do |asset_path|
158
66
  decoded_path = URI.unescape(asset_path)
@@ -161,35 +69,54 @@ module CanvasQtiToLearnosityConverter
161
69
  end
162
70
  end
163
71
 
164
- def self.convert_item(qti_quiz, assets, item_index)
165
- type = extract_type(qti_quiz)
72
+ def self.extract_type(xml)
73
+ xml.css(%{ item > itemmetadata > qtimetadata >
74
+ qtimetadatafield > fieldlabel:contains("question_type")})
75
+ &.first&.next&.text&.to_sym
76
+ end
77
+
78
+ def self.convert_item(qti_string:)
79
+ xml = Nokogiri.XML(qti_string, &:noblanks)
80
+ type = extract_type(xml)
81
+
82
+ if FEATURE_TYPES.include?(type)
83
+ learnosity_type = "feature"
84
+ else
85
+ learnosity_type = "question"
86
+ end
166
87
 
167
88
  question = case type
168
89
  when :multiple_choice_question
169
- convert_multiple_choice(qti_quiz)
90
+ MultipleChoiceQuestion.new(xml)
170
91
  when :true_false_question
171
- convert_multiple_choice(qti_quiz)
92
+ MultipleChoiceQuestion.new(xml)
172
93
  when :multiple_answers_question
173
- convert_multiple_answers(qti_quiz)
94
+ MultipleAnswersQuestion.new(xml)
95
+ when :short_answer_question
96
+ ShortAnswerQuestion.new(xml)
97
+ when :fill_in_multiple_blanks_question
98
+ FillTheBlanksQuestion.new(xml)
99
+ when :multiple_dropdowns_question
100
+ MultipleDropdownsQuestion.new(xml)
101
+ when :matching_question
102
+ MatchingQuestion.new(xml)
103
+ when :essay_question
104
+ EssayQuestion.new(xml)
105
+ when :file_upload_question
106
+ FileUploadQuestion.new(xml)
107
+ when :text_only_question
108
+ TextOnlyQuestion.new(xml)
109
+ when :numerical_question
110
+ NumericalQuestion.new(xml)
174
111
  else
175
112
  raise CanvasQuestionTypeNotSupportedError
176
113
  end
177
114
 
178
- add_files_to_assets(
179
- assets,
180
- [item_index, :stimulus],
181
- question.stimulus
182
- )
183
-
184
- question.options.each.with_index do |option, index|
185
- add_files_to_assets(
186
- assets,
187
- [item_index, :options, index, "label"],
188
- option["label"]
189
- )
190
- end
115
+ [learnosity_type, question]
116
+ end
191
117
 
192
- question
118
+ def self.clean_title(title)
119
+ title.gsub(/["']/, "")
193
120
  end
194
121
 
195
122
  def self.convert(qti, assets)
@@ -198,17 +125,23 @@ module CanvasQtiToLearnosityConverter
198
125
  ident = assessment.attribute("ident").value
199
126
  assets[ident] = {}
200
127
 
201
- items = quiz.css("item").map.with_index do |item, index|
128
+ items = []
129
+
130
+ quiz.css("item").each.with_index do |item, index|
202
131
  begin
203
- quiz_item = CanvasQtiQuizQuestion.new(qti_string: item.to_html)
204
- convert_item(quiz_item, assets[ident], index)
132
+ learnosity_type, quiz_item = convert_item(qti_string: item.to_html)
133
+
134
+ items.push({type: learnosity_type, data: quiz_item.to_learnosity})
135
+ path = [items.count - 1, :data]
136
+
137
+ quiz_item.add_learnosity_assets(assets[ident], path)
205
138
  rescue CanvasQuestionTypeNotSupportedError
206
139
  nil
207
140
  end
208
- end.compact
141
+ end
209
142
 
210
143
  {
211
- title: assessment.attribute("title").value,
144
+ title: clean_title(assessment.attribute("title").value),
212
145
  ident: ident,
213
146
  items: items,
214
147
  }
@@ -228,12 +161,6 @@ module CanvasQtiToLearnosityConverter
228
161
  map { |entry| entry.attribute("href").value }
229
162
  end
230
163
 
231
- def self.to_native_types(activity)
232
- clone = activity.clone
233
- clone[:items] = activity[:items].map(&:to_h)
234
- clone
235
- end
236
-
237
164
  def self.convert_imscc_export(path)
238
165
  Zip::File.open(path) do |zip_file|
239
166
  entry = zip_file.find_entry("imsmanifest.xml")
@@ -244,7 +171,7 @@ module CanvasQtiToLearnosityConverter
244
171
  assets = {}
245
172
  converted_assesments = paths.map do |qti_path|
246
173
  qti = zip_file.find_entry(qti_path).get_input_stream.read
247
- to_native_types(convert(qti, assets))
174
+ convert(qti, assets)
248
175
  end
249
176
 
250
177
  {
@@ -0,0 +1,21 @@
1
+ require "canvas_qti_to_learnosity_converter/questions/question"
2
+
3
+ module CanvasQtiToLearnosityConverter
4
+ class EssayQuestion < QuizQuestion
5
+ def to_learnosity
6
+ {
7
+ type: "longtextV2",
8
+ stimulus: extract_stimulus(),
9
+ }
10
+ end
11
+
12
+ def add_learnosity_assets(assets, path)
13
+ learnosity = to_learnosity
14
+ CanvasQtiToLearnosityConverter.add_files_to_assets(
15
+ assets,
16
+ path + [:stimulus],
17
+ learnosity[:stimulus]
18
+ )
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,34 @@
1
+ require "canvas_qti_to_learnosity_converter/questions/question"
2
+
3
+ module CanvasQtiToLearnosityConverter
4
+ class FileUploadQuestion < QuizQuestion
5
+ def to_learnosity
6
+ {
7
+ type: "fileupload",
8
+ stimulus: extract_stimulus(),
9
+ allow_pdf: true,
10
+ allow_jpg: true,
11
+ allow_gif: true,
12
+ allow_png: true,
13
+ allow_csv: true,
14
+ allow_rtf: true,
15
+ allow_txt: true,
16
+ allow_xps: true,
17
+ allow_ms_word: true,
18
+ allow_ms_excel: true,
19
+ allow_ms_powerpoint: true,
20
+ allow_ms_publisher: true,
21
+ allow_open_office: true
22
+ }
23
+ end
24
+
25
+ def add_learnosity_assets(assets, path)
26
+ learnosity = to_learnosity
27
+ CanvasQtiToLearnosityConverter.add_files_to_assets(
28
+ assets,
29
+ path + [:stimulus],
30
+ learnosity[:stimulus]
31
+ )
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,62 @@
1
+ require "canvas_qti_to_learnosity_converter/questions/template_question"
2
+
3
+ module CanvasQtiToLearnosityConverter
4
+ class FillTheBlanksQuestion < TemplateQuestion
5
+ def to_learnosity
6
+ {
7
+ type: "clozetext",
8
+ stimulus: "",
9
+ template: extract_template(),
10
+ validation: extract_validation(),
11
+ }
12
+ end
13
+
14
+ def add_learnosity_assets(assets, path)
15
+ learnosity = to_learnosity
16
+
17
+ CanvasQtiToLearnosityConverter.add_files_to_assets(
18
+ assets,
19
+ path + [:template],
20
+ learnosity[:template]
21
+ )
22
+ end
23
+
24
+ def extract_validation()
25
+ template = get_template()
26
+
27
+ responses = extract_template_values(template).map do |name|
28
+ result = @xml.css(%{item > presentation >
29
+ response_lid[ident="response_#{name}"] > render_choice material >
30
+ mattext}).map do |node|
31
+ extract_mattext(node)
32
+ end
33
+
34
+ if result.empty?
35
+ nil
36
+ else
37
+ result
38
+ end
39
+ end.compact
40
+
41
+ all_responses = []
42
+ create_responses(responses, 0, all_responses, [])
43
+
44
+ {
45
+ "scoring_type" => "partialMatch",
46
+ "valid_response" => all_responses.shift,
47
+ "alt_responses" => all_responses
48
+ }
49
+ end
50
+
51
+ def create_responses(blank_responses, depth, result, current_response)
52
+ if depth == blank_responses.count
53
+ result.push({ "value" => current_response })
54
+ return
55
+ end
56
+
57
+ blank_responses[depth].each do |possible_response|
58
+ create_responses(blank_responses, depth + 1, result, current_response + [possible_response])
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,75 @@
1
+ require "canvas_qti_to_learnosity_converter/questions/question"
2
+
3
+ module CanvasQtiToLearnosityConverter
4
+ class MatchingQuestion < QuizQuestion
5
+ def to_learnosity
6
+ {
7
+ type: "association",
8
+ stimulus: extract_stimulus(),
9
+ stimulus_list: extract_stimulus_list(),
10
+ validation: extract_validation(),
11
+ possible_responses: extract_responses(),
12
+ duplicate_responses: true,
13
+ }
14
+ end
15
+
16
+ def extract_stimulus_list()
17
+ @xml.css("item > presentation > response_lid > material > mattext").map do |node|
18
+ extract_mattext(node)
19
+ end
20
+ end
21
+
22
+ def extract_response_idents()
23
+ @xml.css("item > presentation > response_lid").map do |node|
24
+ node.attribute("ident").text
25
+ end
26
+ end
27
+
28
+ def extract_valid_label_idents(valid_response_idents)
29
+ valid_response_idents.map do |ident|
30
+ @xml.css(%{item > resprocessing > respcondition varequal[respident="#{ident}"]}).map do |node|
31
+ node.content
32
+ end
33
+ end.flatten
34
+ end
35
+
36
+ def extract_validation()
37
+ valid_response_idents = extract_response_idents()
38
+ valid_label_idents = extract_valid_label_idents(valid_response_idents)
39
+
40
+ valid_idents = Hash[valid_response_idents.zip(valid_label_idents)]
41
+
42
+ valid_responses = valid_idents.map do |response_ident, label_ident|
43
+ @xml.css(%{item > presentation >
44
+ response_lid[ident="#{response_ident}"]
45
+ response_label[ident="#{label_ident}"] > material > mattext}).map do |node|
46
+ extract_mattext(node)
47
+ end
48
+ end.flatten
49
+
50
+ {
51
+ "scoring_type" => "partialMatch",
52
+ "valid_response" => {
53
+ "value" => valid_responses
54
+ }
55
+ }
56
+ end
57
+
58
+ def extract_responses()
59
+ response_node = @xml.css("item > presentation > response_lid").first
60
+
61
+ response_node.css("response_label mattext").map do |node|
62
+ extract_mattext(node)
63
+ end
64
+ end
65
+
66
+ def add_learnosity_assets(assets, path)
67
+ learnosity = to_learnosity
68
+ CanvasQtiToLearnosityConverter.add_files_to_assets(
69
+ assets,
70
+ path + [:stimulus],
71
+ learnosity[:stimulus]
72
+ )
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,89 @@
1
+ require "canvas_qti_to_learnosity_converter/questions/question"
2
+
3
+ module CanvasQtiToLearnosityConverter
4
+ class MultipleChoiceQuestion < QuizQuestion
5
+ def extract_options()
6
+ choices = @xml.css("item > presentation > response_lid > render_choice > response_label")
7
+ choices.map do |choice|
8
+ ident = choice.attribute("ident").value
9
+ {
10
+ "value" => ident,
11
+ "label" => extract_mattext(choice.css("material > mattext").first),
12
+ }
13
+ end
14
+ end
15
+
16
+ def extract_response_id()
17
+ @xml.css("item > presentation > response_lid").attribute("ident").value
18
+ end
19
+
20
+ def extract_validation()
21
+ resp_conditions = @xml.css("item > resprocessing > respcondition")
22
+ correct_condition = resp_conditions.select do |condition|
23
+ setvar = condition.css("setvar")
24
+ setvar.length === 1 && setvar.text === "100"
25
+ end.first
26
+
27
+ # TODO check for more than 1 element
28
+ correct_value = correct_condition.css("varequal").text
29
+ {
30
+ "scoring_type" => "exactMatch",
31
+ "valid_response" => {
32
+ "value" => [correct_value],
33
+ },
34
+ }
35
+ end
36
+
37
+ def to_learnosity
38
+ {
39
+ stimulus: extract_stimulus(),
40
+ options: extract_options(),
41
+ multiple_responses: false,
42
+ response_id: extract_response_id(),
43
+ type: "mcq",
44
+ validation: extract_validation(),
45
+ }
46
+ end
47
+
48
+ def add_learnosity_assets(assets, path)
49
+ learnosity = to_learnosity
50
+ CanvasQtiToLearnosityConverter.add_files_to_assets(
51
+ assets,
52
+ path + [:stimulus],
53
+ learnosity[:stimulus]
54
+ )
55
+
56
+ learnosity[:options].each.with_index do |option, index|
57
+ CanvasQtiToLearnosityConverter.add_files_to_assets(
58
+ assets,
59
+ path + [:options, index, "label"],
60
+ option["label"]
61
+ )
62
+ end
63
+ end
64
+ end
65
+
66
+ class MultipleAnswersQuestion < MultipleChoiceQuestion
67
+ def to_learnosity
68
+ {
69
+ stimulus: extract_stimulus(),
70
+ options: extract_options(),
71
+ multiple_responses: true,
72
+ response_id: extract_response_id(),
73
+ type: "mcq",
74
+ validation: extract_validation()
75
+ }
76
+ end
77
+
78
+ def extract_validation()
79
+ correct_condition = @xml.css('item > resprocessing >
80
+ respcondition[continue="No"] > conditionvar >
81
+ and > varequal')
82
+ alt_responses = correct_condition.map(&:text)
83
+ {
84
+ "scoring_type" => "partialMatch",
85
+ "alt_responses" => alt_responses,
86
+ }
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,55 @@
1
+ require "canvas_qti_to_learnosity_converter/questions/template_question"
2
+
3
+ module CanvasQtiToLearnosityConverter
4
+ class MultipleDropdownsQuestion < TemplateQuestion
5
+ def to_learnosity
6
+ {
7
+ type: "clozedropdown",
8
+ stimulus: "",
9
+ template: extract_template(),
10
+ validation: extract_validation(),
11
+ possible_responses: extract_responses(),
12
+ }
13
+ end
14
+
15
+ def extract_validation()
16
+ template = get_template()
17
+
18
+ valid_responses = extract_template_values(template).map do |name|
19
+ correct_ident = @xml.css(%{item > resprocessing > respcondition >
20
+ conditionvar > varequal[respident="response_#{name}"]}).first&.content
21
+
22
+ @xml.css(%{item > presentation >
23
+ response_lid[ident="response_#{name}"] response_label}).map do |node|
24
+ text = extract_mattext(node.css("mattext").first)
25
+ ident = node.attribute("ident").text
26
+ ident == correct_ident ? text : nil
27
+ end.compact
28
+ end.flatten
29
+
30
+ {
31
+ "scoring_type" => "partialMatch",
32
+ "valid_response" => {
33
+ "value" => valid_responses,
34
+ }
35
+ }
36
+ end
37
+
38
+ def extract_responses()
39
+ template = get_template()
40
+
41
+ extract_template_values(template).map do |name|
42
+ result = @xml.css(%{item > presentation >
43
+ response_lid[ident="response_#{name}"] > render_choice mattext}).map do |node|
44
+ extract_mattext(node)
45
+ end
46
+
47
+ if result.empty?
48
+ nil
49
+ else
50
+ result
51
+ end
52
+ end.compact
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,65 @@
1
+ require "canvas_qti_to_learnosity_converter/questions/question"
2
+
3
+ module CanvasQtiToLearnosityConverter
4
+ class NumericalQuestion < QuizQuestion
5
+ def to_learnosity
6
+ {
7
+ is_math: true,
8
+ type: "formulaV2",
9
+ stimulus: extract_stimulus(),
10
+ template: "{{response}}",
11
+ validation: extract_validation(),
12
+ }
13
+ end
14
+
15
+ def extract_validation()
16
+ response_mins = @xml.css('item > resprocessing >
17
+ respcondition[continue="No"] > conditionvar vargte').map do |node|
18
+ node.content
19
+ end
20
+
21
+ response_maxs = @xml.css('item > resprocessing >
22
+ respcondition[continue="No"] > conditionvar varlte').map do |node|
23
+ node.content
24
+ end
25
+
26
+ answer_bounds = response_mins.zip(response_maxs).map do |bounds|
27
+ puts bounds.inspect
28
+ # Get the precision by counting the number of places after the decimal
29
+ precision = [
30
+ bounds.first.split(".").last.length,
31
+ bounds.last.split(".").last.length
32
+ ].max
33
+
34
+ {
35
+ center: ((bounds.first.to_f + bounds.last.to_f) / 2.0).round(precision),
36
+ pm: ((bounds.first.to_f - bounds.last.to_f) / 2.0).round(precision).abs,
37
+ }
38
+ end
39
+
40
+ valid_answers = answer_bounds.map do |bounds|
41
+ {
42
+ "value" => [{
43
+ "method" => "equivValue",
44
+ "value" => "#{bounds[:center]}\\pm#{bounds[:pm]}",
45
+ }]
46
+ }
47
+ end
48
+
49
+ {
50
+ "scoring_type" => "exactMatch",
51
+ "valid_response" => valid_answers.shift,
52
+ "alt_responses" => valid_answers,
53
+ }
54
+ end
55
+
56
+ def add_learnosity_assets(assets, path)
57
+ learnosity = to_learnosity
58
+ CanvasQtiToLearnosityConverter.add_files_to_assets(
59
+ assets,
60
+ path + [:stimulus],
61
+ learnosity[:stimulus]
62
+ )
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,19 @@
1
+ module CanvasQtiToLearnosityConverter
2
+ class QuizQuestion
3
+ extend Forwardable
4
+ def_delegators :@xml, :css
5
+
6
+ def initialize(xml)
7
+ @xml = xml
8
+ end
9
+
10
+ def extract_stimulus()
11
+ mattext = @xml.css("item > presentation > material > mattext").first
12
+ extract_mattext(mattext)
13
+ end
14
+
15
+ def extract_mattext(mattext_node)
16
+ mattext_node.content
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,40 @@
1
+ require "canvas_qti_to_learnosity_converter/questions/question"
2
+
3
+ module CanvasQtiToLearnosityConverter
4
+ # This is fill in the blank in the Canvas UI, but it is actually a short
5
+ # answer type.
6
+ class ShortAnswerQuestion < QuizQuestion
7
+ def extract_response_id()
8
+ @xml.css("item > presentation > response_str").attribute("ident").value
9
+ end
10
+
11
+ def to_learnosity
12
+ {
13
+ type: "shorttext",
14
+ stimulus: extract_stimulus(),
15
+ validation: extract_validation(),
16
+ response_id: extract_response_id(),
17
+ }
18
+ end
19
+
20
+ def extract_validation()
21
+ correct_responses = @xml.css('item > resprocessing >
22
+ respcondition[continue="No"] > conditionvar > varequal')
23
+ correct_response = { "value" => correct_responses.shift.text }
24
+ {
25
+ "scoring_type" => "exactMatch",
26
+ "valid_response" => correct_response,
27
+ "alt_responses" => correct_responses.map { |res| { "value" => res.text } }
28
+ }
29
+ end
30
+
31
+ def add_learnosity_assets(assets, path)
32
+ learnosity = to_learnosity
33
+ CanvasQtiToLearnosityConverter.add_files_to_assets(
34
+ assets,
35
+ path + [:stimulus],
36
+ learnosity[:stimulus]
37
+ )
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,39 @@
1
+ require "canvas_qti_to_learnosity_converter/questions/question"
2
+
3
+ module CanvasQtiToLearnosityConverter
4
+ class TemplateQuestion < QuizQuestion
5
+ def extract_template()
6
+ placeholders = @xml.css("item > presentation > response_lid > material >
7
+ mattext").map { |text| extract_mattext(text) }
8
+
9
+ template = get_template()
10
+
11
+ placeholders.each do |placeholder|
12
+ template.sub!("[#{placeholder}]", "{{response}}")
13
+ end
14
+
15
+ template
16
+ end
17
+
18
+ def extract_template_values(template)
19
+ template.scan(/\[([^\]]+)\]/).map do |capture_list|
20
+ capture_list.first
21
+ end
22
+ end
23
+
24
+ def get_template()
25
+ template_node_list = @xml.css("item > presentation > material > mattext")
26
+ extract_mattext(template_node_list.first)
27
+ end
28
+
29
+ def add_learnosity_assets(assets, path)
30
+ learnosity = to_learnosity
31
+
32
+ CanvasQtiToLearnosityConverter.add_files_to_assets(
33
+ assets,
34
+ path + [:template],
35
+ learnosity[:template]
36
+ )
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,22 @@
1
+ require "canvas_qti_to_learnosity_converter/questions/question"
2
+
3
+ module CanvasQtiToLearnosityConverter
4
+ class TextOnlyQuestion < QuizQuestion
5
+ def to_learnosity
6
+ {
7
+ type: "sharedpassage",
8
+ heading: "",
9
+ content: extract_stimulus(),
10
+ }
11
+ end
12
+
13
+ def add_learnosity_assets(assets, path)
14
+ learnosity = to_learnosity
15
+ CanvasQtiToLearnosityConverter.add_files_to_assets(
16
+ assets,
17
+ path + [:content],
18
+ learnosity[:content]
19
+ )
20
+ end
21
+ end
22
+ end
@@ -1,3 +1,3 @@
1
1
  module CanvasQtiToLearnosityConverter
2
- VERSION = "1.0.0"
2
+ VERSION = "2.1.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: canvas_qti_to_learnosity_converter
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Atomic Jolt
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2018-07-31 00:00:00.000000000 Z
12
+ date: 2018-08-10 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -115,6 +115,17 @@ files:
115
115
  - canvas_qti_to_learnosity_converter.gemspec
116
116
  - lib/canvas_qti_to_learnosity_converter.rb
117
117
  - lib/canvas_qti_to_learnosity_converter/convert.rb
118
+ - lib/canvas_qti_to_learnosity_converter/questions/essay.rb
119
+ - lib/canvas_qti_to_learnosity_converter/questions/file_upload.rb
120
+ - lib/canvas_qti_to_learnosity_converter/questions/fill_the_blanks.rb
121
+ - lib/canvas_qti_to_learnosity_converter/questions/matching.rb
122
+ - lib/canvas_qti_to_learnosity_converter/questions/multiple_choice.rb
123
+ - lib/canvas_qti_to_learnosity_converter/questions/multiple_dropdowns.rb
124
+ - lib/canvas_qti_to_learnosity_converter/questions/numerical.rb
125
+ - lib/canvas_qti_to_learnosity_converter/questions/question.rb
126
+ - lib/canvas_qti_to_learnosity_converter/questions/short_answer.rb
127
+ - lib/canvas_qti_to_learnosity_converter/questions/template_question.rb
128
+ - lib/canvas_qti_to_learnosity_converter/questions/text_only.rb
118
129
  - lib/canvas_qti_to_learnosity_converter/version.rb
119
130
  homepage: https://github.com/atomicjolt/qti_to_learnosity_converter
120
131
  licenses: