Module: Senkyoshi

Included in:
OutcomeDefinition, RuleCriteria, WikiPage
Defined in:
lib/senkyoshi/version.rb,
lib/senkyoshi.rb,
lib/senkyoshi/tasks.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/link.rb,
lib/senkyoshi/models/quiz.rb,
lib/senkyoshi/models/rule.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/heirarchy.rb,
lib/senkyoshi/models/assessment.rb,
lib/senkyoshi/models/assignment.rb,
lib/senkyoshi/models/attachment.rb,
lib/senkyoshi/models/course_toc.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/rule_criteria.rb,
lib/senkyoshi/models/scorm_package.rb,
lib/senkyoshi/models/grade_criteria.rb,
lib/senkyoshi/models/questions/essay.rb,
lib/senkyoshi/models/assignment_group.rb,
lib/senkyoshi/models/module_converter.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/grade_range_criteria.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/grade_completed_criteria.rb,
lib/senkyoshi/models/questions/short_response.rb,
lib/senkyoshi/models/content_reviewed_criteria.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/grade_range_percent_criteria.rb,
lib/senkyoshi/models/questions/fill_in_blank_plus.rb

Overview

You should have received a copy of the GNU Affero General Public License along with this program. If not, see <www.gnu.org/licenses/>.

Defined Under Namespace

Classes: Announcement, Answer, Assessment, Assignment, AssignmentGroup, Attachment, Blog, Calculated, CanvasCourse, Collection, Configuration, Content, ContentFile, ContentReviewedCriteria, Course, CourseToc, EitherOr, Essay, ExternalUrl, FileResource, FileUpload, FillInBlank, FillInBlankPlus, Forum, GradeCompletedCriteria, GradeCriteria, GradeRangeCriteria, GradeRangePercentCriteria, Gradebook, Group, Heirarchy, HotSpot, JumbledSentence, Link, Matching, Module, ModuleConverter, ModuleItem, MultipleAnswer, MultipleChoice, NumericalQuestion, OpinionScale, Ordering, OutcomeDefinition, QTI, Question, QuestionBank, Quiz, QuizBowl, Resource, Rule, RuleCriteria, 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
MAIN_CANVAS_MODULE =
"aj_main_module".freeze
MASTER_MODULE =
"master_module".freeze
VERSION =
"1.0.11".freeze
RESOURCE_TYPE =
{
  groups: "Group",
  blog: "Blog",
  announcement: "Announcement",
  forum: "Forum",
  course: "Course",
  questestinterop: "QTI",
  content: "Content",
  staffinfo: "StaffInfo",
  gradebook: "Gradebook",
  rule: "Rule",
}.freeze
PRE_RESOURCE_TYPE =
{
  coursetoc: "CourseToc",
  gradebook: "Gradebook",
  link: "Link",
  courseassessment: "QTI",
}.freeze

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.configurationObject



44
45
46
# File 'lib/senkyoshi.rb', line 44

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

Class Method Details

.build_file(course, imscc_path, resources) ⇒ Object



90
91
92
93
94
95
# File 'lib/senkyoshi.rb', line 90

def self.build_file(course, imscc_path, resources)
  file = CanvasCc::CanvasCC::CartridgeCreator.new(course).create(Dir.tmpdir)
  FileUtils.mv(file, imscc_path, force: true)
  cleanup resources
  puts "Created a file #{imscc_path}"
end

.build_heirarchy(organizations, resources, course_toc) ⇒ Object



170
171
172
173
174
175
176
# File 'lib/senkyoshi/xml_parser.rb', line 170

def self.build_heirarchy(organizations, resources, course_toc)
  discussion_boards = resources.
    search("resource[type=\"resource/x-bb-discussionboard\"]")
  organizations.at("organization").children.flat_map do |item|
    Heirarchy.item_iterator(item, course_toc, discussion_boards)
  end
end

.cleanup(resources) ⇒ Object

Perform any necessary cleanup from creating canvas cartridge



100
101
102
# File 'lib/senkyoshi.rb', line 100

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

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

Yields:



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

def self.configure
  yield configuration
end

.connect_content(pre_data) ⇒ Object



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

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

    if pre_data["link"]
      matching_link = pre_data["link"].detect do |link|
        link[:referrer] == content[:file_name]
      end

      if matching_link
        content[:referred_to_title] = matching_link[:referred_to_title]
      end
    end
  end

  pre_data["content"]
end

.create_canvas_course(resources, zip_name, pre_data) ⇒ Object



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/senkyoshi.rb', line 104

def self.create_canvas_course(resources, zip_name, pre_data)
  course = CanvasCc::CanvasCC::Models::Course.new
  course.course_code = zip_name

  # Wait until after we set modules to convert Rules
  resources.find_instances_not_of([Rule]).each do |resource|
    course = resource.canvas_conversion(course, resources)
  end

  course = ModuleConverter.set_modules(course, pre_data)
  resources.find_instances_of(Rule).each do |rule|
    course = rule.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.



202
203
204
# File 'lib/senkyoshi/xml_parser.rb', line 202

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

.get_attribute_value(xml_data, type) ⇒ Object



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

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



222
223
224
225
226
227
228
# File 'lib/senkyoshi/xml_parser.rb', line 222

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



107
108
109
110
111
# File 'lib/senkyoshi/xml_parser.rb', line 107

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

.get_text(xml_data, type) ⇒ Object



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

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

.initialize_course(canvas_file_path, blackboard_file_path) ⇒ Object



121
122
123
124
125
126
127
128
# File 'lib/senkyoshi.rb', line 121

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.



182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/senkyoshi/xml_parser.rb', line 182

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(resources, zip_file, resource_xids, pre_data) ⇒ Object



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/senkyoshi/xml_parser.rb', line 91

def self.iterate_xml(resources, zip_file, resource_xids, pre_data)
  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.flatten - ["", nil]
end

.iterator_master(resources, zip_file) ⇒ Object



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

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 ? xml_data.name.downcase : "empty"

      yield xml_data, type, file
    end
  end
end

.parse(zip_path, imscc_path) ⇒ Object



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/senkyoshi.rb', line 56

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-") }

    xml = Nokogiri::XML.parse(manifest)
    xml_resources = xml.at("resources")
    xml_organizations = xml.at("organizations")

    pre_data = Senkyoshi.pre_iterator(xml_organizations, xml_resources, file)
    resources.add(
      Senkyoshi.iterate_xml(xml_resources, file, resource_xids, pre_data),
    )

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

.parse_and_process_single(zip_path, imscc_path) ⇒ Object



80
81
82
# File 'lib/senkyoshi.rb', line 80

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

.pre_iterator(organizations, resources, zip_file) ⇒ Object



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

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["content"] = build_heirarchy(organizations, resources,
                                        pre_data["coursetoc"]) - ["", nil]
  pre_data = connect_content(pre_data)
end

.read_file(zip_file, file_name) ⇒ Object



84
85
86
87
88
# File 'lib/senkyoshi.rb', line 84

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



48
49
50
# File 'lib/senkyoshi.rb', line 48

def self.reset
  @configuration = Configuration.new
end

.true?(obj) ⇒ Boolean

Returns:

  • (Boolean)


130
131
132
# File 'lib/senkyoshi.rb', line 130

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