openstax_kitchen 3.2.0 → 6.0.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 (144) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/changelog.yml +27 -0
  3. data/.github/workflows/rubocop.yml +28 -0
  4. data/.gitignore +1 -1
  5. data/CHANGELOG.md +94 -1
  6. data/Gemfile.lock +26 -18
  7. data/README.md +16 -0
  8. data/codecov.yaml +1 -0
  9. data/docker/ci +0 -1
  10. data/docker/rubocop +22 -0
  11. data/lib/kitchen/book_document.rb +1 -1
  12. data/lib/kitchen/book_element.rb +16 -2
  13. data/lib/kitchen/chapter_element.rb +10 -13
  14. data/lib/kitchen/chapter_element_enumerator.rb +1 -1
  15. data/lib/kitchen/composite_chapter_element.rb +7 -11
  16. data/lib/kitchen/composite_chapter_element_enumerator.rb +21 -0
  17. data/lib/kitchen/composite_page_element.rb +15 -10
  18. data/lib/kitchen/composite_page_element_enumerator.rb +1 -1
  19. data/lib/kitchen/config.rb +14 -0
  20. data/lib/kitchen/directions/bake_appendix.rb +3 -1
  21. data/lib/kitchen/directions/bake_chapter_glossary/main.rb +18 -0
  22. data/lib/kitchen/directions/bake_chapter_glossary/v1.rb +30 -0
  23. data/lib/kitchen/directions/bake_chapter_introductions.rb +23 -16
  24. data/lib/kitchen/directions/bake_chapter_introductions/chapter_introduction.xhtml.erb +0 -0
  25. data/lib/kitchen/directions/bake_chapter_key_concepts/main.rb +7 -2
  26. data/lib/kitchen/directions/bake_chapter_key_concepts/v1.rb +12 -7
  27. data/lib/kitchen/directions/bake_chapter_key_equations.rb +26 -21
  28. data/lib/kitchen/directions/bake_chapter_references/main.rb +16 -0
  29. data/lib/kitchen/directions/bake_chapter_references/v1.rb +35 -0
  30. data/lib/kitchen/directions/bake_chapter_section_exercises/main.rb +2 -2
  31. data/lib/kitchen/directions/bake_chapter_section_exercises/v1.rb +2 -1
  32. data/lib/kitchen/directions/bake_chapter_solutions/main.rb +11 -0
  33. data/lib/kitchen/directions/bake_chapter_solutions/v1.rb +37 -0
  34. data/lib/kitchen/directions/bake_chapter_summary.rb +56 -43
  35. data/lib/kitchen/directions/bake_composite_chapters.rb +1 -1
  36. data/lib/kitchen/directions/bake_composite_pages.rb +1 -1
  37. data/lib/kitchen/directions/bake_equations.rb +2 -2
  38. data/lib/kitchen/directions/bake_example.rb +8 -1
  39. data/lib/kitchen/directions/bake_figure.rb +14 -1
  40. data/lib/kitchen/directions/bake_first_elements.rb +22 -0
  41. data/lib/kitchen/directions/bake_footnotes/main.rb +2 -2
  42. data/lib/kitchen/directions/bake_footnotes/v1.rb +13 -9
  43. data/lib/kitchen/directions/bake_free_response/free_response.xhtml.erb +10 -0
  44. data/lib/kitchen/directions/{bake_chapter_review → bake_free_response}/main.rb +3 -3
  45. data/lib/kitchen/directions/bake_free_response/v1.rb +29 -0
  46. data/lib/kitchen/directions/bake_further_research.rb +61 -0
  47. data/lib/kitchen/directions/bake_index/v1.rb +36 -26
  48. data/lib/kitchen/directions/bake_link_placeholders.rb +1 -1
  49. data/lib/kitchen/directions/bake_notes/bake_note_subtitle.rb +4 -0
  50. data/lib/kitchen/directions/bake_notes/bake_numbered_notes/main.rb +43 -0
  51. data/lib/kitchen/directions/bake_notes/bake_numbered_notes/v1.rb +37 -0
  52. data/lib/kitchen/directions/bake_notes/bake_numbered_notes/v2.rb +25 -0
  53. data/lib/kitchen/directions/bake_notes/bake_numbered_notes/v3.rb +32 -0
  54. data/lib/kitchen/directions/bake_numbered_exercise/main.rb +7 -2
  55. data/lib/kitchen/directions/bake_numbered_exercise/v1.rb +34 -12
  56. data/lib/kitchen/directions/bake_numbered_table/bake_table_body.rb +29 -0
  57. data/lib/kitchen/directions/bake_numbered_table/main.rb +4 -0
  58. data/lib/kitchen/directions/bake_numbered_table/v1.rb +1 -24
  59. data/lib/kitchen/directions/bake_numbered_table/v2.rb +31 -0
  60. data/lib/kitchen/directions/bake_page_abstracts.rb +1 -1
  61. data/lib/kitchen/directions/bake_preface/main.rb +2 -2
  62. data/lib/kitchen/directions/bake_preface/v1.rb +3 -2
  63. data/lib/kitchen/directions/bake_references/main.rb +16 -0
  64. data/lib/kitchen/directions/bake_references/v1.rb +48 -0
  65. data/lib/kitchen/directions/bake_suggested_reading.rb +5 -0
  66. data/lib/kitchen/directions/bake_toc.rb +4 -2
  67. data/lib/kitchen/directions/book_answer_key_container/eob_answer_key_outer_container.xhtml.erb +9 -0
  68. data/lib/kitchen/directions/book_answer_key_container/main.rb +11 -0
  69. data/lib/kitchen/directions/book_answer_key_container/v1.rb +14 -0
  70. data/lib/kitchen/directions/chapter_review_container/chapter_review.xhtml.erb +9 -0
  71. data/lib/kitchen/directions/chapter_review_container/main.rb +11 -0
  72. data/lib/kitchen/directions/chapter_review_container/v1.rb +15 -0
  73. data/lib/kitchen/directions/eoc_section_title_link_snippet.rb +14 -1
  74. data/lib/kitchen/directions/move_exercises_to_eoc/main.rb +37 -0
  75. data/lib/kitchen/directions/{bake_chapter_review_exercises → move_exercises_to_eoc}/v1.rb +8 -10
  76. data/lib/kitchen/directions/{bake_chapter_review_exercises → move_exercises_to_eoc}/v2.rb +8 -9
  77. data/lib/kitchen/directions/move_exercises_to_eoc/v3.rb +49 -0
  78. data/lib/kitchen/directions/{bake_chapter_answer_key → move_solutions_to_answer_key}/main.rb +7 -3
  79. data/lib/kitchen/directions/{bake_chapter_answer_key → move_solutions_to_answer_key}/strategies/calculus.rb +1 -1
  80. data/lib/kitchen/directions/move_solutions_to_answer_key/strategies/default.rb +27 -0
  81. data/lib/kitchen/directions/move_solutions_to_answer_key/strategies/precalculus.rb +84 -0
  82. data/lib/kitchen/directions/{bake_chapter_answer_key → move_solutions_to_answer_key}/strategies/uphysics.rb +7 -5
  83. data/lib/kitchen/directions/{bake_chapter_answer_key → move_solutions_to_answer_key}/v1.rb +12 -6
  84. data/lib/kitchen/document.rb +20 -42
  85. data/lib/kitchen/element.rb +9 -3
  86. data/lib/kitchen/element_base.rb +108 -21
  87. data/lib/kitchen/element_enumerator_base.rb +33 -2
  88. data/lib/kitchen/element_enumerator_factory.rb +28 -12
  89. data/lib/kitchen/element_factory.rb +3 -3
  90. data/lib/kitchen/example_element.rb +8 -11
  91. data/lib/kitchen/example_element_enumerator.rb +1 -1
  92. data/lib/kitchen/exercise_element.rb +7 -10
  93. data/lib/kitchen/exercise_element_enumerator.rb +1 -1
  94. data/lib/kitchen/figure_element.rb +8 -11
  95. data/lib/kitchen/figure_element_enumerator.rb +1 -1
  96. data/lib/kitchen/id_tracker.rb +68 -0
  97. data/lib/kitchen/metadata_element.rb +8 -2
  98. data/lib/kitchen/metadata_element_enumerator.rb +1 -1
  99. data/lib/kitchen/note_element.rb +8 -11
  100. data/lib/kitchen/note_element_enumerator.rb +1 -1
  101. data/lib/kitchen/oven.rb +5 -1
  102. data/lib/kitchen/page_element.rb +27 -12
  103. data/lib/kitchen/page_element_enumerator.rb +1 -1
  104. data/lib/kitchen/patches/i18n.rb +34 -0
  105. data/lib/kitchen/patches/integer.rb +24 -0
  106. data/lib/kitchen/patches/nokogiri.rb +62 -0
  107. data/lib/kitchen/patches/nokogiri_profiling.rb +60 -0
  108. data/lib/kitchen/reference_element.rb +27 -0
  109. data/lib/kitchen/references_element_enumerator.rb +20 -0
  110. data/lib/kitchen/search_query.rb +31 -3
  111. data/lib/kitchen/selector.rb +25 -0
  112. data/lib/kitchen/selectors/base.rb +39 -0
  113. data/lib/kitchen/selectors/standard_1.rb +13 -0
  114. data/lib/kitchen/table_element.rb +8 -11
  115. data/lib/kitchen/table_element_enumerator.rb +1 -1
  116. data/lib/kitchen/templates/eob_section_title_template.xhtml.erb +10 -0
  117. data/lib/kitchen/templates/eoc_section_title_template.xhtml.erb +10 -0
  118. data/lib/kitchen/term_element.rb +5 -8
  119. data/lib/kitchen/term_element_enumerator.rb +1 -1
  120. data/lib/kitchen/unit_element.rb +13 -7
  121. data/lib/kitchen/unit_element_enumerator.rb +1 -1
  122. data/lib/kitchen/version.rb +1 -1
  123. data/lib/locales/en.yml +5 -1
  124. data/lib/locales/es.yml +33 -0
  125. data/lib/locales/pl.yml +3 -2
  126. data/lib/openstax_kitchen.rb +2 -5
  127. data/openstax_kitchen.gemspec +1 -0
  128. metadata +66 -25
  129. data/.github/config.yml +0 -14
  130. data/lib/kitchen/directions/bake_book_answer_key/eob_solutions_container.xhtml.erb +0 -9
  131. data/lib/kitchen/directions/bake_book_answer_key/main.rb +0 -11
  132. data/lib/kitchen/directions/bake_book_answer_key/v1.rb +0 -13
  133. data/lib/kitchen/directions/bake_chapter_glossary.rb +0 -39
  134. data/lib/kitchen/directions/bake_chapter_key_concepts/key_concepts.xhtml.erb +0 -16
  135. data/lib/kitchen/directions/bake_chapter_review/chapter_review.xhtml.erb +0 -9
  136. data/lib/kitchen/directions/bake_chapter_review/v1.rb +0 -13
  137. data/lib/kitchen/directions/bake_chapter_review_exercises/main.rb +0 -15
  138. data/lib/kitchen/directions/bake_chapter_review_exercises/review_exercises.xhtml.erb +0 -10
  139. data/lib/kitchen/directions/bake_exercises/main.rb +0 -12
  140. data/lib/kitchen/directions/bake_exercises/v1.rb +0 -169
  141. data/lib/kitchen/directions/bake_notes/bake_notes.rb +0 -48
  142. data/lib/kitchen/directions/bake_notes/bake_numbered_notes.rb +0 -63
  143. data/lib/kitchen/directions/bake_problem_first_elements.rb +0 -16
  144. data/lib/kitchen/transliterations.rb +0 -21
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Kitchen::Directions::BakeChapterAnswerKey
3
+ module Kitchen::Directions::MoveSolutionsToAnswerKey
4
4
  module Strategies
5
5
  class UPhysics
6
6
  def bake(chapter:, append_to:)
@@ -18,7 +18,7 @@ module Kitchen::Directions::BakeChapterAnswerKey
18
18
  def bake_section(chapter:, append_to:, klass:)
19
19
  section_solutions_set = []
20
20
  chapter.search(".#{klass}").each do |section|
21
- section.search('[data-type="solution"]').each do |solution|
21
+ section.search('div[data-type="solution"]').each do |solution|
22
22
  section_solutions_set.push(solution.cut)
23
23
  end
24
24
  end
@@ -31,9 +31,11 @@ module Kitchen::Directions::BakeChapterAnswerKey
31
31
 
32
32
  def bake_from_notes(chapter:, append_to:, klass:)
33
33
  solutions = []
34
- chapter.notes(".#{klass}").each do |note|
35
- solution = note.exercises.first.solution
36
- solutions.push(solution.cut) if solution
34
+ chapter.notes("$.#{klass}").each do |note|
35
+ note.exercises.each do |exercise|
36
+ solution = exercise.solution
37
+ solutions.push(solution.cut) if solution
38
+ end
37
39
  end
38
40
  return if solutions.empty?
39
41
 
@@ -1,21 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Kitchen::Directions::BakeChapterAnswerKey
3
+ module Kitchen::Directions::MoveSolutionsToAnswerKey
4
4
  class V1
5
- def bake(chapter:, metadata_source:, strategy:, append_to:)
5
+ def bake(chapter:, metadata_source:, strategy:, append_to:, strategy_options: {}, solutions_plural: true)
6
6
  strategy =
7
7
  case strategy
8
8
  when :calculus
9
- Strategies::Calculus
9
+ Strategies::Calculus.new
10
10
  when :uphysics
11
- Strategies::UPhysics
11
+ Strategies::UPhysics.new
12
+ when :precalculus
13
+ Strategies::Precalculus.new
14
+ when :default
15
+ Strategies::Default.new(strategy_options)
12
16
  else
13
17
  raise 'No such strategy'
14
18
  end
15
19
 
20
+ solutions_or_solution = solutions_plural ? 'solutions' : 'solution'
16
21
  append_to.append(child:
17
22
  <<~HTML
18
- <div class="os-eob os-solutions-container" data-type="composite-page" data-uuid-key=".solutions#{chapter.count_in(:book)}">
23
+ <div class="os-eob os-#{solutions_or_solution}-container" data-type="composite-page" \
24
+ data-uuid-key=".#{solutions_or_solution}#{chapter.count_in(:book)}">
19
25
  <h2 data-type="document-title">
20
26
  <span class="os-text">#{I18n.t(:chapter)} #{chapter.count_in(:book)}</span>
21
27
  </h2>
@@ -26,7 +32,7 @@ module Kitchen::Directions::BakeChapterAnswerKey
26
32
  </div>
27
33
  HTML
28
34
  )
29
- strategy.new.bake(chapter: chapter, append_to: append_to.last_element)
35
+ strategy.bake(chapter: chapter, append_to: append_to.last_element)
30
36
  end
31
37
  end
32
38
  end
@@ -13,6 +13,8 @@ module Kitchen
13
13
  attr_accessor :location
14
14
  # @return [Config] the configuration used in this document
15
15
  attr_reader :config
16
+ # @return [IdTracker] the counter for duplicate IDs
17
+ attr_reader :id_tracker
16
18
 
17
19
  # @!method selectors
18
20
  # The document's selectors
@@ -34,7 +36,10 @@ module Kitchen
34
36
  # @!method to_html
35
37
  # @see https://www.rubydoc.info/github/sparklemotion/nokogiri/Nokogiri/XML/Node#to_html-instance_method Nokogiri::XML::Node#to_html
36
38
  # @return [String] the document as an HTML string
37
- def_delegators :@nokogiri_document, :to_xhtml, :to_s, :to_xml, :to_html
39
+ # @!method encoding
40
+ # @see https://www.rubydoc.info/github/sparklemotion/nokogiri/Nokogiri/XML/Document#encoding-instance_method Nokogiri::XML::Document#encoding
41
+ # @return [String] the document as an HTML string
42
+ def_delegators :@nokogiri_document, :to_xhtml, :to_s, :to_xml, :to_html, :encoding
38
43
 
39
44
  # Return a new instance of Document
40
45
  #
@@ -44,8 +49,10 @@ module Kitchen
44
49
  @nokogiri_document = nokogiri_document
45
50
  @location = nil
46
51
  @config = config || Config.new
47
- @next_paste_count_for_id = {}
48
- @id_copy_suffix = '_copy_'
52
+ @id_tracker = IdTracker.new
53
+
54
+ # Nokogiri by default only recognizes the namespaces on the root node. Add all others.
55
+ raw&.add_all_namespaces! if @config.enable_all_namespaces
49
56
  end
50
57
 
51
58
  # Returns an enumerator that iterates over all children of this document
@@ -145,38 +152,6 @@ module Kitchen
145
152
  end
146
153
  end
147
154
 
148
- # Keeps track that an element with the given ID has been copied. When such
149
- # elements are pasted, this information is used to give those elements unique
150
- # IDs that don't duplicate the original element.
151
- #
152
- # @param id [String] the ID
153
- #
154
- def record_id_copied(id)
155
- return if id.blank?
156
-
157
- @next_paste_count_for_id[id] ||= 1
158
- end
159
-
160
- # Returns a unique ID given the ID of an element that was copied and is about
161
- # to be pasted
162
- #
163
- # @param original_id [String]
164
- #
165
- def modified_id_to_paste(original_id)
166
- return nil if original_id.nil?
167
- return '' if original_id.blank?
168
-
169
- count = next_count_for_pasted_id(original_id)
170
-
171
- # A count of 0 means the element was cut and this is the first paste, do not
172
- # modify the ID; otherwise, use the uniquified ID.
173
- if count.zero?
174
- original_id
175
- else
176
- "#{original_id}#{@id_copy_suffix}#{count}"
177
- end
178
- end
179
-
180
155
  # Returns the underlying Nokogiri Document object
181
156
  #
182
157
  # @return [Nokogiri::XML::Document]
@@ -185,16 +160,19 @@ module Kitchen
185
160
  @nokogiri_document
186
161
  end
187
162
 
188
- protected
189
-
190
- def next_count_for_pasted_id(id)
191
- return if id.blank?
192
-
193
- (@next_paste_count_for_id[id] ||= 0).tap do
194
- @next_paste_count_for_id[id] += 1
163
+ # Returns the locale for this document, default to `:en` if no locale detected
164
+ #
165
+ # @return [Symbol]
166
+ #
167
+ def locale
168
+ raw.root['lang']&.to_sym || begin
169
+ warn 'No `lang` attribute on this document so cannot detect its locale; defaulting to `:en`'
170
+ :en
195
171
  end
196
172
  end
197
173
 
174
+ protected
175
+
198
176
  attr_reader :nokogiri_document
199
177
 
200
178
  end
@@ -19,8 +19,14 @@ module Kitchen
19
19
  short_type: short_type)
20
20
  end
21
21
 
22
- # # @!method pages
23
- # # Returns a pages enumerator
24
- # def_delegators :as_enumerator, :pages, :chapters, :terms, :figures, :notes, :tables, :examples
22
+ # Returns true if this class represents the element for the given node; always false
23
+ # for this generic class
24
+ #
25
+ # @param node [Nokogiri::XML::Node] the underlying node
26
+ # @return [Boolean]
27
+ #
28
+ def self.is_the_element_class_for?(_node, **)
29
+ false
30
+ end
25
31
  end
26
32
  end
@@ -3,6 +3,7 @@
3
3
  require 'forwardable'
4
4
  require 'securerandom'
5
5
 
6
+ # rubocop:disable Metrics/ClassLength
6
7
  module Kitchen
7
8
  # Abstract base class for all elements. If you are looking for a simple concrete
8
9
  # element class, use `Element`.
@@ -92,6 +93,16 @@ module Kitchen
92
93
  # @return [Selectors::Base]
93
94
  def_delegators :config, :selectors
94
95
 
96
+ # @!method pantry
97
+ # Access the pantry for this element's document
98
+ # @return [Pantry]
99
+ # @!method :clipboard
100
+ # Access the clipboard for this element's document
101
+ # @return [Clipboard]
102
+ def_delegators :document, :pantry, :clipboard
103
+
104
+ def_delegators :document, :id_tracker
105
+
95
106
  # Creates a new instance
96
107
  #
97
108
  # @param node [Nokogiri::XML::Node] the wrapped element
@@ -108,7 +119,9 @@ module Kitchen
108
119
 
109
120
  @enumerator_class = enumerator_class
110
121
 
111
- @short_type = short_type || "unknown_type_#{SecureRandom.hex(4)}"
122
+ @short_type = short_type ||
123
+ self.class.try(:short_type) ||
124
+ "unknown_type_#{SecureRandom.hex(4)}"
112
125
 
113
126
  @document =
114
127
  case document
@@ -121,16 +134,53 @@ module Kitchen
121
134
  @ancestors = HashWithIndifferentAccess.new
122
135
  @search_query_matches_that_have_been_counted = {}
123
136
  @is_a_clone = false
137
+ @search_cache = {}
138
+ end
139
+
140
+ # Returns ElementBase descendent type or nil if none found
141
+ #
142
+ # @param type [Symbol] the descendant type, e.g. `:page`
143
+ # @return [Class] the child class for the given type
144
+ #
145
+ def self.descendant(type)
146
+ @types_to_descendants ||=
147
+ descendants.each_with_object({}) do |descendant, hash|
148
+ next unless descendant.try(:short_type)
149
+
150
+ hash[descendant.short_type] = descendant
151
+ end
152
+
153
+ @types_to_descendants[type]
154
+ end
155
+
156
+ # Returns ElementBase descendent type or Error if none found
157
+ #
158
+ # @param type [Symbol] the descendant type, e.g. `:page`
159
+ # @raise if the type is unknown
160
+ # @return [Class] the child class for the given type
161
+ #
162
+ def self.descendant!(type)
163
+ descendant(type) || raise("Unknown ElementBase descendant type '#{type}'")
164
+ end
165
+
166
+ # Returns true if this element is the given type
167
+ #
168
+ # @param type [Symbol] the descendant type, e.g. `:page`
169
+ # @raise if the type is unknown
170
+ # @return [Boolean]
171
+ #
172
+ def is?(type)
173
+ ElementBase.descendant!(type).is_the_element_class_for?(raw, config: config)
124
174
  end
125
175
 
126
176
  # Returns true if this class represents the element for the given node
127
177
  #
128
178
  # @param node [Nokogiri::XML::Node] the underlying node
179
+ # @param config [Kitchen::Config]
129
180
  # @return [Boolean]
130
181
  #
131
- def self.is_the_element_class_for?(_node)
132
- # override this in subclasses
133
- false
182
+ def self.is_the_element_class_for?(node, config:)
183
+ Selector.named(short_type).matches?(node, config: config)
134
184
  end
135
185
 
136
186
  # Returns true if this element has the given class
@@ -298,7 +348,7 @@ module Kitchen
298
348
  # search results if the method or callable returns false
299
349
  # @return [ElementEnumerator]
300
350
  #
301
- def search(*selector_or_xpath_args, only: nil, except: nil)
351
+ def search(*selector_or_xpath_args, only: nil, except: nil, reload: false)
302
352
  block_error_if(block_given?)
303
353
 
304
354
  ElementEnumerator.factory.build_within(
@@ -307,19 +357,29 @@ module Kitchen
307
357
  css_or_xpath: selector_or_xpath_args,
308
358
  only: only,
309
359
  except: except
310
- )
360
+ ),
361
+ reload: reload
311
362
  )
312
363
  end
313
364
 
365
+ def raw_search(*selector_or_xpath_args, reload: false)
366
+ key = selector_or_xpath_args
367
+ @search_cache[key] = nil if reload || !config.enable_search_cache
368
+ # cache nil search results with a fake -1 value
369
+ @search_cache[key] ||= raw.search(*selector_or_xpath_args) || -1
370
+ @search_cache[key] == -1 ? nil : @search_cache[key]
371
+ end
372
+
314
373
  # Yields and returns the first child element that matches the provided
315
374
  # selector or XPath arguments.
316
375
  #
317
376
  # @param selector_or_xpath_args [Array<String>] CSS selectors or XPath arguments
377
+ # @param reload [Boolean] ignores cache if true
318
378
  # @yieldparam [Element] the matched XML element
319
379
  # @return [Element, nil] the matched XML element or nil if no match found
320
380
  #
321
- def first(*selector_or_xpath_args)
322
- search(*selector_or_xpath_args).first.tap do |element|
381
+ def first(*selector_or_xpath_args, reload: false)
382
+ search(*selector_or_xpath_args, reload: reload).first.tap do |element|
323
383
  yield(element) if block_given?
324
384
  end
325
385
  end
@@ -328,12 +388,13 @@ module Kitchen
328
388
  # selector or XPath arguments.
329
389
  #
330
390
  # @param selector_or_xpath_args [Array<String>] CSS selectors or XPath arguments
391
+ # @param reload [Boolean] ignores cache if true
331
392
  # @yieldparam [Element] the matched XML element
332
393
  # @raise [ElementNotFoundError] if no matching element is found
333
394
  # @return [Element] the matched XML element
334
395
  #
335
- def first!(*selector_or_xpath_args)
336
- search(*selector_or_xpath_args).first!.tap do |element|
396
+ def first!(*selector_or_xpath_args, reload: false)
397
+ search(*selector_or_xpath_args, reload: reload).first!.tap do |element|
337
398
  yield(element) if block_given?
338
399
  end
339
400
  end
@@ -382,8 +443,13 @@ module Kitchen
382
443
  def cut(to: nil)
383
444
  block_error_if(block_given?)
384
445
 
446
+ raw.traverse do |node|
447
+ next if node.text? || node.document?
448
+
449
+ id_tracker.record_id_cut(node[:id])
450
+ end
385
451
  node.remove
386
- clipboard(to).add(self) if to.present?
452
+ get_clipboard(to).add(self) if to.present?
387
453
  self
388
454
  end
389
455
 
@@ -402,9 +468,9 @@ module Kitchen
402
468
  the_copy.raw.traverse do |node|
403
469
  next if node.text? || node.document?
404
470
 
405
- document.record_id_copied(node[:id])
471
+ id_tracker.record_id_copied(node[:id])
406
472
  end
407
- clipboard(to).add(the_copy) if to.present?
473
+ get_clipboard(to).add(the_copy) if to.present?
408
474
  the_copy
409
475
  end
410
476
 
@@ -413,20 +479,22 @@ module Kitchen
413
479
  def paste
414
480
  # See `clone` method for a note about namespaces
415
481
  block_error_if(block_given?)
416
-
417
482
  temp_copy = clone
418
483
  temp_copy.raw.traverse do |node|
419
484
  next if node.text? || node.document?
420
485
 
421
- node[:id] = document.modified_id_to_paste(node[:id]) unless node[:id].blank?
486
+ if node[:id].present?
487
+ id_tracker.record_id_pasted(node[:id])
488
+ node[:id] = id_tracker.modified_id_to_paste(node[:id])
489
+ end
422
490
  end
423
491
  temp_copy.to_s
424
492
  end
425
493
 
426
494
  # Copy the element's id
427
495
  def copied_id
428
- document.record_id_copied(id)
429
- document.modified_id_to_paste(id)
496
+ id_tracker.record_id_copied(id)
497
+ id_tracker.modified_id_to_paste(id)
430
498
  end
431
499
 
432
500
  # Delete the element
@@ -440,6 +508,21 @@ module Kitchen
440
508
  Element.new(node: raw.parent, document: document, short_type: "parent(#{short_type})")
441
509
  end
442
510
 
511
+ # returns previous element
512
+ # skips double indentations that the nokigiri sometimes picks up
513
+ # nil if there's no previous sibling
514
+ #
515
+ def previous
516
+ prev = raw.previous
517
+ return prev if prev.nil?
518
+
519
+ Element.new(
520
+ node: prev,
521
+ document: document,
522
+ short_type: "previous(#{short_type})"
523
+ )
524
+ end
525
+
443
526
  # TODO: make it clear if all of these methods take Element, Node, or String
444
527
 
445
528
  # If child argument given, prepends it before the element's current children.
@@ -646,7 +729,8 @@ module Kitchen
646
729
  # @!method pages
647
730
  # Returns a pages enumerator
648
731
  def_delegators :as_enumerator, :pages, :chapters, :terms, :figures, :notes, :tables, :examples,
649
- :metadatas, :non_introduction_pages, :units, :titles, :exercises, :composite_pages
732
+ :metadatas, :non_introduction_pages, :units, :titles, :exercises, :references,
733
+ :composite_pages, :composite_chapters
650
734
 
651
735
  # Returns this element as an enumerator (over only one element, itself)
652
736
  #
@@ -671,10 +755,10 @@ module Kitchen
671
755
  # @param name_or_object [String, Clipboard] the name of the clipboard or the clipboard itself
672
756
  # @return [Clipboard]
673
757
  #
674
- def clipboard(name_or_object)
758
+ def get_clipboard(name_or_object)
675
759
  case name_or_object
676
760
  when Symbol
677
- document.clipboard(name: name_or_object)
761
+ clipboard(name: name_or_object)
678
762
  when Clipboard
679
763
  name_or_object
680
764
  else
@@ -688,7 +772,9 @@ module Kitchen
688
772
  # @param string [String] the string to clean
689
773
  def remove_default_namespaces_if_clone(string)
690
774
  if is_a_clone
691
- string.gsub('xmlns:default="http://www.w3.org/1999/xhtml"', '').gsub('default:', '')
775
+ string.gsub('xmlns:default="http://www.w3.org/1999/xhtml"', '')
776
+ .gsub('xmlns="http://www.w3.org/1999/xhtml"', '')
777
+ .gsub('default:', '')
692
778
  else
693
779
  string
694
780
  end
@@ -701,3 +787,4 @@ module Kitchen
701
787
 
702
788
  end
703
789
  end
790
+ # rubocop:enable Metrics/ClassLength