qti 1.0.3 → 1.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/qti.rb +1 -1
- data/lib/qti/models/base.rb +10 -4
- data/lib/qti/models/manifest.rb +9 -87
- data/lib/qti/models/metadata.rb +63 -0
- data/lib/qti/models/resource.rb +115 -0
- data/lib/qti/v1/models/assessment.rb +1 -1
- data/lib/qti/v1/models/assessment_item.rb +4 -1
- data/lib/qti/v2/models/assessment_test.rb +5 -3
- data/lib/qti/v2/models/non_assessment_test.rb +7 -10
- data/lib/qti/version.rb +1 -1
- data/lib/qti/xpath_helpers.rb +20 -0
- data/spec/fixtures/test_qti_2.1_taxonpath/imsmanifest.xml +85 -0
- data/spec/lib/qti/models/metadata_spec.rb +94 -0
- data/spec/lib/qti/models/resource_spec.rb +53 -0
- data/spec/lib/qti/v2/models/assessment_test_spec.rb +2 -2
- data/spec/lib/qti_spec.rb +1 -1
- metadata +12 -9
- data/spec/gemfiles/nokogiri-1.6.gemfile.lock +0 -149
- data/spec/gemfiles/nokogiri-1.8.gemfile.lock +0 -149
- data/spec/gemfiles/rails-5.2.gemfile.lock +0 -222
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 852302a8eb0d02d23199e435c997d027f720c476
|
4
|
+
data.tar.gz: c52b00f23b75fc6b0c0d14ccf1bf3e1309b7016b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0cb5f6a63677f60d18ed90dd6729c061f2a64189168f8ffb7bc8ee93693c938ec9fbfa102c0f1ccda4996c2db97b066d38d01c9aa1c40e4a8001f1f63d6ea114
|
7
|
+
data.tar.gz: 436eeb7b20019d9f95b61df809993f4b2293fbd06598dd8e2f70d2bb29177093080a2da5633dc4f395e92b968ead921abb0c7a9cf969de8b924146811e138b2a
|
data/lib/qti.rb
CHANGED
@@ -27,7 +27,7 @@ module Qti
|
|
27
27
|
def self.manifest(path)
|
28
28
|
mpath = manifest_path(path)
|
29
29
|
package_root = File.dirname(mpath)
|
30
|
-
manifest = Qti::Models::Manifest.from_path!(mpath, package_root)
|
30
|
+
manifest = Qti::Models::Manifest.from_path!(mpath, package_root = package_root)
|
31
31
|
[mpath, package_root, manifest]
|
32
32
|
end
|
33
33
|
|
data/lib/qti/models/base.rb
CHANGED
@@ -10,19 +10,21 @@ module Qti
|
|
10
10
|
|
11
11
|
module Models
|
12
12
|
class Base
|
13
|
-
attr_reader :doc, :path, :package_root
|
13
|
+
attr_reader :doc, :path, :package_root, :resource
|
14
14
|
attr_accessor :manifest
|
15
|
+
delegate :metadata, to: :@resource, allow_nil: true
|
15
16
|
|
16
17
|
def sanitize_content!(html)
|
17
18
|
sanitizer.clean(html)
|
18
19
|
end
|
19
20
|
|
20
|
-
def self.from_path!(path, package_root = nil)
|
21
|
-
new(path: path, package_root: package_root)
|
21
|
+
def self.from_path!(path, package_root = nil, resource = nil)
|
22
|
+
new(path: path, package_root: package_root, resource: resource)
|
22
23
|
end
|
23
24
|
|
24
|
-
def initialize(path:, package_root: nil, html: false)
|
25
|
+
def initialize(path:, package_root: nil, html: false, resource: nil)
|
25
26
|
@path = path
|
27
|
+
@resource = resource
|
26
28
|
self.package_root = package_root || File.dirname(path)
|
27
29
|
@doc = html ? parse_html(File.read(path)) : parse_xml(File.read(path))
|
28
30
|
raise ArgumentError unless @doc
|
@@ -76,6 +78,10 @@ module Qti
|
|
76
78
|
path
|
77
79
|
end
|
78
80
|
|
81
|
+
def raise_unsupported(message = 'Unsupported QTI version')
|
82
|
+
raise Qti::UnsupportedSchema, message
|
83
|
+
end
|
84
|
+
|
79
85
|
protected
|
80
86
|
|
81
87
|
def package_root=(package_root)
|
data/lib/qti/models/manifest.rb
CHANGED
@@ -3,22 +3,13 @@ require 'qti/models/assessment_meta'
|
|
3
3
|
require 'qti/v1/models/assessment'
|
4
4
|
require 'qti/v2/models/assessment_test'
|
5
5
|
require 'qti/v2/models/non_assessment_test'
|
6
|
+
require 'qti/models/resource'
|
6
7
|
|
7
8
|
module Qti
|
8
9
|
module Models
|
9
10
|
class Manifest < Qti::Models::Base
|
10
|
-
|
11
|
-
|
12
|
-
imsqti_xmlv1p2].freeze
|
13
|
-
ASSESSMENT_CLASSES = {
|
14
|
-
'imsqti_xmlv1p2' => Qti::V1::Models::Assessment,
|
15
|
-
'imsqti_test_xmlv2p1' => Qti::V2::Models::AssessmentTest,
|
16
|
-
'imsqti_test_xmlv2p2' => Qti::V2::Models::AssessmentTest
|
17
|
-
}.freeze
|
18
|
-
EMBEDDED_QTI_TYPES = %w[imsqti_item_xmlv2p1
|
19
|
-
imsqti_item_xmlv2p2].freeze
|
20
|
-
EMBEDDED_NON_ASSESSMENT_ID = '@embedded_non_assessment'.freeze
|
21
|
-
|
11
|
+
include Qti::Models::ResourceGroup
|
12
|
+
include Qti::XPathHelpers
|
22
13
|
def assessment_test(resource_id = nil)
|
23
14
|
resource_id ||= assessment_identifiers.first
|
24
15
|
test = assessment_from_identifier(resource_id)
|
@@ -26,31 +17,11 @@ module Qti
|
|
26
17
|
test
|
27
18
|
end
|
28
19
|
|
29
|
-
def raise_unsupported
|
30
|
-
raise Qti::UnsupportedSchema, 'Unsupported QTI version'
|
31
|
-
end
|
32
|
-
|
33
|
-
def assessment_identifiers(embedded_as_assessment = true)
|
34
|
-
id_list = identifier_list('/assessment')
|
35
|
-
return id_list + [EMBEDDED_NON_ASSESSMENT_ID] if embedded_as_assessment && embedded_non_assessment?
|
36
|
-
id_list
|
37
|
-
end
|
38
|
-
|
39
|
-
def question_bank_identifiers
|
40
|
-
identifier_list('/question-bank')
|
41
|
-
end
|
42
|
-
|
43
|
-
def identifier_list(rsc_type)
|
44
|
-
RESOURCE_QTI_TYPES.map do |v|
|
45
|
-
xmlns_resource_list("[#{rtype_predicate(v, rsc_type)}]").map { |r| r[:identifier] }
|
46
|
-
end.flatten
|
47
|
-
end
|
48
|
-
|
49
20
|
private
|
50
21
|
|
51
22
|
def assessment_from_identifier(identifier)
|
52
23
|
return embedded_non_assessment if identifier == EMBEDDED_NON_ASSESSMENT_ID
|
53
|
-
rsc_ver = xpath_with_single_check(
|
24
|
+
rsc_ver = xpath_with_single_check(xpath_resource("[@identifier='#{identifier}']"))&.[](:type)
|
54
25
|
raise_unsupported unless rsc_ver
|
55
26
|
assessment_from(rsc_ver, identifier)
|
56
27
|
end
|
@@ -58,73 +29,24 @@ module Qti
|
|
58
29
|
def assessment_from(version, identifier)
|
59
30
|
builder = ASSESSMENT_CLASSES[version.split('/').first]
|
60
31
|
raise_unsupported unless builder
|
61
|
-
|
62
|
-
assessment = builder.from_path!(
|
63
|
-
|
64
|
-
@package_root
|
65
|
-
)
|
66
|
-
assessment.canvas_meta_data(canvas_meta_data_for(identifier))
|
32
|
+
rsc = resource_for(identifier, version)
|
33
|
+
assessment = builder.from_path!(remap_href_path(asset_resource_for(rsc)), @package_root, rsc)
|
34
|
+
assessment.canvas_meta_data(rsc.canvas_metadata)
|
67
35
|
assessment
|
68
36
|
end
|
69
37
|
|
70
|
-
def asset_resource_for(identifier, qti_type, canvas_ident)
|
71
|
-
asset_resource_for_canvas(canvas_ident) || asset_resource_for_ims(identifier, qti_type)
|
72
|
-
end
|
73
|
-
|
74
|
-
def asset_resource_for_canvas(identifier)
|
75
|
-
canvas_extra_file(identifier, '.xml.qti')
|
76
|
-
end
|
77
|
-
|
78
|
-
def asset_resource_for_ims(identifier, qti_type)
|
79
|
-
base_xpath = "[@identifier='#{identifier}' and starts-with(@type, '#{qti_type}')]"
|
80
|
-
xmlns_resource(base_xpath + '/@href') || xmlns_resource(base_xpath + '/xmlns:file/@href')
|
81
|
-
end
|
82
|
-
|
83
|
-
def dependency_id(identifier)
|
84
|
-
xmlns_resource("[@identifier='#{identifier}']/xmlns:dependency/@identifierref")
|
85
|
-
end
|
86
|
-
|
87
|
-
def canvas_meta_data_for(identifier)
|
88
|
-
meta_file = canvas_extra_file(identifier, 'assessment_meta.xml')
|
89
|
-
return Qti::Models::AssessmentMeta.from_path!(File.join(@package_root, meta_file)) if meta_file
|
90
|
-
end
|
91
|
-
|
92
|
-
def canvas_extra_file(identifier, filename)
|
93
|
-
dep_id = dependency_id(identifier)
|
94
|
-
xmlns_resource(
|
95
|
-
"[@identifier='#{dep_id}']/xmlns:file[#{xpath_endswith('@href', filename)}]/@href"
|
96
|
-
)
|
97
|
-
end
|
98
|
-
|
99
38
|
def xmlns_resource(type)
|
100
|
-
xpath_with_single_check(
|
39
|
+
xpath_with_single_check(xpath_resource(type))
|
101
40
|
end
|
102
41
|
|
103
42
|
def xmlns_resource_list(type)
|
104
|
-
@doc.xpath(
|
43
|
+
@doc.xpath(xpath_resource(type))
|
105
44
|
end
|
106
45
|
|
107
46
|
def xmlns_resource_count(type)
|
108
47
|
xmlns_resource_list(type).count
|
109
48
|
end
|
110
49
|
|
111
|
-
def xpath_xmlns_resource(type = '')
|
112
|
-
"//xmlns:resources/xmlns:resource#{type}"
|
113
|
-
end
|
114
|
-
|
115
|
-
def xpath_endswith(tag, tail)
|
116
|
-
"substring(#{tag}, string-length(#{tag}) - string-length('#{tail}') + 1) = '#{tail}'"
|
117
|
-
end
|
118
|
-
|
119
|
-
def rtype_predicate(ver, rsc_type)
|
120
|
-
# XPath 2.0 supports ends-with, which is what substring is doing here.
|
121
|
-
# It also support regex matching with matches.
|
122
|
-
# We only have XPath 1.0 available.
|
123
|
-
cc_match = "starts-with(@type, '#{ver}') and " + xpath_endswith('@type', rsc_type)
|
124
|
-
qti_match = "@type='#{ver}'"
|
125
|
-
"#{qti_match} or (#{cc_match})"
|
126
|
-
end
|
127
|
-
|
128
50
|
def embedded_non_assessment?
|
129
51
|
EMBEDDED_QTI_TYPES.map { |typ| xmlns_resource_count("[@type='#{typ}']/@href") }.flatten.sum.positive?
|
130
52
|
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Qti
|
2
|
+
module Models
|
3
|
+
class MetaData < Qti::Models::Base
|
4
|
+
def initialize(node)
|
5
|
+
@node = node
|
6
|
+
end
|
7
|
+
|
8
|
+
def taxonpaths
|
9
|
+
return unless lom
|
10
|
+
hier = Hash.new { |h, k| h[k] = [] }
|
11
|
+
lom.xpath('imsmd:classification/imsmd:taxonPath').each do |tp|
|
12
|
+
entry = taxonpath_entry(tp)
|
13
|
+
hier[entry[:source]] = entry[:taxonpath]
|
14
|
+
end
|
15
|
+
hier
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def taxonpath_entry(node)
|
21
|
+
{
|
22
|
+
source: node.xpath('imsmd:source/imsmd:string').text,
|
23
|
+
taxonpath: taxons(node)
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
def metadata
|
28
|
+
@metadata ||= @node&.xpath('xmlns:metadata')&.first
|
29
|
+
end
|
30
|
+
|
31
|
+
def lom
|
32
|
+
return unless imsmd
|
33
|
+
@lom ||= metadata&.xpath('imsmd:lom')&.first
|
34
|
+
end
|
35
|
+
|
36
|
+
def imsmd
|
37
|
+
@node.namespaces&.keys&.include?('xmlns:imsmd')
|
38
|
+
end
|
39
|
+
|
40
|
+
def taxons(node)
|
41
|
+
hier = Hash.new { |h, k| h[k] = [] }
|
42
|
+
taxon(node, hier)
|
43
|
+
end
|
44
|
+
|
45
|
+
def taxon(node, path)
|
46
|
+
return path unless node
|
47
|
+
xpath = 'imsmd:taxon/imsmd:entry/*[self::imsmd:string or self::imsmd:langstring]'
|
48
|
+
node.xpath(xpath).each do |taxon|
|
49
|
+
lang = taxon.attr('language') || taxon.attr('lang') || taxon.attr('xml:lang') || 'default'
|
50
|
+
path[lang].push(taxon.text)
|
51
|
+
end
|
52
|
+
taxon(node.xpath('imsmd:taxon')&.first, path)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
module MetaDataBase
|
57
|
+
delegate :taxonpath, to: :@meta_data, allow_nil: true
|
58
|
+
def metadata_from_node!(node)
|
59
|
+
@meta_data ||= MetaData.new(node)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'qti/v1/models/base'
|
2
|
+
require 'qti/models/assessment_meta'
|
3
|
+
require 'qti/v1/models/assessment'
|
4
|
+
require 'qti/v2/models/assessment_test'
|
5
|
+
require 'qti/v2/models/non_assessment_test'
|
6
|
+
require 'qti/xpath_helpers'
|
7
|
+
require 'qti/models/metadata'
|
8
|
+
|
9
|
+
module Qti
|
10
|
+
module Models
|
11
|
+
class Resource < Qti::Models::Base
|
12
|
+
include Qti::Models::MetaDataBase
|
13
|
+
include Qti::XPathHelpers
|
14
|
+
def initialize(node, parent)
|
15
|
+
@node = node
|
16
|
+
@parent = parent
|
17
|
+
@resource_type = node.attr('type')
|
18
|
+
@identifier = node.attr('identifier')
|
19
|
+
copy_paths_from_item(parent)
|
20
|
+
end
|
21
|
+
|
22
|
+
def href
|
23
|
+
@href ||= @node.attr('href') || @node.xpath('xmlns:file/@href')&.first&.content
|
24
|
+
end
|
25
|
+
|
26
|
+
def metadata
|
27
|
+
@metadata = metadata_from_node!(@node)
|
28
|
+
end
|
29
|
+
|
30
|
+
def canvas_metadata
|
31
|
+
@canvas_meta_file ||= canvas_extra_file('assessment_meta.xml')
|
32
|
+
return unless @canvas_meta_file
|
33
|
+
meta_file = File.join(@package_root, @canvas_meta_file)
|
34
|
+
@canvas_metadata ||= Qti::Models::AssessmentMeta.from_path!(meta_file) if @canvas_meta_file
|
35
|
+
end
|
36
|
+
|
37
|
+
def canvas_extra_file(filename)
|
38
|
+
dep_id = dependency_id
|
39
|
+
rsc = @parent.resource_node(
|
40
|
+
"[@identifier='#{dep_id}']/xmlns:file[#{xpath_endswith('@href', filename)}]/@href"
|
41
|
+
)
|
42
|
+
rsc&.content
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
# Canvas Metadata Helpers
|
48
|
+
def dependency_id
|
49
|
+
@node.xpath('xmlns:dependency/@identifierref')&.first&.content
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
module ResourceGroup
|
54
|
+
RESOURCE_QTI_TYPES = %w[imsqti_test_xmlv2p1
|
55
|
+
imsqti_test_xmlv2p2
|
56
|
+
imsqti_xmlv1p2].freeze
|
57
|
+
ASSESSMENT_CLASSES = {
|
58
|
+
'imsqti_xmlv1p2' => Qti::V1::Models::Assessment,
|
59
|
+
'imsqti_test_xmlv2p1' => Qti::V2::Models::AssessmentTest,
|
60
|
+
'imsqti_test_xmlv2p2' => Qti::V2::Models::AssessmentTest
|
61
|
+
}.freeze
|
62
|
+
EMBEDDED_QTI_TYPES = %w[imsqti_item_xmlv2p1
|
63
|
+
imsqti_item_xmlv2p2].freeze
|
64
|
+
EMBEDDED_NON_ASSESSMENT_ID = '@embedded_non_assessment'.freeze
|
65
|
+
|
66
|
+
def resources(type = '')
|
67
|
+
@doc.xpath(xpath_resource(type))
|
68
|
+
end
|
69
|
+
|
70
|
+
def asset_resource_for(rsc)
|
71
|
+
asset_resource_for_canvas(rsc) || asset_resource_for_ims(rsc)
|
72
|
+
end
|
73
|
+
|
74
|
+
def asset_resource_for_canvas(rsc)
|
75
|
+
rsc.canvas_extra_file('.xml.qti')
|
76
|
+
end
|
77
|
+
|
78
|
+
def asset_resource_for_ims(rsc)
|
79
|
+
rsc.href
|
80
|
+
end
|
81
|
+
|
82
|
+
def identifier_list(rsc_type)
|
83
|
+
RESOURCE_QTI_TYPES.map do |v|
|
84
|
+
xmlns_resource_list("[#{rtype_predicate(v, rsc_type)}]").map { |r| r[:identifier] }
|
85
|
+
end.flatten
|
86
|
+
end
|
87
|
+
|
88
|
+
def resource_for(identifier, qti_type = nil)
|
89
|
+
qti_type = " and starts-with(@type, '#{qti_type}')" if qti_type
|
90
|
+
base_xpath = "[@identifier='#{identifier}'#{qti_type}]"
|
91
|
+
Resource.new(resource_node(base_xpath), self)
|
92
|
+
end
|
93
|
+
|
94
|
+
def assessment_identifiers(embedded_as_assessment = true)
|
95
|
+
id_list = identifier_list('/assessment')
|
96
|
+
return id_list + [EMBEDDED_NON_ASSESSMENT_ID] if embedded_as_assessment && embedded_non_assessment?
|
97
|
+
id_list
|
98
|
+
end
|
99
|
+
|
100
|
+
def question_bank_identifiers
|
101
|
+
identifier_list('/question-bank')
|
102
|
+
end
|
103
|
+
|
104
|
+
def item_resources_v2
|
105
|
+
nodes = resources('[@type="imsqti_item_xmlv2p2"]')
|
106
|
+
return nodes if nodes.count >= 1
|
107
|
+
resources('[@type="imsqti_item_xmlv2p1"]')
|
108
|
+
end
|
109
|
+
|
110
|
+
def resource_node(type)
|
111
|
+
xpath_with_single_check(xpath_resource(type))
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -18,7 +18,7 @@ module Qti
|
|
18
18
|
|
19
19
|
def create_assessment_item(assessment_item)
|
20
20
|
return nil if sub_section?(assessment_item)
|
21
|
-
item = Qti::V1::Models::AssessmentItem.new(assessment_item, @package_root)
|
21
|
+
item = Qti::V1::Models::AssessmentItem.new(assessment_item, @package_root, self)
|
22
22
|
item.manifest = manifest
|
23
23
|
item
|
24
24
|
end
|
@@ -6,10 +6,13 @@ module Qti
|
|
6
6
|
module Models
|
7
7
|
class AssessmentItem < Qti::V1::Models::Base
|
8
8
|
attr_reader :doc
|
9
|
+
attr_reader :resource
|
10
|
+
delegate :metadata, to: :@resource
|
9
11
|
|
10
|
-
def initialize(item, package_root = nil)
|
12
|
+
def initialize(item, package_root = nil, resource = nil)
|
11
13
|
@doc = item
|
12
14
|
@path = item.document.url
|
15
|
+
@resource = resource
|
13
16
|
self.package_root = package_root
|
14
17
|
end
|
15
18
|
|
@@ -1,11 +1,13 @@
|
|
1
1
|
require 'qti/v2/models/base'
|
2
2
|
require 'qti/models/assessment_meta'
|
3
|
+
require 'qti/xpath_helpers'
|
3
4
|
|
4
5
|
module Qti
|
5
6
|
module V2
|
6
7
|
module Models
|
7
8
|
class AssessmentTest < Qti::V2::Models::Base
|
8
9
|
include Qti::Models::AssessmentMetaBase
|
10
|
+
include Qti::XPathHelpers
|
9
11
|
def title
|
10
12
|
@title ||= xpath_with_single_check('//xmlns:assessmentTest/@title')&.content || File.basename(@path, '.xml')
|
11
13
|
end
|
@@ -14,7 +16,7 @@ module Qti
|
|
14
16
|
# Return the xml files we should be parsing
|
15
17
|
@assessment_item_reference_hrefs ||= begin
|
16
18
|
@doc.xpath('//xmlns:assessmentItemRef/@href').map(&:content).map do |href|
|
17
|
-
remap_href_path(href)
|
19
|
+
{ path: remap_href_path(href), resource: self }
|
18
20
|
end
|
19
21
|
end
|
20
22
|
end
|
@@ -27,8 +29,8 @@ module Qti
|
|
27
29
|
@assessment_sections ||= test_parts.first.xpath('//xmlns:assessmentSection')
|
28
30
|
end
|
29
31
|
|
30
|
-
def create_assessment_item(
|
31
|
-
item = Qti::V2::Models::AssessmentItem.from_path!(
|
32
|
+
def create_assessment_item(ref)
|
33
|
+
item = Qti::V2::Models::AssessmentItem.from_path!(ref[:path], @package_root, ref[:resource])
|
32
34
|
item.manifest = manifest
|
33
35
|
item
|
34
36
|
end
|
@@ -1,31 +1,28 @@
|
|
1
1
|
require 'qti/v2/models/base'
|
2
|
+
require 'qti/models/resource'
|
2
3
|
|
3
4
|
module Qti
|
4
5
|
module V2
|
5
6
|
module Models
|
6
7
|
class NonAssessmentTest < Qti::V2::Models::AssessmentTest
|
8
|
+
include Qti::Models::ResourceGroup
|
7
9
|
def assessment_items
|
8
10
|
# Return the xml files we should be parsing
|
9
|
-
@
|
10
|
-
|
11
|
-
|
11
|
+
@assessment_item_resources ||= begin
|
12
|
+
item_resources_v2.map do |node|
|
13
|
+
rsc = Qti::Models::Resource.new(node, self)
|
14
|
+
{ path: remap_href_path(rsc.href), resource: rsc }
|
12
15
|
end
|
13
16
|
end
|
14
17
|
end
|
15
18
|
|
16
19
|
def stimulus_ref(assessment_item_ref)
|
17
|
-
ref = assessment_item_ref.sub(@package_root, '')
|
20
|
+
ref = assessment_item_ref[:path].sub(@package_root, '')
|
18
21
|
dependencies = @doc.xpath("//xmlns:resource[@href='#{ref}']/xmlns:dependency/@identifierref")
|
19
22
|
return unless dependencies&.count == 1
|
20
23
|
href = xpath_with_single_check("//xmlns:resource[@identifier='#{dependencies.first}']/@href")
|
21
24
|
remap_href_path(href)
|
22
25
|
end
|
23
|
-
|
24
|
-
def hrefs
|
25
|
-
nodes = @doc.xpath("//xmlns:resource[@type='imsqti_item_xmlv2p2']/@href")
|
26
|
-
return nodes if nodes.count >= 1
|
27
|
-
@doc.xpath("//xmlns:resource[@type='imsqti_item_xmlv2p1']/@href")
|
28
|
-
end
|
29
26
|
end
|
30
27
|
end
|
31
28
|
end
|