senkyoshi 1.0.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,27 +2,29 @@ require "senkyoshi/models/content"
2
2
 
3
3
  module Senkyoshi
4
4
  class WikiPage < Content
5
+ include Senkyoshi
6
+
5
7
  def canvas_conversion(course, resources)
6
8
  unless @title == "--TOP--"
7
9
  page_count = course.pages.
8
10
  select { |p| p.title.start_with? @title }.count
9
11
  @title = "#{@title}-#{page_count + 1}" if page_count > 0
10
12
  page = CanvasCc::CanvasCC::Models::Page.new
11
- if !@url.empty?
12
- @body = %{
13
- <a href="#{@url}">
14
- #{@url}
15
- </a>
16
- #{@body}
17
- }
18
- end
13
+ @body = _set_body(@body, @url, @extendeddata)
19
14
  page.body = fix_html(@body, resources)
20
15
  page.identifier = @id
21
16
  page.page_name = @title
22
17
  page.workflow_state = "active"
23
18
 
24
19
  # Add page links to page body
25
- @files.each { |f| page.body << f.canvas_conversion }
20
+ @files.each do |file|
21
+ if canvas_file = course.files.detect { |f| f.identifier == file.name }
22
+ page.body << file.canvas_conversion(canvas_file)
23
+ else
24
+ page.body << "<p>File: " + file.linkname +
25
+ " -- doesn't exist in blackboard</p>"
26
+ end
27
+ end
26
28
  course.pages << page
27
29
 
28
30
  course = create_module(course)
@@ -30,5 +32,49 @@ module Senkyoshi
30
32
 
31
33
  course
32
34
  end
35
+
36
+ def _set_body(original_body, url, extendeddata)
37
+ body = original_body.dup
38
+ if !url.empty?
39
+ body = %{
40
+ <a href="#{url}">
41
+ #{url}
42
+ </a>
43
+ #{body}
44
+ }
45
+ end
46
+ if extendeddata
47
+ body = %{
48
+ #{body}
49
+ #{_extendeddata(extendeddata)}
50
+ }
51
+ end
52
+ body
53
+ end
54
+
55
+ def _extendeddata(extendeddata)
56
+ Nokogiri::XML(extendeddata).
57
+ search("LessonPlanComponent").
58
+ map do |node|
59
+ _component_label(node) + node.search("componentValue/@value").to_s
60
+ end.
61
+ compact.
62
+ join(" ")
63
+ end
64
+
65
+ def _component_label(node)
66
+ visible = true?(node.search("vislableToStudents/@value").to_s)
67
+ if visible
68
+ component_label = node.search("componentLabel/@value").to_s
69
+ overridden = true?(node.search("labelOverridden/@value").to_s)
70
+ if overridden
71
+ component_label
72
+ else
73
+ component_label.split(".").last.capitalize
74
+ end
75
+ else
76
+ ""
77
+ end
78
+ end
33
79
  end
34
80
  end
@@ -1,4 +1,3 @@
1
-
2
1
  module Senkyoshi
3
- VERSION = "1.0.1".freeze
2
+ VERSION = "1.0.2".freeze
4
3
  end
@@ -16,9 +16,13 @@ require "senkyoshi/models/questions/quiz_bowl"
16
16
  require "senkyoshi/models/questions/short_response"
17
17
  require "senkyoshi/models/questions/true_false"
18
18
 
19
+ require "senkyoshi/models/assessment"
20
+ require "senkyoshi/models/question_bank"
21
+ require "senkyoshi/models/survey"
22
+
19
23
  require "senkyoshi/models/announcement"
20
24
  require "senkyoshi/models/answer"
21
- require "senkyoshi/models/assessment"
25
+ require "senkyoshi/models/qti"
22
26
  require "senkyoshi/models/assignment"
23
27
  require "senkyoshi/models/assignment_group"
24
28
  require "senkyoshi/models/blog"
@@ -37,6 +41,8 @@ require "senkyoshi/models/resource"
37
41
  require "senkyoshi/models/scorm_package"
38
42
  require "senkyoshi/models/staff_info"
39
43
  require "senkyoshi/models/wikipage"
44
+ require "senkyoshi/models/attachment"
45
+ require "senkyoshi/models/external_url"
40
46
 
41
47
  require "senkyoshi/exceptions"
42
48
 
@@ -47,7 +53,7 @@ module Senkyoshi
47
53
  announcement: "Announcement",
48
54
  forum: "Forum",
49
55
  course: "Course",
50
- questestinterop: "Assessment",
56
+ questestinterop: "QTI",
51
57
  content: "Content",
52
58
  staffinfo: "StaffInfo",
53
59
  }.freeze
@@ -55,22 +61,32 @@ module Senkyoshi
55
61
  PRE_RESOURCE_TYPE = {
56
62
  content: "Content",
57
63
  gradebook: "Gradebook",
64
+ courseassessment: "QTI",
58
65
  }.freeze
59
66
 
60
- def self.parse_manifest(zip_file, manifest)
67
+ def self.parse_manifest(zip_file, manifest, resource_xids)
61
68
  doc = Nokogiri::XML.parse(manifest)
62
69
  resources = doc.at("resources")
63
- iterate_xml(resources, zip_file).flatten - ["", nil]
70
+ organizations = doc.at("organizations")
71
+ iterate_xml(organizations, resources, zip_file, resource_xids).
72
+ flatten - ["", nil]
64
73
  end
65
74
 
66
- def self.iterate_xml(resources, zip_file)
67
- pre_data = pre_iterator(resources, zip_file)
75
+ def self.iterate_xml(organizations, resources, zip_file, resource_xids)
76
+ pre_data = pre_iterator(organizations, resources, zip_file)
77
+ staff_info = StaffInfo.new
68
78
  iterator_master(resources, zip_file) do |xml_data, type, file|
69
79
  if RESOURCE_TYPE[type.to_sym]
70
80
  single_pre_data = get_single_pre_data(pre_data, file)
71
81
  res_class = Senkyoshi.const_get RESOURCE_TYPE[type.to_sym]
72
- if type == "content"
73
- Content.from(xml_data, single_pre_data)
82
+ case type
83
+ when "content"
84
+ Content.from(xml_data, single_pre_data, resource_xids)
85
+ when "questestinterop"
86
+ single_pre_data ||= { file_name: file }
87
+ QTI.from(xml_data, single_pre_data)
88
+ when "staffinfo"
89
+ staff_info.iterate_xml(xml_data, single_pre_data)
74
90
  else
75
91
  resource = res_class.new
76
92
  resource.iterate_xml(xml_data, single_pre_data)
@@ -98,7 +114,7 @@ module Senkyoshi
98
114
  end
99
115
  end
100
116
 
101
- def self.pre_iterator(resources, zip_file)
117
+ def self.pre_iterator(organizations, resources, zip_file)
102
118
  pre_data = {}
103
119
  iterator_master(resources, zip_file) do |xml_data, type, file|
104
120
  if PRE_RESOURCE_TYPE[type.to_sym]
@@ -109,41 +125,46 @@ module Senkyoshi
109
125
  end
110
126
  end
111
127
  pre_data = connect_content(pre_data)
112
- build_heirarchy(pre_data)
128
+ build_heirarchy(organizations, pre_data)
113
129
  end
114
130
 
115
131
  def self.connect_content(pre_data)
116
132
  pre_data["content"].each do |content|
117
133
  gradebook = pre_data["gradebook"].first.
118
134
  detect { |g| g[:content_id] == content[:file_name] }
119
- if gradebook
120
- content[:points] = gradebook[:points] || ""
121
- content[:assignment_id] = gradebook[:assignment_id] || ""
122
- end
135
+ content.merge!(gradebook) if gradebook
136
+
137
+ course_assessment = pre_data["courseassessment"].
138
+ detect { |ca| ca[:original_file_name] == content[:assignment_id] }
139
+ content.merge!(course_assessment) if course_assessment
123
140
  end
124
141
  pre_data["content"]
125
142
  end
126
143
 
127
- def self.build_heirarchy(pre_data)
128
- parents_ids = pre_data.
129
- select { |p| p[:parent_id] == "{unset id}" }.
130
- map { |u| u[:id] }
144
+ def self.build_heirarchy(organizations, pre_data)
145
+ unset_id = "{unset id}"
146
+ parents = pre_data.
147
+ select { |p| p[:parent_id] == unset_id }
148
+ parents_ids = parents.map { |u| u[:id] }
131
149
  pre_data.each do |content|
150
+ parent_id = content[:parent_id]
151
+ parent = pre_data.detect { |p| p[:id] == parent_id }
152
+ if parent_id == unset_id
153
+ content[:title] = get_title(organizations, content)
154
+ elsif parent[:parent_id] == unset_id
155
+ content[:parent_title] = parent[:title]
156
+ end
132
157
  next if parents_ids.include?(content[:id])
133
- next if parents_ids.include?(content[:parent_id])
134
- parent_id = get_master_parent(pre_data, parents_ids,
135
- content[:parent_id])
136
- content[:parent_id] = parent_id
158
+ next if parents_ids.include?(parent_id)
159
+ parents_ids << parent_id
160
+ parent[:parent_id] = parent[:id]
161
+ parent[:parent_title] = nil
137
162
  end
138
163
  end
139
164
 
140
- def self.get_master_parent(pre_data, parents_ids, parent_id)
141
- parent = pre_data.detect { |p| p[:id] == parent_id }
142
- if parents_ids.include? parent[:id]
143
- parent[:id]
144
- else
145
- get_master_parent(pre_data, parents_ids, parent[:parent_id])
146
- end
165
+ def self.get_title(organizations, content)
166
+ item = organizations.at("item[@identifierref=#{content[:file_name]}]")
167
+ item.parent.at("title").text
147
168
  end
148
169
 
149
170
  ##
@@ -153,17 +174,25 @@ module Senkyoshi
153
174
  def self.iterate_files(zipfile)
154
175
  files = zipfile.entries.select(&:file?)
155
176
 
177
+ dir_names = zipfile.entries.map { |entry| File.dirname(entry.name) }.uniq
156
178
  file_names = files.map(&:name)
179
+ entry_names = dir_names + file_names
180
+
157
181
  scorm_paths = ScormPackage.find_scorm_paths(zipfile)
158
182
 
159
183
  files.select do |file|
160
- SenkyoshiFile.valid_file?(file_names, scorm_paths, file)
184
+ SenkyoshiFile.valid_file?(entry_names, scorm_paths, file)
161
185
  end.
162
186
  map { |file| SenkyoshiFile.new(file) }
163
187
  end
164
188
 
189
+ ##
190
+ # Create a random hex prepended with aj_
191
+ # This is because the instructure qti migration tool requires
192
+ # the first character to be a letter.
193
+ ##
165
194
  def self.create_random_hex
166
- SecureRandom.hex
195
+ "aj_" + SecureRandom.hex(32)
167
196
  end
168
197
 
169
198
  def self.get_attribute_value(xml_data, type)
data/lib/senkyoshi.rb CHANGED
@@ -12,7 +12,6 @@ require "zip"
12
12
  require "senkyoshi/exceptions"
13
13
 
14
14
  module Senkyoshi
15
- IMPORTED_FILES_DIRNAME = "Imported".freeze
16
15
  BASE = "$IMS-CC-FILEBASE$".freeze
17
16
 
18
17
  def self.parse(zip_path, imscc_path)
@@ -20,11 +19,14 @@ module Senkyoshi
20
19
  manifest = read_file(file, "imsmanifest.xml")
21
20
 
22
21
  resources = Senkyoshi::Collection.new
23
- resources.add(Senkyoshi.parse_manifest(file, manifest))
24
22
  resources.add(Senkyoshi.iterate_files(file))
23
+ resource_xids = resources.resources.
24
+ map(&:xid).
25
+ select { |r| r.include?("xid-") }
26
+ resources.add(Senkyoshi.parse_manifest(file, manifest, resource_xids))
25
27
 
26
28
  course = create_canvas_course(resources, zip_path)
27
- build_file(course, imscc_path)
29
+ build_file(course, imscc_path, resources)
28
30
  end
29
31
  end
30
32
 
@@ -34,20 +36,19 @@ module Senkyoshi
34
36
  raise Exceptions::MissingFileError
35
37
  end
36
38
 
37
- def self.build_file(course, imscc_path)
39
+ def self.build_file(course, imscc_path, resources)
38
40
  folder = imscc_path.split("/").first
39
41
  file = CanvasCc::CanvasCC::CartridgeCreator.new(course).create(folder)
40
42
  File.rename(file, imscc_path)
41
- cleanup
43
+ cleanup resources
42
44
  puts "Created a file #{imscc_path}"
43
45
  end
44
46
 
45
47
  ##
46
48
  # Perform any necessary cleanup from creating canvas cartridge
47
49
  ##
48
- def self.cleanup
49
- SenkyoshiFile.cleanup
50
- ScormPackage.cleanup
50
+ def self.cleanup(resources)
51
+ resources.each(&:cleanup)
51
52
  end
52
53
 
53
54
  def self.create_canvas_course(resources, zip_name)
@@ -64,6 +65,11 @@ module Senkyoshi
64
65
  Zip::File.open(blackboard_file_path, "rb") do |bb_zip|
65
66
  course = Senkyoshi::CanvasCourse.from_metadata(metadata, bb_zip)
66
67
  course.upload_content(canvas_file_path)
68
+ cleanup course.scorm_packages
67
69
  end
68
70
  end
71
+
72
+ def true?(obj)
73
+ obj.to_s == "true"
74
+ end
69
75
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: senkyoshi
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Atomic Jolt
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-12-27 00:00:00.000000000 Z
11
+ date: 2017-01-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pry-byebug
@@ -173,17 +173,21 @@ files:
173
173
  - lib/senkyoshi/models/assessment.rb
174
174
  - lib/senkyoshi/models/assignment.rb
175
175
  - lib/senkyoshi/models/assignment_group.rb
176
+ - lib/senkyoshi/models/attachment.rb
176
177
  - lib/senkyoshi/models/blog.rb
177
178
  - lib/senkyoshi/models/content.rb
178
179
  - lib/senkyoshi/models/content_file.rb
179
180
  - lib/senkyoshi/models/course.rb
181
+ - lib/senkyoshi/models/external_url.rb
180
182
  - lib/senkyoshi/models/file.rb
181
183
  - lib/senkyoshi/models/forum.rb
182
184
  - lib/senkyoshi/models/gradebook.rb
183
185
  - lib/senkyoshi/models/group.rb
184
186
  - lib/senkyoshi/models/module.rb
185
187
  - lib/senkyoshi/models/module_item.rb
188
+ - lib/senkyoshi/models/qti.rb
186
189
  - lib/senkyoshi/models/question.rb
190
+ - lib/senkyoshi/models/question_bank.rb
187
191
  - lib/senkyoshi/models/questions/calculated.rb
188
192
  - lib/senkyoshi/models/questions/either_or.rb
189
193
  - lib/senkyoshi/models/questions/essay.rb
@@ -205,6 +209,7 @@ files:
205
209
  - lib/senkyoshi/models/resource.rb
206
210
  - lib/senkyoshi/models/scorm_package.rb
207
211
  - lib/senkyoshi/models/staff_info.rb
212
+ - lib/senkyoshi/models/survey.rb
208
213
  - lib/senkyoshi/models/wikipage.rb
209
214
  - lib/senkyoshi/tasks.rb
210
215
  - lib/senkyoshi/version.rb
@@ -229,7 +234,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
229
234
  version: '0'
230
235
  requirements: []
231
236
  rubyforge_project:
232
- rubygems_version: 2.5.1
237
+ rubygems_version: 2.4.8
233
238
  signing_key:
234
239
  specification_version: 4
235
240
  summary: Converts Blackboard zip file to Canvas Common Cartridge