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.
- checksums.yaml +4 -4
- data/.github/workflows/changelog.yml +27 -0
- data/.github/workflows/rubocop.yml +28 -0
- data/.gitignore +1 -1
- data/CHANGELOG.md +94 -1
- data/Gemfile.lock +26 -18
- data/README.md +16 -0
- data/codecov.yaml +1 -0
- data/docker/ci +0 -1
- data/docker/rubocop +22 -0
- data/lib/kitchen/book_document.rb +1 -1
- data/lib/kitchen/book_element.rb +16 -2
- data/lib/kitchen/chapter_element.rb +10 -13
- data/lib/kitchen/chapter_element_enumerator.rb +1 -1
- data/lib/kitchen/composite_chapter_element.rb +7 -11
- data/lib/kitchen/composite_chapter_element_enumerator.rb +21 -0
- data/lib/kitchen/composite_page_element.rb +15 -10
- data/lib/kitchen/composite_page_element_enumerator.rb +1 -1
- data/lib/kitchen/config.rb +14 -0
- data/lib/kitchen/directions/bake_appendix.rb +3 -1
- data/lib/kitchen/directions/bake_chapter_glossary/main.rb +18 -0
- data/lib/kitchen/directions/bake_chapter_glossary/v1.rb +30 -0
- data/lib/kitchen/directions/bake_chapter_introductions.rb +23 -16
- data/lib/kitchen/directions/bake_chapter_introductions/chapter_introduction.xhtml.erb +0 -0
- data/lib/kitchen/directions/bake_chapter_key_concepts/main.rb +7 -2
- data/lib/kitchen/directions/bake_chapter_key_concepts/v1.rb +12 -7
- data/lib/kitchen/directions/bake_chapter_key_equations.rb +26 -21
- data/lib/kitchen/directions/bake_chapter_references/main.rb +16 -0
- data/lib/kitchen/directions/bake_chapter_references/v1.rb +35 -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 +56 -43
- 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 +2 -2
- data/lib/kitchen/directions/bake_example.rb +8 -1
- data/lib/kitchen/directions/bake_figure.rb +14 -1
- data/lib/kitchen/directions/bake_first_elements.rb +22 -0
- data/lib/kitchen/directions/bake_footnotes/main.rb +2 -2
- data/lib/kitchen/directions/bake_footnotes/v1.rb +13 -9
- data/lib/kitchen/directions/bake_free_response/free_response.xhtml.erb +10 -0
- data/lib/kitchen/directions/{bake_chapter_review → bake_free_response}/main.rb +3 -3
- data/lib/kitchen/directions/bake_free_response/v1.rb +29 -0
- data/lib/kitchen/directions/bake_further_research.rb +61 -0
- data/lib/kitchen/directions/bake_index/v1.rb +36 -26
- data/lib/kitchen/directions/bake_link_placeholders.rb +1 -1
- data/lib/kitchen/directions/bake_notes/bake_note_subtitle.rb +4 -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 +7 -2
- data/lib/kitchen/directions/bake_numbered_exercise/v1.rb +34 -12
- 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_page_abstracts.rb +1 -1
- 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 +16 -0
- data/lib/kitchen/directions/bake_references/v1.rb +48 -0
- data/lib/kitchen/directions/bake_suggested_reading.rb +5 -0
- data/lib/kitchen/directions/bake_toc.rb +4 -2
- 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 +11 -0
- data/lib/kitchen/directions/book_answer_key_container/v1.rb +14 -0
- data/lib/kitchen/directions/chapter_review_container/chapter_review.xhtml.erb +9 -0
- data/lib/kitchen/directions/chapter_review_container/main.rb +11 -0
- data/lib/kitchen/directions/chapter_review_container/v1.rb +15 -0
- data/lib/kitchen/directions/eoc_section_title_link_snippet.rb +14 -1
- data/lib/kitchen/directions/move_exercises_to_eoc/main.rb +37 -0
- data/lib/kitchen/directions/{bake_chapter_review_exercises → move_exercises_to_eoc}/v1.rb +8 -10
- data/lib/kitchen/directions/{bake_chapter_review_exercises → move_exercises_to_eoc}/v2.rb +8 -9
- data/lib/kitchen/directions/move_exercises_to_eoc/v3.rb +49 -0
- data/lib/kitchen/directions/{bake_chapter_answer_key → move_solutions_to_answer_key}/main.rb +7 -3
- data/lib/kitchen/directions/{bake_chapter_answer_key → move_solutions_to_answer_key}/strategies/calculus.rb +1 -1
- 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/{bake_chapter_answer_key → move_solutions_to_answer_key}/strategies/uphysics.rb +7 -5
- data/lib/kitchen/directions/{bake_chapter_answer_key → move_solutions_to_answer_key}/v1.rb +12 -6
- data/lib/kitchen/document.rb +20 -42
- data/lib/kitchen/element.rb +9 -3
- data/lib/kitchen/element_base.rb +108 -21
- data/lib/kitchen/element_enumerator_base.rb +33 -2
- data/lib/kitchen/element_enumerator_factory.rb +28 -12
- data/lib/kitchen/element_factory.rb +3 -3
- data/lib/kitchen/example_element.rb +8 -11
- data/lib/kitchen/example_element_enumerator.rb +1 -1
- data/lib/kitchen/exercise_element.rb +7 -10
- data/lib/kitchen/exercise_element_enumerator.rb +1 -1
- data/lib/kitchen/figure_element.rb +8 -11
- data/lib/kitchen/figure_element_enumerator.rb +1 -1
- data/lib/kitchen/id_tracker.rb +68 -0
- data/lib/kitchen/metadata_element.rb +8 -2
- data/lib/kitchen/metadata_element_enumerator.rb +1 -1
- data/lib/kitchen/note_element.rb +8 -11
- data/lib/kitchen/note_element_enumerator.rb +1 -1
- data/lib/kitchen/oven.rb +5 -1
- data/lib/kitchen/page_element.rb +27 -12
- data/lib/kitchen/page_element_enumerator.rb +1 -1
- 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/reference_element.rb +27 -0
- data/lib/kitchen/references_element_enumerator.rb +20 -0
- data/lib/kitchen/search_query.rb +31 -3
- data/lib/kitchen/selector.rb +25 -0
- data/lib/kitchen/selectors/base.rb +39 -0
- data/lib/kitchen/selectors/standard_1.rb +13 -0
- data/lib/kitchen/table_element.rb +8 -11
- data/lib/kitchen/table_element_enumerator.rb +1 -1
- data/lib/kitchen/templates/eob_section_title_template.xhtml.erb +10 -0
- data/lib/kitchen/templates/eoc_section_title_template.xhtml.erb +10 -0
- data/lib/kitchen/term_element.rb +5 -8
- data/lib/kitchen/term_element_enumerator.rb +1 -1
- data/lib/kitchen/unit_element.rb +13 -7
- data/lib/kitchen/unit_element_enumerator.rb +1 -1
- data/lib/kitchen/version.rb +1 -1
- data/lib/locales/en.yml +5 -1
- data/lib/locales/es.yml +33 -0
- data/lib/locales/pl.yml +3 -2
- data/lib/openstax_kitchen.rb +2 -5
- data/openstax_kitchen.gemspec +1 -0
- metadata +66 -25
- data/.github/config.yml +0 -14
- data/lib/kitchen/directions/bake_book_answer_key/eob_solutions_container.xhtml.erb +0 -9
- data/lib/kitchen/directions/bake_book_answer_key/main.rb +0 -11
- data/lib/kitchen/directions/bake_book_answer_key/v1.rb +0 -13
- data/lib/kitchen/directions/bake_chapter_glossary.rb +0 -39
- data/lib/kitchen/directions/bake_chapter_key_concepts/key_concepts.xhtml.erb +0 -16
- data/lib/kitchen/directions/bake_chapter_review/chapter_review.xhtml.erb +0 -9
- data/lib/kitchen/directions/bake_chapter_review/v1.rb +0 -13
- data/lib/kitchen/directions/bake_chapter_review_exercises/main.rb +0 -15
- data/lib/kitchen/directions/bake_chapter_review_exercises/review_exercises.xhtml.erb +0 -10
- data/lib/kitchen/directions/bake_exercises/main.rb +0 -12
- data/lib/kitchen/directions/bake_exercises/v1.rb +0 -169
- data/lib/kitchen/directions/bake_notes/bake_notes.rb +0 -48
- data/lib/kitchen/directions/bake_notes/bake_numbered_notes.rb +0 -63
- data/lib/kitchen/directions/bake_problem_first_elements.rb +0 -16
- data/lib/kitchen/transliterations.rb +0 -21
data/lib/kitchen/page_element.rb
CHANGED
@@ -13,8 +13,14 @@ module Kitchen
|
|
13
13
|
def initialize(node:, document: nil)
|
14
14
|
super(node: node,
|
15
15
|
document: document,
|
16
|
-
enumerator_class: PageElementEnumerator
|
17
|
-
|
16
|
+
enumerator_class: PageElementEnumerator)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Returns the short type
|
20
|
+
# @return [Symbol]
|
21
|
+
#
|
22
|
+
def self.short_type
|
23
|
+
:page
|
18
24
|
end
|
19
25
|
|
20
26
|
# Returns the title element. This method is aware that the title of the
|
@@ -23,9 +29,20 @@ module Kitchen
|
|
23
29
|
# @raise [ElementNotFoundError] if no matching element is found
|
24
30
|
# @return [Element]
|
25
31
|
#
|
26
|
-
def title
|
32
|
+
def title(reload: false)
|
27
33
|
# The selector for intro titles changes during the baking process
|
28
|
-
|
34
|
+
@title ||= begin
|
35
|
+
selector = is_introduction? ? selectors.title_in_introduction_page : selectors.title_in_page
|
36
|
+
first!(selector, reload: reload)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns the title's text regardless of whether the title has been baked
|
41
|
+
#
|
42
|
+
# @return [String]
|
43
|
+
#
|
44
|
+
def title_text
|
45
|
+
title.children.one? ? title.text : title.first('.os-text').text
|
29
46
|
end
|
30
47
|
|
31
48
|
# Returns an enumerator for titles.
|
@@ -71,11 +88,10 @@ module Kitchen
|
|
71
88
|
|
72
89
|
# Returns the summary element.
|
73
90
|
#
|
74
|
-
# @
|
75
|
-
# @return [Element]
|
91
|
+
# @return [Element, nil] the summary or nil if no summary found
|
76
92
|
#
|
77
93
|
def summary
|
78
|
-
first
|
94
|
+
first(selectors.page_summary)
|
79
95
|
end
|
80
96
|
|
81
97
|
# Returns the exercises element.
|
@@ -95,13 +111,12 @@ module Kitchen
|
|
95
111
|
search('section.key-concepts')
|
96
112
|
end
|
97
113
|
|
98
|
-
# Returns
|
114
|
+
# Returns the free response questions
|
99
115
|
#
|
100
|
-
# @
|
101
|
-
# @return [Boolean]
|
116
|
+
# @return [Element]
|
102
117
|
#
|
103
|
-
def
|
104
|
-
|
118
|
+
def free_response
|
119
|
+
search('section.free-response')
|
105
120
|
end
|
106
121
|
|
107
122
|
end
|
@@ -11,7 +11,7 @@ module Kitchen
|
|
11
11
|
#
|
12
12
|
def self.factory
|
13
13
|
ElementEnumeratorFactory.new(
|
14
|
-
default_css_or_xpath:
|
14
|
+
default_css_or_xpath: Selector.named(:page),
|
15
15
|
sub_element_class: PageElement,
|
16
16
|
enumerator_class: self
|
17
17
|
)
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'twitter_cldr'
|
4
|
+
|
5
|
+
# rubocop:disable Style/Documentation
|
6
|
+
module I18n
|
7
|
+
def self.sort_strings(first, second)
|
8
|
+
string_sorter.compare(first, second)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.string_sorter
|
12
|
+
@string_sorter ||= begin
|
13
|
+
# TwitterCldr does not know about our :test locale, so substitute the English one
|
14
|
+
locale = I18n.locale == :test ? :en : I18n.locale
|
15
|
+
TwitterCldr::Collation::Collator.new(locale)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.clear_string_sorter
|
20
|
+
@string_sorter = nil
|
21
|
+
end
|
22
|
+
|
23
|
+
class <<self
|
24
|
+
alias_method :original_locale=, :locale=
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.locale=(locale)
|
28
|
+
# We wrap the setting of locale so that we can clear the string sorter (so that it
|
29
|
+
# gets reset to the new locale the next time it is used)
|
30
|
+
clear_string_sorter
|
31
|
+
self.original_locale = locale
|
32
|
+
end
|
33
|
+
end
|
34
|
+
# rubocop:enable Style/Documentation
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Monkey patches for +Integer+
|
4
|
+
#
|
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
|
7
|
+
|
8
|
+
# Formats as different types of integers, including roman numerals.
|
9
|
+
#
|
10
|
+
# @return [String]
|
11
|
+
#
|
12
|
+
def to_format(format)
|
13
|
+
case format
|
14
|
+
when :arabic
|
15
|
+
to_s
|
16
|
+
when :roman
|
17
|
+
raise 'Unknown conversion to Roman numerals' if self >= ROMAN_NUMERALS.size
|
18
|
+
|
19
|
+
ROMAN_NUMERALS[self]
|
20
|
+
else
|
21
|
+
raise 'Unknown integer format'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -31,6 +31,21 @@ module Nokogiri
|
|
31
31
|
end
|
32
32
|
end
|
33
33
|
end
|
34
|
+
|
35
|
+
def add_all_namespaces!
|
36
|
+
# Nokogiri by default only recognizes the namespaces on the root node. Collect all
|
37
|
+
# namespaces and add them manually.
|
38
|
+
return if @all_namespaces_added
|
39
|
+
|
40
|
+
collect_namespaces.each do |namespace, url|
|
41
|
+
prefix, name = namespace.split(':')
|
42
|
+
next unless prefix == 'xmlns' && name.present?
|
43
|
+
|
44
|
+
root.add_namespace_definition(name, url)
|
45
|
+
end
|
46
|
+
|
47
|
+
@all_namespaces_added = true
|
48
|
+
end
|
34
49
|
end
|
35
50
|
|
36
51
|
# Monkey patches for Nokogiri::XML::Node
|
@@ -43,6 +58,53 @@ module Nokogiri
|
|
43
58
|
def inspect
|
44
59
|
to_s
|
45
60
|
end
|
61
|
+
|
62
|
+
def quick_matches?(selector)
|
63
|
+
self.class.selector_to_css_nodes(selector).any? { |css_node| matches_css_node?(css_node) }
|
64
|
+
end
|
65
|
+
|
66
|
+
def classes
|
67
|
+
self[:class]&.split || []
|
68
|
+
end
|
69
|
+
|
70
|
+
def previous
|
71
|
+
prev = previous_element
|
72
|
+
return nil if prev.nil?
|
73
|
+
|
74
|
+
prev.text? ? prev.previous : prev
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.selector_to_css_nodes(selector)
|
78
|
+
# No need to parse the same selector more than once.
|
79
|
+
@parsed_selectors ||= {}
|
80
|
+
@parsed_selectors[selector] ||= Nokogiri::CSS::Parser.new.parse(selector)
|
81
|
+
end
|
82
|
+
|
83
|
+
protected
|
84
|
+
|
85
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
86
|
+
def matches_css_node?(css_node)
|
87
|
+
case css_node.type
|
88
|
+
when :CONDITIONAL_SELECTOR
|
89
|
+
css_node.value.all? { |inner_css_node| matches_css_node?(inner_css_node) }
|
90
|
+
when :ELEMENT_NAME
|
91
|
+
css_node.value == ['*'] || css_node.value.include?(name)
|
92
|
+
when :CLASS_CONDITION
|
93
|
+
(css_node.value & classes).any?
|
94
|
+
when :ATTRIBUTE_CONDITION
|
95
|
+
attribute, operator, value = css_node.value
|
96
|
+
|
97
|
+
raise "Unknown attribute condition operator in #{css_node}" if operator != :equal
|
98
|
+
|
99
|
+
attribute_name = attribute.value
|
100
|
+
raise "More attribute names than expected, #{attribute_name}" if attribute_name.many?
|
101
|
+
|
102
|
+
self[attribute_name.first] == value.gsub('"', '').gsub("'", '')
|
103
|
+
else
|
104
|
+
raise "Unknown Nokogiri::CSS:Node type in #{css_node}"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
46
108
|
end
|
47
109
|
end
|
48
110
|
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# rubocop:disable Style/Documentation
|
4
|
+
|
5
|
+
# Make debug output more useful (dumping entire document out is not useful)
|
6
|
+
module Nokogiri
|
7
|
+
module XML
|
8
|
+
# rubocop:disable Style/MutableConstant
|
9
|
+
PROFILE_DATA = {}
|
10
|
+
# rubocop:enable Style/MutableConstant
|
11
|
+
|
12
|
+
if ENV['PROFILE']
|
13
|
+
ENV['VERBOSE_PROFILE'] = 1 if ENV['PROFILE'].to_s.downcase == 'verbose'
|
14
|
+
|
15
|
+
# Patches inside Nokogiri to count, time, and print searches. At end of baking
|
16
|
+
# you can `puts Nokogiri::XML::PROFILE_DATA` to see the totals. The counts
|
17
|
+
# hash is defined outside of the if block so that code that prints it doesn't
|
18
|
+
# explode if run without the env var. The `print_profile_data` method is also
|
19
|
+
# provided for a nice printout
|
20
|
+
|
21
|
+
def self.print_profile_data
|
22
|
+
total_duration = 0
|
23
|
+
|
24
|
+
sorted_profile_data = PROFILE_DATA.sort_by { |_, data| data[:time] / data[:count] }.reverse
|
25
|
+
|
26
|
+
puts "\nSearch Profile Data"
|
27
|
+
puts '-----------------------------------------------------------------'
|
28
|
+
puts "#{'Total Time (ms)'.ljust(17)}#{'Avg Time (ms)'.ljust(15)}#{'Count'.ljust(7)}Query"
|
29
|
+
puts '-----------------------------------------------------------------'
|
30
|
+
|
31
|
+
sorted_profile_data.each do |search_path, data|
|
32
|
+
total_time = format('%0.4f', (data[:time] * 1000))
|
33
|
+
avg_time = format('%0.4f', ((data[:time] / data[:count]) * 1000))
|
34
|
+
puts total_time.ljust(17) + avg_time.to_s.ljust(15) + data[:count].to_s.ljust(7) + \
|
35
|
+
search_path
|
36
|
+
total_duration += data[:time] * 1000
|
37
|
+
end
|
38
|
+
|
39
|
+
puts "\nTotal time across all searches (ms): #{total_duration}"
|
40
|
+
end
|
41
|
+
|
42
|
+
class XPathContext
|
43
|
+
alias_method :original_evaluate, :evaluate
|
44
|
+
def evaluate(search_path, handler=nil)
|
45
|
+
puts search_path if ENV['VERBOSE_PROFILE']
|
46
|
+
|
47
|
+
PROFILE_DATA[search_path] ||= Hash.new(0)
|
48
|
+
PROFILE_DATA[search_path][:count] += 1
|
49
|
+
|
50
|
+
start_time = Time.now
|
51
|
+
original_evaluate(search_path, handler).tap do
|
52
|
+
PROFILE_DATA[search_path][:time] += Time.now - start_time
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# rubocop:enable Style/Documentation
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kitchen
|
4
|
+
# An element for an example
|
5
|
+
#
|
6
|
+
class ReferenceElement < ElementBase
|
7
|
+
|
8
|
+
# Creates a new +ReferenceElement+
|
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: ReferenceElementEnumerator)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Returns the short type
|
20
|
+
# @return [Symbol]
|
21
|
+
#
|
22
|
+
def self.short_type
|
23
|
+
:reference
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kitchen
|
4
|
+
# An enumerator for table elements
|
5
|
+
#
|
6
|
+
class ReferenceElementEnumerator < ElementEnumeratorBase
|
7
|
+
# Returns a factory for this enumerator
|
8
|
+
#
|
9
|
+
# @return [ElementEnumeratorFactory]
|
10
|
+
#
|
11
|
+
def self.factory
|
12
|
+
ElementEnumeratorFactory.new(
|
13
|
+
default_css_or_xpath: Selector.named(:reference),
|
14
|
+
sub_element_class: ReferenceElement,
|
15
|
+
enumerator_class: self
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
data/lib/kitchen/search_query.rb
CHANGED
@@ -24,6 +24,7 @@ module Kitchen
|
|
24
24
|
@css_or_xpath = css_or_xpath
|
25
25
|
@only = only.is_a?(String) ? only.to_sym : only
|
26
26
|
@except = except.is_a?(String) ? except.to_sym : except
|
27
|
+
@default_already_applied = false
|
27
28
|
end
|
28
29
|
|
29
30
|
# Returns true iff the element passes the `only` and `except` conditions
|
@@ -35,13 +36,34 @@ module Kitchen
|
|
35
36
|
end
|
36
37
|
|
37
38
|
# Replaces '$' in the `css_or_xpath` with the provided value; also normalizes
|
38
|
-
# `css_or_xpath` to an array
|
39
|
+
# `css_or_xpath` to an array.
|
39
40
|
#
|
40
|
-
|
41
|
+
# @param default_css_or_xpath [String, Proc, Symbol] The selectors to substitute for the "$" character
|
42
|
+
# when this factory is used to build an enumerator. A string argument is used literally. A proc
|
43
|
+
# is eventually called given the document's Config object (for accessing selectors). A symbol
|
44
|
+
# is interpreted as the name of a selector and is called on the document's Config object's
|
45
|
+
# selectors object.
|
46
|
+
#
|
47
|
+
def apply_default_css_or_xpath_and_normalize(default_css_or_xpath=nil, config: nil)
|
48
|
+
return if @default_already_applied
|
49
|
+
|
50
|
+
default_css_or_xpath = [default_css_or_xpath].flatten.map do |item|
|
51
|
+
case item
|
52
|
+
when Proc
|
53
|
+
item.call(config)
|
54
|
+
when Symbol
|
55
|
+
config.selectors.send(item)
|
56
|
+
else
|
57
|
+
item
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
41
61
|
@as_type = nil
|
42
62
|
@css_or_xpath = [css_or_xpath || '$'].flatten.map do |item|
|
43
|
-
item.gsub(/\$/,
|
63
|
+
item.gsub(/\$/, default_css_or_xpath.join(', '))
|
44
64
|
end
|
65
|
+
|
66
|
+
@default_already_applied = true
|
45
67
|
end
|
46
68
|
|
47
69
|
# Returns the search query as a spaceless string suitable for use as an element type
|
@@ -60,6 +82,12 @@ module Kitchen
|
|
60
82
|
as_type
|
61
83
|
end
|
62
84
|
|
85
|
+
# Returns true if the query has the substitution character ('$')
|
86
|
+
#
|
87
|
+
def expects_substitution?
|
88
|
+
css_or_xpath.nil? || [css_or_xpath].flatten.all? { |item| item.include?('$') }
|
89
|
+
end
|
90
|
+
|
63
91
|
protected
|
64
92
|
|
65
93
|
def condition_passes?(method_or_callable, element, success_outcome)
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kitchen
|
4
|
+
# A wrapper for a selector. Can be used as the default_css_or_xpath.
|
5
|
+
#
|
6
|
+
class Selector < Proc
|
7
|
+
attr_reader :name
|
8
|
+
|
9
|
+
def self.named(name)
|
10
|
+
@instances ||= {}
|
11
|
+
@instances[name] ||= new(name) { |config| config.selectors.send(name) }
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(name, &block)
|
15
|
+
@name = name
|
16
|
+
super(&block)
|
17
|
+
end
|
18
|
+
|
19
|
+
def matches?(node, config:)
|
20
|
+
# This may not be incredibly efficient as it does a search of this node's
|
21
|
+
# ancestors to see if the node is in the results. Watch the performance.
|
22
|
+
node.quick_matches?(call(config))
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -17,6 +17,45 @@ module Kitchen
|
|
17
17
|
# Selector for the summary in a page
|
18
18
|
# @return [String]
|
19
19
|
attr_accessor :page_summary
|
20
|
+
# Selector for a reference
|
21
|
+
# @return [String]
|
22
|
+
attr_accessor :reference
|
23
|
+
# Selector for a chapter
|
24
|
+
# @return [String]
|
25
|
+
attr_accessor :chapter
|
26
|
+
# Selector for a page
|
27
|
+
# @return [String]
|
28
|
+
attr_accessor :page
|
29
|
+
# Selector for a note
|
30
|
+
# @return [String]
|
31
|
+
attr_accessor :note
|
32
|
+
# Selector for a term
|
33
|
+
# @return [String]
|
34
|
+
attr_accessor :term
|
35
|
+
# Selector for a table
|
36
|
+
# @return [String]
|
37
|
+
attr_accessor :table
|
38
|
+
# Selector for a figure
|
39
|
+
# @return [String]
|
40
|
+
attr_accessor :figure
|
41
|
+
# Selector for a metadata
|
42
|
+
# @return [String]
|
43
|
+
attr_accessor :metadata
|
44
|
+
# Selector for a composite page
|
45
|
+
# @return [String]
|
46
|
+
attr_accessor :composite_page
|
47
|
+
# Selector for a composite chapter
|
48
|
+
# @return [String]
|
49
|
+
attr_accessor :composite_chapter
|
50
|
+
# Selector for an example
|
51
|
+
# @return [String]
|
52
|
+
attr_accessor :example
|
53
|
+
# Selector for an exercise
|
54
|
+
# @return [String]
|
55
|
+
attr_accessor :exercise
|
56
|
+
# Selector for an unit
|
57
|
+
# @return [String]
|
58
|
+
attr_accessor :unit
|
20
59
|
|
21
60
|
# Override specific selectors
|
22
61
|
#
|