scorm 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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>
|