common_cartridge 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 (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
+