senkyoshi 1.0.0
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 +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
|