scorm 1.0.0
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.
- data/LICENSE +19 -0
- data/bin/scorm +13 -0
- data/examples/example/README +15 -0
- data/examples/example/adlcp_rootv1p2.xsd +110 -0
- data/examples/example/example.html +10 -0
- data/examples/example/images/large/example.png +0 -0
- data/examples/example/ims_xml.xsd +1 -0
- data/examples/example/imscp_rootv1p1p2.xsd +345 -0
- data/examples/example/imsmanifest.xml +45 -0
- data/examples/example/imsmd_rootv1p2p1.xsd +573 -0
- data/lib/scorm.rb +5 -0
- data/lib/scorm/command.rb +63 -0
- data/lib/scorm/commands/base.rb +53 -0
- data/lib/scorm/commands/bundle.rb +29 -0
- data/lib/scorm/commands/check.rb +45 -0
- data/lib/scorm/commands/create.rb +15 -0
- data/lib/scorm/commands/extract.rb +12 -0
- data/lib/scorm/commands/help.rb +80 -0
- data/lib/scorm/commands/version.rb +7 -0
- data/lib/scorm/datatypes.rb +57 -0
- data/lib/scorm/manifest.rb +154 -0
- data/lib/scorm/metadata.rb +100 -0
- data/lib/scorm/organization.rb +77 -0
- data/lib/scorm/package.rb +210 -0
- data/lib/scorm/resource.rb +49 -0
- data/skeleton/adlcp_rootv1p2.xsd +110 -0
- data/skeleton/ims_xml.xsd +1 -0
- data/skeleton/imscp_rootv1p1p2.xsd +345 -0
- data/skeleton/imsmanifest.xml +45 -0
- data/skeleton/imsmd_rootv1p2p1.xsd +573 -0
- metadata +111 -0
data/lib/scorm.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'scorm/commands/base'
|
2
|
+
|
3
|
+
Dir["#{File.dirname(__FILE__)}/commands/*.rb"].each { |c| require c }
|
4
|
+
|
5
|
+
module Scorm
|
6
|
+
module Command
|
7
|
+
class InvalidCommand < RuntimeError; end
|
8
|
+
class CommandFailed < RuntimeError; end
|
9
|
+
|
10
|
+
class << self
|
11
|
+
|
12
|
+
def error(msg)
|
13
|
+
STDERR.puts(msg)
|
14
|
+
exit 1
|
15
|
+
end
|
16
|
+
|
17
|
+
def run(command, args)
|
18
|
+
begin
|
19
|
+
run_internal(command, args.dup)
|
20
|
+
rescue Zip::ZipError => e
|
21
|
+
error e.message
|
22
|
+
rescue InvalidPackage => e
|
23
|
+
error e.message
|
24
|
+
rescue InvalidManifest => e
|
25
|
+
error e.message
|
26
|
+
rescue InvalidCommand
|
27
|
+
error "Unknown command. Run 'scorm help' for usage information."
|
28
|
+
rescue CommandFailed => e
|
29
|
+
error e.message
|
30
|
+
rescue Interrupt => e
|
31
|
+
error "\n[canceled]"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def run_internal(command, args)
|
36
|
+
klass, method = parse(command)
|
37
|
+
runner = klass.new(args)
|
38
|
+
raise InvalidCommand unless runner.respond_to?(method)
|
39
|
+
runner.send(method)
|
40
|
+
end
|
41
|
+
|
42
|
+
def parse(command)
|
43
|
+
parts = command.split(':')
|
44
|
+
case parts.size
|
45
|
+
when 1
|
46
|
+
begin
|
47
|
+
return eval("Scorm::Command::#{command.capitalize}"), :index
|
48
|
+
rescue NameError, NoMethodError
|
49
|
+
raise InvalidCommand
|
50
|
+
end
|
51
|
+
when 2
|
52
|
+
begin
|
53
|
+
return Scorm::Command.const_get(parts[0].capitalize), parts[1]
|
54
|
+
rescue NameError
|
55
|
+
raise InvalidCommand
|
56
|
+
end
|
57
|
+
else
|
58
|
+
raise InvalidCommand
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module Scorm::Command
|
4
|
+
class Base
|
5
|
+
attr_accessor :args
|
6
|
+
attr_reader :autodetected_package
|
7
|
+
|
8
|
+
def initialize(args)
|
9
|
+
@args = args
|
10
|
+
@autodetected_package = false
|
11
|
+
end
|
12
|
+
|
13
|
+
def display(msg, newline=true)
|
14
|
+
if newline
|
15
|
+
puts(msg)
|
16
|
+
else
|
17
|
+
print(msg)
|
18
|
+
STDOUT.flush
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def error(msg)
|
23
|
+
STDERR.puts(msg)
|
24
|
+
exit 1
|
25
|
+
end
|
26
|
+
|
27
|
+
def extract_package(force=true)
|
28
|
+
package = extract_option('--package', false)
|
29
|
+
raise(CommandFailed, "You must specify a package name after --package") if package == false
|
30
|
+
unless package
|
31
|
+
raise(CommandFailed, "No package specified.\nRun this command from package folder or set it adding --package <package name>") if force
|
32
|
+
@autodetected_package = true
|
33
|
+
end
|
34
|
+
package
|
35
|
+
end
|
36
|
+
|
37
|
+
def extract_option(options, default=true)
|
38
|
+
values = options.is_a?(Array) ? options : [options]
|
39
|
+
return unless opt_index = args.select { |a| values.include? a }.first
|
40
|
+
opt_position = args.index(opt_index) + 1
|
41
|
+
if args.size > opt_position && opt_value = args[opt_position]
|
42
|
+
if opt_value.include?('--')
|
43
|
+
opt_value = nil
|
44
|
+
else
|
45
|
+
args.delete_at(opt_position)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
opt_value ||= default
|
49
|
+
args.delete(opt_index)
|
50
|
+
block_given? ? yield(opt_value) : opt_value
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Scorm::Command
|
2
|
+
class Bundle < Base
|
3
|
+
def index
|
4
|
+
name = args.shift.strip rescue '.'
|
5
|
+
unless File.exist?(File.join(File.expand_path(name), 'imsmanifest.xml'))
|
6
|
+
raise(CommandFailed, "Invalid package, didn't find any imsmanifest.xml file.")
|
7
|
+
end
|
8
|
+
|
9
|
+
outname = File.basename(File.expand_path(name)) + '.zip'
|
10
|
+
|
11
|
+
require 'zip/zip'
|
12
|
+
Zip::ZipFile.open(outname, Zip::ZipFile::CREATE) do |zipfile|
|
13
|
+
Scorm::Package.open(name) do |pkg|
|
14
|
+
Scorm::Manifest::MANIFEST_FILES.each do |file|
|
15
|
+
zipfile.get_output_stream(file) {|f| f.write(pkg.file(file)) }
|
16
|
+
display file
|
17
|
+
end
|
18
|
+
files = pkg.manifest.resources.map {|r| r.files }.flatten.uniq
|
19
|
+
files.each do |file|
|
20
|
+
zipfile.get_output_stream(file) {|f| f.write(pkg.file(file)) }
|
21
|
+
display file
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
display "Created new SCORM package \"#{outname}\"."
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Scorm::Command
|
2
|
+
class Check < Base
|
3
|
+
def index
|
4
|
+
package = args.shift.strip rescue ''
|
5
|
+
raise(CommandFailed, "Invalid package.") if package == ''
|
6
|
+
|
7
|
+
Scorm::Package.open(package, :dry_run => true) do |pkg|
|
8
|
+
display "Checking package \"#{File.basename(package)}\""
|
9
|
+
display ""
|
10
|
+
display "== UUID =="
|
11
|
+
display "Identifier: #{pkg.manifest.identifier}"
|
12
|
+
display ""
|
13
|
+
display "== Manifest =="
|
14
|
+
Scorm::Manifest::MANIFEST_FILES.each do |file|
|
15
|
+
if pkg.exists?(file)
|
16
|
+
display "#{file} -> OK"
|
17
|
+
else
|
18
|
+
display "#{file} -> Missing"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
display ""
|
22
|
+
display "== Organizations =="
|
23
|
+
pkg.manifest.organizations.each do |id, organization|
|
24
|
+
if organization == pkg.manifest.default_organization
|
25
|
+
display "#{organization.title} (default)"
|
26
|
+
else
|
27
|
+
display "#{organization.title}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
display ""
|
31
|
+
display "== Resources =="
|
32
|
+
pkg.manifest.resources.each do |resource|
|
33
|
+
display "#{resource.href} (#{resource.type}, #{resource.scorm_type}):"
|
34
|
+
resource.files.each do |file|
|
35
|
+
if pkg.exists?(file)
|
36
|
+
display " - #{file} -> OK"
|
37
|
+
else
|
38
|
+
display " - #{file} -> Missing"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Scorm::Command
|
2
|
+
class Create < Base
|
3
|
+
def index
|
4
|
+
name = args.shift.strip rescue ''
|
5
|
+
raise(CommandFailed, "Invalid package name.") if name == ''
|
6
|
+
|
7
|
+
FileUtils.mkdir_p(name)
|
8
|
+
Dir.glob(File.join(File.dirname(File.expand_path(__FILE__)), '../../../skeleton/*')).each do |file|
|
9
|
+
FileUtils.cp(file, name)
|
10
|
+
display "#{name}/#{File.basename(file)}"
|
11
|
+
end
|
12
|
+
display "Created new SCORM package \"#{name}\"."
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Scorm::Command
|
2
|
+
class Extract < Base
|
3
|
+
def index
|
4
|
+
package = args.shift.strip rescue ''
|
5
|
+
raise(CommandFailed, "Invalid package.") if package == ''
|
6
|
+
|
7
|
+
Scorm::Package.open(package) do |pkg|
|
8
|
+
display "Extracted package to #{pkg.path}"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Scorm::Command
|
2
|
+
class Help < Base
|
3
|
+
class HelpGroup < Array
|
4
|
+
attr_reader :title
|
5
|
+
|
6
|
+
def initialize(title)
|
7
|
+
@title = title
|
8
|
+
end
|
9
|
+
|
10
|
+
def command(name, description)
|
11
|
+
self << [name, description]
|
12
|
+
end
|
13
|
+
|
14
|
+
def space
|
15
|
+
self << ['', '']
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.groups
|
20
|
+
@groups ||= []
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.group(title, &block)
|
24
|
+
groups << begin
|
25
|
+
group = HelpGroup.new(title)
|
26
|
+
yield group
|
27
|
+
group
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.create_default_groups!
|
32
|
+
group 'Commands' do |group|
|
33
|
+
group.command 'help', 'show this usage'
|
34
|
+
group.command 'version', 'show the gem version'
|
35
|
+
group.space
|
36
|
+
group.command 'create <name>', 'create a new package skeleton'
|
37
|
+
group.command 'bundle [<path to directory>]', 'creates a package from the current directory'
|
38
|
+
group.command 'check <path to zip file>', 'runs a test suite against your package'
|
39
|
+
group.command 'extract <path to zip file>', 'extracts and checks the specified package'
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def index
|
44
|
+
display usage
|
45
|
+
end
|
46
|
+
|
47
|
+
def usage
|
48
|
+
longest_command_length = self.class.groups.map do |group|
|
49
|
+
group.map { |g| g.first.length }
|
50
|
+
end.flatten.max
|
51
|
+
|
52
|
+
self.class.groups.inject(StringIO.new) do |output, group|
|
53
|
+
output.puts "=== %s" % group.title
|
54
|
+
output.puts
|
55
|
+
|
56
|
+
group.each do |command, description|
|
57
|
+
if command.empty?
|
58
|
+
output.puts
|
59
|
+
else
|
60
|
+
output.puts "%-*s # %s" % [longest_command_length, command, description]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
output.puts
|
65
|
+
output
|
66
|
+
end.string + <<-EOTXT
|
67
|
+
=== Example
|
68
|
+
|
69
|
+
scorm create mypackage
|
70
|
+
cd mypackage
|
71
|
+
scorm check
|
72
|
+
scorm bundle
|
73
|
+
scorm extract mypackage.zip
|
74
|
+
|
75
|
+
EOTXT
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
Scorm::Command::Help.create_default_groups!
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Scorm
|
2
|
+
module Datatypes
|
3
|
+
|
4
|
+
class Timeinterval
|
5
|
+
def initialize(seconds)
|
6
|
+
@sec = seconds
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.parse(str)
|
10
|
+
case str
|
11
|
+
when /(\d{2,4}):(\d{2}):(\d{2})/
|
12
|
+
values = str.match(/(\d{2,4}):(\d{2}):(\d{2})/)
|
13
|
+
hour = values[1].to_i
|
14
|
+
minute = values[2].to_i
|
15
|
+
second = values[3].to_i
|
16
|
+
else
|
17
|
+
date, time = str.split('T')
|
18
|
+
if date
|
19
|
+
year = date.match(/([0-9]+Y)/)[1].to_i if date.match(/([0-9]+Y)/)
|
20
|
+
month = date.match(/([0-9]+M)/)[1].to_i if date.match(/([0-9]+M)/)
|
21
|
+
day = date.match(/([0-9]+D)/)[1].to_i if date.match(/([0-9]+D)/)
|
22
|
+
end
|
23
|
+
if time
|
24
|
+
hour = time.match(/([0-9]+H)/)[1].to_i if time.match(/([0-9]+H)/)
|
25
|
+
minute = time.match(/([0-9]+M)/)[1].to_i if time.match(/([0-9]+M)/)
|
26
|
+
second = time.match(/([0-9\.]+S)/)[1].to_f if time.match(/([0-9\.]+S)/)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
year = year || 0
|
30
|
+
month = month || 0
|
31
|
+
day = day || 0
|
32
|
+
hour = hour || 0
|
33
|
+
minute = minute || 0
|
34
|
+
second = second || 0
|
35
|
+
self.new((year*31557600) + (month*2629800) + (day*86400) + (hour*3600) + (minute*60) + second)
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_i
|
39
|
+
@sec.to_i
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_f
|
43
|
+
@sec.to_f
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_s
|
47
|
+
sec = self.to_i
|
48
|
+
hours = (sec/60/60).to_i
|
49
|
+
sec -= hours*60*60
|
50
|
+
min = (sec/60).to_i
|
51
|
+
sec -= min*60
|
52
|
+
return "#{hours}:#{min}:#{sec}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
require 'rexml/document'
|
2
|
+
require 'scorm/metadata'
|
3
|
+
require 'scorm/organization'
|
4
|
+
require 'scorm/resource'
|
5
|
+
|
6
|
+
module Scorm
|
7
|
+
class Manifest
|
8
|
+
|
9
|
+
# Versions of the SCORM standard that is supported when running in
|
10
|
+
# strict mode. When not running in strict mode, the library will not
|
11
|
+
# care about the version specified in the package manifest and will
|
12
|
+
# simply try its best to parse the information that it finds.
|
13
|
+
SUPPORTED_VERSIONS = ['2004 3rd Edition', 'CAM 1.3', '1.2']
|
14
|
+
|
15
|
+
# List of XML and XML Schema files that are part of the manifest for
|
16
|
+
# the package.
|
17
|
+
MANIFEST_FILES = %w(imsmanifest.xml adlcp_rootv1p2.xsd ims_xml.xsd
|
18
|
+
imscp_rootv1p1p2.xsd imsmd_rootv1p2p1.xsd)
|
19
|
+
|
20
|
+
# Files that might be present in a package, but that should not be
|
21
|
+
# interprested as resources. All files starting with a "." (i.e. hidden
|
22
|
+
# files) is also implicitly included in this list.
|
23
|
+
RESOURCES_BLACKLIST = [
|
24
|
+
'__MACOSX', 'desktop.ini', 'Thumbs.db'
|
25
|
+
].concat(MANIFEST_FILES)
|
26
|
+
|
27
|
+
attr_accessor :identifier
|
28
|
+
attr_accessor :metadata
|
29
|
+
attr_accessor :organizations
|
30
|
+
attr_accessor :default_organization
|
31
|
+
attr_accessor :resources
|
32
|
+
attr_accessor :base_url
|
33
|
+
attr_accessor :schema
|
34
|
+
attr_accessor :schema_version
|
35
|
+
|
36
|
+
def initialize(package, manifest_data)
|
37
|
+
@xmldoc = REXML::Document.new(manifest_data)
|
38
|
+
|
39
|
+
@package = package
|
40
|
+
@metadata = Scorm::Metadata.new
|
41
|
+
@organizations = Hash.new
|
42
|
+
@resources = Hash.new
|
43
|
+
|
44
|
+
# Manifest identifier
|
45
|
+
@identifier = @xmldoc.root.attribute('identifier').to_s
|
46
|
+
|
47
|
+
# Read metadata
|
48
|
+
if metadata_el = REXML::XPath.first(@xmldoc.root, '/manifest/metadata')
|
49
|
+
# Read <schema> and <schemaversion>
|
50
|
+
schema_el = REXML::XPath.first(metadata_el, 'schema')
|
51
|
+
schemaversion_el = REXML::XPath.first(metadata_el, 'schemaversion')
|
52
|
+
@schema = schema_el.text.to_s unless schema_el.nil?
|
53
|
+
@schema_version = schemaversion_el.text.to_s unless schemaversion_el.nil?
|
54
|
+
|
55
|
+
if @package.options[:strict]
|
56
|
+
if (@schema != 'ADL SCORM') || (!SUPPORTED_VERSIONS.include?(@schema_version))
|
57
|
+
raise InvalidManifest, "Sorry, unsupported SCORM-version (#{schema_el.text.to_s} #{schemaversion_el.text.to_s}), try turning strict parsing off."
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Find a <lom> element...
|
62
|
+
lom_el = nil
|
63
|
+
if adlcp_location = REXML::XPath.first(metadata_el, 'adlcp:location')
|
64
|
+
# Read external metadata file
|
65
|
+
metadata_xmldoc = REXML::Document.new(package.file(adlcp_location.text.to_s))
|
66
|
+
if metadata_xmldoc.nil? || (metadata_xmldoc.root.name != 'lom')
|
67
|
+
raise InvalidManifest, "Invalid external metadata file (#{adlcp_location.text.to_s})."
|
68
|
+
else
|
69
|
+
lom_el = metadata_xmldoc.root
|
70
|
+
end
|
71
|
+
else
|
72
|
+
# Read inline metadata
|
73
|
+
lom_el = REXML::XPath.first(metadata_el, 'lom') ||
|
74
|
+
REXML::XPath.first(metadata_el, 'lom:lom')
|
75
|
+
end
|
76
|
+
|
77
|
+
# Read lom metadata
|
78
|
+
if lom_el
|
79
|
+
@metadata = Scorm::Metadata.from_xml(lom_el)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Read organizations
|
84
|
+
if organizations_el = REXML::XPath.first(@xmldoc.root, '/manifest/organizations')
|
85
|
+
default_organization_id = organizations_el.attribute('default').to_s
|
86
|
+
REXML::XPath.each(@xmldoc.root, '/manifest/organizations/organization') do |el|
|
87
|
+
org = Scorm::Organization.from_xml(el)
|
88
|
+
@organizations[org.id.to_s] = org
|
89
|
+
end
|
90
|
+
# Set the default organization
|
91
|
+
@default_organization = @organizations[default_organization_id]
|
92
|
+
raise InvalidManifest, "No default organization (#{default_organization_id})." if @default_organization.nil?
|
93
|
+
else
|
94
|
+
raise InvalidManifest, 'Missing organizations element.'
|
95
|
+
end
|
96
|
+
|
97
|
+
# Read resources
|
98
|
+
REXML::XPath.each(@xmldoc.root, '/manifest/resources/resource') do |el|
|
99
|
+
res = Scorm::Resource.from_xml(el)
|
100
|
+
@resources[res.id] = res
|
101
|
+
end
|
102
|
+
|
103
|
+
# Read additional resources as assets (this is a fix for packages that
|
104
|
+
# don't correctly specify all resource dependencies in the manifest).
|
105
|
+
@package.files.each do |file|
|
106
|
+
next if File.directory?(file)
|
107
|
+
next if RESOURCES_BLACKLIST.include?(File.basename(file))
|
108
|
+
next if File.basename(file) =~ /^\./
|
109
|
+
next unless self.resources(:with_file => file).empty?
|
110
|
+
next unless self.resources(:href => file).empty?
|
111
|
+
|
112
|
+
res = Scorm::Resource.new(file, 'webcontent', 'asset', file, nil, [file])
|
113
|
+
@resources[file] = res
|
114
|
+
end
|
115
|
+
|
116
|
+
# Read (optional) base url for resources
|
117
|
+
resources_el = REXML::XPath.first(@xmldoc.root, '/manifest/resources')
|
118
|
+
@base_url = (resources_el.attribute('xml:base') || '').to_s
|
119
|
+
|
120
|
+
# Read sub-manifests
|
121
|
+
#REXML::XPath.
|
122
|
+
end
|
123
|
+
|
124
|
+
def resources(options = nil)
|
125
|
+
if (options.nil?) || (!options.is_a?(Hash))
|
126
|
+
@resources.values
|
127
|
+
else
|
128
|
+
subset = @resources.values
|
129
|
+
if options[:id]
|
130
|
+
subset = subset.find_all {|r| r.id == options[:id].to_s }
|
131
|
+
end
|
132
|
+
if options[:type]
|
133
|
+
subset = subset.find_all {|r| r.type == options[:type].to_s }
|
134
|
+
end
|
135
|
+
if options[:scorm_type]
|
136
|
+
subset = subset.find_all {|r| r.scorm_type == options[:scorm_type].to_s }
|
137
|
+
end
|
138
|
+
if options[:href]
|
139
|
+
subset = subset.find_all {|r| r.href == options[:href].to_s }
|
140
|
+
end
|
141
|
+
if options[:with_file]
|
142
|
+
subset = subset.find_all {|r| r.files.include?(options[:with_file].to_s) }
|
143
|
+
end
|
144
|
+
subset
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def sco(item, attribute = nil)
|
149
|
+
resource = self.resources(:id => item.resource_id).first
|
150
|
+
resource = (resource && resource.scorm_type == 'sco') ? resource : nil
|
151
|
+
return (resource && attribute) ? resource.send(attribute) : resource
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|