senkyoshi 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +92 -0
- data/bin/console +14 -0
- data/bin/import_blackboard +6 -0
- data/lib/senkyoshi/canvas_course.rb +179 -0
- data/lib/senkyoshi/collection.rb +26 -0
- data/lib/senkyoshi/config.rb +35 -0
- data/lib/senkyoshi/exceptions.rb +13 -0
- data/lib/senkyoshi/models/announcement.rb +36 -0
- data/lib/senkyoshi/models/answer.rb +32 -0
- data/lib/senkyoshi/models/assessment.rb +95 -0
- data/lib/senkyoshi/models/assignment.rb +26 -0
- data/lib/senkyoshi/models/assignment_group.rb +23 -0
- data/lib/senkyoshi/models/blog.rb +22 -0
- data/lib/senkyoshi/models/content.rb +92 -0
- data/lib/senkyoshi/models/content_file.rb +26 -0
- data/lib/senkyoshi/models/course.rb +39 -0
- data/lib/senkyoshi/models/file.rb +99 -0
- data/lib/senkyoshi/models/forum.rb +28 -0
- data/lib/senkyoshi/models/gradebook.rb +30 -0
- data/lib/senkyoshi/models/group.rb +22 -0
- data/lib/senkyoshi/models/module.rb +22 -0
- data/lib/senkyoshi/models/module_item.rb +23 -0
- data/lib/senkyoshi/models/question.rb +188 -0
- data/lib/senkyoshi/models/questions/calculated.rb +72 -0
- data/lib/senkyoshi/models/questions/either_or.rb +31 -0
- data/lib/senkyoshi/models/questions/essay.rb +11 -0
- data/lib/senkyoshi/models/questions/file_upload.rb +4 -0
- data/lib/senkyoshi/models/questions/fill_in_blank.rb +15 -0
- data/lib/senkyoshi/models/questions/fill_in_blank_plus.rb +20 -0
- data/lib/senkyoshi/models/questions/hot_spot.rb +11 -0
- data/lib/senkyoshi/models/questions/jumbled_sentence.rb +37 -0
- data/lib/senkyoshi/models/questions/matching.rb +57 -0
- data/lib/senkyoshi/models/questions/multiple_answer.rb +56 -0
- data/lib/senkyoshi/models/questions/multiple_choice.rb +18 -0
- data/lib/senkyoshi/models/questions/numerical.rb +31 -0
- data/lib/senkyoshi/models/questions/opinion_scale.rb +18 -0
- data/lib/senkyoshi/models/questions/ordering.rb +40 -0
- data/lib/senkyoshi/models/questions/quiz_bowl.rb +11 -0
- data/lib/senkyoshi/models/questions/short_response.rb +4 -0
- data/lib/senkyoshi/models/questions/true_false.rb +15 -0
- data/lib/senkyoshi/models/quiz.rb +7 -0
- data/lib/senkyoshi/models/resource.rb +33 -0
- data/lib/senkyoshi/models/scorm_package.rb +109 -0
- data/lib/senkyoshi/models/staff_info.rb +72 -0
- data/lib/senkyoshi/models/wikipage.rb +34 -0
- data/lib/senkyoshi/tasks.rb +90 -0
- data/lib/senkyoshi/version.rb +3 -0
- data/lib/senkyoshi/xml_parser.rb +154 -0
- data/lib/senkyoshi.rb +69 -0
- metadata +250 -0
@@ -0,0 +1,15 @@
|
|
1
|
+
module Senkyoshi
|
2
|
+
class FillInBlank < Question
|
3
|
+
def iterate_xml(data)
|
4
|
+
super
|
5
|
+
conditionvar = data.at("resprocessing").at("conditionvar")
|
6
|
+
# not all fill in the blank questions have answers(ie: surveys)
|
7
|
+
if conditionvar
|
8
|
+
answer = Answer.new(conditionvar.at("varequal").text)
|
9
|
+
answer.fraction = @max_score
|
10
|
+
@answers.push(answer)
|
11
|
+
end
|
12
|
+
self
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Senkyoshi
|
2
|
+
class FillInBlankPlus < Question
|
3
|
+
def iterate_xml(data)
|
4
|
+
super
|
5
|
+
conditionvar = data.at("resprocessing").at("conditionvar")
|
6
|
+
# not all fill in the blank questions have answers(ie: surveys)
|
7
|
+
if conditionvar
|
8
|
+
conditionvar.at("and").children.each do |or_child|
|
9
|
+
or_child.children.each do |varequal|
|
10
|
+
answer = Answer.new(varequal.text)
|
11
|
+
answer.resp_ident = varequal.attributes["respident"].value
|
12
|
+
answer.fraction = @max_score
|
13
|
+
@answers.push(answer)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
self
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Senkyoshi
|
2
|
+
class HotSpot < Question
|
3
|
+
def iterate_xml(data)
|
4
|
+
super
|
5
|
+
@material = "#{@title} -- This question was imported from an
|
6
|
+
external source. It was a #{@blackboard_type} question, which
|
7
|
+
is not supported in this quiz tool."
|
8
|
+
self
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Senkyoshi
|
2
|
+
class JumbledSentence < Question
|
3
|
+
def initialize
|
4
|
+
@responses = []
|
5
|
+
super
|
6
|
+
end
|
7
|
+
|
8
|
+
def iterate_xml(data)
|
9
|
+
super
|
10
|
+
if response_block = data.at("flow[@class=RESPONSE_BLOCK]")
|
11
|
+
choices = []
|
12
|
+
response_block.at("flow_label").children.each do |response|
|
13
|
+
text = response.at("mattext").text
|
14
|
+
choices << { id: response.attributes["ident"], text: text }
|
15
|
+
end
|
16
|
+
set_answers(data.at("resprocessing"))
|
17
|
+
correct = data.at("respcondition[title=correct]")
|
18
|
+
correct.at("and").children.each do |answer_element|
|
19
|
+
id = answer_element.text
|
20
|
+
response_label = data.at("response_label[ident='#{id}']")
|
21
|
+
answer_text = response_label.at("mattext").text
|
22
|
+
answer = Answer.new(answer_text, id)
|
23
|
+
resp_ident = answer_element.attributes["respident"].value
|
24
|
+
answer.resp_ident = resp_ident
|
25
|
+
@responses << { id: resp_ident, choices: choices }
|
26
|
+
@answers.push(answer)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
def canvas_conversion(assessment, _resources = nil)
|
33
|
+
@question.responses = @responses
|
34
|
+
super
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Senkyoshi
|
2
|
+
class Matching < Question
|
3
|
+
def initialize
|
4
|
+
super
|
5
|
+
@matches = []
|
6
|
+
@matching_answers = {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def iterate_xml(data)
|
10
|
+
super
|
11
|
+
resprocessing = data.at("resprocessing")
|
12
|
+
@matching_answers = set_matching_answers(resprocessing)
|
13
|
+
matches_array = []
|
14
|
+
if match_block = data.at("flow[@class=RIGHT_MATCH_BLOCK]")
|
15
|
+
matches_array = match_block.children.map do |match|
|
16
|
+
match.at("mat_formattedtext").text
|
17
|
+
end
|
18
|
+
end
|
19
|
+
if response_block = data.at("flow[@class=RESPONSE_BLOCK]")
|
20
|
+
response_block.children.each do |response|
|
21
|
+
id = response.at("response_lid").attributes["ident"].value
|
22
|
+
question = response.at("mat_formattedtext").text
|
23
|
+
answer_id = @matching_answers[id]
|
24
|
+
answer = ""
|
25
|
+
flow_label = response.at("flow_label")
|
26
|
+
flow_label.children.each_with_index do |label, index|
|
27
|
+
if label.attributes["ident"].value == answer_id
|
28
|
+
answer = matches_array[index]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
@matches << { id: id, question_text: question, answer_text: answer }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
def canvas_conversion(assessment, _resources = nil)
|
38
|
+
@question.matches = @matches
|
39
|
+
super
|
40
|
+
end
|
41
|
+
|
42
|
+
def set_matching_answers(resprocessing)
|
43
|
+
matching_answers = {}
|
44
|
+
respcondition = resprocessing.css("respcondition")
|
45
|
+
respcondition.each do |condition|
|
46
|
+
if condition.attributes["title"] != "incorrect"
|
47
|
+
varequal = condition.at("varequal")
|
48
|
+
if varequal
|
49
|
+
id = varequal.attributes["respident"].value
|
50
|
+
matching_answers[id] = varequal.text
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
matching_answers
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Senkyoshi
|
2
|
+
class MultipleAnswer < Question
|
3
|
+
def iterate_xml(data)
|
4
|
+
super
|
5
|
+
if response_block = data.at("flow[@class=RESPONSE_BLOCK]")
|
6
|
+
set_answers(data.at("resprocessing"))
|
7
|
+
response_block.at("render_choice").children.each do |choice|
|
8
|
+
id = choice.at("response_label").attributes["ident"].value
|
9
|
+
@answer_text = choice.at("mat_formattedtext").text
|
10
|
+
answer = Answer.new(@answer_text, id)
|
11
|
+
answer.fraction = get_fraction(id)
|
12
|
+
@answers.push(answer)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
18
|
+
def set_answers(resprocessing)
|
19
|
+
@correct_answers = set_correct_answer(resprocessing)
|
20
|
+
@incorrect_answers = set_incorrect_answer(resprocessing)
|
21
|
+
end
|
22
|
+
|
23
|
+
def set_correct_answer(resprocessing)
|
24
|
+
correct_answers = {}
|
25
|
+
correct = resprocessing.at("respcondition[title=correct]")
|
26
|
+
if correct
|
27
|
+
correct.at("and").children.each do |answer|
|
28
|
+
if answer.name == "varequal"
|
29
|
+
id = answer.text
|
30
|
+
correct_answers[id] = {}
|
31
|
+
correct_answers[id]["name"] = id
|
32
|
+
if correct.at("setvar")
|
33
|
+
score = correct.at("setvar").text
|
34
|
+
score_number = score == "SCORE.max" ? @max_score.to_f : score.to_f
|
35
|
+
correct_answers[id]["fraction"] = score_number
|
36
|
+
else
|
37
|
+
correct_answers[id]["fraction"] = 0
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
correct_answers
|
43
|
+
end
|
44
|
+
|
45
|
+
def set_incorrect_answer(resprocessing)
|
46
|
+
incorrect = resprocessing.at("respcondition[ident=incorrect]")
|
47
|
+
incorrect_answers = {}
|
48
|
+
if incorrect && incorrect.at("setvar")
|
49
|
+
incorrect_answers["fraction"] = incorrect.at("setvar").text
|
50
|
+
else
|
51
|
+
incorrect_answers["fraction"] = 0
|
52
|
+
end
|
53
|
+
incorrect_answers
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Senkyoshi
|
2
|
+
class MultipleChoice < Question
|
3
|
+
def iterate_xml(data)
|
4
|
+
super
|
5
|
+
if response_block = data.at("flow[@class=RESPONSE_BLOCK]")
|
6
|
+
set_answers(data)
|
7
|
+
response_block.at("render_choice").children.each do |choice|
|
8
|
+
id = choice.at("response_label").attributes["ident"].value
|
9
|
+
answer_text = choice.at("mat_formattedtext").text
|
10
|
+
answer = Answer.new(answer_text, id)
|
11
|
+
answer.fraction = get_fraction(id)
|
12
|
+
@answers.push(answer)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
self
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Senkyoshi
|
2
|
+
class NumericalQuestion < Question
|
3
|
+
def initialize
|
4
|
+
@ranges = {}
|
5
|
+
@tolerances = {}
|
6
|
+
super
|
7
|
+
end
|
8
|
+
|
9
|
+
def iterate_xml(data)
|
10
|
+
super
|
11
|
+
conditionvar = data.at("resprocessing").at("conditionvar")
|
12
|
+
if conditionvar
|
13
|
+
range = CanvasCc::CanvasCC::Models::Range.new
|
14
|
+
range.low_range = conditionvar.at("vargte").text.to_i
|
15
|
+
range.high_range = conditionvar.at("varlte").text.to_i
|
16
|
+
answer_text = conditionvar.at("varequal").text.to_i
|
17
|
+
answer = Answer.new(answer_text)
|
18
|
+
@ranges[answer.id] = range
|
19
|
+
answer.fraction = @max_score
|
20
|
+
@answers.push(answer)
|
21
|
+
end
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
def canvas_conversion(assessment, _resources = nil)
|
26
|
+
@question.tolerances = @tolerances
|
27
|
+
@question.ranges = @ranges
|
28
|
+
super
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Senkyoshi
|
2
|
+
class OpinionScale < Question
|
3
|
+
def iterate_xml(data)
|
4
|
+
super
|
5
|
+
if response_block = data.at("flow[@class=RESPONSE_BLOCK]")
|
6
|
+
set_answers(data.at("resprocessing"))
|
7
|
+
response_block.at("render_choice").children.each do |choice|
|
8
|
+
id = choice.at("response_label").attributes["ident"].value
|
9
|
+
@answer_text = choice.at("mat_formattedtext").text
|
10
|
+
answer = Answer.new(@answer_text, id)
|
11
|
+
answer.fraction = get_fraction(id)
|
12
|
+
@answers.push(answer)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
self
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Senkyoshi
|
2
|
+
class Ordering < Question
|
3
|
+
def initialize
|
4
|
+
super
|
5
|
+
@matches = []
|
6
|
+
@order_answers = {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def iterate_xml(data)
|
10
|
+
super
|
11
|
+
resprocessing = data.at("resprocessing")
|
12
|
+
@order_answers = set_order_answers(resprocessing)
|
13
|
+
if response_block = data.at("flow[@class=RESPONSE_BLOCK]")
|
14
|
+
response_block.at("render_choice").children.each do |choice|
|
15
|
+
id = choice.at("response_label").attributes["ident"].value
|
16
|
+
question = @order_answers[id].to_s
|
17
|
+
answer = choice.at("mat_formattedtext").text
|
18
|
+
@matches << { id: id, question_text: question, answer_text: answer }
|
19
|
+
end
|
20
|
+
@matches = @matches.sort_by { |hsh| hsh[:question_text] }
|
21
|
+
end
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
def canvas_conversion(assessment, _resources = nil)
|
26
|
+
@question.matches = @matches
|
27
|
+
super
|
28
|
+
end
|
29
|
+
|
30
|
+
def set_order_answers(resprocessing)
|
31
|
+
order_answers = {}
|
32
|
+
correct = resprocessing.at("respcondition[title=correct]")
|
33
|
+
correct.at("and").children.each_with_index do |varequal, index|
|
34
|
+
id = varequal.text
|
35
|
+
order_answers[id] = index + 1
|
36
|
+
end
|
37
|
+
order_answers
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Senkyoshi
|
2
|
+
class QuizBowl < Question
|
3
|
+
def iterate_xml(data)
|
4
|
+
super
|
5
|
+
@material = "#{@title} -- This question was imported from
|
6
|
+
an external source. It was a #{@blackboard_type} question,
|
7
|
+
which is not supported in this quiz tool."
|
8
|
+
self
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Senkyoshi
|
2
|
+
class TrueFalse < Question
|
3
|
+
def iterate_xml(data)
|
4
|
+
super
|
5
|
+
answers_array = [true, false]
|
6
|
+
set_answers(data.at("resprocessing"))
|
7
|
+
answers_array.each do |answer_text|
|
8
|
+
answer = Answer.new(answer_text)
|
9
|
+
answer.fraction = get_fraction(answer_text)
|
10
|
+
@answers.push(answer)
|
11
|
+
end
|
12
|
+
self
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Senkyoshi
|
2
|
+
class Resource
|
3
|
+
def fix_html(contents, resources)
|
4
|
+
if contents && contents.respond_to?(:empty?) && !contents.empty?
|
5
|
+
node_html = Nokogiri::HTML.fragment(contents)
|
6
|
+
|
7
|
+
_search_and_replace(resources, node_html, "a", "href")
|
8
|
+
_search_and_replace(resources, node_html, "img", "src")
|
9
|
+
|
10
|
+
node_html.to_s
|
11
|
+
else
|
12
|
+
contents
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def matches_xid?(_xid)
|
17
|
+
false
|
18
|
+
end
|
19
|
+
|
20
|
+
def _search_and_replace(resources, node_html, tag, attr)
|
21
|
+
node_html.search(tag).each do |element|
|
22
|
+
original_src = element[attr]
|
23
|
+
xid = original_src.split("/").last
|
24
|
+
file_resource = resources.detect_xid(xid)
|
25
|
+
|
26
|
+
if file_resource
|
27
|
+
name = file_resource.name
|
28
|
+
element[attr] = "#{BASE}/#{IMPORTED_FILES_DIRNAME}/#{name}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
module Senkyoshi
|
2
|
+
class ScormPackage
|
3
|
+
attr_accessor(:entries, :manifest)
|
4
|
+
|
5
|
+
##
|
6
|
+
# Scorm packages should include this string in the <schema> tag. We
|
7
|
+
# downcase, and remove spaces before checking to see if a manifest contains
|
8
|
+
# this schema to determine whether or not it belongs to a scorm package
|
9
|
+
##
|
10
|
+
SCORM_SCHEMA = "adlscorm".freeze
|
11
|
+
|
12
|
+
def initialize(zip_file, manifest)
|
13
|
+
@manifest = manifest
|
14
|
+
@entries = ScormPackage.get_entries zip_file, manifest
|
15
|
+
end
|
16
|
+
|
17
|
+
##
|
18
|
+
# Returns true if a manifest is a scorm manifest file, false otherwise
|
19
|
+
##
|
20
|
+
def self.scorm_manifest?(manifest)
|
21
|
+
parsed_manifest = Nokogiri::XML(manifest.get_input_stream.read)
|
22
|
+
schema_name = parsed_manifest.
|
23
|
+
xpath("//xmlns:metadata/xmlns:schema").
|
24
|
+
text.delete(" ").downcase
|
25
|
+
return schema_name == SCORM_SCHEMA
|
26
|
+
# NOTE we occasionally run into malformed manifest files
|
27
|
+
rescue Nokogiri::XML::XPath::SyntaxError => e
|
28
|
+
filename = manifest.zipfile
|
29
|
+
STDERR.puts "Malformed scorm manifest found: #{manifest} in #{filename}"
|
30
|
+
STDERR.puts e.to_s
|
31
|
+
false
|
32
|
+
end
|
33
|
+
|
34
|
+
##
|
35
|
+
# Extracts scorm packages from a blackboard export zip file
|
36
|
+
##
|
37
|
+
def self.get_scorm_packages(blackboard_export)
|
38
|
+
find_scorm_manifests(blackboard_export).map do |manifest|
|
39
|
+
ScormPackage.new blackboard_export, manifest
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
##
|
44
|
+
# Returns array of all scorm manifest files inside of blackboard export
|
45
|
+
##
|
46
|
+
def self.find_scorm_manifests(zip_file)
|
47
|
+
return [] if zip_file.nil?
|
48
|
+
zip_file.entries.select do |e|
|
49
|
+
File.fnmatch("*imsmanifest.xml", e.name) && scorm_manifest?(e)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
##
|
54
|
+
# Returns array of paths to scorm packages
|
55
|
+
##
|
56
|
+
def self.find_scorm_paths(zip_file)
|
57
|
+
manifests = ScormPackage.find_scorm_manifests(zip_file)
|
58
|
+
manifests.map { |manifest| File.dirname(manifest.name) }
|
59
|
+
end
|
60
|
+
|
61
|
+
##
|
62
|
+
# Returns array of all zip file entries that belong in scorm package
|
63
|
+
##
|
64
|
+
def self.get_entries(zip_file, manifest)
|
65
|
+
zip_file.entries.select do |e|
|
66
|
+
File.dirname(e.name).start_with?(File.dirname(manifest.name)) &&
|
67
|
+
!e.directory?
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
##
|
72
|
+
# Returns file path with relative path to scorm package removed
|
73
|
+
##
|
74
|
+
def self.correct_path(path, scorm_path)
|
75
|
+
corrected = path.gsub(scorm_path, "")
|
76
|
+
corrected.slice(1, corrected.size) if corrected.start_with? "/"
|
77
|
+
end
|
78
|
+
|
79
|
+
##
|
80
|
+
# Writes all entries to a zip file in a temporary directory and returns
|
81
|
+
# location of temporary file
|
82
|
+
##
|
83
|
+
def write_zip(export_name)
|
84
|
+
@@dir ||= Dir.mktmpdir
|
85
|
+
scorm_path = File.dirname @manifest.name
|
86
|
+
path = "#{@@dir}/#{export_name}"
|
87
|
+
Zip::File.open path, Zip::File::CREATE do |zip|
|
88
|
+
@entries.each do |entry|
|
89
|
+
if entry.file?
|
90
|
+
zip.get_output_stream(
|
91
|
+
ScormPackage.correct_path(entry.name, scorm_path),
|
92
|
+
) do |file|
|
93
|
+
file.write(entry.get_input_stream.read)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
path
|
99
|
+
end
|
100
|
+
|
101
|
+
##
|
102
|
+
# Removes all temp files if they exist
|
103
|
+
##
|
104
|
+
def self.cleanup
|
105
|
+
@@dir ||= nil
|
106
|
+
FileUtils.rm_r @@dir unless @@dir.nil?
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require "senkyoshi/models/resource"
|
2
|
+
|
3
|
+
module Senkyoshi
|
4
|
+
class StaffInfo < Resource
|
5
|
+
attr_reader(
|
6
|
+
:id,
|
7
|
+
:title,
|
8
|
+
:bio,
|
9
|
+
:name,
|
10
|
+
:email,
|
11
|
+
:phone,
|
12
|
+
:office_hours,
|
13
|
+
:office_address,
|
14
|
+
:home_page,
|
15
|
+
:image,
|
16
|
+
)
|
17
|
+
|
18
|
+
def parse_name(contact)
|
19
|
+
parts = [
|
20
|
+
contact.xpath("./NAME/FORMALTITLE/@value").text,
|
21
|
+
contact.xpath("./NAME/GIVEN/@value").text,
|
22
|
+
contact.xpath("./NAME/FAMILY/@value").text,
|
23
|
+
]
|
24
|
+
|
25
|
+
resp = ""
|
26
|
+
parts.each do |part|
|
27
|
+
resp << " " unless resp.empty?
|
28
|
+
resp << part unless part.empty?
|
29
|
+
end
|
30
|
+
resp
|
31
|
+
end
|
32
|
+
|
33
|
+
def iterate_xml(xml, _pre_data)
|
34
|
+
contact = xml.xpath("//CONTACT")
|
35
|
+
@id = xml.xpath("//STAFFINFO/@id").text
|
36
|
+
@title = xml.xpath("//STAFFINFO/TITLE/@value").text
|
37
|
+
@bio = xml.xpath("//BIOGRAPHY/TEXT").text
|
38
|
+
@name = parse_name(contact)
|
39
|
+
@email = xml.xpath("//CONTACT/EMAIL/@value").text
|
40
|
+
@phone = xml.xpath("//CONTACT/PHONE/@value").text
|
41
|
+
@office_hours = xml.xpath("//OFFICE/HOURS/@value").text
|
42
|
+
@office_address = xml.xpath("//OFFICE/ADDRESS/@value").text
|
43
|
+
@home_page = xml.xpath("//HOMEPAGE/@value").text
|
44
|
+
@image = xml.xpath("//IMAGE/@value").text
|
45
|
+
|
46
|
+
self
|
47
|
+
end
|
48
|
+
|
49
|
+
def construct_body
|
50
|
+
<<-HTML
|
51
|
+
<h3>#{@name}</h3>
|
52
|
+
<p>#{@bio}</p>
|
53
|
+
<ul>
|
54
|
+
<li>Email: #{@email}</li>
|
55
|
+
<li>Phone: #{@phone}</li>
|
56
|
+
<li>Office Hours: #{@office_hours}</li>
|
57
|
+
<li>Office Address: #{@office_address}</li>
|
58
|
+
</ul>
|
59
|
+
HTML
|
60
|
+
end
|
61
|
+
|
62
|
+
def canvas_conversion(course, _resources = nil)
|
63
|
+
page = CanvasCc::CanvasCC::Models::Page.new
|
64
|
+
page.body = construct_body
|
65
|
+
page.identifier = @id
|
66
|
+
page.page_name = @title.empty? ? "Contact" : @title
|
67
|
+
|
68
|
+
course.pages << page
|
69
|
+
course
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require "senkyoshi/models/resource"
|
2
|
+
|
3
|
+
module Senkyoshi
|
4
|
+
class WikiPage < Content
|
5
|
+
def canvas_conversion(course, resources)
|
6
|
+
unless @title == "--TOP--"
|
7
|
+
page_count = course.pages.
|
8
|
+
select { |p| p.title.start_with? @title }.count
|
9
|
+
@title = "#{@title}-#{page_count + 1}" if page_count > 0
|
10
|
+
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
|
19
|
+
page.body = fix_html(@body, resources)
|
20
|
+
page.identifier = @id
|
21
|
+
page.page_name = @title
|
22
|
+
page.workflow_state = "active"
|
23
|
+
|
24
|
+
# Add page links to page body
|
25
|
+
@files.each { |f| page.body << f.canvas_conversion }
|
26
|
+
course.pages << page
|
27
|
+
|
28
|
+
course = create_module(course)
|
29
|
+
end
|
30
|
+
|
31
|
+
course
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|