openstax_kitchen 1.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 +7 -0
- data/.devcontainer/devcontainer.json +19 -0
- data/.github/workflows/tests.yml +36 -0
- data/.gitignore +20 -0
- data/.rspec +3 -0
- data/.solargraph.yml +15 -0
- data/CHANGELOG.md +11 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Dockerfile +19 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +74 -0
- data/LICENSE.txt +21 -0
- data/README.md +674 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/normalize +79 -0
- data/bin/setup +8 -0
- data/books/chemistry2e/bake.rb +133 -0
- data/codecov.yaml +27 -0
- data/docker-compose.yml +12 -0
- data/docker/bash +1 -0
- data/docker/entrypoint +9 -0
- data/lib/kitchen.rb +57 -0
- data/lib/kitchen/ancestor.rb +30 -0
- data/lib/kitchen/book_document.rb +18 -0
- data/lib/kitchen/book_element.rb +24 -0
- data/lib/kitchen/book_element_enumerator.rb +5 -0
- data/lib/kitchen/book_recipe.rb +25 -0
- data/lib/kitchen/chapter_element.rb +35 -0
- data/lib/kitchen/chapter_element_enumerator.rb +13 -0
- data/lib/kitchen/clipboard.rb +37 -0
- data/lib/kitchen/composite_chapter_element.rb +23 -0
- data/lib/kitchen/composite_page_element.rb +27 -0
- data/lib/kitchen/composite_page_element_enumerator.rb +13 -0
- data/lib/kitchen/config.rb +20 -0
- data/lib/kitchen/counter.rb +34 -0
- data/lib/kitchen/debug/print_recipe_error.rb +80 -0
- data/lib/kitchen/directions/bake_appendix.rb +26 -0
- data/lib/kitchen/directions/bake_chapter_glossary.rb +34 -0
- data/lib/kitchen/directions/bake_chapter_introductions.rb +58 -0
- data/lib/kitchen/directions/bake_chapter_key_equations.rb +30 -0
- data/lib/kitchen/directions/bake_chapter_summary.rb +52 -0
- data/lib/kitchen/directions/bake_composite_pages.rb +13 -0
- data/lib/kitchen/directions/bake_example.rb +31 -0
- data/lib/kitchen/directions/bake_exercises.rb +164 -0
- data/lib/kitchen/directions/bake_figure.rb +25 -0
- data/lib/kitchen/directions/bake_footnotes/main.rb +11 -0
- data/lib/kitchen/directions/bake_footnotes/v1.rb +38 -0
- data/lib/kitchen/directions/bake_index/main.rb +11 -0
- data/lib/kitchen/directions/bake_index/v1.rb +138 -0
- data/lib/kitchen/directions/bake_index/v1.xhtml.erb +28 -0
- data/lib/kitchen/directions/bake_math_in_paragraph.rb +13 -0
- data/lib/kitchen/directions/bake_notes.rb +58 -0
- data/lib/kitchen/directions/bake_numbered_table/main.rb +11 -0
- data/lib/kitchen/directions/bake_numbered_table/v1.rb +47 -0
- data/lib/kitchen/directions/bake_stepwise.rb +27 -0
- data/lib/kitchen/directions/bake_toc.rb +103 -0
- data/lib/kitchen/directions/bake_unnumbered_tables.rb +14 -0
- data/lib/kitchen/directions/move_title_text_into_span.rb +15 -0
- data/lib/kitchen/document.rb +142 -0
- data/lib/kitchen/element.rb +15 -0
- data/lib/kitchen/element_base.rb +444 -0
- data/lib/kitchen/element_enumerator.rb +12 -0
- data/lib/kitchen/element_enumerator_base.rb +101 -0
- data/lib/kitchen/element_enumerator_factory.rb +111 -0
- data/lib/kitchen/element_factory.rb +32 -0
- data/lib/kitchen/errors.rb +4 -0
- data/lib/kitchen/example_element.rb +20 -0
- data/lib/kitchen/example_element_enumerator.rb +13 -0
- data/lib/kitchen/figure_element.rb +20 -0
- data/lib/kitchen/figure_element_enumerator.rb +13 -0
- data/lib/kitchen/mixins/block_error_if.rb +19 -0
- data/lib/kitchen/note_element.rb +43 -0
- data/lib/kitchen/note_element_enumerator.rb +13 -0
- data/lib/kitchen/oven.rb +61 -0
- data/lib/kitchen/page_element.rb +51 -0
- data/lib/kitchen/page_element_enumerator.rb +13 -0
- data/lib/kitchen/pantry.rb +35 -0
- data/lib/kitchen/patches/nokogiri.rb +31 -0
- data/lib/kitchen/patches/renderable.rb +31 -0
- data/lib/kitchen/patches/string.rb +5 -0
- data/lib/kitchen/recipe.rb +78 -0
- data/lib/kitchen/search_history.rb +33 -0
- data/lib/kitchen/selectors/base.rb +8 -0
- data/lib/kitchen/selectors/standard_1.rb +12 -0
- data/lib/kitchen/table_element.rb +36 -0
- data/lib/kitchen/table_element_enumerator.rb +13 -0
- data/lib/kitchen/term_element.rb +16 -0
- data/lib/kitchen/term_element_enumerator.rb +13 -0
- data/lib/kitchen/transliterations.rb +19 -0
- data/lib/kitchen/type_casting_element_enumerator.rb +23 -0
- data/lib/kitchen/utils.rb +19 -0
- data/lib/kitchen/version.rb +3 -0
- data/lib/locales/en.yml +21 -0
- data/lib/notes.md +9 -0
- data/openstax_kitchen.gemspec +39 -0
- data/tutorials/00/expected_baked.html +3 -0
- data/tutorials/00/raw.html +3 -0
- data/tutorials/00/solution_1.rb +7 -0
- data/tutorials/00/solution_2.rb +6 -0
- data/tutorials/01/expected_baked.html +66 -0
- data/tutorials/01/raw.html +62 -0
- data/tutorials/01/solution_1.rb +16 -0
- data/tutorials/01/solution_2.rb +24 -0
- data/tutorials/02/expected_baked.html +207 -0
- data/tutorials/02/raw.html +201 -0
- data/tutorials/02/solution_1.rb +29 -0
- data/tutorials/03/expected_baked.html +33 -0
- data/tutorials/03/raw.html +31 -0
- data/tutorials/03/solution_1.rb +16 -0
- data/tutorials/03/solution_2.rb +15 -0
- data/tutorials/04/expected_baked.html +36 -0
- data/tutorials/04/raw.html +36 -0
- data/tutorials/04/solution_1.rb +20 -0
- data/tutorials/04/solution_2.rb +25 -0
- data/tutorials/05/expected_baked.html +11 -0
- data/tutorials/05/raw.html +11 -0
- data/tutorials/05/solution_1.rb +9 -0
- data/tutorials/check_it +64 -0
- data/tutorials/setup_my_recipes +30 -0
- metadata +278 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module Kitchen
|
|
2
|
+
class Element < ElementBase
|
|
3
|
+
|
|
4
|
+
def initialize(node:, document:, short_type: nil)
|
|
5
|
+
super(node: node,
|
|
6
|
+
document: document,
|
|
7
|
+
enumerator_class: ElementEnumerator,
|
|
8
|
+
short_type: short_type)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# # @!method pages
|
|
12
|
+
# # Returns a pages enumerator
|
|
13
|
+
# def_delegators :as_enumerator, :pages, :chapters, :terms, :figures, :notes, :tables, :examples
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
require 'forwardable'
|
|
2
|
+
require 'securerandom'
|
|
3
|
+
|
|
4
|
+
module Kitchen
|
|
5
|
+
|
|
6
|
+
# Abstract base class for all elements. If you are looking for a simple concrete
|
|
7
|
+
# element class, use `Element`.
|
|
8
|
+
#
|
|
9
|
+
class ElementBase
|
|
10
|
+
extend Forwardable
|
|
11
|
+
include Mixins::BlockErrorIf
|
|
12
|
+
|
|
13
|
+
attr_reader :document
|
|
14
|
+
attr_reader :short_type
|
|
15
|
+
attr_reader :enumerator_class
|
|
16
|
+
attr_accessor :css_or_xpath_that_found_me
|
|
17
|
+
|
|
18
|
+
# @!method name
|
|
19
|
+
# Get the element name (the tag)
|
|
20
|
+
# @!method name=
|
|
21
|
+
# Set the element name (the tag)
|
|
22
|
+
# @!method []
|
|
23
|
+
# Get an element attribute
|
|
24
|
+
# @!method []=
|
|
25
|
+
# Set an element attribute
|
|
26
|
+
# @!method add_class
|
|
27
|
+
# Add a class to the element
|
|
28
|
+
# @!method remove_class
|
|
29
|
+
# Remove a class from the element
|
|
30
|
+
def_delegators :@node, :name=, :name, :[], :[]=, :add_class, :remove_class,
|
|
31
|
+
:text, :wrap, :children, :to_html, :remove_attribute,
|
|
32
|
+
:classes, :path
|
|
33
|
+
|
|
34
|
+
def_delegators :document, :config
|
|
35
|
+
def_delegators :config, :selectors
|
|
36
|
+
|
|
37
|
+
def initialize(node:, document:, enumerator_class:, short_type: nil)
|
|
38
|
+
raise(ArgumentError, "node cannot be nil") if node.nil?
|
|
39
|
+
@node = node
|
|
40
|
+
|
|
41
|
+
raise(ArgumentError, "enumerator_class cannot be nil") if enumerator_class.nil?
|
|
42
|
+
@enumerator_class = enumerator_class
|
|
43
|
+
|
|
44
|
+
@short_type = short_type || "unknown_type_#{SecureRandom.hex(4)}"
|
|
45
|
+
|
|
46
|
+
@document =
|
|
47
|
+
case document
|
|
48
|
+
when Kitchen::Document
|
|
49
|
+
document
|
|
50
|
+
else
|
|
51
|
+
raise(ArgumentError, "`document` is not a known document type")
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
@ancestors = HashWithIndifferentAccess.new
|
|
55
|
+
@counts_in = HashWithIndifferentAccess.new
|
|
56
|
+
@css_or_xpath_that_has_been_counted = {}
|
|
57
|
+
@is_a_clone = false
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def self.is_the_element_class_for?(node)
|
|
61
|
+
# override this in subclasses
|
|
62
|
+
false
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def has_class?(klass)
|
|
66
|
+
(self[:class] || "").include?(klass)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def id
|
|
70
|
+
self[:id]
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def id=(value)
|
|
74
|
+
self[:id] = value
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# A way to set values and chain them
|
|
78
|
+
def set(property, value)
|
|
79
|
+
case property.to_sym
|
|
80
|
+
when :name
|
|
81
|
+
self.name = value
|
|
82
|
+
else
|
|
83
|
+
self[property.to_sym] = value
|
|
84
|
+
end
|
|
85
|
+
self
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def ancestor(type)
|
|
89
|
+
@ancestors[type.to_sym]&.element || raise("No ancestor of type '#{type}'")
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def has_ancestor?(type)
|
|
93
|
+
@ancestors[type.to_sym].present?
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def ancestors
|
|
97
|
+
@ancestors
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def add_ancestors(*args)
|
|
101
|
+
args.each do |arg|
|
|
102
|
+
case arg
|
|
103
|
+
when Hash
|
|
104
|
+
add_ancestors(*arg.values)
|
|
105
|
+
when Ancestor
|
|
106
|
+
add_ancestor(arg)
|
|
107
|
+
when Element, Document
|
|
108
|
+
add_ancestor(Ancestor.new(arg))
|
|
109
|
+
else
|
|
110
|
+
raise "Unsupported ancestor type `#{arg.class}`"
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def add_ancestor(ancestor)
|
|
116
|
+
if @ancestors[ancestor.type].present?
|
|
117
|
+
raise "Trying to add an ancestor of type '#{ancestor.type}' but one of that " \
|
|
118
|
+
"type is already present"
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
@ancestors[ancestor.type] = ancestor
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def ancestor_elements
|
|
125
|
+
@ancestors.values.map(&:element)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def count_as_descendant
|
|
129
|
+
@ancestors.each_pair do |type, ancestor|
|
|
130
|
+
@counts_in[type] = ancestor.increment_descendant_count(short_type)
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def count_in(ancestor_type)
|
|
135
|
+
@counts_in[ancestor_type] || raise("No ancestor of type '#{ancestor_type}'")
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def remember_that_sub_elements_are_already_counted(css_or_xpath:, count:)
|
|
139
|
+
@css_or_xpath_that_has_been_counted[css_or_xpath] = count
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def have_sub_elements_already_been_counted?(css_or_xpath)
|
|
143
|
+
number_of_sub_elements_already_counted(css_or_xpath) != 0
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def number_of_sub_elements_already_counted(css_or_xpath)
|
|
147
|
+
@css_or_xpath_that_has_been_counted[css_or_xpath] || 0
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def search_history
|
|
151
|
+
history = ancestor_elements.map(&:css_or_xpath_that_found_me) + [css_or_xpath_that_found_me]
|
|
152
|
+
history.compact.join(" ")
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def search(*selector_or_xpath_args)
|
|
156
|
+
block_error_if(block_given?)
|
|
157
|
+
|
|
158
|
+
ElementEnumerator.factory.build_within(self, css_or_xpath: selector_or_xpath_args)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Yields and returns the first child element that matches the provided
|
|
162
|
+
# selector or XPath arguments.
|
|
163
|
+
#
|
|
164
|
+
# @param selector_or_xpath_args [Array<String>] CSS selectors or XPath arguments
|
|
165
|
+
# @yieldparam [Element] the matched XML element
|
|
166
|
+
# @return [Element, nil] the matched XML element or nil if no match found
|
|
167
|
+
#
|
|
168
|
+
def first(*selector_or_xpath_args)
|
|
169
|
+
search(*selector_or_xpath_args).first.tap do |element|
|
|
170
|
+
yield(element) if block_given?
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Yields and returns the first child element that matches the provided
|
|
175
|
+
# selector or XPath arguments.
|
|
176
|
+
#
|
|
177
|
+
# @param selector_or_xpath_args [Array<String>] CSS selectors or XPath arguments
|
|
178
|
+
# @yieldparam [Element] the matched XML element
|
|
179
|
+
# @raise [ElementNotFoundError] if no matching element is found
|
|
180
|
+
# @return [Element] the matched XML element
|
|
181
|
+
#
|
|
182
|
+
def first!(*selector_or_xpath_args)
|
|
183
|
+
search(*selector_or_xpath_args).first!.tap do |element|
|
|
184
|
+
yield(element) if block_given?
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
alias_method :at, :first
|
|
189
|
+
|
|
190
|
+
def element_children()
|
|
191
|
+
block_error_if(block_given?)
|
|
192
|
+
TypeCastingElementEnumerator.factory.build_within(self, css_or_xpath: "./*")
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def search_with(*enumerator_classes)
|
|
196
|
+
block_error_if(block_given?)
|
|
197
|
+
raise "must supply at least one enumerator class" if enumerator_classes.empty?
|
|
198
|
+
|
|
199
|
+
factory = enumerator_classes[0].factory
|
|
200
|
+
enumerator_classes[1..-1].each do |enumerator_class|
|
|
201
|
+
factory = factory.or_with(enumerator_class.factory)
|
|
202
|
+
end
|
|
203
|
+
factory.build_within(self)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# Removes the element from its parent and places it on the specified clipboard
|
|
207
|
+
#
|
|
208
|
+
# @param to [Symbol, String, Clipboard, nil] the name of the clipboard (or a Clipboard
|
|
209
|
+
# object) to cut to. String values are converted to symbols. If not provided, the
|
|
210
|
+
# element is not placed on a clipboard.
|
|
211
|
+
# @return [Element] the cut element
|
|
212
|
+
#
|
|
213
|
+
def cut(to: nil)
|
|
214
|
+
block_error_if(block_given?)
|
|
215
|
+
|
|
216
|
+
node.remove
|
|
217
|
+
clipboard(to).add(self) if to.present?
|
|
218
|
+
self
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Makes a copy of the element and places it on the specified clipboard.
|
|
222
|
+
#
|
|
223
|
+
# @param to [Symbol, String, Clipboard, nil] the name of the clipboard (or a Clipboard
|
|
224
|
+
# object) to cut to. String values are converted to symbols. If not provided, the
|
|
225
|
+
# copy is not placed on a clipboard.
|
|
226
|
+
# @return [Element] the copied element
|
|
227
|
+
#
|
|
228
|
+
def copy(to: nil)
|
|
229
|
+
# See `clone` method for a note about namespaces
|
|
230
|
+
block_error_if(block_given?)
|
|
231
|
+
|
|
232
|
+
the_copy = clone
|
|
233
|
+
the_copy.raw.traverse do |node|
|
|
234
|
+
next if node.text? || node.document?
|
|
235
|
+
document.record_id_copied(node[:id])
|
|
236
|
+
end
|
|
237
|
+
clipboard(to).add(the_copy) if to.present?
|
|
238
|
+
the_copy
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# When an element is cut or copied, use this method to get the element's content;
|
|
242
|
+
# keeps IDs unique
|
|
243
|
+
def paste
|
|
244
|
+
# See `clone` method for a note about namespaces
|
|
245
|
+
block_error_if(block_given?)
|
|
246
|
+
|
|
247
|
+
temp_copy = clone
|
|
248
|
+
temp_copy.raw.traverse do |node|
|
|
249
|
+
next if node.text? || node.document?
|
|
250
|
+
node[:id] = document.modified_id_to_paste(node[:id]) unless node[:id].blank?
|
|
251
|
+
end
|
|
252
|
+
temp_copy.to_s
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
# Delete the element
|
|
256
|
+
#
|
|
257
|
+
def trash
|
|
258
|
+
node.remove
|
|
259
|
+
self
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def parent
|
|
263
|
+
Element.new(node: raw.parent, document: document, short_type: "parent(#{short_type})")
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
# TODO make it clear if all of these methods take Element, Node, or String
|
|
267
|
+
|
|
268
|
+
# If child argument given, prepends it before the element's current children.
|
|
269
|
+
# If sibling is given, prepends it as a sibling to this element.
|
|
270
|
+
#
|
|
271
|
+
# @param child [String] the child to prepend
|
|
272
|
+
# @param sibling [String] the sibling to prepend
|
|
273
|
+
#
|
|
274
|
+
def prepend(child: nil, sibling: nil)
|
|
275
|
+
if child && sibling
|
|
276
|
+
raise RecipeError, "Only one of `child` or `sibling` can be specified"
|
|
277
|
+
elsif !child && !sibling
|
|
278
|
+
raise RecipeError, "One of `child` or `sibling` must be specified"
|
|
279
|
+
elsif child
|
|
280
|
+
if node.children.empty?
|
|
281
|
+
node.children = child.to_s
|
|
282
|
+
else
|
|
283
|
+
node.children.first.add_previous_sibling(child)
|
|
284
|
+
end
|
|
285
|
+
else
|
|
286
|
+
node.add_previous_sibling(sibling)
|
|
287
|
+
end
|
|
288
|
+
self
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# If child argument given, appends it after the element's current children.
|
|
292
|
+
# If sibling is given, appends it as a sibling to this element.
|
|
293
|
+
#
|
|
294
|
+
# @param child [String] the child to append
|
|
295
|
+
# @param sibling [String] the sibling to append
|
|
296
|
+
#
|
|
297
|
+
def append(child: nil, sibling: nil)
|
|
298
|
+
if child && sibling
|
|
299
|
+
raise RecipeError, "Only one of `child` or `sibling` can be specified"
|
|
300
|
+
elsif !child && !sibling
|
|
301
|
+
raise RecipeError, "One of `child` or `sibling` must be specified"
|
|
302
|
+
elsif child
|
|
303
|
+
if node.children.empty?
|
|
304
|
+
node.children = child.to_s
|
|
305
|
+
else
|
|
306
|
+
node.add_child(child)
|
|
307
|
+
end
|
|
308
|
+
else
|
|
309
|
+
node.next = sibling
|
|
310
|
+
end
|
|
311
|
+
self
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
# Replaces this element's children
|
|
315
|
+
#
|
|
316
|
+
# @param with [String] the children to substitute for the current children
|
|
317
|
+
#
|
|
318
|
+
def replace_children(with:)
|
|
319
|
+
node.children = with
|
|
320
|
+
self
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
# TODO methods like replace_children that take string, either forbid or handle Element/Node args
|
|
324
|
+
|
|
325
|
+
# Get the content of children matching the provided selector. Mostly
|
|
326
|
+
# useful when there is one child with text you want to extract.
|
|
327
|
+
#
|
|
328
|
+
# @param selector_or_xpath_args [Array<String>] CSS selectors or XPath arguments
|
|
329
|
+
# @return [String]
|
|
330
|
+
#
|
|
331
|
+
def content(*selector_or_xpath_args)
|
|
332
|
+
node.search(*selector_or_xpath_args).children.to_s
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
# Returns true if this element has a child matching the provided selector
|
|
336
|
+
#
|
|
337
|
+
# @param selector_or_xpath_args [Array<String>] CSS selectors or XPath arguments
|
|
338
|
+
# @return [Boolean]
|
|
339
|
+
#
|
|
340
|
+
def contains?(*selector_or_xpath_args)
|
|
341
|
+
!node.at(*selector_or_xpath_args).nil?
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
# Returns the header tag name that is one level under the first header tag in this
|
|
345
|
+
# element, e.g. if this element is a "div" whose first header is "h1", this will
|
|
346
|
+
# return "h2"
|
|
347
|
+
#
|
|
348
|
+
# TODO this method may not be needed.
|
|
349
|
+
#
|
|
350
|
+
# @return [String] the sub header tag name
|
|
351
|
+
#
|
|
352
|
+
def sub_header_name
|
|
353
|
+
first_header = node.search("h1, h2, h3, h4, h5, h6").first
|
|
354
|
+
|
|
355
|
+
first_header.nil? ?
|
|
356
|
+
"h1" :
|
|
357
|
+
first_header.name.gsub(/\d/) {|num| (num.to_i + 1).to_s}
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
# Returns the underlying Nokogiri object
|
|
361
|
+
#
|
|
362
|
+
# @return [Nokogiri::XML::Node]
|
|
363
|
+
#
|
|
364
|
+
def raw
|
|
365
|
+
node
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
def inspect
|
|
369
|
+
to_s
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
def to_s
|
|
373
|
+
remove_default_namespaces_if_clone(node.to_s)
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
def to_xml
|
|
377
|
+
remove_default_namespaces_if_clone(node.to_xml)
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
def to_xhtml
|
|
381
|
+
remove_default_namespaces_if_clone(node.to_xhtml)
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
def clone
|
|
385
|
+
super.tap do |element|
|
|
386
|
+
# When we call dup, the dup gets a bunch of default namespace stuff that
|
|
387
|
+
# the original doesn't have. Why? Unclear, but hard to get rid of nicely.
|
|
388
|
+
# So here we mark that the element is a clone and then all of the `to_s`-like
|
|
389
|
+
# methods gsub out the default namespace gunk. Clones are mostly used for
|
|
390
|
+
# clipboards and are accessed using `paste` methods, so modifying the `to_s`
|
|
391
|
+
# behavior works for us. If we end up using `clone` in a way that doesn't
|
|
392
|
+
# eventually get converted to string, we may have to investigate other
|
|
393
|
+
# options.
|
|
394
|
+
#
|
|
395
|
+
# An alternative is to remove the `xmlns` attribute in the `html` tag before
|
|
396
|
+
# the input file is parse into a Nokogiri document and then to add it back
|
|
397
|
+
# in when the baked file is written out.
|
|
398
|
+
#
|
|
399
|
+
# Nokogiri::XML::Document.remove_namespaces! is not an option because that blows
|
|
400
|
+
# away our MathML namespace.
|
|
401
|
+
#
|
|
402
|
+
# I may not fully understand why the extra default namespace stuff is happening
|
|
403
|
+
# FWIW :-)
|
|
404
|
+
#
|
|
405
|
+
element.node = node.dup
|
|
406
|
+
element.is_a_clone = true
|
|
407
|
+
end
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
# @!method pages
|
|
411
|
+
# Returns a pages enumerator
|
|
412
|
+
def_delegators :as_enumerator, :pages, :chapters, :terms, :figures, :notes, :tables, :examples
|
|
413
|
+
|
|
414
|
+
def as_enumerator
|
|
415
|
+
enumerator_class.new(css_or_xpath: css_or_xpath_that_found_me) {|block| block.yield(self)}
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
protected
|
|
419
|
+
|
|
420
|
+
attr_accessor :node
|
|
421
|
+
attr_accessor :is_a_clone
|
|
422
|
+
|
|
423
|
+
def clipboard(name_or_object)
|
|
424
|
+
case name_or_object
|
|
425
|
+
when Symbol
|
|
426
|
+
document.clipboard(name: name_or_object)
|
|
427
|
+
when Clipboard
|
|
428
|
+
name_or_object
|
|
429
|
+
else
|
|
430
|
+
raise ArgumentError, "The provided argument (#{name_or_object}) is not "
|
|
431
|
+
"a clipboard name or a clipboard"
|
|
432
|
+
end
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
def remove_default_namespaces_if_clone(string)
|
|
436
|
+
if is_a_clone
|
|
437
|
+
string.gsub("xmlns:default=\"http://www.w3.org/1999/xhtml\"","").gsub("default:","")
|
|
438
|
+
else
|
|
439
|
+
string
|
|
440
|
+
end
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
end
|
|
444
|
+
end
|