senkyoshi 1.0.1 → 1.0.2

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.
@@ -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