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.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/rake.yml +16 -0
  3. data/.github/workflows/release.yml +25 -0
  4. data/.gitignore +72 -0
  5. data/.rspec +3 -0
  6. data/.rubocop.yml +11 -0
  7. data/.rubocop_todo.yml +432 -0
  8. data/CHANGELOG.adoc +446 -0
  9. data/Gemfile +21 -0
  10. data/LICENSE.adoc +29 -0
  11. data/README.adoc +386 -0
  12. data/Rakefile +11 -0
  13. data/examples/README.adoc +334 -0
  14. data/examples/advanced_usage.rb +286 -0
  15. data/examples/html_generation.rb +167 -0
  16. data/examples/parser_usage.rb +102 -0
  17. data/examples/schemas/person.xsd +171 -0
  18. data/examples/simple_generation.rb +149 -0
  19. data/exe/xseed +6 -0
  20. data/lib/xseed/cli.rb +376 -0
  21. data/lib/xseed/documentation/config.rb +101 -0
  22. data/lib/xseed/documentation/constants.rb +76 -0
  23. data/lib/xseed/documentation/generators/hierarchy_table_generator.rb +554 -0
  24. data/lib/xseed/documentation/generators/instance_sample_generator.rb +723 -0
  25. data/lib/xseed/documentation/generators/properties_table_generator.rb +983 -0
  26. data/lib/xseed/documentation/html_generator.rb +836 -0
  27. data/lib/xseed/documentation/html_generator.rb.bak +723 -0
  28. data/lib/xseed/documentation/presentation/css_generator.rb +510 -0
  29. data/lib/xseed/documentation/presentation/javascript_generator.rb +151 -0
  30. data/lib/xseed/documentation/presentation/navigation_builder.rb +169 -0
  31. data/lib/xseed/documentation/schema_loader.rb +121 -0
  32. data/lib/xseed/documentation/utils/helpers.rb +205 -0
  33. data/lib/xseed/documentation/utils/namespaces.rb +149 -0
  34. data/lib/xseed/documentation/utils/references.rb +135 -0
  35. data/lib/xseed/documentation/utils/strings.rb +75 -0
  36. data/lib/xseed/models/element_declaration.rb +144 -0
  37. data/lib/xseed/parser/xsd_parser.rb +192 -0
  38. data/lib/xseed/version.rb +5 -0
  39. data/lib/xseed.rb +76 -0
  40. data/xseed.gemspec +39 -0
  41. metadata +158 -0
@@ -0,0 +1,554 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "nokogiri"
4
+ require_relative "../config"
5
+ require_relative "../constants"
6
+
7
+ module Xseed
8
+ module Documentation
9
+ module Generators
10
+ # Generates HTML hierarchy tables for schema components
11
+ # Shows type hierarchies and substitution groups
12
+ # Ported from XS3P hierarchy-tables.xsl
13
+ class HierarchyTableGenerator
14
+ include Constants
15
+
16
+ attr_reader :component, :parser, :config
17
+
18
+ # Initialize the hierarchy table generator
19
+ #
20
+ # @param component [Nokogiri::XML::Element] Schema component
21
+ # @param parser [Xseed::Parser::XsdParser] XSD parser instance
22
+ # @param config [Config] Configuration options
23
+ def initialize(component, parser, config = Config.new)
24
+ raise ArgumentError, "Component cannot be nil" if component.nil?
25
+ raise ArgumentError, "Parser cannot be nil" if parser.nil?
26
+
27
+ @component = component
28
+ @parser = parser
29
+ @config = config
30
+ end
31
+
32
+ # Generate HTML hierarchy table
33
+ #
34
+ # @return [String, nil] HTML table markup or nil if no hierarchy
35
+ def generate
36
+ return nil unless has_hierarchy?
37
+
38
+ builder = Nokogiri::HTML::Builder.new do |html|
39
+ html.div(class: "hierarchy") do
40
+ html.table(class: "table table-striped xs3p-in-panel-table") do
41
+ html.tbody do
42
+ if element?
43
+ generate_substitution_groups(html)
44
+ elsif complex_type?
45
+ generate_complex_type_hierarchy(html)
46
+ elsif simple_type?
47
+ generate_simple_type_hierarchy(html)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ builder.to_html
54
+ end
55
+
56
+ private
57
+
58
+ # Check if component has hierarchy to display
59
+ #
60
+ # @return [Boolean]
61
+ def has_hierarchy?
62
+ if element?
63
+ # Element has hierarchy if it has substitutionGroup or is head of one
64
+ !component["substitutionGroup"].nil? || has_substitution_members?
65
+ elsif complex_type?
66
+ # Complex type has hierarchy if it has base type or derived types
67
+ has_base_type? || !find_subtypes.empty?
68
+ elsif simple_type?
69
+ # Simple type has hierarchy if it has restriction base or derived types
70
+ has_restriction_base? || !find_simple_subtypes.empty?
71
+ else
72
+ false
73
+ end
74
+ end
75
+
76
+ # Generate substitution groups section for elements
77
+ #
78
+ # @param html [Nokogiri::HTML::Builder] HTML builder
79
+ def generate_substitution_groups(html)
80
+ # Show substitution group this element belongs to
81
+ if component["substitutionGroup"]
82
+ html.tr do
83
+ html.td do
84
+ html.ul do
85
+ html.li do
86
+ html.em do
87
+ html.text "This element can be used wherever the following element is referenced:"
88
+ end
89
+ html.ul do
90
+ html.li do
91
+ html.text component["substitutionGroup"]
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+
100
+ # Show substitution group members (elements that can substitute this one)
101
+ return unless has_substitution_members?
102
+
103
+ members = find_substitution_members
104
+ html.tr do
105
+ html.td do
106
+ html.ul do
107
+ html.li do
108
+ html.em do
109
+ html.text "The following elements can be used wherever this element is referenced:"
110
+ end
111
+ html.ul do
112
+ members.each do |member|
113
+ html.li do
114
+ html.text member["name"]
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
123
+
124
+ # Generate type hierarchy for complex types
125
+ #
126
+ # @param html [Nokogiri::HTML::Builder] HTML builder
127
+ def generate_complex_type_hierarchy(html)
128
+ # Show supertypes
129
+ if has_base_type?
130
+ html.tr do
131
+ html.th do
132
+ if config.print_all_super_types
133
+ html.text "Super-types:"
134
+ else
135
+ html.text "Parent type:"
136
+ end
137
+ end
138
+ html.td do
139
+ generate_supertypes(html)
140
+ end
141
+ end
142
+ end
143
+
144
+ # Show subtypes
145
+ subtypes = find_subtypes
146
+ return if subtypes.empty?
147
+
148
+ html.tr do
149
+ html.th do
150
+ if config.print_all_sub_types
151
+ html.text "Sub-types:"
152
+ else
153
+ html.text "Direct sub-types:"
154
+ end
155
+ end
156
+ html.td do
157
+ generate_subtypes(html, subtypes)
158
+ end
159
+ end
160
+ end
161
+
162
+ # Generate type hierarchy for simple types
163
+ #
164
+ # @param html [Nokogiri::HTML::Builder] HTML builder
165
+ def generate_simple_type_hierarchy(html)
166
+ # Show supertypes
167
+ if has_restriction_base?
168
+ html.tr do
169
+ html.th do
170
+ if config.print_all_super_types
171
+ html.text "Super-types:"
172
+ else
173
+ html.text "Parent type:"
174
+ end
175
+ end
176
+ html.td do
177
+ generate_simple_supertypes(html)
178
+ end
179
+ end
180
+ end
181
+
182
+ # Show subtypes
183
+ subtypes = find_simple_subtypes
184
+ return if subtypes.empty?
185
+
186
+ html.tr do
187
+ html.th do
188
+ if config.print_all_sub_types
189
+ html.text "Sub-types:"
190
+ else
191
+ html.text "Direct sub-types:"
192
+ end
193
+ end
194
+ html.td do
195
+ generate_simple_derived_types(html, subtypes)
196
+ end
197
+ end
198
+ end
199
+
200
+ # Generate supertypes chain for complex types
201
+ #
202
+ # @param html [Nokogiri::HTML::Builder] HTML builder
203
+ def generate_supertypes(html)
204
+ base_ref = extract_base_type_ref
205
+ return unless base_ref
206
+
207
+ if config.print_all_super_types
208
+ # Show full hierarchy
209
+ chain = build_supertype_chain(base_ref)
210
+ html.text chain.join(" < ")
211
+ else
212
+ # Show only immediate parent
213
+ html.text "#{base_ref} (#{derivation_method})"
214
+ end
215
+ end
216
+
217
+ # Generate supertypes for simple types
218
+ #
219
+ # @param html [Nokogiri::HTML::Builder] HTML builder
220
+ def generate_simple_supertypes(html)
221
+ restriction = component.at_xpath("xsd:restriction", "xsd" => XSD_NS)
222
+ return unless restriction
223
+
224
+ base_ref = restriction["base"]
225
+ return unless base_ref
226
+
227
+ if config.print_all_super_types
228
+ # Show full hierarchy
229
+ chain = build_simple_supertype_chain(base_ref)
230
+ html.text chain.join(" < ")
231
+ else
232
+ # Show only immediate parent
233
+ html.text "#{base_ref} (by restriction)"
234
+ end
235
+ end
236
+
237
+ # Generate subtypes list
238
+ #
239
+ # @param html [Nokogiri::HTML::Builder] HTML builder
240
+ # @param subtypes [Array<Nokogiri::XML::Element>] Subtype elements
241
+ def generate_subtypes(html, subtypes)
242
+ html.ul do
243
+ subtypes.each do |subtype|
244
+ html.li do
245
+ html.text subtype["name"]
246
+ html.text " (by #{get_derivation_method(subtype)})"
247
+
248
+ # Recursively show sub-subtypes if config allows
249
+ if config.print_all_sub_types
250
+ sub_subtypes = find_subtypes_of(subtype)
251
+ unless sub_subtypes.empty?
252
+ generate_subtypes(html,
253
+ sub_subtypes)
254
+ end
255
+ end
256
+ end
257
+ end
258
+ end
259
+ end
260
+
261
+ # Generate subtypes list for simple types
262
+ #
263
+ # @param html [Nokogiri::HTML::Builder] HTML builder
264
+ # @param subtypes [Array<Nokogiri::XML::Element>] Subtype elements
265
+ def generate_simple_derived_types(html, subtypes)
266
+ html.ul do
267
+ subtypes.each do |subtype|
268
+ html.li do
269
+ html.text "#{subtype['name']} (by restriction)"
270
+
271
+ # Recursively show sub-subtypes if config allows
272
+ if config.print_all_sub_types
273
+ sub_subtypes = find_simple_subtypes_of(subtype)
274
+ unless sub_subtypes.empty?
275
+ generate_simple_derived_types(html, sub_subtypes)
276
+ end
277
+ end
278
+ end
279
+ end
280
+ end
281
+ end
282
+
283
+ # Check if component is an element
284
+ #
285
+ # @return [Boolean]
286
+ def element?
287
+ component.name == "element"
288
+ end
289
+
290
+ # Check if component is a complex type
291
+ #
292
+ # @return [Boolean]
293
+ def complex_type?
294
+ component.name == "complexType"
295
+ end
296
+
297
+ # Check if component is a simple type
298
+ #
299
+ # @return [Boolean]
300
+ def simple_type?
301
+ component.name == "simpleType"
302
+ end
303
+
304
+ # Check if element has substitution group members
305
+ #
306
+ # @return [Boolean]
307
+ def has_substitution_members?
308
+ !find_substitution_members.empty?
309
+ end
310
+
311
+ # Find elements that can substitute this element
312
+ #
313
+ # @return [Array<Nokogiri::XML::Element>]
314
+ def find_substitution_members
315
+ element_name = component["name"]
316
+ return [] unless element_name
317
+
318
+ parser.elements.select do |elem|
319
+ elem["substitutionGroup"] == element_name
320
+ end
321
+ end
322
+
323
+ # Check if complex type has base type
324
+ #
325
+ # @return [Boolean]
326
+ def has_base_type?
327
+ return false unless complex_type?
328
+
329
+ !extract_base_type_ref.nil?
330
+ end
331
+
332
+ # Check if simple type has restriction base
333
+ #
334
+ # @return [Boolean]
335
+ def has_restriction_base?
336
+ return false unless simple_type?
337
+
338
+ restriction = component.at_xpath("xsd:restriction", "xsd" => XSD_NS)
339
+ !!(restriction && restriction["base"])
340
+ end
341
+
342
+ # Extract base type reference from complex type
343
+ #
344
+ # @return [String, nil]
345
+ def extract_base_type_ref
346
+ # Check simpleContent
347
+ if (extension = component.at_xpath(
348
+ "xsd:simpleContent/xsd:extension",
349
+ "xsd" => XSD_NS,
350
+ ))
351
+ return extension["base"]
352
+ end
353
+
354
+ if (restriction = component.at_xpath(
355
+ "xsd:simpleContent/xsd:restriction",
356
+ "xsd" => XSD_NS,
357
+ ))
358
+ return restriction["base"]
359
+ end
360
+
361
+ # Check complexContent
362
+ if (extension = component.at_xpath(
363
+ "xsd:complexContent/xsd:extension",
364
+ "xsd" => XSD_NS,
365
+ ))
366
+ return extension["base"]
367
+ end
368
+
369
+ if (restriction = component.at_xpath(
370
+ "xsd:complexContent/xsd:restriction",
371
+ "xsd" => XSD_NS,
372
+ ))
373
+ return restriction["base"]
374
+ end
375
+
376
+ nil
377
+ end
378
+
379
+ # Get derivation method (extension or restriction)
380
+ #
381
+ # @return [String]
382
+ def derivation_method
383
+ if component.at_xpath(
384
+ "xsd:simpleContent/xsd:extension | xsd:complexContent/xsd:extension",
385
+ "xsd" => XSD_NS,
386
+ )
387
+ "extension"
388
+ elsif component.at_xpath(
389
+ "xsd:simpleContent/xsd:restriction | xsd:complexContent/xsd:restriction",
390
+ "xsd" => XSD_NS,
391
+ )
392
+ "restriction"
393
+ else
394
+ "unknown"
395
+ end
396
+ end
397
+
398
+ # Get derivation method for a given type
399
+ #
400
+ # @param type [Nokogiri::XML::Element] Type element
401
+ # @return [String]
402
+ def get_derivation_method(type)
403
+ if type.at_xpath(
404
+ "xsd:complexContent/xsd:extension | xsd:simpleContent/xsd:extension",
405
+ "xsd" => XSD_NS,
406
+ )
407
+ "extension"
408
+ else
409
+ "restriction"
410
+ end
411
+ end
412
+
413
+ # Build supertype chain
414
+ #
415
+ # @param base_ref [String] Base type reference
416
+ # @return [Array<String>]
417
+ def build_supertype_chain(base_ref)
418
+ chain = [base_ref]
419
+ current_type_name = strip_namespace_prefix(base_ref)
420
+
421
+ # Find the type in the schema
422
+ current_type = parser.complex_types.find do |t|
423
+ t["name"] == current_type_name
424
+ end
425
+
426
+ # Walk up the hierarchy
427
+ while current_type
428
+ generator = self.class.new(current_type, parser, config)
429
+ next_base = generator.send(:extract_base_type_ref)
430
+ break unless next_base
431
+
432
+ chain.unshift(next_base)
433
+ current_type_name = strip_namespace_prefix(next_base)
434
+ current_type = parser.complex_types.find do |t|
435
+ t["name"] == current_type_name
436
+ end
437
+ end
438
+
439
+ chain << "#{component['name']} (by #{derivation_method})"
440
+ chain
441
+ end
442
+
443
+ # Build supertype chain for simple types
444
+ #
445
+ # @param base_ref [String] Base type reference
446
+ # @return [Array<String>]
447
+ def build_simple_supertype_chain(base_ref)
448
+ chain = [base_ref]
449
+ current_type_name = strip_namespace_prefix(base_ref)
450
+
451
+ # Find the type in the schema
452
+ current_type = parser.simple_types.find do |t|
453
+ t["name"] == current_type_name
454
+ end
455
+
456
+ # Walk up the hierarchy
457
+ while current_type
458
+ restriction = current_type.at_xpath(
459
+ "xsd:restriction",
460
+ "xsd" => XSD_NS,
461
+ )
462
+ break unless restriction
463
+
464
+ next_base = restriction["base"]
465
+ break unless next_base
466
+
467
+ chain.unshift(next_base)
468
+ current_type_name = strip_namespace_prefix(next_base)
469
+ current_type = parser.simple_types.find do |t|
470
+ t["name"] == current_type_name
471
+ end
472
+ end
473
+
474
+ chain << "#{component['name']} (by restriction)"
475
+ chain
476
+ end
477
+
478
+ # Find direct subtypes of this complex type
479
+ #
480
+ # @return [Array<Nokogiri::XML::Element>]
481
+ def find_subtypes
482
+ type_name = component["name"]
483
+ return [] unless type_name
484
+
485
+ parser.complex_types.select do |ct|
486
+ base_ref = self.class.new(ct, parser, config)
487
+ .send(:extract_base_type_ref)
488
+ base_ref && strip_namespace_prefix(base_ref) == type_name
489
+ end
490
+ end
491
+
492
+ # Find direct subtypes of a given type
493
+ #
494
+ # @param type [Nokogiri::XML::Element] Type element
495
+ # @return [Array<Nokogiri::XML::Element>]
496
+ def find_subtypes_of(type)
497
+ type_name = type["name"]
498
+ return [] unless type_name
499
+
500
+ parser.complex_types.select do |ct|
501
+ next if ct == type
502
+
503
+ base_ref = self.class.new(ct, parser, config)
504
+ .send(:extract_base_type_ref)
505
+ base_ref && strip_namespace_prefix(base_ref) == type_name
506
+ end
507
+ end
508
+
509
+ # Find direct subtypes of this simple type
510
+ #
511
+ # @return [Array<Nokogiri::XML::Element>]
512
+ def find_simple_subtypes
513
+ type_name = component["name"]
514
+ return [] unless type_name
515
+
516
+ parser.simple_types.select do |st|
517
+ restriction = st.at_xpath("xsd:restriction", "xsd" => XSD_NS)
518
+ next unless restriction
519
+
520
+ base_ref = restriction["base"]
521
+ base_ref && strip_namespace_prefix(base_ref) == type_name
522
+ end
523
+ end
524
+
525
+ # Find direct subtypes of a given simple type
526
+ #
527
+ # @param type [Nokogiri::XML::Element] Type element
528
+ # @return [Array<Nokogiri::XML::Element>]
529
+ def find_simple_subtypes_of(type)
530
+ type_name = type["name"]
531
+ return [] unless type_name
532
+
533
+ parser.simple_types.select do |st|
534
+ next if st == type
535
+
536
+ restriction = st.at_xpath("xsd:restriction", "xsd" => XSD_NS)
537
+ next unless restriction
538
+
539
+ base_ref = restriction["base"]
540
+ base_ref && strip_namespace_prefix(base_ref) == type_name
541
+ end
542
+ end
543
+
544
+ # Strip namespace prefix from a type reference
545
+ #
546
+ # @param ref [String] Type reference (may include prefix)
547
+ # @return [String] Type name without prefix
548
+ def strip_namespace_prefix(ref)
549
+ ref.to_s.split(":").last
550
+ end
551
+ end
552
+ end
553
+ end
554
+ end