xsdvi 1.0.0 → 1.0.2
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 +4 -4
- data/README.adoc +364 -6
- data/lib/xsdvi/cli.rb +162 -10
- data/lib/xsdvi/comparison/dual_generator.rb +190 -0
- data/lib/xsdvi/comparison/html_generator.rb +181 -0
- data/lib/xsdvi/comparison/java_manager.rb +124 -0
- data/lib/xsdvi/comparison/metadata_extractor.rb +75 -0
- data/lib/xsdvi/svg/generator.rb +1 -0
- data/lib/xsdvi/svg/symbol.rb +39 -30
- data/lib/xsdvi/svg/symbols/attribute.rb +1 -1
- data/lib/xsdvi/svg/symbols/element.rb +1 -1
- data/lib/xsdvi/version.rb +1 -1
- data/lib/xsdvi/xsd_handler.rb +486 -24
- data/lib/xsdvi.rb +6 -0
- data/resources/comparison/template.html +234 -0
- data/resources/svg/defined_symbols.svg +9 -9
- data/resources/svg/menu_buttons.svg +6 -6
- data/resources/svg/script.js +264 -264
- data/resources/svg/style.css +28 -28
- data/resources/svg/style.html +2 -2
- metadata +8 -6
data/lib/xsdvi/version.rb
CHANGED
data/lib/xsdvi/xsd_handler.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "nokogiri"
|
|
4
|
+
require "set"
|
|
4
5
|
|
|
5
6
|
module Xsdvi
|
|
6
7
|
# Handles XSD parsing and tree building
|
|
@@ -16,6 +17,17 @@ module Xsdvi
|
|
|
16
17
|
@root_node_name = nil
|
|
17
18
|
@one_node_only = false
|
|
18
19
|
@schema_namespace = nil
|
|
20
|
+
|
|
21
|
+
# Type registries for resolution (Phase 1)
|
|
22
|
+
@complex_types = {} # QName => complexType node
|
|
23
|
+
@simple_types = {} # QName => simpleType node
|
|
24
|
+
@model_groups = {} # QName => group node
|
|
25
|
+
@attribute_groups = {} # QName => attributeGroup node
|
|
26
|
+
@elements = {} # QName => element node (for refs)
|
|
27
|
+
|
|
28
|
+
# Type extension/restriction chain tracking (prevents stack overflow)
|
|
29
|
+
@type_depth = 0
|
|
30
|
+
@type_ancestors = Set.new
|
|
19
31
|
end
|
|
20
32
|
|
|
21
33
|
def process_file(file_path)
|
|
@@ -30,12 +42,19 @@ module Xsdvi
|
|
|
30
42
|
schema_node = doc.at_xpath("/xs:schema", "xs" => XSD_NAMESPACE)
|
|
31
43
|
@schema_namespace = schema_node["targetNamespace"] if schema_node
|
|
32
44
|
|
|
45
|
+
# Phase 1: Collect all type definitions, groups, and elements
|
|
46
|
+
collect_type_definitions(doc)
|
|
47
|
+
collect_group_definitions(doc)
|
|
48
|
+
collect_attribute_group_definitions(doc)
|
|
49
|
+
collect_element_definitions(doc)
|
|
50
|
+
|
|
33
51
|
# Create schema symbol when no root node specified (matches Java line 65-68)
|
|
34
52
|
if root_node_name.nil?
|
|
35
53
|
symbol = SVG::Symbols::Schema.new
|
|
36
54
|
builder.set_root(symbol)
|
|
37
55
|
end
|
|
38
56
|
|
|
57
|
+
# Phase 2: Process elements with type resolution
|
|
39
58
|
process_element_declarations(doc)
|
|
40
59
|
|
|
41
60
|
# Level up after processing all elements (matches Java line 71-73)
|
|
@@ -61,6 +80,42 @@ module Xsdvi
|
|
|
61
80
|
|
|
62
81
|
private
|
|
63
82
|
|
|
83
|
+
def collect_type_definitions(doc)
|
|
84
|
+
# Collect complexType definitions
|
|
85
|
+
doc.xpath("//xs:complexType[@name]", "xs" => XSD_NAMESPACE).each do |node|
|
|
86
|
+
name = node["name"]
|
|
87
|
+
@complex_types[name] = node
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Collect simpleType definitions
|
|
91
|
+
doc.xpath("//xs:simpleType[@name]", "xs" => XSD_NAMESPACE).each do |node|
|
|
92
|
+
name = node["name"]
|
|
93
|
+
@simple_types[name] = node
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def collect_group_definitions(doc)
|
|
98
|
+
doc.xpath("//xs:group[@name]", "xs" => XSD_NAMESPACE).each do |node|
|
|
99
|
+
name = node["name"]
|
|
100
|
+
@model_groups[name] = node
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def collect_attribute_group_definitions(doc)
|
|
105
|
+
doc.xpath("//xs:attributeGroup[@name]",
|
|
106
|
+
"xs" => XSD_NAMESPACE).each do |node|
|
|
107
|
+
name = node["name"]
|
|
108
|
+
@attribute_groups[name] = node
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def collect_element_definitions(doc)
|
|
113
|
+
doc.xpath("//xs:element[@name]", "xs" => XSD_NAMESPACE).each do |node|
|
|
114
|
+
name = node["name"]
|
|
115
|
+
@elements[name] = node
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
64
119
|
def process_element_declarations(doc)
|
|
65
120
|
elements = doc.xpath("//xs:schema/xs:element", "xs" => XSD_NAMESPACE)
|
|
66
121
|
|
|
@@ -75,9 +130,14 @@ module Xsdvi
|
|
|
75
130
|
end
|
|
76
131
|
|
|
77
132
|
def process_element_declaration(elem_node, cardinality, is_root)
|
|
133
|
+
# Phase 5: Handle element references first
|
|
134
|
+
if (ref = elem_node["ref"])
|
|
135
|
+
process_element_ref(ref, cardinality)
|
|
136
|
+
return
|
|
137
|
+
end
|
|
138
|
+
|
|
78
139
|
name = elem_node["name"]
|
|
79
140
|
elem_namespace = elem_node["namespace"]
|
|
80
|
-
elem_node["ref"]
|
|
81
141
|
type_attr = elem_node["type"]
|
|
82
142
|
nillable = elem_node["nillable"] == "true"
|
|
83
143
|
abstract = elem_node["abstract"] == "true"
|
|
@@ -118,20 +178,37 @@ module Xsdvi
|
|
|
118
178
|
|
|
119
179
|
# Skip processing children if stack size > 1 and oneNodeOnly (matches Java line 253-260)
|
|
120
180
|
unless @stack.size > 1 && one_node_only
|
|
121
|
-
# Check
|
|
181
|
+
# Check for inline complexType first
|
|
122
182
|
complex_type = elem_node.at_xpath("xs:complexType",
|
|
123
183
|
"xs" => XSD_NAMESPACE)
|
|
124
184
|
if complex_type
|
|
185
|
+
# Process inline anonymous complexType
|
|
125
186
|
process_complex_type_node(complex_type)
|
|
126
|
-
elsif
|
|
187
|
+
elsif type_attr
|
|
188
|
+
# Try to resolve type reference
|
|
189
|
+
resolved_type = resolve_type(type_attr)
|
|
190
|
+
if resolved_type
|
|
191
|
+
# Process the resolved named type
|
|
192
|
+
if resolved_type.name == "complexType"
|
|
193
|
+
process_complex_type_node(resolved_type)
|
|
194
|
+
elsif resolved_type.name == "simpleType"
|
|
195
|
+
# simpleTypes don't have children to process
|
|
196
|
+
# The type string is already set above
|
|
197
|
+
end
|
|
198
|
+
elsif type_attr == "anyType"
|
|
199
|
+
# Explicit anyType reference
|
|
200
|
+
process_any_type_default
|
|
201
|
+
end
|
|
202
|
+
# If type is built-in (string, int, etc), we just show the type string
|
|
203
|
+
else
|
|
127
204
|
# No type means anyType - expand it
|
|
128
205
|
process_any_type_default
|
|
129
|
-
elsif type_attr == "anyType"
|
|
130
|
-
process_any_type_default
|
|
131
206
|
end
|
|
132
207
|
end
|
|
133
208
|
|
|
134
209
|
@stack.pop
|
|
210
|
+
# Process identity constraints (matches Java line 262)
|
|
211
|
+
process_identity_constraints(elem_node)
|
|
135
212
|
builder.level_up
|
|
136
213
|
end
|
|
137
214
|
|
|
@@ -177,25 +254,108 @@ module Xsdvi
|
|
|
177
254
|
end
|
|
178
255
|
|
|
179
256
|
def process_complex_type_node(complex_type_node)
|
|
257
|
+
# Phase 6: Check for complexContent or simpleContent first
|
|
258
|
+
complex_content = complex_type_node.at_xpath("xs:complexContent",
|
|
259
|
+
"xs" => XSD_NAMESPACE)
|
|
260
|
+
simple_content = complex_type_node.at_xpath("xs:simpleContent",
|
|
261
|
+
"xs" => XSD_NAMESPACE)
|
|
262
|
+
|
|
263
|
+
if complex_content
|
|
264
|
+
process_complex_content(complex_content)
|
|
265
|
+
elsif simple_content
|
|
266
|
+
process_simple_content(simple_content)
|
|
267
|
+
else
|
|
268
|
+
# Direct content model (no extension/restriction wrapper)
|
|
269
|
+
process_complex_type_content(complex_type_node)
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# Phase 6: Process complexContent
|
|
274
|
+
def process_complex_content(complex_content_node)
|
|
275
|
+
extension = complex_content_node.at_xpath("xs:extension",
|
|
276
|
+
"xs" => XSD_NAMESPACE)
|
|
277
|
+
restriction = complex_content_node.at_xpath("xs:restriction",
|
|
278
|
+
"xs" => XSD_NAMESPACE)
|
|
279
|
+
|
|
280
|
+
if extension
|
|
281
|
+
process_extension(extension)
|
|
282
|
+
elsif restriction
|
|
283
|
+
process_restriction(restriction)
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
# Phase 6: Process simpleContent
|
|
288
|
+
def process_simple_content(simple_content_node)
|
|
289
|
+
extension = simple_content_node.at_xpath("xs:extension",
|
|
290
|
+
"xs" => XSD_NAMESPACE)
|
|
291
|
+
restriction = simple_content_node.at_xpath("xs:restriction",
|
|
292
|
+
"xs" => XSD_NAMESPACE)
|
|
293
|
+
|
|
294
|
+
if extension
|
|
295
|
+
process_extension(extension)
|
|
296
|
+
elsif restriction
|
|
297
|
+
process_restriction(restriction)
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# Phase 6: Process extension
|
|
302
|
+
def process_extension(extension_node)
|
|
303
|
+
# Process base type first (inherit content model)
|
|
304
|
+
if (base = extension_node["base"])
|
|
305
|
+
base_type = resolve_type(base)
|
|
306
|
+
if base_type && base_type.name == "complexType" && type_chain_enter(base)
|
|
307
|
+
process_complex_type_node(base_type)
|
|
308
|
+
type_chain_exit(base)
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
# Then process extension's own content
|
|
313
|
+
process_complex_type_content(extension_node)
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
# Phase 6: Process restriction
|
|
317
|
+
def process_restriction(restriction_node)
|
|
318
|
+
# Process base type first (inherit and potentially restrict content model)
|
|
319
|
+
if (base = restriction_node["base"])
|
|
320
|
+
base_type = resolve_type(base)
|
|
321
|
+
if base_type && base_type.name == "complexType" && type_chain_enter(base)
|
|
322
|
+
process_complex_type_node(base_type)
|
|
323
|
+
type_chain_exit(base)
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
# Then process restriction's own content
|
|
328
|
+
process_complex_type_content(restriction_node)
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
# Phase 6: Process complex type content (particles and attributes)
|
|
332
|
+
def process_complex_type_content(node)
|
|
180
333
|
# Process particles (sequence/choice/all)
|
|
181
|
-
sequence =
|
|
182
|
-
"xs" => XSD_NAMESPACE)
|
|
334
|
+
sequence = node.at_xpath("xs:sequence", "xs" => XSD_NAMESPACE)
|
|
183
335
|
process_sequence(sequence, nil) if sequence
|
|
184
336
|
|
|
185
|
-
choice =
|
|
337
|
+
choice = node.at_xpath("xs:choice", "xs" => XSD_NAMESPACE)
|
|
186
338
|
process_choice(choice, nil) if choice
|
|
187
339
|
|
|
188
|
-
all_node =
|
|
340
|
+
all_node = node.at_xpath("xs:all", "xs" => XSD_NAMESPACE)
|
|
189
341
|
process_all(all_node, nil) if all_node
|
|
190
342
|
|
|
191
|
-
# Process attributes
|
|
192
|
-
attributes =
|
|
193
|
-
|
|
194
|
-
|
|
343
|
+
# Process attributes (both direct and references) - SORT ALPHABETICALLY
|
|
344
|
+
attributes = node.xpath("xs:attribute", "xs" => XSD_NAMESPACE)
|
|
345
|
+
sorted_attributes = attributes.sort_by do |attr|
|
|
346
|
+
# Sort by ref or name
|
|
347
|
+
attr["ref"] || attr["name"] || ""
|
|
348
|
+
end
|
|
349
|
+
sorted_attributes.each { |attr| process_attribute(attr) }
|
|
350
|
+
|
|
351
|
+
# Process attribute group references
|
|
352
|
+
attr_groups = node.xpath("xs:attributeGroup[@ref]", "xs" => XSD_NAMESPACE)
|
|
353
|
+
attr_groups.each do |attr_group|
|
|
354
|
+
process_attribute_group_ref(attr_group["ref"])
|
|
355
|
+
end
|
|
195
356
|
|
|
196
357
|
# Process attribute wildcard
|
|
197
|
-
any_attr =
|
|
198
|
-
"xs" => XSD_NAMESPACE)
|
|
358
|
+
any_attr = node.at_xpath("xs:anyAttribute", "xs" => XSD_NAMESPACE)
|
|
199
359
|
process_any_attribute(any_attr) if any_attr
|
|
200
360
|
end
|
|
201
361
|
|
|
@@ -211,6 +371,12 @@ module Xsdvi
|
|
|
211
371
|
process_element_declaration(elem, get_cardinality(elem), false)
|
|
212
372
|
end
|
|
213
373
|
|
|
374
|
+
# Process group references (Phase 3)
|
|
375
|
+
sequence_node.xpath("xs:group[@ref]",
|
|
376
|
+
"xs" => XSD_NAMESPACE).each do |group|
|
|
377
|
+
process_group_ref(group["ref"])
|
|
378
|
+
end
|
|
379
|
+
|
|
214
380
|
# Process any
|
|
215
381
|
sequence_node.xpath("xs:any", "xs" => XSD_NAMESPACE).each do |any|
|
|
216
382
|
process_any(any)
|
|
@@ -231,6 +397,11 @@ module Xsdvi
|
|
|
231
397
|
process_element_declaration(elem, get_cardinality(elem), false)
|
|
232
398
|
end
|
|
233
399
|
|
|
400
|
+
# Process group references (Phase 3)
|
|
401
|
+
choice_node.xpath("xs:group[@ref]", "xs" => XSD_NAMESPACE).each do |group|
|
|
402
|
+
process_group_ref(group["ref"])
|
|
403
|
+
end
|
|
404
|
+
|
|
234
405
|
builder.level_up
|
|
235
406
|
end
|
|
236
407
|
|
|
@@ -246,6 +417,11 @@ module Xsdvi
|
|
|
246
417
|
process_element_declaration(elem, get_cardinality(elem), false)
|
|
247
418
|
end
|
|
248
419
|
|
|
420
|
+
# Process group references (Phase 3)
|
|
421
|
+
all_node.xpath("xs:group[@ref]", "xs" => XSD_NAMESPACE).each do |group|
|
|
422
|
+
process_group_ref(group["ref"])
|
|
423
|
+
end
|
|
424
|
+
|
|
249
425
|
builder.level_up
|
|
250
426
|
end
|
|
251
427
|
|
|
@@ -258,7 +434,7 @@ module Xsdvi
|
|
|
258
434
|
when "skip" then SVG::Symbol::PC_SKIP
|
|
259
435
|
when "lax" then SVG::Symbol::PC_LAX
|
|
260
436
|
when "strict" then SVG::Symbol::PC_STRICT
|
|
261
|
-
else SVG::Symbol::PC_LAX
|
|
437
|
+
else SVG::Symbol::PC_LAX # Default is lax for anyType
|
|
262
438
|
end
|
|
263
439
|
symbol.cardinality = get_cardinality(any_node)
|
|
264
440
|
symbol.description = extract_documentation(any_node)
|
|
@@ -267,18 +443,123 @@ module Xsdvi
|
|
|
267
443
|
end
|
|
268
444
|
|
|
269
445
|
def process_attribute(attr_node)
|
|
446
|
+
# Handle attribute references (like <xsd:attribute ref="xml:lang"/>)
|
|
447
|
+
if (ref = attr_node["ref"])
|
|
448
|
+
process_attribute_ref(ref, attr_node)
|
|
449
|
+
return
|
|
450
|
+
end
|
|
451
|
+
|
|
270
452
|
symbol = SVG::Symbols::Attribute.new
|
|
271
453
|
symbol.name = attr_node["name"]
|
|
272
454
|
namespace = attr_node["namespace"]
|
|
273
455
|
symbol.namespace = namespace if namespace && namespace != schema_namespace
|
|
274
|
-
|
|
456
|
+
|
|
457
|
+
# Determine type - either from type attribute or inline simpleType restriction
|
|
458
|
+
type_value = nil
|
|
459
|
+
type_prefix = "type"
|
|
460
|
+
|
|
461
|
+
if attr_node["type"]
|
|
462
|
+
type_value = attr_node["type"]
|
|
463
|
+
if type_value.start_with?("xsd:")
|
|
464
|
+
type_value = type_value.sub(/^xsd:/,
|
|
465
|
+
"")
|
|
466
|
+
end
|
|
467
|
+
else
|
|
468
|
+
# Check for inline simpleType definition
|
|
469
|
+
simple_type = attr_node.at_xpath("xs:simpleType", "xs" => XSD_NAMESPACE)
|
|
470
|
+
if simple_type
|
|
471
|
+
# Check for union type first
|
|
472
|
+
union = simple_type.at_xpath("xs:union", "xs" => XSD_NAMESPACE)
|
|
473
|
+
if union
|
|
474
|
+
# Union types have base type of anySimpleType
|
|
475
|
+
type_value = "anySimpleType"
|
|
476
|
+
type_prefix = "base"
|
|
477
|
+
else
|
|
478
|
+
# Check for restriction
|
|
479
|
+
restriction = simple_type.at_xpath("xs:restriction",
|
|
480
|
+
"xs" => XSD_NAMESPACE)
|
|
481
|
+
if restriction && restriction["base"]
|
|
482
|
+
type_value = restriction["base"]
|
|
483
|
+
if type_value.start_with?("xsd:")
|
|
484
|
+
type_value = type_value.sub(/^xsd:/,
|
|
485
|
+
"")
|
|
486
|
+
end
|
|
487
|
+
type_prefix = "base"
|
|
488
|
+
end
|
|
489
|
+
end
|
|
490
|
+
end
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
symbol.type = "#{type_prefix}: #{type_value}" if type_value
|
|
494
|
+
|
|
275
495
|
symbol.required = attr_node["use"] == "required"
|
|
496
|
+
|
|
497
|
+
# Capture default or fixed values
|
|
498
|
+
if attr_node["default"]
|
|
499
|
+
default_value = attr_node["default"]
|
|
500
|
+
|
|
501
|
+
# Format default value for double type: 0 becomes 0.0E1
|
|
502
|
+
if type_value == "double" && (default_value == "0" || default_value.to_f == 0.0)
|
|
503
|
+
default_value = "0.0E1"
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
symbol.constraint = "default: #{default_value}"
|
|
507
|
+
elsif attr_node["fixed"]
|
|
508
|
+
symbol.constraint = "fixed: #{attr_node['fixed']}"
|
|
509
|
+
end
|
|
510
|
+
|
|
276
511
|
symbol.description = extract_documentation(attr_node)
|
|
277
512
|
|
|
278
513
|
builder.append_child(symbol)
|
|
279
514
|
builder.level_up
|
|
280
515
|
end
|
|
281
516
|
|
|
517
|
+
# Process attribute reference
|
|
518
|
+
def process_attribute_ref(ref, ref_node)
|
|
519
|
+
# Extract namespace prefix and local name
|
|
520
|
+
if ref.include?(":")
|
|
521
|
+
prefix, local_name = ref.split(":", 2)
|
|
522
|
+
else
|
|
523
|
+
prefix = nil
|
|
524
|
+
local_name = ref
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
symbol = SVG::Symbols::Attribute.new
|
|
528
|
+
|
|
529
|
+
# Handle xml: namespace attributes specially
|
|
530
|
+
if prefix == "xml"
|
|
531
|
+
# W3C XML namespace
|
|
532
|
+
symbol.namespace = "http://www.w3.org/XML/1998/namespace"
|
|
533
|
+
symbol.name = local_name # Just "lang" or "id", not "xml:lang" or "xml:id"
|
|
534
|
+
|
|
535
|
+
# Set type based on specific xml: attribute
|
|
536
|
+
symbol.type = case local_name
|
|
537
|
+
when "id"
|
|
538
|
+
"type: ID"
|
|
539
|
+
when "lang"
|
|
540
|
+
"base: anySimpleType"
|
|
541
|
+
when "space"
|
|
542
|
+
"type: NCName"
|
|
543
|
+
when "base"
|
|
544
|
+
"type: anyURI"
|
|
545
|
+
else
|
|
546
|
+
"base: anySimpleType"
|
|
547
|
+
end
|
|
548
|
+
else
|
|
549
|
+
# Regular attribute reference - would need to look up in schema
|
|
550
|
+
symbol.name = local_name
|
|
551
|
+
end
|
|
552
|
+
|
|
553
|
+
# Get use constraint from the reference location
|
|
554
|
+
symbol.required = ref_node["use"] == "required"
|
|
555
|
+
|
|
556
|
+
# Extract documentation from the reference node itself
|
|
557
|
+
symbol.description = extract_documentation(ref_node)
|
|
558
|
+
|
|
559
|
+
builder.append_child(symbol)
|
|
560
|
+
builder.level_up
|
|
561
|
+
end
|
|
562
|
+
|
|
282
563
|
def process_any_attribute(any_attr_node)
|
|
283
564
|
symbol = SVG::Symbols::AnyAttribute.new
|
|
284
565
|
namespace = any_attr_node["namespace"]
|
|
@@ -288,36 +569,217 @@ module Xsdvi
|
|
|
288
569
|
when "skip" then SVG::Symbol::PC_SKIP
|
|
289
570
|
when "lax" then SVG::Symbol::PC_LAX
|
|
290
571
|
when "strict" then SVG::Symbol::PC_STRICT
|
|
291
|
-
else SVG::Symbol::PC_LAX
|
|
572
|
+
else SVG::Symbol::PC_LAX # Default is lax for anyType
|
|
292
573
|
end
|
|
293
574
|
symbol.description = extract_documentation(any_attr_node)
|
|
294
575
|
builder.append_child(symbol)
|
|
295
576
|
builder.level_up
|
|
296
577
|
end
|
|
297
578
|
|
|
579
|
+
# Phase 3: Group reference resolution
|
|
580
|
+
def process_group_ref(ref)
|
|
581
|
+
return unless ref
|
|
582
|
+
|
|
583
|
+
# Strip namespace prefix if present
|
|
584
|
+
group_name = ref.include?(":") ? ref.split(":").last : ref
|
|
585
|
+
group_node = @model_groups[group_name]
|
|
586
|
+
|
|
587
|
+
return unless group_node
|
|
588
|
+
|
|
589
|
+
# Process the referenced group's content (sequence, choice, or all)
|
|
590
|
+
model_group = group_node.at_xpath("xs:sequence | xs:choice | xs:all",
|
|
591
|
+
"xs" => XSD_NAMESPACE)
|
|
592
|
+
return unless model_group
|
|
593
|
+
|
|
594
|
+
case model_group.name
|
|
595
|
+
when "sequence"
|
|
596
|
+
process_sequence(model_group, nil)
|
|
597
|
+
when "choice"
|
|
598
|
+
process_choice(model_group, nil)
|
|
599
|
+
when "all"
|
|
600
|
+
process_all(model_group, nil)
|
|
601
|
+
end
|
|
602
|
+
end
|
|
603
|
+
|
|
604
|
+
# Phase 4: Attribute group reference resolution
|
|
605
|
+
def process_attribute_group_ref(ref)
|
|
606
|
+
return unless ref
|
|
607
|
+
|
|
608
|
+
# Strip namespace prefix if present
|
|
609
|
+
group_name = ref.include?(":") ? ref.split(":").last : ref
|
|
610
|
+
group_node = @attribute_groups[group_name]
|
|
611
|
+
|
|
612
|
+
return unless group_node
|
|
613
|
+
|
|
614
|
+
# Process all attributes in the group
|
|
615
|
+
group_node.xpath("xs:attribute", "xs" => XSD_NAMESPACE).each do |attr|
|
|
616
|
+
process_attribute(attr)
|
|
617
|
+
end
|
|
618
|
+
|
|
619
|
+
# Process nested attribute group references (can be recursive)
|
|
620
|
+
group_node.xpath("xs:attributeGroup[@ref]",
|
|
621
|
+
"xs" => XSD_NAMESPACE).each do |nested|
|
|
622
|
+
process_attribute_group_ref(nested["ref"])
|
|
623
|
+
end
|
|
624
|
+
end
|
|
625
|
+
|
|
626
|
+
# Phase 5: Element reference resolution
|
|
627
|
+
def process_element_ref(ref, cardinality)
|
|
628
|
+
return unless ref
|
|
629
|
+
|
|
630
|
+
# Strip namespace prefix if present
|
|
631
|
+
elem_name = ref.include?(":") ? ref.split(":").last : ref
|
|
632
|
+
elem_node = @elements[elem_name]
|
|
633
|
+
|
|
634
|
+
return unless elem_node
|
|
635
|
+
|
|
636
|
+
# Check for circular reference to prevent infinite recursion
|
|
637
|
+
if @stack.any? { |e| e["name"] == elem_name }
|
|
638
|
+
# Create a loop symbol instead (Loop doesn't have a name attribute)
|
|
639
|
+
symbol = SVG::Symbols::Loop.new
|
|
640
|
+
builder.append_child(symbol)
|
|
641
|
+
builder.level_up
|
|
642
|
+
return
|
|
643
|
+
end
|
|
644
|
+
|
|
645
|
+
# Process the referenced element
|
|
646
|
+
process_element_declaration(elem_node, cardinality, false)
|
|
647
|
+
end
|
|
648
|
+
|
|
298
649
|
def process_loop?(elem_node)
|
|
299
650
|
@stack.any?(elem_node)
|
|
300
651
|
end
|
|
301
652
|
|
|
302
653
|
def get_cardinality(node)
|
|
303
654
|
min_occurs = node["minOccurs"]&.to_i || 1
|
|
304
|
-
|
|
655
|
+
max_occurs_str = node["maxOccurs"] || "1" # XSD default is "1" when not specified
|
|
305
656
|
|
|
306
|
-
return nil if min_occurs == 1 &&
|
|
657
|
+
return nil if min_occurs == 1 && max_occurs_str == "1"
|
|
307
658
|
|
|
308
|
-
if
|
|
659
|
+
if max_occurs_str == "unbounded"
|
|
309
660
|
"#{min_occurs}..∞"
|
|
310
|
-
|
|
661
|
+
else
|
|
662
|
+
max_occurs = max_occurs_str.to_i
|
|
311
663
|
"#{min_occurs}..#{max_occurs}"
|
|
312
664
|
end
|
|
313
665
|
end
|
|
314
666
|
|
|
315
667
|
def extract_documentation(node)
|
|
316
668
|
docs = node.xpath(
|
|
317
|
-
"
|
|
669
|
+
"./xs:annotation/xs:documentation", # Changed from .// to ./ (direct children only)
|
|
318
670
|
"xs" => XSD_NAMESPACE,
|
|
319
671
|
)
|
|
320
|
-
|
|
672
|
+
# Use inner_html to preserve XML entities like < and >
|
|
673
|
+
# Java's XML parser preserves these, affecting wrap length calculations
|
|
674
|
+
docs.map(&:inner_html).map { |text| text.gsub(/\n[ \t]+/, "\n") }
|
|
675
|
+
end
|
|
676
|
+
|
|
677
|
+
# Phase 2: Type resolution methods
|
|
678
|
+
def resolve_type(type_attr)
|
|
679
|
+
return nil unless type_attr
|
|
680
|
+
|
|
681
|
+
# Strip namespace prefix if present (e.g., "xs:string" -> "string")
|
|
682
|
+
type_name = type_attr.include?(":") ? type_attr.split(":").last : type_attr
|
|
683
|
+
|
|
684
|
+
# Don't try to resolve built-in XSD types
|
|
685
|
+
return nil if is_builtin_type?(type_name)
|
|
686
|
+
|
|
687
|
+
# Look up in registries
|
|
688
|
+
@complex_types[type_name] || @simple_types[type_name]
|
|
689
|
+
end
|
|
690
|
+
|
|
691
|
+
def is_builtin_type?(type_name)
|
|
692
|
+
# W3C XML Schema built-in types
|
|
693
|
+
%w[
|
|
694
|
+
string boolean decimal float double duration dateTime
|
|
695
|
+
time date gYearMonth gYear gMonthDay gDay gMonth
|
|
696
|
+
hexBinary base64Binary anyURI QName NOTATION
|
|
697
|
+
normalizedString token language NMTOKEN NMTOKENS
|
|
698
|
+
Name NCName ID IDREF IDREFS ENTITY ENTITIES
|
|
699
|
+
integer nonPositiveInteger negativeInteger long int
|
|
700
|
+
short byte nonNegativeInteger unsignedLong unsignedInt
|
|
701
|
+
unsignedShort unsignedByte positiveInteger
|
|
702
|
+
anyType anySimpleType anyAtomicType
|
|
703
|
+
].include?(type_name)
|
|
704
|
+
end
|
|
705
|
+
|
|
706
|
+
def process_identity_constraints(elem_node)
|
|
707
|
+
# Process xs:key
|
|
708
|
+
elem_node.xpath("xs:key", "xs" => XSD_NAMESPACE).each do |constraint|
|
|
709
|
+
process_identity_constraint(constraint, :key)
|
|
710
|
+
end
|
|
711
|
+
|
|
712
|
+
# Process xs:keyref
|
|
713
|
+
elem_node.xpath("xs:keyref", "xs" => XSD_NAMESPACE).each do |constraint|
|
|
714
|
+
process_identity_constraint(constraint, :keyref)
|
|
715
|
+
end
|
|
716
|
+
|
|
717
|
+
# Process xs:unique
|
|
718
|
+
elem_node.xpath("xs:unique", "xs" => XSD_NAMESPACE).each do |constraint|
|
|
719
|
+
process_identity_constraint(constraint, :unique)
|
|
720
|
+
end
|
|
721
|
+
end
|
|
722
|
+
|
|
723
|
+
def process_identity_constraint(constraint_node, category)
|
|
724
|
+
# Create appropriate symbol based on category
|
|
725
|
+
symbol = case category
|
|
726
|
+
when :key
|
|
727
|
+
SVG::Symbols::Key.new
|
|
728
|
+
when :keyref
|
|
729
|
+
SVG::Symbols::Keyref.new
|
|
730
|
+
when :unique
|
|
731
|
+
SVG::Symbols::Unique.new
|
|
732
|
+
end
|
|
733
|
+
|
|
734
|
+
# Set common properties
|
|
735
|
+
symbol.name = constraint_node["name"]
|
|
736
|
+
namespace = constraint_node["namespace"]
|
|
737
|
+
symbol.namespace = namespace if namespace && namespace != schema_namespace
|
|
738
|
+
symbol.description = extract_documentation(constraint_node)
|
|
739
|
+
|
|
740
|
+
# For keyref, set the refer attribute
|
|
741
|
+
if category == :keyref
|
|
742
|
+
symbol.refer = constraint_node["refer"]
|
|
743
|
+
end
|
|
744
|
+
|
|
745
|
+
builder.append_child(symbol)
|
|
746
|
+
|
|
747
|
+
# Process selector
|
|
748
|
+
selector_node = constraint_node.at_xpath("xs:selector",
|
|
749
|
+
"xs" => XSD_NAMESPACE)
|
|
750
|
+
if selector_node
|
|
751
|
+
selector_symbol = SVG::Symbols::Selector.new
|
|
752
|
+
selector_symbol.xpath = selector_node["xpath"]
|
|
753
|
+
builder.append_child(selector_symbol)
|
|
754
|
+
builder.level_up
|
|
755
|
+
end
|
|
756
|
+
|
|
757
|
+
# Process field(s)
|
|
758
|
+
constraint_node.xpath("xs:field",
|
|
759
|
+
"xs" => XSD_NAMESPACE).each do |field_node|
|
|
760
|
+
field_symbol = SVG::Symbols::Field.new
|
|
761
|
+
field_symbol.xpath = field_node["xpath"]
|
|
762
|
+
builder.append_child(field_symbol)
|
|
763
|
+
builder.level_up
|
|
764
|
+
end
|
|
765
|
+
|
|
766
|
+
builder.level_up
|
|
767
|
+
end
|
|
768
|
+
|
|
769
|
+
MAX_TYPE_DEPTH = 100
|
|
770
|
+
|
|
771
|
+
def type_chain_enter(type_name)
|
|
772
|
+
return false if @type_depth >= MAX_TYPE_DEPTH
|
|
773
|
+
return false if @type_ancestors.include?(type_name)
|
|
774
|
+
|
|
775
|
+
@type_depth += 1
|
|
776
|
+
@type_ancestors.add(type_name)
|
|
777
|
+
true
|
|
778
|
+
end
|
|
779
|
+
|
|
780
|
+
def type_chain_exit(type_name)
|
|
781
|
+
@type_depth -= 1
|
|
782
|
+
@type_ancestors.delete(type_name)
|
|
321
783
|
end
|
|
322
784
|
end
|
|
323
785
|
end
|
data/lib/xsdvi.rb
CHANGED
|
@@ -25,6 +25,12 @@ require_relative "xsdvi/utils/writer"
|
|
|
25
25
|
require_relative "xsdvi/utils/resource_loader"
|
|
26
26
|
require_relative "xsdvi/utils/width_calculator"
|
|
27
27
|
|
|
28
|
+
# Comparison components
|
|
29
|
+
require_relative "xsdvi/comparison/java_manager"
|
|
30
|
+
require_relative "xsdvi/comparison/metadata_extractor"
|
|
31
|
+
require_relative "xsdvi/comparison/html_generator"
|
|
32
|
+
require_relative "xsdvi/comparison/dual_generator"
|
|
33
|
+
|
|
28
34
|
module Xsdvi
|
|
29
35
|
class Error < StandardError; end
|
|
30
36
|
end
|