senkyoshi 1.0.1 → 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +7 -2
- data/lib/senkyoshi/canvas_course.rb +15 -9
- data/lib/senkyoshi/models/assessment.rb +4 -93
- data/lib/senkyoshi/models/attachment.rb +21 -0
- data/lib/senkyoshi/models/content.rb +39 -18
- data/lib/senkyoshi/models/content_file.rb +21 -4
- data/lib/senkyoshi/models/external_url.rb +9 -0
- data/lib/senkyoshi/models/file.rb +22 -21
- data/lib/senkyoshi/models/gradebook.rb +5 -8
- data/lib/senkyoshi/models/module_item.rb +3 -1
- data/lib/senkyoshi/models/qti.rb +215 -0
- data/lib/senkyoshi/models/question.rb +3 -3
- data/lib/senkyoshi/models/question_bank.rb +46 -0
- data/lib/senkyoshi/models/questions/fill_in_blank.rb +3 -1
- data/lib/senkyoshi/models/questions/fill_in_blank_plus.rb +3 -1
- data/lib/senkyoshi/models/questions/matching.rb +8 -3
- data/lib/senkyoshi/models/questions/multiple_choice.rb +12 -0
- data/lib/senkyoshi/models/questions/numerical.rb +4 -1
- data/lib/senkyoshi/models/questions/true_false.rb +1 -1
- data/lib/senkyoshi/models/resource.rb +8 -6
- data/lib/senkyoshi/models/scorm_package.rb +51 -34
- data/lib/senkyoshi/models/staff_info.rb +49 -31
- data/lib/senkyoshi/models/survey.rb +10 -0
- data/lib/senkyoshi/models/wikipage.rb +55 -9
- data/lib/senkyoshi/version.rb +1 -2
- data/lib/senkyoshi/xml_parser.rb +60 -31
- data/lib/senkyoshi.rb +14 -8
- metadata +8 -3
@@ -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
|
-
|
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
|
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
|
data/lib/senkyoshi/version.rb
CHANGED
data/lib/senkyoshi/xml_parser.rb
CHANGED
@@ -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/
|
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: "
|
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
|
-
|
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
|
-
|
73
|
-
|
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
|
-
|
121
|
-
|
122
|
-
|
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
|
-
|
129
|
-
|
130
|
-
|
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?(
|
134
|
-
|
135
|
-
|
136
|
-
|
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.
|
141
|
-
|
142
|
-
|
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?(
|
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
|
-
|
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.
|
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:
|
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.
|
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
|