qti 0.0.1
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 +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
|