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,171 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
|
3
|
+
targetNamespace="http://example.com/person"
|
|
4
|
+
xmlns:tns="http://example.com/person"
|
|
5
|
+
elementFormDefault="qualified"
|
|
6
|
+
version="1.0.0">
|
|
7
|
+
|
|
8
|
+
<xs:annotation>
|
|
9
|
+
<xs:documentation>
|
|
10
|
+
Example schema defining person and contact information.
|
|
11
|
+
This schema demonstrates various XSD features including
|
|
12
|
+
complex types, sequences, choices, and attributes.
|
|
13
|
+
</xs:documentation>
|
|
14
|
+
</xs:annotation>
|
|
15
|
+
|
|
16
|
+
<!-- Simple Types -->
|
|
17
|
+
<xs:simpleType name="EmailType">
|
|
18
|
+
<xs:annotation>
|
|
19
|
+
<xs:documentation>Email address with pattern validation</xs:documentation>
|
|
20
|
+
</xs:annotation>
|
|
21
|
+
<xs:restriction base="xs:string">
|
|
22
|
+
<xs:pattern value="[^@]+@[^@]+\.[^@]+"/>
|
|
23
|
+
</xs:restriction>
|
|
24
|
+
</xs:simpleType>
|
|
25
|
+
|
|
26
|
+
<xs:simpleType name="PhoneType">
|
|
27
|
+
<xs:annotation>
|
|
28
|
+
<xs:documentation>Phone number in international format</xs:documentation>
|
|
29
|
+
</xs:annotation>
|
|
30
|
+
<xs:restriction base="xs:string">
|
|
31
|
+
<xs:pattern value="\+[0-9]{1,3}-[0-9]{1,14}"/>
|
|
32
|
+
</xs:restriction>
|
|
33
|
+
</xs:simpleType>
|
|
34
|
+
|
|
35
|
+
<xs:simpleType name="StatusType">
|
|
36
|
+
<xs:annotation>
|
|
37
|
+
<xs:documentation>Person status enumeration</xs:documentation>
|
|
38
|
+
</xs:annotation>
|
|
39
|
+
<xs:restriction base="xs:string">
|
|
40
|
+
<xs:enumeration value="active"/>
|
|
41
|
+
<xs:enumeration value="inactive"/>
|
|
42
|
+
<xs:enumeration value="pending"/>
|
|
43
|
+
</xs:restriction>
|
|
44
|
+
</xs:simpleType>
|
|
45
|
+
|
|
46
|
+
<!-- Complex Types -->
|
|
47
|
+
<xs:complexType name="AddressType">
|
|
48
|
+
<xs:annotation>
|
|
49
|
+
<xs:documentation>Postal address information</xs:documentation>
|
|
50
|
+
</xs:annotation>
|
|
51
|
+
<xs:sequence>
|
|
52
|
+
<xs:element name="street" type="xs:string">
|
|
53
|
+
<xs:annotation>
|
|
54
|
+
<xs:documentation>Street address</xs:documentation>
|
|
55
|
+
</xs:annotation>
|
|
56
|
+
</xs:element>
|
|
57
|
+
<xs:element name="city" type="xs:string">
|
|
58
|
+
<xs:annotation>
|
|
59
|
+
<xs:documentation>City name</xs:documentation>
|
|
60
|
+
</xs:annotation>
|
|
61
|
+
</xs:element>
|
|
62
|
+
<xs:element name="state" type="xs:string" minOccurs="0">
|
|
63
|
+
<xs:annotation>
|
|
64
|
+
<xs:documentation>State or province (optional)</xs:documentation>
|
|
65
|
+
</xs:annotation>
|
|
66
|
+
</xs:element>
|
|
67
|
+
<xs:element name="postalCode" type="xs:string">
|
|
68
|
+
<xs:annotation>
|
|
69
|
+
<xs:documentation>Postal or ZIP code</xs:documentation>
|
|
70
|
+
</xs:annotation>
|
|
71
|
+
</xs:element>
|
|
72
|
+
<xs:element name="country" type="xs:string">
|
|
73
|
+
<xs:annotation>
|
|
74
|
+
<xs:documentation>Country name</xs:documentation>
|
|
75
|
+
</xs:annotation>
|
|
76
|
+
</xs:element>
|
|
77
|
+
</xs:sequence>
|
|
78
|
+
<xs:attribute name="type" type="xs:string" use="optional" default="home">
|
|
79
|
+
<xs:annotation>
|
|
80
|
+
<xs:documentation>Address type (home, work, other)</xs:documentation>
|
|
81
|
+
</xs:annotation>
|
|
82
|
+
</xs:attribute>
|
|
83
|
+
</xs:complexType>
|
|
84
|
+
|
|
85
|
+
<xs:complexType name="ContactInfoType">
|
|
86
|
+
<xs:annotation>
|
|
87
|
+
<xs:documentation>Contact information with choice of email or phone</xs:documentation>
|
|
88
|
+
</xs:annotation>
|
|
89
|
+
<xs:choice>
|
|
90
|
+
<xs:element name="email" type="tns:EmailType">
|
|
91
|
+
<xs:annotation>
|
|
92
|
+
<xs:documentation>Email address</xs:documentation>
|
|
93
|
+
</xs:annotation>
|
|
94
|
+
</xs:element>
|
|
95
|
+
<xs:element name="phone" type="tns:PhoneType">
|
|
96
|
+
<xs:annotation>
|
|
97
|
+
<xs:documentation>Phone number</xs:documentation>
|
|
98
|
+
</xs:annotation>
|
|
99
|
+
</xs:element>
|
|
100
|
+
</xs:choice>
|
|
101
|
+
<xs:attribute name="preferred" type="xs:boolean" default="false">
|
|
102
|
+
<xs:annotation>
|
|
103
|
+
<xs:documentation>Whether this is the preferred contact method</xs:documentation>
|
|
104
|
+
</xs:annotation>
|
|
105
|
+
</xs:attribute>
|
|
106
|
+
</xs:complexType>
|
|
107
|
+
|
|
108
|
+
<xs:complexType name="PersonType">
|
|
109
|
+
<xs:annotation>
|
|
110
|
+
<xs:documentation>A person with name, contact info, and address</xs:documentation>
|
|
111
|
+
</xs:annotation>
|
|
112
|
+
<xs:sequence>
|
|
113
|
+
<xs:element name="firstName" type="xs:string">
|
|
114
|
+
<xs:annotation>
|
|
115
|
+
<xs:documentation>Person's first name</xs:documentation>
|
|
116
|
+
</xs:annotation>
|
|
117
|
+
</xs:element>
|
|
118
|
+
<xs:element name="lastName" type="xs:string">
|
|
119
|
+
<xs:annotation>
|
|
120
|
+
<xs:documentation>Person's last name</xs:documentation>
|
|
121
|
+
</xs:annotation>
|
|
122
|
+
</xs:element>
|
|
123
|
+
<xs:element name="age" type="xs:positiveInteger" minOccurs="0">
|
|
124
|
+
<xs:annotation>
|
|
125
|
+
<xs:documentation>Person's age</xs:documentation>
|
|
126
|
+
</xs:annotation>
|
|
127
|
+
</xs:element>
|
|
128
|
+
<xs:element name="contact" type="tns:ContactInfoType"
|
|
129
|
+
minOccurs="0" maxOccurs="unbounded">
|
|
130
|
+
<xs:annotation>
|
|
131
|
+
<xs:documentation>Contact information (email or phone)</xs:documentation>
|
|
132
|
+
</xs:annotation>
|
|
133
|
+
</xs:element>
|
|
134
|
+
<xs:element name="address" type="tns:AddressType"
|
|
135
|
+
minOccurs="0" maxOccurs="unbounded">
|
|
136
|
+
<xs:annotation>
|
|
137
|
+
<xs:documentation>Postal addresses</xs:documentation>
|
|
138
|
+
</xs:annotation>
|
|
139
|
+
</xs:element>
|
|
140
|
+
</xs:sequence>
|
|
141
|
+
<xs:attribute name="id" type="xs:ID" use="required">
|
|
142
|
+
<xs:annotation>
|
|
143
|
+
<xs:documentation>Unique identifier for the person</xs:documentation>
|
|
144
|
+
</xs:annotation>
|
|
145
|
+
</xs:attribute>
|
|
146
|
+
<xs:attribute name="status" type="tns:StatusType" default="active">
|
|
147
|
+
<xs:annotation>
|
|
148
|
+
<xs:documentation>Person's current status</xs:documentation>
|
|
149
|
+
</xs:annotation>
|
|
150
|
+
</xs:attribute>
|
|
151
|
+
</xs:complexType>
|
|
152
|
+
|
|
153
|
+
<!-- Global Elements -->
|
|
154
|
+
<xs:element name="person" type="tns:PersonType">
|
|
155
|
+
<xs:annotation>
|
|
156
|
+
<xs:documentation>Root element representing a person</xs:documentation>
|
|
157
|
+
</xs:annotation>
|
|
158
|
+
</xs:element>
|
|
159
|
+
|
|
160
|
+
<xs:element name="people">
|
|
161
|
+
<xs:annotation>
|
|
162
|
+
<xs:documentation>Collection of person elements</xs:documentation>
|
|
163
|
+
</xs:annotation>
|
|
164
|
+
<xs:complexType>
|
|
165
|
+
<xs:sequence>
|
|
166
|
+
<xs:element ref="tns:person" maxOccurs="unbounded"/>
|
|
167
|
+
</xs:sequence>
|
|
168
|
+
</xs:complexType>
|
|
169
|
+
</xs:element>
|
|
170
|
+
|
|
171
|
+
</xs:schema>
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Example: Simple SVG Generation
|
|
5
|
+
#
|
|
6
|
+
# This example demonstrates the basic usage of Xseed for generating
|
|
7
|
+
# SVG diagrams from XSD schema files.
|
|
8
|
+
|
|
9
|
+
require "bundler/setup"
|
|
10
|
+
require "xseed"
|
|
11
|
+
|
|
12
|
+
# Example 1: Generate SVG and print to stdout
|
|
13
|
+
def example_generate_to_stdout
|
|
14
|
+
puts "Example 1: Generate SVG to stdout"
|
|
15
|
+
puts "=" * 60
|
|
16
|
+
|
|
17
|
+
# Path to example XSD file
|
|
18
|
+
xsd_file = File.join(__dir__, "schemas", "person.xsd")
|
|
19
|
+
|
|
20
|
+
# Create generator
|
|
21
|
+
generator = Xseed::Svg::SvgGenerator.new(xsd_file)
|
|
22
|
+
|
|
23
|
+
# Generate SVG content
|
|
24
|
+
svg_content = generator.generate
|
|
25
|
+
|
|
26
|
+
# Print first 200 characters
|
|
27
|
+
puts svg_content[0..200]
|
|
28
|
+
puts "..."
|
|
29
|
+
puts "\n✓ SVG generated successfully (#{svg_content.length} bytes)\n\n"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Example 2: Generate SVG and save to file
|
|
33
|
+
def example_generate_to_file
|
|
34
|
+
puts "Example 2: Generate SVG to file"
|
|
35
|
+
puts "=" * 60
|
|
36
|
+
|
|
37
|
+
xsd_file = File.join(__dir__, "schemas", "person.xsd")
|
|
38
|
+
output_file = File.join(__dir__, "output", "person-diagram.svg")
|
|
39
|
+
|
|
40
|
+
# Ensure output directory exists
|
|
41
|
+
FileUtils.mkdir_p(File.dirname(output_file))
|
|
42
|
+
|
|
43
|
+
# Create generator and generate file
|
|
44
|
+
generator = Xseed::Svg::SvgGenerator.new(xsd_file)
|
|
45
|
+
result = generator.generate_file(output_file)
|
|
46
|
+
|
|
47
|
+
puts "✓ SVG diagram saved to: #{result}"
|
|
48
|
+
puts " File size: #{File.size(result)} bytes\n\n"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Example 3: Error handling
|
|
52
|
+
def example_with_error_handling
|
|
53
|
+
puts "Example 3: Generate with error handling"
|
|
54
|
+
puts "=" * 60
|
|
55
|
+
|
|
56
|
+
xsd_file = File.join(__dir__, "schemas", "person.xsd")
|
|
57
|
+
output_file = File.join(__dir__, "output", "person-safe.svg")
|
|
58
|
+
|
|
59
|
+
FileUtils.mkdir_p(File.dirname(output_file))
|
|
60
|
+
|
|
61
|
+
begin
|
|
62
|
+
generator = Xseed::Svg::SvgGenerator.new(xsd_file)
|
|
63
|
+
generator.generate_file(output_file)
|
|
64
|
+
puts "✓ Generation successful: #{output_file}\n\n"
|
|
65
|
+
rescue ArgumentError => e
|
|
66
|
+
puts "✗ Validation error: #{e.message}\n\n"
|
|
67
|
+
rescue Xseed::ParserError => e
|
|
68
|
+
puts "✗ Parser error: #{e.message}\n\n"
|
|
69
|
+
rescue Xseed::Svg::GenerationError => e
|
|
70
|
+
puts "✗ Generation error: #{e.message}\n\n"
|
|
71
|
+
rescue StandardError => e
|
|
72
|
+
puts "✗ Unexpected error: #{e.message}\n\n"
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Example 4: Inspect schema before generation
|
|
77
|
+
def example_inspect_before_generate
|
|
78
|
+
puts "Example 4: Inspect schema metadata"
|
|
79
|
+
puts "=" * 60
|
|
80
|
+
|
|
81
|
+
xsd_file = File.join(__dir__, "schemas", "person.xsd")
|
|
82
|
+
|
|
83
|
+
# Parse to inspect metadata
|
|
84
|
+
parser = Xseed::Parser::XsdParser.new(xsd_file)
|
|
85
|
+
|
|
86
|
+
puts "Schema Information:"
|
|
87
|
+
puts " Target Namespace: #{parser.target_namespace}"
|
|
88
|
+
puts " Version: #{parser.schema_version || 'Not specified'}"
|
|
89
|
+
puts " Elements: #{parser.elements.size}"
|
|
90
|
+
puts " Complex Types: #{parser.complex_types.size}"
|
|
91
|
+
puts " Simple Types: #{parser.simple_types.size}"
|
|
92
|
+
puts ""
|
|
93
|
+
|
|
94
|
+
# Now generate
|
|
95
|
+
generator = Xseed::Svg::SvgGenerator.new(xsd_file)
|
|
96
|
+
output_file = File.join(__dir__, "output", "person-inspected.svg")
|
|
97
|
+
FileUtils.mkdir_p(File.dirname(output_file))
|
|
98
|
+
generator.generate_file(output_file)
|
|
99
|
+
|
|
100
|
+
puts "✓ SVG generated after inspection: #{output_file}\n\n"
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Example 5: Batch processing multiple schemas
|
|
104
|
+
def example_batch_processing
|
|
105
|
+
puts "Example 5: Batch process multiple schemas"
|
|
106
|
+
puts "=" * 60
|
|
107
|
+
|
|
108
|
+
schemas_dir = File.join(__dir__, "schemas")
|
|
109
|
+
output_dir = File.join(__dir__, "output", "batch")
|
|
110
|
+
FileUtils.mkdir_p(output_dir)
|
|
111
|
+
|
|
112
|
+
# Find all XSD files
|
|
113
|
+
xsd_files = Dir.glob(File.join(schemas_dir, "*.xsd"))
|
|
114
|
+
|
|
115
|
+
puts "Found #{xsd_files.size} schema files\n"
|
|
116
|
+
|
|
117
|
+
xsd_files.each do |xsd_file|
|
|
118
|
+
basename = File.basename(xsd_file, ".xsd")
|
|
119
|
+
output_file = File.join(output_dir, "#{basename}-diagram.svg")
|
|
120
|
+
|
|
121
|
+
begin
|
|
122
|
+
generator = Xseed::Svg::SvgGenerator.new(xsd_file)
|
|
123
|
+
generator.generate_file(output_file)
|
|
124
|
+
puts " ✓ #{basename}.xsd → #{basename}-diagram.svg"
|
|
125
|
+
rescue StandardError => e
|
|
126
|
+
puts " ✗ #{basename}.xsd: #{e.message}"
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
puts "\n✓ Batch processing complete\n\n"
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Run all examples
|
|
134
|
+
if __FILE__ == $PROGRAM_NAME
|
|
135
|
+
puts "\n"
|
|
136
|
+
puts "╔#{'═' * 58}╗"
|
|
137
|
+
puts "║#{' ' * 12}Xseed Simple Generation Examples#{' ' * 13}║"
|
|
138
|
+
puts "╚#{'═' * 58}╝"
|
|
139
|
+
puts "\n"
|
|
140
|
+
|
|
141
|
+
example_generate_to_stdout
|
|
142
|
+
example_generate_to_file
|
|
143
|
+
example_with_error_handling
|
|
144
|
+
example_inspect_before_generate
|
|
145
|
+
example_batch_processing
|
|
146
|
+
|
|
147
|
+
puts "All examples completed!"
|
|
148
|
+
puts "\nGenerated files are in: #{File.join(__dir__, 'output')}"
|
|
149
|
+
end
|
data/exe/xseed
ADDED
data/lib/xseed/cli.rb
ADDED
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "thor"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
require_relative "documentation/config"
|
|
6
|
+
require_relative "documentation/html_generator"
|
|
7
|
+
require_relative "version"
|
|
8
|
+
|
|
9
|
+
module Xseed
|
|
10
|
+
# Command-line interface for Xseed gem
|
|
11
|
+
# Provides commands for generating SVG diagrams and HTML documentation from XSD
|
|
12
|
+
class CLI < Thor
|
|
13
|
+
def self.exit_on_failure?
|
|
14
|
+
true
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Global options
|
|
18
|
+
class_option :verbose,
|
|
19
|
+
type: :boolean,
|
|
20
|
+
aliases: "-v",
|
|
21
|
+
desc: "Enable verbose output"
|
|
22
|
+
|
|
23
|
+
desc "svg INPUT_XSD [OPTIONS]", "Generate SVG diagram from XSD schema"
|
|
24
|
+
long_desc <<~DESC
|
|
25
|
+
Generate an SVG diagram visualizing the structure of an XSD schema file.
|
|
26
|
+
|
|
27
|
+
This command uses the xsdvi gem for SVG generation.
|
|
28
|
+
|
|
29
|
+
Examples:
|
|
30
|
+
|
|
31
|
+
# Generate SVG and write to file
|
|
32
|
+
$ xseed svg schema.xsd -o diagram.svg
|
|
33
|
+
|
|
34
|
+
# Generate diagram for a single element
|
|
35
|
+
$ xseed svg schema.xsd -r ElementName -o diagram.svg
|
|
36
|
+
|
|
37
|
+
# Output SVG to stdout
|
|
38
|
+
$ xseed svg schema.xsd
|
|
39
|
+
|
|
40
|
+
For more options, see: https://github.com/metanorma/xsdvi-ruby
|
|
41
|
+
DESC
|
|
42
|
+
option :output,
|
|
43
|
+
aliases: "-o",
|
|
44
|
+
desc: "Output file path (default: stdout)",
|
|
45
|
+
banner: "PATH"
|
|
46
|
+
option :root_node_name,
|
|
47
|
+
aliases: "-r",
|
|
48
|
+
desc: "Root element name to visualize",
|
|
49
|
+
banner: "NAME"
|
|
50
|
+
option :one_node_only,
|
|
51
|
+
type: :boolean,
|
|
52
|
+
desc: "Show only the specified element"
|
|
53
|
+
option :force,
|
|
54
|
+
type: :boolean,
|
|
55
|
+
aliases: "-f",
|
|
56
|
+
desc: "Overwrite output file if it exists"
|
|
57
|
+
def svg(input_xsd)
|
|
58
|
+
require "xsdvi"
|
|
59
|
+
|
|
60
|
+
start_time = Time.now
|
|
61
|
+
|
|
62
|
+
# Validate input file
|
|
63
|
+
validate_input_file!(input_xsd)
|
|
64
|
+
|
|
65
|
+
# Check output file permissions if specified
|
|
66
|
+
validate_output_path!(options[:output]) if options[:output]
|
|
67
|
+
|
|
68
|
+
log_verbose "Generating SVG using xsdvi gem"
|
|
69
|
+
|
|
70
|
+
# Create xsdvi components using proper Ruby API
|
|
71
|
+
output_path = options[:output] || "/dev/stdout"
|
|
72
|
+
writer = Xsdvi::Utils::Writer.new(output_path)
|
|
73
|
+
builder = Xsdvi::Tree::Builder.new
|
|
74
|
+
handler = Xsdvi::XsdHandler.new(builder)
|
|
75
|
+
|
|
76
|
+
# Set options
|
|
77
|
+
handler.root_node_name = options[:root_node_name] if options[:root_node_name]
|
|
78
|
+
handler.one_node_only = options[:one_node_only] if options[:one_node_only]
|
|
79
|
+
|
|
80
|
+
# Process XSD file
|
|
81
|
+
handler.process_file(input_xsd)
|
|
82
|
+
root_symbol = builder.root
|
|
83
|
+
|
|
84
|
+
# Generate SVG
|
|
85
|
+
generator = Xsdvi::SVG::Generator.new(writer)
|
|
86
|
+
generator.hide_menu_buttons = options[:one_node_only] if options[:one_node_only]
|
|
87
|
+
|
|
88
|
+
log_with_progress("Generating SVG diagram") do
|
|
89
|
+
generator.draw(root_symbol)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
if options[:output]
|
|
93
|
+
elapsed = Time.now - start_time
|
|
94
|
+
say "✓ SVG diagram generated: #{options[:output]}", :green
|
|
95
|
+
log_verbose "Generation completed in #{elapsed.round(3)}s"
|
|
96
|
+
end
|
|
97
|
+
rescue LoadError
|
|
98
|
+
error_exit "xsdvi gem not found. Please run: gem install xsdvi"
|
|
99
|
+
rescue ArgumentError => e
|
|
100
|
+
error_exit "Validation error: #{e.message}"
|
|
101
|
+
rescue StandardError => e
|
|
102
|
+
error_exit "SVG generation error: #{e.message}", show_backtrace: true
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
desc "html INPUT_XSD [OPTIONS]",
|
|
106
|
+
"Generate HTML documentation from XSD"
|
|
107
|
+
long_desc <<~DESC
|
|
108
|
+
Generate comprehensive HTML documentation from an XSD schema file.
|
|
109
|
+
|
|
110
|
+
SVG diagrams are automatically generated for all elements using the xsdvi gem.
|
|
111
|
+
The diagrams are placed in a subdirectory (default: diagrams/) and automatically
|
|
112
|
+
embedded in the HTML with <object> tags.
|
|
113
|
+
|
|
114
|
+
Options:
|
|
115
|
+
-o, --output PATH Output HTML file path
|
|
116
|
+
-t, --title TEXT Documentation title
|
|
117
|
+
--css PATH External CSS file to include
|
|
118
|
+
-d, --diagrams-dir DIR Directory for SVG diagrams (default: diagrams)
|
|
119
|
+
|
|
120
|
+
Example:
|
|
121
|
+
$ xseed htm schema.xsd -o docs/index.html
|
|
122
|
+
|
|
123
|
+
This generates:
|
|
124
|
+
docs/index.html (HTML documentation)
|
|
125
|
+
docs/diagrams/*.svg (SVG diagrams for each element)
|
|
126
|
+
|
|
127
|
+
For more information, visit: https://github.com/metanorma/xseed
|
|
128
|
+
DESC
|
|
129
|
+
option :output,
|
|
130
|
+
aliases: "-o",
|
|
131
|
+
desc: "Output file path",
|
|
132
|
+
banner: "PATH"
|
|
133
|
+
option :title,
|
|
134
|
+
aliases: "-t",
|
|
135
|
+
desc: "Documentation title",
|
|
136
|
+
banner: "TEXT"
|
|
137
|
+
option :css,
|
|
138
|
+
desc: "External CSS file",
|
|
139
|
+
banner: "PATH"
|
|
140
|
+
option :diagrams_dir,
|
|
141
|
+
aliases: "-d",
|
|
142
|
+
desc: "Directory for SVG diagrams relative to output (default: diagrams)",
|
|
143
|
+
banner: "DIR"
|
|
144
|
+
option :force,
|
|
145
|
+
type: :boolean,
|
|
146
|
+
aliases: "-f",
|
|
147
|
+
desc: "Overwrite output file if it exists"
|
|
148
|
+
def html(input_xsd)
|
|
149
|
+
start_time = Time.now
|
|
150
|
+
|
|
151
|
+
# Validate input file
|
|
152
|
+
validate_input_file!(input_xsd)
|
|
153
|
+
|
|
154
|
+
# Check output file permissions if specified
|
|
155
|
+
validate_output_path!(options[:output]) if options[:output]
|
|
156
|
+
|
|
157
|
+
# Create config
|
|
158
|
+
config = Xseed::Documentation::Config.new
|
|
159
|
+
config.title = options[:title] if options[:title]
|
|
160
|
+
config.external_css_url = options[:css] if options[:css]
|
|
161
|
+
config.diagrams_dir = options[:diagrams_dir] if options[:diagrams_dir]
|
|
162
|
+
|
|
163
|
+
log_verbose "Creating HTML generator for: #{input_xsd}"
|
|
164
|
+
|
|
165
|
+
# Create generator
|
|
166
|
+
generator = Xseed::Documentation::HtmlGenerator.new(input_xsd, config)
|
|
167
|
+
log_verbose "HTML generator initialized"
|
|
168
|
+
|
|
169
|
+
# Determine output path
|
|
170
|
+
output_path = options[:output]
|
|
171
|
+
|
|
172
|
+
if output_path
|
|
173
|
+
# Check if output file exists
|
|
174
|
+
check_output_file_exists!(output_path)
|
|
175
|
+
|
|
176
|
+
# Generate and write to file
|
|
177
|
+
log_with_progress("Generating HTML documentation") do
|
|
178
|
+
generator.generate_file(output_path)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
elapsed = Time.now - start_time
|
|
182
|
+
say "✓ HTML documentation generated: #{output_path}", :green
|
|
183
|
+
log_verbose "Generation completed in #{elapsed.round(3)}s"
|
|
184
|
+
else
|
|
185
|
+
# Generate and output to stdout
|
|
186
|
+
log_verbose "Generating HTML to stdout"
|
|
187
|
+
html_content = generator.generate
|
|
188
|
+
say html_content
|
|
189
|
+
end
|
|
190
|
+
rescue ArgumentError => e
|
|
191
|
+
error_exit "Validation error: #{e.message}"
|
|
192
|
+
rescue StandardError => e
|
|
193
|
+
error_exit "HTML generation error: #{e.message}", show_backtrace: true
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
desc "version", "Display Xseed version information"
|
|
197
|
+
def version
|
|
198
|
+
say "━" * 60, :cyan
|
|
199
|
+
say "Xseed - XSD Documentation Generator", :cyan
|
|
200
|
+
say "━" * 60, :cyan
|
|
201
|
+
say ""
|
|
202
|
+
say "Version: #{Xseed::VERSION}", :green
|
|
203
|
+
say ""
|
|
204
|
+
say "Features:"
|
|
205
|
+
say " ✓ HTML documentation generation (native)", :green
|
|
206
|
+
say " ✓ SVG diagram generation (via xsdvi gem)", :green
|
|
207
|
+
say " ✓ XSD schema parsing", :green
|
|
208
|
+
say ""
|
|
209
|
+
say "GitHub: https://github.com/metanorma/xseed"
|
|
210
|
+
say "License: BSD-2-Clause"
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
desc "info INPUT_XSD", "Display information about an XSD schema"
|
|
214
|
+
long_desc <<~DESC
|
|
215
|
+
Display detailed information about an XSD schema file without generating output.
|
|
216
|
+
|
|
217
|
+
Shows:
|
|
218
|
+
• Schema metadata (namespace, version, etc.)
|
|
219
|
+
• Component counts (elements, types, groups)
|
|
220
|
+
• Complexity metrics
|
|
221
|
+
• Estimated generation time
|
|
222
|
+
|
|
223
|
+
Example:
|
|
224
|
+
$ xseed info schema.xsd
|
|
225
|
+
DESC
|
|
226
|
+
def info(input_xsd)
|
|
227
|
+
validate_input_file!(input_xsd)
|
|
228
|
+
|
|
229
|
+
say "━" * 60, :cyan
|
|
230
|
+
say "XSD Schema Information", :cyan
|
|
231
|
+
say "━" * 60, :cyan
|
|
232
|
+
say ""
|
|
233
|
+
|
|
234
|
+
log_verbose "Parsing schema..."
|
|
235
|
+
parser = Parser::XsdParser.new(input_xsd)
|
|
236
|
+
|
|
237
|
+
say "File: #{input_xsd}", :green
|
|
238
|
+
say "Size: #{format_file_size(File.size(input_xsd))}"
|
|
239
|
+
say ""
|
|
240
|
+
|
|
241
|
+
say "Schema Metadata:", :cyan
|
|
242
|
+
say " Target Namespace: #{parser.target_namespace || 'None'}"
|
|
243
|
+
say " Version: #{parser.schema_version || 'Not specified'}"
|
|
244
|
+
say " Element Form Default: #{parser.element_form_default || 'Not specified'}"
|
|
245
|
+
say ""
|
|
246
|
+
|
|
247
|
+
say "Components:", :cyan
|
|
248
|
+
say " Elements: #{parser.elements.size}"
|
|
249
|
+
say " Complex Types: #{parser.complex_types.size}"
|
|
250
|
+
say " Simple Types: #{parser.simple_types.size}"
|
|
251
|
+
say " Groups: #{parser.groups.size}"
|
|
252
|
+
say " Attribute Groups: #{parser.attribute_groups.size}"
|
|
253
|
+
say ""
|
|
254
|
+
|
|
255
|
+
total_components = parser.elements.size + parser.types.size +
|
|
256
|
+
parser.groups.size + parser.attribute_groups.size
|
|
257
|
+
say " Total Components: #{total_components}"
|
|
258
|
+
say ""
|
|
259
|
+
|
|
260
|
+
# Estimate complexity
|
|
261
|
+
complexity = estimate_complexity(total_components)
|
|
262
|
+
say "Complexity: #{complexity[:level]}", complexity[:color]
|
|
263
|
+
say " Estimated generation time: #{complexity[:time]}"
|
|
264
|
+
say ""
|
|
265
|
+
|
|
266
|
+
if parser.documentation
|
|
267
|
+
say "Schema Documentation:", :cyan
|
|
268
|
+
say " #{parser.documentation.lines.first.strip}"
|
|
269
|
+
if parser.documentation.lines.size > 1
|
|
270
|
+
say " (#{parser.documentation.lines.size} lines total)"
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
rescue StandardError => e
|
|
274
|
+
error_exit "Error reading schema: #{e.message}"
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
no_commands do
|
|
278
|
+
# Validates input file exists and is readable
|
|
279
|
+
def validate_input_file!(path)
|
|
280
|
+
raise ArgumentError, "File not found: #{path}" unless File.exist?(path)
|
|
281
|
+
|
|
282
|
+
unless File.readable?(path)
|
|
283
|
+
raise ArgumentError, "File not readable: #{path}"
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
raise ArgumentError, "Not a file: #{path}" unless File.file?(path)
|
|
287
|
+
|
|
288
|
+
return if path.end_with?(".xsd")
|
|
289
|
+
|
|
290
|
+
return unless options[:verbose]
|
|
291
|
+
|
|
292
|
+
say "Warning: File does not have .xsd extension",
|
|
293
|
+
:yellow
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
# Validates output path is writable
|
|
297
|
+
def validate_output_path!(path)
|
|
298
|
+
dir = File.dirname(path)
|
|
299
|
+
|
|
300
|
+
unless Dir.exist?(dir)
|
|
301
|
+
log_verbose "Output directory does not exist, will create: #{dir}"
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
return unless File.exist?(path) && !File.writable?(path)
|
|
305
|
+
|
|
306
|
+
raise ArgumentError, "Output file exists but is not writable: #{path}"
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# Checks if output file exists and handles --force option
|
|
310
|
+
def check_output_file_exists!(path)
|
|
311
|
+
return unless File.exist?(path)
|
|
312
|
+
return if options[:force]
|
|
313
|
+
|
|
314
|
+
say "Warning: Output file already exists: #{path}", :yellow
|
|
315
|
+
return if yes?("Overwrite? (y/n)")
|
|
316
|
+
|
|
317
|
+
say "Aborted.", :red
|
|
318
|
+
exit 1
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
# Logs message if verbose option is enabled
|
|
322
|
+
def log_verbose(message)
|
|
323
|
+
say " → #{message}", :cyan if options[:verbose]
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
# Executes block with progress indicator for verbose mode
|
|
327
|
+
def log_with_progress(message)
|
|
328
|
+
if options[:verbose]
|
|
329
|
+
say " → #{message}...", :cyan
|
|
330
|
+
result = yield
|
|
331
|
+
say " ✓ Complete", :green
|
|
332
|
+
result
|
|
333
|
+
else
|
|
334
|
+
yield
|
|
335
|
+
end
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
# Exits with error message
|
|
339
|
+
def error_exit(message, show_backtrace: false)
|
|
340
|
+
say "✗ Error: #{message}", :red
|
|
341
|
+
|
|
342
|
+
if show_backtrace && options[:verbose]
|
|
343
|
+
say "\nBacktrace:", :red
|
|
344
|
+
say caller.join("\n"), :red
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
exit 1
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
# Formats file size for display
|
|
351
|
+
def format_file_size(bytes)
|
|
352
|
+
return "#{bytes} B" if bytes < 1024
|
|
353
|
+
|
|
354
|
+
kb = bytes / 1024.0
|
|
355
|
+
return "#{kb.round(1)} KB" if kb < 1024
|
|
356
|
+
|
|
357
|
+
mb = kb / 1024.0
|
|
358
|
+
"#{mb.round(2)} MB"
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
# Estimates schema complexity
|
|
362
|
+
def estimate_complexity(component_count)
|
|
363
|
+
case component_count
|
|
364
|
+
when 0..10
|
|
365
|
+
{ level: "Simple", time: "< 100ms", color: :green }
|
|
366
|
+
when 11..50
|
|
367
|
+
{ level: "Moderate", time: "< 500ms", color: :cyan }
|
|
368
|
+
when 51..200
|
|
369
|
+
{ level: "Complex", time: "< 2s", color: :yellow }
|
|
370
|
+
else
|
|
371
|
+
{ level: "Very Complex", time: "> 2s", color: :red }
|
|
372
|
+
end
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
end
|
|
376
|
+
end
|