common_cartridge 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/lib/common_cartridge.rb +40 -0
  3. data/lib/common_cartridge/elements/lom.rb +60 -0
  4. data/lib/common_cartridge/elements/manifest.rb +24 -0
  5. data/lib/common_cartridge/elements/metadata.rb +12 -0
  6. data/lib/common_cartridge/elements/organizations.rb +42 -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 +117 -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/page.rb +22 -0
  16. data/lib/common_cartridge/elements/resources/topic.rb +28 -0
  17. data/lib/common_cartridge/elements/resources/web_link.rb +25 -0
  18. data/lib/common_cartridge/package.rb +87 -0
  19. data/lib/common_cartridge/parsers/dependencies.rb +23 -0
  20. data/lib/common_cartridge/parsers/files.rb +34 -0
  21. data/lib/common_cartridge/parsers/items.rb +58 -0
  22. data/lib/common_cartridge/parsers/outcomes.rb +17 -0
  23. data/lib/common_cartridge/parsers/parser.rb +78 -0
  24. data/lib/common_cartridge/parsers/questions.rb +34 -0
  25. data/lib/sax_machine/sax_handler.rb +13 -0
  26. data/spec/elements/lom_spec.rb +66 -0
  27. data/spec/elements/manifest_spec.rb +27 -0
  28. data/spec/elements/metadata_spec.rb +40 -0
  29. data/spec/elements/organizations_spec.rb +51 -0
  30. data/spec/elements/outcomes/outcome_group_spec.rb +29 -0
  31. data/spec/elements/outcomes/outcome_spec.rb +37 -0
  32. data/spec/elements/resources/assignment_spec.rb +53 -0
  33. data/spec/elements/resources/attachments_spec.rb +24 -0
  34. data/spec/elements/resources/resources_spec.rb +73 -0
  35. data/spec/elements/resources/topic_spec.rb +39 -0
  36. data/spec/elements/resources/web_link_spec.rb +31 -0
  37. data/spec/files/canvas_large_1.3.imscc +0 -0
  38. data/spec/files/canvas_small_1.1.corrupt.imscc +0 -0
  39. data/spec/files/canvas_small_1.1.imscc +0 -0
  40. data/spec/package_spec.rb +110 -0
  41. data/spec/parser_spec.rb +13 -0
  42. data/spec/spec_helper.rb +13 -0
  43. metadata +179 -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 :attachment, class: Attachment, as: :attachments
16
+ end
17
+ end
18
+ end
19
+ end
20
+ 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 :title
10
+ element :text
11
+ element :text, value: :texttype, as: :text_type
12
+ element :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,78 @@
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
+ def self.use_file(zipfile, path)
13
+ Zip::File.open(File.join(CommonCartridge.config.import_directory, zipfile)) do |file|
14
+ f = file.glob(path).first
15
+ unless f.nil?
16
+ yield f.get_input_stream.read
17
+ end
18
+ end
19
+ end
20
+
21
+ def initialize(zipfile)
22
+ @zipfile = zipfile
23
+ @package = Package.new
24
+ end
25
+
26
+ def parse
27
+ Parser.use_file(@zipfile, 'imsmanifest.xml') do |xml|
28
+ @package.manifest = CommonCartridge::Elements::Manifest.parse(xml)
29
+ end
30
+
31
+ parse_content!
32
+ @package.outcomes = parse_outcomes
33
+ parse_questions!
34
+ parse_module_items!
35
+ @package
36
+ end
37
+
38
+ private
39
+ def parse_module_items!
40
+ @package.modules.each do |mod|
41
+ item_parser = Parsers::Items.new(@zipfile, mod, @package.manifest.resources)
42
+ item_parser.parse!
43
+ end
44
+ end
45
+
46
+ def parse_outcomes
47
+ outcomes_parser = Parsers::Outcomes.new(@zipfile)
48
+ outcomes_parser.parse!
49
+ end
50
+
51
+ def parse_content!
52
+ @package.manifest.resources.map do |resource|
53
+ parse_resource! resource
54
+ end
55
+ end
56
+
57
+ def parse_resource! resource
58
+ file_parser = Parsers::Files.new(@zipfile, resource)
59
+ file_parser.parse!(@package)
60
+ dependency_parser = Parsers::Dependencies.new(@zipfile, resource)
61
+ dependency_parser.parse!(@package)
62
+ rescue RuntimeError => e
63
+ Rails.logger.error "CommonCartridge parse error: #{e.to_s}"
64
+ Rails.logger.error e.backtrace
65
+ end
66
+
67
+ def parse_questions!
68
+ @package.quizzes.each do |quiz|
69
+ parser = Parsers::Questions.new(@zipfile, quiz)
70
+ parser.parse!
71
+ end
72
+ end
73
+
74
+ end
75
+ end
76
+ end
77
+
78
+
@@ -0,0 +1,34 @@
1
+ module CommonCartridge
2
+ module Parsers
3
+ class Questions
4
+ def initialize(zipfile, quiz)
5
+ @zipfile = zipfile
6
+ @quiz = quiz
7
+ end
8
+
9
+ def parse!
10
+ @quiz.file_locations.each do |location|
11
+ Parser.use_file(@zipfile, location) do |xml|
12
+ doc = Nokogiri::XML(xml)
13
+ doc.remove_namespaces!
14
+ unless @quiz.question_count && !canvas?(location)
15
+ # We need all questions that aren't a text only question. Re-usable, important sibling XML elements *sigh*.
16
+ @quiz.question_count = doc.xpath("//item//fieldlabel[text()='question_type' or text()='cc_profile']/following-sibling::fieldentry[1][text() != 'text_only_question']").length
17
+ end
18
+
19
+ unless @quiz.points_possible
20
+ if points = doc.xpath("//fieldlabel[text()='points_possible']/following-sibling::fieldentry[1]")
21
+ @quiz.points_possible = points.text
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ private
29
+ def canvas?(location)
30
+ location =~ /non_cc_assessments/
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,13 @@
1
+ module SAXMachine
2
+ class SAXHandler
3
+
4
+ # Not a great solution; will be solved gracefully with CNVS-13964
5
+ remove_method :normalize_name
6
+ def normalize_name(name)
7
+ # strip namespaces
8
+ name = name.split(":").last
9
+ name.gsub(/\-/, '_')
10
+ end
11
+ end
12
+ end
13
+