canvas_qti_to_learnosity_converter 1.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: