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,138 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docbook
|
|
4
|
+
module Services
|
|
5
|
+
# Generates hierarchical TOC from document sections
|
|
6
|
+
class TocGenerator
|
|
7
|
+
def initialize(document)
|
|
8
|
+
@document = document
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def generate
|
|
12
|
+
root_elements = get_root_elements
|
|
13
|
+
root_elements.map { |element| build_toc_node(element) }.compact
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def get_root_elements
|
|
19
|
+
case @document
|
|
20
|
+
when Elements::Book
|
|
21
|
+
roots = []
|
|
22
|
+
roots.concat(Array(@document.part))
|
|
23
|
+
roots.concat(Array(@document.chapter))
|
|
24
|
+
roots.concat(Array(@document.appendix))
|
|
25
|
+
roots.concat(Array(@document.preface))
|
|
26
|
+
roots
|
|
27
|
+
when Elements::Article
|
|
28
|
+
roots = []
|
|
29
|
+
roots.concat(Array(@document.section))
|
|
30
|
+
roots.concat(Array(@document.article))
|
|
31
|
+
roots
|
|
32
|
+
else
|
|
33
|
+
Array(@document.elements)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def build_toc_node(element)
|
|
38
|
+
return nil unless can_have_title?(element)
|
|
39
|
+
|
|
40
|
+
node = Models::TocNode.new(
|
|
41
|
+
id: get_id(element),
|
|
42
|
+
title: get_title(element),
|
|
43
|
+
type: element_type(element),
|
|
44
|
+
number: nil # Numbering added separately
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# Recursively add children
|
|
48
|
+
children = get_child_sections(element)
|
|
49
|
+
if children.any?
|
|
50
|
+
node.children = children.map { |child| build_toc_node(child) }.compact
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
node
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def can_have_title?(element)
|
|
57
|
+
element.respond_to?(:title) ||
|
|
58
|
+
element.respond_to?(:info) ||
|
|
59
|
+
element.respond_to?(:refnamediv)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def get_title(element)
|
|
63
|
+
case element
|
|
64
|
+
when Elements::RefEntry
|
|
65
|
+
# Use refname from refnamediv as title
|
|
66
|
+
if element.refnamediv&.refname
|
|
67
|
+
element.refnamediv.refname.map(&:content).join(" ")
|
|
68
|
+
elsif element.refmeta&.refentrytitle
|
|
69
|
+
element.refmeta.refentrytitle.content
|
|
70
|
+
else
|
|
71
|
+
"Untitled"
|
|
72
|
+
end
|
|
73
|
+
when Elements::Reference
|
|
74
|
+
element.info&.title&.content ||
|
|
75
|
+
element.title&.content ||
|
|
76
|
+
"Reference"
|
|
77
|
+
when Elements::Book, Elements::Article, Elements::Chapter, Elements::Appendix,
|
|
78
|
+
Elements::Preface, Elements::Part, Elements::Section
|
|
79
|
+
element.info&.title&.content ||
|
|
80
|
+
element.title&.content ||
|
|
81
|
+
"Untitled"
|
|
82
|
+
else
|
|
83
|
+
"Untitled"
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def get_id(element)
|
|
88
|
+
element.xml_id || generate_id(element)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def generate_id(element)
|
|
92
|
+
# Generate a stable ID from class name
|
|
93
|
+
"section-#{element.class.name.split('::').last.downcase}"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def element_type(element)
|
|
97
|
+
element.class.name.split('::').last.downcase
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def get_child_sections(element)
|
|
101
|
+
children = []
|
|
102
|
+
|
|
103
|
+
case element
|
|
104
|
+
when Elements::Book
|
|
105
|
+
# Book has specific child element collections
|
|
106
|
+
children.concat(Array(element.part))
|
|
107
|
+
children.concat(Array(element.chapter))
|
|
108
|
+
children.concat(Array(element.appendix))
|
|
109
|
+
children.concat(Array(element.preface))
|
|
110
|
+
when Elements::Article
|
|
111
|
+
children.concat(Array(element.section))
|
|
112
|
+
children.concat(Array(element.article))
|
|
113
|
+
when Elements::Part
|
|
114
|
+
children.concat(Array(element.chapter))
|
|
115
|
+
children.concat(Array(element.appendix))
|
|
116
|
+
when Elements::Chapter, Elements::Appendix, Elements::Preface
|
|
117
|
+
children.concat(Array(element.section))
|
|
118
|
+
when Elements::Section
|
|
119
|
+
children.concat(Array(element.section))
|
|
120
|
+
when Elements::Reference
|
|
121
|
+
children.concat(Array(element.refentry))
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
children.select { |e| section_like?(e) }
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def section_like?(element)
|
|
128
|
+
element.is_a?(Elements::Section) ||
|
|
129
|
+
element.is_a?(Elements::Chapter) ||
|
|
130
|
+
element.is_a?(Elements::Appendix) ||
|
|
131
|
+
element.is_a?(Elements::Part) ||
|
|
132
|
+
element.is_a?(Elements::Reference) ||
|
|
133
|
+
element.is_a?(Elements::RefEntry) ||
|
|
134
|
+
element.is_a?(Elements::Preface)
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docbook
|
|
4
|
+
module Services
|
|
5
|
+
# Service classes for data generation
|
|
6
|
+
SERVICES_DIR = "#{__dir__}/services"
|
|
7
|
+
|
|
8
|
+
autoload :TocGenerator, "#{SERVICES_DIR}/toc_generator"
|
|
9
|
+
autoload :IndexGenerator, "#{SERVICES_DIR}/index_generator"
|
|
10
|
+
autoload :NumberingService, "#{SERVICES_DIR}/numbering_service"
|
|
11
|
+
autoload :ElementToHash, "#{SERVICES_DIR}/element_to_hash"
|
|
12
|
+
autoload :DocumentBuilder, "#{SERVICES_DIR}/document_builder"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en" class="scroll-smooth">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>{{ docbook_title | escape }}</title>
|
|
7
|
+
{% include 'partials/head' %}
|
|
8
|
+
</head>
|
|
9
|
+
<body class="bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100">
|
|
10
|
+
<div id="docbook-app"></div>
|
|
11
|
+
<script>
|
|
12
|
+
window.DOCBOOK_DATA = Object.assign([[DOCBOOK_DATA]], {
|
|
13
|
+
toc: [[DOCBOOK_TOC]],
|
|
14
|
+
content: [[DOCBOOK_CONTENT]],
|
|
15
|
+
index: [[DOCBOOK_INDEX]]
|
|
16
|
+
});
|
|
17
|
+
</script>
|
|
18
|
+
{% include 'partials/scripts' %}
|
|
19
|
+
</body>
|
|
20
|
+
</html>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{% comment %}Prism.js for syntax highlighting{% endcomment %}
|
|
2
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css">
|
|
3
|
+
|
|
4
|
+
{% comment %}Google Fonts{% endcomment %}
|
|
5
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Merriweather:ital,wght@0,400;0,700;1,400&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
6
|
+
|
|
7
|
+
{% comment %}Tailwind CDN{% endcomment %}
|
|
8
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
9
|
+
<script>
|
|
10
|
+
tailwind.config = {
|
|
11
|
+
darkMode: 'class',
|
|
12
|
+
theme: {
|
|
13
|
+
extend: {
|
|
14
|
+
fontFamily: {
|
|
15
|
+
sans: ['Inter', 'system-ui', 'sans-serif'],
|
|
16
|
+
serif: ['Merriweather', 'Georgia', 'serif'],
|
|
17
|
+
mono: ['JetBrains Mono', 'ui-monospace', 'monospace'],
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
}
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
{% comment %}Custom styles{% endcomment %}
|
|
25
|
+
<style>
|
|
26
|
+
[x-cloak] { display: none !important; }
|
|
27
|
+
.toc-active { color: #2563eb; background: #dbeafe; }
|
|
28
|
+
.dark .toc-active { color: #60a5fa; background: #1e3a5f; }
|
|
29
|
+
.db-bibentry { padding-left: 2em; text-indent: -2em; margin-bottom: 0.5em; }
|
|
30
|
+
.db-appendix { border-left: 4px solid #8b5cf6; padding-left: 1rem; margin-left: -1rem; }
|
|
31
|
+
.dark .db-appendix { border-left-color: #a78bfa; }
|
|
32
|
+
</style>
|
|
33
|
+
|
|
34
|
+
{% comment %}App CSS - inline for single_file, linked for directory{% endcomment %}
|
|
35
|
+
{% if assets_inline %}
|
|
36
|
+
<style>{{ app_css }}</style>
|
|
37
|
+
{% else %}
|
|
38
|
+
<link rel="stylesheet" href="{{ base_url }}/assets/app.css">
|
|
39
|
+
{% endif %}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{% comment %}Prism.js core + autoloader for additional languages{% endcomment %}
|
|
2
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script>
|
|
3
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/autoloader/prism-autoloader.min.js"></script>
|
|
4
|
+
|
|
5
|
+
{% comment %}App JS - inline for single_file, linked for directory{% endcomment %}
|
|
6
|
+
{% if assets_inline %}
|
|
7
|
+
<script>{{ app_js }}</script>
|
|
8
|
+
{% else %}
|
|
9
|
+
<script src="{{ base_url }}/assets/app.iife.js"></script>
|
|
10
|
+
{% endif %}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "nokogiri"
|
|
4
|
+
|
|
5
|
+
module Docbook
|
|
6
|
+
class XIncludeResolver
|
|
7
|
+
XINCLUDE_NS = "http://www.w3.org/2001/XInclude"
|
|
8
|
+
|
|
9
|
+
# Resolve XInclude elements in a Nokogiri document.
|
|
10
|
+
# Handles standard XML includes, text includes, and text+fragid extensions.
|
|
11
|
+
# Processes iteratively to handle nested includes (e.g., file A includes file B
|
|
12
|
+
# which contains text+fragid self-references).
|
|
13
|
+
def self.resolve(doc)
|
|
14
|
+
loop do
|
|
15
|
+
includes = doc.xpath("//xi:include", "xi" => XINCLUDE_NS)
|
|
16
|
+
break if includes.empty?
|
|
17
|
+
|
|
18
|
+
resolved_any = false
|
|
19
|
+
includes.each do |inc|
|
|
20
|
+
href = inc["href"]
|
|
21
|
+
next unless href
|
|
22
|
+
|
|
23
|
+
parse_mode = inc["parse"] || "xml"
|
|
24
|
+
fragid = inc["fragid"]
|
|
25
|
+
base_dir = base_dir_for(inc.document)
|
|
26
|
+
file_path = resolve_path(base_dir, href)
|
|
27
|
+
next unless file_path && File.exist?(file_path)
|
|
28
|
+
|
|
29
|
+
if parse_mode == "text"
|
|
30
|
+
resolve_text_include(doc, inc, file_path, fragid)
|
|
31
|
+
else
|
|
32
|
+
resolve_xml_include(doc, inc, file_path)
|
|
33
|
+
end
|
|
34
|
+
resolved_any = true
|
|
35
|
+
break # re-scan after each resolve (tree changes)
|
|
36
|
+
end
|
|
37
|
+
break unless resolved_any
|
|
38
|
+
end
|
|
39
|
+
doc
|
|
40
|
+
rescue StandardError
|
|
41
|
+
doc
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Pre-process an XML string to resolve XIncludes before model parsing.
|
|
45
|
+
def self.resolve_string(xml_string, base_path: nil)
|
|
46
|
+
doc = if base_path
|
|
47
|
+
file_uri = "file://#{File.expand_path(base_path)}"
|
|
48
|
+
Nokogiri::XML(xml_string, file_uri)
|
|
49
|
+
else
|
|
50
|
+
Nokogiri::XML(xml_string)
|
|
51
|
+
end
|
|
52
|
+
resolve(doc)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# ── Include Resolution ──────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
def self.resolve_text_include(doc, inc, file_path, fragid)
|
|
58
|
+
content = File.read(file_path)
|
|
59
|
+
if fragid && !fragid.empty?
|
|
60
|
+
filtered = apply_fragid(content, fragid)
|
|
61
|
+
content = filtered if filtered
|
|
62
|
+
end
|
|
63
|
+
text_node = doc.create_text_node(content)
|
|
64
|
+
inc.replace(text_node)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def self.resolve_xml_include(doc, inc, file_path)
|
|
68
|
+
included_xml = File.read(file_path)
|
|
69
|
+
included_doc = Nokogiri::XML(included_xml, "file://#{File.expand_path(file_path)}")
|
|
70
|
+
|
|
71
|
+
root = included_doc.root
|
|
72
|
+
# Ensure namespace is declared in target document
|
|
73
|
+
if root.namespace
|
|
74
|
+
ns = root.namespace
|
|
75
|
+
existing = doc.root.namespace_definitions.find { |n| n.href == ns.href }
|
|
76
|
+
doc.root.add_namespace_definition(ns.prefix, ns.href) unless existing
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Replace the xi:include with the root element itself (not its children)
|
|
80
|
+
inc.replace(root.dup)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def self.base_dir_for(document)
|
|
84
|
+
url = document.url
|
|
85
|
+
return nil unless url
|
|
86
|
+
File.dirname(url.sub(%r{^file://}, ""))
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def self.resolve_path(base_dir, href)
|
|
90
|
+
return nil unless base_dir
|
|
91
|
+
File.join(base_dir, href)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# ── Fragid Filtering ──────────────────────────────────────────
|
|
95
|
+
|
|
96
|
+
# Apply a fragid filter to text content
|
|
97
|
+
def self.apply_fragid(content, fragid)
|
|
98
|
+
if fragid.start_with?("search=")
|
|
99
|
+
apply_search_fragid(content, fragid[7..])
|
|
100
|
+
elsif fragid.start_with?("line=")
|
|
101
|
+
apply_line_fragid(content, fragid[5..])
|
|
102
|
+
elsif fragid.start_with?("char=")
|
|
103
|
+
apply_char_fragid(content, fragid[5..])
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Apply search= fragid scheme (DocBook xslTNG extension)
|
|
108
|
+
# Syntax: search=#PATTERN#[;after|;before],#STOP#
|
|
109
|
+
# or: search=/REGEX/[,/STOP/]
|
|
110
|
+
def self.apply_search_fragid(content, search_spec)
|
|
111
|
+
lines = content.lines
|
|
112
|
+
if search_spec.start_with?("/")
|
|
113
|
+
apply_delimited_search(lines, search_spec, "/")
|
|
114
|
+
elsif search_spec.start_with?("#")
|
|
115
|
+
apply_delimited_search(lines, search_spec, "#")
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Parse delimited patterns and apply search
|
|
120
|
+
# Delimiter is # (literal) or / (regex)
|
|
121
|
+
def self.apply_delimited_search(lines, spec, delim)
|
|
122
|
+
# Parse: DELIM pattern DELIM [;modifier] [, DELIM stop DELIM]
|
|
123
|
+
remainder = spec[1..] # skip opening delimiter
|
|
124
|
+
close_idx = remainder.index(delim)
|
|
125
|
+
return nil unless close_idx
|
|
126
|
+
|
|
127
|
+
pattern_str = remainder[0...close_idx]
|
|
128
|
+
remainder = remainder[(close_idx + 1)..]
|
|
129
|
+
|
|
130
|
+
# Check for modifier (;after or ;before)
|
|
131
|
+
modifier = nil
|
|
132
|
+
if remainder&.start_with?(";")
|
|
133
|
+
mod_match = remainder.match(/\A;(after|before)/)
|
|
134
|
+
if mod_match
|
|
135
|
+
modifier = mod_match[1].to_sym
|
|
136
|
+
remainder = remainder[mod_match[0].length..]
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Skip comma separator
|
|
141
|
+
remainder = remainder[1..] if remainder&.start_with?(",")
|
|
142
|
+
|
|
143
|
+
# Parse stop pattern if present
|
|
144
|
+
stop_pattern = nil
|
|
145
|
+
if remainder && !remainder.empty? && remainder.start_with?(delim)
|
|
146
|
+
stop_remainder = remainder[1..]
|
|
147
|
+
stop_close = stop_remainder.index(delim)
|
|
148
|
+
if stop_close
|
|
149
|
+
stop_pattern = stop_remainder[0...stop_close]
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Apply search
|
|
154
|
+
match_fn = delim == "/" ? ->(line, pat) { line.match?(Regexp.new(pat)) }
|
|
155
|
+
: ->(line, pat) { line.include?(pat) }
|
|
156
|
+
|
|
157
|
+
start_idx = lines.index { |l| match_fn.call(l, pattern_str) }
|
|
158
|
+
return nil unless start_idx
|
|
159
|
+
|
|
160
|
+
# Determine range
|
|
161
|
+
case modifier
|
|
162
|
+
when :after
|
|
163
|
+
range_start = start_idx + 1
|
|
164
|
+
when :before
|
|
165
|
+
range_start = 0
|
|
166
|
+
range_end = start_idx
|
|
167
|
+
else
|
|
168
|
+
range_start = start_idx
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Find stop (exclusive)
|
|
172
|
+
if stop_pattern
|
|
173
|
+
stop_slice = lines[range_start..]
|
|
174
|
+
stop_idx = stop_slice&.index { |l| match_fn.call(l, stop_pattern) }
|
|
175
|
+
range_end = stop_idx ? range_start + stop_idx : lines.length
|
|
176
|
+
elsif modifier != :before
|
|
177
|
+
range_end = start_idx + 1
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
range_end ||= lines.length
|
|
181
|
+
selected = lines[range_start...range_end]
|
|
182
|
+
return nil unless selected && !selected.empty?
|
|
183
|
+
|
|
184
|
+
selected.join
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Apply line= fragid scheme (RFC 5147)
|
|
188
|
+
# Syntax: line=START,END[;length=N]
|
|
189
|
+
def self.apply_line_fragid(content, line_spec)
|
|
190
|
+
parts = line_spec.split(";")
|
|
191
|
+
range_part = parts[0]
|
|
192
|
+
length_part = parts.find { |p| p.start_with?("length=") }
|
|
193
|
+
|
|
194
|
+
line_start, line_end = range_part.split(",").map(&:to_i)
|
|
195
|
+
return nil unless line_start && line_end
|
|
196
|
+
|
|
197
|
+
lines = content.lines
|
|
198
|
+
selected = lines[(line_start - 1)...(line_end)]
|
|
199
|
+
return nil unless selected
|
|
200
|
+
|
|
201
|
+
result = selected.join
|
|
202
|
+
if length_part
|
|
203
|
+
max_len = length_part.split("=")[1].to_i
|
|
204
|
+
result = result[0...max_len] if max_len > 0
|
|
205
|
+
end
|
|
206
|
+
result
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Apply char= fragid scheme (RFC 5147)
|
|
210
|
+
# Syntax: char=START,END
|
|
211
|
+
def self.apply_char_fragid(content, char_spec)
|
|
212
|
+
char_start, char_end = char_spec.split(",").map(&:to_i)
|
|
213
|
+
return nil unless char_start && char_end
|
|
214
|
+
content[char_start...char_end]
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docbook
|
|
4
|
+
# Resolves xrefs in a DocBook document by building an O(1) xml:id lookup hash
|
|
5
|
+
# and resolving all xref/linkend references to their target titles.
|
|
6
|
+
class XrefResolver
|
|
7
|
+
def initialize(document)
|
|
8
|
+
@document = document
|
|
9
|
+
@xml_id_map = {}
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Build the xml:id -> element hash
|
|
13
|
+
def resolve!
|
|
14
|
+
@xml_id_map = build_xml_id_map(@document)
|
|
15
|
+
self
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Get the resolved title text for a linkend ID
|
|
19
|
+
def title_for(linkend)
|
|
20
|
+
target = @xml_id_map[linkend.to_s]
|
|
21
|
+
return nil unless target
|
|
22
|
+
best_title(target)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Get element by xml:id
|
|
26
|
+
def [](xml_id)
|
|
27
|
+
@xml_id_map[xml_id.to_s]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def build_xml_id_map(el, map = {})
|
|
33
|
+
return map unless el.is_a?(Lutaml::Model::Serializable)
|
|
34
|
+
|
|
35
|
+
map[el.xml_id.to_s] = el if el.xml_id
|
|
36
|
+
|
|
37
|
+
# Walk child collections based on element type
|
|
38
|
+
case el
|
|
39
|
+
when Docbook::Elements::Book
|
|
40
|
+
el.part&.each { |p| build_xml_id_map(p, map) }
|
|
41
|
+
el.chapter&.each { |c| build_xml_id_map(c, map) }
|
|
42
|
+
el.appendix&.each { |a| build_xml_id_map(a, map) }
|
|
43
|
+
el.preface&.each { |p| build_xml_id_map(p, map) }
|
|
44
|
+
el.glossary&.each { |g| build_xml_id_map(g, map) }
|
|
45
|
+
el.bibliography&.each { |b| build_xml_id_map(b, map) }
|
|
46
|
+
el.index&.each { |i| build_xml_id_map(i, map) }
|
|
47
|
+
when Docbook::Elements::Article
|
|
48
|
+
el.section&.each { |s| build_xml_id_map(s, map) }
|
|
49
|
+
when Docbook::Elements::Part
|
|
50
|
+
el.chapter&.each { |c| build_xml_id_map(c, map) }
|
|
51
|
+
el.appendix&.each { |a| build_xml_id_map(a, map) }
|
|
52
|
+
el.reference&.each { |r| build_xml_id_map(r, map) }
|
|
53
|
+
el.glossary&.each { |g| build_xml_id_map(g, map) }
|
|
54
|
+
el.bibliography&.each { |b| build_xml_id_map(b, map) }
|
|
55
|
+
el.index&.each { |i| build_xml_id_map(i, map) }
|
|
56
|
+
when Docbook::Elements::Chapter, Docbook::Elements::Appendix,
|
|
57
|
+
Docbook::Elements::Section, Docbook::Elements::Preface
|
|
58
|
+
el.section&.each { |s| build_xml_id_map(s, map) }
|
|
59
|
+
when Docbook::Elements::Reference
|
|
60
|
+
el.refentry&.each { |r| build_xml_id_map(r, map) }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
map
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def best_title(el)
|
|
67
|
+
case el
|
|
68
|
+
when Docbook::Elements::Article, Docbook::Elements::Book
|
|
69
|
+
el.info&.title&.content
|
|
70
|
+
when Docbook::Elements::Section, Docbook::Elements::Chapter, Docbook::Elements::Appendix,
|
|
71
|
+
Docbook::Elements::Preface, Docbook::Elements::Part, Docbook::Elements::Reference
|
|
72
|
+
el.title&.content
|
|
73
|
+
else
|
|
74
|
+
el.title&.content rescue nil
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
data/lib/docbook.rb
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "lutaml/model"
|
|
4
|
+
|
|
5
|
+
module Docbook
|
|
6
|
+
class Error < StandardError; end
|
|
7
|
+
|
|
8
|
+
autoload :Version, "#{__dir__}/docbook/version"
|
|
9
|
+
autoload :Elements, "#{__dir__}/docbook/elements"
|
|
10
|
+
autoload :Models, "#{__dir__}/docbook/models"
|
|
11
|
+
autoload :Services, "#{__dir__}/docbook/services"
|
|
12
|
+
autoload :XIncludeResolver, "#{__dir__}/docbook/xinclude_resolver"
|
|
13
|
+
autoload :XrefResolver, "#{__dir__}/docbook/xref_resolver"
|
|
14
|
+
autoload :Output, "#{__dir__}/docbook/output"
|
|
15
|
+
autoload :CLI, "#{__dir__}/docbook/cli"
|
|
16
|
+
autoload :Document, "#{__dir__}/docbook/document"
|
|
17
|
+
end
|
data/sig/docbook.rbs
ADDED