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,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
|