qti 2.14.0 → 2.15.2

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: ce025f977e8afee917a58d479c15bc56fc7320596b1bfb0c28da5e94ad19602a
4
- data.tar.gz: 642c6b5ba04eb180d5ff363d4aec28bdfc1807cb27fc8d8e99ab0d4b78c36ad0
3
+ metadata.gz: 8cf47da54509068528926ad9f4ef3275d70206130a19e13cd5f28f113a5f0f8c
4
+ data.tar.gz: 9b61ac2909cbf749e759c85f83fc340c51342caddceece2f806b3316f661845c
5
5
  SHA512:
6
- metadata.gz: c96870e3a861ce1e635a5a2f09a8be85820528cc703303bdea362468c0107704be4cf87e425ed4f11d892e3db3453450d4058c5d102442e43c5caa71f3b21b4a
7
- data.tar.gz: d0eac68354d0ffeeae4b1098e90bded2907462f0671ec21377de1d0fa596695fb4ae417f3f6770909c23edf108634f050b1452700b0324fd305833aedb536997
6
+ metadata.gz: 23ad71858fb02cc517255ae56613d606c8385a1b057b9fdac99381ac767ef8ca883e65a3d12a1d3e61b12289499addd91edd366b7364778cfe9b8914615475f5
7
+ data.tar.gz: 97b98fc845357d06405d8882140e59c79cd18e58522bfa11411b78649577e808620b693118dd91cfec141371525000a9fbf16a7aa4765309034a9cf116bce594
data/lib/qti/sanitizer.rb CHANGED
@@ -40,7 +40,7 @@ module Qti
40
40
  'object' => MEDIA_ATTR,
41
41
  'embed' => %w[name src type allowfullscreen pluginspage wmode
42
42
  allowscriptaccess width height],
43
- 'iframe' => %w[src width height name align frameborder scrolling sandbox
43
+ 'iframe' => %w[src style width height name align frameborder scrolling sandbox
44
44
  allowfullscreen webkitallowfullscreen mozallowfullscreen
45
45
  allow] + ALL_DATA_ATTR, # TODO: remove explicit allow with domain whitelist account setting
46
46
  'a' => relaxed_config('a', ['target'] + ALL_DATA_ATTR),
@@ -92,7 +92,7 @@ module Qti
92
92
  lambda do |env|
93
93
  return unless FILTER_TAGS.include?(env[:node_name])
94
94
  return if env[:is_whitelisted] || !env[:node].element?
95
- Sanitize.node!(env[:node], CONFIG)
95
+ Sanitize.node!(env[:node], Sanitize::Config.merge(Sanitize::Config::RELAXED, CONFIG))
96
96
  { node_whitelist: [env[:node]] }
97
97
  end
98
98
  end
@@ -63,6 +63,27 @@ module Qti
63
63
  end
64
64
  end
65
65
 
66
+ def parent_stimulus_item_ident_metadata?
67
+ if @doc.at_xpath('.//xmlns:qtimetadata').present?
68
+ qti_metadata_children.children.find do |node|
69
+ node.text == 'parent_stimulus_item_ident'
70
+ end.present?
71
+ else
72
+ false
73
+ end
74
+ end
75
+
76
+ def parent_stimulus_item_ident
77
+ @parent_stimulus_item_ident ||= begin
78
+ if parent_stimulus_item_ident_metadata?
79
+ parent_stimulus_item_ident_label = qti_metadata_children.children.find do |node|
80
+ node.text == 'parent_stimulus_item_ident'
81
+ end
82
+ parent_stimulus_item_ident_label.next.text
83
+ end
84
+ end
85
+ end
86
+
66
87
  def decvar_maxvalue
67
88
  @doc.at_xpath('.//xmlns:decvar/@maxvalue')&.value&.to_i ||
68
89
  @doc.at_xpath('.//xmlns:decvar/@defaultval')&.value&.to_i || 0
@@ -14,6 +14,10 @@ module Qti
14
14
  @title ||= @node.attributes['title']&.value
15
15
  end
16
16
 
17
+ def instructions
18
+ @instructions ||= @node.attributes['instructions']&.value
19
+ end
20
+
17
21
  def body
18
22
  @body ||= begin
19
23
  presentation = @node.at_xpath('.//xmlns:presentation')
@@ -25,6 +29,38 @@ module Qti
25
29
  def stimulus_type
26
30
  'text'
27
31
  end
32
+
33
+ def qti_metadata_children
34
+ @node.at_xpath('.//xmlns:qtimetadata')&.children
35
+ end
36
+
37
+ def source_url_qti_metadata?
38
+ if @node.at_xpath('.//xmlns:qtimetadata').present?
39
+ source_url_label = qti_metadata_children.children.find { |node| node.text == 'source_url' }
40
+ source_url_label.present?
41
+ else
42
+ false
43
+ end
44
+ end
45
+
46
+ def source_url
47
+ @source_url ||= begin
48
+ if source_url_qti_metadata?
49
+ source_url_label = qti_metadata_children.children.find do |node|
50
+ node.text == 'source_url'
51
+ end
52
+ source_url_label&.next&.text
53
+ end
54
+ end
55
+ end
56
+
57
+ def orientation
58
+ @orientation ||= begin
59
+ presentation = @node.at_xpath('.//xmlns:presentation')
60
+ return 'left' if presentation.blank?
61
+ presentation.at_xpath('.//xmlns:material')&.attributes&.[]('orientation')&.value || 'left'
62
+ end
63
+ end
28
64
  end
29
65
  end
30
66
  end
@@ -31,6 +31,10 @@ module Qti
31
31
  end
32
32
  end
33
33
 
34
+ def parent_stimulus_item_ident
35
+ nil
36
+ end
37
+
34
38
  def interaction_model
35
39
  @interaction_model ||= begin
36
40
  V2::Models::Interactions.interaction_model(@doc, self)
@@ -25,6 +25,18 @@ module Qti
25
25
  def stimulus_type
26
26
  'text'
27
27
  end
28
+
29
+ def instructions
30
+ nil
31
+ end
32
+
33
+ def source_url
34
+ nil
35
+ end
36
+
37
+ def orientation
38
+ 'left'
39
+ end
28
40
  end
29
41
  end
30
42
  end
data/lib/qti/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Qti
2
- VERSION = '2.14.0'.freeze
2
+ VERSION = '2.15.2'.freeze
3
3
  end
@@ -0,0 +1,44 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <questestinterop xmlns="http://www.imsglobal.org/xsd/ims_qtiasiv1p2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/ims_qtiasiv1p2 http://www.imsglobal.org/xsd/ims_qtiasiv1p2p1.xsd">
3
+ <assessment ident="i1e5e0d6fba7342e63ae43afe9863070d" title="Quiz.text">
4
+ <qtimetadata>
5
+ <qtimetadatafield>
6
+ <fieldlabel>cc_maxattempts</fieldlabel>
7
+ <fieldentry>1</fieldentry>
8
+ </qtimetadatafield>
9
+ </qtimetadata>
10
+ <section ident="root_section">
11
+ <item ident="d74b47a4abeeed159e030bfecbcecea2" title="some title" instructions="some instructions">
12
+ <itemmetadata>
13
+ <qtimetadata>
14
+ <qtimetadatafield>
15
+ <fieldlabel>question_type</fieldlabel>
16
+ <fieldentry>text_only_question</fieldentry>
17
+ </qtimetadatafield>
18
+ <qtimetadatafield>
19
+ <fieldlabel>points_possible</fieldlabel>
20
+ <fieldentry>0.0</fieldentry>
21
+ </qtimetadatafield>
22
+ <qtimetadatafield>
23
+ <fieldlabel>original_answer_ids</fieldlabel>
24
+ <fieldentry/>
25
+ </qtimetadatafield>
26
+ <qtimetadatafield>
27
+ <fieldlabel>assessment_question_identifierref</fieldlabel>
28
+ <fieldentry>9f47365e112840d80a79a960b1b0b6a7</fieldentry>
29
+ </qtimetadatafield>
30
+ <qtimetadatafield>
31
+ <fieldlabel>source_url</fieldlabel>
32
+ <fieldentry>https://sourceurl.com</fieldentry>
33
+ </qtimetadatafield>
34
+ </qtimetadata>
35
+ </itemmetadata>
36
+ <presentation>
37
+ <material orientation="top">
38
+ <mattext texttype="text/html">&lt;p&gt;stimulus content&lt;/p&gt;</mattext>
39
+ </material>
40
+ </presentation>
41
+ </item>
42
+ </section>
43
+ </assessment>
44
+ </questestinterop>
@@ -0,0 +1,110 @@
1
+ <?xml version="1.0"?>
2
+ <questestinterop xmlns="http://www.imsglobal.org/xsd/ims_qtiasiv1p2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/ims_qtiasiv1p2 http://www.imsglobal.org/xsd/ims_qtiasiv1p2p1.xsd">
3
+ <assessment ident="7f805e79bec484e9075115f05b2042fa" title="nq q with stimulus with attached question">
4
+ <qtimetadata>
5
+ <qtimetadatafield>
6
+ <fieldlabel>cc_maxattempts</fieldlabel>
7
+ <fieldentry>1</fieldentry>
8
+ </qtimetadatafield>
9
+ </qtimetadata>
10
+ <section ident="root_section">
11
+ <item ident="6e2618d0f5e795a363f1eefec748fb68" title="stimulus title" instructions="some instructions">
12
+ <itemmetadata>
13
+ <qtimetadata>
14
+ <qtimetadatafield>
15
+ <fieldlabel>question_type</fieldlabel>
16
+ <fieldentry>text_only_question</fieldentry>
17
+ </qtimetadatafield>
18
+ <qtimetadatafield>
19
+ <fieldlabel>points_possible</fieldlabel>
20
+ <fieldentry>0.0</fieldentry>
21
+ </qtimetadatafield>
22
+ <qtimetadatafield>
23
+ <fieldlabel>original_answer_ids</fieldlabel>
24
+ <fieldentry/>
25
+ </qtimetadatafield>
26
+ <qtimetadatafield>
27
+ <fieldlabel>assessment_question_identifierref</fieldlabel>
28
+ <fieldentry>8efb1df4dc2fb1048a7f349a3165f28d</fieldentry>
29
+ </qtimetadatafield>
30
+ <qtimetadatafield>
31
+ <fieldlabel>source_url</fieldlabel>
32
+ <fieldentry>https://sourceurl.com</fieldentry>
33
+ </qtimetadatafield>
34
+ </qtimetadata>
35
+ </itemmetadata>
36
+ <presentation>
37
+ <material orientation="top">
38
+ <mattext texttype="text/html">&lt;p&gt;some content&lt;/p&gt;</mattext>
39
+ </material>
40
+ </presentation>
41
+ </item>
42
+ <item ident="bace7f0cffb532a21d2de194c68edeac" title="Child multiple choice question">
43
+ <itemmetadata>
44
+ <qtimetadata>
45
+ <qtimetadatafield>
46
+ <fieldlabel>question_type</fieldlabel>
47
+ <fieldentry>multiple_choice_question</fieldentry>
48
+ </qtimetadatafield>
49
+ <qtimetadatafield>
50
+ <fieldlabel>points_possible</fieldlabel>
51
+ <fieldentry>1.0</fieldentry>
52
+ </qtimetadatafield>
53
+ <qtimetadatafield>
54
+ <fieldlabel>original_answer_ids</fieldlabel>
55
+ <fieldentry>210e9a21-bf55-4472-97d7-1ddb973f4cea,ada66409-0d31-4e5f-b67b-8e33f66c8299,3b6679be-071a-46ca-a79f-6e019cd65af7,3a4658ed-c0ad-40cd-b8d8-627f87f9bcf0</fieldentry>
56
+ </qtimetadatafield>
57
+ <qtimetadatafield>
58
+ <fieldlabel>assessment_question_identifierref</fieldlabel>
59
+ <fieldentry>5d040aba22c641e4c3ff7480aed7b1e2</fieldentry>
60
+ </qtimetadatafield>
61
+ <qtimetadatafield>
62
+ <fieldlabel>parent_stimulus_item_ident</fieldlabel>
63
+ <fieldentry>6e2618d0f5e795a363f1eefec748fb68</fieldentry>
64
+ </qtimetadatafield>
65
+ </qtimetadata>
66
+ </itemmetadata>
67
+ <presentation>
68
+ <material>
69
+ <mattext texttype="text/html">&lt;p&gt;the answer is letter A&lt;/p&gt;</mattext>
70
+ </material>
71
+ <response_lid ident="response1" rcardinality="Single">
72
+ <render_choice>
73
+ <response_label ident="210e9a21-bf55-4472-97d7-1ddb973f4cea">
74
+ <material>
75
+ <mattext texttype="text/html">&lt;p&gt;A&lt;/p&gt;</mattext>
76
+ </material>
77
+ </response_label>
78
+ <response_label ident="ada66409-0d31-4e5f-b67b-8e33f66c8299">
79
+ <material>
80
+ <mattext texttype="text/html">&lt;p&gt;B&lt;/p&gt;</mattext>
81
+ </material>
82
+ </response_label>
83
+ <response_label ident="3b6679be-071a-46ca-a79f-6e019cd65af7">
84
+ <material>
85
+ <mattext texttype="text/html">&lt;p&gt;C&lt;/p&gt;</mattext>
86
+ </material>
87
+ </response_label>
88
+ <response_label ident="3a4658ed-c0ad-40cd-b8d8-627f87f9bcf0">
89
+ <material>
90
+ <mattext texttype="text/html">&lt;p&gt;D&lt;/p&gt;</mattext>
91
+ </material>
92
+ </response_label>
93
+ </render_choice>
94
+ </response_lid>
95
+ </presentation>
96
+ <resprocessing>
97
+ <outcomes>
98
+ <decvar maxvalue="100" minvalue="0" varname="SCORE" vartype="Decimal"/>
99
+ </outcomes>
100
+ <respcondition continue="No">
101
+ <conditionvar>
102
+ <varequal respident="response1">210e9a21-bf55-4472-97d7-1ddb973f4cea</varequal>
103
+ </conditionvar>
104
+ <setvar action="Set" varname="SCORE">100</setvar>
105
+ </respcondition>
106
+ </resprocessing>
107
+ </item>
108
+ </section>
109
+ </assessment>
110
+ </questestinterop>
@@ -54,5 +54,14 @@ describe Qti::Sanitizer do
54
54
 
55
55
  expect(sanitizer.clean(html)).to include 'target="_blank"'
56
56
  end
57
+
58
+ it 'allows style attributes on iframe' do
59
+ html = '<iframe style="width: 523px; height: 294px; display: inline-block;"></iframe>'
60
+
61
+ expect(sanitizer.clean(html)).to include 'style'
62
+ expect(sanitizer.clean(html)).to include 'width: 523px;'
63
+ expect(sanitizer.clean(html)).to include 'height: 294px;'
64
+ expect(sanitizer.clean(html)).to include 'display: inline-block;'
65
+ end
57
66
  end
58
67
  end
@@ -93,6 +93,28 @@ describe Qti::V1::Models::AssessmentItem do
93
93
  end
94
94
  end
95
95
 
96
+ describe '#parent_stimulus_item_ident' do
97
+ let(:file_path) { File.join('spec', 'fixtures', 'items_1.2', 'stimulus_with_child_item.xml') }
98
+ let(:test_object) { Qti::V1::Models::Assessment.from_path!(file_path) }
99
+ let(:assessment_item_refs) { test_object.assessment_items }
100
+
101
+ context 'when it does not contain parent_stimulus_item_ident metadata' do
102
+ let(:loaded_class) { described_class.new(assessment_item_refs[0]) }
103
+
104
+ it 'returns nil' do
105
+ expect(loaded_class.parent_stimulus_item_ident).to be_nil
106
+ end
107
+ end
108
+
109
+ context 'when it contains parent_stimulus_item_ident metadata' do
110
+ let(:loaded_class) { described_class.new(assessment_item_refs[1]) }
111
+
112
+ it 'returns the parent_stimulus_item_ident value' do
113
+ expect(loaded_class.parent_stimulus_item_ident).to eq '6e2618d0f5e795a363f1eefec748fb68'
114
+ end
115
+ end
116
+ end
117
+
96
118
  describe '#scoring_data_structs' do
97
119
  it 'grabs the rcardinality and scoring data value' do
98
120
  struct = loaded_class.send(:scoring_data_structs)
@@ -50,4 +50,36 @@ describe Qti::V1::Models::StimulusItem do
50
50
  it 'returns correct stimulus_type' do
51
51
  expect(loaded_class.stimulus_type).to eq('text')
52
52
  end
53
+
54
+ context 'when the stimulus item was generated by New Quizzes' do
55
+ let(:file_path) { File.join('spec', 'fixtures', 'items_1.2', 'stimulus.xml') }
56
+ let(:test_object) { Qti::V1::Models::Assessment.from_path!(file_path) }
57
+ let(:stimulus_item) { test_object.create_stimulus(test_object.assessment_items.first) }
58
+
59
+ {
60
+ source_url: 'https://sourceurl.com',
61
+ instructions: 'some instructions',
62
+ orientation: 'top'
63
+ }.each do |key, value|
64
+ it "returns the correct #{key}" do
65
+ expect(stimulus_item.send(key)).to eq(value)
66
+ end
67
+ end
68
+ end
69
+
70
+ context 'when the stimulus item was not generated by New Quizzes' do
71
+ let(:file_path) { File.join('spec', 'fixtures', 'items_1.2', 'text.no.question.xml') }
72
+ let(:test_object) { Qti::V1::Models::Assessment.from_path!(file_path) }
73
+ let(:stimulus_item) { test_object.create_stimulus(test_object.assessment_items.first) }
74
+
75
+ {
76
+ source_url: nil,
77
+ instructions: nil,
78
+ orientation: 'left'
79
+ }.each do |key, value|
80
+ it "returns the correct #{key}" do
81
+ expect(stimulus_item.send(key)).to eq(value)
82
+ end
83
+ end
84
+ end
53
85
  end
@@ -28,6 +28,10 @@ describe Qti::V2::Models::AssessmentItem do
28
28
  expect(loaded_class.points_possible).to eq nil
29
29
  end
30
30
 
31
+ it 'falls back to nil parent_stimulus_item_ident value' do
32
+ expect(loaded_class.parent_stimulus_item_ident).to eq nil
33
+ end
34
+
31
35
  it 'grabs the type and scoring data value' do
32
36
  struct = loaded_class.send(:scoring_data_structs)
33
37
  expect(struct.first.values).to eq 'ChoiceA'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: qti
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.14.0
4
+ version: 2.15.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adrian Diaz
@@ -12,7 +12,7 @@ authors:
12
12
  autorequire:
13
13
  bindir: bin
14
14
  cert_chain: []
15
- date: 2023-05-11 00:00:00.000000000 Z
15
+ date: 2023-05-31 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: actionview
@@ -417,6 +417,8 @@ files:
417
417
  - spec/fixtures/items_1.2/ordering.xml
418
418
  - spec/fixtures/items_1.2/question_group.xml
419
419
  - spec/fixtures/items_1.2/single_fib.xml
420
+ - spec/fixtures/items_1.2/stimulus.xml
421
+ - spec/fixtures/items_1.2/stimulus_with_child_item.xml
420
422
  - spec/fixtures/items_1.2/text.no.question.xml
421
423
  - spec/fixtures/items_1.2/true_false.xml
422
424
  - spec/fixtures/items_1.2/upload.xml
@@ -726,7 +728,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
726
728
  - !ruby/object:Gem::Version
727
729
  version: '0'
728
730
  requirements: []
729
- rubygems_version: 3.3.7
731
+ rubygems_version: 3.4.10
730
732
  signing_key:
731
733
  specification_version: 4
732
734
  summary: QTI 1.2 and 2.1 import and export models
@@ -775,6 +777,8 @@ test_files:
775
777
  - spec/fixtures/items_1.2/ordering.xml
776
778
  - spec/fixtures/items_1.2/question_group.xml
777
779
  - spec/fixtures/items_1.2/single_fib.xml
780
+ - spec/fixtures/items_1.2/stimulus.xml
781
+ - spec/fixtures/items_1.2/stimulus_with_child_item.xml
778
782
  - spec/fixtures/items_1.2/text.no.question.xml
779
783
  - spec/fixtures/items_1.2/true_false.xml
780
784
  - spec/fixtures/items_1.2/upload.xml