Module: Senkyoshi

Included in:
OutcomeDefinition, WikiPage
Defined in:
lib/senkyoshi.rb,
lib/senkyoshi/tasks.rb,
lib/senkyoshi/version.rb,
lib/senkyoshi/collection.rb,
lib/senkyoshi/models/qti.rb,
lib/senkyoshi/xml_parser.rb,
lib/senkyoshi/models/blog.rb,
lib/senkyoshi/models/file.rb,
lib/senkyoshi/models/quiz.rb,
lib/senkyoshi/models/forum.rb,
lib/senkyoshi/models/group.rb,
lib/senkyoshi/canvas_course.rb,
lib/senkyoshi/configuration.rb,
lib/senkyoshi/models/answer.rb,
lib/senkyoshi/models/course.rb,
lib/senkyoshi/models/module.rb,
lib/senkyoshi/models/survey.rb,
lib/senkyoshi/models/content.rb,
lib/senkyoshi/models/question.rb,
lib/senkyoshi/models/resource.rb,
lib/senkyoshi/models/wikipage.rb,
lib/senkyoshi/models/gradebook.rb,
lib/senkyoshi/models/assessment.rb,
lib/senkyoshi/models/assignment.rb,
lib/senkyoshi/models/attachment.rb,
lib/senkyoshi/models/staff_info.rb,
lib/senkyoshi/models/module_item.rb,
lib/senkyoshi/models/announcement.rb,
lib/senkyoshi/models/content_file.rb,
lib/senkyoshi/models/external_url.rb,
lib/senkyoshi/models/file_resource.rb,
lib/senkyoshi/models/question_bank.rb,
lib/senkyoshi/models/scorm_package.rb,
lib/senkyoshi/models/questions/essay.rb,
lib/senkyoshi/models/assignment_group.rb,
lib/senkyoshi/models/outcome_definition.rb,
lib/senkyoshi/models/questions/hot_spot.rb,
lib/senkyoshi/models/questions/matching.rb,
lib/senkyoshi/models/questions/ordering.rb,
lib/senkyoshi/models/questions/either_or.rb,
lib/senkyoshi/models/questions/numerical.rb,
lib/senkyoshi/models/questions/quiz_bowl.rb,
lib/senkyoshi/models/questions/calculated.rb,
lib/senkyoshi/models/questions/true_false.rb,
lib/senkyoshi/models/questions/file_upload.rb,
lib/senkyoshi/models/questions/fill_in_blank.rb,
lib/senkyoshi/models/questions/opinion_scale.rb,
lib/senkyoshi/models/questions/short_response.rb,
lib/senkyoshi/models/questions/multiple_answer.rb,
lib/senkyoshi/models/questions/multiple_choice.rb,
lib/senkyoshi/models/questions/jumbled_sentence.rb,
lib/senkyoshi/models/questions/fill_in_blank_plus.rb

Defined Under Namespace

Classes: Announcement, Answer, Assessment, Assignment, AssignmentGroup, Attachment, Blog, Calculated, CanvasCourse, Collection, Configuration, Content, ContentFile, Course, EitherOr, Essay, ExternalUrl, FileResource, FileUpload, FillInBlank, FillInBlankPlus, Forum, Gradebook, Group, HotSpot, JumbledSentence, Matching, Module, ModuleItem, MultipleAnswer, MultipleChoice, NumericalQuestion, OpinionScale, Ordering, OutcomeDefinition, QTI, Question, QuestionBank, Quiz, QuizBowl, Resource, ScormPackage, SenkyoshiFile, ShortResponse, StaffInfo, Survey, Tasks, TrueFalse, WikiPage

Constant Summary collapse

FILE_BASE =
"$IMS-CC-FILEBASE$".freeze
DIR_BASE =
"$CANVAS_COURSE_REFERENCE$/files/folder".freeze
VERSION =
"1.0.3".freeze
RESOURCE_TYPE =
{
  groups: "Group",
  blog: "Blog",
  announcement: "Announcement",
  forum: "Forum",
  course: "Course",
  questestinterop: "QTI",
  content: "Content",
  staffinfo: "StaffInfo",
  gradebook: "Gradebook",
}.freeze
PRE_RESOURCE_TYPE =
{
  content: "Content",
  gradebook: "Gradebook",
  courseassessment: "QTI",
}.freeze

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Class Attribute Details

.configurationObject



23
24
25
# File 'lib/senkyoshi.rb', line 23

def self.configuration
  @configuration ||= Configuration.new
end

Class Method Details

.build_file(course, imscc_path, resources) ⇒ Object



61
62
63
64
65
66
67
# File 'lib/senkyoshi.rb', line 61

def self.build_file(course, imscc_path, resources)
  folder = File.dirname(imscc_path)
  file = CanvasCc::CanvasCC::CartridgeCreator.new(course).create(folder)
  File.rename(file, imscc_path)
  cleanup resources
  puts "Created a file #{imscc_path}"
end

.build_heirarchy(organizations, pre_data) ⇒ Object



142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/senkyoshi/xml_parser.rb', line 142

def self.build_heirarchy(organizations, pre_data)
  unset_id = "{unset id}"
  parents = pre_data.
    select { |p| p[:parent_id] == unset_id }
  parents_ids = parents.map { |u| u[:id] }
  pre_data.each do |content|
    parent_id = content[:parent_id]
    parent = pre_data.detect { |p| p[:id] == parent_id }
    if parent_id == unset_id
      content[:title] = get_title(organizations, content)
    elsif parent[:parent_id] == unset_id
      content[:parent_title] = parent[:title]
    end
    next if parents_ids.include?(content[:id])
    next if parents_ids.include?(parent_id)
    parents_ids << parent_id
    parent[:parent_id] = parent[:id]
    parent[:parent_title] = nil
  end
end

.cleanup(resources) ⇒ Object

Perform any necessary cleanup from creating canvas cartridge



72
73
74
# File 'lib/senkyoshi.rb', line 72

def self.cleanup(resources)
  resources.each(&:cleanup)
end

.configure {|configuration| ... } ⇒ Object

Yields:



31
32
33
# File 'lib/senkyoshi.rb', line 31

def self.configure
  yield configuration
end

.connect_content(pre_data) ⇒ Object



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/senkyoshi/xml_parser.rb', line 126

def self.connect_content(pre_data)
  pre_data["content"].each do |content|
    if pre_data["gradebook"]
      gradebook = pre_data["gradebook"].first.
        detect { |g| g[:content_id] == content[:file_name] }
      content.merge!(gradebook) if gradebook
    end
    if pre_data["courseassessment"]
      course_assessment = pre_data["courseassessment"].
        detect { |ca| ca[:original_file_name] == content[:assignment_id] }
      content.merge!(course_assessment) if course_assessment
    end
  end
  pre_data["content"]
end

.create_canvas_course(resources, zip_name) ⇒ Object



76
77
78
79
80
81
82
83
# File 'lib/senkyoshi.rb', line 76

def self.create_canvas_course(resources, zip_name)
  course = CanvasCc::CanvasCC::Models::Course.new
  course.course_code = zip_name
  resources.each do |resource|
    course = resource.canvas_conversion(course, resources)
  end
  course
end

.create_random_hexObject

Create a random hex prepended with aj_ This is because the instructure qti migration tool requires the first character to be a letter.



192
193
194
# File 'lib/senkyoshi/xml_parser.rb', line 192

def self.create_random_hex
  "aj_" + SecureRandom.hex(32)
end

.get_attribute_value(xml_data, type) ⇒ Object



196
197
198
199
200
201
202
# File 'lib/senkyoshi/xml_parser.rb', line 196

def self.get_attribute_value(xml_data, type)
  value = ""
  if xml_data.children.at(type).attributes["value"]
    value = xml_data.children.at(type).attributes["value"].value
  end
  value
end

.get_description(xml_data) ⇒ Object



212
213
214
215
216
217
218
# File 'lib/senkyoshi/xml_parser.rb', line 212

def self.get_description(xml_data)
  value = ""
  if xml_data.children.at("DESCRIPTION")
    value = xml_data.children.at("DESCRIPTION").text
  end
  value
end

.get_single_pre_data(pre_data, file) ⇒ Object



93
94
95
96
97
# File 'lib/senkyoshi/xml_parser.rb', line 93

def self.get_single_pre_data(pre_data, file)
  pre_data.detect do |d|
    d[:file_name] == file || d[:assignment_id] == file
  end || { file_name: file }
end

.get_text(xml_data, type) ⇒ Object



204
205
206
207
208
209
210
# File 'lib/senkyoshi/xml_parser.rb', line 204

def self.get_text(xml_data, type)
  value = ""
  if xml_data.children.at(type)
    value = xml_data.children.at(type).text
  end
  value
end

.get_title(organizations, content) ⇒ Object



163
164
165
166
# File 'lib/senkyoshi/xml_parser.rb', line 163

def self.get_title(organizations, content)
  item = organizations.at("item[@identifierref=#{content[:file_name]}]")
  item.parent.at("title").text
end

.initialize_course(canvas_file_path, blackboard_file_path) ⇒ Object



85
86
87
88
89
90
91
92
# File 'lib/senkyoshi.rb', line 85

def self.initialize_course(canvas_file_path, blackboard_file_path)
   = Senkyoshi::CanvasCourse.(canvas_file_path)
  Zip::File.open(blackboard_file_path, "rb") do |bb_zip|
    course = Senkyoshi::CanvasCourse.(, bb_zip)
    course.upload_content(canvas_file_path)
    cleanup course.scorm_packages
  end
end

.iterate_files(zipfile) ⇒ Object

Iterate through course files and create new SenkyoshiFile for each non-metadata file.



172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/senkyoshi/xml_parser.rb', line 172

def self.iterate_files(zipfile)
  files = zipfile.entries.select(&:file?)

  dir_names = zipfile.entries.map { |entry| File.dirname(entry.name) }.uniq
  file_names = files.map(&:name)
  entry_names = dir_names + file_names

  scorm_paths = ScormPackage.find_scorm_paths(zipfile)

  files.select do |file|
    SenkyoshiFile.valid_file?(entry_names, scorm_paths, file)
  end.
    map { |file| SenkyoshiFile.new(file) }
end

.iterate_xml(organizations, resources, zip_file, resource_xids) ⇒ Object



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/senkyoshi/xml_parser.rb', line 76

def self.iterate_xml(organizations, resources, zip_file, resource_xids)
  pre_data = pre_iterator(organizations, resources, zip_file)
  staff_info = StaffInfo.new
  iterator_master(resources, zip_file) do |xml_data, type, file|
    if RESOURCE_TYPE[type.to_sym]
      single_pre_data = get_single_pre_data(pre_data, file)
      res_class = Senkyoshi.const_get RESOURCE_TYPE[type.to_sym]
      case type
      when "staffinfo"
        staff_info.iterate_xml(xml_data, single_pre_data)
      else
        res_class.from(xml_data, single_pre_data, resource_xids)
      end
    end
  end
end

.iterator_master(resources, zip_file) ⇒ Object



99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/senkyoshi/xml_parser.rb', line 99

def self.iterator_master(resources, zip_file)
  resources.children.map do |resource|
    file_name = resource.attributes["file"].value
    file = File.basename(file_name, ".dat")
    if zip_file.find_entry(file_name)
      data_file = Senkyoshi.read_file(zip_file, file_name)
      xml_data = Nokogiri::XML.parse(data_file).children.first
      type = xml_data.name.downcase
      yield xml_data, type, file
    end
  end
end

.parse(zip_path, imscc_path) ⇒ Object



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/senkyoshi.rb', line 35

def self.parse(zip_path, imscc_path)
  Zip::File.open(zip_path) do |file|
    manifest = read_file(file, "imsmanifest.xml")

    resources = Senkyoshi::Collection.new
    resources.add(Senkyoshi.iterate_files(file))
    resource_xids = resources.resources.
      map(&:xid).
      select { |r| r.include?("xid-") }
    resources.add(Senkyoshi.parse_manifest(file, manifest, resource_xids))

    course = create_canvas_course(resources, zip_path)
    build_file(course, imscc_path, resources)
  end
end

.parse_and_process_single(zip_path, imscc_path) ⇒ Object



51
52
53
# File 'lib/senkyoshi.rb', line 51

def self.parse_and_process_single(zip_path, imscc_path)
  Senkyoshi.parse(zip_path, imscc_path)
end

.parse_manifest(zip_file, manifest, resource_xids) ⇒ Object



68
69
70
71
72
73
74
# File 'lib/senkyoshi/xml_parser.rb', line 68

def self.parse_manifest(zip_file, manifest, resource_xids)
  doc = Nokogiri::XML.parse(manifest)
  resources = doc.at("resources")
  organizations = doc.at("organizations")
  iterate_xml(organizations, resources, zip_file, resource_xids).
    flatten - ["", nil]
end

.pre_iterator(organizations, resources, zip_file) ⇒ Object



112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/senkyoshi/xml_parser.rb', line 112

def self.pre_iterator(organizations, resources, zip_file)
  pre_data = {}
  iterator_master(resources, zip_file) do |xml_data, type, file|
    if PRE_RESOURCE_TYPE[type.to_sym]
      res_class = Senkyoshi.const_get PRE_RESOURCE_TYPE[type.to_sym]
      pre_data[type] ||= []
      data = res_class.get_pre_data(xml_data, file)
      pre_data[type].push(data) if data
    end
  end
  pre_data = connect_content(pre_data)
  build_heirarchy(organizations, pre_data)
end

.read_file(zip_file, file_name) ⇒ Object



55
56
57
58
59
# File 'lib/senkyoshi.rb', line 55

def self.read_file(zip_file, file_name)
  zip_file.find_entry(file_name).get_input_stream.read
rescue NoMethodError
  raise Exceptions::MissingFileError
end

.resetObject



27
28
29
# File 'lib/senkyoshi.rb', line 27

def self.reset
  @configuration = Configuration.new
end

Instance Method Details

#true?(obj) ⇒ Boolean

Returns:

  • (Boolean)


94
95
96
# File 'lib/senkyoshi.rb', line 94

def true?(obj)
  obj.to_s == "true"
end