qti 2.7.1 → 2.9.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: 01dd841b1a9215aeaddf4e9211f7e2327544067afa5fb2e24a58186f7c035115
4
- data.tar.gz: b4d688f36fb284da0ba8168463b667eb59bf612b2750ef50650230dcc129e2bb
3
+ metadata.gz: 795e15592723e240bf42bb23030ae237606cdb9c4191ce010bf241ae1d23d6f7
4
+ data.tar.gz: ce7a0e634699b57c08ce425d247b928405b36de2dc98f34435dbf6e84e7e4551
5
5
  SHA512:
6
- metadata.gz: 301bfa283539d68e2feca734a9aa97046c2604c5eedc8f656e217cdbd90949d3ddf88dcdda39ecbb1c19dde68d0b51c5f582aa9b33c3f79995109e921126dbe2
7
- data.tar.gz: 880e9ce56fe47276d833ab1b19d1f6b30646cf5a641ddce0f7e6468bb9922b05ccfd5ff140b413f7e8698c532798ade8fa50262a37e93231cfef5af5b3417ae8
6
+ metadata.gz: 2699fb585fa48d0ee298426ae9ecca58724f5298cc3422e0d5363acbffae04bc3e58502bd72e7cc7433571975cb0152c81887d743412678c4a79bc691dfde2e0
7
+ data.tar.gz: 8b6bd0ac53d3c5783c7ebdea6d2cabe238b37ccb0365ae8ef045207a1d9d1493ebc48f4ae13371a4ecb872ca67fdbc8d9232e45c156e6c83e70b10fa311161db
data/lib/qti/sanitizer.rb CHANGED
@@ -37,6 +37,18 @@ module Qti
37
37
 
38
38
  private
39
39
 
40
+ def convert_canvas_math_images(env)
41
+ node = env[:node]
42
+ node_name = env[:node_name]
43
+ latex = node['data-equation-content']
44
+
45
+ return if env[:is_whitelisted] || !env[:node].element?
46
+ return unless node_name == 'img'
47
+ return unless latex
48
+
49
+ node.replace("\\(#{latex}\\)")
50
+ end
51
+
40
52
  def object_tag_transformer
41
53
  lambda do |env|
42
54
  return unless env[:node_name] == 'object'
@@ -73,6 +85,7 @@ module Qti
73
85
  transformers << src_transformers
74
86
  transformers << object_tag_transformer if import_objects
75
87
  transformers << remap_unknown_tags_transformer
88
+ transformers << method(:convert_canvas_math_images) if Qti.configuration.extract_latex_from_image_tags
76
89
  Sanitize::Config::RELAXED.merge transformers: transformers
77
90
  end
78
91
 
@@ -9,7 +9,7 @@ module Qti
9
9
  item_prompt.split(CANVAS_REGEX).map.with_index do |stem_item, index|
10
10
  if canvas_fib_response_ids.include?(stem_item)
11
11
  # Strip the brackets before searching
12
- stem_blank(index, canvas_blank_id(stem_item[1..-2]))
12
+ stem_blank(index, stem_item[1..-2])
13
13
  else
14
14
  stem_text(index, stem_item)
15
15
  end
@@ -21,7 +21,8 @@ module Qti
21
21
  id: "stem_#{index}",
22
22
  position: index + 1,
23
23
  type: 'blank',
24
- blank_id: value
24
+ blank_id: blank_id(value),
25
+ blank_name: value
25
26
  }
26
27
  end
27
28
 
@@ -34,6 +35,15 @@ module Qti
34
35
  }
35
36
  end
36
37
 
38
+ def blank_id(stem_item)
39
+ return stem_item unless canvas_custom_fitb?
40
+ canvas_blank_id(stem_item)
41
+ end
42
+
43
+ def canvas_custom_fitb?
44
+ @canvas_custom_fitb ||= BaseInteraction.canvas_custom_fitb?(@node)
45
+ end
46
+
37
47
  def canvas_blank_id(stem_item)
38
48
  blank_id = nil
39
49
  node.xpath('.//xmlns:response_lid/xmlns:material').children.map do |response_lid_node|
@@ -9,6 +9,11 @@ module Qti
9
9
  false
10
10
  end
11
11
 
12
+ def self.canvas_custom_fitb?(node)
13
+ qtype = question_type(node)
14
+ %w[fill_in_multiple_blanks_question multiple_dropdowns_question].include? qtype
15
+ end
16
+
12
17
  def self.canvas_multiple_fib?(node)
13
18
  matches = node.xpath('.//xmlns:response_lid')
14
19
  return false if matches.count < 1
@@ -19,6 +19,12 @@ module Qti
19
19
  './/xmlns:qtimetadatafield/xmlns:fieldlabel[text()="bank_type"]/../xmlns:fieldentry'
20
20
  )&.content
21
21
  end
22
+
23
+ def bank_context_uuid
24
+ @bank_context_uuid ||= xpath_with_single_check(
25
+ './/xmlns:qtimetadatafield/xmlns:fieldlabel[text()="bank_context_uuid"]/../xmlns:fieldentry'
26
+ )&.content
27
+ end
22
28
  end
23
29
  end
24
30
  end
data/lib/qti/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Qti
2
- VERSION = '2.7.1'.freeze
2
+ VERSION = '2.9.0'.freeze
3
3
  end
data/lib/qti.rb CHANGED
@@ -4,6 +4,7 @@ require 'active_support/core_ext/string'
4
4
  require 'active_support/core_ext/hash/except'
5
5
  require 'active_support/core_ext/module/delegation'
6
6
  require 'dry-struct'
7
+ require 'ostruct'
7
8
  require 'find'
8
9
  require 'forwardable'
9
10
  require 'mathml2latex'
@@ -68,6 +69,14 @@ module Qti
68
69
  @import.create_question_group(question_group_ref)
69
70
  end
70
71
  end
72
+
73
+ def self.configuration
74
+ @configuration ||= OpenStruct.new
75
+ end
76
+
77
+ def self.configure
78
+ yield(configuration)
79
+ end
71
80
  end
72
81
 
73
82
  # The load order of all of these is important.
@@ -5,7 +5,7 @@
5
5
  <item title="Grading - specific - 3 pt score" ident="QUE_1003">
6
6
  <presentation>
7
7
  <material>
8
- <mattext texttype="text/html"><![CDATA[If I get a 3, I must have done something wrong. <img align="bottom" alt="image.png" src="org0/images/image.png" border="0"/>]]></mattext>
8
+ <mattext texttype="text/html"><![CDATA[If I get a 3, I must have done something wrong. <img data-equation-content="sample equation" script="alert('bad')" align="bottom" alt="image.png" src="org0/images/image.png" border="0"/> <img script="alert('bad')" align="bottom" alt="image.png" src="org0/images/image.png" border="0"/>]]></mattext>
9
9
  </material>
10
10
  <response_lid ident="QUE_1004_RL" rcardinality="Single" rtiming="No">
11
11
  <render_choice shuffle="Yes">
@@ -29,6 +29,18 @@ describe Qti::V1::Models::AssessmentItem do
29
29
  it 'has sanitized item_body' do
30
30
  expect(loaded_class.item_body).to include '<img'
31
31
  expect(loaded_class.item_body).to include 'If I get a 3, I must have done something wrong.'
32
+ expect(loaded_class.item_body).not_to include 'script="alert(\'bad\')"'
33
+ end
34
+
35
+ it 'transforms canvas math content when conversion is enabled' do
36
+ expect(loaded_class.item_body).to include '\(sample equation\)'
37
+ end
38
+
39
+ it 'does not transform math content when conversion is Disabled' do
40
+ Qti.configure do |config|
41
+ config.extract_latex_from_image_tags = false
42
+ end
43
+ expect(loaded_class.item_body).not_to include '"sample equation"'
32
44
  end
33
45
 
34
46
  describe '#points_possible' do
@@ -44,9 +44,9 @@ describe Qti::V1::Models::Interactions::CanvasMultipleDropdownInteraction do
44
44
  let(:expected_stem_items) do
45
45
  [
46
46
  { id: 'stem_0', position: 1, type: 'text', value: '<div><p>Roses are ' },
47
- { id: 'stem_1', position: 2, type: 'blank', blank_id: 'response_color1' },
47
+ { id: 'stem_1', position: 2, type: 'blank', blank_id: 'response_color1', blank_name: 'color1' },
48
48
  { id: 'stem_2', position: 3, type: 'text', value: ', violets are ' },
49
- { id: 'stem_3', position: 4, type: 'blank', blank_id: 'response_color2' },
49
+ { id: 'stem_3', position: 4, type: 'blank', blank_id: 'response_color2', blank_name: 'color2' },
50
50
  { id: 'stem_4', position: 5, type: 'text', value: '.</p></div>' }
51
51
  ]
52
52
  end
@@ -47,7 +47,7 @@ describe Qti::V1::Models::Interactions::FillBlankInteraction do
47
47
  let(:expected_stem_items) do
48
48
  [
49
49
  { id: 'stem_0', position: 1, type: 'text', value: '<div><p>Chicago is in what state?</p></div>' },
50
- { id: 'stem_1', position: 2, type: 'blank', blank_id: 'response1' }
50
+ { id: 'stem_1', position: 2, type: 'blank', blank_id: 'response1', blank_name: 'response1' }
51
51
  ]
52
52
  end
53
53
 
@@ -72,9 +72,9 @@ describe Qti::V1::Models::Interactions::FillBlankInteraction do
72
72
  let(:expected_stem_items) do
73
73
  [
74
74
  { id: 'stem_0', position: 1, type: 'text', value: '<div><p><span>Roses are ' },
75
- { id: 'stem_1', position: 2, type: 'blank', blank_id: 'response_color1' },
75
+ { id: 'stem_1', position: 2, type: 'blank', blank_id: 'response_color1', blank_name: 'color1' },
76
76
  { id: 'stem_2', position: 3, type: 'text', value: ', violets are ' },
77
- { id: 'stem_3', position: 4, type: 'blank', blank_id: 'response_color2' },
77
+ { id: 'stem_3', position: 4, type: 'blank', blank_id: 'response_color2', blank_name: 'color2' },
78
78
  { id: 'stem_4', position: 5, type: 'text', value: '</span></p></div>' }
79
79
  ]
80
80
  end
@@ -102,11 +102,11 @@ describe Qti::V1::Models::Interactions::FillBlankInteraction do
102
102
  [
103
103
  { id: 'stem_0', position: 1, type: 'text', value: 'Fill-in-the blanks in this text from Richard III: ' },
104
104
  { id: 'stem_1', position: 2, type: 'text', value: 'Now is the ' },
105
- { id: 'stem_2', position: 3, type: 'blank', blank_id: 'FIB01' },
105
+ { id: 'stem_2', position: 3, type: 'blank', blank_id: 'FIB01', blank_name: 'FIB01' },
106
106
  { id: 'stem_3', position: 4, type: 'text', value: ' of our discontent made glorious ' },
107
- { id: 'stem_4', position: 5, type: 'blank', blank_id: 'FIB02' },
107
+ { id: 'stem_4', position: 5, type: 'blank', blank_id: 'FIB02', blank_name: 'FIB02' },
108
108
  { id: 'stem_5', position: 6, type: 'text', value: ' by these sons of ' },
109
- { id: 'stem_6', position: 7, type: 'blank', blank_id: 'FIB03' }
109
+ { id: 'stem_6', position: 7, type: 'blank', blank_id: 'FIB03', blank_name: 'FIB03' }
110
110
  ]
111
111
  end
112
112
 
@@ -165,11 +165,11 @@ describe Qti::V1::Models::Interactions::FillBlankInteraction do
165
165
  [
166
166
  { id: 'stem_0', position: 1, type: 'text', value: 'Fill-in-the blanks in this text from Richard III: ' },
167
167
  { id: 'stem_1', position: 2, type: 'text', value: 'Now is the ' },
168
- { id: 'stem_2', position: 3, type: 'blank', blank_id: 'FIB01' },
168
+ { id: 'stem_2', position: 3, type: 'blank', blank_id: 'FIB01', blank_name: 'FIB01' },
169
169
  { id: 'stem_3', position: 4, type: 'text', value: ' of our discontent made glorious ' },
170
- { id: 'stem_4', position: 5, type: 'blank', blank_id: 'FIB02' },
170
+ { id: 'stem_4', position: 5, type: 'blank', blank_id: 'FIB02', blank_name: 'FIB02' },
171
171
  { id: 'stem_5', position: 6, type: 'text', value: ' by these sons of ' },
172
- { id: 'stem_6', position: 7, type: 'blank', blank_id: 'FIB03' }
172
+ { id: 'stem_6', position: 7, type: 'blank', blank_id: 'FIB03', blank_name: 'FIB03' }
173
173
  ]
174
174
  end
175
175
 
@@ -195,7 +195,7 @@ describe Qti::V1::Models::Interactions::FillBlankInteraction do
195
195
  let(:expected_stem_items) do
196
196
  [
197
197
  { id: 'stem_0', position: 1, type: 'text', value: '<div><p>Bird, bird, bird, bird is the ' },
198
- { id: 'stem_1', position: 2, type: 'blank', blank_id: 'response_word' },
198
+ { id: 'stem_1', position: 2, type: 'blank', blank_id: 'response_word', blank_name: 'word' },
199
199
  { id: 'stem_2', position: 3, type: 'text', value: '</p></div>' }
200
200
  ]
201
201
  end
@@ -13,6 +13,10 @@ describe Qti::V1::Models::ObjectBank do
13
13
  <fieldlabel>bank_type</fieldlabel>
14
14
  <fieldentry>account</fieldentry>
15
15
  </qtimetadatafield>
16
+ <qtimetadatafield>
17
+ <fieldlabel>bank_context_uuid</fieldlabel>
18
+ <fieldentry>oAORQgMEvQquzZyKIW6Usg6CFveihQH5pOqHadsb</fieldentry>
19
+ </qtimetadatafield>
16
20
  </qtimetadata>
17
21
  </objectbank>
18
22
  </questestinterop>
@@ -60,4 +64,12 @@ describe Qti::V1::Models::ObjectBank do
60
64
  expect(objectbank.bank_type).to eq 'account'
61
65
  end
62
66
  end
67
+
68
+ describe '#bank_context_uuid' do
69
+ it 'has the bank_context_uuid attribute' do
70
+ allow(File).to receive(:read).and_return(doc)
71
+ objectbank = described_class.new path: '/etc/FakeBank008.xml'
72
+ expect(objectbank.bank_context_uuid).to eq 'oAORQgMEvQquzZyKIW6Usg6CFveihQH5pOqHadsb'
73
+ end
74
+ end
63
75
  end
data/spec/spec_helper.rb CHANGED
@@ -16,6 +16,10 @@ end
16
16
  $LOAD_PATH.unshift File.expand_path('../lib', __dir__)
17
17
  require 'qti'
18
18
 
19
+ Qti.configure do |config|
20
+ config.extract_latex_from_image_tags = true
21
+ end
22
+
19
23
  Dir['./spec/support/**/*.rb'].sort.each { |f| require f }
20
24
 
21
25
  RSpec.configure do |config|
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: qti
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.7.1
4
+ version: 2.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hannah Bottalla
8
8
  - Robinson Rodríguez
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2021-12-01 00:00:00.000000000 Z
12
+ date: 2022-03-30 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: actionview
@@ -275,7 +275,7 @@ dependencies:
275
275
  - - "~>"
276
276
  - !ruby/object:Gem::Version
277
277
  version: '1.4'
278
- description:
278
+ description:
279
279
  email:
280
280
  - hannah@instructure.com
281
281
  - rrodriguez-bd@instructure.com
@@ -684,7 +684,7 @@ homepage: https://github.com/instructure/qti
684
684
  licenses:
685
685
  - MIT
686
686
  metadata: {}
687
- post_install_message:
687
+ post_install_message:
688
688
  rdoc_options: []
689
689
  require_paths:
690
690
  - lib
@@ -699,8 +699,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
699
699
  - !ruby/object:Gem::Version
700
700
  version: '0'
701
701
  requirements: []
702
- rubygems_version: 3.1.4
703
- signing_key:
702
+ rubygems_version: 3.0.3
703
+ signing_key:
704
704
  specification_version: 4
705
705
  summary: QTI 1.2 and 2.1 import and export models
706
706
  test_files: