docbook 0.1.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/CHANGELOG.md +5 -0
- data/CLAUDE.md +19 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/README.adoc +335 -0
- data/Rakefile +12 -0
- data/docs/.lycheeignore +33 -0
- data/docs/Gemfile +10 -0
- data/docs/INDEX.adoc +67 -0
- data/docs/_config.yml +186 -0
- data/docs/advanced/element-classes.adoc +185 -0
- data/docs/advanced/frontend-customization.adoc +193 -0
- data/docs/advanced/index.adoc +14 -0
- data/docs/advanced/templates.adoc +123 -0
- data/docs/features/element-coverage.adoc +373 -0
- data/docs/features/html-output/data-model.adoc +285 -0
- data/docs/features/html-output/directory-mode.adoc +180 -0
- data/docs/features/html-output/index.adoc +90 -0
- data/docs/features/html-output/single-file-mode.adoc +125 -0
- data/docs/features/index-generation.adoc +197 -0
- data/docs/features/index.adoc +63 -0
- data/docs/features/numbering.adoc +183 -0
- data/docs/features/toc-generation.adoc +150 -0
- data/docs/features/xinclude/fragid-schemes.adoc +287 -0
- data/docs/features/xinclude/index.adoc +119 -0
- data/docs/features/xinclude/text-includes.adoc +123 -0
- data/docs/features/xinclude/xml-includes.adoc +167 -0
- data/docs/getting-started/index.adoc +50 -0
- data/docs/getting-started/installation.adoc +113 -0
- data/docs/getting-started/quick-start.adoc +161 -0
- data/docs/guides/converting-article.adoc +188 -0
- data/docs/guides/converting-book.adoc +192 -0
- data/docs/guides/index.adoc +12 -0
- data/docs/guides/roundtrip-testing.adoc +129 -0
- data/docs/interfaces/cli/format-command.adoc +109 -0
- data/docs/interfaces/cli/index.adoc +73 -0
- data/docs/interfaces/cli/roundtrip-command.adoc +125 -0
- data/docs/interfaces/cli/to-html-command.adoc +178 -0
- data/docs/interfaces/cli/validate-command.adoc +104 -0
- data/docs/interfaces/index.adoc +101 -0
- data/docs/interfaces/ruby-api/html-output.adoc +186 -0
- data/docs/interfaces/ruby-api/index.adoc +111 -0
- data/docs/interfaces/ruby-api/parsing.adoc +202 -0
- data/docs/interfaces/ruby-api/xinclude.adoc +162 -0
- data/docs/interfaces/ruby-api/xref-resolution.adoc +156 -0
- data/docs/lychee.toml +42 -0
- data/docs/reference/cli-options.adoc +155 -0
- data/docs/reference/content-block-types.adoc +243 -0
- data/docs/reference/glossary.adoc +119 -0
- data/docs/reference/index.adoc +12 -0
- data/docs/reference/supported-elements.adoc +749 -0
- data/docs/understanding/architecture.adoc +145 -0
- data/docs/understanding/content-pipeline.adoc +102 -0
- data/docs/understanding/data-models.adoc +156 -0
- data/docs/understanding/index.adoc +34 -0
- data/exe/docbook +7 -0
- data/frontend/dist/app.css +1 -0
- data/frontend/dist/app.iife.js +24 -0
- data/frontend/package-lock.json +1445 -0
- data/frontend/package.json +22 -0
- data/frontend/src/App.vue +230 -0
- data/frontend/src/app.ts +8 -0
- data/frontend/src/components/AppSidebar.vue +116 -0
- data/frontend/src/components/AppendixSection.vue +39 -0
- data/frontend/src/components/BlockRenderer.vue +358 -0
- data/frontend/src/components/ChapterSection.vue +32 -0
- data/frontend/src/components/ContentRenderer.vue +13 -0
- data/frontend/src/components/EbookContainer.vue +147 -0
- data/frontend/src/components/EbookTopBar.vue +116 -0
- data/frontend/src/components/PartSection.vue +44 -0
- data/frontend/src/components/ReferenceEntry.vue +80 -0
- data/frontend/src/components/SearchModal.vue +286 -0
- data/frontend/src/components/SectionContent.vue +31 -0
- data/frontend/src/components/SettingsPanel.vue +236 -0
- data/frontend/src/components/TocTreeItem.vue +135 -0
- data/frontend/src/composables/useEbookStore.ts +191 -0
- data/frontend/src/composables/useSearch.ts +249 -0
- data/frontend/src/env.d.ts +7 -0
- data/frontend/src/stores/documentStore.ts +221 -0
- data/frontend/src/stores/uiStore.ts +98 -0
- data/frontend/src/styles.css +253 -0
- data/frontend/tsconfig.json +24 -0
- data/frontend/tsconfig.node.json +11 -0
- data/frontend/vite.config.ts +30 -0
- data/lib/docbook/cli.rb +123 -0
- data/lib/docbook/document.rb +67 -0
- data/lib/docbook/elements/abbrev.rb +16 -0
- data/lib/docbook/elements/acknowledgements.rb +22 -0
- data/lib/docbook/elements/address.rb +18 -0
- data/lib/docbook/elements/alt.rb +16 -0
- data/lib/docbook/elements/annotation.rb +18 -0
- data/lib/docbook/elements/appendix.rb +34 -0
- data/lib/docbook/elements/article.rb +31 -0
- data/lib/docbook/elements/att.rb +15 -0
- data/lib/docbook/elements/attribution.rb +20 -0
- data/lib/docbook/elements/audioobject.rb +18 -0
- data/lib/docbook/elements/author.rb +18 -0
- data/lib/docbook/elements/bibliography.rb +24 -0
- data/lib/docbook/elements/bibliomixed.rb +40 -0
- data/lib/docbook/elements/biblioref.rb +20 -0
- data/lib/docbook/elements/blockquote.rb +26 -0
- data/lib/docbook/elements/book.rb +36 -0
- data/lib/docbook/elements/buildtarget.rb +16 -0
- data/lib/docbook/elements/callout.rb +22 -0
- data/lib/docbook/elements/calloutlist.rb +22 -0
- data/lib/docbook/elements/caution.rb +30 -0
- data/lib/docbook/elements/chapter.rb +62 -0
- data/lib/docbook/elements/citation.rb +16 -0
- data/lib/docbook/elements/citerefentry.rb +26 -0
- data/lib/docbook/elements/citetitle.rb +20 -0
- data/lib/docbook/elements/classname.rb +16 -0
- data/lib/docbook/elements/code.rb +16 -0
- data/lib/docbook/elements/colophon.rb +22 -0
- data/lib/docbook/elements/computeroutput.rb +18 -0
- data/lib/docbook/elements/copyright.rb +17 -0
- data/lib/docbook/elements/danger.rb +28 -0
- data/lib/docbook/elements/date.rb +16 -0
- data/lib/docbook/elements/dedication.rb +22 -0
- data/lib/docbook/elements/dir.rb +16 -0
- data/lib/docbook/elements/emphasis.rb +18 -0
- data/lib/docbook/elements/entry.rb +30 -0
- data/lib/docbook/elements/entrytbl.rb +22 -0
- data/lib/docbook/elements/equation.rb +26 -0
- data/lib/docbook/elements/example.rb +30 -0
- data/lib/docbook/elements/fieldsynopsis.rb +21 -0
- data/lib/docbook/elements/figure.rb +28 -0
- data/lib/docbook/elements/filename.rb +16 -0
- data/lib/docbook/elements/firstname.rb +16 -0
- data/lib/docbook/elements/firstterm.rb +18 -0
- data/lib/docbook/elements/footnote.rb +22 -0
- data/lib/docbook/elements/footnoteref.rb +21 -0
- data/lib/docbook/elements/foreignphrase.rb +18 -0
- data/lib/docbook/elements/formalpara.rb +20 -0
- data/lib/docbook/elements/function.rb +16 -0
- data/lib/docbook/elements/glossary.rb +24 -0
- data/lib/docbook/elements/glossdef.rb +18 -0
- data/lib/docbook/elements/glossentry.rb +26 -0
- data/lib/docbook/elements/glosssee.rb +18 -0
- data/lib/docbook/elements/glossseealso.rb +18 -0
- data/lib/docbook/elements/glossterm.rb +18 -0
- data/lib/docbook/elements/holder.rb +16 -0
- data/lib/docbook/elements/honorific.rb +16 -0
- data/lib/docbook/elements/imagedata.rb +29 -0
- data/lib/docbook/elements/imageobject.rb +22 -0
- data/lib/docbook/elements/important.rb +30 -0
- data/lib/docbook/elements/index.rb +26 -0
- data/lib/docbook/elements/indexdiv.rb +22 -0
- data/lib/docbook/elements/indexentry.rb +22 -0
- data/lib/docbook/elements/indexterm.rb +34 -0
- data/lib/docbook/elements/info.rb +25 -0
- data/lib/docbook/elements/informalexample.rb +24 -0
- data/lib/docbook/elements/informalfigure.rb +20 -0
- data/lib/docbook/elements/inlinemediaobject.rb +22 -0
- data/lib/docbook/elements/itemizedlist.rb +21 -0
- data/lib/docbook/elements/legalnotice.rb +22 -0
- data/lib/docbook/elements/link.rb +26 -0
- data/lib/docbook/elements/listitem.rb +32 -0
- data/lib/docbook/elements/literal.rb +16 -0
- data/lib/docbook/elements/literallayout.rb +22 -0
- data/lib/docbook/elements/mediaobject.rb +26 -0
- data/lib/docbook/elements/msg.rb +20 -0
- data/lib/docbook/elements/msgexplan.rb +22 -0
- data/lib/docbook/elements/msgset.rb +22 -0
- data/lib/docbook/elements/note.rb +30 -0
- data/lib/docbook/elements/orderedlist.rb +23 -0
- data/lib/docbook/elements/orgname.rb +16 -0
- data/lib/docbook/elements/para.rb +61 -0
- data/lib/docbook/elements/paragraph_like.rb +18 -0
- data/lib/docbook/elements/parameter.rb +16 -0
- data/lib/docbook/elements/part.rb +38 -0
- data/lib/docbook/elements/personname.rb +22 -0
- data/lib/docbook/elements/phrase.rb +20 -0
- data/lib/docbook/elements/preface.rb +58 -0
- data/lib/docbook/elements/primary.rb +16 -0
- data/lib/docbook/elements/procedure.rb +24 -0
- data/lib/docbook/elements/productname.rb +18 -0
- data/lib/docbook/elements/productnumber.rb +16 -0
- data/lib/docbook/elements/programlisting.rb +28 -0
- data/lib/docbook/elements/property.rb +16 -0
- data/lib/docbook/elements/pubdate.rb +16 -0
- data/lib/docbook/elements/publishername.rb +16 -0
- data/lib/docbook/elements/quotation.rb +24 -0
- data/lib/docbook/elements/quote.rb +18 -0
- data/lib/docbook/elements/refentry.rb +32 -0
- data/lib/docbook/elements/refentrytitle.rb +16 -0
- data/lib/docbook/elements/reference.rb +26 -0
- data/lib/docbook/elements/refmeta.rb +29 -0
- data/lib/docbook/elements/refmiscinfo.rb +16 -0
- data/lib/docbook/elements/refname.rb +16 -0
- data/lib/docbook/elements/refnamediv.rb +22 -0
- data/lib/docbook/elements/refpurpose.rb +16 -0
- data/lib/docbook/elements/refsect1.rb +22 -0
- data/lib/docbook/elements/refsect2.rb +22 -0
- data/lib/docbook/elements/refsect3.rb +22 -0
- data/lib/docbook/elements/refsection.rb +32 -0
- data/lib/docbook/elements/releaseinfo.rb +16 -0
- data/lib/docbook/elements/remark.rb +20 -0
- data/lib/docbook/elements/replaceable.rb +16 -0
- data/lib/docbook/elements/row.rb +15 -0
- data/lib/docbook/elements/screen.rb +28 -0
- data/lib/docbook/elements/secondary.rb +16 -0
- data/lib/docbook/elements/sect1.rb +26 -0
- data/lib/docbook/elements/sect2.rb +24 -0
- data/lib/docbook/elements/sect3.rb +24 -0
- data/lib/docbook/elements/sect4.rb +24 -0
- data/lib/docbook/elements/sect5.rb +22 -0
- data/lib/docbook/elements/section.rb +66 -0
- data/lib/docbook/elements/see.rb +16 -0
- data/lib/docbook/elements/seealso.rb +18 -0
- data/lib/docbook/elements/set.rb +26 -0
- data/lib/docbook/elements/setindex.rb +24 -0
- data/lib/docbook/elements/sidebar.rb +22 -0
- data/lib/docbook/elements/simplesect.rb +32 -0
- data/lib/docbook/elements/step.rb +26 -0
- data/lib/docbook/elements/substeps.rb +18 -0
- data/lib/docbook/elements/subtitle.rb +16 -0
- data/lib/docbook/elements/surname.rb +16 -0
- data/lib/docbook/elements/table.rb +24 -0
- data/lib/docbook/elements/tag.rb +20 -0
- data/lib/docbook/elements/tbody.rb +15 -0
- data/lib/docbook/elements/term.rb +37 -0
- data/lib/docbook/elements/tertiary.rb +16 -0
- data/lib/docbook/elements/textobject.rb +18 -0
- data/lib/docbook/elements/tfoot.rb +15 -0
- data/lib/docbook/elements/tgroup.rb +21 -0
- data/lib/docbook/elements/thead.rb +15 -0
- data/lib/docbook/elements/tip.rb +30 -0
- data/lib/docbook/elements/title.rb +16 -0
- data/lib/docbook/elements/toc.rb +24 -0
- data/lib/docbook/elements/tocdiv.rb +22 -0
- data/lib/docbook/elements/tocentry.rb +20 -0
- data/lib/docbook/elements/topic.rb +26 -0
- data/lib/docbook/elements/type.rb +16 -0
- data/lib/docbook/elements/uri.rb +16 -0
- data/lib/docbook/elements/userinput.rb +18 -0
- data/lib/docbook/elements/variable.rb +16 -0
- data/lib/docbook/elements/variablelist.rb +19 -0
- data/lib/docbook/elements/varlistentry.rb +17 -0
- data/lib/docbook/elements/version.rb +16 -0
- data/lib/docbook/elements/videoobject.rb +18 -0
- data/lib/docbook/elements/warning.rb +30 -0
- data/lib/docbook/elements/xref.rb +18 -0
- data/lib/docbook/elements/year.rb +16 -0
- data/lib/docbook/elements.rb +204 -0
- data/lib/docbook/models/document_metadata.rb +30 -0
- data/lib/docbook/models/document_root.rb +33 -0
- data/lib/docbook/models/index_entry.rb +28 -0
- data/lib/docbook/models/reading_position.rb +22 -0
- data/lib/docbook/models/section_number.rb +18 -0
- data/lib/docbook/models/section_root.rb +29 -0
- data/lib/docbook/models/toc_node.rb +24 -0
- data/lib/docbook/models.rb +16 -0
- data/lib/docbook/output/data.rb +196 -0
- data/lib/docbook/output/html.rb +1450 -0
- data/lib/docbook/output/index_generator.rb +281 -0
- data/lib/docbook/output.rb +8 -0
- data/lib/docbook/services/document_builder.rb +258 -0
- data/lib/docbook/services/element_to_hash.rb +287 -0
- data/lib/docbook/services/index_generator.rb +134 -0
- data/lib/docbook/services/numbering_service.rb +186 -0
- data/lib/docbook/services/toc_generator.rb +138 -0
- data/lib/docbook/services.rb +14 -0
- data/lib/docbook/templates/document.html.liquid +20 -0
- data/lib/docbook/templates/partials/_head.liquid +39 -0
- data/lib/docbook/templates/partials/_scripts.liquid +10 -0
- data/lib/docbook/version.rb +5 -0
- data/lib/docbook/xinclude_resolver.rb +217 -0
- data/lib/docbook/xref_resolver.rb +78 -0
- data/lib/docbook.rb +17 -0
- data/sig/docbook.rbs +4 -0
- metadata +385 -0
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docbook
|
|
4
|
+
module Output
|
|
5
|
+
# Collects all indexterms from a document for index generation
|
|
6
|
+
class IndexCollector
|
|
7
|
+
def initialize(document)
|
|
8
|
+
@document = document
|
|
9
|
+
@index_terms = []
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Collect all indexterms from the document
|
|
13
|
+
def collect
|
|
14
|
+
@index_terms = []
|
|
15
|
+
traverse_element(@document)
|
|
16
|
+
@index_terms
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Get indexterms grouped by type
|
|
20
|
+
def by_type
|
|
21
|
+
result = {}
|
|
22
|
+
@index_terms.each do |term|
|
|
23
|
+
type = term[:type] || "default"
|
|
24
|
+
result[type] ||= []
|
|
25
|
+
result[type] << term
|
|
26
|
+
end
|
|
27
|
+
result
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def traverse_element(element, section_info = {})
|
|
33
|
+
return unless element
|
|
34
|
+
|
|
35
|
+
# Collect section info for linking based on element type
|
|
36
|
+
section_info = update_section_info(element, section_info)
|
|
37
|
+
|
|
38
|
+
# Check for indexterm on this element
|
|
39
|
+
each_attr(element, :indexterm) do |it|
|
|
40
|
+
process_indexterm(it, section_info)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Traverse children based on element type
|
|
44
|
+
case element
|
|
45
|
+
when Elements::Book
|
|
46
|
+
traverse_book(element, section_info)
|
|
47
|
+
when Elements::Part
|
|
48
|
+
traverse_part(element, section_info)
|
|
49
|
+
when Elements::Article
|
|
50
|
+
traverse_article(element, section_info)
|
|
51
|
+
when Elements::Chapter
|
|
52
|
+
traverse_chapter(element, section_info)
|
|
53
|
+
when Elements::Appendix
|
|
54
|
+
traverse_appendix(element, section_info)
|
|
55
|
+
when Elements::Section
|
|
56
|
+
traverse_section(element, section_info)
|
|
57
|
+
when Elements::Simplesect
|
|
58
|
+
traverse_simplesect(element, section_info)
|
|
59
|
+
when Elements::RefEntry
|
|
60
|
+
traverse_refentry(element, section_info)
|
|
61
|
+
when Elements::RefSection
|
|
62
|
+
traverse_refsection(element, section_info)
|
|
63
|
+
when Elements::Para
|
|
64
|
+
# Para children are handled via mixed content traversal
|
|
65
|
+
traverse_mixed_content(element, section_info)
|
|
66
|
+
when Elements::ListItem
|
|
67
|
+
traverse_listitem(element, section_info)
|
|
68
|
+
when Elements::Entry
|
|
69
|
+
traverse_entry(element, section_info)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def update_section_info(element, current_info)
|
|
74
|
+
info = current_info.dup
|
|
75
|
+
if element.xml_id
|
|
76
|
+
info[:id] = element.xml_id
|
|
77
|
+
info[:title] = element_title(element)
|
|
78
|
+
elsif element.respond_to?(:title) && element.title
|
|
79
|
+
info[:title] = element.title.content.to_s
|
|
80
|
+
end
|
|
81
|
+
info
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def traverse_book(book, section_info)
|
|
85
|
+
each_attr(book, :part) { |p| traverse_element(p, section_info) }
|
|
86
|
+
each_attr(book, :chapter) { |c| traverse_element(c, section_info) }
|
|
87
|
+
each_attr(book, :appendix) { |a| traverse_element(a, section_info) }
|
|
88
|
+
each_attr(book, :preface) { |p| traverse_element(p, section_info) }
|
|
89
|
+
each_attr(book, :glossary) { |g| traverse_element(g, section_info) }
|
|
90
|
+
each_attr(book, :bibliography) { |b| traverse_element(b, section_info) }
|
|
91
|
+
each_attr(book, :index) { |i| traverse_element(i, section_info) }
|
|
92
|
+
each_attr(book, :setindex) { |si| traverse_element(si, section_info) }
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def traverse_part(part, section_info)
|
|
96
|
+
each_attr(part, :part) { |p| traverse_element(p, section_info) }
|
|
97
|
+
each_attr(part, :chapter) { |c| traverse_element(c, section_info) }
|
|
98
|
+
each_attr(part, :appendix) { |a| traverse_element(a, section_info) }
|
|
99
|
+
each_attr(part, :reference) { |r| traverse_element(r, section_info) }
|
|
100
|
+
each_attr(part, :preface) { |p| traverse_element(p, section_info) }
|
|
101
|
+
each_attr(part, :glossary) { |g| traverse_element(g, section_info) }
|
|
102
|
+
each_attr(part, :bibliography) { |b| traverse_element(b, section_info) }
|
|
103
|
+
each_attr(part, :index) { |i| traverse_element(i, section_info) }
|
|
104
|
+
each_attr(part, :setindex) { |si| traverse_element(si, section_info) }
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def traverse_article(article, section_info)
|
|
108
|
+
each_attr(article, :section) { |s| traverse_element(s, section_info) }
|
|
109
|
+
each_attr(article, :glossary) { |g| traverse_element(g, section_info) }
|
|
110
|
+
each_attr(article, :bibliography) { |b| traverse_element(b, section_info) }
|
|
111
|
+
each_attr(article, :index) { |i| traverse_element(i, section_info) }
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def traverse_chapter(chapter, section_info)
|
|
115
|
+
each_attr(chapter, :section) { |s| traverse_element(s, section_info) }
|
|
116
|
+
each_attr(chapter, :simplesect) { |ss| traverse_element(ss, section_info) }
|
|
117
|
+
each_attr(chapter, :para) { |p| traverse_element(p, section_info) }
|
|
118
|
+
each_attr(chapter, :indexterm) { |it| process_indexterm(it, section_info) }
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def traverse_appendix(appendix, section_info)
|
|
122
|
+
each_attr(appendix, :section) { |s| traverse_element(s, section_info) }
|
|
123
|
+
each_attr(appendix, :simplesect) { |ss| traverse_element(ss, section_info) }
|
|
124
|
+
each_attr(appendix, :para) { |p| traverse_element(p, section_info) }
|
|
125
|
+
each_attr(appendix, :indexterm) { |it| process_indexterm(it, section_info) }
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def traverse_section(section, section_info)
|
|
129
|
+
each_attr(section, :section) { |s| traverse_element(s, section_info) }
|
|
130
|
+
each_attr(section, :simplesect) { |ss| traverse_element(ss, section_info) }
|
|
131
|
+
each_attr(section, :para) { |p| traverse_element(p, section_info) }
|
|
132
|
+
each_attr(section, :indexterm) { |it| process_indexterm(it, section_info) }
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def traverse_simplesect(simplesect, section_info)
|
|
136
|
+
each_attr(simplesect, :para) { |p| traverse_element(p, section_info) }
|
|
137
|
+
each_attr(simplesect, :indexterm) { |it| process_indexterm(it, section_info) }
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def traverse_refentry(refentry, section_info)
|
|
141
|
+
each_attr(refentry, :refsection) { |rs| traverse_element(rs, section_info) }
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def traverse_refsection(refsection, section_info)
|
|
145
|
+
new_info = update_section_info(refsection, section_info)
|
|
146
|
+
each_attr(refsection, :para) { |p| traverse_element(p, new_info) }
|
|
147
|
+
each_attr(refsection, :indexterm) { |it| process_indexterm(it, new_info) }
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def traverse_listitem(listitem, section_info)
|
|
151
|
+
each_attr(listitem, :para) { |p| traverse_element(p, section_info) }
|
|
152
|
+
each_attr(listitem, :simplesect) { |ss| traverse_element(ss, section_info) }
|
|
153
|
+
each_attr(listitem, :indexterm) { |it| process_indexterm(it, section_info) }
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def traverse_entry(entry, section_info)
|
|
157
|
+
each_attr(entry, :para) { |p| traverse_element(p, section_info) }
|
|
158
|
+
each_attr(entry, :indexterm) { |it| process_indexterm(it, section_info) }
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def traverse_mixed_content(element, section_info)
|
|
162
|
+
return unless element.respond_to?(:each_mixed_content)
|
|
163
|
+
|
|
164
|
+
element.each_mixed_content do |node|
|
|
165
|
+
case node
|
|
166
|
+
when Elements::IndexTerm
|
|
167
|
+
process_indexterm(node, section_info)
|
|
168
|
+
when Elements::Para
|
|
169
|
+
traverse_element(node, section_info)
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def process_indexterm(indexterm, section_info)
|
|
175
|
+
# Skip if no primary (required for meaningful index entry)
|
|
176
|
+
primaries = Array(indexterm.primary).map { |p| p.content.to_s.strip }.compact
|
|
177
|
+
return if primaries.empty?
|
|
178
|
+
|
|
179
|
+
primary_text = primaries.first
|
|
180
|
+
secondaries = Array(indexterm.secondary).map { |s| s.content.to_s.strip }.compact
|
|
181
|
+
tertiaries = Array(indexterm.tertiary).map { |t| t.content.to_s.strip }.compact
|
|
182
|
+
|
|
183
|
+
sees = Array(indexterm.see).map { |s| s.content.to_s.strip }.compact
|
|
184
|
+
see_alsos = Array(indexterm.see_also).map { |sa| sa.content.to_s.strip }.compact
|
|
185
|
+
|
|
186
|
+
term_info = {
|
|
187
|
+
primary: primary_text,
|
|
188
|
+
primary_sort: sort_key(primary_text, indexterm),
|
|
189
|
+
secondary: secondaries.first,
|
|
190
|
+
tertiary: tertiaries.first,
|
|
191
|
+
sees: sees,
|
|
192
|
+
see_alsos: see_alsos,
|
|
193
|
+
type: indexterm.type,
|
|
194
|
+
zone: indexterm.zone,
|
|
195
|
+
xml_id: indexterm.xml_id,
|
|
196
|
+
class_value: indexterm.class_value,
|
|
197
|
+
section_id: section_info[:id],
|
|
198
|
+
section_title: section_info[:title],
|
|
199
|
+
section_info: section_info
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
@index_terms << term_info
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def element_title(element)
|
|
206
|
+
case element
|
|
207
|
+
when Elements::Book, Elements::Article
|
|
208
|
+
element.info&.title&.content
|
|
209
|
+
when Elements::Chapter, Elements::Appendix, Elements::Section,
|
|
210
|
+
Elements::Part, Elements::Preface
|
|
211
|
+
element.info&.title&.content || element.title&.content
|
|
212
|
+
when Elements::RefEntry
|
|
213
|
+
element.refnamediv&.refname&.first&.content
|
|
214
|
+
else
|
|
215
|
+
element.title&.content
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Generate sort key for an index entry
|
|
220
|
+
def sort_key(text, indexterm)
|
|
221
|
+
return "SYMBOLS" if indexterm.class_value == "token" || text.start_with?("@")
|
|
222
|
+
|
|
223
|
+
# Remove leading punctuation for sorting
|
|
224
|
+
text.gsub(/^[^a-zA-Z]+/, "").downcase
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# Helper to safely call attributes - same pattern as Html class
|
|
228
|
+
def each_attr(obj, name)
|
|
229
|
+
val = obj.send(name)
|
|
230
|
+
return unless val
|
|
231
|
+
Array(val).each { |item| yield item }
|
|
232
|
+
rescue NoMethodError
|
|
233
|
+
nil
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# Generates index content from collected indexterms
|
|
238
|
+
class IndexGenerator
|
|
239
|
+
def initialize(index_terms, xref_resolver = nil)
|
|
240
|
+
@index_terms = index_terms
|
|
241
|
+
@xref_resolver = xref_resolver
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# Generate index data structure grouped by letter
|
|
245
|
+
def generate
|
|
246
|
+
by_letter = group_by_letter
|
|
247
|
+
by_letter.sort_by { |letter, _| letter == "SYMBOLS" ? "{" : letter.downcase }
|
|
248
|
+
.to_h
|
|
249
|
+
.map do |letter, terms|
|
|
250
|
+
{
|
|
251
|
+
letter: letter,
|
|
252
|
+
entries: sort_entries(terms)
|
|
253
|
+
}
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
private
|
|
258
|
+
|
|
259
|
+
def group_by_letter
|
|
260
|
+
result = {}
|
|
261
|
+
@index_terms.each do |term|
|
|
262
|
+
letter = letter_for(term[:primary_sort])
|
|
263
|
+
result[letter] ||= []
|
|
264
|
+
result[letter] << term
|
|
265
|
+
end
|
|
266
|
+
result
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def letter_for(sort_key)
|
|
270
|
+
return "SYMBOLS" if sort_key == "SYMBOLS" || sort_key.start_with?("@")
|
|
271
|
+
|
|
272
|
+
first_char = sort_key.chars.find(&:letter?) || sort_key[0] || ""
|
|
273
|
+
first_char.upcase
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def sort_entries(terms)
|
|
277
|
+
terms.sort_by { |t| [t[:primary_sort], t[:secondary].to_s] }
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
end
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docbook
|
|
4
|
+
module Services
|
|
5
|
+
# Builds the complete DocumentRoot from a parsed Docbook document
|
|
6
|
+
class DocumentBuilder
|
|
7
|
+
def initialize(document, options = {})
|
|
8
|
+
@document = document
|
|
9
|
+
@options = options
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def build
|
|
13
|
+
doc_root = Models::DocumentRoot.new(
|
|
14
|
+
title: extract_title,
|
|
15
|
+
metadata: build_metadata,
|
|
16
|
+
toc: generate_toc,
|
|
17
|
+
sections: build_sections,
|
|
18
|
+
index: generate_index,
|
|
19
|
+
numbering: generate_numbering,
|
|
20
|
+
generator: "docbook-gem/#{Docbook::VERSION}",
|
|
21
|
+
generated_at: Time.now.utc.iso8601
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def extract_title
|
|
28
|
+
@document.respond_to?(:title) && @document.title&.content ||
|
|
29
|
+
@document.respond_to?(:info) && @document.info&.title&.content ||
|
|
30
|
+
"Untitled Document"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def build_metadata
|
|
34
|
+
metadata = Models::DocumentMetadata.new
|
|
35
|
+
|
|
36
|
+
if @document.respond_to?(:info)
|
|
37
|
+
info = @document.info
|
|
38
|
+
metadata.author = extract_author(info)
|
|
39
|
+
metadata.title = info.title&.content if info.respond_to?(:title)
|
|
40
|
+
metadata.subtitle = info.subtitle&.content if info.respond_to?(:subtitle)
|
|
41
|
+
metadata.productname = info.productname&.content if info.respond_to?(:productname)
|
|
42
|
+
metadata.pubdate = info.date&.content || info.pubdate&.content if info.respond_to?(:date) || info.respond_to?(:pubdate)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
metadata
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def extract_author(info)
|
|
49
|
+
return nil unless info.respond_to?(:author)
|
|
50
|
+
|
|
51
|
+
author = info.author
|
|
52
|
+
return nil unless author
|
|
53
|
+
|
|
54
|
+
if author.respond_to?(:personname)
|
|
55
|
+
author.personname&.content ||
|
|
56
|
+
[author.personname&.firstname&.content, author.personname&.surname&.content].compact.join(" ")
|
|
57
|
+
elsif author.respond_to?(:orgname)
|
|
58
|
+
author.orgname&.content
|
|
59
|
+
else
|
|
60
|
+
author.content
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def generate_toc
|
|
65
|
+
TocGenerator.new(@document).generate
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def generate_index
|
|
69
|
+
IndexGenerator.new(@document).generate
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def generate_numbering
|
|
73
|
+
NumberingService.new(@document).generate
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def build_sections
|
|
77
|
+
sections = []
|
|
78
|
+
|
|
79
|
+
root_elements = case @document
|
|
80
|
+
when Elements::Book
|
|
81
|
+
roots = []
|
|
82
|
+
roots.concat(Array(@document.part))
|
|
83
|
+
roots.concat(Array(@document.chapter))
|
|
84
|
+
roots.concat(Array(@document.appendix))
|
|
85
|
+
roots.concat(Array(@document.preface))
|
|
86
|
+
roots
|
|
87
|
+
when Elements::Article
|
|
88
|
+
roots = []
|
|
89
|
+
roots.concat(Array(@document.section))
|
|
90
|
+
roots.concat(Array(@document.article))
|
|
91
|
+
roots
|
|
92
|
+
else
|
|
93
|
+
Array(@document.elements)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
root_elements.each do |element|
|
|
97
|
+
section = build_section(element)
|
|
98
|
+
sections << section if section
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
sections
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def build_section(element)
|
|
105
|
+
type = element_type(element)
|
|
106
|
+
return nil unless type
|
|
107
|
+
|
|
108
|
+
section = Models::SectionRoot.new(
|
|
109
|
+
id: element.xml_id || generate_id(element),
|
|
110
|
+
type: type,
|
|
111
|
+
title: extract_element_title(element),
|
|
112
|
+
number: get_section_number(element)
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# Convert content to hash
|
|
116
|
+
section.content = build_section_content(element)
|
|
117
|
+
|
|
118
|
+
# Process children
|
|
119
|
+
children = build_child_sections(element)
|
|
120
|
+
section.children = children if children.any?
|
|
121
|
+
|
|
122
|
+
# For refentry, extract refname
|
|
123
|
+
if element.is_a?(Elements::RefEntry)
|
|
124
|
+
section.refname = element.refnamediv&.refname&.map(&:content)&.join(" ") ||
|
|
125
|
+
element.refmeta&.refentrytitle&.content
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
section
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def element_type(element)
|
|
132
|
+
case element
|
|
133
|
+
when Elements::Part then "part"
|
|
134
|
+
when Elements::Chapter then "chapter"
|
|
135
|
+
when Elements::Appendix then "appendix"
|
|
136
|
+
when Elements::Section then "section"
|
|
137
|
+
when Elements::Reference then "reference"
|
|
138
|
+
when Elements::RefEntry then "refentry"
|
|
139
|
+
when Elements::Preface then "preface"
|
|
140
|
+
when Elements::Article then "article"
|
|
141
|
+
when Elements::Book then "book"
|
|
142
|
+
else nil
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def extract_element_title(element)
|
|
147
|
+
case element
|
|
148
|
+
when Elements::RefEntry
|
|
149
|
+
element.refnamediv&.refname&.map(&:content)&.join(" ") ||
|
|
150
|
+
element.refmeta&.refentrytitle&.content ||
|
|
151
|
+
"Untitled"
|
|
152
|
+
when Elements::Reference
|
|
153
|
+
element.info&.title&.content || element.title&.content || "Reference"
|
|
154
|
+
else
|
|
155
|
+
element.info&.title&.content ||
|
|
156
|
+
element.title&.content ||
|
|
157
|
+
"Untitled"
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def generate_id(element)
|
|
162
|
+
"section-#{element.class.name.split('::').last.downcase}-#{element.object_id}"
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def get_section_number(element)
|
|
166
|
+
# Will be filled in by numbering service
|
|
167
|
+
nil
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def build_section_content(element)
|
|
171
|
+
# Use ElementToHash to convert child elements
|
|
172
|
+
blocks = []
|
|
173
|
+
|
|
174
|
+
if element.respond_to?(:elements)
|
|
175
|
+
element.elements.each do |child|
|
|
176
|
+
next if skip_element?(child)
|
|
177
|
+
block = ElementToHash.new(child).to_h
|
|
178
|
+
blocks << block if block
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# For refentry, also process refsection
|
|
183
|
+
if element.is_a?(Elements::RefEntry)
|
|
184
|
+
element.refsection&.each do |rs|
|
|
185
|
+
rs.elements&.each do |child|
|
|
186
|
+
next if skip_element?(child)
|
|
187
|
+
block = ElementToHash.new(child).to_h
|
|
188
|
+
blocks << block if block
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
blocks
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def skip_element?(element)
|
|
197
|
+
element.is_a?(Elements::Title) ||
|
|
198
|
+
element.is_a?(Elements::Info) ||
|
|
199
|
+
element.is_a?(Elements::IndexTerm) ||
|
|
200
|
+
element.is_a?(Elements::Simplesect) && element.title.nil?
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def build_child_sections(element)
|
|
204
|
+
children = []
|
|
205
|
+
|
|
206
|
+
child_elements = get_child_elements(element)
|
|
207
|
+
|
|
208
|
+
child_elements.each do |child|
|
|
209
|
+
next unless section_like?(child)
|
|
210
|
+
section = build_section(child)
|
|
211
|
+
children << section if section
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
children
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def get_child_elements(element)
|
|
218
|
+
case element
|
|
219
|
+
when Elements::Book
|
|
220
|
+
children = []
|
|
221
|
+
children.concat(Array(element.part))
|
|
222
|
+
children.concat(Array(element.chapter))
|
|
223
|
+
children.concat(Array(element.appendix))
|
|
224
|
+
children.concat(Array(element.preface))
|
|
225
|
+
children
|
|
226
|
+
when Elements::Article
|
|
227
|
+
children = []
|
|
228
|
+
children.concat(Array(element.section))
|
|
229
|
+
children.concat(Array(element.article))
|
|
230
|
+
children
|
|
231
|
+
when Elements::Part
|
|
232
|
+
children = []
|
|
233
|
+
children.concat(Array(element.chapter))
|
|
234
|
+
children.concat(Array(element.appendix))
|
|
235
|
+
children
|
|
236
|
+
when Elements::Chapter, Elements::Appendix, Elements::Preface
|
|
237
|
+
Array(element.section)
|
|
238
|
+
when Elements::Section
|
|
239
|
+
Array(element.section)
|
|
240
|
+
when Elements::Reference
|
|
241
|
+
Array(element.refentry)
|
|
242
|
+
else
|
|
243
|
+
[]
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def section_like?(element)
|
|
248
|
+
element.is_a?(Elements::Section) ||
|
|
249
|
+
element.is_a?(Elements::Chapter) ||
|
|
250
|
+
element.is_a?(Elements::Appendix) ||
|
|
251
|
+
element.is_a?(Elements::Part) ||
|
|
252
|
+
element.is_a?(Elements::Reference) ||
|
|
253
|
+
element.is_a?(Elements::RefEntry) ||
|
|
254
|
+
element.is_a?(Elements::Preface)
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
end
|