qti 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +28 -0
- data/Rakefile +1 -0
- data/lib/qti.rb +62 -0
- data/lib/qti/models/base.rb +80 -0
- data/lib/qti/models/manifest.rb +20 -0
- data/lib/qti/v1/models/assessment.rb +17 -0
- data/lib/qti/v1/models/assessment_item.rb +59 -0
- data/lib/qti/v1/models/base.rb +13 -0
- data/lib/qti/v1/models/choices/logical_identifier_choice.rb +26 -0
- data/lib/qti/v1/models/interactions/logical_identifier_interaction.rb +32 -0
- data/lib/qti/v2/models/assessment_item.rb +62 -0
- data/lib/qti/v2/models/assessment_test.rb +32 -0
- data/lib/qti/v2/models/base.rb +70 -0
- data/lib/qti/v2/models/choices/simple_choice.rb +28 -0
- data/lib/qti/v2/models/interactions.rb +25 -0
- data/lib/qti/v2/models/interactions/choice_interaction.rb +50 -0
- data/spec/fixtures/items_1.2/multiple_answer.xml +105 -0
- data/spec/fixtures/items_1.2/multiple_choice.xml +83 -0
- data/spec/fixtures/items_1.2/true_false.xml +50 -0
- data/spec/fixtures/items_2.1/Example01-modalFeedback.xml +79 -0
- data/spec/fixtures/items_2.1/Example02-feedbackInline.xml +67 -0
- data/spec/fixtures/items_2.1/Example03-feedbackBlock-solution-random.xml +223 -0
- data/spec/fixtures/items_2.1/Example03-feedbackBlock-solution.xml +238 -0
- data/spec/fixtures/items_2.1/Example04-feedbackBlock-templateBlock.xml +582 -0
- data/spec/fixtures/items_2.1/Example05-feedbackBlock-adaptive.xml +397 -0
- data/spec/fixtures/items_2.1/adaptive.xml +286 -0
- data/spec/fixtures/items_2.1/adaptive_template.xml +350 -0
- data/spec/fixtures/items_2.1/associate.xml +33 -0
- data/spec/fixtures/items_2.1/audio-video.xml +46 -0
- data/spec/fixtures/items_2.1/choice.xml +31 -0
- data/spec/fixtures/items_2.1/choice_aria.xml +42 -0
- data/spec/fixtures/items_2.1/choice_fixed.xml +33 -0
- data/spec/fixtures/items_2.1/choice_multiple.xml +31 -0
- data/spec/fixtures/items_2.1/choice_multiple_chocolade.xml +74 -0
- data/spec/fixtures/items_2.1/choice_multiple_rtl.xml +33 -0
- data/spec/fixtures/items_2.1/choice_ruby.xml +39 -0
- data/spec/fixtures/items_2.1/choice_t_f.xml +30 -0
- data/spec/fixtures/items_2.1/choice_true_false.xml +30 -0
- data/spec/fixtures/items_2.1/data-attributes.xml +61 -0
- data/spec/fixtures/items_2.1/drawing.xml +16 -0
- data/spec/fixtures/items_2.1/essay.xml +14 -0
- data/spec/fixtures/items_2.1/extended_text.xml +24 -0
- data/spec/fixtures/items_2.1/extended_text_rubric.xml +36 -0
- data/spec/fixtures/items_2.1/feedback_adaptive.xml +142 -0
- data/spec/fixtures/items_2.1/figures.xml +32 -0
- data/spec/fixtures/items_2.1/gap_match.xml +33 -0
- data/spec/fixtures/items_2.1/graphic_associate.xml +36 -0
- data/spec/fixtures/items_2.1/graphic_gap_match.xml +54 -0
- data/spec/fixtures/items_2.1/graphic_gap_match_text.xml +50 -0
- data/spec/fixtures/items_2.1/graphic_order.xml +32 -0
- data/spec/fixtures/items_2.1/hint.xml +85 -0
- data/spec/fixtures/items_2.1/hotspot.xml +26 -0
- data/spec/fixtures/items_2.1/hottext.xml +33 -0
- data/spec/fixtures/items_2.1/images/CBG.png +0 -0
- data/spec/fixtures/items_2.1/images/EBG.png +0 -0
- data/spec/fixtures/items_2.1/images/EDI.png +0 -0
- data/spec/fixtures/items_2.1/images/GLA.png +0 -0
- data/spec/fixtures/items_2.1/images/MAN.png +0 -0
- data/spec/fixtures/items_2.1/images/MCH.png +0 -0
- data/spec/fixtures/items_2.1/images/airport.png +0 -0
- data/spec/fixtures/items_2.1/images/blueStar.png +0 -0
- data/spec/fixtures/items_2.1/images/blue_door.png +0 -0
- data/spec/fixtures/items_2.1/images/bus.png +0 -0
- data/spec/fixtures/items_2.1/images/castle.png +0 -0
- data/spec/fixtures/items_2.1/images/greenTriangle.png +0 -0
- data/spec/fixtures/items_2.1/images/green_door.png +0 -0
- data/spec/fixtures/items_2.1/images/house.png +0 -0
- data/spec/fixtures/items_2.1/images/open_car.png +0 -0
- data/spec/fixtures/items_2.1/images/open_goat.png +0 -0
- data/spec/fixtures/items_2.1/images/plane.png +0 -0
- data/spec/fixtures/items_2.1/images/postcard.eps +3581 -0
- data/spec/fixtures/items_2.1/images/postcard.png +0 -0
- data/spec/fixtures/items_2.1/images/rectangle.svg +1 -0
- data/spec/fixtures/items_2.1/images/redCircle.png +0 -0
- data/spec/fixtures/items_2.1/images/red_door.png +0 -0
- data/spec/fixtures/items_2.1/images/sign.png +0 -0
- data/spec/fixtures/items_2.1/images/survey.jpg +0 -0
- data/spec/fixtures/items_2.1/images/train.png +0 -0
- data/spec/fixtures/items_2.1/images/uk.png +0 -0
- data/spec/fixtures/items_2.1/images/ukair.png +0 -0
- data/spec/fixtures/items_2.1/images/ukairtags.png +0 -0
- data/spec/fixtures/items_2.1/imsmanifest.xml +227 -0
- data/spec/fixtures/items_2.1/inline_choice.xml +26 -0
- data/spec/fixtures/items_2.1/inline_choice_math.xml +68 -0
- data/spec/fixtures/items_2.1/likert.xml +21 -0
- data/spec/fixtures/items_2.1/match.xml +41 -0
- data/spec/fixtures/items_2.1/math.xml +35 -0
- data/spec/fixtures/items_2.1/mc_calc3.xml +164 -0
- data/spec/fixtures/items_2.1/mc_calc5.xml +211 -0
- data/spec/fixtures/items_2.1/mc_stat2.xml +219 -0
- data/spec/fixtures/items_2.1/media_coords.xml +33 -0
- data/spec/fixtures/items_2.1/multi-input.xml +271 -0
- data/spec/fixtures/items_2.1/nested_object.xml +26 -0
- data/spec/fixtures/items_2.1/order.xml +25 -0
- data/spec/fixtures/items_2.1/order_partial_scoring.xml +55 -0
- data/spec/fixtures/items_2.1/order_rtl.xml +27 -0
- data/spec/fixtures/items_2.1/orkney1.xml +34 -0
- data/spec/fixtures/items_2.1/orkney2.xml +34 -0
- data/spec/fixtures/items_2.1/position_object.xml +34 -0
- data/spec/fixtures/items_2.1/select_point.xml +23 -0
- data/spec/fixtures/items_2.1/shared/orkney.css +1 -0
- data/spec/fixtures/items_2.1/shared/orkney.html +20 -0
- data/spec/fixtures/items_2.1/slider.xml +35 -0
- data/spec/fixtures/items_2.1/svg.xml +28 -0
- data/spec/fixtures/items_2.1/template.xml +88 -0
- data/spec/fixtures/items_2.1/template_image.xml +74 -0
- data/spec/fixtures/items_2.1/text_entry.xml +27 -0
- data/spec/fixtures/items_2.1/upload.xml +18 -0
- data/spec/fixtures/items_2.1/upload_composite.xml +24 -0
- data/spec/fixtures/test_qti_1.2.zip +0 -0
- data/spec/fixtures/test_qti_1.2/contact info.png +0 -0
- data/spec/fixtures/test_qti_1.2/exam1/my_files/org0/images/image.png +0 -0
- data/spec/fixtures/test_qti_1.2/exam1/my_files/org1/images/image.png +0 -0
- data/spec/fixtures/test_qti_1.2/image.png +0 -0
- data/spec/fixtures/test_qti_1.2/imsmanifest.xml +37 -0
- data/spec/fixtures/test_qti_1.2/quiz.xml +216 -0
- data/spec/fixtures/test_qti_2.1.zip +0 -0
- data/spec/fixtures/test_qti_2.1/assessment.xml +77 -0
- data/spec/fixtures/test_qti_2.1/imsmanifest.xml +211 -0
- data/spec/fixtures/test_qti_2.1/mathassess.xsd +54 -0
- data/spec/fixtures/test_qti_2.1/multiple-answer/multiple-answer.xml +31 -0
- data/spec/fixtures/test_qti_2.1/multiple-choice/multiple-choice.xml +31 -0
- data/spec/fixtures/test_qti_2.1/text-entry/text-entry.xml +330 -0
- data/spec/fixtures/test_qti_2.1/true-false/true-false.xml +55 -0
- data/spec/fixtures/tests/feedbackTest/assessment.xml +252 -0
- data/spec/fixtures/tests/feedbackTest/id-38d47747fb30/Example02-feedbackInline.xml +51 -0
- data/spec/fixtures/tests/feedbackTest/id-5e216df10030/Example03-feedbackBlock-solution.xml +199 -0
- data/spec/fixtures/tests/feedbackTest/id-9c33d141576e/Example05-feedbackBlock-adaptive.xml +330 -0
- data/spec/fixtures/tests/feedbackTest/id-aaa0bbf5da1d/Example03-feedbackBlock-solution-random.xml +223 -0
- data/spec/fixtures/tests/feedbackTest/id-d599169403d2/Example04-feedbackBlock-templateBlock.xml +534 -0
- data/spec/fixtures/tests/feedbackTest/id-fa3ee5852723/Example01-modalFeedback.xml +55 -0
- data/spec/fixtures/tests/feedbackTest/imsmanifest.xml +239 -0
- data/spec/fixtures/tests/interactionmix_saxony_v3/Choicemultiple_2014410822.xml +110 -0
- data/spec/fixtures/tests/interactionmix_saxony_v3/Choicemultiple_871212949.xml +208 -0
- data/spec/fixtures/tests/interactionmix_saxony_v3/Choicesingle_853928446.xml +159 -0
- data/spec/fixtures/tests/interactionmix_saxony_v3/Choicetruefalse_176040516.xml +98 -0
- data/spec/fixtures/tests/interactionmix_saxony_v3/Hotspot_278940407.xml +109 -0
- data/spec/fixtures/tests/interactionmix_saxony_v3/Hottext_801974120.xml +106 -0
- data/spec/fixtures/tests/interactionmix_saxony_v3/InteractionMixSachsen_1901710679.xml +54 -0
- data/spec/fixtures/tests/interactionmix_saxony_v3/Matchmultiple_1038910213.xml +139 -0
- data/spec/fixtures/tests/interactionmix_saxony_v3/Matchsingle_143114773.xml +133 -0
- data/spec/fixtures/tests/interactionmix_saxony_v3/Order_913967682.xml +117 -0
- data/spec/fixtures/tests/interactionmix_saxony_v3/TextEntry_883368511.xml +114 -0
- data/spec/fixtures/tests/interactionmix_saxony_v3/TextEntrynumeric_2040297025.xml +154 -0
- data/spec/fixtures/tests/interactionmix_saxony_v3/TextEntrynumeric_770468849.xml +124 -0
- data/spec/fixtures/tests/interactionmix_saxony_v3/TextEntrysubset_806481421.xml +227 -0
- data/spec/fixtures/tests/interactionmix_saxony_v3/imsmanifest.xml +278 -0
- data/spec/fixtures/tests/interactionmix_saxony_v3/media/03c5e2f774db49598deb3ac7d05b53f9.jpg +0 -0
- data/spec/fixtures/tests/interactionmix_saxony_v3/media/0563e67fb1bb48ad882e0a5abf2a2686.jpg +0 -0
- data/spec/fixtures/tests/interactionmix_saxony_v3/media/2f44366716474a5d8e2a2e32346cbc05.jpg +0 -0
- data/spec/fixtures/tests/interactionmix_saxony_v3/media/71a299b50b5e4b1492f8ee51ce25be7d.jpg +0 -0
- data/spec/fixtures/tests/interactionmix_saxony_v3/media/a439b702d5034ca38f6df26a12835db3.jpg +0 -0
- data/spec/fixtures/tests/interactionmix_saxony_v3/media/b660fc9f268c4ef688d8606116c70160.jpg +0 -0
- data/spec/fixtures/tests/interactionmix_saxony_v3/media/c2f541343b9b4bfdba9fc90c7a18403e.jpg +0 -0
- data/spec/fixtures/tests/interactionmix_saxony_v3/media/e18b5a2eb8104d0994a182ee0176a05c.jpg +0 -0
- data/spec/fixtures/tests/interactionmix_saxony_v3/media/f88c5aa2c6724a388fdbadc7133eb757.jpg +0 -0
- data/spec/fixtures/tests/interactionmix_saxony_v3/media/ffe337505ef74334a0cecb33310f99c1.jpg +0 -0
- data/spec/fixtures/tests/tests/complete.xml +33 -0
- data/spec/fixtures/tests/tests/report.xml +70 -0
- data/spec/fixtures/tests/tests/rtest01-fragment.xml +12 -0
- data/spec/fixtures/tests/tests/rtest01-set01.xml +17 -0
- data/spec/fixtures/tests/tests/rtest01-set02.xml +17 -0
- data/spec/fixtures/tests/tests/rtest01-set03.xml +17 -0
- data/spec/fixtures/tests/tests/rtest01.xml +16 -0
- data/spec/fixtures/tests/tests/rtest02.xml +48 -0
- data/spec/fixtures/tests/tests/rtest03.xml +38 -0
- data/spec/fixtures/tests/tests/rtest04.xml +50 -0
- data/spec/fixtures/tests/tests/rtest06.xml +34 -0
- data/spec/fixtures/tests/tests/rtest08.xml +38 -0
- data/spec/fixtures/tests/tests/rtest09.xml +33 -0
- data/spec/fixtures/tests/tests/rtest10.xml +48 -0
- data/spec/fixtures/tests/tests/rtest12.xml +38 -0
- data/spec/fixtures/tests/tests/rtest13.xml +62 -0
- data/spec/fixtures/tests/tests/rtest22.xml +41 -0
- data/spec/fixtures/tests/tests/rtest24.xml +37 -0
- data/spec/fixtures/tests/tests/rtest25.xml +62 -0
- data/spec/fixtures/tests/tests/rtest26.xml +38 -0
- data/spec/fixtures/tests/tests/rtest27.xml +46 -0
- data/spec/qti/models/base_spec.rb +71 -0
- data/spec/qti/models/manifest_spec.rb +19 -0
- data/spec/qti/v1/models/assessment_item_spec.rb +49 -0
- data/spec/qti/v1/models/assessment_spec.rb +18 -0
- data/spec/qti/v1/models/choices/logical_identifier_choice_spec.rb +23 -0
- data/spec/qti/v1/models/interactions/logical_identifier_interaction_spec.rb +20 -0
- data/spec/qti/v2/models/assessment_item_spec.rb +67 -0
- data/spec/qti/v2/models/assessment_test_spec.rb +60 -0
- data/spec/qti/v2/models/choices/simple_choice_spec.rb +22 -0
- data/spec/qti/v2/models/interactions/choice_interaction_spec.rb +27 -0
- data/spec/qti_spec.rb +38 -0
- data/spec/spec_helper.rb +10 -0
- metadata +506 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 393747e20e5dfd4ea82c84d94a453ff255dca004
|
4
|
+
data.tar.gz: 4895db6433e8b5b37775888cffc6b884103637b2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 025acd7b5b163a07ff92daf38373037fe62ae82a6a46aa0101f95d0d924186dcecd467d5a4c585cb4e35b20d94e7e716b00c1070719b60871f3847a23f017506
|
7
|
+
data.tar.gz: 5bc328455e6b63cb3e543d0199e135aee3696b2d66be5994732ba7ae36dccc6211602870887c6c1d7b0f633caa2275238b718d88e56f82a7e8bf2b880faa325a
|
data/README.md
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# Qti Gem
|
2
|
+
|
3
|
+
The Qti gem supports QTI 1.2 and 2.1. It currently supports the following interaction types:
|
4
|
+
|
5
|
+
- True/False
|
6
|
+
- Multiple Choice
|
7
|
+
- Multiple Answer
|
8
|
+
|
9
|
+
### Installation
|
10
|
+
|
11
|
+
```sh
|
12
|
+
$ cd your_project
|
13
|
+
$ bundle install qti
|
14
|
+
```
|
15
|
+
|
16
|
+
### Usage
|
17
|
+
|
18
|
+
`require 'qti'`
|
19
|
+
`@gem = Qti::Importer.new(path_of_quiz)`
|
20
|
+
|
21
|
+
Now you can use the gem to access the manifest, the assessments, and the assessment items and use it in your own code!
|
22
|
+
|
23
|
+
### Development
|
24
|
+
|
25
|
+
Want to contribute? Submit a pull request!
|
26
|
+
|
27
|
+
### To Do
|
28
|
+
- More interaction models!
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/lib/qti.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'find'
|
2
|
+
|
3
|
+
module Qti
|
4
|
+
class Importer
|
5
|
+
def initialize(path)
|
6
|
+
Find.find(path) do |subdir|
|
7
|
+
@path = subdir if subdir =~ /imsmanifest.xml/
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def import_manifest
|
12
|
+
@manifest = Qti::Models::Manifest.from_path!(@path)
|
13
|
+
raise 'Unsupported QTI version' if @manifest.assessment_test_href.nil?
|
14
|
+
@manifest.assessment_test_href
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_object
|
18
|
+
version_agnostic_test_object(import_manifest)
|
19
|
+
end
|
20
|
+
|
21
|
+
def version_agnostic_test_object(assessment_test_file)
|
22
|
+
if @manifest.qti_1_href
|
23
|
+
Qti::V1::Models::Assessment.from_path!(assessment_test_file)
|
24
|
+
else
|
25
|
+
Qti::V2::Models::AssessmentTest.from_path!(assessment_test_file)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def assessment_item_refs
|
30
|
+
@assessment_item_refs ||= begin
|
31
|
+
if @manifest.qti_1_href
|
32
|
+
test_object.assessment_items
|
33
|
+
else
|
34
|
+
test_object.assessment_item_reference_hrefs
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def create_assessment_item(assessment_item_ref)
|
40
|
+
if @manifest.qti_1_href
|
41
|
+
Qti::V1::Models::AssessmentItem.new(assessment_item_ref)
|
42
|
+
else
|
43
|
+
Qti::V2::Models::AssessmentItem.from_path!(assessment_item_ref)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
require 'qti/models/manifest'
|
50
|
+
require 'qti/models/base'
|
51
|
+
|
52
|
+
require 'qti/v1/models/base'
|
53
|
+
require 'qti/v1/models/assessment'
|
54
|
+
require 'qti/v1/models/assessment_item'
|
55
|
+
require 'qti/v1/models/choices/logical_identifier_choice'
|
56
|
+
require 'qti/v1/models/interactions/logical_identifier_interaction'
|
57
|
+
|
58
|
+
require 'qti/v2/models/base'
|
59
|
+
require 'qti/v2/models/choices/simple_choice'
|
60
|
+
require 'qti/v2/models/interactions/choice_interaction'
|
61
|
+
require 'qti/v2/models/assessment_item'
|
62
|
+
require 'qti/v2/models/assessment_test'
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
require 'sanitize'
|
3
|
+
|
4
|
+
module Qti
|
5
|
+
class ParseError < StandardError; end
|
6
|
+
class SpecificationViolation < StandardError; end
|
7
|
+
class UnsupportedSchema < StandardError; end
|
8
|
+
|
9
|
+
module Models
|
10
|
+
class Base
|
11
|
+
ELEMENTS_REMAP = {
|
12
|
+
'prompt' => 'div',
|
13
|
+
'simpleBlock' => 'div',
|
14
|
+
'simpleInline' => 'span',
|
15
|
+
'atomicBlock' => 'div',
|
16
|
+
'atomicInline' => 'span'
|
17
|
+
}.freeze
|
18
|
+
|
19
|
+
def sanitize_content!(html)
|
20
|
+
Sanitize.fragment(html, sanitize_config)
|
21
|
+
end
|
22
|
+
|
23
|
+
def remap_unknown_tags_transformer
|
24
|
+
lambda do |env|
|
25
|
+
node_name = env[:node_name]
|
26
|
+
node = env[:node]
|
27
|
+
|
28
|
+
return if env[:is_whitelisted] || !node.element?
|
29
|
+
return unless ELEMENTS_REMAP.keys.include? node_name
|
30
|
+
|
31
|
+
new_name = ELEMENTS_REMAP[node_name]
|
32
|
+
env[:node].name = new_name
|
33
|
+
env[:node_name] = new_name
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def sanitize_config
|
38
|
+
Sanitize::Config::RELAXED.merge transformers: remap_unknown_tags_transformer
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.from_path!(path)
|
42
|
+
new(path: path)
|
43
|
+
end
|
44
|
+
|
45
|
+
def initialize(path: nil)
|
46
|
+
@path = path
|
47
|
+
@doc = parse_xml(File.read(path))
|
48
|
+
raise ArgumentError unless @doc
|
49
|
+
end
|
50
|
+
|
51
|
+
def xpath_with_single_check(xpath)
|
52
|
+
node_list = @doc.xpath(xpath)
|
53
|
+
raise Qti::ParseError, 'Too many matches' if node_list.count > 1
|
54
|
+
node_list.first
|
55
|
+
end
|
56
|
+
|
57
|
+
def css_with_single_check(css)
|
58
|
+
node_list = @doc.css(css)
|
59
|
+
raise Qti::ParseError, 'Too many matches' if node_list.count > 1
|
60
|
+
node_list.first
|
61
|
+
end
|
62
|
+
|
63
|
+
def parse_xml(xml_string)
|
64
|
+
Nokogiri.XML(xml_string, &:noblanks)
|
65
|
+
end
|
66
|
+
|
67
|
+
def remap_href_path(href, source_path)
|
68
|
+
return href unless href
|
69
|
+
# Attempts to map to a file path relative href if href doesn't exist
|
70
|
+
# Returns original href if that file doesn't exist
|
71
|
+
if File.exist?(href)
|
72
|
+
href
|
73
|
+
else
|
74
|
+
new_path = File.join(File.dirname(source_path), href)
|
75
|
+
File.exist?(new_path) ? new_path : href
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'qti/v1/models/base'
|
2
|
+
|
3
|
+
module Qti
|
4
|
+
module Models
|
5
|
+
class Manifest < Qti::Models::Base
|
6
|
+
def assessment_test_href
|
7
|
+
href = qti_1_href.nil? ? qti_2_href : qti_1_href
|
8
|
+
remap_href_path(href, @path)
|
9
|
+
end
|
10
|
+
|
11
|
+
def qti_1_href
|
12
|
+
xpath_with_single_check("//xmlns:resources/xmlns:resource[@type='imsqti_xmlv1p1']/@href")&.content
|
13
|
+
end
|
14
|
+
|
15
|
+
def qti_2_href
|
16
|
+
xpath_with_single_check("//xmlns:resources/xmlns:resource[@type='imsqti_test_xmlv2p2']/@href")&.content
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'qti/v1/models/base'
|
2
|
+
|
3
|
+
module Qti
|
4
|
+
module V1
|
5
|
+
module Models
|
6
|
+
class Assessment < Qti::V1::Models::Base
|
7
|
+
def title
|
8
|
+
@title ||= xpath_with_single_check('//assessment/@title').content
|
9
|
+
end
|
10
|
+
|
11
|
+
def assessment_items
|
12
|
+
@doc.xpath('//item')
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'qti/v1/models/interactions/logical_identifier_interaction'
|
2
|
+
|
3
|
+
module Qti
|
4
|
+
module V1
|
5
|
+
module Models
|
6
|
+
class AssessmentItem < Qti::V1::Models::Base
|
7
|
+
ScoringData = Struct.new(:values, :rcardinality)
|
8
|
+
|
9
|
+
def initialize(item)
|
10
|
+
@doc = item
|
11
|
+
end
|
12
|
+
|
13
|
+
def item_body
|
14
|
+
@item_body ||= begin
|
15
|
+
node = @doc.dup
|
16
|
+
prompt = node.at_xpath('./presentation/material/mattext').content
|
17
|
+
sanitize_content!(prompt)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def identifier
|
22
|
+
@identifier ||= @doc.attribute('ident').value
|
23
|
+
end
|
24
|
+
|
25
|
+
def title
|
26
|
+
@title ||= @doc.attribute('title').value
|
27
|
+
end
|
28
|
+
|
29
|
+
def points_possible
|
30
|
+
@points_possible ||= begin
|
31
|
+
@doc.at_xpath('.//decvar/@maxvalue')&.value || @doc.at_xpath('.//decvar/@defaultval')&.value
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def rcardinality
|
36
|
+
@rcardinality ||= @doc.at_xpath('.//response_lid/@rcardinality').value
|
37
|
+
end
|
38
|
+
|
39
|
+
def interaction_model
|
40
|
+
@interaction_model ||= V1::Models::Interactions::LogicalIdentifierInteraction.new(@doc)
|
41
|
+
end
|
42
|
+
|
43
|
+
def scoring_data_structs
|
44
|
+
@scoring_data_structs ||= begin
|
45
|
+
rcardinality = @doc.at_xpath('//response_lid/@rcardinality').value
|
46
|
+
|
47
|
+
choice_nodes = @doc.xpath('.//respcondition')
|
48
|
+
value_nodes = []
|
49
|
+
# We need to find an action of Set or Add greater than 0 bc there is no correct answer tag
|
50
|
+
choice_nodes.each do |choice_node|
|
51
|
+
value_nodes << choice_node if choice_node.at_xpath('.//setvar').content.to_f.positive?
|
52
|
+
end
|
53
|
+
value_nodes.map { |value_node| ScoringData.new(value_node.at_xpath('//varequal').content, rcardinality) }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'qti/v1/models/base'
|
2
|
+
|
3
|
+
module Qti
|
4
|
+
module V1
|
5
|
+
module Models
|
6
|
+
module Choices
|
7
|
+
class LogicalIdentifierChoice < Qti::V1::Models::Base
|
8
|
+
def initialize(node)
|
9
|
+
@node = node
|
10
|
+
end
|
11
|
+
|
12
|
+
def identifier
|
13
|
+
@identifier ||= @node.attributes['ident'].value
|
14
|
+
end
|
15
|
+
|
16
|
+
def item_body
|
17
|
+
@item_body ||= begin
|
18
|
+
node = @node.dup
|
19
|
+
node.content.strip.gsub(/\s+/, ' ')
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'qti/v1/models/base'
|
2
|
+
require 'qti/v1/models/choices/logical_identifier_choice'
|
3
|
+
|
4
|
+
module Qti
|
5
|
+
module V1
|
6
|
+
module Models
|
7
|
+
module Interactions
|
8
|
+
class LogicalIdentifierInteraction < Qti::V1::Models::Base
|
9
|
+
def initialize(node)
|
10
|
+
@node = node
|
11
|
+
end
|
12
|
+
|
13
|
+
def shuffled?
|
14
|
+
@node.at_xpath('.//render_choice/@shuffle')&.value == 'Yes'
|
15
|
+
end
|
16
|
+
|
17
|
+
def answers
|
18
|
+
@answers ||= answer_nodes.map do |node|
|
19
|
+
V1::Models::Choices::LogicalIdentifierChoice.new(node)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def answer_nodes
|
26
|
+
@node.xpath('.//response_label')
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'qti/v2/models/base'
|
2
|
+
require 'qti/v2/models/interactions'
|
3
|
+
|
4
|
+
module Qti
|
5
|
+
module V2
|
6
|
+
module Models
|
7
|
+
class AssessmentItem < Qti::V2::Models::Base
|
8
|
+
ScoringData = Struct.new(:values, :type)
|
9
|
+
|
10
|
+
def item_body
|
11
|
+
@item_body ||= begin
|
12
|
+
node = item_body_node.dup
|
13
|
+
# ensure a prompt is carried into the html
|
14
|
+
prompt = node.at_xpath('//xmlns:prompt')
|
15
|
+
|
16
|
+
# Filter undesired interaction nodes out of the list (need to make this a deep traversal)
|
17
|
+
node.children.filter(INTERACTION_ELEMENTS_CSS).map(&:unlink)
|
18
|
+
|
19
|
+
node.add_child(prompt) if prompt&.parent && prompt.parent != node
|
20
|
+
sanitize_content!(node.to_html)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Not used yet
|
25
|
+
def identifier
|
26
|
+
@identifier ||= xpath_with_single_check('//xmlns:assessmentItem/@identifier').content
|
27
|
+
end
|
28
|
+
|
29
|
+
def title
|
30
|
+
@title ||= xpath_with_single_check('//xmlns:assessmentItem/@title').content
|
31
|
+
end
|
32
|
+
|
33
|
+
def points_possible
|
34
|
+
@points_possible ||= begin
|
35
|
+
xpath_with_single_check("//xmlns:outcomeDeclaration[@identifier='SCORE']/@normalMaximum")&.content ||
|
36
|
+
xpath_with_single_check("//xmlns:outcomeDeclaration[@identifier='MAXSCORE']//xmlns:value")&.content
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def interaction_model
|
41
|
+
@interaction_model ||= begin
|
42
|
+
V2::Models::Interactions.interaction_model(item_body_node)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def scoring_data_structs
|
47
|
+
@scoring_data_structs ||= begin
|
48
|
+
type = xpath_with_single_check('//xmlns:responseDeclaration/@baseType').content
|
49
|
+
value_nodes = @doc.xpath('//xmlns:responseDeclaration/xmlns:correctResponse/xmlns:value')
|
50
|
+
value_nodes.map { |value_node| ScoringData.new(value_node.content, type) }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def item_body_node
|
57
|
+
@item_body_node ||= xpath_with_single_check('//xmlns:itemBody')
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'qti/v2/models/base'
|
2
|
+
|
3
|
+
module Qti
|
4
|
+
module V2
|
5
|
+
module Models
|
6
|
+
class AssessmentTest < Qti::V2::Models::Base
|
7
|
+
def title
|
8
|
+
@title ||= xpath_with_single_check('//xmlns:assessmentTest/@title').content
|
9
|
+
end
|
10
|
+
|
11
|
+
def assessment_item_reference_hrefs
|
12
|
+
# Return the xml files we should be parsing
|
13
|
+
@assessment_item_reference_hrefs ||= begin
|
14
|
+
@doc.xpath('//xmlns:assessmentItemRef/@href').map(&:content).map do |href|
|
15
|
+
remap_href_path(href, @path)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Not used yet
|
21
|
+
def test_parts
|
22
|
+
@test_parts ||= @doc.xpath('//xmlns:testPart')
|
23
|
+
end
|
24
|
+
|
25
|
+
# Not used yet
|
26
|
+
def assessment_sections
|
27
|
+
@assessment_sections ||= test_parts.first.xpath('//xmlns:assessmentSection')
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|