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 +4 -4
- data/lib/qti/sanitizer.rb +2 -2
- data/lib/qti/v1/models/assessment_item.rb +21 -0
- data/lib/qti/v1/models/stimulus_item.rb +36 -0
- data/lib/qti/v2/models/assessment_item.rb +4 -0
- data/lib/qti/v2/models/stimulus_item.rb +12 -0
- data/lib/qti/version.rb +1 -1
- data/spec/fixtures/items_1.2/stimulus.xml +44 -0
- data/spec/fixtures/items_1.2/stimulus_with_child_item.xml +110 -0
- data/spec/lib/qti/sanitizer_spec.rb +9 -0
- data/spec/lib/qti/v1/models/assessment_item_spec.rb +22 -0
- data/spec/lib/qti/v1/models/stimulus_item_spec.rb +32 -0
- data/spec/lib/qti/v2/models/assessment_item_spec.rb +4 -0
- metadata +7 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8cf47da54509068528926ad9f4ef3275d70206130a19e13cd5f28f113a5f0f8c
|
4
|
+
data.tar.gz: 9b61ac2909cbf749e759c85f83fc340c51342caddceece2f806b3316f661845c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
data/lib/qti/version.rb
CHANGED
@@ -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"><p>stimulus content</p></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"><p>some content</p></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"><p>the answer is letter A</p></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"><p>A</p></mattext>
|
76
|
+
</material>
|
77
|
+
</response_label>
|
78
|
+
<response_label ident="ada66409-0d31-4e5f-b67b-8e33f66c8299">
|
79
|
+
<material>
|
80
|
+
<mattext texttype="text/html"><p>B</p></mattext>
|
81
|
+
</material>
|
82
|
+
</response_label>
|
83
|
+
<response_label ident="3b6679be-071a-46ca-a79f-6e019cd65af7">
|
84
|
+
<material>
|
85
|
+
<mattext texttype="text/html"><p>C</p></mattext>
|
86
|
+
</material>
|
87
|
+
</response_label>
|
88
|
+
<response_label ident="3a4658ed-c0ad-40cd-b8d8-627f87f9bcf0">
|
89
|
+
<material>
|
90
|
+
<mattext texttype="text/html"><p>D</p></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.
|
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-
|
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.
|
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
|