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