lutaml-model 0.8.17 → 0.8.18
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/.github/workflows/downstream-performance.yml +27 -1
- data/.github/workflows/js-pr-check.yml +102 -0
- data/.github/workflows/js-sync.yml +51 -0
- data/.github/workflows/opal.yml +26 -6
- data/.github/workflows/performance.yml +32 -7
- data/.github/workflows/rake.yml +78 -32
- data/.gitmodules +6 -0
- data/.rubocop_todo.yml +8 -0
- data/Gemfile +16 -1
- data/README.adoc +267 -0
- data/Rakefile +166 -4
- data/docs/_guides/schema-import.adoc +42 -0
- data/docs/_guides/xml-mapping.adoc +21 -0
- data/docs/_pages/validation.adoc +65 -0
- data/lib/compat/opal/generate_boot.rb +123 -0
- data/lib/compat/opal/js_bundle_entry.rb +42 -0
- data/lib/compat/opal/lutaml_model_boot.rb +497 -0
- data/lib/compat/opal/moxml_boot.rb +68 -0
- data/lib/compat/opal/yaml_compat.rb +32 -0
- data/lib/lutaml/json.rb +1 -1
- data/lib/lutaml/key_value/transform.rb +3 -8
- data/lib/lutaml/key_value/transformation/value_serializer.rb +14 -1
- data/lib/lutaml/key_value/transformation.rb +17 -5
- data/lib/lutaml/model/adapter_resolver.rb +2 -2
- data/lib/lutaml/model/attribute.rb +88 -11
- data/lib/lutaml/model/cached_type_resolver.rb +1 -1
- data/lib/lutaml/model/error/union_schema_unsupported_error.rb +11 -0
- data/lib/lutaml/model/global_context.rb +11 -8
- data/lib/lutaml/model/instrumentation.rb +2 -2
- data/lib/lutaml/model/mapping/mapping_rule.rb +0 -6
- data/lib/lutaml/model/register.rb +2 -2
- data/lib/lutaml/model/runtime_compatibility.rb +50 -0
- data/lib/lutaml/model/schema/class_loader.rb +58 -0
- data/lib/lutaml/model/schema/compiled_output.rb +83 -0
- data/lib/lutaml/model/schema/definitions/attribute.rb +32 -0
- data/lib/lutaml/model/schema/definitions/choice.rb +20 -0
- data/lib/lutaml/model/schema/definitions/facet.rb +40 -0
- data/lib/lutaml/model/schema/definitions/group_import.rb +22 -0
- data/lib/lutaml/model/schema/definitions/member_walk.rb +33 -0
- data/lib/lutaml/model/schema/definitions/model.rb +40 -0
- data/lib/lutaml/model/schema/definitions/namespace.rb +23 -0
- data/lib/lutaml/model/schema/definitions/restricted_type.rb +30 -0
- data/lib/lutaml/model/schema/definitions/sequence.rb +19 -0
- data/lib/lutaml/model/schema/definitions/simple_content.rb +22 -0
- data/lib/lutaml/model/schema/definitions/transform_facet.rb +21 -0
- data/lib/lutaml/model/schema/definitions/type_ref.rb +20 -0
- data/lib/lutaml/model/schema/definitions/union_type.rb +28 -0
- data/lib/lutaml/model/schema/definitions/xml_root.rb +20 -0
- data/lib/lutaml/model/schema/definitions.rb +24 -0
- data/lib/lutaml/model/schema/file_writer.rb +70 -0
- data/lib/lutaml/model/schema/generator/definitions_collection.rb +16 -2
- data/lib/lutaml/model/schema/generator/property.rb +21 -4
- data/lib/lutaml/model/schema/module_nesting.rb +31 -0
- data/lib/lutaml/model/schema/namespace_naming.rb +46 -0
- data/lib/lutaml/model/schema/registry_generator.rb +115 -0
- data/lib/lutaml/model/schema/renderers/base.rb +40 -0
- data/lib/lutaml/model/schema/renderers/mappings.rb +72 -0
- data/lib/lutaml/model/schema/renderers/member_decls.rb +115 -0
- data/lib/lutaml/model/schema/renderers/model.rb +121 -0
- data/lib/lutaml/model/schema/renderers/namespace.rb +30 -0
- data/lib/lutaml/model/schema/renderers/registration.rb +64 -0
- data/lib/lutaml/model/schema/renderers/required_files_calculator.rb +93 -0
- data/lib/lutaml/model/schema/renderers/restricted_type.rb +88 -0
- data/lib/lutaml/model/schema/renderers/union.rb +83 -0
- data/lib/lutaml/model/schema/renderers.rb +19 -0
- data/lib/lutaml/model/schema/rnc_compiler/source_resolver.rb +47 -0
- data/lib/lutaml/model/schema/rnc_compiler.rb +62 -0
- data/lib/lutaml/model/schema/rng_compiler/define_classifier.rb +91 -0
- data/lib/lutaml/model/schema/rng_compiler/element_visitor.rb +392 -0
- data/lib/lutaml/model/schema/rng_compiler/member_collector.rb +30 -0
- data/lib/lutaml/model/schema/rng_compiler/rng_helpers.rb +152 -0
- data/lib/lutaml/model/schema/rng_compiler/value_type_resolver.rb +125 -0
- data/lib/lutaml/model/schema/rng_compiler.rb +220 -0
- data/lib/lutaml/model/schema/templates.rb +162 -0
- data/lib/lutaml/model/schema/xml_compiler/element_order.rb +41 -0
- data/lib/lutaml/model/schema/xml_compiler/registry_generator.rb +31 -107
- data/lib/lutaml/model/schema/xml_compiler/spec_builder/complex_types.rb +191 -0
- data/lib/lutaml/model/schema/xml_compiler/spec_builder/members.rb +216 -0
- data/lib/lutaml/model/schema/xml_compiler/spec_builder/simple_types.rb +153 -0
- data/lib/lutaml/model/schema/xml_compiler/spec_builder.rb +167 -0
- data/lib/lutaml/model/schema/xml_compiler/supported_data_types.rb +87 -0
- data/lib/lutaml/model/schema/xml_compiler.rb +38 -517
- data/lib/lutaml/model/schema.rb +19 -0
- data/lib/lutaml/model/serialize/attribute_definition.rb +14 -2
- data/lib/lutaml/model/serialize/builder.rb +15 -2
- data/lib/lutaml/model/serialize/value_mapping.rb +9 -57
- data/lib/lutaml/model/serialize.rb +6 -9
- data/lib/lutaml/model/services/rule_value_extractor.rb +2 -3
- data/lib/lutaml/model/services.rb +0 -2
- data/lib/lutaml/model/store.rb +2 -1
- data/lib/lutaml/model/toml.rb +1 -1
- data/lib/lutaml/model/transform.rb +17 -39
- data/lib/lutaml/model/transformation_registry.rb +1 -1
- data/lib/lutaml/model/type.rb +1 -0
- data/lib/lutaml/model/union.rb +320 -0
- data/lib/lutaml/model/utils.rb +6 -0
- data/lib/lutaml/model/validation.rb +1 -4
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/model.rb +18 -0
- data/lib/lutaml/toml.rb +1 -1
- data/lib/lutaml/xml/adapter.rb +1 -1
- data/lib/lutaml/xml/adapter_loader.rb +1 -1
- data/lib/lutaml/xml/builder/base.rb +1 -1
- data/lib/lutaml/xml/builder.rb +1 -1
- data/lib/lutaml/xml/error/schema_validation_error.rb +25 -0
- data/lib/lutaml/xml/model_transform.rb +5 -1
- data/lib/lutaml/xml/schema/relaxng_schema.rb +5 -0
- data/lib/lutaml/xml/schema/xsd_schema.rb +4 -0
- data/lib/lutaml/xml/serialization/format_conversion.rb +48 -0
- data/lib/lutaml/xml/serialization/instance_methods.rb +13 -0
- data/lib/lutaml/xml/transformation/element_builder.rb +21 -8
- data/lib/lutaml/xml/transformation/ordered_applier.rb +6 -0
- data/lib/lutaml/xml/xsd_validator.rb +66 -0
- data/lib/lutaml/xml.rb +53 -12
- data/lutaml-model.gemspec +4 -2
- data/spec/fixtures/xml/schema/rnc/address_book.rnc +15 -0
- data/spec/fixtures/xml/schema/rnc/book_features.rnc +18 -0
- data/spec/fixtures/xml/schema/rnc/includes/book.rnc +6 -0
- data/spec/fixtures/xml/schema/rnc/includes/circular_a.rnc +8 -0
- data/spec/fixtures/xml/schema/rnc/includes/circular_b.rnc +8 -0
- data/spec/fixtures/xml/schema/rnc/includes/library.rnc +8 -0
- data/spec/fixtures/xml/schema/rng/address_book.rng +16 -0
- data/spec/fixtures/xml/schema/rng/book.rng +27 -0
- data/spec/fixtures/xml/schema/rng/fixed_value.rng +11 -0
- data/spec/fixtures/xml/schema/rng/fragment_in_choice.rng +30 -0
- data/spec/fixtures/xml/schema/rng/full_name.rng +39 -0
- data/spec/fixtures/xml/schema/rng/integer_range.rng +32 -0
- data/spec/fixtures/xml/schema/rng/list_type.rng +12 -0
- data/spec/fixtures/xml/schema/rng/namespaced.rng +9 -0
- data/spec/fixtures/xml/schema/rng/paragraph.rng +13 -0
- data/spec/fixtures/xml/schema/rng/person.rng +23 -0
- data/spec/fixtures/xml/schema/rng/union.rng +14 -0
- data/spec/fixtures/xml/validate_xml_with_address.xsd +13 -0
- data/spec/fixtures/xml/validate_xml_with_contact.xsd +11 -0
- data/spec/fixtures/xml/validate_xml_with_person.xsd +11 -0
- data/spec/fixtures/xml/validate_xml_with_person_strict.xsd +17 -0
- data/spec/fixtures/xml/validate_xml_with_product.xsd +37 -0
- data/spec/lutaml/model/attribute_apply_value_map_spec.rb +134 -0
- data/spec/lutaml/model/attribute_spec.rb +169 -12
- data/spec/lutaml/model/boot_manifest_spec.rb +30 -0
- data/spec/lutaml/model/collection_spec.rb +18 -0
- data/spec/lutaml/model/mixed_content_spec.rb +171 -0
- data/spec/lutaml/model/opal_smoke_spec.rb +67 -2
- data/spec/lutaml/model/ordered_content_spec.rb +30 -0
- data/spec/lutaml/model/raw_element_spec.rb +125 -0
- data/spec/lutaml/model/rule_value_extractor_spec.rb +7 -14
- data/spec/lutaml/model/runtime_compatibility_spec.rb +25 -0
- data/spec/lutaml/model/schema/renderers/model_spec.rb +149 -0
- data/spec/lutaml/model/schema/renderers/namespace_spec.rb +36 -0
- data/spec/lutaml/model/schema/renderers/restricted_type_spec.rb +75 -0
- data/spec/lutaml/model/schema/renderers/union_spec.rb +58 -0
- data/spec/lutaml/model/transform_apply_value_map_spec.rb +64 -0
- data/spec/lutaml/model/union_attribute_spec.rb +658 -0
- data/spec/lutaml/model/union_spec.rb +257 -0
- data/spec/lutaml/model/utils_spec.rb +14 -0
- data/spec/lutaml/xml/opal_xml_spec.rb +22 -20
- data/spec/lutaml/xml/schema/compiler_spec.rb +21 -4
- data/spec/lutaml/xml/schema/rnc_compiler_spec.rb +404 -0
- data/spec/lutaml/xml/schema/rng_compiler_spec.rb +893 -0
- data/spec/lutaml/xml/validate_xml_with_constructs_spec.rb +222 -0
- data/spec/lutaml/xml/validate_xml_with_spec.rb +186 -0
- data/spec/spec_helper.rb +28 -11
- data/spec/support/opal.rb +5 -2
- metadata +97 -31
- data/lib/lutaml/model/schema/xml_compiler/attribute.rb +0 -106
- data/lib/lutaml/model/schema/xml_compiler/attribute_group.rb +0 -47
- data/lib/lutaml/model/schema/xml_compiler/choice.rb +0 -67
- data/lib/lutaml/model/schema/xml_compiler/complex_content.rb +0 -27
- data/lib/lutaml/model/schema/xml_compiler/complex_content_restriction.rb +0 -34
- data/lib/lutaml/model/schema/xml_compiler/complex_type.rb +0 -173
- data/lib/lutaml/model/schema/xml_compiler/element.rb +0 -121
- data/lib/lutaml/model/schema/xml_compiler/group.rb +0 -113
- data/lib/lutaml/model/schema/xml_compiler/restriction.rb +0 -104
- data/lib/lutaml/model/schema/xml_compiler/sequence.rb +0 -52
- data/lib/lutaml/model/schema/xml_compiler/simple_content.rb +0 -40
- data/lib/lutaml/model/schema/xml_compiler/simple_type.rb +0 -265
- data/lib/lutaml/model/schema/xml_compiler/xml_namespace_class.rb +0 -110
- data/lib/lutaml/model/services/default_value_resolver.rb +0 -60
- data/spec/lutaml/model/services/default_value_resolver_spec.rb +0 -162
- data/spec/lutaml/xml/schema/compiler/attribute_group_spec.rb +0 -71
- data/spec/lutaml/xml/schema/compiler/attribute_spec.rb +0 -67
- data/spec/lutaml/xml/schema/compiler/choice_spec.rb +0 -74
- data/spec/lutaml/xml/schema/compiler/complex_content_restriction_spec.rb +0 -60
- data/spec/lutaml/xml/schema/compiler/complex_content_spec.rb +0 -41
- data/spec/lutaml/xml/schema/compiler/complex_type_spec.rb +0 -202
- data/spec/lutaml/xml/schema/compiler/element_spec.rb +0 -68
- data/spec/lutaml/xml/schema/compiler/group_spec.rb +0 -90
- data/spec/lutaml/xml/schema/compiler/restriction_spec.rb +0 -77
- data/spec/lutaml/xml/schema/compiler/sequence_spec.rb +0 -65
- data/spec/lutaml/xml/schema/compiler/simple_content_spec.rb +0 -61
- data/spec/lutaml/xml/schema/compiler/simple_type_spec.rb +0 -184
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Model
|
|
5
|
+
module Schema
|
|
6
|
+
module RngCompiler
|
|
7
|
+
# Walks an RNG <element> / <define> subtree and produces
|
|
8
|
+
# Definitions::Model / Definitions::RestrictedType / Definitions::UnionType
|
|
9
|
+
# objects, registered in the shared `classes` hash.
|
|
10
|
+
#
|
|
11
|
+
# Supported constructs:
|
|
12
|
+
# element, attribute, ref, text, data, value, empty,
|
|
13
|
+
# optional, zeroOrMore, oneOrMore, group, choice, mixed, list,
|
|
14
|
+
# interleave
|
|
15
|
+
#
|
|
16
|
+
# Shape classification (define-wraps-data, define-wraps-enum-choice,
|
|
17
|
+
# union) is delegated to DefineClassifier. Value-shape resolution
|
|
18
|
+
# for individual children is delegated to ValueTypeResolver.
|
|
19
|
+
class ElementVisitor
|
|
20
|
+
DISPATCHABLE_KINDS = %i[
|
|
21
|
+
element attribute ref group optional oneOrMore zeroOrMore
|
|
22
|
+
choice list interleave empty
|
|
23
|
+
].freeze
|
|
24
|
+
|
|
25
|
+
REPETITION_CTX = {
|
|
26
|
+
zeroOrMore: { collection: (0..Float::INFINITY), initialize_empty: true },
|
|
27
|
+
oneOrMore: { collection: (1..Float::INFINITY), initialize_empty: true },
|
|
28
|
+
list: { collection: (0..Float::INFINITY), initialize_empty: true },
|
|
29
|
+
}.freeze
|
|
30
|
+
|
|
31
|
+
HANDLERS = {
|
|
32
|
+
element: :handle_element,
|
|
33
|
+
attribute: :handle_attribute,
|
|
34
|
+
ref: :handle_ref,
|
|
35
|
+
group: :handle_group,
|
|
36
|
+
optional: :handle_optional,
|
|
37
|
+
oneOrMore: :handle_repeating,
|
|
38
|
+
zeroOrMore: :handle_repeating,
|
|
39
|
+
list: :handle_repeating,
|
|
40
|
+
choice: :handle_choice,
|
|
41
|
+
interleave: :handle_interleave,
|
|
42
|
+
empty: :handle_empty,
|
|
43
|
+
}.freeze
|
|
44
|
+
|
|
45
|
+
def initialize(defines, classes, default_namespace_class: nil,
|
|
46
|
+
register_namespace: nil)
|
|
47
|
+
@defines = defines
|
|
48
|
+
@classes = classes
|
|
49
|
+
@default_namespace_class = default_namespace_class
|
|
50
|
+
@register_namespace = register_namespace
|
|
51
|
+
@value_type_resolver = ValueTypeResolver.new(
|
|
52
|
+
defines, classes,
|
|
53
|
+
compile_define: method(:compile_define),
|
|
54
|
+
register_class: method(:register_class!)
|
|
55
|
+
)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Compile a top-level <element>. Always becomes a rooted model.
|
|
59
|
+
def compile_element(element)
|
|
60
|
+
class_name = Utils.camel_case(element.attr_name)
|
|
61
|
+
model = @classes[class_name] ||= Definitions::Model.new(
|
|
62
|
+
class_name: class_name,
|
|
63
|
+
xml_root: Definitions::XmlRoot.new(kind: :element, name: element.attr_name),
|
|
64
|
+
documentation: documentation_text(element),
|
|
65
|
+
namespace_class_name: namespace_class_for(element),
|
|
66
|
+
)
|
|
67
|
+
visit_content(element, model)
|
|
68
|
+
model
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Compile a <define> into a class.
|
|
72
|
+
# - Define body matches a simple-type shape -> RestrictedType/UnionType.
|
|
73
|
+
# - Wraps exactly one element -> rooted Definitions::Model.
|
|
74
|
+
# - Multiple or zero wrapped elements -> fragment Definitions::Model.
|
|
75
|
+
def compile_define(define)
|
|
76
|
+
class_name = Utils.camel_case(define.name)
|
|
77
|
+
return @classes[class_name] if @classes.key?(class_name)
|
|
78
|
+
|
|
79
|
+
if (simple = DefineClassifier.build(define, class_name))
|
|
80
|
+
register_class!(simple)
|
|
81
|
+
return simple
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
build_complex_define(define, class_name)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
def build_complex_define(define, class_name)
|
|
90
|
+
wrapping = define.element.size == 1 ? define.element.first : nil
|
|
91
|
+
|
|
92
|
+
xml_root = if wrapping
|
|
93
|
+
Definitions::XmlRoot.new(kind: :element, name: wrapping.attr_name)
|
|
94
|
+
else
|
|
95
|
+
Definitions::XmlRoot.new(kind: :fragment)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
model = Definitions::Model.new(
|
|
99
|
+
class_name: class_name,
|
|
100
|
+
xml_root: xml_root,
|
|
101
|
+
documentation: documentation_text(define) || documentation_text(wrapping),
|
|
102
|
+
namespace_class_name: namespace_class_for(wrapping || define),
|
|
103
|
+
)
|
|
104
|
+
register_class!(model)
|
|
105
|
+
visit_content(wrapping || define, model)
|
|
106
|
+
model
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def register_class!(klass)
|
|
110
|
+
@classes[klass.class_name] = klass
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# A node's foreign XML namespace URI, or nil. An empty `ns` (RNC's
|
|
114
|
+
# `ns=""` no-namespace override) and the built-in "xml" prefix are
|
|
115
|
+
# both treated as not foreign, so `namespace_class_for` hands the
|
|
116
|
+
# node the grammar default namespace. lutaml expresses namespaces
|
|
117
|
+
# only at class level, so a per-node `ns=""` override cannot be
|
|
118
|
+
# represented distinctly from the default and is not separately
|
|
119
|
+
# honored -- excluding "" here just avoids registering a bogus
|
|
120
|
+
# empty-URI namespace that would clobber the grammar default.
|
|
121
|
+
def foreign_namespace_uri(node)
|
|
122
|
+
uri = node.respond_to?(:ns) ? node.ns : nil
|
|
123
|
+
uri if uri.is_a?(String) && !uri.empty? && uri != "xml"
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# An element's own ns (from `element ex:name`) becomes its model's
|
|
127
|
+
# class-level namespace; otherwise it inherits the grammar default.
|
|
128
|
+
def namespace_class_for(node)
|
|
129
|
+
uri = foreign_namespace_uri(node)
|
|
130
|
+
uri ? @register_namespace.call(uri) : @default_namespace_class
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def documentation_text(node)
|
|
134
|
+
return nil unless node.respond_to?(:documentation)
|
|
135
|
+
|
|
136
|
+
docs = Array(node.documentation).map(&:to_s).reject(&:empty?)
|
|
137
|
+
docs.empty? ? nil : docs.join("\n")
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Generic content walker.
|
|
141
|
+
def visit_content(node, model, ctx = default_ctx)
|
|
142
|
+
ordered_children(node).each do |kind, child|
|
|
143
|
+
dispatch(kind, child, model, ctx)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
model.mixed = true if mixed?(node)
|
|
147
|
+
model.text_content = true if text?(node)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def default_ctx
|
|
151
|
+
{ collection: nil, initialize_empty: false }
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def ordered_children(node)
|
|
155
|
+
element_entries = element_order_entries(node)
|
|
156
|
+
return fallback_each_child_kind(node) if element_entries.empty?
|
|
157
|
+
|
|
158
|
+
arrays = {}
|
|
159
|
+
indices = ::Hash.new(0)
|
|
160
|
+
element_entries.each_with_object([]) do |entry, pairs|
|
|
161
|
+
kind = entry.name.to_sym
|
|
162
|
+
next unless DISPATCHABLE_KINDS.include?(kind) && node.respond_to?(kind)
|
|
163
|
+
|
|
164
|
+
children = arrays[kind] ||= Array(node.public_send(kind))
|
|
165
|
+
child = children[indices[kind]]
|
|
166
|
+
indices[kind] += 1
|
|
167
|
+
pairs << [kind, child] if child
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def element_order_entries(node)
|
|
172
|
+
return [] unless node.respond_to?(:element_order)
|
|
173
|
+
|
|
174
|
+
Array(node.element_order).select do |e|
|
|
175
|
+
e.respond_to?(:node_type) && e.node_type == :element
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def fallback_each_child_kind(node)
|
|
180
|
+
DISPATCHABLE_KINDS.each_with_object([]) do |attr_name, pairs|
|
|
181
|
+
next unless node.respond_to?(attr_name)
|
|
182
|
+
|
|
183
|
+
Array(node.public_send(attr_name)).each do |child|
|
|
184
|
+
pairs << [attr_name, child] unless child.nil?
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def dispatch(kind, child, parent, ctx)
|
|
190
|
+
handler = HANDLERS[kind] or return
|
|
191
|
+
|
|
192
|
+
send(handler, kind, child, parent, ctx)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# --- handlers ---------------------------------------------------
|
|
196
|
+
|
|
197
|
+
def handle_element(_kind, child, parent, ctx)
|
|
198
|
+
doc = documentation_text(child)
|
|
199
|
+
value_type = @value_type_resolver.resolve(child)
|
|
200
|
+
type_ref = type_ref_for_element(child, value_type)
|
|
201
|
+
push_attribute(parent, build_attribute(child, type_ref, :element, ctx, doc))
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def type_ref_for_element(child, value_type)
|
|
205
|
+
return Definitions::TypeRef.new(kind: :symbol, value: value_type.to_s) if value_type
|
|
206
|
+
|
|
207
|
+
compiled = compile_element(child)
|
|
208
|
+
Definitions::TypeRef.new(kind: :class_ref, value: compiled.class_name)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def handle_attribute(_kind, child, parent, ctx)
|
|
212
|
+
doc = documentation_text(child)
|
|
213
|
+
symbol = attribute_type_symbol(child)
|
|
214
|
+
type_ref = Definitions::TypeRef.new(kind: :symbol, value: symbol.to_s)
|
|
215
|
+
fixed = fixed_value_default(child)
|
|
216
|
+
push_attribute(parent, build_attribute(child, type_ref, :attribute, ctx, doc, default: fixed))
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# The attribute's value type, as a Symbol. An attribute in a foreign
|
|
220
|
+
# namespace (a prefix other than the built-in "xml") must carry that
|
|
221
|
+
# namespace, which lutaml-model expresses only through a Type::Value
|
|
222
|
+
# subclass — so wrap the value in a generated namespaced type.
|
|
223
|
+
def attribute_type_symbol(child)
|
|
224
|
+
base = @value_type_resolver.resolve(child) || :string
|
|
225
|
+
uri = foreign_namespace_uri(child)
|
|
226
|
+
return base unless uri
|
|
227
|
+
|
|
228
|
+
namespaced_attribute_type(child, base, @register_namespace.call(uri))
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# Give the attribute's value a namespace via a dedicated generated
|
|
232
|
+
# type — never by mutating the resolved type, which may be a named
|
|
233
|
+
# define or an inline type shared with unqualified members. When the
|
|
234
|
+
# resolved base is itself a generated RestrictedType, subclass it so
|
|
235
|
+
# its constraints are inherited; otherwise subclass the built-in base.
|
|
236
|
+
def namespaced_attribute_type(child, base, ns_class)
|
|
237
|
+
resolved = generated_type_for(base)
|
|
238
|
+
parent = resolved ? resolved.class_name : RngHelpers.parent_class_for(base)
|
|
239
|
+
|
|
240
|
+
type = Definitions::RestrictedType.new(
|
|
241
|
+
class_name: RngHelpers.unique_class_name(
|
|
242
|
+
@classes, "#{Utils.camel_case(child.attr_name)}Type"
|
|
243
|
+
),
|
|
244
|
+
parent_class: parent,
|
|
245
|
+
facets: Definitions::Facet.new,
|
|
246
|
+
namespace_class_name: ns_class,
|
|
247
|
+
)
|
|
248
|
+
register_class!(type)
|
|
249
|
+
RngHelpers.type_symbol(type.class_name)
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# The generated RestrictedType a resolved type symbol refers to, found
|
|
253
|
+
# by symbol identity (so acronym class names round-trip) — or nil for a
|
|
254
|
+
# built-in symbol. A built-in symbol always means the built-in type,
|
|
255
|
+
# even if a define shares its name (a custom `dateTime` must not shadow
|
|
256
|
+
# the primitive `:date_time`).
|
|
257
|
+
def generated_type_for(base)
|
|
258
|
+
return nil if Lutaml::Model::Type::TYPE_CODES.key?(base)
|
|
259
|
+
|
|
260
|
+
@classes.values.find do |klass|
|
|
261
|
+
klass.is_a?(Definitions::RestrictedType) &&
|
|
262
|
+
RngHelpers.type_symbol(klass.class_name) == base
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def build_attribute(child, type_ref, kind, ctx, doc, default: nil)
|
|
267
|
+
xml_name = xml_name_for(child)
|
|
268
|
+
Definitions::Attribute.new(
|
|
269
|
+
name: Utils.snake_case(xml_name.tr(":", "_")),
|
|
270
|
+
type: type_ref,
|
|
271
|
+
xml_name: xml_name,
|
|
272
|
+
kind: kind,
|
|
273
|
+
collection: ctx[:collection] || false,
|
|
274
|
+
initialize_empty: ctx[:initialize_empty] || false,
|
|
275
|
+
documentation: doc,
|
|
276
|
+
default: default,
|
|
277
|
+
)
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def xml_name_for(child)
|
|
281
|
+
name = child.attr_name
|
|
282
|
+
ns = child.respond_to?(:ns) ? child.ns : nil
|
|
283
|
+
return name unless ns == "xml"
|
|
284
|
+
|
|
285
|
+
"#{ns}:#{name}"
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# <attribute name="x"><value>X</value></attribute> = fixed value.
|
|
289
|
+
# Match XSD compiler's behavior — emit `default: -> { "X" }` so
|
|
290
|
+
# the value round-trips even when omitted.
|
|
291
|
+
def fixed_value_default(container)
|
|
292
|
+
return nil unless container.respond_to?(:value)
|
|
293
|
+
|
|
294
|
+
values = Array(container.value)
|
|
295
|
+
return nil if values.size != 1
|
|
296
|
+
return nil if RngHelpers.structural_content?(container)
|
|
297
|
+
|
|
298
|
+
values.first.value.to_s
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
def handle_ref(_kind, ref, parent, ctx)
|
|
302
|
+
target_define = @defines[ref.name]
|
|
303
|
+
raise Lutaml::Model::Error, "ref to unknown define: #{ref.name}" unless target_define
|
|
304
|
+
|
|
305
|
+
target_class = compile_define(target_define)
|
|
306
|
+
|
|
307
|
+
if RngHelpers.simple_type?(target_class)
|
|
308
|
+
type_ref = Definitions::TypeRef.new(kind: :symbol, value: RngHelpers.type_symbol(target_class.class_name).to_s)
|
|
309
|
+
push_attribute(parent, build_ref_attribute(ref, type_ref, ref.name, ctx))
|
|
310
|
+
elsif RngHelpers.fragment_model?(target_class) && ctx[:collection].nil?
|
|
311
|
+
push_import(parent, target_class.class_name)
|
|
312
|
+
else
|
|
313
|
+
type_ref = Definitions::TypeRef.new(kind: :class_ref, value: target_class.class_name)
|
|
314
|
+
xml_name = target_class.xml_root.name || ref.name
|
|
315
|
+
push_attribute(parent, build_ref_attribute(ref, type_ref, xml_name, ctx))
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
def build_ref_attribute(ref, type_ref, xml_name, ctx)
|
|
320
|
+
Definitions::Attribute.new(
|
|
321
|
+
name: Utils.snake_case(ref.name),
|
|
322
|
+
type: type_ref,
|
|
323
|
+
xml_name: xml_name,
|
|
324
|
+
kind: :element,
|
|
325
|
+
collection: ctx[:collection] || false,
|
|
326
|
+
initialize_empty: ctx[:initialize_empty] || false,
|
|
327
|
+
)
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
# <group> = ordered sequence of items.
|
|
331
|
+
def handle_group(_kind, group, parent, ctx)
|
|
332
|
+
collector = MemberCollector.new
|
|
333
|
+
visit_content(group, collector, ctx)
|
|
334
|
+
return if collector.members.empty?
|
|
335
|
+
|
|
336
|
+
parent.members << Definitions::Sequence.new(members: collector.members)
|
|
337
|
+
collector.imports.each { |name| push_import(parent, name) }
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
def handle_optional(_kind, opt, parent, ctx)
|
|
341
|
+
visit_content(opt, parent, ctx)
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
def handle_interleave(_kind, interleave, parent, ctx)
|
|
345
|
+
visit_content(interleave, parent, ctx)
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
def handle_repeating(kind, node, parent, ctx)
|
|
349
|
+
visit_content(node, parent, ctx.merge(REPETITION_CTX.fetch(kind)))
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
def handle_choice(_kind, choice, parent, ctx)
|
|
353
|
+
return if RngHelpers.pure_value_choice?(choice)
|
|
354
|
+
|
|
355
|
+
collector = MemberCollector.new
|
|
356
|
+
visit_content(choice, collector, ctx)
|
|
357
|
+
return if collector.members.empty?
|
|
358
|
+
|
|
359
|
+
parent.members << Definitions::Choice.new(
|
|
360
|
+
alternatives: collector.members,
|
|
361
|
+
header: "choice",
|
|
362
|
+
)
|
|
363
|
+
collector.imports.each { |name| push_import(parent, name) }
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
def handle_empty(_kind, _child, _parent, _ctx)
|
|
367
|
+
# <empty/> contributes no content — intentionally no-op.
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
def push_attribute(parent, attr)
|
|
371
|
+
parent.members.reject! do |m|
|
|
372
|
+
m.is_a?(Definitions::Attribute) && m.name == attr.name
|
|
373
|
+
end
|
|
374
|
+
parent.members << attr
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
def push_import(parent, name)
|
|
378
|
+
parent.imports << name unless parent.imports.include?(name)
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
def mixed?(node)
|
|
382
|
+
node.respond_to?(:mixed) && Utils.present?(node.mixed)
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
def text?(node)
|
|
386
|
+
node.respond_to?(:text) && Utils.present?(node.text)
|
|
387
|
+
end
|
|
388
|
+
end
|
|
389
|
+
end
|
|
390
|
+
end
|
|
391
|
+
end
|
|
392
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Model
|
|
5
|
+
module Schema
|
|
6
|
+
module RngCompiler
|
|
7
|
+
# Lightweight accumulator used by ElementVisitor when collecting the
|
|
8
|
+
# children of a transient construct (`<group>`, `<choice>`) before
|
|
9
|
+
# promoting them into a Definitions::Sequence or Definitions::Choice on
|
|
10
|
+
# the real Definitions::Model.
|
|
11
|
+
#
|
|
12
|
+
# Mirrors the surface ElementVisitor expects on a model
|
|
13
|
+
# (members, imports, mixed, text_content) but with no XML root,
|
|
14
|
+
# documentation, or namespace — none of which apply to a scratch
|
|
15
|
+
# container.
|
|
16
|
+
class MemberCollector
|
|
17
|
+
attr_reader :members, :imports
|
|
18
|
+
attr_accessor :mixed, :text_content
|
|
19
|
+
|
|
20
|
+
def initialize
|
|
21
|
+
@members = []
|
|
22
|
+
@imports = []
|
|
23
|
+
@mixed = false
|
|
24
|
+
@text_content = false
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Model
|
|
5
|
+
module Schema
|
|
6
|
+
module RngCompiler
|
|
7
|
+
# RNG-specific helpers used by ElementVisitor, DefineClassifier, and
|
|
8
|
+
# ValueTypeResolver. Centralising these here avoids the three-way
|
|
9
|
+
# semantic drift the first refactor introduced (e.g. inconsistent
|
|
10
|
+
# `pure_value_choice?` implementations).
|
|
11
|
+
module RngHelpers
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
# True if the compiled define renders as a value type (RestrictedType
|
|
15
|
+
# or UnionType) rather than its own Serializable class. Single
|
|
16
|
+
# source of truth — used by ElementVisitor and ValueTypeResolver.
|
|
17
|
+
def simple_type?(klass)
|
|
18
|
+
klass.is_a?(Definitions::RestrictedType) ||
|
|
19
|
+
klass.is_a?(Definitions::UnionType)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# True iff `klass` is a fragment model (no rooted xml element).
|
|
23
|
+
def fragment_model?(klass)
|
|
24
|
+
klass.is_a?(Definitions::Model) && klass.xml_root.kind == :fragment
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Returns the only element of a collection, or nil if the
|
|
28
|
+
# collection is empty or has more than one element. Treats nil as
|
|
29
|
+
# an empty collection.
|
|
30
|
+
def single(collection)
|
|
31
|
+
arr = Array(collection)
|
|
32
|
+
arr.size == 1 ? arr.first : nil
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# An RNG node has structural content when any of its element /
|
|
36
|
+
# ref / attribute / choice / group / optional / repetition
|
|
37
|
+
# children are populated.
|
|
38
|
+
def structural_content?(node)
|
|
39
|
+
node && present_children?(node, STRUCTURAL_CHILD_NAMES)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# True iff any of `attrs` is a populated array-child of `node`.
|
|
43
|
+
def present_children?(node, attrs)
|
|
44
|
+
attrs.any? do |attr|
|
|
45
|
+
node.respond_to?(attr) && Array(node.public_send(attr)).any?
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# True when `node` has branching structure that needs its own
|
|
50
|
+
# class. Pure value-choices and `<ref>`s to simple types are NOT
|
|
51
|
+
# branching — they're typed leaves.
|
|
52
|
+
def branching_structural?(node)
|
|
53
|
+
return false unless node
|
|
54
|
+
|
|
55
|
+
non_leaf = STRUCTURAL_CHILD_NAMES - %i[choice ref]
|
|
56
|
+
return true if present_children?(node, non_leaf)
|
|
57
|
+
|
|
58
|
+
return false unless node.respond_to?(:choice)
|
|
59
|
+
|
|
60
|
+
Array(node.choice).any? { |c| !pure_value_choice?(c) }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# True iff `choice` is a <choice> consisting purely of <value>
|
|
64
|
+
# alternatives (no <element>, <ref>, <data>, or <text>). A <text>
|
|
65
|
+
# alternative subsumes the literals, so `text | "x"` is a plain
|
|
66
|
+
# string, not an enum restricted to "x".
|
|
67
|
+
def pure_value_choice?(choice)
|
|
68
|
+
return false unless choice
|
|
69
|
+
return false unless choice.respond_to?(:value) && Array(choice.value).any?
|
|
70
|
+
return false if choice.respond_to?(:text) && Utils.present?(choice.text)
|
|
71
|
+
|
|
72
|
+
!present_children?(choice, %i[element ref data])
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# True iff `choice` is a union: at least 2 <data> alternatives
|
|
76
|
+
# and no other content.
|
|
77
|
+
def pure_union_choice?(choice)
|
|
78
|
+
return false unless choice
|
|
79
|
+
return false unless choice.respond_to?(:data) && Array(choice.data).size >= 2
|
|
80
|
+
|
|
81
|
+
!present_children?(choice, %i[element ref value])
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Build a Definitions::Facet from an RNG <data>'s <param> children.
|
|
85
|
+
def facet_from_data(data)
|
|
86
|
+
facet = Definitions::Facet.new
|
|
87
|
+
Array(data.param).each do |param|
|
|
88
|
+
apply_param(facet, param.name, param.value.to_s)
|
|
89
|
+
end
|
|
90
|
+
facet
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Build a Definitions::Facet from an array of <value> elements.
|
|
94
|
+
def facet_from_values(values)
|
|
95
|
+
Definitions::Facet.new(
|
|
96
|
+
enumerations: Array(values).map(&:value),
|
|
97
|
+
)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Snake_case symbol used as the registry key for a generated
|
|
101
|
+
# simple-type/union-type/namespace class.
|
|
102
|
+
def type_symbol(class_name)
|
|
103
|
+
Utils.snake_case(class_name).to_sym
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# A class name derived from base_name that does not collide with an
|
|
107
|
+
# existing key in `classes` (appends 2, 3, ... on collision).
|
|
108
|
+
def unique_class_name(classes, base_name)
|
|
109
|
+
return base_name unless classes.key?(base_name)
|
|
110
|
+
|
|
111
|
+
counter = 2
|
|
112
|
+
counter += 1 while classes.key?("#{base_name}#{counter}")
|
|
113
|
+
"#{base_name}#{counter}"
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Maps an RNG/XSD base symbol to its Ruby parent class string,
|
|
117
|
+
# defaulting to the string type when unknown.
|
|
118
|
+
def parent_class_for(base_symbol)
|
|
119
|
+
Lutaml::Model::Type::TYPE_CODES.fetch(
|
|
120
|
+
base_symbol, Lutaml::Model::Type::TYPE_CODES[:string]
|
|
121
|
+
).to_s
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def apply_param(facet, name, value)
|
|
125
|
+
case name
|
|
126
|
+
when "minInclusive" then facet.min_inclusive = numeric_or_string(value)
|
|
127
|
+
when "maxInclusive" then facet.max_inclusive = numeric_or_string(value)
|
|
128
|
+
when "minExclusive" then facet.min_exclusive = numeric_or_string(value)
|
|
129
|
+
when "maxExclusive" then facet.max_exclusive = numeric_or_string(value)
|
|
130
|
+
when "minLength" then facet.min_length = value.to_i
|
|
131
|
+
when "maxLength" then facet.max_length = value.to_i
|
|
132
|
+
when "length" then facet.length = value.to_i
|
|
133
|
+
when "pattern" then facet.pattern = value
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def numeric_or_string(value)
|
|
138
|
+
return value.to_i if /\A-?\d+\z/.match?(value)
|
|
139
|
+
return value.to_f if /\A-?\d+\.\d+\z/.match?(value)
|
|
140
|
+
|
|
141
|
+
value
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
STRUCTURAL_CHILD_NAMES = %i[
|
|
145
|
+
element ref attribute choice group
|
|
146
|
+
optional zeroOrMore oneOrMore
|
|
147
|
+
].freeze
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Model
|
|
5
|
+
module Schema
|
|
6
|
+
module RngCompiler
|
|
7
|
+
# Resolves the value-shape of an element or attribute container into a
|
|
8
|
+
# Symbol (built-in / registered type) or nil if the container has
|
|
9
|
+
# structural content needing its own class.
|
|
10
|
+
#
|
|
11
|
+
# Patterns handled:
|
|
12
|
+
# <element><text/></element> -> :string
|
|
13
|
+
# <element><data type="X"/></element> -> mapped primitive
|
|
14
|
+
# <element><data ...><param ...>/></data></element> -> anonymous RestrictedType (returns its registry symbol)
|
|
15
|
+
# <element><choice><value>...</value></choice></element> -> anonymous RestrictedType (enum) (returns its registry symbol)
|
|
16
|
+
# <element><ref name="StFoo"/></element> -> :st_foo (if simple)
|
|
17
|
+
#
|
|
18
|
+
# When an anonymous RestrictedType is needed, the resolver *builds* it
|
|
19
|
+
# (pure) and asks the supplied `register_class` callback to register
|
|
20
|
+
# it. Build and register are separate methods so unit testing can
|
|
21
|
+
# exercise the pure logic without side effects.
|
|
22
|
+
class ValueTypeResolver
|
|
23
|
+
def initialize(defines, classes, compile_define:, register_class:)
|
|
24
|
+
@defines = defines
|
|
25
|
+
@classes = classes
|
|
26
|
+
@compile_define = compile_define
|
|
27
|
+
@register_class = register_class
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Returns the resolved type symbol, or nil if `container` needs
|
|
31
|
+
# its own class. May register an anonymous RestrictedType as a
|
|
32
|
+
# side effect (delegated to the register_class callback).
|
|
33
|
+
def resolve(container)
|
|
34
|
+
return nil if RngHelpers.branching_structural?(container)
|
|
35
|
+
|
|
36
|
+
type_from_inline_simple(container) || primitive_or_ref(container)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def type_from_inline_simple(container)
|
|
42
|
+
simple = build_anonymous_simple_type(container)
|
|
43
|
+
return nil unless simple
|
|
44
|
+
|
|
45
|
+
@register_class.call(simple)
|
|
46
|
+
RngHelpers.type_symbol(simple.class_name)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def build_anonymous_simple_type(container)
|
|
50
|
+
anonymous_from_data(container) || anonymous_from_enum_choice(container)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def anonymous_from_data(container)
|
|
54
|
+
data = RngHelpers.single(container.data)
|
|
55
|
+
return nil unless data && Array(data.param).any?
|
|
56
|
+
|
|
57
|
+
base = RngCompiler::DATA_TYPE_MAP.fetch(
|
|
58
|
+
data.type, RngCompiler::DEFAULT_DATA_TYPE
|
|
59
|
+
)
|
|
60
|
+
anonymous_restricted_type(container, base, RngHelpers.facet_from_data(data))
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def anonymous_from_enum_choice(container)
|
|
64
|
+
choice = RngHelpers.single(container.choice)
|
|
65
|
+
return nil unless choice && RngHelpers.pure_value_choice?(choice)
|
|
66
|
+
|
|
67
|
+
anonymous_restricted_type(container, :string, RngHelpers.facet_from_values(choice.value))
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def anonymous_restricted_type(container, base, facet)
|
|
71
|
+
Definitions::RestrictedType.new(
|
|
72
|
+
class_name: RngHelpers.unique_class_name(
|
|
73
|
+
@classes, "#{Utils.camel_case(container.attr_name.to_s)}Type"
|
|
74
|
+
),
|
|
75
|
+
parent_class: RngHelpers.parent_class_for(base),
|
|
76
|
+
facets: facet,
|
|
77
|
+
)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def primitive_or_ref(container)
|
|
81
|
+
primitive = detect_primitive_type(container)
|
|
82
|
+
return primitive if primitive
|
|
83
|
+
|
|
84
|
+
ref_to_simple_type_symbol(container)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def ref_to_simple_type_symbol(container)
|
|
88
|
+
ref = RngHelpers.single(container.ref)
|
|
89
|
+
return nil unless ref
|
|
90
|
+
|
|
91
|
+
target = @defines[ref.name]
|
|
92
|
+
return nil unless target
|
|
93
|
+
|
|
94
|
+
target_class = @compile_define.call(target)
|
|
95
|
+
return RngHelpers.type_symbol(target_class.class_name) if RngHelpers.simple_type?(target_class)
|
|
96
|
+
|
|
97
|
+
nil
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def detect_primitive_type(child)
|
|
101
|
+
return nil if RngHelpers.structural_content?(child)
|
|
102
|
+
|
|
103
|
+
primitive_from_data(child) || primitive_from_text(child) || primitive_from_value(child)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def primitive_from_data(child)
|
|
107
|
+
return nil unless child.data
|
|
108
|
+
|
|
109
|
+
RngCompiler::DATA_TYPE_MAP.fetch(child.data.type, RngCompiler::DEFAULT_DATA_TYPE)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def primitive_from_text(child)
|
|
113
|
+
:string if Utils.present?(child.text)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def primitive_from_value(child)
|
|
117
|
+
# <element><choice><value>a</value>...</choice></element> falls
|
|
118
|
+
# through to this path as :string with no constraint emitted.
|
|
119
|
+
:string if Array(child.value).any?
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|