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,287 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docbook
|
|
4
|
+
module Services
|
|
5
|
+
# Converts Lutaml elements to Vue-friendly hashes
|
|
6
|
+
# Uses Lutaml's as_json but ensures compatibility with Vue rendering
|
|
7
|
+
class ElementToHash
|
|
8
|
+
def initialize(element)
|
|
9
|
+
@element = element
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Convert element to hash suitable for Vue
|
|
13
|
+
def to_h
|
|
14
|
+
case @element
|
|
15
|
+
when Elements::RefEntry
|
|
16
|
+
refentry_to_h
|
|
17
|
+
when Elements::Para
|
|
18
|
+
para_to_h
|
|
19
|
+
when Elements::VariableList
|
|
20
|
+
variablelist_to_h
|
|
21
|
+
when Elements::OrderedList
|
|
22
|
+
orderedlist_to_h
|
|
23
|
+
when Elements::ItemizedList
|
|
24
|
+
itemizedlist_to_h
|
|
25
|
+
when Elements::ProgramListing, Elements::Screen
|
|
26
|
+
codelike_to_h
|
|
27
|
+
when Elements::Example, Elements::InformalExample
|
|
28
|
+
example_to_h
|
|
29
|
+
when Elements::Figure, Elements::InformalFigure
|
|
30
|
+
figure_to_h
|
|
31
|
+
when Elements::Table
|
|
32
|
+
table_to_h
|
|
33
|
+
when Elements::Note, Elements::Warning, Elements::Caution,
|
|
34
|
+
Elements::Important, Elements::Tip, Elements::Danger
|
|
35
|
+
admonition_to_h
|
|
36
|
+
when Elements::BlockQuote
|
|
37
|
+
blockquote_to_h
|
|
38
|
+
when Elements::Title
|
|
39
|
+
{ type: "title", text: @element.content }
|
|
40
|
+
else
|
|
41
|
+
generic_to_h
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def refentry_to_h
|
|
48
|
+
content = @element.as_json
|
|
49
|
+
|
|
50
|
+
# Flatten for Vue - extract key fields
|
|
51
|
+
result = {
|
|
52
|
+
type: "refentry",
|
|
53
|
+
id: @element.xml_id
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
# Extract refmeta info
|
|
57
|
+
if @element.refmeta
|
|
58
|
+
result[:refentrytitle] = @element.refmeta.refentrytitle&.content
|
|
59
|
+
result[:refmiscinfo] = @element.refmeta.refmiscinfo&.map(&:content)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Extract refnamediv info
|
|
63
|
+
if @element.refnamediv
|
|
64
|
+
result[:refname] = @element.refnamediv.refname&.map(&:content)&.join(" ")
|
|
65
|
+
result[:refpurpose] = @element.refnamediv.refpurpose&.content
|
|
66
|
+
result[:refclass] = @element.refnamediv.refclass&.content
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Convert refsection content
|
|
70
|
+
if @element.refsection&.any?
|
|
71
|
+
result[:sections] = @element.refsection.map do |rs|
|
|
72
|
+
section_content_to_h(rs)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
result
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def section_content_to_h(section)
|
|
80
|
+
children = []
|
|
81
|
+
|
|
82
|
+
# Process all child elements
|
|
83
|
+
if section.respond_to?(:elements)
|
|
84
|
+
section.elements.each do |el|
|
|
85
|
+
children << ElementToHash.new(el).to_h
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
{ type: "section", children: children }
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def para_to_h
|
|
93
|
+
content = extract_inline_content(@element)
|
|
94
|
+
{
|
|
95
|
+
type: "para",
|
|
96
|
+
content: content
|
|
97
|
+
}
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def variablelist_to_h
|
|
101
|
+
{
|
|
102
|
+
type: "variablelist",
|
|
103
|
+
items: @element.varlistentry.map do |ve|
|
|
104
|
+
terms = ve.term&.map { |t| extract_inline_content(t) } || []
|
|
105
|
+
definition = if ve.listitem&.respond_to?(:para)
|
|
106
|
+
ve.listitem.para.map { |p| extract_inline_content(p) }
|
|
107
|
+
elsif ve.listitem&.respond_to?(:elements)
|
|
108
|
+
ve.listitem.elements.map { |e| ElementToHash.new(e).to_h }
|
|
109
|
+
else
|
|
110
|
+
[]
|
|
111
|
+
end
|
|
112
|
+
{ terms: terms, definition: definition }
|
|
113
|
+
end
|
|
114
|
+
}
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def orderedlist_to_h
|
|
118
|
+
{
|
|
119
|
+
type: "orderedlist",
|
|
120
|
+
items: @element.listitem.map do |li|
|
|
121
|
+
items = []
|
|
122
|
+
if li.respond_to?(:para)
|
|
123
|
+
items = li.para.map { |p| extract_inline_content(p) }
|
|
124
|
+
elsif li.respond_to?(:elements)
|
|
125
|
+
items = li.elements.map { |e| ElementToHash.new(e).to_h }
|
|
126
|
+
end
|
|
127
|
+
items
|
|
128
|
+
end
|
|
129
|
+
}
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def itemizedlist_to_h
|
|
133
|
+
{
|
|
134
|
+
type: "itemizedlist",
|
|
135
|
+
items: @element.listitem.map do |li|
|
|
136
|
+
items = []
|
|
137
|
+
if li.respond_to?(:para)
|
|
138
|
+
items = li.para.map { |p| extract_inline_content(p) }
|
|
139
|
+
elsif li.respond_to?(:elements)
|
|
140
|
+
items = li.elements.map { |e| ElementToHash.new(e).to_h }
|
|
141
|
+
end
|
|
142
|
+
items
|
|
143
|
+
end
|
|
144
|
+
}
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def codelike_to_h
|
|
148
|
+
code_text = build_code_text
|
|
149
|
+
{
|
|
150
|
+
type: "code",
|
|
151
|
+
text: code_text,
|
|
152
|
+
language: @element.language
|
|
153
|
+
}
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def example_to_h
|
|
157
|
+
{
|
|
158
|
+
type: "example",
|
|
159
|
+
content: @element.elements.map { |e| ElementToHash.new(e).to_h }
|
|
160
|
+
}
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def figure_to_h
|
|
164
|
+
result = {
|
|
165
|
+
type: "figure",
|
|
166
|
+
id: @element.xml_id
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if @element.respond_to?(:mediaobject) && @element.mediaobject
|
|
170
|
+
obj = @element.mediaobject
|
|
171
|
+
if obj.respond_to?(:imageobject) && obj.imageobject&.first
|
|
172
|
+
imagedata = obj.imageobject.first.imagedata
|
|
173
|
+
result[:src] = imagedata&.fileref
|
|
174
|
+
result[:alt] = obj.imageobject.first.textobject&.phrase&.content
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
result
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def table_to_h
|
|
182
|
+
# Simplified table representation
|
|
183
|
+
{
|
|
184
|
+
type: "table",
|
|
185
|
+
id: @element.xml_id,
|
|
186
|
+
rows: [] # Would need complex traversal
|
|
187
|
+
}
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def admonition_to_h
|
|
191
|
+
title = @element.respond_to?(:title) ? @element.title&.content : nil
|
|
192
|
+
content = @element.elements.map { |e| ElementToHash.new(e).to_h } if @element.elements
|
|
193
|
+
|
|
194
|
+
{
|
|
195
|
+
type: @element.class.name.split('::').last.downcase,
|
|
196
|
+
title: title,
|
|
197
|
+
content: content
|
|
198
|
+
}
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def blockquote_to_h
|
|
202
|
+
attribution = @element.attribution&.content if @element.attribution
|
|
203
|
+
content = @element.elements.map { |e| ElementToHash.new(e).to_h } if @element.elements
|
|
204
|
+
|
|
205
|
+
{
|
|
206
|
+
type: "blockquote",
|
|
207
|
+
attribution: attribution,
|
|
208
|
+
content: content
|
|
209
|
+
}
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def generic_to_h
|
|
213
|
+
# Default: try to use as_json
|
|
214
|
+
if @element.respond_to?(:as_json)
|
|
215
|
+
@element.as_json.merge(type: @element.class.name.split('::').last.downcase)
|
|
216
|
+
else
|
|
217
|
+
{ type: "unknown", content: @element.to_s }
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def extract_inline_content(element)
|
|
222
|
+
return [] unless element
|
|
223
|
+
|
|
224
|
+
content = []
|
|
225
|
+
if element.respond_to?(:content)
|
|
226
|
+
c = element.content
|
|
227
|
+
if c.is_a?(Array)
|
|
228
|
+
c.each { |item| content << process_inline_item(item) }
|
|
229
|
+
elsif c.is_a?(String)
|
|
230
|
+
content << { type: "text", value: c }
|
|
231
|
+
end
|
|
232
|
+
elsif element.respond_to?(:elements)
|
|
233
|
+
element.elements.each do |el|
|
|
234
|
+
content << process_inline_item(el)
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
content
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def process_inline_item(item)
|
|
241
|
+
case item
|
|
242
|
+
when String
|
|
243
|
+
{ type: "text", value: item }
|
|
244
|
+
when Elements::Emphasis
|
|
245
|
+
{
|
|
246
|
+
type: "emphasis",
|
|
247
|
+
role: item.role,
|
|
248
|
+
content: extract_inline_content(item)
|
|
249
|
+
}
|
|
250
|
+
when Elements::Link
|
|
251
|
+
{
|
|
252
|
+
type: "link",
|
|
253
|
+
href: item.href,
|
|
254
|
+
content: extract_inline_content(item)
|
|
255
|
+
}
|
|
256
|
+
when Elements::Xref
|
|
257
|
+
{
|
|
258
|
+
type: "xref",
|
|
259
|
+
linkend: item.linkend,
|
|
260
|
+
content: item.content || item.linkend
|
|
261
|
+
}
|
|
262
|
+
when Elements::Literal, Elements::Code
|
|
263
|
+
{ type: "codetext", value: item.content }
|
|
264
|
+
when Elements::Tag
|
|
265
|
+
{ type: "tag", value: "<#{item.content}>" }
|
|
266
|
+
when Elements::Quote
|
|
267
|
+
{ type: "quote", value: item.content }
|
|
268
|
+
else
|
|
269
|
+
if item.respond_to?(:content)
|
|
270
|
+
{ type: "text", value: item.content.to_s }
|
|
271
|
+
else
|
|
272
|
+
{ type: "text", value: item.to_s }
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def build_code_text
|
|
278
|
+
if @element.respond_to?(:content)
|
|
279
|
+
c = @element.content
|
|
280
|
+
c.is_a?(Array) ? c.join : c.to_s
|
|
281
|
+
else
|
|
282
|
+
""
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
end
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docbook
|
|
4
|
+
module Services
|
|
5
|
+
# Generates index entries from IndexTerm elements in the document
|
|
6
|
+
class IndexGenerator
|
|
7
|
+
def initialize(document)
|
|
8
|
+
@document = document
|
|
9
|
+
@entries = []
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def generate
|
|
13
|
+
@entries = []
|
|
14
|
+
collect_index_terms(@document)
|
|
15
|
+
sort_entries!
|
|
16
|
+
@entries
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def collect_index_terms(element, section_info = {})
|
|
22
|
+
return unless element
|
|
23
|
+
|
|
24
|
+
# Extract section info from current element
|
|
25
|
+
section_info = extract_section_info(element) if section_info.empty?
|
|
26
|
+
|
|
27
|
+
# Process index terms at this level
|
|
28
|
+
process_index_terms(element, section_info)
|
|
29
|
+
|
|
30
|
+
# Recurse into children
|
|
31
|
+
if element.respond_to?(:elements)
|
|
32
|
+
Array(element.elements).each do |child|
|
|
33
|
+
collect_index_terms(child, section_info)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Process nested sections
|
|
38
|
+
if element.respond_to?(:section)
|
|
39
|
+
Array(element.section).each do |section|
|
|
40
|
+
collect_index_terms(section, section_info)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def extract_section_info(element)
|
|
46
|
+
{
|
|
47
|
+
section_id: element.xml_id || generate_id(element),
|
|
48
|
+
section_title: get_element_title(element)
|
|
49
|
+
}
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def get_element_title(element)
|
|
53
|
+
case element
|
|
54
|
+
when Elements::RefEntry
|
|
55
|
+
element.refnamediv&.refname&.map(&:content)&.join(" ") ||
|
|
56
|
+
element.refmeta&.refentrytitle&.content
|
|
57
|
+
else
|
|
58
|
+
element.respond_to?(:title) && element.title&.content ||
|
|
59
|
+
element.respond_to?(:info) && element.info&.title&.content
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def generate_id(element)
|
|
64
|
+
"section-#{element.class.name.split('::').last.downcase}-#{element.object_id}"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def process_index_terms(element, section_info)
|
|
68
|
+
# Find indexterm elements
|
|
69
|
+
index_terms = find_index_terms(element)
|
|
70
|
+
|
|
71
|
+
index_terms.each do |term|
|
|
72
|
+
entry = build_index_entry(term, section_info)
|
|
73
|
+
@entries << entry if entry
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def find_index_terms(element)
|
|
78
|
+
return [] unless element
|
|
79
|
+
|
|
80
|
+
terms = []
|
|
81
|
+
if element.is_a?(Elements::IndexTerm)
|
|
82
|
+
terms << element
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Check for nested index terms
|
|
86
|
+
if element.respond_to?(:indexterm)
|
|
87
|
+
terms.concat(Array(element.indexterm))
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
terms
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def build_index_entry(index_term, section_info)
|
|
94
|
+
primary = extract_primary(index_term)
|
|
95
|
+
return nil unless primary
|
|
96
|
+
|
|
97
|
+
entry = Models::IndexEntry.new(
|
|
98
|
+
primary: primary,
|
|
99
|
+
section_id: section_info[:section_id],
|
|
100
|
+
section_title: section_info[:section_title],
|
|
101
|
+
sort_key: primary.downcase.strip
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
# Extract secondary terms
|
|
105
|
+
if index_term.respond_to?(:secondary)
|
|
106
|
+
entry.secondary = Array(index_term.secondary).map(&:content).compact
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Extract see-also
|
|
110
|
+
if index_term.respond_to?(:see_also)
|
|
111
|
+
entry.see_also = Array(index_term.see_also).map(&:content).compact
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
entry
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def extract_primary(index_term)
|
|
118
|
+
# Primary is typically in the content or primary attribute
|
|
119
|
+
if index_term.respond_to?(:primary) && index_term.primary
|
|
120
|
+
Array(index_term.primary).map(&:content).join(" ")
|
|
121
|
+
elsif index_term.respond_to?(:content)
|
|
122
|
+
content = index_term.content
|
|
123
|
+
content.is_a?(Array) ? content.join(" ") : content.to_s
|
|
124
|
+
elsif index_term.respond_to?(:to_s)
|
|
125
|
+
index_term.to_s
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def sort_entries!
|
|
130
|
+
@entries.sort_by!(&:sort_key)
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docbook
|
|
4
|
+
module Services
|
|
5
|
+
# Pre-computes section numbering for the document
|
|
6
|
+
# Handles: Roman numerals for Parts, Arabic for Chapters,
|
|
7
|
+
# Hierarchical for Sections, Alpha for Appendices
|
|
8
|
+
class NumberingService
|
|
9
|
+
def initialize(document)
|
|
10
|
+
@document = document
|
|
11
|
+
@numbering = []
|
|
12
|
+
@part_counter = 0
|
|
13
|
+
@chapter_counters = {} # part_index => counter
|
|
14
|
+
@appendix_counter = 0
|
|
15
|
+
@section_counters = {} # scope_id => [level1, level2, ...]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def generate
|
|
19
|
+
@numbering = []
|
|
20
|
+
@part_counter = 0
|
|
21
|
+
@chapter_counters = {}
|
|
22
|
+
@appendix_counter = 0
|
|
23
|
+
@section_counters = {}
|
|
24
|
+
|
|
25
|
+
process_document(@document)
|
|
26
|
+
@numbering
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def process_document(element, parent_info = {})
|
|
32
|
+
return unless element
|
|
33
|
+
|
|
34
|
+
case element
|
|
35
|
+
when Elements::Part
|
|
36
|
+
process_part(element, parent_info)
|
|
37
|
+
when Elements::Chapter
|
|
38
|
+
process_chapter(element, parent_info)
|
|
39
|
+
when Elements::Appendix
|
|
40
|
+
process_appendix(element, parent_info)
|
|
41
|
+
when Elements::Section
|
|
42
|
+
process_section(element, parent_info)
|
|
43
|
+
when Elements::RefEntry
|
|
44
|
+
process_refentry(element, parent_info)
|
|
45
|
+
else
|
|
46
|
+
# Process children
|
|
47
|
+
process_children(element, parent_info)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def process_part(part, parent_info)
|
|
52
|
+
@part_counter += 1
|
|
53
|
+
part_number = roman_numeral(@part_counter)
|
|
54
|
+
|
|
55
|
+
add_numbering(part, part_number, "part")
|
|
56
|
+
|
|
57
|
+
# Track chapters within this part
|
|
58
|
+
@chapter_counters[@part_counter] = 0
|
|
59
|
+
|
|
60
|
+
process_children(part, parent_info.merge(part_counter: @part_counter))
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def process_chapter(chapter, parent_info)
|
|
64
|
+
part_idx = parent_info[:part_counter] || 0
|
|
65
|
+
@chapter_counters[part_idx] ||= 0
|
|
66
|
+
@chapter_counters[part_idx] += 1
|
|
67
|
+
chapter_number = @chapter_counters[part_idx].to_s
|
|
68
|
+
|
|
69
|
+
add_numbering(chapter, chapter_number, "chapter")
|
|
70
|
+
|
|
71
|
+
# Reset section counters for this chapter
|
|
72
|
+
scope_id = chapter.xml_id || "chapter-#{chapter_number}"
|
|
73
|
+
@section_counters[scope_id] = [0, 0, 0, 0, 0]
|
|
74
|
+
|
|
75
|
+
process_children(chapter, parent_info.merge(chapter_scope: scope_id))
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def process_appendix(appendix, parent_info)
|
|
79
|
+
@appendix_counter += 1
|
|
80
|
+
appendix_number = alpha_numeral(@appendix_counter)
|
|
81
|
+
|
|
82
|
+
add_numbering(appendix, appendix_number, "appendix")
|
|
83
|
+
|
|
84
|
+
# Reset section counters for this appendix
|
|
85
|
+
scope_id = appendix.xml_id || "appendix-#{appendix_number}"
|
|
86
|
+
@section_counters[scope_id] = [0, 0, 0, 0, 0]
|
|
87
|
+
|
|
88
|
+
process_children(appendix, parent_info.merge(appendix_scope: scope_id))
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def process_section(section, parent_info)
|
|
92
|
+
# Determine scope
|
|
93
|
+
scope_id = parent_info[:chapter_scope] ||
|
|
94
|
+
parent_info[:appendix_scope] ||
|
|
95
|
+
section.xml_id ||
|
|
96
|
+
"section-#{@numbering.length}"
|
|
97
|
+
|
|
98
|
+
# Initialize counters for this scope
|
|
99
|
+
@section_counters[scope_id] ||= [0, 0, 0, 0, 0]
|
|
100
|
+
|
|
101
|
+
# Count nesting depth
|
|
102
|
+
depth = section_depth(section)
|
|
103
|
+
|
|
104
|
+
# Increment counter at this depth
|
|
105
|
+
@section_counters[scope_id][depth - 1] += 1
|
|
106
|
+
|
|
107
|
+
# Reset counters for deeper levels
|
|
108
|
+
(depth...5).each { |i| @section_counters[scope_id][i] = 0 }
|
|
109
|
+
|
|
110
|
+
# Build number string
|
|
111
|
+
numbers = @section_counters[scope_id].first(depth)
|
|
112
|
+
section_number = numbers.join(".")
|
|
113
|
+
|
|
114
|
+
add_numbering(section, section_number, "section")
|
|
115
|
+
|
|
116
|
+
process_children(section, parent_info)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def process_refentry(refentry, parent_info)
|
|
120
|
+
refname = if refentry.refnamediv&.refname
|
|
121
|
+
refentry.refnamediv.refname.map(&:content).join(" ")
|
|
122
|
+
elsif refentry.refmeta&.refentrytitle
|
|
123
|
+
refentry.refmeta.refentrytitle.content
|
|
124
|
+
else
|
|
125
|
+
refentry.xml_id || "refentry"
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
add_numbering(refentry, refname, "refentry")
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def process_children(element, parent_info)
|
|
132
|
+
return unless element.respond_to?(:elements)
|
|
133
|
+
|
|
134
|
+
Array(element.elements).each do |child|
|
|
135
|
+
process_document(child, parent_info)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def section_depth(section)
|
|
140
|
+
# Determine section nesting depth
|
|
141
|
+
depth = 1
|
|
142
|
+
el = section
|
|
143
|
+
while el.respond_to?(:parent) && el.parent
|
|
144
|
+
depth += 1
|
|
145
|
+
el = el.parent
|
|
146
|
+
end
|
|
147
|
+
[depth, 5].min
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def add_numbering(element, number, type)
|
|
151
|
+
@numbering << Models::SectionNumber.new(
|
|
152
|
+
id: element.xml_id || "element-#{@numbering.length}",
|
|
153
|
+
number: number.to_s,
|
|
154
|
+
type: type
|
|
155
|
+
)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def roman_numeral(num)
|
|
159
|
+
result = +""
|
|
160
|
+
roman_map = {
|
|
161
|
+
1000 => 'M', 900 => 'CM', 500 => 'D', 400 => 'CD',
|
|
162
|
+
100 => 'C', 90 => 'XC', 50 => 'L', 40 => 'XL',
|
|
163
|
+
10 => 'X', 9 => 'IX', 5 => 'V', 4 => 'IV', 1 => 'I'
|
|
164
|
+
}
|
|
165
|
+
remaining = num
|
|
166
|
+
roman_map.each do |value, letter|
|
|
167
|
+
while remaining >= value
|
|
168
|
+
result << letter
|
|
169
|
+
remaining -= value
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
result
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def alpha_numeral(num)
|
|
176
|
+
result = ""
|
|
177
|
+
while num > 0
|
|
178
|
+
num -= 1
|
|
179
|
+
result = (('A'.ord + (num % 26)).chr) + result
|
|
180
|
+
num /= 26
|
|
181
|
+
end
|
|
182
|
+
result
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|