openstax_kitchen 11.0.0 → 11.1.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 +4 -4
- data/CHANGELOG.md +21 -0
- data/Gemfile.lock +1 -1
- data/lib/kitchen/composite_page_element.rb +8 -0
- data/lib/kitchen/directions/bake_chapter_introductions/bake_chapter_objectives.rb +46 -0
- data/lib/kitchen/directions/bake_chapter_introductions/bake_chapter_outline.rb +14 -0
- data/lib/kitchen/directions/bake_chapter_introductions/main.rb +43 -0
- data/lib/kitchen/directions/bake_chapter_introductions/v1.rb +56 -0
- data/lib/kitchen/directions/bake_chapter_introductions/v2.rb +91 -0
- data/lib/kitchen/directions/bake_custom_sections/main.rb +14 -0
- data/lib/kitchen/directions/bake_custom_sections/v1.rb +42 -0
- data/lib/kitchen/directions/bake_equations.rb +2 -3
- data/lib/kitchen/directions/bake_example.rb +4 -5
- data/lib/kitchen/directions/bake_figure.rb +5 -3
- data/lib/kitchen/directions/bake_iframes/main.rb +11 -0
- data/lib/kitchen/directions/bake_iframes/v1.rb +25 -0
- data/lib/kitchen/directions/bake_index/main.rb +2 -2
- data/lib/kitchen/directions/bake_index/v1.rb +39 -7
- data/lib/kitchen/directions/bake_index/v1.xhtml.erb +3 -3
- data/lib/kitchen/directions/bake_injected_exercise.rb +18 -0
- data/lib/kitchen/directions/bake_injected_exercise_question.rb +71 -0
- data/lib/kitchen/directions/bake_link_placeholders.rb +15 -2
- data/lib/kitchen/directions/bake_lists_with_para.rb +3 -3
- data/lib/kitchen/directions/bake_notes/bake_autotitled_notes.rb +5 -5
- data/lib/kitchen/directions/bake_notes/bake_note_subtitle.rb +6 -2
- data/lib/kitchen/directions/bake_notes/bake_numbered_notes/main.rb +13 -3
- data/lib/kitchen/directions/bake_notes/bake_numbered_notes/v1.rb +29 -25
- data/lib/kitchen/directions/bake_notes/bake_numbered_notes/v2.rb +22 -17
- data/lib/kitchen/directions/bake_notes/bake_numbered_notes/v3.rb +27 -22
- data/lib/kitchen/directions/bake_numbered_exercise/main.rb +3 -2
- data/lib/kitchen/directions/bake_numbered_exercise/v1.rb +6 -24
- data/lib/kitchen/directions/bake_numbered_table/bake_table_body.rb +3 -3
- data/lib/kitchen/directions/bake_numbered_table/main.rb +4 -4
- data/lib/kitchen/directions/bake_numbered_table/v1.rb +3 -3
- data/lib/kitchen/directions/bake_numbered_table/v2.rb +3 -3
- data/lib/kitchen/directions/bake_toc.rb +1 -1
- data/lib/kitchen/directions/move_solutions_to_answer_key/move_solutions_from_exercise_section.rb +1 -7
- data/lib/kitchen/directions/move_solutions_to_answer_key/move_solutions_from_numbered_note.rb +1 -7
- data/lib/kitchen/directions/move_solutions_to_answer_key/strategies/contemporary_math.rb +17 -0
- data/lib/kitchen/element_base.rb +38 -2
- data/lib/kitchen/element_enumerator_base.rb +35 -0
- data/lib/kitchen/injected_question_element.rb +77 -0
- data/lib/kitchen/injected_question_element_enumerator.rb +21 -0
- data/lib/kitchen/selectors/base.rb +6 -0
- data/lib/kitchen/selectors/standard_1.rb +3 -0
- data/lib/kitchen/solution_element_enumerator.rb +21 -0
- data/lib/kitchen/version.rb +1 -1
- data/lib/locales/en.yml +6 -4
- data/lib/locales/es.yml +5 -4
- data/lib/locales/pl.yml +52 -7
- metadata +17 -6
- data/lib/kitchen/directions/bake_chapter_introductions/chapter_introduction.xhtml.erb +0 -0
- data/lib/kitchen/directions/bake_chapter_introductions.rb +0 -65
- data/lib/kitchen/directions/bake_notes/bake_note_iframes.rb +0 -27
@@ -1,29 +1,34 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module Kitchen::Directions
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
classes
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
3
|
+
module Kitchen::Directions
|
4
|
+
module BakeNumberedNotes
|
5
|
+
class V3
|
6
|
+
# for the try it notes, must be called AFTER bake_exercises
|
7
|
+
def bake(book:, classes:, suppress_solution: true)
|
8
|
+
classes.each do |klass|
|
9
|
+
book.chapters.pages.notes("$.#{klass}").each do |note|
|
10
|
+
note.wrap_children(class: 'os-note-body')
|
11
|
+
previous_example = note.previous
|
12
|
+
os_number = previous_example&.first('.os-number')&.children&.to_s
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
)
|
21
|
-
|
22
|
-
note.title&.trash
|
23
|
-
note.exercises.each do |exercise|
|
24
|
-
Kitchen::Directions::BakeNumberedNotes.bake_note_exercise(
|
25
|
-
note: note, exercise: exercise, divider: '. ', suppress_solution: suppress_solution
|
14
|
+
note.prepend(child:
|
15
|
+
<<~HTML
|
16
|
+
<h3 class="os-title">
|
17
|
+
<span class="os-title-label">#{note.autogenerated_title}</span>
|
18
|
+
<span class="os-number">#{os_number}</span>
|
19
|
+
</h3>
|
20
|
+
HTML
|
26
21
|
)
|
22
|
+
|
23
|
+
note.title&.trash
|
24
|
+
note.exercises.each do |exercise|
|
25
|
+
BakeNumberedNotes.bake_note_exercise(
|
26
|
+
note: note, exercise: exercise, divider: '. ', suppress_solution: suppress_solution
|
27
|
+
)
|
28
|
+
end
|
29
|
+
note.injected_questions.each do |question|
|
30
|
+
BakeNumberedNotes.bake_note_injected_question(note: note, question: question)
|
31
|
+
end
|
27
32
|
end
|
28
33
|
end
|
29
34
|
end
|
@@ -3,9 +3,10 @@
|
|
3
3
|
module Kitchen
|
4
4
|
module Directions
|
5
5
|
module BakeNumberedExercise
|
6
|
-
def self.v1(exercise:, number:, suppress_solution_if: false,
|
6
|
+
def self.v1(exercise:, number:, suppress_solution_if: false,
|
7
|
+
note_suppressed_solutions: false, cases: false)
|
7
8
|
V1.new.bake(exercise: exercise, number: number, suppress_solution_if: suppress_solution_if,
|
8
|
-
note_suppressed_solutions: note_suppressed_solutions)
|
9
|
+
note_suppressed_solutions: note_suppressed_solutions, cases: cases)
|
9
10
|
end
|
10
11
|
|
11
12
|
def self.bake_solution_v1(exercise:, number:, divider: '. ')
|
@@ -2,14 +2,15 @@
|
|
2
2
|
|
3
3
|
module Kitchen::Directions::BakeNumberedExercise
|
4
4
|
class V1
|
5
|
-
def bake(exercise:, number:, suppress_solution_if: false,
|
5
|
+
def bake(exercise:, number:, suppress_solution_if: false,
|
6
|
+
note_suppressed_solutions: false, cases: false)
|
6
7
|
problem = exercise.problem
|
7
8
|
solution = exercise.solution
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
# Store label information
|
11
|
+
label_number = "#{exercise.ancestor(:chapter).count_in(:book)}.#{number}"
|
12
|
+
exercise.target_label(label_text: 'exercise', custom_content: label_number, cases: cases)
|
13
|
+
|
13
14
|
problem_number = "<span class='os-number'>#{number}</span>"
|
14
15
|
|
15
16
|
suppress_solution =
|
@@ -37,25 +38,6 @@ module Kitchen::Directions::BakeNumberedExercise
|
|
37
38
|
<div class="os-problem-container">#{problem.children}</div>
|
38
39
|
HTML
|
39
40
|
)
|
40
|
-
|
41
|
-
# Bake multipart questions
|
42
|
-
multipart_questions = problem.search('div.question-stem')
|
43
|
-
return unless multipart_questions.count > 1
|
44
|
-
|
45
|
-
multipart_clipboard = Kitchen::Clipboard.new
|
46
|
-
multipart_questions.each do |question|
|
47
|
-
question.wrap('<li>')
|
48
|
-
question = question.parent
|
49
|
-
question.cut(to: multipart_clipboard)
|
50
|
-
end
|
51
|
-
|
52
|
-
problem.first('div.question-stimulus, div.exercise-stimulus').append(sibling:
|
53
|
-
<<~HTML
|
54
|
-
<ol type="a">
|
55
|
-
#{multipart_clipboard.paste}
|
56
|
-
</ol>
|
57
|
-
HTML
|
58
|
-
)
|
59
41
|
end
|
60
42
|
|
61
43
|
def bake_solution(exercise:, number:, divider: '. ')
|
@@ -36,12 +36,12 @@ module Kitchen
|
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
|
-
def bake(table:, number:)
|
39
|
+
def bake(table:, number:, cases: false)
|
40
40
|
table.remove_attribute('summary')
|
41
41
|
table.wrap(%(<div class="os-table">))
|
42
42
|
|
43
|
-
|
44
|
-
table.
|
43
|
+
# Store label information
|
44
|
+
table.target_label(label_text: 'table', custom_content: number, cases: cases)
|
45
45
|
|
46
46
|
if table.top_titled?
|
47
47
|
custom_table = CustomBody.new(table: table,
|
@@ -3,12 +3,12 @@
|
|
3
3
|
module Kitchen
|
4
4
|
module Directions
|
5
5
|
module BakeNumberedTable
|
6
|
-
def self.v1(table:, number:, always_caption: false)
|
7
|
-
V1.new.bake(table: table, number: number, always_caption: always_caption)
|
6
|
+
def self.v1(table:, number:, always_caption: false, cases: false)
|
7
|
+
V1.new.bake(table: table, number: number, always_caption: always_caption, cases: cases)
|
8
8
|
end
|
9
9
|
|
10
|
-
def self.v2(table:, number:)
|
11
|
-
V2.new.bake(table: table, number: number)
|
10
|
+
def self.v2(table:, number:, cases: false)
|
11
|
+
V2.new.bake(table: table, number: number, cases: cases)
|
12
12
|
end
|
13
13
|
end
|
14
14
|
end
|
@@ -3,8 +3,8 @@
|
|
3
3
|
module Kitchen::Directions::BakeNumberedTable
|
4
4
|
class V1
|
5
5
|
|
6
|
-
def bake(table:, number:, always_caption: false)
|
7
|
-
Kitchen::Directions::BakeTableBody::V1.new.bake(table: table, number: number)
|
6
|
+
def bake(table:, number:, always_caption: false, cases: false)
|
7
|
+
Kitchen::Directions::BakeTableBody::V1.new.bake(table: table, number: number, cases: cases)
|
8
8
|
|
9
9
|
# TODO: extra spaces added here to match legacy implementation, but probably not meaningful?
|
10
10
|
new_caption = ''
|
@@ -31,7 +31,7 @@ module Kitchen::Directions::BakeNumberedTable
|
|
31
31
|
table.append(sibling:
|
32
32
|
<<~HTML
|
33
33
|
<div class="os-caption-container">
|
34
|
-
<span class="os-title-label">#{I18n.t(
|
34
|
+
<span class="os-title-label">#{I18n.t("table#{'.nominative' if cases}")} </span>
|
35
35
|
<span class="os-number">#{number}</span>
|
36
36
|
<span class="os-divider"> </span>#{caption_title}
|
37
37
|
<span class="os-divider"> </span>#{new_caption}
|
@@ -5,8 +5,8 @@ module Kitchen::Directions::BakeNumberedTable
|
|
5
5
|
# V2 caption titles are nested within an .os-caption span
|
6
6
|
class V2
|
7
7
|
|
8
|
-
def bake(table:, number:)
|
9
|
-
Kitchen::Directions::BakeTableBody::V1.new.bake(table: table, number: number)
|
8
|
+
def bake(table:, number:, cases: false)
|
9
|
+
Kitchen::Directions::BakeTableBody::V1.new.bake(table: table, number: number, cases: cases)
|
10
10
|
|
11
11
|
caption = ''
|
12
12
|
if table&.caption&.first("span[data-type='title']") && !table.top_captioned?
|
@@ -19,7 +19,7 @@ module Kitchen::Directions::BakeNumberedTable
|
|
19
19
|
table.append(sibling:
|
20
20
|
<<~HTML
|
21
21
|
<div class="os-caption-container">
|
22
|
-
<span class="os-title-label">#{I18n.t(
|
22
|
+
<span class="os-title-label">#{I18n.t("table#{'.nominative' if cases}")} </span>
|
23
23
|
<span class="os-number">#{number}</span>
|
24
24
|
<span class="os-divider"> </span>
|
25
25
|
#{caption}
|
@@ -103,7 +103,7 @@ module Kitchen
|
|
103
103
|
raise "do not know what TOC class to use for page with classes #{page.classes}"
|
104
104
|
end
|
105
105
|
when CompositePageElement
|
106
|
-
if page.is_index?
|
106
|
+
if page.is_index? || page.is_index_of_type?
|
107
107
|
'os-toc-index'
|
108
108
|
elsif page.is_citation_reference?
|
109
109
|
'os-toc-reference'
|
data/lib/kitchen/directions/move_solutions_to_answer_key/move_solutions_from_exercise_section.rb
CHANGED
@@ -8,13 +8,7 @@ module Kitchen::Directions::MoveSolutionsFromExerciseSection
|
|
8
8
|
|
9
9
|
class V1
|
10
10
|
def bake(chapter:, append_to:, section_class:, title_number:)
|
11
|
-
solutions_clipboard =
|
12
|
-
chapter.search("section.#{section_class}").exercises.each do |exercise|
|
13
|
-
solution = exercise.solution
|
14
|
-
next unless solution
|
15
|
-
|
16
|
-
solution.cut(to: solutions_clipboard)
|
17
|
-
end
|
11
|
+
solutions_clipboard = chapter.search("section.#{section_class}").solutions.cut
|
18
12
|
|
19
13
|
return if solutions_clipboard.items.empty?
|
20
14
|
|
data/lib/kitchen/directions/move_solutions_to_answer_key/move_solutions_from_numbered_note.rb
CHANGED
@@ -7,13 +7,7 @@ module Kitchen::Directions::MoveSolutionsFromNumberedNote
|
|
7
7
|
|
8
8
|
class V1
|
9
9
|
def bake(chapter:, append_to:, note_class:)
|
10
|
-
solutions_clipboard =
|
11
|
-
chapter.notes("$.#{note_class}").exercises.each do |exercise|
|
12
|
-
solution = exercise.solution
|
13
|
-
next unless solution
|
14
|
-
|
15
|
-
solution.cut(to: solutions_clipboard)
|
16
|
-
end
|
10
|
+
solutions_clipboard = chapter.notes("$.#{note_class}").solutions.cut
|
17
11
|
|
18
12
|
return if solutions_clipboard.items.empty?
|
19
13
|
|
@@ -17,6 +17,23 @@ module Kitchen::Directions::MoveSolutionsToAnswerKey
|
|
17
17
|
Kitchen::Directions::MoveSolutionsFromNumberedNote.v1(
|
18
18
|
chapter: chapter, append_to: append_to, note_class: 'your-turn'
|
19
19
|
)
|
20
|
+
|
21
|
+
# Bake section exercises
|
22
|
+
chapter.non_introduction_pages.each do |page|
|
23
|
+
number = "#{chapter.count_in(:book)}.#{page.count_in(:chapter)}"
|
24
|
+
Kitchen::Directions::MoveSolutionsFromExerciseSection.v1(
|
25
|
+
chapter: page, append_to: append_to, section_class: 'section-exercises',
|
26
|
+
title_number: number
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Bake other exercise sections
|
31
|
+
classes = %w[chapter-review chapter-test]
|
32
|
+
classes.each do |klass|
|
33
|
+
Kitchen::Directions::MoveSolutionsFromExerciseSection.v1(
|
34
|
+
chapter: chapter, append_to: append_to, section_class: klass
|
35
|
+
)
|
36
|
+
end
|
20
37
|
end
|
21
38
|
end
|
22
39
|
end
|
data/lib/kitchen/element_base.rb
CHANGED
@@ -67,6 +67,9 @@ module Kitchen
|
|
67
67
|
# @!method remove_attribute
|
68
68
|
# Removes an attribute from the element
|
69
69
|
# @see https://www.rubydoc.info/github/sparklemotion/nokogiri/Nokogiri/XML/Node#remove_attribute-instance_method Nokogiri::XML::Node#remove_attribute
|
70
|
+
# @!method key?(attribute)
|
71
|
+
# Returns true if attribute is set
|
72
|
+
# @see https://www.rubydoc.info/github/sparklemotion/nokogiri/Nokogiri/XML/Node#key%3F-instance_method Nokogiri::XML::Node#key?(attribute)
|
70
73
|
# @!method classes
|
71
74
|
# Gets the element's classes
|
72
75
|
# @see https://www.rubydoc.info/github/sparklemotion/nokogiri/Nokogiri/XML/Node#classes-instance_method Nokogiri::XML::Node#classes
|
@@ -81,7 +84,7 @@ module Kitchen
|
|
81
84
|
# @return Object
|
82
85
|
def_delegators :@node, :name=, :name, :[], :[]=, :add_class, :remove_class,
|
83
86
|
:text, :wrap, :children, :to_html, :remove_attribute,
|
84
|
-
:classes, :path, :inner_html=
|
87
|
+
:key?, :classes, :path, :inner_html=
|
85
88
|
|
86
89
|
# @!method config
|
87
90
|
# Get the config for this element's document
|
@@ -722,11 +725,44 @@ module Kitchen
|
|
722
725
|
end
|
723
726
|
end
|
724
727
|
|
728
|
+
# Creates labels for links to inside elements
|
729
|
+
# like Figures, Tables, Equations, Exercises, Notes.
|
730
|
+
#
|
731
|
+
# @param label_text [String] label of the element defined in yml file.
|
732
|
+
# (e.g. "Figure", "Table", "Equation")
|
733
|
+
# @param custom_content [String] might be numbering of the element or text
|
734
|
+
# copied from content (e.g. note title)
|
735
|
+
# @param cases [Boolean] true if labels should use grammatical cases
|
736
|
+
# (used in Polish books)
|
737
|
+
# @return [Pantry]
|
738
|
+
#
|
739
|
+
def target_label(label_text: nil, custom_content: nil, cases: false)
|
740
|
+
if cases
|
741
|
+
cases = %w[nominative genitive dative accusative instrumental locative vocative]
|
742
|
+
element_labels = {}
|
743
|
+
|
744
|
+
cases.each do |label_case|
|
745
|
+
element_labels[label_case] = "#{I18n.t("#{label_text}.#{label_case}")} #{custom_content}"
|
746
|
+
|
747
|
+
element_label_case = element_labels[label_case]
|
748
|
+
|
749
|
+
pantry(name: "#{label_case}_link_text").store element_label_case, label: id if id
|
750
|
+
end
|
751
|
+
else
|
752
|
+
element_label = if label_text
|
753
|
+
"#{I18n.t(label_text.to_s)} #{custom_content}"
|
754
|
+
else
|
755
|
+
custom_content
|
756
|
+
end
|
757
|
+
pantry(name: :link_text).store element_label, label: id if id
|
758
|
+
end
|
759
|
+
end
|
760
|
+
|
725
761
|
# @!method pages
|
726
762
|
# Returns a pages enumerator
|
727
763
|
def_delegators :as_enumerator, :pages, :chapters, :terms, :figures, :notes, :tables, :examples,
|
728
764
|
:metadatas, :non_introduction_pages, :units, :titles, :exercises, :references,
|
729
|
-
:composite_pages, :composite_chapters
|
765
|
+
:composite_pages, :composite_chapters, :solutions, :injected_questions
|
730
766
|
|
731
767
|
# Returns this element as an enumerator (over only one element, itself)
|
732
768
|
#
|
@@ -283,6 +283,41 @@ module Kitchen
|
|
283
283
|
chain_to(MetadataElementEnumerator, css_or_xpath: css_or_xpath, only: only, except: except)
|
284
284
|
end
|
285
285
|
|
286
|
+
# Returns an enumerator that iterates through solutions within the scope of this enumerator
|
287
|
+
#
|
288
|
+
# @param css_or_xpath [String] additional selectors to further narrow the element iterated over;
|
289
|
+
# a "$" in this argument will be replaced with the default selector for the element being
|
290
|
+
# iterated over.
|
291
|
+
# @param only [Symbol, Callable] the name of a method to call on an element or a
|
292
|
+
# lambda or proc that accepts an element; elements will only be included in the
|
293
|
+
# search results if the method or callable returns true
|
294
|
+
# @param except [Symbol, Callable] the name of a method to call on an element or a
|
295
|
+
# lambda or proc that accepts an element; elements will not be included in the
|
296
|
+
# search results if the method or callable returns false
|
297
|
+
#
|
298
|
+
def solutions(css_or_xpath=nil, only: nil, except: nil)
|
299
|
+
block_error_if(block_given?)
|
300
|
+
chain_to(SolutionElementEnumerator, css_or_xpath: css_or_xpath, only: only, except: except)
|
301
|
+
end
|
302
|
+
|
303
|
+
# Returns an enumerator that iterates through injected questions within the scope of this enumerator
|
304
|
+
#
|
305
|
+
# @param css_or_xpath [String] additional selectors to further narrow the element iterated over;
|
306
|
+
# a "$" in this argument will be replaced with the default selector for the element being
|
307
|
+
# iterated over.
|
308
|
+
# @param only [Symbol, Callable] the name of a method to call on an element or a
|
309
|
+
# lambda or proc that accepts an element; elements will only be included in the
|
310
|
+
# search results if the method or callable returns true
|
311
|
+
# @param except [Symbol, Callable] the name of a method to call on an element or a
|
312
|
+
# lambda or proc that accepts an element; elements will not be included in the
|
313
|
+
# search results if the method or callable returns false
|
314
|
+
#
|
315
|
+
def injected_questions(css_or_xpath=nil, only: nil, except: nil)
|
316
|
+
block_error_if(block_given?)
|
317
|
+
chain_to(InjectedQuestionElementEnumerator,
|
318
|
+
css_or_xpath: css_or_xpath, only: only, except: except)
|
319
|
+
end
|
320
|
+
|
286
321
|
# Returns an enumerator that iterates within the scope of this enumerator
|
287
322
|
#
|
288
323
|
# @param css_or_xpath [String] additional selectors to further narrow the element iterated over
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kitchen
|
4
|
+
# An element for an example
|
5
|
+
#
|
6
|
+
class InjectedQuestionElement < ElementBase
|
7
|
+
|
8
|
+
# Creates a new +InjectedQuestionElement+
|
9
|
+
#
|
10
|
+
# @param node [Nokogiri::XML::Node] the node this element wraps
|
11
|
+
# @param document [Document] this element's document
|
12
|
+
#
|
13
|
+
def initialize(node:, document: nil)
|
14
|
+
super(node: node,
|
15
|
+
document: document,
|
16
|
+
enumerator_class: InjectedQuestionElementEnumerator)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Returns the short type
|
20
|
+
# @return [Symbol]
|
21
|
+
#
|
22
|
+
def self.short_type
|
23
|
+
:injected_question
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns the question stimulus as an element.
|
27
|
+
#
|
28
|
+
# @return [Element]
|
29
|
+
#
|
30
|
+
def stimulus
|
31
|
+
first('div[data-type="question-stimulus"]')
|
32
|
+
end
|
33
|
+
|
34
|
+
# Returns the question stem as an element.
|
35
|
+
#
|
36
|
+
# @return [Element]
|
37
|
+
#
|
38
|
+
def stem
|
39
|
+
first('div[data-type="question-stem"]')
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns the list of answers as an element.
|
43
|
+
#
|
44
|
+
# @return [Element]
|
45
|
+
#
|
46
|
+
def answers
|
47
|
+
first("ol[data-type='question-answers']")
|
48
|
+
end
|
49
|
+
|
50
|
+
# Returns the solution element.
|
51
|
+
#
|
52
|
+
# @return [Element]
|
53
|
+
#
|
54
|
+
def solution
|
55
|
+
first("div[data-type='question-solution']")
|
56
|
+
end
|
57
|
+
|
58
|
+
# Returns the answer correctness given an alphabet
|
59
|
+
#
|
60
|
+
# @return [Array]
|
61
|
+
#
|
62
|
+
def correct_answer_letters(alphabet)
|
63
|
+
answers.search('li[data-type="question-answer"]').each_with_index.map \
|
64
|
+
do |answer, index|
|
65
|
+
answer[:'data-correctness'] == '1.0' ? alphabet[index] : nil
|
66
|
+
end.compact
|
67
|
+
end
|
68
|
+
|
69
|
+
# Returns or creates the question's id
|
70
|
+
#
|
71
|
+
# @return [String]
|
72
|
+
#
|
73
|
+
def id
|
74
|
+
self[:id] ||= "auto_#{ancestor(:page).id.gsub(/page_/, '')}_#{self[:'data-id']}"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|