openstax_kitchen 14.0.0 → 17.1.0

Sign up to get free protection for your applications and to get access to all the features.
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