Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
b74e93f
First pass at parsing out a Wiris short answer question
seanrcollings Mar 12, 2024
1de0af1
multichoisewiris implemented
seanrcollings Mar 14, 2024
279ff10
First pass on implementing essay, match, multichoice, truefalse complete
seanrcollings Mar 15, 2024
afe2549
working on multianswer question type
seanrcollings Mar 15, 2024
3f5bf8c
Plucking out expressions from MathML encoded questions
seanrcollings Mar 15, 2024
9b7b6f4
Brought the source for mathml2asciimath into code for modification
seanrcollings Mar 18, 2024
806ea7b
First pass at translating Wiris algorithms to JS. First pass on my te…
seanrcollings Mar 18, 2024
6dcb00e
Converting Short answer question type
seanrcollings Mar 19, 2024
bf58739
Almost all data scripts are running from my tests course
seanrcollings Mar 19, 2024
13028b2
Round results to 2 decimal places by default
seanrcollings Mar 19, 2024
a2dacc6
Keeping MathML formatting in formula
seanrcollings Mar 19, 2024
fc05afb
super basic implementation of approximating answers
seanrcollings Mar 19, 2024
90f7e1d
First pass at multichoice wiris questions complete
seanrcollings Mar 20, 2024
354d165
minor tweaks
seanrcollings Mar 20, 2024
93a2b95
Swapped to retrieving the algorithm for each sheet instead of attempt…
seanrcollings Mar 20, 2024
cc11201
All data engine scripts working in test course
seanrcollings Mar 20, 2024
4c736df
Testing translating all courses
seanrcollings Mar 20, 2024
17f2d41
Reworked shortanswerwiris converter & parser to take into account mor…
seanrcollings Mar 21, 2024
64e1e36
formatting is mostly working now, with the exception of images
seanrcollings Mar 21, 2024
061f837
fixes
seanrcollings Mar 22, 2024
9b85982
Tag activities with the short name for their origin course
seanrcollings Mar 25, 2024
55b67e0
Fixes
seanrcollings Apr 3, 2024
5dae0e4
adds something
seanrcollings Jul 12, 2024
59b54a9
Adding data table script to calculated questions
seanrcollings Jul 23, 2024
c2b1a4c
Include dat table in multi part moodle question type
seanrcollings Jul 24, 2024
8e5d8fd
removed byebug
seanrcollings Aug 12, 2024
2f078cd
Fixed skipping some of the data table columns in script generation
seanrcollings Oct 7, 2024
e20b9f0
Added JSON writier
seanrcollings Oct 7, 2024
f65f106
Fixes
seanrcollings Oct 22, 2024
d7792ee
various tweaks
seanrcollings May 2, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,6 @@ test/version_tmp
tmp
vendor
out
moodle_test.mbz
moodle_test.mbz
.DS_Store
.byebug_history
2 changes: 1 addition & 1 deletion .tool-versions
Original file line number Diff line number Diff line change
@@ -1 +1 @@
ruby 2.7.5
ruby 3.2.2
6 changes: 6 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,9 @@ source 'https://rubygems.org'
gemspec

gem 'simplecov'

gem "htmlentities", "~> 4.3"

gem "debug", "~> 1.9"

gem "httparty", "~> 0.21.0"
88 changes: 88 additions & 0 deletions bin/extract
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#!/usr/bin/env ruby
require 'rubygems'
require 'nokogiri'
require 'pp'
require 'cgi'


def html_entity_decode(str)
CGI.unescapeHTML(str)
end

# Read input from STDIN (or use ARGV[0] if provided)
input = ARGV[0]

# Decode HTML entities to get valid XML
decoded_input = html_entity_decode(input)

begin
# Parse the XML
doc = Nokogiri::XML(decoded_input) { |config| config.strict }

# Extract the CAS session if available
cas_session = doc.at_xpath('//wirisCasSession')
if cas_session && !cas_session.children.empty?
# Extract CDATA content from wirisCasSession
cdata_content = cas_session.children.first.text
begin
cas_content = Nokogiri::XML(cdata_content) { |config| config.noblanks }
puts "CAS Session Data:"
puts cas_content.to_xml
rescue => e
puts "Error parsing CAS session CDATA: #{e.message}"
puts "Raw CDATA content:"
puts cdata_content
end
end

# Extract the correct answers
correct_answers = doc.xpath('//correctAnswer')
if !correct_answers.empty?
puts "\nCorrect Answers:"
correct_answers.each_with_index do |answer, index|
puts "Answer #{index + 1}:"
if !answer.children.empty? && answer.children.first.cdata?
begin
answer_content = Nokogiri::XML(answer.children.first.text) { |config| config.noblanks }
puts answer_content.to_xml
rescue => e
puts "Error parsing answer CDATA: #{e.message}"
puts answer.children.first.text
end
else
pp answer.content
end
end
end

# Extract assertions if available
assertions = doc.xpath('//assertion')
if !assertions.empty?
puts "\nAssertions:"
assertions.each do |assertion|
puts "- #{assertion['name']}"
assertion.xpath('./param').each do |param|
param_content = param.children.first.cdata? ? param.children.first.text : param.content
puts " #{param['name']}: #{param_content}"
end
end
end

# Extract options if available
options = doc.xpath('//option')
if !options.empty?
puts "\nOptions:"
options.each do |option|
puts "- #{option['name']}: #{option.content}"
end
end

# Print the entire document structure in a more readable format
puts "\nComplete XML Structure:"
puts doc.to_xml(indent: 2)

rescue => e
puts "Error processing the XML: #{e.message}"
puts e.backtrace
end

2 changes: 2 additions & 0 deletions lib/moodle2aa.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ class OpenStruct < ::OpenStruct

autoload :ResourceFactory, 'moodle2aa/resource_factory'

autoload :OutputLogger, 'moodle2aa/output_logger'

module CC
autoload :Assessment, 'moodle2aa/cc/assessment'
autoload :Assignment, 'moodle2aa/cc/assignment'
Expand Down
6 changes: 3 additions & 3 deletions lib/moodle2aa/canvas_cc/assessment_writer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def write_assessment_meta_xml(assessment)
end.to_xml

file_path = File.join(@work_dir, assessment.meta_file_path)
FileUtils.mkdir_p(File.dirname(file_path)) unless File.exists?(File.dirname(file_path))
FileUtils.mkdir_p(File.dirname(file_path)) unless File.exist?(File.dirname(file_path))
File.open(file_path, 'w') { |f| f.write(xml) }
end

Expand Down Expand Up @@ -75,8 +75,8 @@ def write_assessment_non_cc_qti_xml(assessment)
end.to_xml

file_path = File.join(@work_dir, assessment.qti_file_path)
FileUtils.mkdir_p(File.dirname(file_path)) unless File.exists?(File.dirname(file_path))
FileUtils.mkdir_p(File.dirname(file_path)) unless File.exist?(File.dirname(file_path))
File.open(file_path, 'w') { |f| f.write(xml) }
end
end
end
end
2 changes: 1 addition & 1 deletion lib/moodle2aa/canvas_cc/models/answer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ def initialize(answer=nil)
end
end
end
end
end
4 changes: 2 additions & 2 deletions lib/moodle2aa/canvas_cc/question_bank_writer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ def write_bank(bank)
end.to_xml

file_path = File.join(@work_dir, bank_resource.href)
FileUtils.mkdir_p(File.dirname(file_path)) unless File.exists?(File.dirname(file_path))
FileUtils.mkdir_p(File.dirname(file_path)) unless File.exist?(File.dirname(file_path))
File.open(file_path, 'w') { |f| f.write(xml) }
end
end
end
end
11 changes: 6 additions & 5 deletions lib/moodle2aa/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ def self.shared_options
method_option :format, :type => :string, :default => 'cc'
method_option :generate_archive, :type => :boolean, :default => true
method_option :generate_report, :type => :boolean, :default => false
method_option :version, :type => :string, :default => ''
method_option :version, :type => :string, :default => ''
method_option :convert_unknown_qtypes, :type => :boolean, :default => true
method_option :convert_unknown_activities, :type => :boolean, :default => true
method_option :group_by_quiz_page, :type => :boolean, :default => true
method_option :group_by_quiz_page, :type => :boolean, :default => true
method_option :unused_question_mode, :type => :string, :default => 'keep', :enum => ['exclude', 'keep', 'only']
end

desc "migrate MOODLE_BACKUP_ZIP EXPORT_DIR", "Migrates Moodle backup ZIP to IMS Common Cartridge package"
Expand Down Expand Up @@ -59,16 +60,16 @@ def bulkmigrate(*sources, destination)
next unless File.directory? source_folder
numbackups += Pathname.new(source_folder).children.select { |child| child.directory? }.size
end
bar = ProgressBar.new(numbackups)
# bar = ProgressBar.new(numbackups)

sources.each do |source_folder|
next unless File.directory? source_folder
Pathname.new(source_folder).children.select { |child| child.directory? }.collect { |backup|
Pathname.new(source_folder).children.collect { |backup|
puts "Converting #{backup}"
migrator = Moodle2AA::Migrator.new backup, destination, options
migrator.migrate
#puts "#{backup} converted to #{migrator.imscc_path}" if options[:generate_archive]
bar.increment!
# bar.increment!
}
end
end
Expand Down
6 changes: 6 additions & 0 deletions lib/moodle2aa/learnosity/converters.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,11 @@ module Converters
require_relative 'converters/html_converter'
require_relative 'converters/file_converter'
require_relative 'converters/gapselect_converter'

module Wiris
require_relative 'converters/wiris/wiris_converter'
require_relative 'converters/wiris/shortanswerwiris_converter'
require_relative 'converters/wiris/multianswerwiris_converter'
end
end
end
18 changes: 11 additions & 7 deletions lib/moodle2aa/learnosity/converters/assignment_converter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ def convert(moodle_quiz, question_categories)
activity.data.config.regions = "main"
activity.data.config.navigation.show_intro = true
activity.data.config.navigation.show_outro = true
activity.data.config.title = moodle_quiz.name
activity.title = moodle_quiz.name
title = moodle_quiz.name
max_length = [title.length, 149].min
activity.data.config.title = title[0..max_length]
activity.title = title[0..max_length]
source = moodle_quiz_url(moodle_quiz)
# TODO: handle quiz intro
activity.data.items, question_random_usages = resolve_item_references(moodle_quiz.question_instances, question_categories, moodle_quiz)
Expand All @@ -26,7 +28,9 @@ def convert(moodle_quiz, question_categories)
else
activity.tags[IMPORT_STATUS_TAG_TYPE] = [IMPORT_STATUS_PARTIAL]
end


activity.tags['Moodle Course'] = [@moodle_course.shortname]

activity.description = "Moodle source url: #{source}\n"
if question_random_usages.count > 0
activity.description += "\n\nRandomization parameters:\n"
Expand All @@ -37,7 +41,7 @@ def convert(moodle_quiz, question_categories)
end

private

def moodle_quiz_url(moodle_quiz)
"#{@moodle_course.url}/mod/quiz/view.php?q=#{moodle_quiz.id}"
end
Expand Down Expand Up @@ -72,15 +76,15 @@ def resolve_item_references(question_instances, question_categories, moodle_quiz
newquestions = []
newquestions += cat.questions.select {|q| q.qtype!='random' && q.qtype!='description'}
#(cat.questions.select {|q| q.type!='random'}).each {|q| puts "#{moodle_quiz.name},#{q.qtype}"}
if recurse
if recurse
# find questions in child categories too
catids = [cat.id]
done = false
while !done
new_cats = question_categories.select { |c| catids.include?(c.parent) && !catids.include?(c.id) }
# add all questions in new_cats
if new_cats.count > 0
new_cats.each do |c|
new_cats.each do |c|
newquestions += c.questions.select {|q| q.qtype!='random' && q.qtype!='description'}
#(c.questions.select {|q| q.type!='random'}).each {|q| puts "#{moodle_quiz.name},#{q.qtype}"}
end
Expand All @@ -90,7 +94,7 @@ def resolve_item_references(question_instances, question_categories, moodle_quiz
end
end
# add special tags unique to this recursive group. Once random questions
# work in learnosity, we'll configure the quiz to select from this group.
# work in learnosity, we'll configure the quiz to select from this group.
# TODO: generate unique tag for hierarchy
# TODO: finish random questions
else
Expand Down
Loading