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.
Files changed (271) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +5 -0
  3. data/CLAUDE.md +19 -0
  4. data/CODE_OF_CONDUCT.md +10 -0
  5. data/README.adoc +335 -0
  6. data/Rakefile +12 -0
  7. data/docs/.lycheeignore +33 -0
  8. data/docs/Gemfile +10 -0
  9. data/docs/INDEX.adoc +67 -0
  10. data/docs/_config.yml +186 -0
  11. data/docs/advanced/element-classes.adoc +185 -0
  12. data/docs/advanced/frontend-customization.adoc +193 -0
  13. data/docs/advanced/index.adoc +14 -0
  14. data/docs/advanced/templates.adoc +123 -0
  15. data/docs/features/element-coverage.adoc +373 -0
  16. data/docs/features/html-output/data-model.adoc +285 -0
  17. data/docs/features/html-output/directory-mode.adoc +180 -0
  18. data/docs/features/html-output/index.adoc +90 -0
  19. data/docs/features/html-output/single-file-mode.adoc +125 -0
  20. data/docs/features/index-generation.adoc +197 -0
  21. data/docs/features/index.adoc +63 -0
  22. data/docs/features/numbering.adoc +183 -0
  23. data/docs/features/toc-generation.adoc +150 -0
  24. data/docs/features/xinclude/fragid-schemes.adoc +287 -0
  25. data/docs/features/xinclude/index.adoc +119 -0
  26. data/docs/features/xinclude/text-includes.adoc +123 -0
  27. data/docs/features/xinclude/xml-includes.adoc +167 -0
  28. data/docs/getting-started/index.adoc +50 -0
  29. data/docs/getting-started/installation.adoc +113 -0
  30. data/docs/getting-started/quick-start.adoc +161 -0
  31. data/docs/guides/converting-article.adoc +188 -0
  32. data/docs/guides/converting-book.adoc +192 -0
  33. data/docs/guides/index.adoc +12 -0
  34. data/docs/guides/roundtrip-testing.adoc +129 -0
  35. data/docs/interfaces/cli/format-command.adoc +109 -0
  36. data/docs/interfaces/cli/index.adoc +73 -0
  37. data/docs/interfaces/cli/roundtrip-command.adoc +125 -0
  38. data/docs/interfaces/cli/to-html-command.adoc +178 -0
  39. data/docs/interfaces/cli/validate-command.adoc +104 -0
  40. data/docs/interfaces/index.adoc +101 -0
  41. data/docs/interfaces/ruby-api/html-output.adoc +186 -0
  42. data/docs/interfaces/ruby-api/index.adoc +111 -0
  43. data/docs/interfaces/ruby-api/parsing.adoc +202 -0
  44. data/docs/interfaces/ruby-api/xinclude.adoc +162 -0
  45. data/docs/interfaces/ruby-api/xref-resolution.adoc +156 -0
  46. data/docs/lychee.toml +42 -0
  47. data/docs/reference/cli-options.adoc +155 -0
  48. data/docs/reference/content-block-types.adoc +243 -0
  49. data/docs/reference/glossary.adoc +119 -0
  50. data/docs/reference/index.adoc +12 -0
  51. data/docs/reference/supported-elements.adoc +749 -0
  52. data/docs/understanding/architecture.adoc +145 -0
  53. data/docs/understanding/content-pipeline.adoc +102 -0
  54. data/docs/understanding/data-models.adoc +156 -0
  55. data/docs/understanding/index.adoc +34 -0
  56. data/exe/docbook +7 -0
  57. data/frontend/dist/app.css +1 -0
  58. data/frontend/dist/app.iife.js +24 -0
  59. data/frontend/package-lock.json +1445 -0
  60. data/frontend/package.json +22 -0
  61. data/frontend/src/App.vue +230 -0
  62. data/frontend/src/app.ts +8 -0
  63. data/frontend/src/components/AppSidebar.vue +116 -0
  64. data/frontend/src/components/AppendixSection.vue +39 -0
  65. data/frontend/src/components/BlockRenderer.vue +358 -0
  66. data/frontend/src/components/ChapterSection.vue +32 -0
  67. data/frontend/src/components/ContentRenderer.vue +13 -0
  68. data/frontend/src/components/EbookContainer.vue +147 -0
  69. data/frontend/src/components/EbookTopBar.vue +116 -0
  70. data/frontend/src/components/PartSection.vue +44 -0
  71. data/frontend/src/components/ReferenceEntry.vue +80 -0
  72. data/frontend/src/components/SearchModal.vue +286 -0
  73. data/frontend/src/components/SectionContent.vue +31 -0
  74. data/frontend/src/components/SettingsPanel.vue +236 -0
  75. data/frontend/src/components/TocTreeItem.vue +135 -0
  76. data/frontend/src/composables/useEbookStore.ts +191 -0
  77. data/frontend/src/composables/useSearch.ts +249 -0
  78. data/frontend/src/env.d.ts +7 -0
  79. data/frontend/src/stores/documentStore.ts +221 -0
  80. data/frontend/src/stores/uiStore.ts +98 -0
  81. data/frontend/src/styles.css +253 -0
  82. data/frontend/tsconfig.json +24 -0
  83. data/frontend/tsconfig.node.json +11 -0
  84. data/frontend/vite.config.ts +30 -0
  85. data/lib/docbook/cli.rb +123 -0
  86. data/lib/docbook/document.rb +67 -0
  87. data/lib/docbook/elements/abbrev.rb +16 -0
  88. data/lib/docbook/elements/acknowledgements.rb +22 -0
  89. data/lib/docbook/elements/address.rb +18 -0
  90. data/lib/docbook/elements/alt.rb +16 -0
  91. data/lib/docbook/elements/annotation.rb +18 -0
  92. data/lib/docbook/elements/appendix.rb +34 -0
  93. data/lib/docbook/elements/article.rb +31 -0
  94. data/lib/docbook/elements/att.rb +15 -0
  95. data/lib/docbook/elements/attribution.rb +20 -0
  96. data/lib/docbook/elements/audioobject.rb +18 -0
  97. data/lib/docbook/elements/author.rb +18 -0
  98. data/lib/docbook/elements/bibliography.rb +24 -0
  99. data/lib/docbook/elements/bibliomixed.rb +40 -0
  100. data/lib/docbook/elements/biblioref.rb +20 -0
  101. data/lib/docbook/elements/blockquote.rb +26 -0
  102. data/lib/docbook/elements/book.rb +36 -0
  103. data/lib/docbook/elements/buildtarget.rb +16 -0
  104. data/lib/docbook/elements/callout.rb +22 -0
  105. data/lib/docbook/elements/calloutlist.rb +22 -0
  106. data/lib/docbook/elements/caution.rb +30 -0
  107. data/lib/docbook/elements/chapter.rb +62 -0
  108. data/lib/docbook/elements/citation.rb +16 -0
  109. data/lib/docbook/elements/citerefentry.rb +26 -0
  110. data/lib/docbook/elements/citetitle.rb +20 -0
  111. data/lib/docbook/elements/classname.rb +16 -0
  112. data/lib/docbook/elements/code.rb +16 -0
  113. data/lib/docbook/elements/colophon.rb +22 -0
  114. data/lib/docbook/elements/computeroutput.rb +18 -0
  115. data/lib/docbook/elements/copyright.rb +17 -0
  116. data/lib/docbook/elements/danger.rb +28 -0
  117. data/lib/docbook/elements/date.rb +16 -0
  118. data/lib/docbook/elements/dedication.rb +22 -0
  119. data/lib/docbook/elements/dir.rb +16 -0
  120. data/lib/docbook/elements/emphasis.rb +18 -0
  121. data/lib/docbook/elements/entry.rb +30 -0
  122. data/lib/docbook/elements/entrytbl.rb +22 -0
  123. data/lib/docbook/elements/equation.rb +26 -0
  124. data/lib/docbook/elements/example.rb +30 -0
  125. data/lib/docbook/elements/fieldsynopsis.rb +21 -0
  126. data/lib/docbook/elements/figure.rb +28 -0
  127. data/lib/docbook/elements/filename.rb +16 -0
  128. data/lib/docbook/elements/firstname.rb +16 -0
  129. data/lib/docbook/elements/firstterm.rb +18 -0
  130. data/lib/docbook/elements/footnote.rb +22 -0
  131. data/lib/docbook/elements/footnoteref.rb +21 -0
  132. data/lib/docbook/elements/foreignphrase.rb +18 -0
  133. data/lib/docbook/elements/formalpara.rb +20 -0
  134. data/lib/docbook/elements/function.rb +16 -0
  135. data/lib/docbook/elements/glossary.rb +24 -0
  136. data/lib/docbook/elements/glossdef.rb +18 -0
  137. data/lib/docbook/elements/glossentry.rb +26 -0
  138. data/lib/docbook/elements/glosssee.rb +18 -0
  139. data/lib/docbook/elements/glossseealso.rb +18 -0
  140. data/lib/docbook/elements/glossterm.rb +18 -0
  141. data/lib/docbook/elements/holder.rb +16 -0
  142. data/lib/docbook/elements/honorific.rb +16 -0
  143. data/lib/docbook/elements/imagedata.rb +29 -0
  144. data/lib/docbook/elements/imageobject.rb +22 -0
  145. data/lib/docbook/elements/important.rb +30 -0
  146. data/lib/docbook/elements/index.rb +26 -0
  147. data/lib/docbook/elements/indexdiv.rb +22 -0
  148. data/lib/docbook/elements/indexentry.rb +22 -0
  149. data/lib/docbook/elements/indexterm.rb +34 -0
  150. data/lib/docbook/elements/info.rb +25 -0
  151. data/lib/docbook/elements/informalexample.rb +24 -0
  152. data/lib/docbook/elements/informalfigure.rb +20 -0
  153. data/lib/docbook/elements/inlinemediaobject.rb +22 -0
  154. data/lib/docbook/elements/itemizedlist.rb +21 -0
  155. data/lib/docbook/elements/legalnotice.rb +22 -0
  156. data/lib/docbook/elements/link.rb +26 -0
  157. data/lib/docbook/elements/listitem.rb +32 -0
  158. data/lib/docbook/elements/literal.rb +16 -0
  159. data/lib/docbook/elements/literallayout.rb +22 -0
  160. data/lib/docbook/elements/mediaobject.rb +26 -0
  161. data/lib/docbook/elements/msg.rb +20 -0
  162. data/lib/docbook/elements/msgexplan.rb +22 -0
  163. data/lib/docbook/elements/msgset.rb +22 -0
  164. data/lib/docbook/elements/note.rb +30 -0
  165. data/lib/docbook/elements/orderedlist.rb +23 -0
  166. data/lib/docbook/elements/orgname.rb +16 -0
  167. data/lib/docbook/elements/para.rb +61 -0
  168. data/lib/docbook/elements/paragraph_like.rb +18 -0
  169. data/lib/docbook/elements/parameter.rb +16 -0
  170. data/lib/docbook/elements/part.rb +38 -0
  171. data/lib/docbook/elements/personname.rb +22 -0
  172. data/lib/docbook/elements/phrase.rb +20 -0
  173. data/lib/docbook/elements/preface.rb +58 -0
  174. data/lib/docbook/elements/primary.rb +16 -0
  175. data/lib/docbook/elements/procedure.rb +24 -0
  176. data/lib/docbook/elements/productname.rb +18 -0
  177. data/lib/docbook/elements/productnumber.rb +16 -0
  178. data/lib/docbook/elements/programlisting.rb +28 -0
  179. data/lib/docbook/elements/property.rb +16 -0
  180. data/lib/docbook/elements/pubdate.rb +16 -0
  181. data/lib/docbook/elements/publishername.rb +16 -0
  182. data/lib/docbook/elements/quotation.rb +24 -0
  183. data/lib/docbook/elements/quote.rb +18 -0
  184. data/lib/docbook/elements/refentry.rb +32 -0
  185. data/lib/docbook/elements/refentrytitle.rb +16 -0
  186. data/lib/docbook/elements/reference.rb +26 -0
  187. data/lib/docbook/elements/refmeta.rb +29 -0
  188. data/lib/docbook/elements/refmiscinfo.rb +16 -0
  189. data/lib/docbook/elements/refname.rb +16 -0
  190. data/lib/docbook/elements/refnamediv.rb +22 -0
  191. data/lib/docbook/elements/refpurpose.rb +16 -0
  192. data/lib/docbook/elements/refsect1.rb +22 -0
  193. data/lib/docbook/elements/refsect2.rb +22 -0
  194. data/lib/docbook/elements/refsect3.rb +22 -0
  195. data/lib/docbook/elements/refsection.rb +32 -0
  196. data/lib/docbook/elements/releaseinfo.rb +16 -0
  197. data/lib/docbook/elements/remark.rb +20 -0
  198. data/lib/docbook/elements/replaceable.rb +16 -0
  199. data/lib/docbook/elements/row.rb +15 -0
  200. data/lib/docbook/elements/screen.rb +28 -0
  201. data/lib/docbook/elements/secondary.rb +16 -0
  202. data/lib/docbook/elements/sect1.rb +26 -0
  203. data/lib/docbook/elements/sect2.rb +24 -0
  204. data/lib/docbook/elements/sect3.rb +24 -0
  205. data/lib/docbook/elements/sect4.rb +24 -0
  206. data/lib/docbook/elements/sect5.rb +22 -0
  207. data/lib/docbook/elements/section.rb +66 -0
  208. data/lib/docbook/elements/see.rb +16 -0
  209. data/lib/docbook/elements/seealso.rb +18 -0
  210. data/lib/docbook/elements/set.rb +26 -0
  211. data/lib/docbook/elements/setindex.rb +24 -0
  212. data/lib/docbook/elements/sidebar.rb +22 -0
  213. data/lib/docbook/elements/simplesect.rb +32 -0
  214. data/lib/docbook/elements/step.rb +26 -0
  215. data/lib/docbook/elements/substeps.rb +18 -0
  216. data/lib/docbook/elements/subtitle.rb +16 -0
  217. data/lib/docbook/elements/surname.rb +16 -0
  218. data/lib/docbook/elements/table.rb +24 -0
  219. data/lib/docbook/elements/tag.rb +20 -0
  220. data/lib/docbook/elements/tbody.rb +15 -0
  221. data/lib/docbook/elements/term.rb +37 -0
  222. data/lib/docbook/elements/tertiary.rb +16 -0
  223. data/lib/docbook/elements/textobject.rb +18 -0
  224. data/lib/docbook/elements/tfoot.rb +15 -0
  225. data/lib/docbook/elements/tgroup.rb +21 -0
  226. data/lib/docbook/elements/thead.rb +15 -0
  227. data/lib/docbook/elements/tip.rb +30 -0
  228. data/lib/docbook/elements/title.rb +16 -0
  229. data/lib/docbook/elements/toc.rb +24 -0
  230. data/lib/docbook/elements/tocdiv.rb +22 -0
  231. data/lib/docbook/elements/tocentry.rb +20 -0
  232. data/lib/docbook/elements/topic.rb +26 -0
  233. data/lib/docbook/elements/type.rb +16 -0
  234. data/lib/docbook/elements/uri.rb +16 -0
  235. data/lib/docbook/elements/userinput.rb +18 -0
  236. data/lib/docbook/elements/variable.rb +16 -0
  237. data/lib/docbook/elements/variablelist.rb +19 -0
  238. data/lib/docbook/elements/varlistentry.rb +17 -0
  239. data/lib/docbook/elements/version.rb +16 -0
  240. data/lib/docbook/elements/videoobject.rb +18 -0
  241. data/lib/docbook/elements/warning.rb +30 -0
  242. data/lib/docbook/elements/xref.rb +18 -0
  243. data/lib/docbook/elements/year.rb +16 -0
  244. data/lib/docbook/elements.rb +204 -0
  245. data/lib/docbook/models/document_metadata.rb +30 -0
  246. data/lib/docbook/models/document_root.rb +33 -0
  247. data/lib/docbook/models/index_entry.rb +28 -0
  248. data/lib/docbook/models/reading_position.rb +22 -0
  249. data/lib/docbook/models/section_number.rb +18 -0
  250. data/lib/docbook/models/section_root.rb +29 -0
  251. data/lib/docbook/models/toc_node.rb +24 -0
  252. data/lib/docbook/models.rb +16 -0
  253. data/lib/docbook/output/data.rb +196 -0
  254. data/lib/docbook/output/html.rb +1450 -0
  255. data/lib/docbook/output/index_generator.rb +281 -0
  256. data/lib/docbook/output.rb +8 -0
  257. data/lib/docbook/services/document_builder.rb +258 -0
  258. data/lib/docbook/services/element_to_hash.rb +287 -0
  259. data/lib/docbook/services/index_generator.rb +134 -0
  260. data/lib/docbook/services/numbering_service.rb +186 -0
  261. data/lib/docbook/services/toc_generator.rb +138 -0
  262. data/lib/docbook/services.rb +14 -0
  263. data/lib/docbook/templates/document.html.liquid +20 -0
  264. data/lib/docbook/templates/partials/_head.liquid +39 -0
  265. data/lib/docbook/templates/partials/_scripts.liquid +10 -0
  266. data/lib/docbook/version.rb +5 -0
  267. data/lib/docbook/xinclude_resolver.rb +217 -0
  268. data/lib/docbook/xref_resolver.rb +78 -0
  269. data/lib/docbook.rb +17 -0
  270. data/sig/docbook.rbs +4 -0
  271. metadata +385 -0
@@ -0,0 +1,180 @@
1
+ ---
2
+ layout: default
3
+ title: Directory Mode
4
+ parent: HTML Output
5
+ grand_parent: Features
6
+ nav_order: 2
7
+ ---
8
+
9
+ # Directory Mode
10
+
11
+ Directory mode (`output_mode: :directory`) produces a directory of files suitable for serving over HTTP. Instead of inlining everything into a single HTML file, assets are separated into their own files and the Vue SPA fetches data at runtime.
12
+
13
+ ## Output Structure
14
+
15
+ When directory mode is active, the output directory contains:
16
+
17
+ [source]
18
+ ----
19
+ output_dir/
20
+ index.html # HTML shell with external asset references
21
+ docbook.data.json # Full document data (TOC, content, index) as JSON
22
+ assets/
23
+ app.css # Compiled Vue SPA stylesheet
24
+ app.iife.js # Compiled Vue SPA JavaScript bundle
25
+ ----
26
+
27
+ Images are **not** copied to the output directory. Instead, they are referenced by their resolved file paths relative to the source document. This means the source image directories must remain accessible at their original locations (or be served by the web server).
28
+
29
+ ## How It Works
30
+
31
+ In directory mode, the `Output::Html` class performs these steps:
32
+
33
+ . **Serialize data** -- The full `DocbookOutput` model is serialized to JSON and written to `docbook.data.json`
34
+ . **Render template** -- The Liquid template is rendered with `assets_inline: false`, producing external `<link>` and `<script>` references
35
+ . **Replace placeholders** -- The `[[DOCBOOK_DATA]]` placeholders are replaced with `null` comments since data is loaded separately
36
+ . **Write files** -- The CLI copies frontend assets to `assets/` and writes the HTML to `index.html`
37
+
38
+ ## Asset References
39
+
40
+ The template partials use the `assets_inline` flag to switch between inline and external references:
41
+
42
+ **Head partial (`_head.liquid`):**
43
+
44
+ [source,html]
45
+ ----
46
+ <!-- Directory mode: external CSS -->
47
+ {% if assets_inline %}
48
+ <style>{{ app_css }}</style>
49
+ {% else %}
50
+ <link rel="stylesheet" href="{{ base_url }}/assets/app.css">
51
+ {% endif %}
52
+ ----
53
+
54
+ **Scripts partial (`_scripts.liquid`):**
55
+
56
+ [source,html]
57
+ ----
58
+ <!-- Directory mode: external JS -->
59
+ {% if assets_inline %}
60
+ <script>{{ app_js }}</script>
61
+ {% else %}
62
+ <script src="{{ base_url }}/assets/app.iife.js"></script>
63
+ {% endif %}
64
+ ----
65
+
66
+ ## Data Loading
67
+
68
+ In directory mode, the Vue SPA's `documentStore` does not find `window.DOCBOOK_DATA` and falls back to fetching `docbook.data.json` via HTTP:
69
+
70
+ [source,typescript]
71
+ ----
72
+ // From documentStore.ts
73
+ function loadFromWindow(): Promise<void> {
74
+ const inlineData = (window as any).DOCBOOK_DATA
75
+ if (inlineData && inlineData !== null) {
76
+ // Single file mode: data is already in window
77
+ documentData.value = data as DocumentData
78
+ return Promise.resolve()
79
+ }
80
+
81
+ // Directory mode: fetch from JSON file
82
+ return fetch('docbook.data.json')
83
+ .then(res => res.json())
84
+ .then(data => {
85
+ documentData.value = data as DocumentData
86
+ })
87
+ }
88
+ ----
89
+
90
+ The `docbook.data.json` file contains the complete serialized `DocbookOutput` model, including the TOC tree, numbering map, all section content, and the generated index.
91
+
92
+ ## Image Handling
93
+
94
+ In directory mode, images are referenced by file path rather than base64:
95
+
96
+ [source,ruby]
97
+ ----
98
+ def process_image(fileref)
99
+ if @output_mode == :single_file
100
+ embed_image_base64(fileref)
101
+ else
102
+ find_file_path(fileref) || fileref
103
+ end
104
+ end
105
+ ----
106
+
107
+ The `find_file_path` method searches the document's base path and parent directories to resolve image file paths. It tries common prefixes (`""`, `"resources/"`, `"../resources/"`) to accommodate various project structures.
108
+
109
+ ## Use Case
110
+
111
+ Directory mode is designed for:
112
+
113
+ - **Web serving** -- Deploy to any static file server (nginx, Apache, S3, GitHub Pages)
114
+ - **Large documents** -- Avoids the size overhead of base64-encoded images
115
+ - **CDN delivery** -- Assets can be cached independently by browsers
116
+ - **Development** -- Faster rebuilds since only `docbook.data.json` changes when content changes
117
+
118
+ ## CLI Usage
119
+
120
+ [source,bash]
121
+ ----
122
+ # Directory mode requires an output directory
123
+ docbook to-html document.xml -o ./output_dir --output-type directory
124
+ ----
125
+
126
+ The CLI copies frontend assets automatically:
127
+
128
+ [source,ruby]
129
+ ----
130
+ # From cli.rb
131
+ def write_output(content, output_path, output_type)
132
+ if output_type == :directory
133
+ FileUtils.mkdir_p(output_path)
134
+ File.write(File.join(output_path, "index.html"), content)
135
+
136
+ assets_dir = File.join(output_path, "assets")
137
+ FileUtils.mkdir_p(assets_dir)
138
+ FileUtils.cp(File.join(FRONTEND_ROOT, "app.css"), File.join(assets_dir, "app.css"))
139
+ FileUtils.cp(File.join(FRONTEND_ROOT, "app.iife.js"), File.join(assets_dir, "app.iife.js"))
140
+ end
141
+ end
142
+ ----
143
+
144
+ ## Programmatic Usage
145
+
146
+ [source,ruby]
147
+ ----
148
+ require "docbook"
149
+
150
+ document = Docbook::Document.from_xml(File.read("book.xml"))
151
+ xref_resolver = Docbook::XrefResolver.new(document).resolve!
152
+ base_path = File.dirname(File.expand_path("book.xml"))
153
+ output_dir = "/path/to/output"
154
+
155
+ html = Docbook::Output::Html.new(
156
+ document,
157
+ xref_resolver: xref_resolver,
158
+ output_mode: :directory,
159
+ base_path: base_path,
160
+ output_path: output_dir
161
+ ).to_html
162
+
163
+ File.write(File.join(output_dir, "index.html"), html)
164
+ ----
165
+
166
+ ## Template Rendering Details
167
+
168
+ The Liquid template is rendered with these variables in directory mode:
169
+
170
+ [cols="1,2,3"]
171
+ |===
172
+ | Variable | Value | Purpose
173
+
174
+ | `docbook_title` | Document title | Page `<title>` element
175
+ | `base_url` | `"."` (current directory) | Relative URL base for asset references
176
+ | `assets_inline` | `false` | Controls whether CSS/JS are inlined or linked
177
+ | `app_css` | Contents of `frontend/dist/app.css` | Used only for template rendering (file is also copied to `assets/`)
178
+ | `app_js` | Contents of `frontend/dist/app.iife.js` | Used only for template rendering (file is also copied to `assets/`)
179
+ | `data_url` | `"docbook.data.json"` | URL the SPA fetches for document data
180
+ |===
@@ -0,0 +1,90 @@
1
+ ---
2
+ layout: default
3
+ title: HTML Output
4
+ parent: Features
5
+ nav_order: 1
6
+ has_children: true
7
+ ---
8
+
9
+ # HTML Output
10
+
11
+ The HTML output system converts parsed DocBook XML into an interactive web publication. It uses a two-stage rendering approach: a Ruby-based data extraction layer that builds structured JSON data, and a Vue 3 single-page application (SPA) that renders the content client-side.
12
+
13
+ ## How It Works
14
+
15
+ The `Docbook::Output::Html` class orchestrates the entire output pipeline:
16
+
17
+ . **Section Collection** -- Traverses the document tree to build a `SectionData` hierarchy (the TOC)
18
+ . **Numbering** -- `NumberingBuilder` assigns formatted numbers to each section
19
+ . **Content Extraction** -- For each section, the element tree is walked to produce `ContentBlock` trees
20
+ . **Index Collection** -- `IndexCollector` gathers all `indexterm` elements
21
+ . **Data Assembly** -- Everything is packaged into a `DocbookOutput` model
22
+ . **Template Rendering** -- A Liquid template produces the HTML shell with placeholders
23
+
24
+ ## Template System
25
+
26
+ The HTML shell is generated using https://github.com/Shopify/liquid[Liquid templates] stored in `lib/docbook/templates/`. The main template (`document.html.liquid`) includes two partials:
27
+
28
+ - `_head.liquid` -- Prism.js CSS for syntax highlighting, Google Fonts (Inter, Merriweather, JetBrains Mono), Tailwind CSS, and the compiled app CSS
29
+ - `_scripts.liquid` -- Prism.js core with autoloader for additional languages, and the compiled Vue SPA JavaScript
30
+
31
+ The template uses four placeholder tokens that are replaced during rendering:
32
+
33
+ - `[[DOCBOOK_DATA]]` -- Document title and metadata
34
+ - `[[DOCBOOK_TOC]]` -- TOC tree and numbering map
35
+ - `[[DOCBOOK_CONTENT]]` -- Section content blocks
36
+ - `[[DOCBOOK_INDEX]]` -- Generated index data
37
+
38
+ ## Vue 3 SPA Frontend
39
+
40
+ The frontend is a Vue 3 application built with Vite and TypeScript, located in `frontend/`. It is compiled to `frontend/dist/` as a single IIFE bundle (`app.iife.js`) and a stylesheet (`app.css`).
41
+
42
+ ### Key Features
43
+
44
+ - **Table of Contents** -- Recursive sidebar navigation with collapsible tree items, type badges (Pt, Ch, App, Gl, Bib, Idx, Pref, Ref), and section numbering
45
+ - **Full-Text Search** -- Powered by https://github.com/nextapps-de/flexsearch[FlexSearch] with forward tokenization, persisted to IndexedDB for instant reloads
46
+ - **Four Themes** -- Day (white), Sepia (warm amber), Night (dark gray), and OLED (pure black) -- each with proper dark mode Tailwind classes
47
+ - **Reading Modes** -- Scroll (continuous), Page (paginated), Chapter (one chapter at a time), and Cards (reference entry cards)
48
+ - **Syntax Highlighting** -- Prism.js with the Tomorrow Night theme and autoloader for on-demand language support
49
+ - **Display Settings** -- Configurable font size (12-32px), font weight (300-700), line height (1.2-2.0), and margins (16-96px), persisted to localStorage
50
+ - **Responsive Design** -- Collapsible sidebar, mobile-friendly navigation, and adaptive content width
51
+
52
+ ### Frontend Components
53
+
54
+ [cols="1,3"]
55
+ |===
56
+ | Component | Purpose
57
+
58
+ | `App.vue` | Root component, loads document data and initializes stores
59
+ | `EbookContainer.vue` | Main layout container with theme application and CSS variables
60
+ | `AppSidebar.vue` | Table of contents sidebar with recursive tree
61
+ | `TocTreeItem.vue` | Recursive TOC item with expand/collapse and type badges
62
+ | `ContentRenderer.vue` | Renders `ContentBlock` trees into HTML
63
+ | `BlockRenderer.vue` | Renders individual `ContentBlock` items (paragraphs, code, lists, etc.)
64
+ | `SectionContent.vue` | Section wrapper with heading and numbering
65
+ | `PartSection.vue` | Part-specific rendering with Roman numeral prefix
66
+ | `ChapterSection.vue` | Chapter-specific rendering with Arabic numeral prefix
67
+ | `AppendixSection.vue` | Appendix-specific rendering with Alpha prefix
68
+ | `ReferenceEntry.vue` | Reference page entry with badge, name, and description sections
69
+ | `EbookTopBar.vue` | Top navigation bar with search and settings toggles
70
+ | `SearchModal.vue` | Full-text search modal with FlexSearch integration
71
+ | `SettingsPanel.vue` | Display settings panel (theme, font, size, margins)
72
+ |===
73
+
74
+ ## Output Modes
75
+
76
+ The gem supports two output modes, chosen by the `output_mode` parameter:
77
+
78
+ ### Single File Mode (`:single_file`)
79
+
80
+ All assets are inlined into one self-contained HTML file. See <<single-file-mode,Single File Mode>> for details.
81
+
82
+ ### Directory Mode (`:directory`)
83
+
84
+ A directory of files suitable for web serving. See <<directory-mode,Directory Mode>> for details.
85
+
86
+ ## Child Pages
87
+
88
+ - <<single-file-mode,Single File Mode>> -- Self-contained HTML with base64 images
89
+ - <<directory-mode,Directory Mode>> -- Multi-file output for web serving
90
+ - <<data-model,Output Data Model>> -- JSON data structures and ContentBlock types
@@ -0,0 +1,125 @@
1
+ ---
2
+ layout: default
3
+ title: Single File Mode
4
+ parent: HTML Output
5
+ grand_parent: Features
6
+ nav_order: 1
7
+ ---
8
+
9
+ # Single File Mode
10
+
11
+ Single file mode (`output_mode: :single_file`) produces a single, self-contained HTML file with all assets inlined. This is the default output mode and is ideal for distributing documents that can be opened directly in any browser, including via the `file://` protocol.
12
+
13
+ ## How It Works
14
+
15
+ When single file mode is active, the `Output::Html` class inlines every asset directly into the HTML:
16
+
17
+ . **CSS** -- The compiled `app.css` from `frontend/dist/` is injected inline via a `<style>` tag
18
+ . **JavaScript** -- The compiled `app.iife.js` from `frontend/dist/` is injected inline via a `<script>` tag
19
+ . **Fonts** -- Google Fonts (Inter, Merriweather, JetBrains Mono) are loaded from CDN in `_head.liquid`
20
+ . **JSON Data** -- All document data (TOC, content, index) is serialized to JSON and injected via `[[DOCBOOK_DATA]]` placeholders, with `</script>` tags escaped to `<\/script>`
21
+
22
+ The Liquid template is rendered with `assets_inline: true`, which causes the partials to inline assets rather than reference external files.
23
+
24
+ ## Image Embedding
25
+
26
+ Images are embedded as base64 data URIs. The `process_image` method in `Output::Html` handles this:
27
+
28
+ [source,ruby]
29
+ ----
30
+ # When output_mode is :single_file, images are base64-encoded
31
+ def process_image(fileref)
32
+ if @output_mode == :single_file
33
+ embed_image_base64(fileref)
34
+ else
35
+ find_file_path(fileref) || fileref
36
+ end
37
+ end
38
+
39
+ def embed_image_base64(fileref)
40
+ full_path = find_file_path(fileref)
41
+ data = File.binread(full_path)
42
+ mime = Marcel::MimeType.for(data) # MIME detection via Marcel
43
+ encoded = Base64.strict_encode64(data)
44
+ "data:#{mime};base64,#{encoded}"
45
+ end
46
+ ----
47
+
48
+ The MIME type is detected automatically using the https://github.com/rails/marcel[Marcel] gem, which examines the file's magic bytes rather than relying on file extensions. This ensures correct MIME types even when file extensions are missing or incorrect.
49
+
50
+ Image paths are resolved by searching the document's base path and its parent directories, trying common prefixes like `resources/` and `../resources/`. This accommodates the various directory structures used in DocBook projects.
51
+
52
+ ## Data Injection
53
+
54
+ The template contains four placeholder tokens that are replaced with inline JSON:
55
+
56
+ [source,html]
57
+ ----
58
+ <script>
59
+ window.DOCBOOK_DATA = Object.assign([[DOCBOOK_DATA]], {
60
+ toc: [[DOCBOOK_TOC]],
61
+ content: [[DOCBOOK_CONTENT]],
62
+ index: [[DOCBOOK_INDEX]]
63
+ });
64
+ </script>
65
+ ----
66
+
67
+ In single file mode, the placeholders are replaced with actual JSON data. The Vue SPA's `documentStore` detects `window.DOCBOOK_DATA` and loads the data synchronously without any network requests.
68
+
69
+ ## Use Case
70
+
71
+ Single file mode is designed for:
72
+
73
+ - **Document distribution** -- Send a single HTML file via email or file share
74
+ - **Offline reading** -- Works without a web server; open directly from the filesystem
75
+ - **Archival** -- Self-contained documents with no external dependencies (except CDN fonts)
76
+ - **Email attachments** -- One file to attach, no directory structures to manage
77
+
78
+ ## CLI Usage
79
+
80
+ ```bash
81
+ # Default: single file mode, output to stdout
82
+ docbook to-html document.xml
83
+
84
+ # Write to a file
85
+ docbook to-html document.xml -o output.html
86
+
87
+ # Explicit single file mode
88
+ docbook to-html document.xml -o output.html --output-type single_file
89
+ ```
90
+
91
+ ## Programmatic Usage
92
+
93
+ [source,ruby]
94
+ ----
95
+ require "docbook"
96
+
97
+ document = Docbook::Document.from_xml(File.read("book.xml"))
98
+ xref_resolver = Docbook::XrefResolver.new(document).resolve!
99
+ base_path = File.dirname(File.expand_path("book.xml"))
100
+
101
+ html = Docbook::Output::Html.new(
102
+ document,
103
+ xref_resolver: xref_resolver,
104
+ output_mode: :single_file,
105
+ base_path: base_path
106
+ ).to_html
107
+
108
+ File.write("book.html", html)
109
+ ----
110
+
111
+ ## Template Rendering Details
112
+
113
+ The Liquid template is rendered with these variables in single file mode:
114
+
115
+ [cols="1,2,3"]
116
+ |===
117
+ | Variable | Value | Purpose
118
+
119
+ | `docbook_title` | Document title | Page `<title>` element
120
+ | `base_url` | `""` (empty string) | Relative URL base (not needed for single file)
121
+ | `assets_inline` | `true` | Controls whether CSS/JS are inlined or linked
122
+ | `app_css` | Contents of `frontend/dist/app.css` | Inlined as `<style>` tag
123
+ | `app_js` | Contents of `frontend/dist/app.iife.js` | Inlined as `<script>` tag
124
+ | `data_url` | Not used | Only relevant for directory mode
125
+ |===
@@ -0,0 +1,197 @@
1
+ ---
2
+ layout: default
3
+ title: Index Generation
4
+ parent: Features
5
+ nav_order: 5
6
+ ---
7
+
8
+ # Index Generation
9
+
10
+ The index generation system collects `indexterm` elements from throughout the document, groups them by first letter, sorts them, and produces a structured index that the Vue SPA renders as a navigable back-of-book index.
11
+
12
+ ## Architecture
13
+
14
+ Index generation uses two classes working in sequence:
15
+
16
+ - **`IndexCollector`** -- Traverses the entire document tree to collect `indexterm` elements
17
+ - **`IndexGenerator`** -- Groups collected terms by letter, sorts them, and builds the final index structure
18
+
19
+ ## IndexCollector
20
+
21
+ The `IndexCollector` performs a recursive traversal of the document, visiting every element type that can contain `indexterm` elements.
22
+
23
+ ### Traversal Scope
24
+
25
+ The collector traverses all major structural elements:
26
+
27
+ [cols="1,2"]
28
+ |===
29
+ | Element | Traversed Children
30
+
31
+ | `Book` | parts, chapters, appendices, prefaces, glossary, bibliography, index, setindex
32
+ | `Part` | parts, chapters, appendices, references, prefaces, glossary, bibliography, index
33
+ | `Article` | sections, glossary, bibliography, index
34
+ | `Chapter` | sections, simplesects, paras, indexterms
35
+ | `Appendix` | sections, simplesects, paras, indexterms
36
+ | `Section` | sections, simplesects, paras, indexterms
37
+ | `SimpleSect` | paras, indexterms
38
+ | `RefEntry` | refsections
39
+ | `RefSection` | paras, indexterms
40
+ | `ListItem` | paras, simplesects, indexterms
41
+ | `Entry` (table) | paras, indexterms
42
+ | `Para` | mixed content (handles inline indexterms)
43
+ |===
44
+
45
+ ### Term Extraction
46
+
47
+ For each `indexterm` found, the collector extracts:
48
+
49
+ [source,ruby]
50
+ ----
51
+ term_info = {
52
+ primary: primary_text, # Required: primary term text
53
+ primary_sort: sort_key, # Sort key (lowercased or "SYMBOLS")
54
+ secondary: secondaries.first, # Optional: secondary term
55
+ tertiary: tertiaries.first, # Optional: tertiary term
56
+ sees: sees, # "see" cross-references
57
+ see_alsos: see_alsos, # "see also" cross-references
58
+ type: indexterm.type, # Optional type filter
59
+ section_id: section_info[:id], # xml:id of containing section
60
+ section_title: section_info[:title] # Title of containing section
61
+ }
62
+ ----
63
+ ----
64
+
65
+ ### Sort Keys
66
+
67
+ Sort keys determine grouping and ordering:
68
+
69
+ - **Non-alphabetic terms** -- Terms starting with symbols or having `class="token"` are grouped under `SYMBOLS`
70
+ - **Alphabetic terms** -- Leading punctuation is stripped and the text is lowercased
71
+
72
+ [source,ruby]
73
+ ----
74
+ def sort_key(text, indexterm)
75
+ return "SYMBOLS" if indexterm.class_value == "token" || text.start_with?("@")
76
+ text.gsub(/^[^a-zA-Z]+/, "").downcase
77
+ end
78
+ ----
79
+ ----
80
+
81
+ ## IndexGenerator
82
+
83
+ The `IndexGenerator` takes the collected terms and groups them:
84
+
85
+ ### Grouping by Letter
86
+
87
+ Terms are grouped by the first letter of their sort key:
88
+
89
+ [source,ruby]
90
+ ----
91
+ def group_by_letter
92
+ result = {}
93
+ @index_terms.each do |term|
94
+ letter = letter_for(term[:primary_sort])
95
+ result[letter] ||= []
96
+ result[letter] << term
97
+ end
98
+ result
99
+ end
100
+
101
+ def letter_for(sort_key)
102
+ return "SYMBOLS" if sort_key == "SYMBOLS" || sort_key.start_with?("@")
103
+ first_char = sort_key.chars.find(&:letter?) || sort_key[0] || ""
104
+ first_char.upcase
105
+ end
106
+ ----
107
+ ----
108
+
109
+ ### Sorting
110
+
111
+ Within each letter group, terms are sorted by primary sort key and then by secondary term:
112
+
113
+ [source,ruby]
114
+ ----
115
+ def sort_entries(terms)
116
+ terms.sort_by { |t| [t[:primary_sort], t[:secondary].to_s] }
117
+ end
118
+ ----
119
+ ----
120
+
121
+ The `SYMBOLS` group sorts first by using `"{"` (ASCII after "Z") as its sort key:
122
+
123
+ [source,ruby]
124
+ ----
125
+ by_letter.sort_by { |letter, _| letter == "SYMBOLS" ? "{" : letter.downcase }
126
+ ----
127
+ ----
128
+
129
+ ## Index Model
130
+
131
+ The final index is structured as:
132
+
133
+ [source]
134
+ ----
135
+ Index
136
+ title: "Index"
137
+ type: nil (or type filter)
138
+ groups:
139
+ - IndexGroup (letter: "A")
140
+ entries:
141
+ - IndexTerm (primary: "Apple", section_id: "fruit-sec", ...)
142
+ - IndexTerm (primary: "Application", secondary: "web", ...)
143
+ - IndexGroup (letter: "B")
144
+ entries: [...]
145
+ - IndexGroup (letter: "SYMBOLS")
146
+ entries:
147
+ - IndexTerm (primary: "@example", sort_as: "SYMBOLS", ...)
148
+ ----
149
+ ----
150
+
151
+ ## Type Filtering
152
+
153
+ The DocBook `index` element supports a `type` attribute for creating multiple specialized indexes. When an `index` element has a `type`, the builder filters `indexterm` elements to only include those with a matching `type`:
154
+
155
+ [source,ruby]
156
+ ----
157
+ def build_index_content(index_element, all_index_terms)
158
+ index_type = index_element.type
159
+ filtered_terms = all_index_terms.select { |t| t[:type] == index_type }
160
+ # ...
161
+ end
162
+ ----
163
+ ----
164
+
165
+ ## Cross-References
166
+
167
+ Index terms support two cross-reference mechanisms:
168
+
169
+ - **`see`** -- Directs the reader to a different primary term ("see [term]")
170
+ - **`seealso`** -- Suggests additional related terms ("see also [term]")
171
+
172
+ These are collected from `see` and `seealso` child elements within `indexterm`:
173
+
174
+ [source,xml]
175
+ ----
176
+ <indexterm>
177
+ <primary>cars</primary>
178
+ <see>automobiles</see>
179
+ </indexterm>
180
+
181
+ <indexterm>
182
+ <primary>automobiles</primary>
183
+ <secondary>electric</secondary>
184
+ <seealso>hybrid vehicles</seealso>
185
+ </indexterm>
186
+ ----
187
+
188
+ ## Index Content Rendering
189
+
190
+ The index is rendered in the Vue SPA using `ContentBlock` trees with dedicated block types:
191
+
192
+ - `index_section` -- Container for an index section
193
+ - `index_letter` -- Letter group heading with child entries
194
+ - `index_entry` -- Individual term with primary text
195
+ - `index_reference` -- Link to the section where the term appears
196
+ - `index_see` -- "see" cross-reference
197
+ - `index_see_also` -- "see also" cross-reference
@@ -0,0 +1,63 @@
1
+ ---
2
+ layout: default
3
+ title: Features
4
+ nav_order: 4
5
+ has_children: true
6
+ ---
7
+
8
+ # Features
9
+
10
+ The Metanorma DocBook gem provides a complete pipeline for converting DocBook XML documents into modern, interactive HTML publications. It parses DocBook XML through a typed element model, resolves cross-references and XIncludes, and produces structured data consumed by a Vue 3 single-page application frontend.
11
+
12
+ ## Feature Overview
13
+
14
+ [cols="3,4,5"]
15
+ |===
16
+ | Feature | Description | Details
17
+
18
+ | <<html-output/index,HTML Output>>
19
+ | Converts DocBook XML to interactive HTML with a Vue 3 SPA frontend, supporting both single-file and directory output modes.
20
+ | Two modes: *single file* (self-contained, base64 images) and *directory* (multi-file, web-serving ready)
21
+
22
+ | <<xinclude/index,XInclude Resolution>>
23
+ | Resolves `xi:include` elements for modular document composition, supporting both XML and text parsing modes with advanced filtering.
24
+ | Custom iterative resolver with `search=`, `line=`, and `char=` fragid schemes
25
+
26
+ | <<numbering,Section Numbering>>
27
+ | Automatic numbering of Parts (Roman), Chapters (Arabic), Sections (hierarchical), and Appendices (Alpha) with proper scoping.
28
+ | `NumberingBuilder` with per-part chapter scoping and hierarchical dot notation
29
+
30
+ | <<toc-generation,TOC Generation>>
31
+ | Builds a recursive tree of document sections with type metadata, numbering, and navigation badges.
32
+ | `SectionData` tree with type badges (Pt, Ch, App, Gl, Bib, Idx, Pref, Ref)
33
+
34
+ | <<index-generation,Index Generation>>
35
+ | Collects `indexterm` elements throughout the document and generates a grouped, sorted index with cross-references.
36
+ | `IndexCollector` + `IndexGenerator` with `see`/`seealso` support and SYMBOLS grouping
37
+
38
+ | <<element-coverage,Element Coverage>>
39
+ | Comprehensive support for 157 DocBook element classes organized across 20+ categories.
40
+ | Structural, metadata, block, list, link, inline, media, table, admonition, bibliography, glossary, index, and more
41
+ |===
42
+
43
+ ## Architecture
44
+
45
+ The processing pipeline follows these stages:
46
+
47
+ . **Parse** -- DocBook XML is parsed into Lutaml::Model-based element classes (`Docbook::Document.from_xml`)
48
+ . **Resolve XIncludes** -- `XIncludeResolver` replaces `xi:include` elements iteratively, handling nested includes
49
+ . **Resolve XRefs** -- `XrefResolver` builds an O(1) lookup map of `xml:id` to element titles
50
+ . **Build Output Data** -- `Output::Html` traverses the element tree to produce a `DocbookOutput` model containing:
51
+ * `Toc` -- section tree with numbering
52
+ * `ContentData` -- section content as `ContentBlock` trees
53
+ * `Index` -- grouped index entries
54
+ . **Render** -- A Liquid template produces the HTML shell, and the Vue 3 SPA renders the content client-side
55
+
56
+ ## Child Pages
57
+
58
+ - <<html-output/index,HTML Output>> -- Single-file and directory output modes, data model
59
+ - <<xinclude/index,XInclude Resolution>> -- XML includes, text includes, fragid schemes
60
+ - <<numbering,Section Numbering>> -- Roman, Arabic, hierarchical, and Alpha numbering
61
+ - <<toc-generation,TOC Generation>> -- Table of contents tree structure and rendering
62
+ - <<index-generation,Index Generation>> -- Index collection, grouping, and cross-references
63
+ - <<element-coverage,Element Coverage>> -- Full list of supported DocBook element classes