qti 2.9.4 → 2.11.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: e648510b781222e5d0e43ac7ba30ab1d252cf14360b38d28f03ec5231f053b6e
4
- data.tar.gz: fb44120abe93da6c1237a188641d743724ce5db9c0c5a7bffe9853236d423f06
3
+ metadata.gz: 783579cd76121396d891a45730e833ebed9967a7366f75b61c8dca97dd429091
4
+ data.tar.gz: 15193545fbe7b76ae5e66906a1794f19d3b5d175c7719adb8b01b48223ffe1d2
5
5
  SHA512:
6
- metadata.gz: 5d86e4792c5d904a667299bbc6f005105a064a8cf61bff5d5ca87e3d716fc08274a36804dbb1404c2a82bdd6264c039146614595edfad65f151606cb0f527588
7
- data.tar.gz: 3ced8059b3d3fd86ce0604076e2f8b37a2b6820342ac102ef64b47abca8ac048675fec879ee5a60f113c104d4a9538d5150b43a55eedf90c1bf33aa31f807df7
6
+ metadata.gz: e2a783e103895c2f88d98e7e79c8a8024b1ef8726b7d54a03021213ac5745d6c85f0d5cb822e2034e64812e63ac2b8501067ef584a1167521be89401d08bc4b6
7
+ data.tar.gz: 82a518e1b780ebc9ca5177212bde1108e7980efe4f4bee9473bc753a238268b1f9bfd1ba57bf8341d668faa8ed02166e5566dcfdb61215ddd817c6b8e6d34e05
@@ -1,7 +1,17 @@
1
1
  module Qti
2
2
  module ContentPackaging
3
3
  class AssessmentItem < Dry::Struct
4
- constructor_type :schema
4
+ transform_keys(&:to_sym)
5
+
6
+ transform_types do |type|
7
+ if type.default?
8
+ type.constructor do |value|
9
+ value.nil? ? Dry::Types::Undefined : value
10
+ end
11
+ else
12
+ type
13
+ end
14
+ end
5
15
 
6
16
  attribute :identifier, Types::Strict::String
7
17
  attribute :title, Types::Strict::String
@@ -1,8 +1,17 @@
1
1
  module Qti
2
2
  module ContentPackaging
3
3
  class AssessmentTest < Dry::Struct
4
- constructor_type :schema
4
+ transform_keys(&:to_sym)
5
5
 
6
+ transform_types do |type|
7
+ if type.default?
8
+ type.constructor do |value|
9
+ value.nil? ? Dry::Types::Undefined : value
10
+ end
11
+ else
12
+ type
13
+ end
14
+ end
6
15
  attribute :identifier, Types::Strict::String
7
16
  attribute :title, Types::Strict::String
8
17
  attribute :items, Types::Strict::Array.of(ContentPackaging::AssessmentItem)
@@ -1,8 +1,17 @@
1
1
  module Qti
2
2
  module ContentPackaging
3
3
  class ChoiceInteraction < Dry::Struct
4
- constructor_type :schema
4
+ transform_keys(&:to_sym)
5
5
 
6
+ transform_types do |type|
7
+ if type.default?
8
+ type.constructor do |value|
9
+ value.nil? ? Dry::Types::Undefined : value
10
+ end
11
+ else
12
+ type
13
+ end
14
+ end
6
15
  attribute :prompt, Types::Strict::String
7
16
  attribute :shuffle, Types::Strict::Bool.default(false)
8
17
  attribute :maxChoices, Types::Coercible::Integer
@@ -1,8 +1,17 @@
1
1
  module Qti
2
2
  module ContentPackaging
3
3
  class OutcomeDeclaration < Dry::Struct
4
- constructor_type :schema
4
+ transform_keys(&:to_sym)
5
5
 
6
+ transform_types do |type|
7
+ if type.default?
8
+ type.constructor do |value|
9
+ value.nil? ? Dry::Types::Undefined : value
10
+ end
11
+ else
12
+ type
13
+ end
14
+ end
6
15
  attribute :identifier, Types::Strict::String
7
16
  attribute :baseType, Types::Strict::String
8
17
  attribute :cardinality, Types::Strict::String
@@ -1,8 +1,17 @@
1
1
  module Qti
2
2
  module ContentPackaging
3
3
  class SimpleChoice < Dry::Struct
4
- constructor_type :schema
4
+ transform_keys(&:to_sym)
5
5
 
6
+ transform_types do |type|
7
+ if type.default?
8
+ type.constructor do |value|
9
+ value.nil? ? Dry::Types::Undefined : value
10
+ end
11
+ else
12
+ type
13
+ end
14
+ end
6
15
  attribute :body, Types::Strict::String
7
16
  attribute :identifier, Types::Strict::String
8
17
  attribute :fixed, Types::Strict::Bool.default(true)
@@ -1,7 +1,7 @@
1
1
  module Qti
2
2
  module ContentPackaging
3
3
  module Types
4
- include Dry::Types.module
4
+ include Dry.Types()
5
5
  end
6
6
  end
7
7
  end
data/lib/qti/sanitizer.rb CHANGED
@@ -9,26 +9,37 @@ module Qti
9
9
  }.freeze
10
10
 
11
11
  PROTOCOLS = ['http', 'https', :relative].freeze
12
- FILTER_TAGS = %w[iframe object embed].freeze
12
+ FILTER_TAGS = %w[iframe object embed video audio source].freeze
13
+ MEDIA_SRC_ATTR = %w[src data type codebase].freeze
14
+ MEDIA_FMT_ATTR = %w[width height classid].freeze
15
+ MEDIA_ALT_ATTR = %w[title alt allow allowfullscreen].freeze
16
+ MEDIA_EXT_ATTR = %w[data-media-type data-media-id].freeze
17
+ MEDIA_ATTR = [MEDIA_SRC_ATTR, MEDIA_FMT_ATTR, MEDIA_ALT_ATTR, MEDIA_EXT_ATTR].flatten.freeze
13
18
 
14
19
  CONFIG =
15
20
  {
16
- elements: FILTER_TAGS,
21
+ elements: Sanitize::Config::RELAXED[:elements] + FILTER_TAGS,
17
22
  protocols:
18
23
  {
19
24
  'iframe' => { 'src' => PROTOCOLS },
20
25
  'object' => { 'src' => PROTOCOLS, 'data' => PROTOCOLS },
21
- 'embed' => { 'src' => PROTOCOLS }
22
- }.freeze,
26
+ 'embed' => { 'src' => PROTOCOLS },
27
+ 'video' => { 'src' => PROTOCOLS },
28
+ 'audio' => { 'src' => PROTOCOLS },
29
+ 'source' => { 'src' => PROTOCOLS }
30
+ },
23
31
  attributes:
24
32
  {
25
- 'object' => %w[src width height style data type classid codebase],
33
+ 'video' => MEDIA_ATTR,
34
+ 'audio' => MEDIA_ATTR,
35
+ 'source' => MEDIA_ATTR,
36
+ 'object' => MEDIA_ATTR,
26
37
  'embed' => %w[name src type allowfullscreen pluginspage wmode
27
38
  allowscriptaccess width height],
28
39
  'iframe' => %w[src width height name align frameborder scrolling sandbox
29
40
  allowfullscreen webkitallowfullscreen mozallowfullscreen
30
41
  allow] # TODO: remove explicit allow with domain whitelist account setting
31
- }.freeze
42
+ }
32
43
  }.freeze
33
44
 
34
45
  def clean(html)
@@ -86,7 +97,10 @@ module Qti
86
97
  transformers << object_tag_transformer if import_objects
87
98
  transformers << remap_unknown_tags_transformer
88
99
  transformers << method(:convert_canvas_math_images) if Qti.configuration.extract_latex_from_image_tags
89
- Sanitize::Config::RELAXED.merge transformers: transformers
100
+ Sanitize::Config.merge(
101
+ Sanitize::Config.merge(Sanitize::Config::RELAXED, CONFIG),
102
+ transformers: transformers
103
+ )
90
104
  end
91
105
 
92
106
  def remap_href_path(href)
@@ -29,7 +29,7 @@ module Qti
29
29
  def title
30
30
  @title ||= begin
31
31
  QTIV2_TITLE_PATHS.map do |path|
32
- xpath_with_single_check(path)&.content
32
+ @doc.xpath(path).first&.content
33
33
  end.compact.first
34
34
  end || super
35
35
  end
data/lib/qti/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Qti
2
- VERSION = '2.9.4'.freeze
2
+ VERSION = '2.11.0'.freeze
3
3
  end
@@ -0,0 +1,30 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!-- Thie example adapted from the PET Handbook, copyright University of Cambridge ESOL Examinations -->
3
+ <assessmentItem xmlns="http://www.imsglobal.org/xsd/imsqti_v2p2"
4
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5
+ xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqti_v2p2 http://www.imsglobal.org/xsd/qti/qtiv2p2/imsqti_v2p2p2.xsd"
6
+ identifier="choice" title="Unattended Luggage" adaptive="false" timeDependent="false">
7
+ <responseDeclaration identifier="RESPONSE" cardinality="single" baseType="identifier">
8
+ <correctResponse>
9
+ <value>ChoiceA</value>
10
+ </correctResponse>
11
+ </responseDeclaration>
12
+ <outcomeDeclaration identifier="SCORE" cardinality="single" baseType="float">
13
+ <defaultValue>
14
+ <value>0</value>
15
+ </defaultValue>
16
+ </outcomeDeclaration>
17
+ <itemBody>
18
+ <p>Look at the text in the picture.</p>
19
+ <p>
20
+ <img src="images/sign.png" alt="NEVER LEAVE LUGGAGE UNATTENDED"/>
21
+ </p>
22
+ <choiceInteraction responseIdentifier="RESPONSE" shuffle="false" maxChoices="1">
23
+ <prompt>What does it say?</prompt>
24
+ <simpleChoice identifier="ChoiceA">You must stay with your luggage at all times.</simpleChoice>
25
+ <simpleChoice identifier="ChoiceB">Do not let someone else look after your luggage.</simpleChoice>
26
+ <simpleChoice identifier="ChoiceC">Remember your luggage when you leave.</simpleChoice>
27
+ </choiceInteraction>
28
+ </itemBody>
29
+ <responseProcessing template="http://www.imsglobal.org/question/qti_v2p2/rptemplates/match_correct"/>
30
+ </assessmentItem>
@@ -0,0 +1,101 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!--This is a Reload version 1.3 Content Package document-->
3
+ <!--Spawned from the Reload Content Package Generator - http://www.reload.ac.uk-->
4
+ <manifest xmlns="http://www.imsglobal.org/xsd/imscp_v1p1"
5
+ xmlns:imsmd="http://ltsc.ieee.org/xsd/LOM"
6
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
7
+ xmlns:imsqti="http://www.imsglobal.org/xsd/imsqti_metadata_v2p2"
8
+ identifier="MANIFEST-85D76736-6D19-9DC0-7C0B-57C31A9FD390"
9
+ xsi:schemaLocation="http://www.imsglobal.org/xsd/imscp_v1p1 http://www.imsglobal.org/xsd/qti/qtiv2p2/qtiv2p2_imscpv1p2_v1p0.xsd
10
+ http://ltsc.ieee.org/xsd/LOM http://www.imsglobal.org/xsd/imsmd_loose_v1p3p2.xsd
11
+ http://www.imsglobal.org/xsd/imsqti_metadata_v2p2 http://www.imsglobal.org/xsd/qti/qtiv2p2/imsqti_metadata_v2p2.xsd">
12
+ <metadata>
13
+ <schema>QTIv2.2 Package</schema>
14
+ <schemaversion>1.0.0</schemaversion>
15
+ <imsmd:lom>
16
+ <imsmd:general>
17
+ <imsmd:title>
18
+ <imsmd:string>Example Package</imsmd:string>
19
+ </imsmd:title>
20
+ <imsmd:language>en</imsmd:language>
21
+ <imsmd:description>
22
+ <imsmd:string>This is an example Contentpackage containing a
23
+ single QTI v2.2 item</imsmd:string>
24
+ </imsmd:description>
25
+ </imsmd:general>
26
+ <imsmd:lifeCycle>
27
+ <imsmd:version>
28
+ <imsmd:string>2.1</imsmd:string>
29
+ </imsmd:version>
30
+ <imsmd:status>
31
+ <imsmd:source>LOMv1.0</imsmd:source>
32
+ <imsmd:value>Final</imsmd:value>
33
+ </imsmd:status>
34
+ </imsmd:lifeCycle>
35
+ <imsmd:metaMetadata>
36
+ <imsmd:metadataschema>LOMv1.0</imsmd:metadataschema>
37
+ <imsmd:metadataschema>QTIv2.1</imsmd:metadataschema>
38
+ <imsmd:language>en</imsmd:language>
39
+ </imsmd:metaMetadata>
40
+ <imsmd:technical>
41
+ <imsmd:format>text/x-imsqti-item-xml</imsmd:format>
42
+ <imsmd:format>image/png</imsmd:format>
43
+ </imsmd:technical>
44
+ <imsmd:rights>
45
+ <imsmd:description>
46
+ <imsmd:string>(c) 2005, IMS Global Learning Consortium;
47
+ individual questions may have their own copyright statements.</imsmd:string>
48
+ </imsmd:description>
49
+ </imsmd:rights>
50
+ </imsmd:lom>
51
+ </metadata>
52
+ <organizations/>
53
+ <resources>
54
+ <resource identifier="RES-B38DF83F-A291-86DA-4EC3-B2CEBD1515A4" type="imsqti_item_xmlv2p2"
55
+ href="choice.xml">
56
+ <metadata>
57
+ <imsqti:qtiMetadata>
58
+ <imsqti:timeDependent>false</imsqti:timeDependent>
59
+ <imsqti:interactionType>choiceInteraction</imsqti:interactionType>
60
+ <imsqti:feedbackType>none</imsqti:feedbackType>
61
+ <imsqti:solutionAvailable>true</imsqti:solutionAvailable>
62
+ </imsqti:qtiMetadata>
63
+ <imsmd:lom>
64
+ <imsmd:general>
65
+ <imsmd:identifier>
66
+ <imsmd:entry>choice</imsmd:entry>
67
+ </imsmd:identifier>
68
+ <imsmd:title>
69
+ <imsmd:string>Unattended Luggage</imsmd:string>
70
+ </imsmd:title>
71
+ <imsmd:description>
72
+ <imsmd:string>This example illustrates the
73
+ choiceInteraction being used to obtain a single response from the candidate.</imsmd:string>
74
+ </imsmd:description>
75
+ </imsmd:general>
76
+ <imsmd:lifeCycle>
77
+ <imsmd:version>
78
+ <imsmd:string>2.1</imsmd:string>
79
+ </imsmd:version>
80
+ <imsmd:status>
81
+ <imsmd:source>LOMv1.0</imsmd:source>
82
+ <imsmd:value>Final</imsmd:value>
83
+ </imsmd:status>
84
+ </imsmd:lifeCycle>
85
+ <imsmd:technical>
86
+ <imsmd:format>text/x-imsqti-item-xml</imsmd:format>
87
+ <imsmd:format>image/png</imsmd:format>
88
+ </imsmd:technical>
89
+ <imsmd:rights>
90
+ <imsmd:description>
91
+ <imsmd:string>This example has been adapted from the
92
+ PET Handbook, copyright University of Cambridge ESOL Examinations .</imsmd:string>
93
+ </imsmd:description>
94
+ </imsmd:rights>
95
+ </imsmd:lom>
96
+ </metadata>
97
+ <file href="choice.xml"/>
98
+ <file href="images/sign.png"/>
99
+ </resource>
100
+ </resources>
101
+ </manifest>
@@ -0,0 +1,50 @@
1
+ describe Qti::Sanitizer do
2
+ let(:sanitizer) { Qti::Sanitizer.new }
3
+
4
+ describe '#sanitize' do
5
+ it 'keeps media tags' do
6
+ expect(sanitizer.clean('<img>')).to eq('<img>')
7
+ expect(sanitizer.clean('<video>')).to eq('<video></video>')
8
+ expect(sanitizer.clean('<audio>')).to eq('<audio></audio>')
9
+ expect(sanitizer.clean('<object>')).to eq('<object></object>')
10
+ expect(sanitizer.clean('<embed>')).to eq('<embed>')
11
+ end
12
+
13
+ it 'blocks undesirable tags do' do
14
+ expect(sanitizer.clean('<danger>')).to eq('')
15
+ end
16
+
17
+ it 'allows needed media src attributes' do
18
+ html = '<audio src="http://a.url" data="B64" type="media" codebase="???">'
19
+
20
+ expect(sanitizer.clean(html)).to include 'src'
21
+ expect(sanitizer.clean(html)).to include 'data'
22
+ expect(sanitizer.clean(html)).to include 'type'
23
+ expect(sanitizer.clean(html)).to include 'codebase'
24
+ end
25
+
26
+ it 'allows needed media format attributes' do
27
+ html = '<video width="12" height=14 classid="yes">'
28
+
29
+ expect(sanitizer.clean(html)).to include 'width'
30
+ expect(sanitizer.clean(html)).to include 'height'
31
+ expect(sanitizer.clean(html)).to include 'classid'
32
+ end
33
+
34
+ it 'allows needed media extension attributes' do
35
+ html = '<object data-media-type="thing" data-media-id=123456789>'
36
+
37
+ expect(sanitizer.clean(html)).to include 'data-media-type'
38
+ expect(sanitizer.clean(html)).to include 'data-media-id'
39
+ end
40
+
41
+ it 'allows needed media alt attributes' do
42
+ html = '<source title="Title" alt="description" allow="fullscreen" allowfullscreen=1>'
43
+
44
+ expect(sanitizer.clean(html)).to include 'title'
45
+ expect(sanitizer.clean(html)).to include 'alt'
46
+ expect(sanitizer.clean(html)).to include 'allow'
47
+ expect(sanitizer.clean(html)).to include 'allowfullscreen'
48
+ end
49
+ end
50
+ end
@@ -33,6 +33,14 @@ describe Qti::V2::Models::NonAssessmentTest do
33
33
  include_examples 'loading_a_non-assessment'
34
34
  end
35
35
 
36
+ describe 'imsqti_2.2_package' do
37
+ let(:path) { File.join(fixtures_path, 'imsqti_2.2_package', 'imsmanifest.xml') }
38
+ let(:loaded_class) { described_class.from_path!(path) }
39
+ let(:title) { 'Example Package' }
40
+
41
+ include_examples 'loading_a_non-assessment'
42
+ end
43
+
36
44
  # describe '#stimulus_ref' do
37
45
  # it 'should return the stimulus ref if it exists' do
38
46
  # item = loaded_class.assessment_items[1]
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.9.4
4
+ version: 2.11.0
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: 2022-08-25 00:00:00.000000000 Z
15
+ date: 2022-10-19 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: actionview
@@ -54,34 +54,54 @@ dependencies:
54
54
  - - "<"
55
55
  - !ruby/object:Gem::Version
56
56
  version: '6.2'
57
+ - !ruby/object:Gem::Dependency
58
+ name: dry-logic
59
+ requirement: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - "~>"
62
+ - !ruby/object:Gem::Version
63
+ version: 1.2.0
64
+ - - "<"
65
+ - !ruby/object:Gem::Version
66
+ version: '1.3'
67
+ type: :runtime
68
+ prerelease: false
69
+ version_requirements: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - "~>"
72
+ - !ruby/object:Gem::Version
73
+ version: 1.2.0
74
+ - - "<"
75
+ - !ruby/object:Gem::Version
76
+ version: '1.3'
57
77
  - !ruby/object:Gem::Dependency
58
78
  name: dry-struct
59
79
  requirement: !ruby/object:Gem::Requirement
60
80
  requirements:
61
81
  - - "~>"
62
82
  - !ruby/object:Gem::Version
63
- version: 0.4.0
83
+ version: 1.4.0
64
84
  type: :runtime
65
85
  prerelease: false
66
86
  version_requirements: !ruby/object:Gem::Requirement
67
87
  requirements:
68
88
  - - "~>"
69
89
  - !ruby/object:Gem::Version
70
- version: 0.4.0
90
+ version: 1.4.0
71
91
  - !ruby/object:Gem::Dependency
72
92
  name: dry-types
73
93
  requirement: !ruby/object:Gem::Requirement
74
94
  requirements:
75
95
  - - "~>"
76
96
  - !ruby/object:Gem::Version
77
- version: 0.14.0
97
+ version: 1.5.1
78
98
  type: :runtime
79
99
  prerelease: false
80
100
  version_requirements: !ruby/object:Gem::Requirement
81
101
  requirements:
82
102
  - - "~>"
83
103
  - !ruby/object:Gem::Version
84
- version: 0.14.0
104
+ version: 1.5.1
85
105
  - !ruby/object:Gem::Dependency
86
106
  name: mathml2latex
87
107
  requirement: !ruby/object:Gem::Requirement
@@ -366,6 +386,9 @@ files:
366
386
  - spec/fixtures/canvas_cartridge/non_cc_assessments/ib5281f3537a06f58877107a2501a4e2d.xml.qti
367
387
  - spec/fixtures/edge_cases_1.2.xml
368
388
  - spec/fixtures/feedback_quiz_1.2.xml
389
+ - spec/fixtures/imsqti_2.2_package/choice.xml
390
+ - spec/fixtures/imsqti_2.2_package/images/sign.png
391
+ - spec/fixtures/imsqti_2.2_package/imsmanifest.xml
369
392
  - spec/fixtures/interaction_checks_1.2.xml
370
393
  - spec/fixtures/items_1.2/canvas_multiple_dropdown.xml
371
394
  - spec/fixtures/items_1.2/canvas_multiple_dropdowns.xml
@@ -639,6 +662,7 @@ files:
639
662
  - spec/lib/qti/models/manifest_spec.rb
640
663
  - spec/lib/qti/models/metadata_spec.rb
641
664
  - spec/lib/qti/models/resource_spec.rb
665
+ - spec/lib/qti/sanitizer_spec.rb
642
666
  - spec/lib/qti/v1/models/assessment_item_spec.rb
643
667
  - spec/lib/qti/v1/models/assessment_spec.rb
644
668
  - spec/lib/qti/v1/models/choices/fill_blank_choice_spec.rb
@@ -719,6 +743,9 @@ test_files:
719
743
  - spec/fixtures/canvas_cartridge/non_cc_assessments/ib5281f3537a06f58877107a2501a4e2d.xml.qti
720
744
  - spec/fixtures/edge_cases_1.2.xml
721
745
  - spec/fixtures/feedback_quiz_1.2.xml
746
+ - spec/fixtures/imsqti_2.2_package/choice.xml
747
+ - spec/fixtures/imsqti_2.2_package/images/sign.png
748
+ - spec/fixtures/imsqti_2.2_package/imsmanifest.xml
722
749
  - spec/fixtures/interaction_checks_1.2.xml
723
750
  - spec/fixtures/items_1.2/canvas_multiple_dropdown.xml
724
751
  - spec/fixtures/items_1.2/canvas_multiple_dropdowns.xml
@@ -992,6 +1019,7 @@ test_files:
992
1019
  - spec/lib/qti/models/manifest_spec.rb
993
1020
  - spec/lib/qti/models/metadata_spec.rb
994
1021
  - spec/lib/qti/models/resource_spec.rb
1022
+ - spec/lib/qti/sanitizer_spec.rb
995
1023
  - spec/lib/qti/v1/models/assessment_item_spec.rb
996
1024
  - spec/lib/qti/v1/models/assessment_spec.rb
997
1025
  - spec/lib/qti/v1/models/choices/fill_blank_choice_spec.rb