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,169 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "nokogiri"
|
|
4
|
+
|
|
5
|
+
module Xseed
|
|
6
|
+
module Documentation
|
|
7
|
+
module Presentation
|
|
8
|
+
# Generates navigation sidebar (Table of Contents) for HTML documentation
|
|
9
|
+
# Ports functionality from XS3P navigation.xsl
|
|
10
|
+
class NavigationBuilder
|
|
11
|
+
def initialize(parser, config)
|
|
12
|
+
@parser = parser
|
|
13
|
+
@config = config
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def generate
|
|
17
|
+
builder = Nokogiri::HTML::Builder.new do |html|
|
|
18
|
+
html.nav(id: "toc") do
|
|
19
|
+
html.ul(class: "nav nav-list xs3p-sidenav") do
|
|
20
|
+
generate_toc_items(html)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
builder.to_html
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def generate_toc_items(html)
|
|
30
|
+
# Schema properties
|
|
31
|
+
html.li do
|
|
32
|
+
html.strong do
|
|
33
|
+
html.a("Schema Document Properties", href: "#SchemaProperties")
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Generate component sections based on sorting preference
|
|
38
|
+
if @config.sort_by_component
|
|
39
|
+
generate_sorted_components(html)
|
|
40
|
+
else
|
|
41
|
+
generate_unsorted_components(html)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Glossary (if enabled)
|
|
45
|
+
return unless @config.print_glossary
|
|
46
|
+
|
|
47
|
+
html.li do
|
|
48
|
+
html.strong do
|
|
49
|
+
html.a("Glossary", href: "#Glossary")
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def generate_sorted_components(html)
|
|
55
|
+
# Elements
|
|
56
|
+
if @parser.elements.any?
|
|
57
|
+
generate_component_section(html, @parser.elements, "Elements",
|
|
58
|
+
"#SchemaElements")
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Complex Types
|
|
62
|
+
if @parser.complex_types.any?
|
|
63
|
+
generate_component_section(html, @parser.complex_types,
|
|
64
|
+
"Complex Types", "#SchemaComplexTypes")
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Groups
|
|
68
|
+
if @parser.groups.any?
|
|
69
|
+
generate_component_section(html, @parser.groups, "Groups",
|
|
70
|
+
"#SchemaGroups")
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Simple Types
|
|
74
|
+
if @parser.simple_types.any?
|
|
75
|
+
generate_component_section(html, @parser.simple_types, "Types",
|
|
76
|
+
"#SchemaSimpleTypes")
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Attribute Groups
|
|
80
|
+
return unless @parser.attribute_groups.any?
|
|
81
|
+
|
|
82
|
+
generate_component_section(html, @parser.attribute_groups,
|
|
83
|
+
"Attribute Groups", "#SchemaAttributeGroups")
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def generate_unsorted_components(html)
|
|
87
|
+
html.li do
|
|
88
|
+
html.strong do
|
|
89
|
+
html.a("Global Schema Components", href: "#SchemaComponents")
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Generate all components in order they appear
|
|
94
|
+
all_components = []
|
|
95
|
+
if @parser.attribute_groups.any?
|
|
96
|
+
all_components.concat(@parser.attribute_groups)
|
|
97
|
+
end
|
|
98
|
+
if @parser.complex_types.any?
|
|
99
|
+
all_components.concat(@parser.complex_types)
|
|
100
|
+
end
|
|
101
|
+
all_components.concat(@parser.elements) if @parser.elements.any?
|
|
102
|
+
all_components.concat(@parser.groups) if @parser.groups.any?
|
|
103
|
+
if @parser.simple_types.any?
|
|
104
|
+
all_components.concat(@parser.simple_types)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
all_components.each do |component|
|
|
108
|
+
generate_component_link(html, component)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def generate_component_section(html, components, title, anchor)
|
|
113
|
+
return if components.empty?
|
|
114
|
+
|
|
115
|
+
html.li do
|
|
116
|
+
html.strong do
|
|
117
|
+
html.a(title, href: anchor)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
html.ul(class: "nav nav-list nav-list-#{title.downcase.tr(' ',
|
|
121
|
+
'-')}") do
|
|
122
|
+
sorted_components = components.sort_by { |c| c["name"] || "" }
|
|
123
|
+
sorted_components.each do |component|
|
|
124
|
+
generate_component_link(html, component)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def generate_component_link(html, component)
|
|
131
|
+
component_name = component["name"]
|
|
132
|
+
return unless component_name
|
|
133
|
+
|
|
134
|
+
component_type = component.name
|
|
135
|
+
component_id = generate_component_id(component_type, component_name)
|
|
136
|
+
|
|
137
|
+
html.li(class: "nav-sub-item") do
|
|
138
|
+
html.a(href: "##{component_id}") do
|
|
139
|
+
html.strong component_name
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def generate_component_id(component_type, component_name)
|
|
145
|
+
type_name = case component_type
|
|
146
|
+
when "element"
|
|
147
|
+
"element"
|
|
148
|
+
when "complexType"
|
|
149
|
+
"complex-type"
|
|
150
|
+
when "simpleType"
|
|
151
|
+
"simple-type"
|
|
152
|
+
when "group"
|
|
153
|
+
"model-group"
|
|
154
|
+
when "attributeGroup"
|
|
155
|
+
"attribute-group"
|
|
156
|
+
when "attribute"
|
|
157
|
+
"attribute"
|
|
158
|
+
when "notation"
|
|
159
|
+
"notation"
|
|
160
|
+
else
|
|
161
|
+
component_type.downcase
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
"#{type_name}-#{component_name}"
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "lutaml/xsd"
|
|
4
|
+
require "nokogiri"
|
|
5
|
+
|
|
6
|
+
module Xseed
|
|
7
|
+
module Documentation
|
|
8
|
+
# Wrapper around Lutaml::Xsd for loading and parsing XSD schemas
|
|
9
|
+
#
|
|
10
|
+
# This class provides a simple interface to lutaml-xsd's schema parsing
|
|
11
|
+
# capabilities, with Xseed-specific error handling and configuration.
|
|
12
|
+
#
|
|
13
|
+
# @example Load a schema from file
|
|
14
|
+
# schema = SchemaLoader.load("path/to/schema.xsd")
|
|
15
|
+
# puts schema.target_namespace
|
|
16
|
+
#
|
|
17
|
+
# @example Parse schema content with mappings
|
|
18
|
+
# schema = SchemaLoader.parse(
|
|
19
|
+
# xsd_content,
|
|
20
|
+
# location: "/path/to/schemas",
|
|
21
|
+
# schema_mappings: [
|
|
22
|
+
# { from: "common.xsd", to: "/local/common.xsd" }
|
|
23
|
+
# ]
|
|
24
|
+
# )
|
|
25
|
+
#
|
|
26
|
+
class SchemaLoader
|
|
27
|
+
# Error raised when schema file cannot be loaded
|
|
28
|
+
class LoadError < StandardError; end
|
|
29
|
+
|
|
30
|
+
# Error raised when schema content cannot be parsed
|
|
31
|
+
class ParseError < StandardError; end
|
|
32
|
+
|
|
33
|
+
# Load an XSD schema from a file
|
|
34
|
+
#
|
|
35
|
+
# @param file_path [String] Path to the XSD file
|
|
36
|
+
# @param config [Config] Optional configuration for schema mappings
|
|
37
|
+
# @return [Lutaml::Xsd::Schema] Parsed schema object
|
|
38
|
+
# @raise [LoadError] if file cannot be read
|
|
39
|
+
# @raise [ParseError] if schema cannot be parsed
|
|
40
|
+
def self.load(file_path, config: nil)
|
|
41
|
+
content = File.read(file_path)
|
|
42
|
+
location = File.dirname(File.expand_path(file_path))
|
|
43
|
+
|
|
44
|
+
parse(content, location: location, config: config)
|
|
45
|
+
rescue Errno::ENOENT
|
|
46
|
+
raise LoadError, "Schema file not found: #{file_path}"
|
|
47
|
+
rescue ParseError => e
|
|
48
|
+
raise e
|
|
49
|
+
rescue StandardError => e
|
|
50
|
+
raise ParseError, "Failed to load schema: #{e.message}"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Parse XSD schema content
|
|
54
|
+
#
|
|
55
|
+
# @param content [String] XSD schema content
|
|
56
|
+
# @param location [String] Base directory or URL for resolving imports
|
|
57
|
+
# @param schema_mappings [Array<Hash>] Schema location mappings
|
|
58
|
+
# @param config [Config] Optional configuration for additional mappings
|
|
59
|
+
# @return [Lutaml::Xsd::Schema] Parsed schema object
|
|
60
|
+
# @raise [ParseError] if schema cannot be parsed
|
|
61
|
+
def self.parse(content, location: nil, schema_mappings: nil, config: nil)
|
|
62
|
+
# Validate that it's actually an XSD schema
|
|
63
|
+
validate_schema_content(content)
|
|
64
|
+
|
|
65
|
+
mappings = build_mappings(schema_mappings, config)
|
|
66
|
+
|
|
67
|
+
# Use lutaml-xsd's parse method
|
|
68
|
+
Lutaml::Xsd.parse(
|
|
69
|
+
content,
|
|
70
|
+
location: location,
|
|
71
|
+
schema_mappings: mappings,
|
|
72
|
+
)
|
|
73
|
+
rescue Nokogiri::XML::SyntaxError => e
|
|
74
|
+
raise ParseError, "Invalid XML syntax: #{e.message}"
|
|
75
|
+
rescue StandardError => e
|
|
76
|
+
raise ParseError, "Failed to parse schema: #{e.message}"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Validate that content is an XSD schema document
|
|
80
|
+
#
|
|
81
|
+
# @param content [String] XML content to validate
|
|
82
|
+
# @raise [ParseError] if not a valid schema
|
|
83
|
+
def self.validate_schema_content(content)
|
|
84
|
+
doc = Nokogiri::XML(content)
|
|
85
|
+
root = doc.root
|
|
86
|
+
|
|
87
|
+
raise ParseError, "Empty or invalid XML document" unless root
|
|
88
|
+
|
|
89
|
+
# Check if root element is xs:schema or xsd:schema
|
|
90
|
+
unless root.name == "schema" &&
|
|
91
|
+
root.namespace&.href&.include?("XMLSchema")
|
|
92
|
+
raise ParseError,
|
|
93
|
+
"Not a valid XSD schema: root element must be xs:schema"
|
|
94
|
+
end
|
|
95
|
+
rescue Nokogiri::XML::SyntaxError => e
|
|
96
|
+
raise ParseError, "Invalid XML syntax: #{e.message}"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Build schema location mappings from various sources
|
|
100
|
+
#
|
|
101
|
+
# @param explicit_mappings [Array<Hash>] Explicitly provided mappings
|
|
102
|
+
# @param config [Config] Configuration object with potential mappings
|
|
103
|
+
# @return [Array<Hash>] Combined schema location mappings
|
|
104
|
+
def self.build_mappings(explicit_mappings, config)
|
|
105
|
+
mappings = []
|
|
106
|
+
|
|
107
|
+
# Add explicit mappings if provided
|
|
108
|
+
mappings.concat(explicit_mappings) if explicit_mappings
|
|
109
|
+
|
|
110
|
+
# Add config-based mappings if available
|
|
111
|
+
if config.respond_to?(:schema_mappings)
|
|
112
|
+
mappings.concat(config.schema_mappings)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
mappings.empty? ? nil : mappings
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
private_class_method :build_mappings, :validate_schema_content
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cgi"
|
|
4
|
+
|
|
5
|
+
module Xseed
|
|
6
|
+
module Documentation
|
|
7
|
+
module Utils
|
|
8
|
+
# Helpers utility module for common formatting and component utilities
|
|
9
|
+
#
|
|
10
|
+
# This module provides methods for formatting schema component descriptions,
|
|
11
|
+
# generating anchor IDs, formatting occurrences, URIs, boolean values, and
|
|
12
|
+
# various XSD-specific sets.
|
|
13
|
+
#
|
|
14
|
+
# Based on XS3P common-helpers.xsl utilities.
|
|
15
|
+
module Helpers
|
|
16
|
+
# Component type prefixes for anchor IDs
|
|
17
|
+
COMPONENT_PREFIXES = {
|
|
18
|
+
"attribute" => "attr-",
|
|
19
|
+
"attributeGroup" => "attrgrp-",
|
|
20
|
+
"complexType" => "ctype-",
|
|
21
|
+
"element" => "elem-",
|
|
22
|
+
"group" => "grp-",
|
|
23
|
+
"notation" => "nota-",
|
|
24
|
+
"simpleType" => "stype-",
|
|
25
|
+
"key" => "key-",
|
|
26
|
+
"unique" => "key-",
|
|
27
|
+
}.freeze
|
|
28
|
+
|
|
29
|
+
# Returns a human-readable description for a schema component type
|
|
30
|
+
#
|
|
31
|
+
# @param component_type [String] The component type (e.g., "element", "complexType")
|
|
32
|
+
# @return [String] Human-readable description
|
|
33
|
+
#
|
|
34
|
+
# @example
|
|
35
|
+
# component_description("element") #=> "Element"
|
|
36
|
+
# component_description("complexType") #=> "Complex Type"
|
|
37
|
+
def component_description(component_type)
|
|
38
|
+
case component_type
|
|
39
|
+
when "attribute" then "Attribute"
|
|
40
|
+
when "attributeGroup" then "Attribute Group"
|
|
41
|
+
when "complexType" then "Complex Type"
|
|
42
|
+
when "element" then "Element"
|
|
43
|
+
when "simpleType" then "Simple Type"
|
|
44
|
+
when "group" then "Model Group"
|
|
45
|
+
when "notation" then "Notation"
|
|
46
|
+
when "all" then "All Model Group"
|
|
47
|
+
when "choice" then "Choice Model Group"
|
|
48
|
+
when "sequence" then "Sequence Model Group"
|
|
49
|
+
else "Unknown Component"
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Generates a unique anchor ID for a schema component
|
|
54
|
+
#
|
|
55
|
+
# @param component_type [String] The component type
|
|
56
|
+
# @param name [String, nil] The component name
|
|
57
|
+
# @return [String] The generated anchor ID
|
|
58
|
+
#
|
|
59
|
+
# @example
|
|
60
|
+
# generate_component_id("element", "Person") #=> "elem-Person"
|
|
61
|
+
# generate_component_id("schema", nil) #=> "schema"
|
|
62
|
+
def generate_component_id(component_type, name)
|
|
63
|
+
return "schema" if component_type == "schema"
|
|
64
|
+
|
|
65
|
+
prefix = COMPONENT_PREFIXES[component_type] || "comp-"
|
|
66
|
+
"#{prefix}#{name}"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Formats min/max occurrences as [min..max]
|
|
70
|
+
#
|
|
71
|
+
# @param min_occurs [Integer, String, nil] Minimum occurrences (default: 1)
|
|
72
|
+
# @param max_occurs [Integer, String, nil] Maximum occurrences (default: 1)
|
|
73
|
+
# @return [String] Formatted occurrence string
|
|
74
|
+
#
|
|
75
|
+
# @example
|
|
76
|
+
# format_occurs(1, 1) #=> "[1]"
|
|
77
|
+
# format_occurs(0, "unbounded") #=> "[0..*]"
|
|
78
|
+
# format_occurs(1, 5) #=> "[1..5]"
|
|
79
|
+
def format_occurs(min_occurs, max_occurs)
|
|
80
|
+
min = min_occurs.nil? ? 1 : min_occurs.to_i
|
|
81
|
+
max = if max_occurs == "unbounded"
|
|
82
|
+
"*"
|
|
83
|
+
elsif max_occurs.nil?
|
|
84
|
+
1
|
|
85
|
+
else
|
|
86
|
+
max_occurs.to_i
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
if min == 1 && max == 1
|
|
90
|
+
"[1]"
|
|
91
|
+
else
|
|
92
|
+
"[#{min}..#{max}]"
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Formats a URI, creating a clickable link for HTTP/HTTPS URIs
|
|
97
|
+
#
|
|
98
|
+
# @param uri [String, nil] The URI to format
|
|
99
|
+
# @return [String] Formatted URI (HTML link or plain text)
|
|
100
|
+
#
|
|
101
|
+
# @example
|
|
102
|
+
# format_uri("http://example.com")
|
|
103
|
+
# #=> "<a href=\"http://example.com\" title=\"http://example.com\">http://example.com</a>"
|
|
104
|
+
#
|
|
105
|
+
# format_uri("schema.xsd")
|
|
106
|
+
# #=> "schema.xsd"
|
|
107
|
+
def format_uri(uri)
|
|
108
|
+
return "" if uri.nil? || uri.empty?
|
|
109
|
+
|
|
110
|
+
if uri.start_with?("http://", "https://")
|
|
111
|
+
escaped_uri = CGI.escape_html(uri)
|
|
112
|
+
"<a href=\"#{escaped_uri}\" title=\"#{escaped_uri}\">#{escaped_uri}</a>"
|
|
113
|
+
else
|
|
114
|
+
uri
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Formats a boolean value as "yes" or "no"
|
|
119
|
+
#
|
|
120
|
+
# @param value [Boolean, String, Integer, nil] The boolean value
|
|
121
|
+
# @return [String] "yes" or "no"
|
|
122
|
+
#
|
|
123
|
+
# @example
|
|
124
|
+
# format_boolean(true) #=> "yes"
|
|
125
|
+
# format_boolean("true") #=> "yes"
|
|
126
|
+
# format_boolean(1) #=> "yes"
|
|
127
|
+
# format_boolean(false) #=> "no"
|
|
128
|
+
# format_boolean(nil) #=> "no"
|
|
129
|
+
def format_boolean(value)
|
|
130
|
+
return "no" if value.nil?
|
|
131
|
+
|
|
132
|
+
normalized = value.to_s.downcase.strip
|
|
133
|
+
if %w[true 1].include?(normalized)
|
|
134
|
+
"yes"
|
|
135
|
+
else
|
|
136
|
+
"no"
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Formats a block attribute set, expanding #all
|
|
141
|
+
#
|
|
142
|
+
# @param value [String, nil] The block value
|
|
143
|
+
# @return [String] Formatted block set
|
|
144
|
+
#
|
|
145
|
+
# @example
|
|
146
|
+
# format_block_set("#all")
|
|
147
|
+
# #=> "restriction, extension, substitution"
|
|
148
|
+
#
|
|
149
|
+
# format_block_set("restriction")
|
|
150
|
+
# #=> "restriction"
|
|
151
|
+
def format_block_set(value)
|
|
152
|
+
return "" if value.nil? || value.strip.empty?
|
|
153
|
+
|
|
154
|
+
if value.strip == "#all"
|
|
155
|
+
"restriction, extension, substitution"
|
|
156
|
+
else
|
|
157
|
+
value
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Formats a derivation set, expanding #all
|
|
162
|
+
#
|
|
163
|
+
# @param value [String, nil] The derivation value
|
|
164
|
+
# @return [String] Formatted derivation set
|
|
165
|
+
#
|
|
166
|
+
# @example
|
|
167
|
+
# format_derivation_set("#all")
|
|
168
|
+
# #=> "restriction, extension"
|
|
169
|
+
#
|
|
170
|
+
# format_derivation_set("restriction")
|
|
171
|
+
# #=> "restriction"
|
|
172
|
+
def format_derivation_set(value)
|
|
173
|
+
return "" if value.nil? || value.strip.empty?
|
|
174
|
+
|
|
175
|
+
if value.strip == "#all"
|
|
176
|
+
"restriction, extension"
|
|
177
|
+
else
|
|
178
|
+
value
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Formats a simple type derivation set, expanding #all
|
|
183
|
+
#
|
|
184
|
+
# @param value [String, nil] The derivation value
|
|
185
|
+
# @return [String] Formatted simple derivation set
|
|
186
|
+
#
|
|
187
|
+
# @example
|
|
188
|
+
# format_simple_derivation_set("#all")
|
|
189
|
+
# #=> "restriction, list, union"
|
|
190
|
+
#
|
|
191
|
+
# format_simple_derivation_set("restriction")
|
|
192
|
+
# #=> "restriction"
|
|
193
|
+
def format_simple_derivation_set(value)
|
|
194
|
+
return "" if value.nil? || value.strip.empty?
|
|
195
|
+
|
|
196
|
+
if value.strip == "#all"
|
|
197
|
+
"restriction, list, union"
|
|
198
|
+
else
|
|
199
|
+
value
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cgi"
|
|
4
|
+
|
|
5
|
+
module Xseed
|
|
6
|
+
module Documentation
|
|
7
|
+
module Utils
|
|
8
|
+
# Namespaces utility module for handling XSD namespace prefixes
|
|
9
|
+
#
|
|
10
|
+
# This module provides methods for formatting namespace prefixes,
|
|
11
|
+
# generating links to namespace declarations, and collecting namespace
|
|
12
|
+
# information from XML Schemas.
|
|
13
|
+
#
|
|
14
|
+
# Based on XS3P namespaces.xsl utilities.
|
|
15
|
+
module Namespaces
|
|
16
|
+
# Formats a namespace prefix with a trailing colon
|
|
17
|
+
#
|
|
18
|
+
# @param prefix [String, nil] The namespace prefix
|
|
19
|
+
# @return [String] The formatted prefix with colon, or empty string
|
|
20
|
+
#
|
|
21
|
+
# @example
|
|
22
|
+
# format_namespace_prefix("xs") #=> "xs:"
|
|
23
|
+
# format_namespace_prefix("") #=> ""
|
|
24
|
+
# format_namespace_prefix(nil) #=> ""
|
|
25
|
+
def format_namespace_prefix(prefix)
|
|
26
|
+
return "" if prefix.nil? || prefix.strip.empty?
|
|
27
|
+
|
|
28
|
+
"#{prefix}:"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Generates an HTML link to a namespace declaration
|
|
32
|
+
#
|
|
33
|
+
# @param prefix [String, nil] The namespace prefix
|
|
34
|
+
# @param schema [Object] The schema object with namespaces hash
|
|
35
|
+
# @param schema_loc [String] Optional schema location for external refs
|
|
36
|
+
# @return [String] HTML link element or plain text
|
|
37
|
+
#
|
|
38
|
+
# @example
|
|
39
|
+
# namespace_link("xs", schema)
|
|
40
|
+
# #=> "<a href=\"#ns-xs\" title=\"...\">xs</a>"
|
|
41
|
+
def namespace_link(prefix, schema, schema_loc: nil)
|
|
42
|
+
return "" if prefix.nil? || prefix.strip.empty?
|
|
43
|
+
|
|
44
|
+
namespaces = schema&.namespaces || {}
|
|
45
|
+
|
|
46
|
+
if namespaces.key?(prefix)
|
|
47
|
+
# Known namespace - create link to declaration
|
|
48
|
+
href = if schema_loc && schema_loc != "this"
|
|
49
|
+
"#{schema_loc}##{namespace_anchor_id(prefix)}"
|
|
50
|
+
else
|
|
51
|
+
"##{namespace_anchor_id(prefix)}"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
namespace_uri = namespaces[prefix]
|
|
55
|
+
title = "Find out namespace of '#{prefix}' prefix: #{namespace_uri}"
|
|
56
|
+
|
|
57
|
+
"<a href=\"#{CGI.escape_html(href)}\" " \
|
|
58
|
+
"title=\"#{CGI.escape_html(title)}\">#{CGI.escape_html(prefix)}</a>"
|
|
59
|
+
else
|
|
60
|
+
# Unknown namespace - create warning link
|
|
61
|
+
warning = "Unknown namespace prefix: #{prefix}"
|
|
62
|
+
"<a href=\"javascript:void(0)\" " \
|
|
63
|
+
"onclick=\"alert('#{CGI.escape_html(warning)}')\" " \
|
|
64
|
+
"title=\"#{CGI.escape_html(warning)}\">#{CGI.escape_html(prefix)}</a>"
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Formats a namespace prefix with optional link and colon
|
|
69
|
+
#
|
|
70
|
+
# @param prefix [String, nil] The namespace prefix
|
|
71
|
+
# @param schema [Object] The schema object with namespaces hash
|
|
72
|
+
# @param link [Boolean] Whether to generate a link (default: true)
|
|
73
|
+
# @param schema_loc [String] Optional schema location for external refs
|
|
74
|
+
# @return [String] Formatted prefix with colon, optionally linked
|
|
75
|
+
#
|
|
76
|
+
# @example
|
|
77
|
+
# format_namespace_with_prefix("xs", schema)
|
|
78
|
+
# #=> "<a href=\"#ns-xs\" title=\"...\">xs</a>:"
|
|
79
|
+
#
|
|
80
|
+
# format_namespace_with_prefix("xs", schema, link: false)
|
|
81
|
+
# #=> "xs:"
|
|
82
|
+
def format_namespace_with_prefix(prefix, schema, link: true,
|
|
83
|
+
schema_loc: nil)
|
|
84
|
+
return "" if prefix.nil? || prefix.strip.empty?
|
|
85
|
+
|
|
86
|
+
if link
|
|
87
|
+
"#{namespace_link(prefix, schema, schema_loc: schema_loc)}:"
|
|
88
|
+
else
|
|
89
|
+
format_namespace_prefix(prefix)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Collects all namespaces from a schema
|
|
94
|
+
#
|
|
95
|
+
# @param schema [Object, nil] The schema object with namespaces hash
|
|
96
|
+
# @return [Hash] Hash of prefix => namespace URI mappings
|
|
97
|
+
#
|
|
98
|
+
# @example
|
|
99
|
+
# collect_namespaces(schema)
|
|
100
|
+
# #=> { "xs" => "http://www.w3.org/2001/XMLSchema",
|
|
101
|
+
# # "tns" => "http://example.com/target" }
|
|
102
|
+
def collect_namespaces(schema)
|
|
103
|
+
return {} if schema.nil?
|
|
104
|
+
|
|
105
|
+
namespaces = schema.namespaces
|
|
106
|
+
return {} if namespaces.nil?
|
|
107
|
+
|
|
108
|
+
namespaces.dup
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Generates an anchor ID for a namespace prefix
|
|
112
|
+
#
|
|
113
|
+
# @param prefix [String, nil] The namespace prefix
|
|
114
|
+
# @return [String] The anchor ID for the namespace
|
|
115
|
+
#
|
|
116
|
+
# @example
|
|
117
|
+
# namespace_anchor_id("xs") #=> "ns-xs"
|
|
118
|
+
# namespace_anchor_id("tns") #=> "ns-tns"
|
|
119
|
+
def namespace_anchor_id(prefix)
|
|
120
|
+
"ns-#{prefix}"
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Formats a namespace declaration (xmlns)
|
|
124
|
+
#
|
|
125
|
+
# @param prefix [String, nil] The namespace prefix (empty for default)
|
|
126
|
+
# @param namespace [String, nil] The namespace URI
|
|
127
|
+
# @return [String] Formatted xmlns declaration
|
|
128
|
+
#
|
|
129
|
+
# @example
|
|
130
|
+
# format_namespace_declaration("xs", "http://www.w3.org/2001/XMLSchema")
|
|
131
|
+
# #=> "xmlns:xs=\"http://www.w3.org/2001/XMLSchema\""
|
|
132
|
+
#
|
|
133
|
+
# format_namespace_declaration("", "http://example.com/default")
|
|
134
|
+
# #=> "xmlns=\"http://example.com/default\""
|
|
135
|
+
def format_namespace_declaration(prefix, namespace)
|
|
136
|
+
prefix_part = if prefix.nil? || prefix.strip.empty?
|
|
137
|
+
"xmlns"
|
|
138
|
+
else
|
|
139
|
+
"xmlns:#{prefix}"
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
namespace_value = namespace.nil? ? "" : CGI.escape_html(namespace)
|
|
143
|
+
|
|
144
|
+
"#{prefix_part}=\"#{namespace_value}\""
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|