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