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,52 @@
|
|
|
1
|
+
module Kitchen
|
|
2
|
+
module Directions
|
|
3
|
+
module BakeChapterSummary
|
|
4
|
+
|
|
5
|
+
def self.v1(chapter:, metadata_source:)
|
|
6
|
+
metadata_elements = metadata_source.search(%w(.authors .publishers .print-style
|
|
7
|
+
.permissions [data-type='subject'])).copy
|
|
8
|
+
|
|
9
|
+
summaries = Clipboard.new
|
|
10
|
+
|
|
11
|
+
# TODO include specific page types somehow without writing it out
|
|
12
|
+
chapter.pages("$:not(.introduction)").each do |page|
|
|
13
|
+
summary = page.summary
|
|
14
|
+
summary.first("[data-type='title']").trash # get rid of old title
|
|
15
|
+
summary_title = page.title.copy
|
|
16
|
+
summary_title.name = "h3"
|
|
17
|
+
summary_title.replace_children(with: <<~HTML
|
|
18
|
+
<span class="os-number">#{chapter.count_in(:book)}.#{page.count_in(:chapter)}</span>
|
|
19
|
+
<span class="os-divider"> </span>
|
|
20
|
+
<span class="os-text" data-type="" itemprop="">#{summary_title.children}</span>
|
|
21
|
+
HTML
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
summary.prepend(child:
|
|
25
|
+
<<~HTML
|
|
26
|
+
<a href="##{page.title.id}">
|
|
27
|
+
#{summary_title.paste}
|
|
28
|
+
</a>
|
|
29
|
+
HTML
|
|
30
|
+
)
|
|
31
|
+
summary.cut(to: summaries)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
chapter.append(child:
|
|
35
|
+
<<~HTML
|
|
36
|
+
<div class="os-eoc os-summary-container" data-type="composite-page" data-uuid-key=".summary">
|
|
37
|
+
<h2 data-type="document-title">
|
|
38
|
+
<span class="os-text">#{I18n.t(:eoc_summary_title)}</span>
|
|
39
|
+
</h2>
|
|
40
|
+
<div data-type="metadata" style="display: none;">
|
|
41
|
+
<h1 data-type="document-title" itemprop="name">#{I18n.t(:eoc_summary_title)}</h1>
|
|
42
|
+
#{metadata_elements.paste}
|
|
43
|
+
</div>
|
|
44
|
+
#{summaries.paste}
|
|
45
|
+
</div>
|
|
46
|
+
HTML
|
|
47
|
+
) unless summaries.none?
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module Kitchen
|
|
2
|
+
module Directions
|
|
3
|
+
module BakeExample
|
|
4
|
+
|
|
5
|
+
def self.v1(example:, number:, title_tag:)
|
|
6
|
+
example.replace_children(with:
|
|
7
|
+
<<~HTML
|
|
8
|
+
<div class="body">
|
|
9
|
+
#{example.children}
|
|
10
|
+
</div>
|
|
11
|
+
HTML
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
example.prepend(child:
|
|
15
|
+
<<~HTML
|
|
16
|
+
<#{title_tag} class="os-title">
|
|
17
|
+
<span class="os-title-label">#{I18n.t(:example_label)} </span>
|
|
18
|
+
<span class="os-number">#{number}</span>
|
|
19
|
+
<span class="os-divider"> </span>
|
|
20
|
+
</#{title_tag}>
|
|
21
|
+
HTML
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
example.document.pantry(name: :link_text).store "#{I18n.t(:example_label)} #{number}", label: example.id
|
|
25
|
+
|
|
26
|
+
example.titles.each {|title| title.name = "h4"}
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
module Kitchen
|
|
2
|
+
module Directions
|
|
3
|
+
module BakeExercises
|
|
4
|
+
|
|
5
|
+
def self.v1(book:)
|
|
6
|
+
metadata_elements = book.metadata.search(%w(.authors .publishers .print-style
|
|
7
|
+
.permissions [data-type='subject'])).copy
|
|
8
|
+
|
|
9
|
+
solutions_clipboards = []
|
|
10
|
+
|
|
11
|
+
book.chapters.each do |chapter|
|
|
12
|
+
exercise_clipboard = Clipboard.new
|
|
13
|
+
solution_clipboard = Clipboard.new
|
|
14
|
+
solutions_clipboards.push(solution_clipboard)
|
|
15
|
+
|
|
16
|
+
chapter.pages("$:not(.introduction)").each do |page|
|
|
17
|
+
exercise_section = page.exercises
|
|
18
|
+
exercise_section.first("[data-type='title']")&.trash
|
|
19
|
+
exercise_section_title = page.title.copy
|
|
20
|
+
exercise_section_title.name = "h3"
|
|
21
|
+
exercise_section_title.replace_children(with: <<~HTML
|
|
22
|
+
<span class="os-number">#{chapter.count_in(:book)}.#{page.count_in(:chapter)}</span>
|
|
23
|
+
<span class="os-divider"> </span>
|
|
24
|
+
<span class="os-text" data-type="" itemprop="">#{exercise_section_title.children}</span>
|
|
25
|
+
HTML
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
exercise_section.prepend(child:
|
|
29
|
+
<<~HTML
|
|
30
|
+
<a href="##{page.title.id}">
|
|
31
|
+
#{exercise_section_title.paste}
|
|
32
|
+
</a>
|
|
33
|
+
HTML
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
exercise_section.search("[data-type='exercise']").each do |exercise|
|
|
37
|
+
exercise.document.pantry(name: :link_text).store(
|
|
38
|
+
"#{I18n.t(:exercise_label)} #{chapter.count_in(:book)}.#{exercise.count_in(:chapter)}",
|
|
39
|
+
label: exercise.id
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
bake_exercise_in_place(exercise: exercise)
|
|
43
|
+
exercise.first("[data-type='solution']")&.cut(to: solution_clipboard)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
exercise_section.cut(to: exercise_clipboard)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
chapter.append(child:
|
|
50
|
+
<<~HTML
|
|
51
|
+
<div class="os-eoc os-exercises-container" data-type="composite-page" data-uuid-key=".exercises">
|
|
52
|
+
<h2 data-type="document-title">
|
|
53
|
+
<span class="os-text">#{I18n.t(:eoc_exercises_title)}</span>
|
|
54
|
+
</h2>
|
|
55
|
+
<div data-type="metadata" style="display: none;">
|
|
56
|
+
<h1 data-type="document-title" itemprop="name">#{I18n.t(:eoc_exercises_title)}</h1>
|
|
57
|
+
#{metadata_elements.paste}
|
|
58
|
+
</div>
|
|
59
|
+
#{exercise_clipboard.paste}
|
|
60
|
+
</div>
|
|
61
|
+
HTML
|
|
62
|
+
) unless exercise_clipboard.none?
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Store a paste here to use at end so that uniquifyied IDs match legacy baking
|
|
66
|
+
eob_metadata = metadata_elements.paste
|
|
67
|
+
|
|
68
|
+
solutions = solutions_clipboards.map.with_index do |solution_clipboard, index|
|
|
69
|
+
<<~HTML
|
|
70
|
+
<div class="os-eob os-solution-container " data-type="composite-page" data-uuid-key=".solution#{index+1}">
|
|
71
|
+
<h2 data-type="document-title">
|
|
72
|
+
<span class="os-text">#{index+1}</span>
|
|
73
|
+
</h2>
|
|
74
|
+
<div data-type="metadata" style="display: none;">
|
|
75
|
+
<h1 data-type="document-title" itemprop="name">#{index+1}</h1>
|
|
76
|
+
#{metadata_elements.paste}
|
|
77
|
+
</div>
|
|
78
|
+
#{solution_clipboard.paste}
|
|
79
|
+
</div>
|
|
80
|
+
HTML
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
book.first("body").append(child:
|
|
84
|
+
<<~HTML
|
|
85
|
+
<div class="os-eob os-solution-container " data-type="composite-chapter" data-uuid-key=".solution">
|
|
86
|
+
<h1 data-type="document-title" id="composite-chapter-1">
|
|
87
|
+
<span class="os-text">#{I18n.t(:eoc_answer_key_title)}</span>
|
|
88
|
+
</h1>
|
|
89
|
+
<div data-type="metadata" style="display: none;">
|
|
90
|
+
<h1 data-type="document-title" itemprop="name">#{I18n.t(:eoc_answer_key_title)}</h1>
|
|
91
|
+
#{eob_metadata}
|
|
92
|
+
</div>
|
|
93
|
+
#{solutions.join("\n")}
|
|
94
|
+
</div>
|
|
95
|
+
HTML
|
|
96
|
+
) unless solutions.none?
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def self.bake_exercise_in_place(exercise:)
|
|
100
|
+
# Bake an exercise in place going from:
|
|
101
|
+
#
|
|
102
|
+
# <div data-type="exercise" id="exerciseId">
|
|
103
|
+
# <div data-type="problem" id="problemId">
|
|
104
|
+
# Problem Content
|
|
105
|
+
# </div>
|
|
106
|
+
# <div data-type="solution" id="solutionId">
|
|
107
|
+
# Solution Content
|
|
108
|
+
# </div>
|
|
109
|
+
# </div>
|
|
110
|
+
#
|
|
111
|
+
# to
|
|
112
|
+
#
|
|
113
|
+
# <div data-type="exercise" id="exerciseId" class="os-hasSolution">
|
|
114
|
+
# <div data-type="problem" id="problemId">
|
|
115
|
+
# <a class="os-number" href="#exerciseId-solution">1</a>
|
|
116
|
+
# <span class="os-divider">. </span>
|
|
117
|
+
# <div class="os-problem-container ">
|
|
118
|
+
# Problem Content
|
|
119
|
+
# </div>
|
|
120
|
+
# </div>
|
|
121
|
+
# <div data-type="solution" id="exerciseId-solution">
|
|
122
|
+
# <a class="os-number" href="#exerciseId">1</a>
|
|
123
|
+
# <span class="os-divider">. </span>
|
|
124
|
+
# <div class="os-solution-container ">
|
|
125
|
+
# Solution Content
|
|
126
|
+
# </div>
|
|
127
|
+
# </div>
|
|
128
|
+
# </div>
|
|
129
|
+
#
|
|
130
|
+
# If there is no solution, don't add the 'os-hasSolution' class and don't
|
|
131
|
+
# link the number.
|
|
132
|
+
|
|
133
|
+
problem = exercise.first("[data-type='problem']")
|
|
134
|
+
solution = exercise.first("[data-type='solution']")
|
|
135
|
+
|
|
136
|
+
problem_number = "<span class='os-number'>#{exercise.count_in(:chapter)}</span>"
|
|
137
|
+
|
|
138
|
+
if solution.present?
|
|
139
|
+
solution.id = "#{exercise.id}-solution"
|
|
140
|
+
|
|
141
|
+
exercise.add_class("os-hasSolution")
|
|
142
|
+
problem_number = "<a href='##{solution.id}' class='os-number' >#{exercise.count_in(:chapter)}</a>"
|
|
143
|
+
|
|
144
|
+
solution.replace_children(with:
|
|
145
|
+
<<~HTML
|
|
146
|
+
<a class="os-number" href="##{exercise.id}">#{exercise.count_in(:chapter)}</a>
|
|
147
|
+
<span class="os-divider">. </span>
|
|
148
|
+
<div class="os-solution-container ">#{solution.children}</div>
|
|
149
|
+
HTML
|
|
150
|
+
)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
problem.replace_children(with:
|
|
154
|
+
<<~HTML
|
|
155
|
+
#{problem_number}
|
|
156
|
+
<span class="os-divider">. </span>
|
|
157
|
+
<div class="os-problem-container ">#{problem.children}</div>
|
|
158
|
+
HTML
|
|
159
|
+
)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Kitchen
|
|
2
|
+
module Directions
|
|
3
|
+
module BakeFigure
|
|
4
|
+
|
|
5
|
+
def self.v1(figure:, number:)
|
|
6
|
+
figure.wrap(%Q(<div class="os-figure#{' has-splash' if figure.has_class?('splash')}">))
|
|
7
|
+
|
|
8
|
+
figure.document.pantry(name: :link_text).store "Figure #{number}", label: figure.id
|
|
9
|
+
|
|
10
|
+
caption = figure.caption&.cut
|
|
11
|
+
figure.append(sibling:
|
|
12
|
+
<<~HTML
|
|
13
|
+
<div class="os-caption-container">
|
|
14
|
+
<span class="os-title-label">Figure </span>
|
|
15
|
+
<span class="os-number">#{number}</span>
|
|
16
|
+
<span class="os-divider"> </span>
|
|
17
|
+
<span class="os-divider"> </span>
|
|
18
|
+
#{'<span class="os-caption">' + caption.children.to_s + '</span>'if caption}
|
|
19
|
+
</div>
|
|
20
|
+
HTML
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module Kitchen::Directions::BakeFootnotes
|
|
2
|
+
class V1
|
|
3
|
+
|
|
4
|
+
def bake(book:)
|
|
5
|
+
# Footnotes are numbered either within their top-level pages (preface,
|
|
6
|
+
# appendices, etc) or within chapters. Tackle each case separately
|
|
7
|
+
|
|
8
|
+
book.body.element_children.only(Kitchen::PageElement,
|
|
9
|
+
Kitchen::CompositePageElement).each do |page|
|
|
10
|
+
bake_footnotes_within(page)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
book.chapters.each do |chapter|
|
|
14
|
+
bake_footnotes_within(chapter)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def bake_footnotes_within(container)
|
|
19
|
+
footnote_number = 0
|
|
20
|
+
aside_id_to_footnote_number = {}
|
|
21
|
+
|
|
22
|
+
container.search("a[role='doc-noteref']").each do |anchor|
|
|
23
|
+
footnote_number += 1
|
|
24
|
+
anchor.replace_children(with: footnote_number.to_s)
|
|
25
|
+
aside_id = anchor[:href][1..-1]
|
|
26
|
+
aside_id_to_footnote_number[aside_id] = footnote_number
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
container.search("aside").each do |aside|
|
|
31
|
+
footnote_number = aside_id_to_footnote_number[aside.id]
|
|
32
|
+
aside.prepend(child: "<div class='footnote-number'>#{footnote_number}</div>")
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
module Kitchen::Directions::BakeIndex
|
|
2
|
+
class V1
|
|
3
|
+
renderable
|
|
4
|
+
|
|
5
|
+
class Term
|
|
6
|
+
attr_reader :text, :id, :group_by, :page_title
|
|
7
|
+
|
|
8
|
+
def initialize(text:, id:, group_by:, page_title:)
|
|
9
|
+
@text = text.strip
|
|
10
|
+
@id = id
|
|
11
|
+
@group_by = group_by
|
|
12
|
+
@page_title = page_title
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class IndexItem
|
|
17
|
+
attr_reader :term_text
|
|
18
|
+
attr_reader :terms
|
|
19
|
+
|
|
20
|
+
def initialize(term_text:)
|
|
21
|
+
@term_text = term_text
|
|
22
|
+
@terms = []
|
|
23
|
+
|
|
24
|
+
# Sort by transliterated version first to support accent marks,
|
|
25
|
+
# then by the raw text to support the same text with different capitalization
|
|
26
|
+
@sortable = [
|
|
27
|
+
ActiveSupport::Inflector.transliterate(term_text).downcase,
|
|
28
|
+
term_text
|
|
29
|
+
]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def add_term(term)
|
|
33
|
+
@terms.push(term)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def uncapitalize_term_text!
|
|
37
|
+
@term_text = @term_text.uncapitalize
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def <=>(other)
|
|
41
|
+
self.sortable <=> other.sortable
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
protected
|
|
45
|
+
|
|
46
|
+
attr_reader :sortable
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
class IndexSection
|
|
50
|
+
attr_reader :name
|
|
51
|
+
attr_reader :items
|
|
52
|
+
|
|
53
|
+
def initialize(name:)
|
|
54
|
+
@force_first = name == I18n.t(:eob_index_symbols_group)
|
|
55
|
+
@name = name
|
|
56
|
+
@items = SortedSet.new
|
|
57
|
+
@items_by_term_text = {}
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def add_term(term)
|
|
61
|
+
item_for(term).add_term(term)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def <=>(other)
|
|
65
|
+
return -1 if force_first
|
|
66
|
+
return 1 if other.force_first
|
|
67
|
+
self.name <=> other.name
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
protected
|
|
71
|
+
|
|
72
|
+
attr_reader :force_first
|
|
73
|
+
|
|
74
|
+
def item_for(term)
|
|
75
|
+
@items_by_term_text[term.text] ||= begin
|
|
76
|
+
different_caps_item = @items_by_term_text[term.text.uncapitalize]
|
|
77
|
+
different_caps_item&.uncapitalize_term_text!
|
|
78
|
+
|
|
79
|
+
(different_caps_item || IndexItem.new(term_text: term.text)).tap do |item|
|
|
80
|
+
@items.add(item)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
class Index
|
|
87
|
+
attr_reader :sections
|
|
88
|
+
|
|
89
|
+
def initialize
|
|
90
|
+
@sections = SortedSet.new
|
|
91
|
+
@sections_by_name = {}
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def add_term(term)
|
|
95
|
+
section_named(term.group_by.capitalize).add_term(term)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
protected
|
|
99
|
+
|
|
100
|
+
def section_named(name)
|
|
101
|
+
@sections_by_name[name] ||= begin
|
|
102
|
+
IndexSection.new(name: name).tap do |section|
|
|
103
|
+
@sections.add(section)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def bake(book:)
|
|
110
|
+
@metadata_elements = book.metadata.search(%w(.authors .publishers .print-style
|
|
111
|
+
.permissions [data-type='subject'])).copy
|
|
112
|
+
@index = Index.new
|
|
113
|
+
|
|
114
|
+
book.pages.terms.each do |term_element|
|
|
115
|
+
# Markup the term
|
|
116
|
+
page = term_element.ancestor(:page)
|
|
117
|
+
term_element.id = "auto_#{page.id}_term#{term_element.count_in(:book)}"
|
|
118
|
+
|
|
119
|
+
group_by = term_element.text.strip[0]
|
|
120
|
+
if !group_by.match? (/\w/)
|
|
121
|
+
group_by = I18n.t(:eob_index_symbols_group)
|
|
122
|
+
end
|
|
123
|
+
term_element['group-by'] = group_by
|
|
124
|
+
|
|
125
|
+
# Add it to our index object
|
|
126
|
+
@index.add_term(Term.new(
|
|
127
|
+
text: term_element.text,
|
|
128
|
+
id: term_element.id,
|
|
129
|
+
group_by: group_by,
|
|
130
|
+
page_title: page.title.text.gsub(/\n/,'')
|
|
131
|
+
))
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
book.first("body").append(child: render(file: 'v1.xhtml.erb'))
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
end
|
|
138
|
+
end
|