openstax_kitchen 4.0.0 → 6.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/changelog.yml +24 -0
- data/.github/workflows/rubocop.yml +28 -0
- data/CHANGELOG.md +58 -0
- data/Gemfile.lock +15 -6
- data/README.md +16 -0
- data/codecov.yaml +1 -0
- data/docker/rubocop +22 -0
- data/lib/kitchen/book_document.rb +1 -1
- data/lib/kitchen/chapter_element.rb +2 -2
- data/lib/kitchen/composite_chapter_element_enumerator.rb +21 -0
- data/lib/kitchen/composite_page_element.rb +19 -2
- data/lib/kitchen/config.rb +7 -0
- data/lib/kitchen/directions/bake_appendix.rb +3 -1
- data/lib/kitchen/directions/bake_chapter_introductions.rb +22 -15
- data/lib/kitchen/directions/bake_chapter_introductions/chapter_introduction.xhtml.erb +0 -0
- data/lib/kitchen/directions/bake_chapter_key_concepts/v1.rb +1 -1
- data/lib/kitchen/directions/bake_chapter_references/main.rb +15 -0
- data/lib/kitchen/directions/bake_chapter_references/v1.rb +49 -0
- data/lib/kitchen/directions/bake_chapter_section_exercises/main.rb +2 -2
- data/lib/kitchen/directions/bake_chapter_section_exercises/v1.rb +2 -1
- data/lib/kitchen/directions/bake_chapter_solutions/main.rb +11 -0
- data/lib/kitchen/directions/bake_chapter_solutions/v1.rb +37 -0
- data/lib/kitchen/directions/bake_chapter_summary.rb +13 -6
- data/lib/kitchen/directions/bake_composite_chapters.rb +1 -1
- data/lib/kitchen/directions/bake_composite_pages.rb +1 -1
- data/lib/kitchen/directions/bake_equations.rb +1 -1
- data/lib/kitchen/directions/bake_example.rb +4 -1
- data/lib/kitchen/directions/bake_figure.rb +13 -0
- data/lib/kitchen/directions/bake_first_elements.rb +7 -1
- data/lib/kitchen/directions/bake_footnotes/main.rb +2 -2
- data/lib/kitchen/directions/bake_footnotes/v1.rb +11 -8
- data/lib/kitchen/directions/bake_further_research.rb +2 -0
- data/lib/kitchen/directions/bake_index/v1.rb +3 -14
- data/lib/kitchen/directions/bake_inline_lists.rb +22 -0
- data/lib/kitchen/directions/bake_notes/bake_numbered_notes/main.rb +43 -0
- data/lib/kitchen/directions/bake_notes/bake_numbered_notes/v1.rb +37 -0
- data/lib/kitchen/directions/bake_notes/bake_numbered_notes/v2.rb +25 -0
- data/lib/kitchen/directions/bake_notes/bake_numbered_notes/v3.rb +32 -0
- data/lib/kitchen/directions/bake_numbered_exercise/main.rb +3 -2
- data/lib/kitchen/directions/bake_numbered_exercise/v1.rb +10 -1
- data/lib/kitchen/directions/bake_numbered_table/bake_table_body.rb +29 -0
- data/lib/kitchen/directions/bake_numbered_table/main.rb +4 -0
- data/lib/kitchen/directions/bake_numbered_table/v1.rb +1 -24
- data/lib/kitchen/directions/bake_numbered_table/v2.rb +31 -0
- data/lib/kitchen/directions/bake_preface/main.rb +2 -2
- data/lib/kitchen/directions/bake_preface/v1.rb +3 -2
- data/lib/kitchen/directions/bake_references/main.rb +7 -0
- data/lib/kitchen/directions/bake_references/v2.rb +35 -0
- data/lib/kitchen/directions/bake_toc.rb +3 -1
- data/lib/kitchen/directions/book_answer_key_container/eob_answer_key_outer_container.xhtml.erb +9 -0
- data/lib/kitchen/directions/book_answer_key_container/main.rb +2 -2
- data/lib/kitchen/directions/book_answer_key_container/v1.rb +4 -3
- data/lib/kitchen/directions/chapter_review_container/chapter_review.xhtml.erb +3 -3
- data/lib/kitchen/directions/chapter_review_container/main.rb +2 -2
- data/lib/kitchen/directions/chapter_review_container/v1.rb +4 -2
- data/lib/kitchen/directions/eoc_section_title_link_snippet.rb +13 -0
- data/lib/kitchen/directions/move_exercises_to_eoc/main.rb +10 -0
- data/lib/kitchen/directions/move_exercises_to_eoc/v3.rb +49 -0
- data/lib/kitchen/directions/move_solutions_to_answer_key/main.rb +6 -2
- data/lib/kitchen/directions/move_solutions_to_answer_key/strategies/default.rb +27 -0
- data/lib/kitchen/directions/move_solutions_to_answer_key/strategies/precalculus.rb +84 -0
- data/lib/kitchen/directions/move_solutions_to_answer_key/strategies/uphysics.rb +2 -2
- data/lib/kitchen/directions/move_solutions_to_answer_key/v1.rb +11 -7
- data/lib/kitchen/document.rb +19 -52
- data/lib/kitchen/element_base.rb +48 -12
- data/lib/kitchen/element_enumerator_base.rb +24 -1
- data/lib/kitchen/element_enumerator_factory.rb +19 -7
- data/lib/kitchen/exercise_element.rb +2 -2
- data/lib/kitchen/id_tracker.rb +68 -0
- data/lib/kitchen/oven.rb +5 -1
- data/lib/kitchen/page_element.rb +7 -5
- data/lib/kitchen/patches/i18n.rb +34 -0
- data/lib/kitchen/patches/integer.rb +24 -0
- data/lib/kitchen/patches/nokogiri.rb +62 -0
- data/lib/kitchen/patches/nokogiri_profiling.rb +60 -0
- data/lib/kitchen/search_query.rb +6 -0
- data/lib/kitchen/selector.rb +3 -2
- data/lib/kitchen/version.rb +1 -1
- data/lib/locales/en.yml +2 -1
- data/lib/locales/es.yml +33 -0
- data/lib/locales/pl.yml +4 -2
- data/lib/openstax_kitchen.rb +1 -5
- data/openstax_kitchen.gemspec +1 -0
- metadata +42 -7
- data/.github/config.yml +0 -14
- data/lib/kitchen/directions/bake_notes/bake_numbered_notes.rb +0 -51
- data/lib/kitchen/directions/book_answer_key_container/eob_solutions_container.xhtml.erb +0 -9
- data/lib/kitchen/directions/move_solutions_to_answer_key/strategies/american_government.rb +0 -19
- data/lib/kitchen/transliterations.rb +0 -21
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kitchen::Directions::MoveExercisesToEOC
|
4
|
+
# The difference from v1 is the presence of a section title
|
5
|
+
# and from v2 the lack of additional "os-section-area" and os-#{@klass} wrappers
|
6
|
+
class V3
|
7
|
+
renderable
|
8
|
+
|
9
|
+
def bake(chapter:, metadata_source:, klass:, append_to: nil, uuid_prefix: '.')
|
10
|
+
@klass = klass
|
11
|
+
@metadata = metadata_source.children_to_keep.copy
|
12
|
+
@title = I18n.t(:"eoc.#{klass}")
|
13
|
+
@uuid_prefix = uuid_prefix
|
14
|
+
|
15
|
+
exercise_clipboard = Kitchen::Clipboard.new
|
16
|
+
|
17
|
+
chapter.non_introduction_pages.each do |page|
|
18
|
+
sections = page.search("section.#{@klass}")
|
19
|
+
|
20
|
+
sections.each do |exercise_section|
|
21
|
+
exercise_section.first("[data-type='title']")&.trash
|
22
|
+
|
23
|
+
section_title = Kitchen::Directions::EocSectionTitleLinkSnippet.v1(page: page)
|
24
|
+
|
25
|
+
exercise_section.exercises.each do |exercise|
|
26
|
+
exercise.document.pantry(name: :link_text).store(
|
27
|
+
"#{I18n.t(:exercise_label)} #{chapter.count_in(:book)}.#{exercise.count_in(:chapter)}",
|
28
|
+
label: exercise.id
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Configure section title
|
33
|
+
exercise_section.prepend(child: section_title)
|
34
|
+
exercise_section.cut(to: exercise_clipboard)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
return if exercise_clipboard.none?
|
39
|
+
|
40
|
+
@content = exercise_clipboard.paste
|
41
|
+
|
42
|
+
append_to_element = append_to || chapter
|
43
|
+
@in_composite_chapter = append_to_element.is?(:composite_chapter)
|
44
|
+
|
45
|
+
append_to_element.append(child: render(file:
|
46
|
+
'../../templates/eoc_section_title_template.xhtml.erb'))
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -3,11 +3,15 @@
|
|
3
3
|
module Kitchen
|
4
4
|
module Directions
|
5
5
|
module MoveSolutionsToAnswerKey
|
6
|
-
def self.v1(chapter:, metadata_source:, strategy:, append_to:)
|
6
|
+
def self.v1(chapter:, metadata_source:, strategy:, append_to:, strategy_options: {}, solutions_plural: true)
|
7
7
|
V1.new.bake(
|
8
8
|
chapter: chapter,
|
9
9
|
metadata_source: metadata_source,
|
10
|
-
strategy: strategy,
|
10
|
+
strategy: strategy,
|
11
|
+
append_to: append_to,
|
12
|
+
strategy_options: strategy_options,
|
13
|
+
solutions_plural: solutions_plural
|
14
|
+
)
|
11
15
|
end
|
12
16
|
end
|
13
17
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kitchen::Directions::MoveSolutionsToAnswerKey
|
4
|
+
module Strategies
|
5
|
+
class Default
|
6
|
+
def bake(chapter:, append_to:)
|
7
|
+
bake_section(chapter: chapter, append_to: append_to)
|
8
|
+
end
|
9
|
+
|
10
|
+
protected
|
11
|
+
|
12
|
+
def bake_section(chapter:, append_to:)
|
13
|
+
@classes.each do |klass|
|
14
|
+
chapter.search(".#{klass} [data-type='solution']").each do |solution|
|
15
|
+
append_to.add_child(solution.cut.to_s)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# This method helps to obtain more strategy-specific params through
|
21
|
+
# "strategy_options: {blah1: 1, blah2: 2}"
|
22
|
+
def initialize(strategy_options)
|
23
|
+
@classes = strategy_options[:classes] || (raise 'missing classes for strategy')
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kitchen::Directions::MoveSolutionsToAnswerKey
|
4
|
+
module Strategies
|
5
|
+
class Precalculus
|
6
|
+
def bake(chapter:, append_to:)
|
7
|
+
try_note_solutions(chapter: chapter, append_to: append_to)
|
8
|
+
|
9
|
+
# Bake section exercises
|
10
|
+
chapter.non_introduction_pages.each do |page|
|
11
|
+
number = "#{chapter.count_in(:book)}.#{page.count_in(:chapter)}"
|
12
|
+
bake_section(chapter: page, append_to: append_to, klass: 'section-exercises',
|
13
|
+
number: number)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Bake other types of exercises
|
17
|
+
classes = %w[review-exercises practice-test]
|
18
|
+
classes.each do |klass|
|
19
|
+
bake_section(chapter: chapter, append_to: append_to, klass: klass)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
|
25
|
+
def bake_section(chapter:, append_to:, klass:, number: nil)
|
26
|
+
section_solutions_set = Kitchen::Clipboard.new
|
27
|
+
chapter.search(".#{klass}").each do |section|
|
28
|
+
section.search('[data-type="solution"]').each do |solution|
|
29
|
+
solution.cut(to: section_solutions_set)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
return if section_solutions_set.items.empty?
|
34
|
+
|
35
|
+
title = <<~HTML
|
36
|
+
<h3 data-type="title">
|
37
|
+
<span class="os-title-label">#{I18n.t(:"eoc.#{klass}", number: number)}</span>
|
38
|
+
</h3>
|
39
|
+
HTML
|
40
|
+
|
41
|
+
append_solution_area(title: title, solutions: section_solutions_set, append_to: append_to)
|
42
|
+
end
|
43
|
+
|
44
|
+
def try_note_solutions(chapter:, append_to:)
|
45
|
+
append_to.add_child(
|
46
|
+
<<~HTML
|
47
|
+
<div class="os-module-reset-solution-area os-try-solution-area">
|
48
|
+
<h3 data-type="title">
|
49
|
+
<span class="os-title-label">#{I18n.t(:"notes.try")}</span>
|
50
|
+
</h3>
|
51
|
+
</div>
|
52
|
+
HTML
|
53
|
+
)
|
54
|
+
chapter.non_introduction_pages.each do |page|
|
55
|
+
solutions = Kitchen::Clipboard.new
|
56
|
+
page.notes('$.try').each do |note|
|
57
|
+
note.exercises.each do |exercise|
|
58
|
+
solution = exercise.solution
|
59
|
+
solution&.cut(to: solutions) #if solution
|
60
|
+
end
|
61
|
+
end
|
62
|
+
next if solutions.items.empty?
|
63
|
+
|
64
|
+
title_snippet = Kitchen::Directions::EocSectionTitleLinkSnippet.v2(page: page)
|
65
|
+
|
66
|
+
append_solution_area(title: title_snippet, solutions: solutions,
|
67
|
+
append_to: append_to.search('.os-try-solution-area').first)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def append_solution_area(title:, solutions:, append_to:)
|
72
|
+
append_to = append_to.add_child(
|
73
|
+
<<~HTML
|
74
|
+
<div class="os-solution-area">
|
75
|
+
#{title}
|
76
|
+
</div>
|
77
|
+
HTML
|
78
|
+
).first
|
79
|
+
|
80
|
+
append_to.add_child(solutions.paste)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -18,7 +18,7 @@ module Kitchen::Directions::MoveSolutionsToAnswerKey
|
|
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,7 +31,7 @@ module Kitchen::Directions::MoveSolutionsToAnswerKey
|
|
31
31
|
|
32
32
|
def bake_from_notes(chapter:, append_to:, klass:)
|
33
33
|
solutions = []
|
34
|
-
chapter.notes("
|
34
|
+
chapter.notes("$.#{klass}").each do |note|
|
35
35
|
note.exercises.each do |exercise|
|
36
36
|
solution = exercise.solution
|
37
37
|
solutions.push(solution.cut) if solution
|
@@ -2,22 +2,26 @@
|
|
2
2
|
|
3
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
|
12
|
-
when :
|
13
|
-
Strategies::
|
11
|
+
Strategies::UPhysics.new
|
12
|
+
when :precalculus
|
13
|
+
Strategies::Precalculus.new
|
14
|
+
when :default
|
15
|
+
Strategies::Default.new(strategy_options)
|
14
16
|
else
|
15
17
|
raise 'No such strategy'
|
16
18
|
end
|
17
19
|
|
20
|
+
solutions_or_solution = solutions_plural ? 'solutions' : 'solution'
|
18
21
|
append_to.append(child:
|
19
22
|
<<~HTML
|
20
|
-
<div class="os-eob os-
|
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)}">
|
21
25
|
<h2 data-type="document-title">
|
22
26
|
<span class="os-text">#{I18n.t(:chapter)} #{chapter.count_in(:book)}</span>
|
23
27
|
</h2>
|
@@ -28,7 +32,7 @@ module Kitchen::Directions::MoveSolutionsToAnswerKey
|
|
28
32
|
</div>
|
29
33
|
HTML
|
30
34
|
)
|
31
|
-
strategy.
|
35
|
+
strategy.bake(chapter: chapter, append_to: append_to.last_element)
|
32
36
|
end
|
33
37
|
end
|
34
38
|
end
|
data/lib/kitchen/document.rb
CHANGED
@@ -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
|
-
|
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,19 +49,10 @@ module Kitchen
|
|
44
49
|
@nokogiri_document = nokogiri_document
|
45
50
|
@location = nil
|
46
51
|
@config = config || Config.new
|
47
|
-
@
|
48
|
-
@id_copy_suffix = '_copy_'
|
49
|
-
|
50
|
-
# Nokogiri by default only recognizes the namespaces on the root node. Collect all
|
51
|
-
# namespaces and add them manually.
|
52
|
-
return unless @config.enable_all_namespaces && raw.present?
|
53
|
-
|
54
|
-
raw.collect_namespaces.each do |namespace, url|
|
55
|
-
prefix, name = namespace.split(':')
|
56
|
-
next unless prefix == 'xmlns' && name.present?
|
52
|
+
@id_tracker = IdTracker.new
|
57
53
|
|
58
|
-
|
59
|
-
|
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
|
60
56
|
end
|
61
57
|
|
62
58
|
# Returns an enumerator that iterates over all children of this document
|
@@ -156,38 +152,6 @@ module Kitchen
|
|
156
152
|
end
|
157
153
|
end
|
158
154
|
|
159
|
-
# Keeps track that an element with the given ID has been copied. When such
|
160
|
-
# elements are pasted, this information is used to give those elements unique
|
161
|
-
# IDs that don't duplicate the original element.
|
162
|
-
#
|
163
|
-
# @param id [String] the ID
|
164
|
-
#
|
165
|
-
def record_id_copied(id)
|
166
|
-
return if id.blank?
|
167
|
-
|
168
|
-
@next_paste_count_for_id[id] ||= 1
|
169
|
-
end
|
170
|
-
|
171
|
-
# Returns a unique ID given the ID of an element that was copied and is about
|
172
|
-
# to be pasted
|
173
|
-
#
|
174
|
-
# @param original_id [String]
|
175
|
-
#
|
176
|
-
def modified_id_to_paste(original_id)
|
177
|
-
return nil if original_id.nil?
|
178
|
-
return '' if original_id.blank?
|
179
|
-
|
180
|
-
count = next_count_for_pasted_id(original_id)
|
181
|
-
|
182
|
-
# A count of 0 means the element was cut and this is the first paste, do not
|
183
|
-
# modify the ID; otherwise, use the uniquified ID.
|
184
|
-
if count.zero?
|
185
|
-
original_id
|
186
|
-
else
|
187
|
-
"#{original_id}#{@id_copy_suffix}#{count}"
|
188
|
-
end
|
189
|
-
end
|
190
|
-
|
191
155
|
# Returns the underlying Nokogiri Document object
|
192
156
|
#
|
193
157
|
# @return [Nokogiri::XML::Document]
|
@@ -196,16 +160,19 @@ module Kitchen
|
|
196
160
|
@nokogiri_document
|
197
161
|
end
|
198
162
|
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
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
|
206
171
|
end
|
207
172
|
end
|
208
173
|
|
174
|
+
protected
|
175
|
+
|
209
176
|
attr_reader :nokogiri_document
|
210
177
|
|
211
178
|
end
|
data/lib/kitchen/element_base.rb
CHANGED
@@ -101,6 +101,8 @@ module Kitchen
|
|
101
101
|
# @return [Clipboard]
|
102
102
|
def_delegators :document, :pantry, :clipboard
|
103
103
|
|
104
|
+
def_delegators :document, :id_tracker
|
105
|
+
|
104
106
|
# Creates a new instance
|
105
107
|
#
|
106
108
|
# @param node [Nokogiri::XML::Node] the wrapped element
|
@@ -132,6 +134,7 @@ module Kitchen
|
|
132
134
|
@ancestors = HashWithIndifferentAccess.new
|
133
135
|
@search_query_matches_that_have_been_counted = {}
|
134
136
|
@is_a_clone = false
|
137
|
+
@search_cache = {}
|
135
138
|
end
|
136
139
|
|
137
140
|
# Returns ElementBase descendent type or nil if none found
|
@@ -345,7 +348,7 @@ module Kitchen
|
|
345
348
|
# search results if the method or callable returns false
|
346
349
|
# @return [ElementEnumerator]
|
347
350
|
#
|
348
|
-
def search(*selector_or_xpath_args, only: nil, except: nil)
|
351
|
+
def search(*selector_or_xpath_args, only: nil, except: nil, reload: false)
|
349
352
|
block_error_if(block_given?)
|
350
353
|
|
351
354
|
ElementEnumerator.factory.build_within(
|
@@ -354,19 +357,29 @@ module Kitchen
|
|
354
357
|
css_or_xpath: selector_or_xpath_args,
|
355
358
|
only: only,
|
356
359
|
except: except
|
357
|
-
)
|
360
|
+
),
|
361
|
+
reload: reload
|
358
362
|
)
|
359
363
|
end
|
360
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
|
+
|
361
373
|
# Yields and returns the first child element that matches the provided
|
362
374
|
# selector or XPath arguments.
|
363
375
|
#
|
364
376
|
# @param selector_or_xpath_args [Array<String>] CSS selectors or XPath arguments
|
377
|
+
# @param reload [Boolean] ignores cache if true
|
365
378
|
# @yieldparam [Element] the matched XML element
|
366
379
|
# @return [Element, nil] the matched XML element or nil if no match found
|
367
380
|
#
|
368
|
-
def first(*selector_or_xpath_args)
|
369
|
-
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|
|
370
383
|
yield(element) if block_given?
|
371
384
|
end
|
372
385
|
end
|
@@ -375,12 +388,13 @@ module Kitchen
|
|
375
388
|
# selector or XPath arguments.
|
376
389
|
#
|
377
390
|
# @param selector_or_xpath_args [Array<String>] CSS selectors or XPath arguments
|
391
|
+
# @param reload [Boolean] ignores cache if true
|
378
392
|
# @yieldparam [Element] the matched XML element
|
379
393
|
# @raise [ElementNotFoundError] if no matching element is found
|
380
394
|
# @return [Element] the matched XML element
|
381
395
|
#
|
382
|
-
def first!(*selector_or_xpath_args)
|
383
|
-
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|
|
384
398
|
yield(element) if block_given?
|
385
399
|
end
|
386
400
|
end
|
@@ -429,6 +443,11 @@ module Kitchen
|
|
429
443
|
def cut(to: nil)
|
430
444
|
block_error_if(block_given?)
|
431
445
|
|
446
|
+
raw.traverse do |node|
|
447
|
+
next if node.text? || node.document?
|
448
|
+
|
449
|
+
id_tracker.record_id_cut(node[:id])
|
450
|
+
end
|
432
451
|
node.remove
|
433
452
|
get_clipboard(to).add(self) if to.present?
|
434
453
|
self
|
@@ -449,7 +468,7 @@ module Kitchen
|
|
449
468
|
the_copy.raw.traverse do |node|
|
450
469
|
next if node.text? || node.document?
|
451
470
|
|
452
|
-
|
471
|
+
id_tracker.record_id_copied(node[:id])
|
453
472
|
end
|
454
473
|
get_clipboard(to).add(the_copy) if to.present?
|
455
474
|
the_copy
|
@@ -460,20 +479,22 @@ module Kitchen
|
|
460
479
|
def paste
|
461
480
|
# See `clone` method for a note about namespaces
|
462
481
|
block_error_if(block_given?)
|
463
|
-
|
464
482
|
temp_copy = clone
|
465
483
|
temp_copy.raw.traverse do |node|
|
466
484
|
next if node.text? || node.document?
|
467
485
|
|
468
|
-
|
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
|
469
490
|
end
|
470
491
|
temp_copy.to_s
|
471
492
|
end
|
472
493
|
|
473
494
|
# Copy the element's id
|
474
495
|
def copied_id
|
475
|
-
|
476
|
-
|
496
|
+
id_tracker.record_id_copied(id)
|
497
|
+
id_tracker.modified_id_to_paste(id)
|
477
498
|
end
|
478
499
|
|
479
500
|
# Delete the element
|
@@ -487,6 +508,21 @@ module Kitchen
|
|
487
508
|
Element.new(node: raw.parent, document: document, short_type: "parent(#{short_type})")
|
488
509
|
end
|
489
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
|
+
|
490
526
|
# TODO: make it clear if all of these methods take Element, Node, or String
|
491
527
|
|
492
528
|
# If child argument given, prepends it before the element's current children.
|
@@ -694,7 +730,7 @@ module Kitchen
|
|
694
730
|
# Returns a pages enumerator
|
695
731
|
def_delegators :as_enumerator, :pages, :chapters, :terms, :figures, :notes, :tables, :examples,
|
696
732
|
:metadatas, :non_introduction_pages, :units, :titles, :exercises, :references,
|
697
|
-
:composite_pages
|
733
|
+
:composite_pages, :composite_chapters
|
698
734
|
|
699
735
|
# Returns this element as an enumerator (over only one element, itself)
|
700
736
|
#
|