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