xseed 1.0.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/.github/workflows/rake.yml +16 -0
- data/.github/workflows/release.yml +25 -0
- data/.gitignore +72 -0
- data/.rspec +3 -0
- data/.rubocop.yml +11 -0
- data/.rubocop_todo.yml +432 -0
- data/CHANGELOG.adoc +446 -0
- data/Gemfile +21 -0
- data/LICENSE.adoc +29 -0
- data/README.adoc +386 -0
- data/Rakefile +11 -0
- data/examples/README.adoc +334 -0
- data/examples/advanced_usage.rb +286 -0
- data/examples/html_generation.rb +167 -0
- data/examples/parser_usage.rb +102 -0
- data/examples/schemas/person.xsd +171 -0
- data/examples/simple_generation.rb +149 -0
- data/exe/xseed +6 -0
- data/lib/xseed/cli.rb +376 -0
- data/lib/xseed/documentation/config.rb +101 -0
- data/lib/xseed/documentation/constants.rb +76 -0
- data/lib/xseed/documentation/generators/hierarchy_table_generator.rb +554 -0
- data/lib/xseed/documentation/generators/instance_sample_generator.rb +723 -0
- data/lib/xseed/documentation/generators/properties_table_generator.rb +983 -0
- data/lib/xseed/documentation/html_generator.rb +836 -0
- data/lib/xseed/documentation/html_generator.rb.bak +723 -0
- data/lib/xseed/documentation/presentation/css_generator.rb +510 -0
- data/lib/xseed/documentation/presentation/javascript_generator.rb +151 -0
- data/lib/xseed/documentation/presentation/navigation_builder.rb +169 -0
- data/lib/xseed/documentation/schema_loader.rb +121 -0
- data/lib/xseed/documentation/utils/helpers.rb +205 -0
- data/lib/xseed/documentation/utils/namespaces.rb +149 -0
- data/lib/xseed/documentation/utils/references.rb +135 -0
- data/lib/xseed/documentation/utils/strings.rb +75 -0
- data/lib/xseed/models/element_declaration.rb +144 -0
- data/lib/xseed/parser/xsd_parser.rb +192 -0
- data/lib/xseed/version.rb +5 -0
- data/lib/xseed.rb +76 -0
- data/xseed.gemspec +39 -0
- metadata +158 -0
|
@@ -0,0 +1,836 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "nokogiri"
|
|
4
|
+
require_relative "config"
|
|
5
|
+
require_relative "constants"
|
|
6
|
+
require_relative "utils/references"
|
|
7
|
+
require_relative "utils/namespaces"
|
|
8
|
+
require_relative "utils/helpers"
|
|
9
|
+
require_relative "generators/properties_table_generator"
|
|
10
|
+
require_relative "generators/hierarchy_table_generator"
|
|
11
|
+
require_relative "generators/instance_sample_generator"
|
|
12
|
+
require_relative "presentation/css_generator"
|
|
13
|
+
require_relative "presentation/javascript_generator"
|
|
14
|
+
require_relative "presentation/navigation_builder"
|
|
15
|
+
require_relative "../parser/xsd_parser"
|
|
16
|
+
|
|
17
|
+
module Xseed
|
|
18
|
+
module Documentation
|
|
19
|
+
# Main HTML documentation generator
|
|
20
|
+
# Integrates all content generators for complete schema documentation
|
|
21
|
+
class HtmlGenerator
|
|
22
|
+
include Utils::References
|
|
23
|
+
include Utils::Namespaces
|
|
24
|
+
include Utils::Helpers
|
|
25
|
+
include Constants
|
|
26
|
+
|
|
27
|
+
attr_reader :xsd_file, :config, :parser
|
|
28
|
+
|
|
29
|
+
# Initialize the HTML generator
|
|
30
|
+
#
|
|
31
|
+
# @param xsd_file [String] Path to XSD schema file
|
|
32
|
+
# @param config [Config] Configuration options (optional)
|
|
33
|
+
def initialize(xsd_file, config = Config.new)
|
|
34
|
+
@xsd_file = xsd_file
|
|
35
|
+
@config = config
|
|
36
|
+
@parser = Parser::XsdParser.new(xsd_file)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Generate HTML documentation
|
|
40
|
+
#
|
|
41
|
+
# @return [String] Generated HTML content
|
|
42
|
+
def generate
|
|
43
|
+
builder = Nokogiri::HTML::Builder.new do |html|
|
|
44
|
+
html.html do
|
|
45
|
+
generate_head(html)
|
|
46
|
+
html.body do
|
|
47
|
+
generate_body(html)
|
|
48
|
+
generate_navigation(html)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Replace HTML4 DOCTYPE with HTML5 DOCTYPE
|
|
54
|
+
html_output = builder.to_html.sub(
|
|
55
|
+
/<!DOCTYPE[^>]+>/,
|
|
56
|
+
"<!DOCTYPE html>",
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# Convert HTML void hr tags to XML-style (xs3p compliance)
|
|
60
|
+
html_output.gsub!("<hr>", "<hr></hr>")
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Generate HTML documentation and write to file
|
|
64
|
+
#
|
|
65
|
+
# @param output_path [String] Path to output HTML file
|
|
66
|
+
def generate_file(output_path)
|
|
67
|
+
# Auto-generate SVG diagrams if enabled
|
|
68
|
+
generate_svg_diagrams(output_path) if @config.print_diagrams
|
|
69
|
+
|
|
70
|
+
html_content = generate
|
|
71
|
+
File.write(output_path, html_content)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
# Compactify HTML to match xs3p compact format
|
|
77
|
+
# Removes whitespace between tags while preserving text content
|
|
78
|
+
#
|
|
79
|
+
# @param html [String] HTML content
|
|
80
|
+
# @return [String] Compactified HTML
|
|
81
|
+
def compactify_html(html)
|
|
82
|
+
# Remove only newlines and indentation whitespace between tags
|
|
83
|
+
# This matches xs3p's compact inline format
|
|
84
|
+
html.gsub(/>\n\s*</, "><")
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Generate modal popup divs for element/attribute documentation (xs3p pattern)
|
|
88
|
+
#
|
|
89
|
+
# @param html [Nokogiri::HTML::Builder] HTML builder
|
|
90
|
+
def generate_modal_popups(html)
|
|
91
|
+
modal_counter = 0
|
|
92
|
+
|
|
93
|
+
# Helper to generate a single modal
|
|
94
|
+
generate_modal = lambda do |component_name, component_type, doc_text|
|
|
95
|
+
next unless doc_text && !doc_text.strip.empty?
|
|
96
|
+
|
|
97
|
+
modal_counter += 1
|
|
98
|
+
modal_id = "id#{modal_counter}"
|
|
99
|
+
|
|
100
|
+
html.div(class: "modal fade #{component_name}",
|
|
101
|
+
id: "#{modal_id}-popup",
|
|
102
|
+
tabindex: "-1",
|
|
103
|
+
role: "dialog",
|
|
104
|
+
"aria-hidden": "true") do
|
|
105
|
+
html.div(class: "modal-header") do
|
|
106
|
+
html.button(type: "button",
|
|
107
|
+
class: "close",
|
|
108
|
+
"data-dismiss": "modal",
|
|
109
|
+
"aria-hidden": "true") { html.text "×" }
|
|
110
|
+
html.h4(class: "modal-title", id: "#{modal_id}-label") do
|
|
111
|
+
html.text "#{component_type} #{component_name}"
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
html.div(class: "modal-body") do
|
|
115
|
+
html.div(class: "annotation documentation",
|
|
116
|
+
id: "wdoc-#{modal_id}-hidden") do
|
|
117
|
+
html.div(class: "hidden", id: "#{modal_id}-hidden-doc-raw") do
|
|
118
|
+
html.text doc_text.strip
|
|
119
|
+
end
|
|
120
|
+
html.div(class: "xs3p-doc", id: "#{modal_id}-hidden-doc") do
|
|
121
|
+
html.text " "
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Generate modals for all elements
|
|
129
|
+
parser.elements.each do |element|
|
|
130
|
+
element_name = element["name"]
|
|
131
|
+
next unless element_name
|
|
132
|
+
|
|
133
|
+
doc = extract_documentation(element)
|
|
134
|
+
generate_modal.call(element_name, "Element", doc)
|
|
135
|
+
|
|
136
|
+
# Check for nested elements in inline complexType
|
|
137
|
+
complex_type = element.at_xpath("xs:complexType", "xs" => "http://www.w3.org/2001/XMLSchema")
|
|
138
|
+
if complex_type
|
|
139
|
+
# Get sequence/choice/all children
|
|
140
|
+
%w[sequence choice all].each do |group_type|
|
|
141
|
+
group = complex_type.at_xpath("xs:#{group_type}", "xs" => "http://www.w3.org/2001/XMLSchema")
|
|
142
|
+
next unless group
|
|
143
|
+
|
|
144
|
+
group.xpath(".//xs:element",
|
|
145
|
+
"xs" => "http://www.w3.org/2001/XMLSchema").each do |nested_elem|
|
|
146
|
+
nested_name = nested_elem["name"]
|
|
147
|
+
next unless nested_name
|
|
148
|
+
|
|
149
|
+
nested_doc = extract_documentation(nested_elem)
|
|
150
|
+
generate_modal.call(nested_name, "Element", nested_doc)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Check for attributes
|
|
154
|
+
group.xpath(".//xs:attribute",
|
|
155
|
+
"xs" => "http://www.w3.org/2001/XMLSchema").each do |attr|
|
|
156
|
+
attr_name = attr["name"]
|
|
157
|
+
next unless attr_name
|
|
158
|
+
|
|
159
|
+
attr_doc = extract_documentation(attr)
|
|
160
|
+
generate_modal.call(attr_name, "Attribute", attr_doc)
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Direct attributes on complexType
|
|
165
|
+
complex_type.xpath("xs:attribute",
|
|
166
|
+
"xs" => "http://www.w3.org/2001/XMLSchema").each do |attr|
|
|
167
|
+
attr_name = attr["name"]
|
|
168
|
+
next unless attr_name
|
|
169
|
+
|
|
170
|
+
attr_doc = extract_documentation(attr)
|
|
171
|
+
generate_modal.call(attr_name, "Attribute", attr_doc)
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Generate modals for complex types
|
|
177
|
+
parser.complex_types.each do |type|
|
|
178
|
+
type_name = type["name"]
|
|
179
|
+
next unless type_name
|
|
180
|
+
|
|
181
|
+
# Check for nested elements
|
|
182
|
+
%w[sequence choice all].each do |group_type|
|
|
183
|
+
group = type.at_xpath(".//xs:#{group_type}", "xs" => "http://www.w3.org/2001/XMLSchema")
|
|
184
|
+
next unless group
|
|
185
|
+
|
|
186
|
+
group.xpath(".//xs:element",
|
|
187
|
+
"xs" => "http://www.w3.org/2001/XMLSchema").each do |nested_elem|
|
|
188
|
+
nested_name = nested_elem["name"]
|
|
189
|
+
next unless nested_name
|
|
190
|
+
|
|
191
|
+
nested_doc = extract_documentation(nested_elem)
|
|
192
|
+
generate_modal.call(nested_name, "Element", nested_doc)
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Attributes
|
|
197
|
+
type.xpath(".//xs:attribute",
|
|
198
|
+
"xs" => "http://www.w3.org/2001/XMLSchema").each do |attr|
|
|
199
|
+
attr_name = attr["name"]
|
|
200
|
+
next unless attr_name
|
|
201
|
+
|
|
202
|
+
attr_doc = extract_documentation(attr)
|
|
203
|
+
generate_modal.call(attr_name, "Attribute", attr_doc)
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Extract documentation from XSD node
|
|
209
|
+
def extract_documentation(node)
|
|
210
|
+
doc_node = node.at_xpath("xs:annotation/xs:documentation", "xs" => "http://www.w3.org/2001/XMLSchema")
|
|
211
|
+
doc_node&.text
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Auto-generate SVG diagrams for all elements using xsdvi
|
|
215
|
+
#
|
|
216
|
+
# @param html_output_path [String] Path to HTML output file
|
|
217
|
+
def generate_svg_diagrams(html_output_path)
|
|
218
|
+
require "xsdvi"
|
|
219
|
+
require "fileutils"
|
|
220
|
+
|
|
221
|
+
# Determine diagrams directory relative to HTML output
|
|
222
|
+
output_dir = File.dirname(html_output_path)
|
|
223
|
+
diagrams_path = File.join(output_dir, @config.diagrams_dir)
|
|
224
|
+
FileUtils.mkdir_p(diagrams_path)
|
|
225
|
+
|
|
226
|
+
# Generate SVG for each element
|
|
227
|
+
parser.elements.each do |element|
|
|
228
|
+
element_name = element["name"]
|
|
229
|
+
next unless element_name
|
|
230
|
+
|
|
231
|
+
svg_file = File.join(diagrams_path, "#{element_name}.svg")
|
|
232
|
+
|
|
233
|
+
# Use xsdvi Ruby API
|
|
234
|
+
writer = Xsdvi::Utils::Writer.new(svg_file)
|
|
235
|
+
builder = Xsdvi::Tree::Builder.new
|
|
236
|
+
handler = Xsdvi::XsdHandler.new(builder)
|
|
237
|
+
handler.root_node_name = element_name
|
|
238
|
+
handler.one_node_only = true
|
|
239
|
+
handler.process_file(@xsd_file)
|
|
240
|
+
|
|
241
|
+
root = builder.root
|
|
242
|
+
generator = Xsdvi::SVG::Generator.new(writer)
|
|
243
|
+
generator.hide_menu_buttons = true
|
|
244
|
+
generator.draw(root)
|
|
245
|
+
end
|
|
246
|
+
rescue LoadError
|
|
247
|
+
# xsdvi not available, skip SVG generation
|
|
248
|
+
warn "Warning: xsdvi gem not available, skipping SVG generation"
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# Generate HTML head section
|
|
252
|
+
#
|
|
253
|
+
# @param html [Nokogiri::HTML::Builder] HTML builder
|
|
254
|
+
def generate_head(html)
|
|
255
|
+
html.head do
|
|
256
|
+
html.meta(charset: "UTF-8")
|
|
257
|
+
html.meta(name: "viewport",
|
|
258
|
+
content: "width=device-width, initial-scale=1.0")
|
|
259
|
+
html.title(title)
|
|
260
|
+
generate_styles(html)
|
|
261
|
+
generate_scripts(html)
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# Generate styles (inline or external)
|
|
266
|
+
#
|
|
267
|
+
# @param html [Nokogiri::HTML::Builder] HTML builder
|
|
268
|
+
def generate_styles(html)
|
|
269
|
+
# Load Bootstrap CSS first (needed for modal styling)
|
|
270
|
+
bootstrap_url = @config.bootstrap_url || "https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.4.1"
|
|
271
|
+
html.link(rel: "stylesheet",
|
|
272
|
+
href: "#{bootstrap_url}/css/bootstrap.min.css")
|
|
273
|
+
|
|
274
|
+
css_gen = Presentation::CssGenerator.new(@config)
|
|
275
|
+
|
|
276
|
+
if css_gen.external_css_url
|
|
277
|
+
html.link(rel: "stylesheet", href: css_gen.external_css_url)
|
|
278
|
+
else
|
|
279
|
+
html.style do
|
|
280
|
+
html.text(css_gen.generate)
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
# Generate scripts (inline or external)
|
|
286
|
+
#
|
|
287
|
+
# @param html [Nokogiri::HTML::Builder] HTML builder
|
|
288
|
+
def generate_scripts(html)
|
|
289
|
+
js_gen = Presentation::JavascriptGenerator.new(@config)
|
|
290
|
+
|
|
291
|
+
# jQuery
|
|
292
|
+
html.script(src: js_gen.jquery_url, defer: true) {}
|
|
293
|
+
|
|
294
|
+
# Bootstrap JS (if enabled)
|
|
295
|
+
if js_gen.bootstrap_url
|
|
296
|
+
html.script(src: "#{js_gen.bootstrap_url}/js/bootstrap.min.js",
|
|
297
|
+
defer: true) {}
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
# Custom JavaScript
|
|
301
|
+
html.script(defer: true) do
|
|
302
|
+
html.text(js_gen.generate)
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# Generate navigation sidebar
|
|
307
|
+
#
|
|
308
|
+
# @param html [Nokogiri::HTML::Builder] HTML builder
|
|
309
|
+
def generate_navigation(html)
|
|
310
|
+
nav_builder = Presentation::NavigationBuilder.new(@parser, @config)
|
|
311
|
+
html << nav_builder.generate
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
# Generate HTML body content
|
|
315
|
+
#
|
|
316
|
+
# @param html [Nokogiri::HTML::Builder] HTML builder
|
|
317
|
+
def generate_body(html)
|
|
318
|
+
# Generate modal popups FIRST (xs3p pattern)
|
|
319
|
+
generate_modal_popups(html)
|
|
320
|
+
|
|
321
|
+
# Toggle button as direct child of body
|
|
322
|
+
html.div(id: "toggle") do
|
|
323
|
+
html.span "<"
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
html.main do
|
|
327
|
+
html.div(class: "title-section") do
|
|
328
|
+
html.h1 do
|
|
329
|
+
html.a(id: "top") {}
|
|
330
|
+
html.text title
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
# Schema-level information
|
|
335
|
+
generate_schema_info(html)
|
|
336
|
+
|
|
337
|
+
# Generate documentation with 4 major sections
|
|
338
|
+
generate_component_sections(html)
|
|
339
|
+
|
|
340
|
+
# Glossary section (if enabled)
|
|
341
|
+
generate_glossary(html) if @config.print_glossary
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
# Generate components in 4 major sections (XS3P/XSDVI compliance)
|
|
346
|
+
#
|
|
347
|
+
# @param html [Nokogiri::HTML::Builder] HTML builder
|
|
348
|
+
def generate_component_sections(html)
|
|
349
|
+
# Section 2: Global Elements
|
|
350
|
+
if parser.elements.any?
|
|
351
|
+
html.section(id: "SectionSchemaElements", class: "schema-section") do
|
|
352
|
+
html.h2 do
|
|
353
|
+
html.a(id: "SchemaElements") {}
|
|
354
|
+
html.text "Elements"
|
|
355
|
+
end
|
|
356
|
+
parser.elements.each do |element|
|
|
357
|
+
generate_component_content(html, element, "Element")
|
|
358
|
+
end
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
# Section 3: Complex Types
|
|
363
|
+
complex_types = parser.complex_types
|
|
364
|
+
if complex_types.any?
|
|
365
|
+
html.section(id: "SectionSchemaComplexTypes",
|
|
366
|
+
class: "schema-section") do
|
|
367
|
+
html.h2 do
|
|
368
|
+
html.a(id: "SchemaComplexTypes") {}
|
|
369
|
+
html.text "Complex Types"
|
|
370
|
+
end
|
|
371
|
+
complex_types.each do |type|
|
|
372
|
+
generate_component_content(html, type, "Complex Type")
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
# Section 3b: Simple Types
|
|
378
|
+
simple_types = parser.simple_types
|
|
379
|
+
if simple_types.any?
|
|
380
|
+
html.section(id: "SectionSchemaSimpleTypes",
|
|
381
|
+
class: "schema-section") do
|
|
382
|
+
html.h2 do
|
|
383
|
+
html.a(id: "SchemaSimpleTypes") {}
|
|
384
|
+
html.text "Types"
|
|
385
|
+
end
|
|
386
|
+
simple_types.each do |type|
|
|
387
|
+
generate_component_content(html, type, "Simple Type")
|
|
388
|
+
end
|
|
389
|
+
end
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
# Section 4: Attribute Groups
|
|
393
|
+
attr_groups = parser.attribute_groups
|
|
394
|
+
if attr_groups.any?
|
|
395
|
+
html.section(id: "SectionSchemaAttributeGroups",
|
|
396
|
+
class: "schema-section") do
|
|
397
|
+
html.h2 do
|
|
398
|
+
html.a(id: "SchemaAttributeGroups") {}
|
|
399
|
+
html.text "Attribute Groups"
|
|
400
|
+
end
|
|
401
|
+
attr_groups.each do |component|
|
|
402
|
+
generate_component_content(html, component, "Attribute Group")
|
|
403
|
+
end
|
|
404
|
+
end
|
|
405
|
+
end
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
# Get component type label
|
|
409
|
+
#
|
|
410
|
+
# @param node_name [String] XML node name
|
|
411
|
+
# @return [String] Human-readable component type
|
|
412
|
+
def get_component_type_label(node_name)
|
|
413
|
+
case node_name
|
|
414
|
+
when "element"
|
|
415
|
+
"Element"
|
|
416
|
+
when "complexType"
|
|
417
|
+
"Complex Type"
|
|
418
|
+
when "simpleType"
|
|
419
|
+
"Simple Type"
|
|
420
|
+
when "group"
|
|
421
|
+
"Model Group"
|
|
422
|
+
when "attributeGroup"
|
|
423
|
+
"Attribute Group"
|
|
424
|
+
when "attribute"
|
|
425
|
+
"Attribute"
|
|
426
|
+
when "notation"
|
|
427
|
+
"Notation"
|
|
428
|
+
else
|
|
429
|
+
node_name
|
|
430
|
+
end
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
# Generate glossary section
|
|
434
|
+
#
|
|
435
|
+
# @param html [Nokogiri::HTML::Builder] HTML builder
|
|
436
|
+
def generate_glossary(html)
|
|
437
|
+
html.section(id: "Glossary") do
|
|
438
|
+
html.h2 "Glossary"
|
|
439
|
+
html.p "XSD schema terms and definitions."
|
|
440
|
+
# TODO: Implement glossary generation
|
|
441
|
+
end
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
# Generate schema information section
|
|
445
|
+
#
|
|
446
|
+
# @param html [Nokogiri::HTML::Builder] HTML builder
|
|
447
|
+
def generate_schema_info(html)
|
|
448
|
+
schema = parser.schema
|
|
449
|
+
return unless schema
|
|
450
|
+
|
|
451
|
+
html.section(id: "SectionSchemaProperties", class: "schema-info") do
|
|
452
|
+
html.h2 do
|
|
453
|
+
html.a(id: "SchemaProperties") {}
|
|
454
|
+
html.text "Schema Document Properties"
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
html.dl(class: "dl-horizontal") do
|
|
458
|
+
# Target Namespace
|
|
459
|
+
html.dt(class: "header") do
|
|
460
|
+
html.a(title: "Look up 'Target Namespace' in glossary",
|
|
461
|
+
href: "#term_TargetNS") do
|
|
462
|
+
html.text "Target Namespace"
|
|
463
|
+
end
|
|
464
|
+
end
|
|
465
|
+
html.dd(class: "") do
|
|
466
|
+
if (target_ns = schema["targetNamespace"])
|
|
467
|
+
html.span(class: "targetNS") do
|
|
468
|
+
html.text target_ns
|
|
469
|
+
end
|
|
470
|
+
else
|
|
471
|
+
html.text "None"
|
|
472
|
+
end
|
|
473
|
+
end
|
|
474
|
+
|
|
475
|
+
# Element and Attribute Namespaces
|
|
476
|
+
html.dt(class: "header") do
|
|
477
|
+
html.text "Element and Attribute Namespaces"
|
|
478
|
+
end
|
|
479
|
+
html.dd(class: "") do
|
|
480
|
+
html.ul do
|
|
481
|
+
html.li do
|
|
482
|
+
html.text "Global element and attribute declarations belong to this schema's target namespace."
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
# Check elementFormDefault
|
|
486
|
+
element_form = schema["elementFormDefault"]
|
|
487
|
+
if element_form == "qualified"
|
|
488
|
+
html.li do
|
|
489
|
+
html.text "By default, local element declarations belong to this schema's target namespace."
|
|
490
|
+
end
|
|
491
|
+
else
|
|
492
|
+
html.li do
|
|
493
|
+
html.text "By default, local element declarations have no namespace."
|
|
494
|
+
end
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
html.li do
|
|
498
|
+
html.text "By default, local attribute declarations have no namespace."
|
|
499
|
+
end
|
|
500
|
+
end
|
|
501
|
+
end
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
# Declared Namespaces
|
|
505
|
+
generate_declared_namespaces(html, schema)
|
|
506
|
+
|
|
507
|
+
# Schema Component Representation
|
|
508
|
+
generate_schema_component_callout(html, schema)
|
|
509
|
+
|
|
510
|
+
html.div(style: "text-align: right; clear: both;") do
|
|
511
|
+
html.a(href: "#top", title: "Go to top of page") do
|
|
512
|
+
html.span(class: "glyphicon glyphicon-chevron-up") do
|
|
513
|
+
html.text " "
|
|
514
|
+
end
|
|
515
|
+
end
|
|
516
|
+
end
|
|
517
|
+
html.hr
|
|
518
|
+
end
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
# Generate declared namespaces section
|
|
522
|
+
#
|
|
523
|
+
# @param html [Nokogiri::HTML::Builder] HTML builder
|
|
524
|
+
# @param schema [Nokogiri::XML::Element] Schema element
|
|
525
|
+
def generate_declared_namespaces(html, schema)
|
|
526
|
+
html.h4 "Declared Namespaces:"
|
|
527
|
+
html.dl(class: "dl-horizontal") do
|
|
528
|
+
html.dt(class: "header") { html.text "Prefix" }
|
|
529
|
+
html.dd(class: "header") { html.text "Namespace" }
|
|
530
|
+
|
|
531
|
+
# Extract namespace declarations
|
|
532
|
+
schema.namespace_definitions.each do |ns|
|
|
533
|
+
prefix = ns.prefix || "(default)"
|
|
534
|
+
html.dt(class: "") do
|
|
535
|
+
html.a(id: "ns_#{ns.prefix}") {} if ns.prefix
|
|
536
|
+
html.text prefix
|
|
537
|
+
end
|
|
538
|
+
html.dd(class: "") { html.text ns.href }
|
|
539
|
+
end
|
|
540
|
+
end
|
|
541
|
+
end
|
|
542
|
+
|
|
543
|
+
# Generate component content as direct children (not wrapped in div) per xs3p
|
|
544
|
+
#
|
|
545
|
+
# @param html [Nokogiri::HTML::Builder] HTML builder
|
|
546
|
+
# @param component [Nokogiri::XML::Element] Schema component
|
|
547
|
+
# @param component_type [String] Component type label
|
|
548
|
+
def generate_component_content(html, component, component_type)
|
|
549
|
+
component_name = component["name"] || component["ref"]
|
|
550
|
+
return unless component_name
|
|
551
|
+
|
|
552
|
+
component_id = generate_component_id(component_type, component_name)
|
|
553
|
+
|
|
554
|
+
# XS3P does NOT wrap components in divs - content flows directly
|
|
555
|
+
html.h3(class: "xs3p-subsection-heading") do
|
|
556
|
+
html.text "#{component_type}: "
|
|
557
|
+
html.a(id: component_id) {}
|
|
558
|
+
html.strong component_name
|
|
559
|
+
end
|
|
560
|
+
|
|
561
|
+
# SVG diagram reference (if exists)
|
|
562
|
+
generate_svg_reference(html, component_name)
|
|
563
|
+
|
|
564
|
+
# Properties definition lists (no heading) - generates 1-3 DLs per component
|
|
565
|
+
props_gen = Generators::PropertiesTableGenerator.new(component,
|
|
566
|
+
@config)
|
|
567
|
+
props_gen.generate.each { |dl_html| html << dl_html }
|
|
568
|
+
|
|
569
|
+
# Hierarchy table (if applicable)
|
|
570
|
+
hier_gen = Generators::HierarchyTableGenerator.new(
|
|
571
|
+
component,
|
|
572
|
+
@parser,
|
|
573
|
+
@config,
|
|
574
|
+
)
|
|
575
|
+
hierarchy_html = hier_gen.generate
|
|
576
|
+
html << hierarchy_html if hierarchy_html
|
|
577
|
+
|
|
578
|
+
# Instance sample in callout block (skip for simple types and notations)
|
|
579
|
+
unless %w[simpleType notation].include?(component.name)
|
|
580
|
+
generate_instance_representation_callout(html, component)
|
|
581
|
+
end
|
|
582
|
+
|
|
583
|
+
# Schema component representation in callout block
|
|
584
|
+
generate_schema_component_callout(html, component)
|
|
585
|
+
|
|
586
|
+
# Back to top link and separator
|
|
587
|
+
html.div(style: "text-align: right; clear: both;") do
|
|
588
|
+
html.a(href: "#top", title: "Go to top of page") do
|
|
589
|
+
html.span(class: "glyphicon glyphicon-chevron-up") { html.text " " }
|
|
590
|
+
end
|
|
591
|
+
end
|
|
592
|
+
html.hr
|
|
593
|
+
end
|
|
594
|
+
|
|
595
|
+
# Generate SVG diagram reference
|
|
596
|
+
#
|
|
597
|
+
# @param html [Nokogiri::HTML::Builder] HTML builder
|
|
598
|
+
# @param component_name [String] Component name
|
|
599
|
+
def generate_svg_reference(html, component_name)
|
|
600
|
+
return unless @config.print_diagrams
|
|
601
|
+
|
|
602
|
+
html.object(data: "diagrams/#{component_name}.svg",
|
|
603
|
+
type: "image/svg+xml") {}
|
|
604
|
+
end
|
|
605
|
+
|
|
606
|
+
# Generate instance representation callout block
|
|
607
|
+
#
|
|
608
|
+
# @param html [Nokogiri::HTML::Builder] HTML builder
|
|
609
|
+
# @param component [Nokogiri::XML::Element] Schema component
|
|
610
|
+
def generate_instance_representation_callout(html, component)
|
|
611
|
+
html.div(class: "bs-callout bs-callout-info") do
|
|
612
|
+
html.h4 do
|
|
613
|
+
html.text "XML Instance Representation"
|
|
614
|
+
html.text " "
|
|
615
|
+
generate_help_popover(html, "instance")
|
|
616
|
+
end
|
|
617
|
+
sample_gen = Generators::InstanceSampleGenerator.new(
|
|
618
|
+
component,
|
|
619
|
+
@parser,
|
|
620
|
+
@config,
|
|
621
|
+
)
|
|
622
|
+
html << sample_gen.generate
|
|
623
|
+
end
|
|
624
|
+
end
|
|
625
|
+
|
|
626
|
+
# Generate schema component representation callout block
|
|
627
|
+
#
|
|
628
|
+
# @param html [Nokogiri::HTML::Builder] HTML builder
|
|
629
|
+
# @param component [Nokogiri::XML::Element] Schema component
|
|
630
|
+
def generate_schema_component_callout(html, component)
|
|
631
|
+
html.div(class: "bs-callout bs-callout-info") do
|
|
632
|
+
html.h4 do
|
|
633
|
+
html.text "Schema Component Representation"
|
|
634
|
+
html.text " "
|
|
635
|
+
generate_help_popover(html, "schema")
|
|
636
|
+
end
|
|
637
|
+
html.pre(class: "codehilite") do
|
|
638
|
+
html << format_xsd_component(component)
|
|
639
|
+
end
|
|
640
|
+
end
|
|
641
|
+
end
|
|
642
|
+
|
|
643
|
+
# Generate help popover button
|
|
644
|
+
#
|
|
645
|
+
# @param html [Nokogiri::HTML::Builder] HTML builder
|
|
646
|
+
# @param type [String] Type of help ("instance" or "schema")
|
|
647
|
+
def generate_help_popover(html, type)
|
|
648
|
+
content = if type == "instance"
|
|
649
|
+
instance_help_content
|
|
650
|
+
else
|
|
651
|
+
schema_help_content
|
|
652
|
+
end
|
|
653
|
+
|
|
654
|
+
html.span(class: "xs3p-panel-help") do
|
|
655
|
+
html.button(type: "button",
|
|
656
|
+
class: "btn btn-doc",
|
|
657
|
+
"data-container": "body",
|
|
658
|
+
"data-toggle": "popover",
|
|
659
|
+
"data-placement": type == "instance" ? "right" : "left",
|
|
660
|
+
"data-html": "true",
|
|
661
|
+
"data-content": content) do
|
|
662
|
+
html.span(class: "glyphicon glyphicon-question-sign") do
|
|
663
|
+
html.text " "
|
|
664
|
+
end
|
|
665
|
+
end
|
|
666
|
+
end
|
|
667
|
+
end
|
|
668
|
+
|
|
669
|
+
# Format XSD component for display
|
|
670
|
+
#
|
|
671
|
+
# @param component [Nokogiri::XML::Element] Schema component
|
|
672
|
+
# @return [String] Formatted XSD
|
|
673
|
+
def format_xsd_component(component)
|
|
674
|
+
# Special handling for schema element - collapse children
|
|
675
|
+
if component.name == "schema"
|
|
676
|
+
return format_collapsed_schema(component)
|
|
677
|
+
end
|
|
678
|
+
|
|
679
|
+
# Clone component to avoid modifying original
|
|
680
|
+
comp_copy = component.dup
|
|
681
|
+
|
|
682
|
+
# Remove annotation children (xs3p compliance)
|
|
683
|
+
comp_copy.xpath(".//xsd:annotation", "xsd" => "http://www.w3.org/2001/XMLSchema").each(&:remove)
|
|
684
|
+
|
|
685
|
+
# Get the component's XML representation
|
|
686
|
+
xml = comp_copy.to_xml(indent: 3, indent_text: " ")
|
|
687
|
+
|
|
688
|
+
# Add syntax highlighting classes
|
|
689
|
+
xml.gsub!(/<(\/?)([\w:]+)([^>]*)>/) do
|
|
690
|
+
tag_open = Regexp.last_match(1)
|
|
691
|
+
tag_name = Regexp.last_match(2)
|
|
692
|
+
attributes = Regexp.last_match(3)
|
|
693
|
+
|
|
694
|
+
# Highlight tag names
|
|
695
|
+
highlighted = "<span class=\"nt\"><#{tag_open}"
|
|
696
|
+
if tag_name.include?(":")
|
|
697
|
+
prefix = tag_name.split(":").first
|
|
698
|
+
local_name = tag_name.split(":").last
|
|
699
|
+
highlighted += "<a href=\"#ns_#{prefix}\" title=\"Find out namespace of '#{prefix}' prefix\">#{prefix}</a>:#{local_name}"
|
|
700
|
+
else
|
|
701
|
+
highlighted += tag_name
|
|
702
|
+
end
|
|
703
|
+
|
|
704
|
+
highlighted += "</span>"
|
|
705
|
+
|
|
706
|
+
# Highlight attributes
|
|
707
|
+
if attributes && !attributes.empty?
|
|
708
|
+
attributes.gsub!(/(\w+)="([^"]*)"/) do
|
|
709
|
+
attr_name = Regexp.last_match(1)
|
|
710
|
+
attr_value = Regexp.last_match(2)
|
|
711
|
+
|
|
712
|
+
# Check if attribute value is a type reference
|
|
713
|
+
if attr_name == "type" && !attr_value.include?(":")
|
|
714
|
+
# Local type reference - add link
|
|
715
|
+
attr_value_html = "<span class=\"type\"><a title='Jump to \"#{attr_value}\" type definition.' href=\"#type_#{attr_value}\">#{attr_value}</a></span>"
|
|
716
|
+
" <span class=\"na\">#{attr_name}=</span><span class=\"s\">\"#{attr_value_html}\"</span>"
|
|
717
|
+
elsif attr_name.include?(":") || ["ref",
|
|
718
|
+
"base"].include?(attr_name)
|
|
719
|
+
# Potential reference - add link if local
|
|
720
|
+
local_name = attr_value.include?(":") ? attr_value.split(":").last : attr_value
|
|
721
|
+
if ["ref", "base"].include?(attr_name)
|
|
722
|
+
attr_value_html = "<a title='Jump to \"#{local_name}\" #{attr_name == 'base' ? 'type' : 'element'} definition.' href=\"##{attr_name == 'base' ? 'type' : 'element'}_#{local_name}\">#{attr_value}</a>"
|
|
723
|
+
" <span class=\"na\">#{attr_name}=</span><span class=\"s\">\"#{attr_value_html}\"</span>"
|
|
724
|
+
else
|
|
725
|
+
" <span class=\"na\">#{attr_name}=</span><span class=\"s\">\"#{attr_value}\"</span>"
|
|
726
|
+
end
|
|
727
|
+
else
|
|
728
|
+
" <span class=\"na\">#{attr_name}=</span><span class=\"s\">\"#{attr_value}\"</span>"
|
|
729
|
+
end
|
|
730
|
+
end
|
|
731
|
+
highlighted += attributes
|
|
732
|
+
end
|
|
733
|
+
|
|
734
|
+
highlighted += "<span class=\"nt\">></span>"
|
|
735
|
+
highlighted
|
|
736
|
+
end
|
|
737
|
+
|
|
738
|
+
xml
|
|
739
|
+
end
|
|
740
|
+
|
|
741
|
+
# Format collapsed schema component (xs3p compliance)
|
|
742
|
+
#
|
|
743
|
+
# @param schema [Nokogiri::XML::Element] Schema element
|
|
744
|
+
# @return [String] Formatted collapsed XSD
|
|
745
|
+
def format_collapsed_schema(schema)
|
|
746
|
+
result = []
|
|
747
|
+
|
|
748
|
+
# Opening tag with attributes
|
|
749
|
+
tag_parts = ["<span class=\"nt\"><"]
|
|
750
|
+
tag_parts << "<a href=\"#ns_xsd\" title=\"Find out namespace of 'xsd' prefix\">xsd</a>:schema</span>"
|
|
751
|
+
|
|
752
|
+
# Add schema attributes
|
|
753
|
+
schema.attributes.each do |name, attr|
|
|
754
|
+
tag_parts << " <span class=\"na\">#{name}=</span><span class=\"s\">\"#{attr.value}\"</span>"
|
|
755
|
+
end
|
|
756
|
+
tag_parts << "<span class=\"nt\">></span>"
|
|
757
|
+
|
|
758
|
+
result << tag_parts.join
|
|
759
|
+
|
|
760
|
+
# Show first import/include child if exists
|
|
761
|
+
first_child = schema.children.find do |c|
|
|
762
|
+
c.element? && %w[import include].include?(c.name)
|
|
763
|
+
end
|
|
764
|
+
if first_child
|
|
765
|
+
# Format just the first import/include line
|
|
766
|
+
child_line = " <span class=\"nt\"><"
|
|
767
|
+
child_line += "<a href=\"#ns_xsd\" title=\"Find out namespace of 'xsd' prefix\">xsd</a>:#{first_child.name}</span>"
|
|
768
|
+
|
|
769
|
+
first_child.attributes.each do |name, attr|
|
|
770
|
+
child_line += " <span class=\"na\">#{name}=</span><span class=\"s\">\"#{attr.value}\"</span>"
|
|
771
|
+
end
|
|
772
|
+
child_line += "<span class=\"nt\">/></span>"
|
|
773
|
+
|
|
774
|
+
result << child_line
|
|
775
|
+
end
|
|
776
|
+
|
|
777
|
+
# Collapsed content placeholder
|
|
778
|
+
result << "<span class=\"scContent\">...</span>"
|
|
779
|
+
|
|
780
|
+
# Closing tag
|
|
781
|
+
result << "<span class=\"nt\"></<a href=\"#ns_xsd\" title=\"Find out namespace of 'xsd' prefix\">xsd</a>:schema></span>"
|
|
782
|
+
|
|
783
|
+
result.join("\n")
|
|
784
|
+
end
|
|
785
|
+
|
|
786
|
+
# Help content for instance representation
|
|
787
|
+
#
|
|
788
|
+
# @return [String] HTML help content
|
|
789
|
+
def instance_help_content
|
|
790
|
+
"The XML Instance Representation table shows the schema component's content as an XML instance. " \
|
|
791
|
+
"<ul>" \
|
|
792
|
+
"<li>The minimum and maximum occurrence of elements and attributes are provided in square brackets, e.g. [0..1].</li>" \
|
|
793
|
+
"<li>Model group information are shown in gray, e.g. Start Choice ... End Choice.</li>" \
|
|
794
|
+
"<li>For type derivations, the elements and attributes that have been added to or changed from the base type's content are shown in <strong>bold</strong></li>" \
|
|
795
|
+
"<li>If an element/attribute has a fixed value, the fixed value is shown in green.</li>" \
|
|
796
|
+
"</ul>"
|
|
797
|
+
end
|
|
798
|
+
|
|
799
|
+
# Help content for schema component
|
|
800
|
+
#
|
|
801
|
+
# @return [String] HTML help content
|
|
802
|
+
def schema_help_content
|
|
803
|
+
"The Schema Component Representation table below displays the underlying XML representation of the schema component. (Annotations are not shown.)"
|
|
804
|
+
end
|
|
805
|
+
|
|
806
|
+
# Generate component ID for anchor links
|
|
807
|
+
#
|
|
808
|
+
# @param component_type [String] Component type
|
|
809
|
+
# @param component_name [String] Component name
|
|
810
|
+
# @return [String] Component ID
|
|
811
|
+
def generate_component_id(component_type, component_name)
|
|
812
|
+
case component_type
|
|
813
|
+
when "Element"
|
|
814
|
+
"element-#{component_name}"
|
|
815
|
+
when "Complex Type"
|
|
816
|
+
"type-#{component_name}"
|
|
817
|
+
when "Simple Type"
|
|
818
|
+
"type-#{component_name}"
|
|
819
|
+
when "Model Group"
|
|
820
|
+
"group-#{component_name}"
|
|
821
|
+
when "Attribute Group"
|
|
822
|
+
"attributeGroup-#{component_name}"
|
|
823
|
+
else
|
|
824
|
+
"#{component_type.downcase.tr(' ', '-')}-#{component_name}"
|
|
825
|
+
end
|
|
826
|
+
end
|
|
827
|
+
|
|
828
|
+
# Get the title for the documentation
|
|
829
|
+
#
|
|
830
|
+
# @return [String] Documentation title
|
|
831
|
+
def title
|
|
832
|
+
@config.title || "XSD Schema Documentation"
|
|
833
|
+
end
|
|
834
|
+
end
|
|
835
|
+
end
|
|
836
|
+
end
|