openstax_kitchen 14.0.0 → 17.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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +47 -3
  3. data/Gemfile.lock +4 -4
  4. data/codecov.yaml +4 -0
  5. data/lib/kitchen/directions/bake_all_chapter_solutions_types.rb +30 -0
  6. data/lib/kitchen/directions/bake_all_numbered_exercise_types.rb +26 -0
  7. data/lib/kitchen/directions/bake_annotation_classes/v1.rb +11 -2
  8. data/lib/kitchen/directions/bake_autotitled_exercise/main.rb +4 -0
  9. data/lib/kitchen/directions/bake_autotitled_exercise/v3.rb +35 -0
  10. data/lib/kitchen/directions/bake_chapter_references/main.rb +15 -0
  11. data/lib/kitchen/directions/bake_chapter_references/v2.rb +53 -0
  12. data/lib/kitchen/directions/bake_chapter_references/v3.rb +45 -0
  13. data/lib/kitchen/directions/bake_chapter_summary.rb +5 -4
  14. data/lib/kitchen/directions/bake_example.rb +1 -1
  15. data/lib/kitchen/directions/bake_exercise_prefixes/main.rb +14 -0
  16. data/lib/kitchen/directions/bake_exercise_prefixes/v1.rb +22 -0
  17. data/lib/kitchen/directions/bake_footnotes/v1.rb +1 -1
  18. data/lib/kitchen/directions/bake_index/v1.rb +13 -8
  19. data/lib/kitchen/directions/bake_index/v1.xhtml.erb +6 -10
  20. data/lib/kitchen/directions/{bake_injected_exercise.rb → bake_injected_exercise/bake_injected_exercise.rb} +10 -1
  21. data/lib/kitchen/directions/bake_injected_exercise/bake_injected_exercise_question.rb +4 -0
  22. data/lib/kitchen/directions/bake_link_placeholders.rb +1 -1
  23. data/lib/kitchen/directions/bake_lo_link_labels.rb +19 -0
  24. data/lib/kitchen/directions/bake_non_introduction_pages.rb +3 -1
  25. data/lib/kitchen/directions/bake_numbered_exercise/main.rb +8 -3
  26. data/lib/kitchen/directions/bake_numbered_exercise/v1.rb +48 -7
  27. data/lib/kitchen/directions/bake_numbered_table/bake_table_body.rb +5 -1
  28. data/lib/kitchen/directions/bake_numbered_table/v2.rb +2 -1
  29. data/lib/kitchen/directions/bake_references/v1.rb +13 -0
  30. data/lib/kitchen/directions/bake_screenreader_spans.rb +22 -0
  31. data/lib/kitchen/directions/bake_unnumbered_exercise.rb +16 -0
  32. data/lib/kitchen/directions/book_answer_key_container/eob_answer_key_container.xhtml.erb +9 -0
  33. data/lib/kitchen/directions/book_answer_key_container/v1.rb +6 -2
  34. data/lib/kitchen/directions/default_strategy_for_answer_key_solutions.rb +35 -0
  35. data/lib/kitchen/directions/move_solutions_to_answer_key/answer_key_inner_container.rb +29 -0
  36. data/lib/kitchen/element_base.rb +25 -5
  37. data/lib/kitchen/element_enumerator_base.rb +18 -0
  38. data/lib/kitchen/injected_exercise_element.rb +41 -0
  39. data/lib/kitchen/injected_exercise_element_enumerator.rb +21 -0
  40. data/lib/kitchen/injected_question_element.rb +7 -0
  41. data/lib/kitchen/patches/i18n.rb +4 -0
  42. data/lib/kitchen/patches/integer.rb +32 -3
  43. data/lib/kitchen/patches/nokogiri.rb +5 -4
  44. data/lib/kitchen/patches/renderable.rb +1 -1
  45. data/lib/kitchen/selectors/base.rb +3 -0
  46. data/lib/kitchen/selectors/standard_1.rb +1 -0
  47. data/lib/kitchen/version.rb +1 -1
  48. data/lib/locales/en.yml +16 -0
  49. data/lib/locales/es.yml +6 -1
  50. metadata +18 -11
  51. data/lib/kitchen/directions/book_answer_key_container/eob_answer_key_outer_container.xhtml.erb +0 -9
  52. data/lib/kitchen/directions/move_solutions_to_answer_key/main.rb +0 -18
  53. data/lib/kitchen/directions/move_solutions_to_answer_key/strategies/calculus.rb +0 -41
  54. data/lib/kitchen/directions/move_solutions_to_answer_key/strategies/contemporary_math.rb +0 -40
  55. data/lib/kitchen/directions/move_solutions_to_answer_key/strategies/default.rb +0 -27
  56. data/lib/kitchen/directions/move_solutions_to_answer_key/strategies/precalculus.rb +0 -63
  57. data/lib/kitchen/directions/move_solutions_to_answer_key/strategies/uphysics.rb +0 -21
  58. data/lib/kitchen/directions/move_solutions_to_answer_key/v1.rb +0 -47
@@ -3,14 +3,19 @@
3
3
  module Kitchen
4
4
  module Directions
5
5
  module BakeNumberedExercise
6
+ # rubocop:disable Metrics/ParameterLists
7
+ # :/
6
8
  def self.v1(exercise:, number:, suppress_solution_if: false,
7
- note_suppressed_solutions: false, cases: false)
9
+ note_suppressed_solutions: false, cases: false, solution_stays_put: false)
8
10
  V1.new.bake(exercise: exercise, number: number, suppress_solution_if: suppress_solution_if,
9
- note_suppressed_solutions: note_suppressed_solutions, cases: cases)
11
+ note_suppressed_solutions: note_suppressed_solutions, cases: cases,
12
+ solution_stays_put: solution_stays_put)
10
13
  end
14
+ # rubocop:enable Metrics/ParameterLists
11
15
 
12
16
  def self.bake_solution_v1(exercise:, number:, divider: '. ')
13
- V1.new.bake_solution(exercise: exercise, number: number, divider: divider)
17
+ V1.new.bake_solution(exercise: exercise, number: number, divider: divider,
18
+ solution_stays_put: false)
14
19
  end
15
20
  end
16
21
  end
@@ -2,13 +2,25 @@
2
2
 
3
3
  module Kitchen::Directions::BakeNumberedExercise
4
4
  class V1
5
- def bake(exercise:, number:, suppress_solution_if: false,
6
- note_suppressed_solutions: false, cases: false)
5
+ # rubocop:disable Metrics/ParameterLists
6
+ def bake(exercise:, number:, suppress_solution_if:,
7
+ note_suppressed_solutions:, cases:, solution_stays_put:)
7
8
  problem = exercise.problem
8
9
  solution = exercise.solution
9
10
 
11
+ in_appendix = exercise.has_ancestor?(:page) && exercise.ancestor(:page).has_class?('appendix')
12
+
10
13
  # Store label information
11
- label_number = "#{exercise.ancestor(:chapter).count_in(:book)}.#{number}"
14
+ if in_appendix
15
+ label_number = number
16
+ title_label = "<span class=\"os-title-label\">#{I18n.t('exercise')}</span>"
17
+ problem_divider = ''
18
+ else
19
+ label_number = "#{exercise.ancestor(:chapter).count_in(:book)}.#{number}"
20
+ title_label = ''
21
+ problem_divider = "<span class='os-divider'>. </span>"
22
+ end
23
+
12
24
  exercise.target_label(label_text: 'exercise', custom_content: label_number, cases: cases)
13
25
 
14
26
  problem_number = "<span class='os-number'>#{number}</span>"
@@ -26,25 +38,54 @@ module Kitchen::Directions::BakeNumberedExercise
26
38
  solution.trash
27
39
  exercise.add_class('os-hasSolution-trashed') if note_suppressed_solutions
28
40
  else
29
- problem_number = "<a class='os-number' href='##{exercise.id}-solution'>#{number}</a>"
30
- bake_solution(exercise: exercise, number: number)
41
+ problem_number = \
42
+ if solution_stays_put || in_appendix
43
+ "<span class='os-number'>#{number}</span>"
44
+ else
45
+ "<a class='os-number' href='##{exercise.id}-solution'>#{number}</a>"
46
+ end
47
+ bake_solution(
48
+ exercise: exercise,
49
+ number: number,
50
+ solution_stays_put: solution_stays_put,
51
+ in_appendix: in_appendix
52
+ )
31
53
  end
32
54
  end
33
55
 
34
56
  problem.replace_children(with:
35
57
  <<~HTML
58
+ #{title_label}
36
59
  #{problem_number}
37
- <span class='os-divider'>. </span>
60
+ #{problem_divider}
38
61
  <div class="os-problem-container">#{problem.children}</div>
39
62
  HTML
40
63
  )
41
64
  end
65
+ # rubocop:enable Metrics/ParameterLists
42
66
 
43
- def bake_solution(exercise:, number:, divider: '. ')
67
+ def bake_solution(exercise:, number:, solution_stays_put:, divider: '. ', in_appendix: false)
44
68
  solution = exercise.solution
69
+ if solution_stays_put
70
+ solution.wrap_children(class: 'os-solution-container')
71
+ solution.prepend(child:
72
+ <<~HTML
73
+ <h4 class="solution-title" data-type="title">
74
+ <span class="os-text">#{I18n.t(:solution)}</span>
75
+ </h4>
76
+ HTML
77
+ )
78
+ return
79
+ end
80
+
45
81
  solution.id = "#{exercise.id}-solution"
46
82
  exercise.add_class('os-hasSolution')
47
83
 
84
+ if in_appendix
85
+ solution.wrap_children(class: 'os-solution-container')
86
+ return
87
+ end
88
+
48
89
  solution.replace_children(with:
49
90
  <<~HTML
50
91
  <a class='os-number' href='##{exercise.id}'>#{number}</a>
@@ -44,8 +44,9 @@ module Kitchen
44
44
  table.target_label(label_text: 'table', custom_content: number, cases: cases)
45
45
 
46
46
  if table.top_titled?
47
+ klass = table.text_heavy? ? 'text-heavy-top-titled' : 'top-titled'
47
48
  custom_table = CustomBody.new(table: table,
48
- klass: 'top-titled',
49
+ klass: klass,
49
50
  fake_title_class: 'os-table-title',
50
51
  fake_title: table.title,
51
52
  to_trash: table.title_row)
@@ -65,6 +66,9 @@ module Kitchen
65
66
  elsif table.text_heavy?
66
67
  custom_table = CustomBody.new(table: table, klass: 'text-heavy')
67
68
  custom_table.modify_body(has_fake_title: false)
69
+ elsif table.unstyled?
70
+ custom_table = CustomBody.new(table: table, klass: 'unstyled')
71
+ custom_table.modify_body(has_fake_title: false)
68
72
  end
69
73
  end
70
74
  end
@@ -9,7 +9,8 @@ module Kitchen::Directions::BakeNumberedTable
9
9
  Kitchen::Directions::BakeTableBody::V1.new.bake(table: table, number: number, cases: cases)
10
10
 
11
11
  caption = ''
12
- if table&.caption&.first("span[data-type='title']") && !table.top_captioned?
12
+ if (table&.caption&.first("span[data-type='title']") || table&.caption) \
13
+ && !table.top_captioned?
13
14
  caption_el = table.caption
14
15
  caption_el.add_class('os-caption')
15
16
  caption_el.name = 'span'
@@ -12,6 +12,19 @@ module Kitchen::Directions::BakeReferences
12
12
  <sup class="os-citation-number">#{link.count_in(:chapter)}</sup>
13
13
  HTML
14
14
  )
15
+
16
+ next if link.nil?
17
+
18
+ link_sibling = link.previous unless link.preceded_by_text?
19
+ next if link_sibling.nil?
20
+
21
+ next unless link_sibling&.raw&.attr('data-type') == 'cite'
22
+
23
+ link.prepend(sibling:
24
+ <<~HTML
25
+ <span class="os-reference-link-separator">, </span>
26
+ HTML
27
+ )
15
28
  end
16
29
 
17
30
  chapter.references.each do |reference|
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kitchen
4
+ module Directions
5
+ module BakeScreenreaderSpans
6
+ # Add text for accessibility.
7
+ # Additional screenreader spans can be added below.
8
+ def self.v1(book:)
9
+ book.search('u[data-effect="double-underline"]').each do |element|
10
+ element.add_previous_sibling(
11
+ '<span data-screenreader-only="true">double underline</span>'
12
+ )
13
+ end
14
+ book.search('u[data-effect="underline"]').each do |element|
15
+ element.add_previous_sibling(
16
+ '<span data-screenreader-only="true">underline</span>'
17
+ )
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kitchen
4
+ module Directions
5
+ module BakeUnnumberedExercise
6
+ def self.v1(exercise:)
7
+ exercise.problem.wrap_children(class: 'os-problem-container')
8
+ return unless exercise.solution
9
+
10
+ exercise.solutions.each do |solution|
11
+ solution.wrap_children(class: 'os-solution-container')
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,9 @@
1
+ <div class="os-eob os-<%= @solutions_or_solution %>-container" data-type="<%= @composite_element %>" data-uuid-key=".<%= @uuid_key %>">
2
+ <<%= @main_title_tag %> data-type="document-title">
3
+ <span class="os-text"><%= @title %></span>
4
+ </<%= @main_title_tag %>>
5
+ <div data-type="metadata" style="display: none;">
6
+ <h1 data-type="document-title" itemprop="name"><%= @title %></h1>
7
+ <%= @metadata.paste %>
8
+ </div>
9
+ </div>
@@ -4,10 +4,14 @@ module Kitchen::Directions::BookAnswerKeyContainer
4
4
  class V1
5
5
  renderable
6
6
 
7
- def bake(book:, solutions_plural: true)
7
+ def bake(book:, solutions_plural:)
8
+ @composite_element = 'composite-chapter'
8
9
  @metadata = book.metadata.children_to_keep.copy
9
10
  @solutions_or_solution = solutions_plural ? 'solutions' : 'solution'
10
- book.body.append(child: render(file: 'eob_answer_key_outer_container.xhtml.erb'))
11
+ @title = I18n.t(:answer_key_title)
12
+ @main_title_tag = 'h1'
13
+ @uuid_key = @solutions_or_solution
14
+ book.body.append(child: render(file: 'eob_answer_key_container.xhtml.erb'))
11
15
  book.body.first("div.os-eob.os-#{@solutions_or_solution}-container")
12
16
  end
13
17
  end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kitchen::Directions::DefaultStrategyForAnswerKeySolutions
4
+ def self.v1(strategy_options:, chapter:, append_to:)
5
+ V1.new(
6
+ strategy_options
7
+ ).bake(
8
+ chapter: chapter,
9
+ append_to: append_to
10
+ )
11
+ end
12
+
13
+ class V1
14
+ def bake(chapter:, append_to:)
15
+ bake_section(chapter: chapter, append_to: append_to)
16
+ end
17
+
18
+ protected
19
+
20
+ def bake_section(chapter:, append_to:)
21
+ @selectors.each do |selector|
22
+ chapter.search("#{selector} div[data-type='solution']").each do |solution|
23
+ append_to.append(child: solution.cut.to_s) unless @condition.present? && !@condition
24
+ end
25
+ end
26
+ end
27
+
28
+ # This method helps to obtain more strategy-specific params through
29
+ # "strategy_options: {blah1: 1, blah2: 2}"
30
+ def initialize(strategy_options)
31
+ @selectors = strategy_options[:selectors] || (raise 'missing selectors for strategy')
32
+ @condition = strategy_options[:condition]
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kitchen::Directions::AnswerKeyInnerContainer
4
+ def self.v1(chapter:, metadata_source:, append_to:, solutions_plural: true)
5
+ V1.new.bake(
6
+ chapter: chapter,
7
+ metadata_source: metadata_source,
8
+ append_to: append_to,
9
+ solutions_plural: solutions_plural
10
+ )
11
+ end
12
+
13
+ class V1
14
+ renderable
15
+
16
+ def bake(chapter:, metadata_source:, append_to:, solutions_plural:)
17
+ @solutions_or_solution = solutions_plural ? 'solutions' : 'solution'
18
+ @uuid_key = "#{@solutions_or_solution}#{chapter.count_in(:book)}"
19
+ @metadata = metadata_source.children_to_keep.copy
20
+ @composite_element = 'composite-page'
21
+ @title = "#{I18n.t(:chapter)} #{chapter.count_in(:book)}"
22
+ @main_title_tag = 'h2'
23
+
24
+ append_to.append(
25
+ child: render(file: '../book_answer_key_container/eob_answer_key_container.xhtml.erb')
26
+ ).first("div[data-uuid-key='.#{@uuid_key}']")
27
+ end
28
+ end
29
+ end
@@ -82,9 +82,13 @@ module Kitchen
82
82
  # Set the inner HTML for this element
83
83
  # @see https://www.rubydoc.info/github/sparklemotion/nokogiri/Nokogiri/XML/Node#inner_html=-instance_method Nokogiri::XML::Node#inner_html=
84
84
  # @return Object
85
+ # @!method preceded_by_text
86
+ # Returns true if the immediately preceding sibling is text
87
+ # @return Boolean
85
88
  def_delegators :@node, :name=, :name, :[], :[]=, :add_class, :remove_class,
86
89
  :text, :wrap, :children, :to_html, :remove_attribute,
87
- :key?, :classes, :path, :inner_html=
90
+ :key?, :classes, :path, :inner_html=, :add_previous_sibling,
91
+ :preceded_by_text?
88
92
 
89
93
  # @!method config
90
94
  # Get the config for this element's document
@@ -219,6 +223,22 @@ module Kitchen
219
223
  self[:'data-type']
220
224
  end
221
225
 
226
+ # Returns the element's href
227
+ #
228
+ # @return [String]
229
+ #
230
+ def href
231
+ self[:href]
232
+ end
233
+
234
+ # Sets the element's href
235
+ #
236
+ # @param value [String] the new value for the href
237
+ #
238
+ def href=(value)
239
+ self[:href] = value
240
+ end
241
+
222
242
  # A way to set values and chain them
223
243
  #
224
244
  # @param property [String, Symbol] the name of the property to set
@@ -502,12 +522,12 @@ module Kitchen
502
522
  Element.new(node: raw.parent, document: document, short_type: "parent(#{short_type})")
503
523
  end
504
524
 
505
- # returns previous element
506
- # skips double indentations that the nokigiri sometimes picks up
525
+ # returns previous element sibling
526
+ # (only returns elements or nil)
507
527
  # nil if there's no previous sibling
508
528
  #
509
529
  def previous
510
- prev = raw.previous
530
+ prev = raw.previous_element
511
531
  return prev if prev.nil?
512
532
 
513
533
  Element.new(
@@ -754,7 +774,7 @@ module Kitchen
754
774
  def_delegators :as_enumerator, :pages, :chapters, :terms, :figures, :notes, :tables, :examples,
755
775
  :metadatas, :non_introduction_pages, :units, :titles, :exercises, :references,
756
776
  :composite_pages, :composite_chapters, :solutions, :injected_questions,
757
- :search_with, :sections
777
+ :search_with, :sections, :injected_exercises
758
778
 
759
779
  # Returns this element as an enumerator (over only one element, itself)
760
780
  #
@@ -336,6 +336,24 @@ module Kitchen
336
336
  css_or_xpath: css_or_xpath, only: only, except: except)
337
337
  end
338
338
 
339
+ # Returns an enumerator that iterates through injected exercises within the scope of this enumerator
340
+ #
341
+ # @param css_or_xpath [String] additional selectors to further narrow the element iterated over;
342
+ # a "$" in this argument will be replaced with the default selector for the element being
343
+ # iterated over.
344
+ # @param only [Symbol, Callable] the name of a method to call on an element or a
345
+ # lambda or proc that accepts an element; elements will only be included in the
346
+ # search results if the method or callable returns true
347
+ # @param except [Symbol, Callable] the name of a method to call on an element or a
348
+ # lambda or proc that accepts an element; elements will not be included in the
349
+ # search results if the method or callable returns false
350
+ #
351
+ def injected_exercises(css_or_xpath=nil, only: nil, except: nil)
352
+ block_error_if(block_given?)
353
+ chain_to(InjectedExerciseElementEnumerator,
354
+ css_or_xpath: css_or_xpath, only: only, except: except)
355
+ end
356
+
339
357
  # Returns an enumerator that iterates within the scope of this enumerator
340
358
  #
341
359
  # @param css_or_xpath [String] additional selectors to further narrow the element iterated over
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kitchen
4
+ # An element for an example
5
+ #
6
+ class InjectedExerciseElement < 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: InjectedExerciseElementEnumerator)
17
+ end
18
+
19
+ # Returns the short type
20
+ # @return [Symbol]
21
+ #
22
+ def self.short_type
23
+ :injected_exercise
24
+ end
25
+
26
+ # Returns the exercise context element.
27
+ # @return [Element]
28
+ #
29
+ def exercise_context
30
+ first("div[data-type='exercise-context']")
31
+ end
32
+
33
+ # Returns the exercise question element.
34
+ # @return [Element]
35
+ #
36
+ def exercise_question
37
+ first("div[data-type='exercise-question']")
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kitchen
4
+ # An enumerator for example elements
5
+ #
6
+ class InjectedExerciseElementEnumerator < ElementEnumeratorBase
7
+
8
+ # Returns a factory for this enumerator
9
+ #
10
+ # @return [ElementEnumeratorFactory]
11
+ #
12
+ def self.factory
13
+ ElementEnumeratorFactory.new(
14
+ default_css_or_xpath: Selector.named(:injected_exercise),
15
+ sub_element_class: InjectedExerciseElement,
16
+ enumerator_class: self
17
+ )
18
+ end
19
+
20
+ end
21
+ end
@@ -55,6 +55,13 @@ module Kitchen
55
55
  first("div[data-type='question-solution']")
56
56
  end
57
57
 
58
+ # Returns the exercise context element.
59
+ # @return [Element]
60
+ #
61
+ def exercise_context_in_question
62
+ first("div[data-type='exercise-context']")
63
+ end
64
+
58
65
  # Returns the answer correctness given an alphabet
59
66
  #
60
67
  # @return [Array]
@@ -4,6 +4,10 @@ require 'twitter_cldr'
4
4
 
5
5
  # rubocop:disable Style/Documentation
6
6
  module I18n
7
+ def self.character_to_group(character)
8
+ I18n.locale == :pl ? character : I18n.transliterate(character)
9
+ end
10
+
7
11
  def self.sort_strings(first, second)
8
12
  string_sorter.compare(first, second)
9
13
  end
@@ -3,7 +3,22 @@
3
3
  # Monkey patches for +Integer+
4
4
  #
5
5
  class Integer
6
- ROMAN_NUMERALS = %w[0 i ii iii iv v vi vii viii ix x xi xii xiii xiv xv xvi xvii xviii xix xx].freeze
6
+
7
+ @roman_numerals = {
8
+ 100 => 'c',
9
+ 90 => 'xc',
10
+ 50 => 'l',
11
+ 40 => 'xl',
12
+ 10 => 'x',
13
+ 9 => 'ix',
14
+ 5 => 'v',
15
+ 4 => 'iv',
16
+ 1 => 'i'
17
+ }
18
+
19
+ class << self
20
+ attr_accessor :roman_numerals
21
+ end
7
22
 
8
23
  # Formats as different types of integers, including roman numerals.
9
24
  #
@@ -14,11 +29,25 @@ class Integer
14
29
  when :arabic
15
30
  to_s
16
31
  when :roman
17
- raise 'Unknown conversion to Roman numerals' if self >= ROMAN_NUMERALS.size
32
+ raise 'Unknown conversion to Roman numerals' if self > self.class.roman_numerals.keys.first
18
33
 
19
- ROMAN_NUMERALS[self]
34
+ to_roman
20
35
  else
21
36
  raise 'Unknown integer format'
22
37
  end
23
38
  end
39
+
40
+ def to_roman
41
+ return 0 if zero?
42
+
43
+ roman = ''
44
+ integer = self
45
+ self.class.roman_numerals.each do |number, letter|
46
+ until integer < number
47
+ roman += letter
48
+ integer -= number
49
+ end
50
+ end
51
+ roman
52
+ end
24
53
  end
@@ -67,11 +67,12 @@ module Nokogiri
67
67
  self[:class]&.split || []
68
68
  end
69
69
 
70
- def previous
71
- prev = previous_element
72
- return nil if prev.nil?
70
+ def preceded_by_text?
71
+ prev = previous_sibling
72
+ while !prev.nil? && prev.blank? do prev = prev.previous_sibling end
73
+ return false if prev.nil?
73
74
 
74
- prev.text? ? prev.previous : prev
75
+ prev.text?
75
76
  end
76
77
 
77
78
  def self.selector_to_css_nodes(selector)
@@ -29,7 +29,7 @@ class Object
29
29
  def render(file:)
30
30
  file = File.absolute_path(file, renderable_base_dir)
31
31
  template = File.open(file, 'rb', &:read)
32
- ERB.new(template).result(binding)
32
+ ERB.new(template, trim_mode: '-').result(binding)
33
33
  end
34
34
  end
35
35
  end
@@ -65,6 +65,9 @@ module Kitchen
65
65
  # Selector for a section
66
66
  # @return [String]
67
67
  attr_accessor :section
68
+ # Selector for an injected exercise
69
+ # @return [String]
70
+ attr_accessor :injected_exercise
68
71
 
69
72
  # Override specific selectors
70
73
  #
@@ -30,6 +30,7 @@ module Kitchen
30
30
  "div[data-type='question-solution']"
31
31
  self.injected_question = "div[data-type='exercise-question']"
32
32
  self.section = 'section'
33
+ self.injected_exercise = "div[data-type='injected-exercise']"
33
34
  end
34
35
 
35
36
  end
@@ -3,5 +3,5 @@
3
3
  # A library for modifying the structure of OpenStax book XML.
4
4
  #
5
5
  module Kitchen
6
- VERSION = '14.0.0'
6
+ VERSION = '17.1.0'
7
7
  end
data/lib/locales/en.yml CHANGED
@@ -28,6 +28,7 @@ en:
28
28
  iframe_link_text: Click to view content
29
29
  handbook_outline_title: Outline
30
30
  context_lead_text: 'Refer to '
31
+ lo_label_text: 'LO '
31
32
  eoc:
32
33
  glossary: Key Terms
33
34
  key-equations: Key Equations
@@ -45,3 +46,18 @@ en:
45
46
  reference: References
46
47
  index:
47
48
  main: Index
49
+ annotation_icons:
50
+ linguistic-icon:
51
+ title: Language lens icon
52
+ culture-icon:
53
+ title: Culture lens icon
54
+ dreaming-icon:
55
+ title: Generating and capturing ideas icon
56
+ visual-icon:
57
+ title: Visual learning style icon
58
+ speech-icon:
59
+ title: Voice to text icon
60
+ auditory-icon:
61
+ title: Auditory learning style icon
62
+ kinesthetic-icon:
63
+ title: Kinesthetic learning style icon
data/lib/locales/es.yml CHANGED
@@ -21,17 +21,22 @@ es:
21
21
  eoc_exercises_title: Ejercicios
22
22
  eoc_composite_metadata_title: Revisión Del Capítulo
23
23
  eoc_solutions_title: Soluciones
24
- eoc_key_concepts: Conceptos Clave
25
24
  eoc_suggested_reading: Sugerencias Para Estudios Adicionales
26
25
  eob_index_symbols_group: Símbolos
27
26
  review_exercises: Ejercicios De Repaso
28
27
  section_exercises: ! 'Sección %{number} Ejercicios'
28
+ iframe_link_text: Haga Clic Para Ver El Contenido
29
+ handbook_outline_title: Esquema
30
+ context_lead_text: 'Referirse a '
29
31
  eoc:
30
32
  glossary: Términos Clave
31
33
  key-equations: Ecuaciones Clave
32
34
  summary: Resumen
33
35
  further-research: Investigación Adicional
36
+ key-concepts: Conceptos Clave
34
37
  references: Referencias
38
+ solutions: Soluciones
39
+ section-exercises: ! 'Sección %{number} Ejercicios'
35
40
  folio:
36
41
  preface: Prefacio
37
42
  access_for_free: Acceso gratis en openstax.org