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
@@ -0,0 +1,100 @@
|
|
1
|
+
module Scorm
|
2
|
+
|
3
|
+
# The +Metadata+ class holds meta data associated with a SCORM package in a
|
4
|
+
# hash like structure. The +Metadata+ class reads a LOM (Learning Object
|
5
|
+
# Metadata) structure and stores the data in categories. A +Category+ can
|
6
|
+
# contain any number of +DataElement+s. A +DataElement+ behaves just like
|
7
|
+
# a string but can contain the same value in many different languages,
|
8
|
+
# accessed by the DataElement#value (or DataElement#to_s) method by
|
9
|
+
# specifying the language code as the first argument.
|
10
|
+
#
|
11
|
+
# Ex.
|
12
|
+
#
|
13
|
+
# <tt>pkg.manifest.metadata.general.class -> Metadata::Category</tt>
|
14
|
+
# <tt>pkg.manifest.metadata.general.title.class -> Metadata::DataElement</tt>
|
15
|
+
# <tt>pkg.manifest.metadata.general.title.value -> 'My course'</tt>
|
16
|
+
# <tt>pkg.manifest.metadata.general.title.value('sv') -> 'Min kurs'</tt>
|
17
|
+
#
|
18
|
+
class Metadata < Hash
|
19
|
+
|
20
|
+
def self.from_xml(element)
|
21
|
+
metadata = self.new
|
22
|
+
element.elements.each do |category_el|
|
23
|
+
category = Category.from_xml(category_el)
|
24
|
+
metadata.store(category_el.name.to_s, category)
|
25
|
+
end
|
26
|
+
return metadata
|
27
|
+
end
|
28
|
+
|
29
|
+
def method_missing(sym)
|
30
|
+
self.fetch(sym.to_s, nil)
|
31
|
+
end
|
32
|
+
|
33
|
+
class Category < Hash
|
34
|
+
|
35
|
+
def self.from_xml(element)
|
36
|
+
category = Scorm::Metadata::Category.new
|
37
|
+
element.elements.each do |data_el|
|
38
|
+
category[data_el.name.to_s] = DataElement.from_xml(data_el)
|
39
|
+
end
|
40
|
+
return category
|
41
|
+
end
|
42
|
+
|
43
|
+
def method_missing(sym, *args)
|
44
|
+
data_element = self.fetch(sym.to_s, nil)
|
45
|
+
if data_element.is_a? DataElement
|
46
|
+
data_element.value(args.first)
|
47
|
+
else
|
48
|
+
data_element
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class DataElement
|
54
|
+
def initialize(value = '', default_lang = nil)
|
55
|
+
if value.is_a? String
|
56
|
+
@langstrings = Hash.new
|
57
|
+
@langstrings['x-none'] = value
|
58
|
+
@default_lang = 'x-none'
|
59
|
+
elsif value.is_a? Hash
|
60
|
+
@langstrings = value.dup
|
61
|
+
@default_lang = default_lang || 'x-none'
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.from_xml(element)
|
66
|
+
if element.elements.size == 0
|
67
|
+
return self.new(element.text.to_s)
|
68
|
+
|
69
|
+
elsif element.get_elements('value').size != 0
|
70
|
+
value_el = element.get_elements('value').first
|
71
|
+
return self.from_xml(value_el)
|
72
|
+
|
73
|
+
elsif element.get_elements('langstring').size != 0
|
74
|
+
langstrings = Hash.new
|
75
|
+
default_lang = nil
|
76
|
+
element.each_element('langstring') do |ls|
|
77
|
+
default_lang = ls.attribute('xml:lang').to_s if default_lang.nil?
|
78
|
+
langstrings[ls.attribute('xml:lang').to_s || 'x-none'] = ls.text.to_s
|
79
|
+
end
|
80
|
+
return self.new(langstrings, default_lang)
|
81
|
+
|
82
|
+
else
|
83
|
+
return Category.from_xml(element)
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def value(lang = nil)
|
89
|
+
if lang.nil?
|
90
|
+
(@langstrings && @default_lang) ? @langstrings[@default_lang] : ''
|
91
|
+
else
|
92
|
+
(@langstrings) ? @langstrings[lang] || '' : ''
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
alias :to_s :value
|
97
|
+
alias :to_str :value
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
#--
|
2
|
+
# TODO: items should be read as an hierarchy.
|
3
|
+
#
|
4
|
+
# TODO: imsss:sequencing and adlnav:presentation should be parsed and read.
|
5
|
+
#
|
6
|
+
# TODO: read <item><metadata>...</metadata></item>.
|
7
|
+
#++
|
8
|
+
|
9
|
+
module Scorm
|
10
|
+
# The +Organization+ class holds data about the organization of a SCORM
|
11
|
+
# package. An organization contains an id, title and any number of +items+.
|
12
|
+
# An +Item+ are (in most cases) the same thing as a SCO (Shareable Content
|
13
|
+
# Object).
|
14
|
+
class Organization
|
15
|
+
attr_accessor :id
|
16
|
+
attr_accessor :title
|
17
|
+
attr_accessor :items
|
18
|
+
|
19
|
+
def initialize(id, title, items)
|
20
|
+
raise InvalidManifest, 'missing organization id' if id.nil?
|
21
|
+
@id = id.to_s
|
22
|
+
@title = title.to_s
|
23
|
+
@items = items
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.from_xml(element)
|
27
|
+
id = element.attribute('identifier').to_s
|
28
|
+
title = element.get_elements('title').first.text.to_s if element.get_elements('title').first
|
29
|
+
items = []
|
30
|
+
REXML::XPath.each(element, 'item') do |item_el|
|
31
|
+
items << Item.from_xml(item_el)
|
32
|
+
end
|
33
|
+
return self.new(id, title, items)
|
34
|
+
end
|
35
|
+
|
36
|
+
# An item has an id, title, and (in some cases) a parent item. An item is
|
37
|
+
# associated with a resource, which in most cases is a SCO (Shareable
|
38
|
+
# Content Object) resource.
|
39
|
+
class Item
|
40
|
+
attr_accessor :id
|
41
|
+
attr_accessor :title
|
42
|
+
attr_accessor :isvisible
|
43
|
+
attr_accessor :parameters
|
44
|
+
attr_accessor :resource_id
|
45
|
+
attr_accessor :children
|
46
|
+
attr_accessor :time_limit_action
|
47
|
+
attr_accessor :data_from_lms
|
48
|
+
attr_accessor :completion_threshold
|
49
|
+
|
50
|
+
def initialize(id, title, isvisible = true, parameters = nil, resource_id = nil, children = nil)
|
51
|
+
@id = id.to_s
|
52
|
+
@title = title.to_s
|
53
|
+
@isvisible = isvisible || true
|
54
|
+
@parameters = parameters
|
55
|
+
@resource_id = resource_id
|
56
|
+
@children = children if children.is_a? Array
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.from_xml(element)
|
60
|
+
item_id = element.attribute('identifier').to_s
|
61
|
+
item_title = element.get_elements('title').first.text.to_s if element.get_elements('title').first
|
62
|
+
item_isvisible = (element.attribute('isvisible').to_s == 'true')
|
63
|
+
item_parameters = element.attribute('parameters').to_s
|
64
|
+
children = []
|
65
|
+
if element.get_elements('item').empty?
|
66
|
+
resource_id = element.attribute('identifierref').to_s
|
67
|
+
else
|
68
|
+
element.each_element('item') do |item_el|
|
69
|
+
child_item = self.from_xml(item_el)
|
70
|
+
children << child_item
|
71
|
+
end
|
72
|
+
end
|
73
|
+
return self.new(item_id, item_title, item_isvisible, item_parameters, resource_id, children)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,210 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'zip/zip'
|
3
|
+
require 'fileutils'
|
4
|
+
require 'open-uri'
|
5
|
+
require 'scorm/datatypes'
|
6
|
+
require 'scorm/manifest'
|
7
|
+
|
8
|
+
module Scorm
|
9
|
+
class InvalidPackage < RuntimeError; end
|
10
|
+
class InvalidManifest < InvalidPackage; end
|
11
|
+
|
12
|
+
class Package
|
13
|
+
attr_accessor :name # Name of the package.
|
14
|
+
attr_accessor :manifest # An instance of +Scorm::Manifest+.
|
15
|
+
attr_accessor :path # Path to the extracted course.
|
16
|
+
attr_accessor :repository # The directory to which the packages is extracted.
|
17
|
+
attr_accessor :options # The options hash supplied when opening the package.
|
18
|
+
attr_accessor :package # The file name of the package file.
|
19
|
+
|
20
|
+
DEFAULT_LOAD_OPTIONS = {
|
21
|
+
:strict => false,
|
22
|
+
:dry_run => false,
|
23
|
+
:cleanup => true,
|
24
|
+
:force_cleanup => false,
|
25
|
+
:name => nil,
|
26
|
+
:repository => nil
|
27
|
+
}
|
28
|
+
|
29
|
+
def self.set_default_load_options(options = {})
|
30
|
+
DEFAULT_LOAD_OPTIONS.merge!(options)
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.open(filename, options = {}, &block)
|
34
|
+
Package.new(filename, options, &block)
|
35
|
+
end
|
36
|
+
|
37
|
+
# This method will load a SCORM package and extract its content to the
|
38
|
+
# directory specified by the +:repository+ option. The manifest file will be
|
39
|
+
# parsed and made available through the +manifest+ instance variable. This
|
40
|
+
# method should be called with an associated block as it yields the opened
|
41
|
+
# package and then auto-magically closes it when the block has finished. It
|
42
|
+
# will also do any necessary cleanup if an exception occur anywhere in the
|
43
|
+
# block. The available options are:
|
44
|
+
#
|
45
|
+
# :+strict+: If +false+ the manifest will be parsed in a nicer way. Default: +true+.
|
46
|
+
# :+dry_run+: If +true+ nothing will be written to the file system. Default: +false+.
|
47
|
+
# :+cleanup+: If +false+ no cleanup will take place if an error occur. Default: +true+.
|
48
|
+
# :+name+: The name to use when extracting the package to the
|
49
|
+
# repository. Default: will use the filename of the package
|
50
|
+
# (minus the .zip extension).
|
51
|
+
# :+repository+: Path to the course repository. Default: the same directory as the package.
|
52
|
+
#
|
53
|
+
def initialize(filename, options = {}, &block)
|
54
|
+
@options = DEFAULT_LOAD_OPTIONS.merge(options)
|
55
|
+
@package = filename.respond_to?(:path) ? filename.path : filename
|
56
|
+
|
57
|
+
# Check if package is a directory or a file.
|
58
|
+
if File.directory?(@package)
|
59
|
+
@name = File.basename(@package)
|
60
|
+
@repository = File.dirname(@package)
|
61
|
+
@path = File.expand_path(@package)
|
62
|
+
else
|
63
|
+
i = nil
|
64
|
+
begin
|
65
|
+
# Decide on a name for the package.
|
66
|
+
@name = [(@options[:name] || File.basename(@package, File.extname(@package))), i].flatten.join
|
67
|
+
|
68
|
+
# Set the path for the extracted package.
|
69
|
+
@repository = @options[:repository] || File.dirname(@package)
|
70
|
+
@path = File.expand_path(File.join(@repository, @name))
|
71
|
+
|
72
|
+
# First try is nil, subsequent tries sets and increments the value with
|
73
|
+
# one starting at zero.
|
74
|
+
i = (i || 0) + 1
|
75
|
+
|
76
|
+
# Make sure the generated path is unique.
|
77
|
+
end while File.exists?(@path)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Extract the package
|
81
|
+
extract!
|
82
|
+
|
83
|
+
# Detect and read imsmanifest.xml
|
84
|
+
if exists?('imsmanifest.xml')
|
85
|
+
@manifest = Manifest.new(self, file('imsmanifest.xml'))
|
86
|
+
else
|
87
|
+
raise InvalidPackage, "#{File.basename(@package)}: no imsmanifest.xml, maybe not SCORM compatible?"
|
88
|
+
end
|
89
|
+
|
90
|
+
# Yield to the caller.
|
91
|
+
yield(self)
|
92
|
+
|
93
|
+
# Make sure the package is closed when the caller has finished reading it.
|
94
|
+
self.close
|
95
|
+
|
96
|
+
# If an exception occur the package is auto-magically closed and any
|
97
|
+
# residual data deleted in a clean way.
|
98
|
+
rescue Exception => e
|
99
|
+
self.close
|
100
|
+
self.cleanup
|
101
|
+
raise e
|
102
|
+
end
|
103
|
+
|
104
|
+
# Closes the package.
|
105
|
+
def close
|
106
|
+
@zipfile.close if @zipfile
|
107
|
+
|
108
|
+
# Make sure the extracted package is deleted if force_cleanup_on_close
|
109
|
+
# is enabled.
|
110
|
+
self.cleanup if @options[:force_cleanup_on_close]
|
111
|
+
end
|
112
|
+
|
113
|
+
# Cleans up by deleting all extracted files. Called when an error occurs.
|
114
|
+
def cleanup
|
115
|
+
FileUtils.rmtree(@path) if @options[:cleanup] && !@options[:dry_run] && @path && File.exists?(@path) && package?
|
116
|
+
end
|
117
|
+
|
118
|
+
# Extracts the content of the package to the course repository. This will be
|
119
|
+
# done automatically when opening a package so this method will rarely be
|
120
|
+
# used. If the +dry_run+ option was set to +true+ when the package was
|
121
|
+
# opened nothing will happen. This behavior can be overridden with the
|
122
|
+
# +force+ parameter.
|
123
|
+
def extract!(force = false)
|
124
|
+
return if @options[:dry_run] && !force
|
125
|
+
|
126
|
+
# If opening an already extracted package; do nothing.
|
127
|
+
if not package?
|
128
|
+
return
|
129
|
+
end
|
130
|
+
|
131
|
+
# Create the path to the course
|
132
|
+
FileUtils.mkdir_p(@path)
|
133
|
+
|
134
|
+
Zip::ZipFile::foreach(@package) do |entry|
|
135
|
+
entry_path = File.join(@path, entry.name)
|
136
|
+
entry_dir = File.dirname(entry_path)
|
137
|
+
FileUtils.mkdir_p(entry_dir) unless File.exists?(entry_dir)
|
138
|
+
entry.extract(entry_path)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# This will only return +true+ if what was opened was an actual zip file.
|
143
|
+
def package?
|
144
|
+
# If the path to the course in the repository is the same as the path to
|
145
|
+
# the package, we can assume that what was really opened was a directory
|
146
|
+
# in the course repository and therefor not a package.
|
147
|
+
return false if File.extname(@package)
|
148
|
+
return true
|
149
|
+
end
|
150
|
+
|
151
|
+
# Reads a file from the package. If the file is not extracted yet (all files
|
152
|
+
# are extracted by default when opening the package) it will be extracted
|
153
|
+
# to the file system and its content returned. If the +dry_run+ option was
|
154
|
+
# set to +true+ when opening the package the file will <em>not</em> be
|
155
|
+
# extracted to the file system, but read directly into memory.
|
156
|
+
def file(filename)
|
157
|
+
if File.exists?(@path)
|
158
|
+
File.read(path_to(filename))
|
159
|
+
else
|
160
|
+
Zip::ZipFile.foreach(@package) do |entry|
|
161
|
+
return entry.get_input_stream {|io| io.read } if entry.name == filename
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
# Returns +true+ if the specified file (or directory) exists in the package.
|
167
|
+
def exists?(filename)
|
168
|
+
if File.exists?(@path)
|
169
|
+
File.exists?(path_to(filename))
|
170
|
+
else
|
171
|
+
Zip::ZipFile::foreach(@package) do |entry|
|
172
|
+
return true if entry.name == filename
|
173
|
+
end
|
174
|
+
false
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
# Computes the absolute path to a file in an extracted package given its
|
179
|
+
# relative path. The argument +relative+ can be used to get the path
|
180
|
+
# relative to the course repository.
|
181
|
+
#
|
182
|
+
# Ex.
|
183
|
+
# <tt>pkg.path => '/var/lms/courses/MyCourse/'</tt>
|
184
|
+
# <tt>pkg.course_repository => '/var/lms/courses/'</tt>
|
185
|
+
# <tt>path_to('images/myimg.jpg') => '/var/lms/courses/MyCourse/images/myimg.jpg'</tt>
|
186
|
+
# <tt>path_to('images/myimg.jpg', true) => 'MyCourse/images/myimg.jpg'</tt>
|
187
|
+
#
|
188
|
+
def path_to(relative_filename, relative = false)
|
189
|
+
if relative
|
190
|
+
File.join(@name, relative_filename)
|
191
|
+
else
|
192
|
+
File.join(@path, relative_filename)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
# Returns an array with the paths to all the files in the package.
|
197
|
+
def files
|
198
|
+
if File.directory?(@package)
|
199
|
+
Dir.glob(File.join(File.join(File.expand_path(@package), '**'), '*')).reject {|f|
|
200
|
+
File.directory?(f) }.map {|f| f.sub(/^#{File.expand_path(@package)}\/?/, '') }
|
201
|
+
else
|
202
|
+
entries = []
|
203
|
+
Zip::ZipFile::foreach(@package) do |entry|
|
204
|
+
entries << entry.name unless entry.name[-1..-1] == '/'
|
205
|
+
end
|
206
|
+
entries
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Scorm
|
2
|
+
|
3
|
+
# A +Resource+ is a representation/description of an actual resource (image,
|
4
|
+
# sco, pdf, etc...) in a SCORM package.
|
5
|
+
class Resource
|
6
|
+
attr_accessor :id
|
7
|
+
attr_accessor :type
|
8
|
+
attr_accessor :scorm_type
|
9
|
+
attr_accessor :href
|
10
|
+
attr_accessor :metadata
|
11
|
+
attr_accessor :files
|
12
|
+
attr_accessor :dependencies
|
13
|
+
|
14
|
+
def initialize(id, type, scorm_type, href = nil, metadata = nil, files = nil, dependencies = nil)
|
15
|
+
raise InvalidManifest, 'Missing resource id' if id.nil?
|
16
|
+
raise InvalidManifest, 'Missing resource type' if type.nil?
|
17
|
+
breakpoint if scorm_type.nil?
|
18
|
+
raise InvalidManifest, 'Missing resource scormType' if scorm_type.nil?
|
19
|
+
@id = id.to_s
|
20
|
+
@type = type.to_s
|
21
|
+
@scorm_type = scorm_type.to_s
|
22
|
+
@href = href.to_s || ''
|
23
|
+
@metadata = metadata || Hash.new
|
24
|
+
@files = files || []
|
25
|
+
@dependencies = dependencies || []
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.from_xml(element)
|
29
|
+
metadata = nil
|
30
|
+
files = []
|
31
|
+
REXML::XPath.each(element, 'file') do |file_el|
|
32
|
+
files << element.attribute('xml:base').to_s + file_el.attribute('href').to_s
|
33
|
+
end
|
34
|
+
dependencies = []
|
35
|
+
REXML::XPath.each(element, 'dependency') do |dep_el|
|
36
|
+
dependencies << dep_el.attribute('identifierref').to_s
|
37
|
+
end
|
38
|
+
|
39
|
+
res = self.new(
|
40
|
+
element.attribute('identifier'),
|
41
|
+
element.attribute('type'),
|
42
|
+
element.attribute('scormType', 'adlcp') || element.attribute('scormtype', 'adlcp'),
|
43
|
+
element.attribute('xml:base').to_s + element.attribute('href').to_s,
|
44
|
+
metadata,
|
45
|
+
files,
|
46
|
+
dependencies)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
<?xml version="1.0"?>
|
2
|
+
<!-- filename=adlcp_rootv1p2.xsd -->
|
3
|
+
<!-- Conforms to w3c http://www.w3.org/TR/xmlschema-1/ 2000-10-24-->
|
4
|
+
|
5
|
+
<xsd:schema xmlns="http://www.adlnet.org/xsd/adlcp_rootv1p2"
|
6
|
+
targetNamespace="http://www.adlnet.org/xsd/adlcp_rootv1p2"
|
7
|
+
xmlns:xml="http://www.w3.org/XML/1998/namespace"
|
8
|
+
xmlns:imscp="http://www.imsproject.org/xsd/imscp_rootv1p1p2"
|
9
|
+
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
10
|
+
elementFormDefault="unqualified"
|
11
|
+
version="ADL Version 1.2">
|
12
|
+
|
13
|
+
<xsd:import namespace="http://www.imsproject.org/xsd/imscp_rootv1p1p2"
|
14
|
+
schemaLocation="imscp_rootv1p1p2.xsd"/>
|
15
|
+
|
16
|
+
<xsd:element name="location" type="locationType"/>
|
17
|
+
<xsd:element name="prerequisites" type="prerequisitesType"/>
|
18
|
+
<xsd:element name="maxtimeallowed" type="maxtimeallowedType"/>
|
19
|
+
<xsd:element name="timelimitaction" type="timelimitactionType"/>
|
20
|
+
<xsd:element name="datafromlms" type="datafromlmsType"/>
|
21
|
+
<xsd:element name="masteryscore" type="masteryscoreType"/>
|
22
|
+
|
23
|
+
|
24
|
+
<xsd:element name="schema" type="newSchemaType"/>
|
25
|
+
<xsd:simpleType name="newSchemaType">
|
26
|
+
<xsd:restriction base="imscp:schemaType">
|
27
|
+
<xsd:enumeration value="ADL SCORM"/>
|
28
|
+
</xsd:restriction>
|
29
|
+
</xsd:simpleType>
|
30
|
+
|
31
|
+
<xsd:element name="schemaversion" type="newSchemaversionType"/>
|
32
|
+
<xsd:simpleType name="newSchemaversionType">
|
33
|
+
<xsd:restriction base="imscp:schemaversionType">
|
34
|
+
<xsd:enumeration value="1.2"/>
|
35
|
+
</xsd:restriction>
|
36
|
+
</xsd:simpleType>
|
37
|
+
|
38
|
+
|
39
|
+
<xsd:attribute name="scormtype">
|
40
|
+
<xsd:simpleType>
|
41
|
+
<xsd:restriction base="xsd:string">
|
42
|
+
<xsd:enumeration value="asset"/>
|
43
|
+
<xsd:enumeration value="sco"/>
|
44
|
+
</xsd:restriction>
|
45
|
+
</xsd:simpleType>
|
46
|
+
</xsd:attribute>
|
47
|
+
|
48
|
+
<xsd:simpleType name="locationType">
|
49
|
+
<xsd:restriction base="xsd:string">
|
50
|
+
<xsd:maxLength value="2000"/>
|
51
|
+
</xsd:restriction>
|
52
|
+
</xsd:simpleType>
|
53
|
+
|
54
|
+
|
55
|
+
<xsd:complexType name="prerequisitesType">
|
56
|
+
<xsd:simpleContent>
|
57
|
+
<xsd:extension base="prerequisiteStringType">
|
58
|
+
<xsd:attributeGroup ref="attr.prerequisitetype"/>
|
59
|
+
</xsd:extension>
|
60
|
+
</xsd:simpleContent>
|
61
|
+
</xsd:complexType>
|
62
|
+
|
63
|
+
<xsd:attributeGroup name="attr.prerequisitetype">
|
64
|
+
<xsd:attribute name="type" use="required">
|
65
|
+
<xsd:simpleType>
|
66
|
+
<xsd:restriction base="xsd:string">
|
67
|
+
<xsd:enumeration value="aicc_script"/>
|
68
|
+
</xsd:restriction>
|
69
|
+
</xsd:simpleType>
|
70
|
+
</xsd:attribute>
|
71
|
+
</xsd:attributeGroup>
|
72
|
+
|
73
|
+
<xsd:simpleType name="maxtimeallowedType">
|
74
|
+
<xsd:restriction base="xsd:string">
|
75
|
+
<xsd:maxLength value="13"/>
|
76
|
+
</xsd:restriction>
|
77
|
+
</xsd:simpleType>
|
78
|
+
|
79
|
+
<xsd:simpleType name="timelimitactionType">
|
80
|
+
<xsd:restriction base="stringType">
|
81
|
+
<xsd:enumeration value="exit,no message"/>
|
82
|
+
<xsd:enumeration value="exit,message"/>
|
83
|
+
<xsd:enumeration value="continue,no message"/>
|
84
|
+
<xsd:enumeration value="continue,message"/>
|
85
|
+
</xsd:restriction>
|
86
|
+
</xsd:simpleType>
|
87
|
+
|
88
|
+
<xsd:simpleType name="datafromlmsType">
|
89
|
+
<xsd:restriction base="xsd:string">
|
90
|
+
<xsd:maxLength value="255"/>
|
91
|
+
</xsd:restriction>
|
92
|
+
</xsd:simpleType>
|
93
|
+
|
94
|
+
<xsd:simpleType name="masteryscoreType">
|
95
|
+
<xsd:restriction base="xsd:string">
|
96
|
+
<xsd:maxLength value="200"/>
|
97
|
+
</xsd:restriction>
|
98
|
+
</xsd:simpleType>
|
99
|
+
|
100
|
+
<xsd:simpleType name="stringType">
|
101
|
+
<xsd:restriction base="xsd:string"/>
|
102
|
+
</xsd:simpleType>
|
103
|
+
|
104
|
+
<xsd:simpleType name="prerequisiteStringType">
|
105
|
+
<xsd:restriction base="xsd:string">
|
106
|
+
<xsd:maxLength value="200"/>
|
107
|
+
</xsd:restriction>
|
108
|
+
</xsd:simpleType>
|
109
|
+
|
110
|
+
</xsd:schema>
|