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.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/lib/common_cartridge.rb +40 -0
  3. data/lib/common_cartridge/elements/lom.rb +77 -0
  4. data/lib/common_cartridge/elements/manifest.rb +30 -0
  5. data/lib/common_cartridge/elements/metadata.rb +12 -0
  6. data/lib/common_cartridge/elements/organizations.rb +40 -0
  7. data/lib/common_cartridge/elements/outcomes.rb +10 -0
  8. data/lib/common_cartridge/elements/outcomes/outcome.rb +15 -0
  9. data/lib/common_cartridge/elements/outcomes/outcome_group.rb +12 -0
  10. data/lib/common_cartridge/elements/outcomes/outcome_root.rb +11 -0
  11. data/lib/common_cartridge/elements/resources.rb +119 -0
  12. data/lib/common_cartridge/elements/resources/assessment.rb +21 -0
  13. data/lib/common_cartridge/elements/resources/assignment.rb +59 -0
  14. data/lib/common_cartridge/elements/resources/attachments.rb +20 -0
  15. data/lib/common_cartridge/elements/resources/basic_lti_link.rb +66 -0
  16. data/lib/common_cartridge/elements/resources/page.rb +22 -0
  17. data/lib/common_cartridge/elements/resources/topic.rb +28 -0
  18. data/lib/common_cartridge/elements/resources/web_link.rb +25 -0
  19. data/lib/common_cartridge/package.rb +87 -0
  20. data/lib/common_cartridge/parsers/dependencies.rb +23 -0
  21. data/lib/common_cartridge/parsers/files.rb +34 -0
  22. data/lib/common_cartridge/parsers/items.rb +58 -0
  23. data/lib/common_cartridge/parsers/outcomes.rb +17 -0
  24. data/lib/common_cartridge/parsers/parser.rb +84 -0
  25. data/lib/common_cartridge/parsers/questions.rb +34 -0
  26. data/lib/common_cartridge/version.rb +3 -0
  27. data/spec/elements/lom_spec.rb +66 -0
  28. data/spec/elements/manifest_spec.rb +31 -0
  29. data/spec/elements/metadata_spec.rb +40 -0
  30. data/spec/elements/organizations_spec.rb +51 -0
  31. data/spec/elements/outcomes/outcome_group_spec.rb +29 -0
  32. data/spec/elements/outcomes/outcome_spec.rb +37 -0
  33. data/spec/elements/resources/assignment_spec.rb +53 -0
  34. data/spec/elements/resources/attachments_spec.rb +24 -0
  35. data/spec/elements/resources/basic_lti_link_spec.rb +29 -0
  36. data/spec/elements/resources/resources_spec.rb +73 -0
  37. data/spec/elements/resources/topic_spec.rb +39 -0
  38. data/spec/elements/resources/web_link_spec.rb +31 -0
  39. data/spec/files/canvas_large_1.3.imscc +0 -0
  40. data/spec/files/canvas_small_1.1.corrupt.imscc +0 -0
  41. data/spec/files/canvas_small_1.1.imscc +0 -0
  42. data/spec/package_spec.rb +110 -0
  43. data/spec/parser_spec.rb +13 -0
  44. data/spec/spec_helper.rb +13 -0
  45. 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
+