common_cartridge_parser 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.
- checksums.yaml +7 -0
- data/lib/common_cartridge.rb +40 -0
- data/lib/common_cartridge/elements/lom.rb +77 -0
- data/lib/common_cartridge/elements/manifest.rb +30 -0
- data/lib/common_cartridge/elements/metadata.rb +12 -0
- data/lib/common_cartridge/elements/organizations.rb +40 -0
- data/lib/common_cartridge/elements/outcomes.rb +10 -0
- data/lib/common_cartridge/elements/outcomes/outcome.rb +15 -0
- data/lib/common_cartridge/elements/outcomes/outcome_group.rb +12 -0
- data/lib/common_cartridge/elements/outcomes/outcome_root.rb +11 -0
- data/lib/common_cartridge/elements/resources.rb +119 -0
- data/lib/common_cartridge/elements/resources/assessment.rb +21 -0
- data/lib/common_cartridge/elements/resources/assignment.rb +59 -0
- data/lib/common_cartridge/elements/resources/attachments.rb +20 -0
- data/lib/common_cartridge/elements/resources/basic_lti_link.rb +66 -0
- data/lib/common_cartridge/elements/resources/page.rb +22 -0
- data/lib/common_cartridge/elements/resources/topic.rb +28 -0
- data/lib/common_cartridge/elements/resources/web_link.rb +25 -0
- data/lib/common_cartridge/package.rb +87 -0
- data/lib/common_cartridge/parsers/dependencies.rb +23 -0
- data/lib/common_cartridge/parsers/files.rb +34 -0
- data/lib/common_cartridge/parsers/items.rb +58 -0
- data/lib/common_cartridge/parsers/outcomes.rb +17 -0
- data/lib/common_cartridge/parsers/parser.rb +84 -0
- data/lib/common_cartridge/parsers/questions.rb +34 -0
- data/lib/common_cartridge/version.rb +3 -0
- data/spec/elements/lom_spec.rb +66 -0
- data/spec/elements/manifest_spec.rb +31 -0
- data/spec/elements/metadata_spec.rb +40 -0
- data/spec/elements/organizations_spec.rb +51 -0
- data/spec/elements/outcomes/outcome_group_spec.rb +29 -0
- data/spec/elements/outcomes/outcome_spec.rb +37 -0
- data/spec/elements/resources/assignment_spec.rb +53 -0
- data/spec/elements/resources/attachments_spec.rb +24 -0
- data/spec/elements/resources/basic_lti_link_spec.rb +29 -0
- data/spec/elements/resources/resources_spec.rb +73 -0
- data/spec/elements/resources/topic_spec.rb +39 -0
- data/spec/elements/resources/web_link_spec.rb +31 -0
- data/spec/files/canvas_large_1.3.imscc +0 -0
- data/spec/files/canvas_small_1.1.corrupt.imscc +0 -0
- data/spec/files/canvas_small_1.1.imscc +0 -0
- data/spec/package_spec.rb +110 -0
- data/spec/parser_spec.rb +13 -0
- data/spec/spec_helper.rb +13 -0
- metadata +190 -0
@@ -0,0 +1,20 @@
|
|
1
|
+
module CommonCartridge
|
2
|
+
module Elements
|
3
|
+
module Resources
|
4
|
+
module Attachments
|
5
|
+
class Attachment
|
6
|
+
include SAXMachine
|
7
|
+
|
8
|
+
attribute :href
|
9
|
+
attribute :role
|
10
|
+
end
|
11
|
+
|
12
|
+
class RootAttachment
|
13
|
+
include SAXMachine
|
14
|
+
|
15
|
+
elements :'dt:attachment', class: Attachment, as: :attachments
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module CommonCartridge
|
2
|
+
module Elements
|
3
|
+
module Resources
|
4
|
+
module BasicLtiLink
|
5
|
+
class VendorContact
|
6
|
+
include SAXMachine
|
7
|
+
|
8
|
+
element 'lticp:email', as: :email
|
9
|
+
end
|
10
|
+
|
11
|
+
class Vendor
|
12
|
+
include SAXMachine
|
13
|
+
|
14
|
+
element 'lticp:code', as: :code
|
15
|
+
element 'lticp:name', as: :name
|
16
|
+
element 'lticp:description', as: :description
|
17
|
+
element 'lticp:url', as: :url
|
18
|
+
element 'lticp:contact', class: VendorContact, as: :contact
|
19
|
+
end
|
20
|
+
|
21
|
+
class ExtensionProperty
|
22
|
+
include SAXMachine
|
23
|
+
|
24
|
+
attribute :name
|
25
|
+
value :value
|
26
|
+
end
|
27
|
+
|
28
|
+
class Extension
|
29
|
+
include SAXMachine
|
30
|
+
|
31
|
+
attribute :platform
|
32
|
+
elements 'lticm:property', class: ExtensionProperty, as: :properties
|
33
|
+
end
|
34
|
+
|
35
|
+
class BasicLtiLink
|
36
|
+
attr_accessor :identifier
|
37
|
+
|
38
|
+
include SAXMachine
|
39
|
+
|
40
|
+
attribute :xmlns
|
41
|
+
attribute 'xmlns:blti', as: :xmlns_blti
|
42
|
+
attribute 'xmlns:lticm', as: :xmlns_lticm
|
43
|
+
attribute 'xmlns:lticp', as: :xmlns_lticp
|
44
|
+
attribute 'xmlns:xsi', as: :xmlns_xsi
|
45
|
+
attribute 'xsi:schemaLocation', as: :xsi_schema_location
|
46
|
+
|
47
|
+
element 'blti:title', as: :title
|
48
|
+
element 'blti:description', as: :description
|
49
|
+
element 'blti:secure_launch_url', as: :secure_launch_url
|
50
|
+
element 'blti:launch_url', as: :launch_url
|
51
|
+
element 'blti:vendor', class: Vendor, as: :vendor
|
52
|
+
|
53
|
+
elements 'blti:extensions', class: Extension, as: :extensions
|
54
|
+
|
55
|
+
def self.type
|
56
|
+
:basic_lti_link
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.pattern
|
60
|
+
/imsbasiclti_xmlv1p/
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module CommonCartridge
|
2
|
+
module Elements
|
3
|
+
module Resources
|
4
|
+
class Page
|
5
|
+
attr_accessor :identifier, :identifierref
|
6
|
+
|
7
|
+
include SAXMachine
|
8
|
+
element :title
|
9
|
+
element :text
|
10
|
+
element :text, value: :texttype, as: :text_type
|
11
|
+
|
12
|
+
def self.type
|
13
|
+
:page
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.pattern
|
17
|
+
/webcontent/
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module CommonCartridge
|
2
|
+
module Elements
|
3
|
+
module Resources
|
4
|
+
class Topic
|
5
|
+
attr_accessor :identifier
|
6
|
+
|
7
|
+
include SAXMachine
|
8
|
+
|
9
|
+
element 'dt:title', as: :title
|
10
|
+
element 'dt:text', as: :text
|
11
|
+
element 'dt:text', value: :texttype, as: :text_type
|
12
|
+
element 'dt:attachments', class: Attachments::RootAttachment, as: :attachment_root
|
13
|
+
|
14
|
+
def attachments
|
15
|
+
attachment_root.attachments
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.type
|
19
|
+
:discussion
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.pattern
|
23
|
+
/imsdt/
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module CommonCartridge
|
2
|
+
module Elements
|
3
|
+
module Resources
|
4
|
+
class WebLink
|
5
|
+
attr_accessor :identifier
|
6
|
+
|
7
|
+
include SAXMachine
|
8
|
+
|
9
|
+
element :title
|
10
|
+
element :url
|
11
|
+
element :url, value: :href, as: :href
|
12
|
+
element :url, value: :target, as: :target
|
13
|
+
element :url, value: :windoFeatures, as: :window_features
|
14
|
+
|
15
|
+
def self.type
|
16
|
+
:weblink
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.pattern
|
20
|
+
/wl/
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module CommonCartridge
|
2
|
+
class Package
|
3
|
+
attr_accessor :manifest, :outcomes, :assignments
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@assignments = []
|
7
|
+
end
|
8
|
+
|
9
|
+
def find_resource(identifier)
|
10
|
+
manifest.resources.find { |r| r.identifier == identifier }
|
11
|
+
end
|
12
|
+
|
13
|
+
def outcome_groups
|
14
|
+
@outcome_groups ||= outcomes ? outcomes.outcome_groups : []
|
15
|
+
end
|
16
|
+
|
17
|
+
def all_outcomes
|
18
|
+
@all_outcomes ||= outcome_groups.collect { |g| g.outcomes }.flatten
|
19
|
+
end
|
20
|
+
|
21
|
+
def modules
|
22
|
+
@modules ||= manifest.organization.root_item.items
|
23
|
+
end
|
24
|
+
|
25
|
+
def discussions
|
26
|
+
@discussions ||= resources_of_type(Elements::Resources::Topic).reject { |d| d.is_announcement? }
|
27
|
+
end
|
28
|
+
|
29
|
+
def announcements
|
30
|
+
@announcements ||= resources_of_type(Elements::Resources::Topic).select { |d| d.is_announcement? }
|
31
|
+
end
|
32
|
+
|
33
|
+
def pages
|
34
|
+
@pages ||= begin
|
35
|
+
pages = resources_of_type(Elements::Resources::Page).each do |page|
|
36
|
+
assign_title(page)
|
37
|
+
end
|
38
|
+
filter_pages(pages)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
def quizzes
|
44
|
+
@quizzes ||= resources_of_type(Elements::Resources::Assessment).each do |quiz|
|
45
|
+
assign_title(quiz) unless quiz.title
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
private
|
51
|
+
def filter_pages(pages)
|
52
|
+
pages.select do |page|
|
53
|
+
(page.href =~ /wiki/ || page.href =~ /html$/) && page.title
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def assign_title(obj)
|
58
|
+
if ref = find_item_by_ref(obj.identifier)
|
59
|
+
obj.title = ref.title
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def resources_of_type(type)
|
64
|
+
manifest.resources.select { |r| r.type =~ type.pattern }
|
65
|
+
end
|
66
|
+
|
67
|
+
def find_item_by_ref(ref)
|
68
|
+
all_items.detect do |item|
|
69
|
+
item.identifierref == ref
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def all_items
|
74
|
+
@all_items ||= begin
|
75
|
+
modules.each_with_object([]) do |mod, memo|
|
76
|
+
memo << mod.items
|
77
|
+
mod.items.each do |item|
|
78
|
+
memo << item.items if item.items
|
79
|
+
end
|
80
|
+
end.flatten
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module CommonCartridge
|
2
|
+
module Parsers
|
3
|
+
class Dependencies
|
4
|
+
attr_reader :resource
|
5
|
+
|
6
|
+
def initialize(zipfile, resource)
|
7
|
+
@zipfile = zipfile
|
8
|
+
@resource = resource
|
9
|
+
end
|
10
|
+
|
11
|
+
def parse!(package)
|
12
|
+
resource.dependencies.each do |d|
|
13
|
+
resource = package.find_resource(d.identifierref)
|
14
|
+
resource.files.each do |f|
|
15
|
+
Parser.use_file(@zipfile, f.href) do |xml|
|
16
|
+
d.contents << CommonCartridge::Elements::Resources::Content.parse(xml)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module CommonCartridge
|
2
|
+
module Parsers
|
3
|
+
class Files
|
4
|
+
attr_reader :resource
|
5
|
+
|
6
|
+
def initialize(zipfile, resource)
|
7
|
+
@zipfile = zipfile
|
8
|
+
@resource = resource
|
9
|
+
end
|
10
|
+
|
11
|
+
def parse!(package)
|
12
|
+
if mapping = resource_to_mapping(resource)
|
13
|
+
resource.files.each do |f|
|
14
|
+
Parser.use_file(@zipfile, f.href) do |xml|
|
15
|
+
f.content = mapping.last.parse(xml)
|
16
|
+
f.content.identifier = resource.identifier
|
17
|
+
if resource.type =~ CommonCartridge::Elements::Resources::Assignment.pattern && f.href =~ /assignment.*xml/ && !(resource.href =~ /course/)
|
18
|
+
doc = Nokogiri::XML(xml)
|
19
|
+
doc.remove_namespaces!
|
20
|
+
f.content.points_possible = doc.xpath('//points_possible').text
|
21
|
+
package.assignments << f.content
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
def resource_to_mapping(resource)
|
30
|
+
CommonCartridge::Elements::Resources.type_mappings.detect { |regex, klass| resource.type =~ regex }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module CommonCartridge
|
2
|
+
module Parsers
|
3
|
+
class Items
|
4
|
+
def initialize(zipfile, mod, resources)
|
5
|
+
@zipfile = zipfile
|
6
|
+
@mod = mod
|
7
|
+
@resources = resources
|
8
|
+
end
|
9
|
+
|
10
|
+
def parse!
|
11
|
+
@mod.items.each do |item|
|
12
|
+
map_item_to_type(item)
|
13
|
+
unless item.items.empty?
|
14
|
+
item.items.each { |i| map_item_to_type(i) }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
def map_item_to_type(item)
|
21
|
+
if resource = find_resource_by_ref(item.identifierref)
|
22
|
+
if types = Elements::Resources.type_mappings.detect { |pattern, type| resource.type =~ pattern }
|
23
|
+
item.type = types.last.type
|
24
|
+
else !resource.type.empty?
|
25
|
+
item.type = resource.type
|
26
|
+
end
|
27
|
+
|
28
|
+
if item.type == :page && resource.href =~ /web_resources/
|
29
|
+
item.type = 'file'
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
if item.type.nil? || item.type.empty?
|
34
|
+
Parser.use_file(@zipfile, 'course_settings/module_meta.xml') do |xml|
|
35
|
+
doc = Nokogiri::XML(xml)
|
36
|
+
doc.remove_namespaces!
|
37
|
+
item.type = snake_case(doc.xpath("//item[@identifier = '#{item.identifier}' or @identifier='#{item.identifierref}']/content_type").text)
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def find_resource_by_ref(ref)
|
44
|
+
@resources.detect do |resource|
|
45
|
+
resource.identifier == ref
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def snake_case(text)
|
50
|
+
text.gsub(/::/, '/').
|
51
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
52
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
53
|
+
tr("-", "_").
|
54
|
+
downcase
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module CommonCartridge
|
2
|
+
module Parsers
|
3
|
+
class Outcomes
|
4
|
+
def initialize(zipfile)
|
5
|
+
@zipfile = zipfile
|
6
|
+
end
|
7
|
+
|
8
|
+
def parse!
|
9
|
+
Parser.use_file(@zipfile, 'course_settings/learning_outcomes.xml') do |xml|
|
10
|
+
return CommonCartridge::Elements::Outcomes::OutcomeRoot.parse(xml)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'zip'
|
2
|
+
|
3
|
+
require 'common_cartridge/parsers/dependencies'
|
4
|
+
require 'common_cartridge/parsers/files'
|
5
|
+
require 'common_cartridge/parsers/outcomes'
|
6
|
+
require 'common_cartridge/parsers/questions'
|
7
|
+
require 'common_cartridge/parsers/items'
|
8
|
+
|
9
|
+
module CommonCartridge
|
10
|
+
module Parsers
|
11
|
+
class Parser
|
12
|
+
class ManifestDocument
|
13
|
+
include SAXMachine
|
14
|
+
|
15
|
+
element :manifest, class: CommonCartridge::Elements::Manifest, required: true
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.use_file(zipfile, path)
|
19
|
+
Zip::File.open(File.join(CommonCartridge.config.import_directory, zipfile)) do |file|
|
20
|
+
f = file.glob(path).first
|
21
|
+
unless f.nil?
|
22
|
+
yield f.get_input_stream.read
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize(zipfile)
|
28
|
+
@zipfile = zipfile
|
29
|
+
@package = Package.new
|
30
|
+
end
|
31
|
+
|
32
|
+
def parse
|
33
|
+
Parser.use_file(@zipfile, 'imsmanifest.xml') do |xml|
|
34
|
+
@package.manifest = ManifestDocument.parse(xml).manifest
|
35
|
+
end
|
36
|
+
|
37
|
+
parse_content!
|
38
|
+
@package.outcomes = parse_outcomes
|
39
|
+
parse_questions!
|
40
|
+
parse_module_items!
|
41
|
+
@package
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
def parse_module_items!
|
46
|
+
@package.modules.each do |mod|
|
47
|
+
item_parser = Parsers::Items.new(@zipfile, mod, @package.manifest.resources)
|
48
|
+
item_parser.parse!
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def parse_outcomes
|
53
|
+
outcomes_parser = Parsers::Outcomes.new(@zipfile)
|
54
|
+
outcomes_parser.parse!
|
55
|
+
end
|
56
|
+
|
57
|
+
def parse_content!
|
58
|
+
@package.manifest.resources.map do |resource|
|
59
|
+
parse_resource! resource
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def parse_resource! resource
|
64
|
+
file_parser = Parsers::Files.new(@zipfile, resource)
|
65
|
+
file_parser.parse!(@package)
|
66
|
+
dependency_parser = Parsers::Dependencies.new(@zipfile, resource)
|
67
|
+
dependency_parser.parse!(@package)
|
68
|
+
rescue RuntimeError => e
|
69
|
+
Rails.logger.error "CommonCartridge parse error: #{e.to_s}"
|
70
|
+
Rails.logger.error e.backtrace
|
71
|
+
end
|
72
|
+
|
73
|
+
def parse_questions!
|
74
|
+
@package.quizzes.each do |quiz|
|
75
|
+
parser = Parsers::Questions.new(@zipfile, quiz)
|
76
|
+
parser.parse!
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
|