Skip to content
Snippets Groups Projects
Commit 342df358 authored by Iain Bryson's avatar Iain Bryson
Browse files

Add new 2018 course website fill-in-the-blank answer syntax.

parent b98759ee
No related branches found
No related tags found
No related merge requests found
require 'active_model'
module Quiz
class BlankAnswers
include ActiveModel::Model
include ActiveModel::Serialization
attr_accessor :id, :answer_texts
def initialize(attributes={})
super
end
def attributes
{'id' => nil, 'answer_texts' => [] }
end
def to_hash
self.serializable_hash
end
end
end
\ No newline at end of file
......@@ -5,14 +5,14 @@ module Quiz
include ActiveModel::Model
include ActiveModel::Serialization
attr_accessor :id, :elements, :hints, :answers
attr_accessor :id, :elements, :hints, :answers, :wrong_text
def initialize( attributes={} )
super
end
def attributes
{'id' => nil, 'elements' => [], 'hints' => [], 'answers' => []}
{'id' => nil, 'elements' => [], 'wrong_text' => [], 'answers' => []}
end
def to_hash
......
require 'quiz/parse_elements/parse_utils'
require 'quiz/parse_elements/quiz_element'
require 'models/text'
require 'models/blank_answers'
module Quiz
class BlankAnswersElement < MultiFieldTextElement
def initialize()
super('right', true, 'answer_texts', 1)
end
def process(content)
processed_contents, errors = super(content)
return processed_contents, errors if !errors.empty?
processed_contents[:element] = :option
raise 'wtf' if processed_contents.key? :text
processed_contents.delete :element
return BlankAnswers.new( processed_contents ), errors
end
end
end
\ No newline at end of file
......@@ -38,10 +38,10 @@ module Quiz
return { :element => :text, @field_names[0].to_sym => content.join }, []
else
texts = Hash[@field_names.map{|sym| [sym.to_sym, ""]}]
texts = []
lex_state = :outer
lex_state_idx = -1
text_idx = 0
line = content[0]
escape = false
current_quote = nil
......@@ -55,6 +55,7 @@ module Quiz
return nil, [new_semantic_error("fields must be separated by space")]
end
lex_state = :outer
text_idx += 1
next
elsif (lex_state == :outer)
next if line[i] =~ /\s/
......@@ -62,22 +63,21 @@ module Quiz
current_quote = nil
current_quote = line[i] if line[i] =~ /['"]/
lex_state_idx = @field_names.index{ |f| texts[f.to_sym].empty? }
return nil, [new_semantic_error("too many fields")] if !lex_state_idx
lex_state = @field_names[lex_state_idx].to_sym
lex_state = :inner
texts[lex_state] += line[i] if !current_quote
texts << ""
texts[text_idx] += line[i] if !current_quote
else # we're in a field
if current_quote && line[i] == current_quote
# look ahead to see if this is an escape or the end of the string
if (i+1) < line.length
if line[i+1] == current_quote
texts[lex_state] += line[i+1]
texts[text_idx] += line[i+1]
escape = true
next
end
end
return nil, [new_semantic_error("#{@field_names[lex_state_idx]} cannot be empty")] if texts[lex_state].empty?
return nil, [new_semantic_error("#{field_name(text_idx)} cannot be empty")] if texts[text_idx].empty?
lex_state = :separator
next
end
......@@ -86,27 +86,56 @@ module Quiz
next
end
texts[lex_state] += line[i]
texts[text_idx] += line[i]
end
end
return nil, [new_semantic_error("end of line seen before end of #{@field_names[lex_state_idx]} text")] if lex_state != :outer && lex_state != :separator && current_quote
return nil, [new_semantic_error("end of line seen before end of #{field_name(text_idx)} text")] if lex_state != :outer && lex_state != :separator && current_quote
processed_contents = { :element => @identifier.to_sym }
errors = []
(0...@min_fields-1).each{ |field_idx|
errors << [new_semantic_error("no #{field_names[lex_state_idx]} field found")] if texts[@field_names[field_idx].to_sym].empty?
}
errors = errors.concat validate_fields(texts)
errors = errors.concat validate_mathjax(texts)
texts.each do |k,v|
return nil, [new_semantic_error("single line text cannot contain $$")] if v.include? '$$'
processed_contents = processed_contents.merge(populate_fields texts)
return processed_contents, errors
end
end
processed_contents = processed_contents.merge(texts)
def validate_fields(texts)
return [new_semantic_error("too many fields")] if !@max_fields.nil? && texts.length > @max_fields
return processed_contents, errors
(0...@min_fields-1).map do |field_idx|
new_semantic_error("no #{field_name(field_idx)} field found") if texts.length < field_idx
end.reject(&:nil?)
#ALT: return [new_semantic_error("too few fields")] if !@min_fields.nil? && texts.length < @min_fields
end
def validate_mathjax(texts)
texts.map do |text|
new_semantic_error("single line text cannot contain $$") if text.include? '$$'
end.reject(&:nil?)
end
def populate_fields(texts)
if @field_names.is_a? Array
fields = {}
@field_names.each_with_index { |field_name, i| fields[field_name.to_sym] = texts[i] }
fields
elsif @field_names.is_a? String
{ @field_names => texts }
end
end
def field_name(idx)
if @field_names.is_a? Array
@field_names[idx]
elsif @field_names.is_a? String
"#{idx}th #{@field_names}"
end
end
end
......
require 'quiz/parse_elements/parse_utils'
require 'quiz/parse_elements/quiz_elements'
require 'quiz/parse_elements/base_elements/text'
require 'quiz/parse_elements/base_elements/blank_answers'
require 'models/text'
require 'models/blank'
......@@ -9,12 +10,12 @@ require 'models/fill_in_the_blank'
module Quiz
class FillInTheBlankElement < MultipleQuizElements
def initialize(identifier = 'fill_in_the_blank')
elements = [ TextElement.new, AnswerElement.new('right') ]
elements = [ TextElement.new, BlankAnswersElement.new(), AnswerElement.new('wrong') ]
super(elements, identifier)
end
def process(content)
processed_contents = { :elements => [], :hints => [], :answers => [] }
processed_contents = { :elements => [], :wrong_text => nil, :answers => nil }
errors = []
......@@ -34,17 +35,19 @@ module Quiz
end
end
when Option
errors << new_semantic_error("only one answer element allowed per question") unless processed_contents[:answers].empty?
processed_contents[:answers] << c
errors << new_semantic_error("only one wrong text element allowed per question") unless processed_contents[:wrong_text].nil?
processed_contents[:wrong_text] = c
when Hint
processed_contents[:hints] << c
when BlankAnswers
errors << new_semantic_error("only one answer element allowed per question") unless processed_contents[:answers].nil?
processed_contents[:answers] = c
else
errors << new_semantic_error("unknown sub-element #{c}")
end
end
num_blanks = processed_contents[:elements].select{ |e| e.is_a? BlankToFill }.length
blanks = processed_contents[:elements].select{ |e| e.is_a? BlankToFill }
num_blanks = blanks.length
num_texts = processed_contents[:elements].select{ |e| e.is_a? Text }.length
......@@ -52,6 +55,21 @@ module Quiz
errors << new_semantic_error("question must have at least one blank") if num_blanks == 0
if processed_contents[:answers].nil?
errors << new_semantic_error("cannot supply a wrong text without provided right answers") unless processed_contents[:wrong_text].nil?
else
if processed_contents[:answers].answer_texts.length != num_blanks
errors << new_semantic_error("you must supply right answers for all the blanks")
else
processed_contents[:answers].answer_texts.each_with_index do |answer, index|
if answer.length != blanks[index].blank_len
errors << new_semantic_error("answer '#{answer}' is not the same length as the corresponding blank (#{blanks[index].blank_len})")
end
end
end
end
return FillInTheBlank.new(processed_contents), errors
end
end
......
......@@ -56,30 +56,71 @@ end:text
expect(parse_context.content.elements[4].text).to eq ' missing'
expect(parse_context.content.elements[5].blank_len).to eq 1
end
it 'handles multiple blanks' do
describe 'new course website syntax' do
it 'handles right' do
text = <<-BLOCK
begin:text
Which %5% %2% missing%1%"
What? %5%
end:text
right 'This is the solution I thought of. "word, is, ?". There might be others.'
right 'words'
BLOCK
parse_context = parse_string(element = Quiz::FillInTheBlankElement, str = text, multiline = true)
expect(parse_context.errors).to be_empty, "#{parse_context.errors} #{parse_context.content}"
expect(parse_context.content).to_not be nil
expect(parse_context.content.elements[0].text).to eq 'Which '
expect(parse_context.content.elements[0].text).to eq 'What? '
expect(parse_context.content.elements[1].blank_len).to eq 5
expect(parse_context.content.elements[2].text).to eq ' '
expect(parse_context.content.elements[3].blank_len).to eq 2
expect(parse_context.content.elements[4].text).to eq ' missing'
expect(parse_context.content.elements[5].blank_len).to eq 1
expect(parse_context.content.answers[0].answer_text).to eq 'This is the solution I thought of. "word, is, ?". There might be others.'
expect(parse_context.content.elements[2].text).to eq " \n"
expect(parse_context.content.answers.answer_texts[0]).to eq 'words'
end
it 'creates error when right answer does not match length of blank' do
text = <<-BLOCK
begin:text
What? %5% "
end:text
right 'word'
BLOCK
parse_context = parse_string(element = Quiz::FillInTheBlankElement, str = text, multiline = true)
expect(parse_context.errors).to_not be_empty, "#{parse_context.errors} #{parse_context.content}"
# expect(parse_context.content).to be nil
end
it 'creates error when wrong text is provided without an answer' do
text = <<-BLOCK
begin:text
What? %5% "
end:text
wrong 'error'
BLOCK
parse_context = parse_string(element = Quiz::FillInTheBlankElement, str = text, multiline = true)
expect(parse_context.errors).to_not be_empty, "#{parse_context.errors} #{parse_context.content}"
# expect(parse_context.content).to be nil
end
it 'creates error when wrong number of right answers is provided' do
text = <<-BLOCK
begin:text
What? %5% is %2%"
end:text
right 'words'
wrong 'error'
BLOCK
parse_context = parse_string(element = Quiz::FillInTheBlankElement, str = text, multiline = true)
expect(parse_context.errors).to_not be_empty, "#{parse_context.errors} #{parse_context.content}"
# expect(parse_context.content).to be nil
end
it 'handles new course syntax' do
text = <<-BLOCK
text 'Which %4% %2% missing %1%'
right 'This is the solution I thought of. "word, is, ?". There might be others.'
right 'word' 'is' '?'
wrong 'error'
BLOCK
parse_context = parse_string(element = Quiz::FillInTheBlankElement, str = text, multiline = true)
......@@ -91,8 +132,12 @@ right 'This is the solution I thought of. "word, is, ?". There might be others.'
expect(parse_context.content.elements[3].blank_len).to eq 2
expect(parse_context.content.elements[4].text).to eq ' missing '
expect(parse_context.content.elements[5].blank_len).to eq 1
expect(parse_context.content.answers[0].answer_text).to eq 'This is the solution I thought of. "word, is, ?". There might be others.'
expect(parse_context.content.answers.answer_texts[0]).to eq 'word'
expect(parse_context.content.answers.answer_texts[1]).to eq 'is'
expect(parse_context.content.answers.answer_texts[2]).to eq '?'
expect(parse_context.content.wrong_text.answer_text).to eq 'error'
end
end
end
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment