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,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Model
|
|
5
|
+
module Schema
|
|
6
|
+
# Pure value object holding the output of compilation. No I/O — just
|
|
7
|
+
# data. FileWriter / ClassLoader collaborators consume this to perform
|
|
8
|
+
# the impure parts of `to_models`. Shared across schema compilers (RNG,
|
|
9
|
+
# XSD, future formats).
|
|
10
|
+
#
|
|
11
|
+
# Entries are an ordered list of tagged records, each carrying a
|
|
12
|
+
# CamelCase class name, a payload (either a Definitions::* spec or a
|
|
13
|
+
# pre-rendered Ruby source String), and a `kind` of `:model` or
|
|
14
|
+
# `:namespace`.
|
|
15
|
+
#
|
|
16
|
+
# `:model` entries are written to disk AND registered with
|
|
17
|
+
# `register_all`. `:namespace` entries are written and autoloaded but
|
|
18
|
+
# never registered.
|
|
19
|
+
class CompiledOutput
|
|
20
|
+
Entry = Struct.new(:name, :payload, :kind)
|
|
21
|
+
|
|
22
|
+
attr_reader :entries, :module_namespace, :register_id,
|
|
23
|
+
:source_module_namespace
|
|
24
|
+
|
|
25
|
+
# `module_namespace` is what the central registry file wraps in
|
|
26
|
+
# (and where `register_all` lives). `source_module_namespace`
|
|
27
|
+
# is what the per-class files wrap in — defaults to the same,
|
|
28
|
+
# but ClassLoader can pass a different value when the caller
|
|
29
|
+
# asked for unwrapped classes but a registry constant is still
|
|
30
|
+
# needed to drive autoload.
|
|
31
|
+
def initialize(entries:, module_namespace: nil, register_id: :default,
|
|
32
|
+
source_module_namespace: :inherit)
|
|
33
|
+
@entries = entries
|
|
34
|
+
@module_namespace = module_namespace
|
|
35
|
+
@register_id = register_id
|
|
36
|
+
@source_module_namespace =
|
|
37
|
+
source_module_namespace == :inherit ? module_namespace : source_module_namespace
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Model-only entries — what the registry generator and ClassLoader
|
|
41
|
+
# iterate when emitting `register_all` calls.
|
|
42
|
+
def models
|
|
43
|
+
@models ||= entries.select { |e| e.kind == :model }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Namespace-only entries — autoloaded but never registered.
|
|
47
|
+
def namespaces
|
|
48
|
+
@namespaces ||= entries.select { |e| e.kind == :namespace }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Flat `name => Ruby source` hash covering every entry (models +
|
|
52
|
+
# namespaces). Used by FileWriter for the per-file write loop and
|
|
53
|
+
# by RNG / XSD compilers as the public `to_models` return value.
|
|
54
|
+
# Class files are rendered against `source_module_namespace`,
|
|
55
|
+
# which usually equals `module_namespace` but differs when the
|
|
56
|
+
# caller wanted unwrapped class files inside a registry module.
|
|
57
|
+
def sources
|
|
58
|
+
@sources ||= entries.to_h { |e| [e.name, source_for(e)] }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def source_for(entry)
|
|
64
|
+
renderer_for(entry.payload).render(
|
|
65
|
+
entry.payload,
|
|
66
|
+
module_namespace: @source_module_namespace,
|
|
67
|
+
register_id: @register_id,
|
|
68
|
+
)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def renderer_for(spec)
|
|
72
|
+
case spec
|
|
73
|
+
when Definitions::Model then Renderers::Model
|
|
74
|
+
when Definitions::RestrictedType then Renderers::RestrictedType
|
|
75
|
+
when Definitions::UnionType then Renderers::Union
|
|
76
|
+
when Definitions::Namespace then Renderers::Namespace
|
|
77
|
+
else raise ArgumentError, "Unknown spec type: #{spec.class}"
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Model
|
|
5
|
+
module Schema
|
|
6
|
+
module Definitions
|
|
7
|
+
# One attribute on a generated class.
|
|
8
|
+
class Attribute
|
|
9
|
+
attr_accessor :name, :type, :xml_name, :kind, :collection,
|
|
10
|
+
:default, :documentation, :initialize_empty,
|
|
11
|
+
:render_default, :render_empty
|
|
12
|
+
|
|
13
|
+
def initialize(name:, type:, xml_name:, kind:,
|
|
14
|
+
collection: false, default: nil,
|
|
15
|
+
documentation: nil, initialize_empty: false,
|
|
16
|
+
render_default: false, render_empty: false)
|
|
17
|
+
@name = name
|
|
18
|
+
@type = type
|
|
19
|
+
@xml_name = xml_name
|
|
20
|
+
@kind = kind
|
|
21
|
+
@collection = collection
|
|
22
|
+
@default = default
|
|
23
|
+
@documentation = documentation
|
|
24
|
+
@initialize_empty = initialize_empty
|
|
25
|
+
@render_default = render_default
|
|
26
|
+
@render_empty = render_empty
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Model
|
|
5
|
+
module Schema
|
|
6
|
+
module Definitions
|
|
7
|
+
# A `choice do ... end` block. Alternatives may be Attribute,
|
|
8
|
+
# Sequence, or nested Choice specs.
|
|
9
|
+
class Choice
|
|
10
|
+
attr_accessor :alternatives, :header
|
|
11
|
+
|
|
12
|
+
def initialize(alternatives:, header:)
|
|
13
|
+
@alternatives = alternatives
|
|
14
|
+
@header = header
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Model
|
|
5
|
+
module Schema
|
|
6
|
+
module Definitions
|
|
7
|
+
# Facet container for restricted simple types. Every field is
|
|
8
|
+
# optional; nil means "facet not present".
|
|
9
|
+
class Facet
|
|
10
|
+
attr_accessor :min_inclusive, :max_inclusive,
|
|
11
|
+
:min_exclusive, :max_exclusive,
|
|
12
|
+
:pattern, :enumerations,
|
|
13
|
+
:min_length, :max_length, :length,
|
|
14
|
+
:total_digits, :fraction_digits,
|
|
15
|
+
:white_space
|
|
16
|
+
|
|
17
|
+
def initialize(min_inclusive: nil, max_inclusive: nil,
|
|
18
|
+
min_exclusive: nil, max_exclusive: nil,
|
|
19
|
+
pattern: nil, enumerations: nil,
|
|
20
|
+
min_length: nil, max_length: nil, length: nil,
|
|
21
|
+
total_digits: nil, fraction_digits: nil,
|
|
22
|
+
white_space: nil)
|
|
23
|
+
@min_inclusive = min_inclusive
|
|
24
|
+
@max_inclusive = max_inclusive
|
|
25
|
+
@min_exclusive = min_exclusive
|
|
26
|
+
@max_exclusive = max_exclusive
|
|
27
|
+
@pattern = pattern
|
|
28
|
+
@enumerations = enumerations
|
|
29
|
+
@min_length = min_length
|
|
30
|
+
@max_length = max_length
|
|
31
|
+
@length = length
|
|
32
|
+
@total_digits = total_digits
|
|
33
|
+
@fraction_digits = fraction_digits
|
|
34
|
+
@white_space = white_space
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Model
|
|
5
|
+
module Schema
|
|
6
|
+
module Definitions
|
|
7
|
+
# An inline `<xs:group ref="..."/>` reference appearing as a
|
|
8
|
+
# member inside a Choice / Sequence / Model. Renders as
|
|
9
|
+
# `import_model_attributes :name` in the attribute-decl block
|
|
10
|
+
# and `import_model_mappings :name` in the xml-mapping block.
|
|
11
|
+
# name is the snake_case symbol form (without leading colon).
|
|
12
|
+
class GroupImport
|
|
13
|
+
attr_accessor :name
|
|
14
|
+
|
|
15
|
+
def initialize(name:)
|
|
16
|
+
@name = name
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Model
|
|
5
|
+
module Schema
|
|
6
|
+
module Definitions
|
|
7
|
+
# Stateless traversal helpers for the heterogeneous `members`
|
|
8
|
+
# array of a Definitions::Model. Definitions stay as pure data;
|
|
9
|
+
# the walk logic lives here so consumers (XmlCompiler /
|
|
10
|
+
# RngCompiler dependency collection, future visitors) don't each
|
|
11
|
+
# re-implement the same case-dispatch.
|
|
12
|
+
module MemberWalk
|
|
13
|
+
module_function
|
|
14
|
+
|
|
15
|
+
# Yields every Attribute leaf, recursing into Sequence members
|
|
16
|
+
# and Choice alternatives. Returns an Enumerator when called
|
|
17
|
+
# without a block.
|
|
18
|
+
def each_attribute(members, &block)
|
|
19
|
+
return enum_for(:each_attribute, members) unless block
|
|
20
|
+
|
|
21
|
+
members.each do |member|
|
|
22
|
+
case member
|
|
23
|
+
when Attribute then yield member
|
|
24
|
+
when Sequence then each_attribute(member.members, &block)
|
|
25
|
+
when Choice then each_attribute(member.alternatives, &block)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Model
|
|
5
|
+
module Schema
|
|
6
|
+
module Definitions
|
|
7
|
+
# One generated Lutaml::Model::Serializable subclass.
|
|
8
|
+
class Model
|
|
9
|
+
attr_accessor :class_name, :xml_root, :members,
|
|
10
|
+
:parent_class, :namespace_class_name,
|
|
11
|
+
:mixed, :text_content, :imports,
|
|
12
|
+
:documentation, :simple_content, :required_files,
|
|
13
|
+
:module_wrappable, :lazy_register
|
|
14
|
+
|
|
15
|
+
def initialize(class_name:, xml_root:, members: [],
|
|
16
|
+
parent_class: "Lutaml::Model::Serializable",
|
|
17
|
+
namespace_class_name: nil,
|
|
18
|
+
mixed: false, text_content: false,
|
|
19
|
+
imports: [], documentation: nil,
|
|
20
|
+
simple_content: nil, required_files: [],
|
|
21
|
+
module_wrappable: true, lazy_register: false)
|
|
22
|
+
@class_name = class_name
|
|
23
|
+
@xml_root = xml_root
|
|
24
|
+
@members = members
|
|
25
|
+
@parent_class = parent_class
|
|
26
|
+
@namespace_class_name = namespace_class_name
|
|
27
|
+
@mixed = mixed
|
|
28
|
+
@text_content = text_content
|
|
29
|
+
@imports = imports
|
|
30
|
+
@documentation = documentation
|
|
31
|
+
@simple_content = simple_content
|
|
32
|
+
@required_files = required_files
|
|
33
|
+
@module_wrappable = module_wrappable
|
|
34
|
+
@lazy_register = lazy_register
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Model
|
|
5
|
+
module Schema
|
|
6
|
+
module Definitions
|
|
7
|
+
# An XML namespace declaration (subclass of
|
|
8
|
+
# Lutaml::Xml::W3c::XmlNamespace).
|
|
9
|
+
class Namespace
|
|
10
|
+
attr_accessor :class_name, :uri, :prefix_default, :element_form_default
|
|
11
|
+
|
|
12
|
+
def initialize(class_name:, uri:, prefix_default: nil,
|
|
13
|
+
element_form_default: nil)
|
|
14
|
+
@class_name = class_name
|
|
15
|
+
@uri = uri
|
|
16
|
+
@prefix_default = prefix_default
|
|
17
|
+
@element_form_default = element_form_default
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Model
|
|
5
|
+
module Schema
|
|
6
|
+
module Definitions
|
|
7
|
+
# Restricted simple type — `class X < Lutaml::Model::Type::Y`
|
|
8
|
+
# with a cast body that applies facets and delegates to super.
|
|
9
|
+
class RestrictedType
|
|
10
|
+
attr_accessor :class_name, :parent_class, :facets,
|
|
11
|
+
:transform_facet, :required_files,
|
|
12
|
+
:keep_register_when_namespaced, :namespace_class_name
|
|
13
|
+
|
|
14
|
+
def initialize(class_name:, facets:, parent_class: nil,
|
|
15
|
+
transform_facet: nil, required_files: [],
|
|
16
|
+
keep_register_when_namespaced: false,
|
|
17
|
+
namespace_class_name: nil)
|
|
18
|
+
@class_name = class_name
|
|
19
|
+
@parent_class = parent_class
|
|
20
|
+
@facets = facets
|
|
21
|
+
@transform_facet = transform_facet
|
|
22
|
+
@required_files = required_files
|
|
23
|
+
@keep_register_when_namespaced = keep_register_when_namespaced
|
|
24
|
+
@namespace_class_name = namespace_class_name
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Model
|
|
5
|
+
module Schema
|
|
6
|
+
module Definitions
|
|
7
|
+
# A `sequence do ... end` group. Transparent in attribute
|
|
8
|
+
# declarations; wraps with `sequence do` only in xml mappings.
|
|
9
|
+
class Sequence
|
|
10
|
+
attr_accessor :members
|
|
11
|
+
|
|
12
|
+
def initialize(members:)
|
|
13
|
+
@members = members
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Model
|
|
5
|
+
module Schema
|
|
6
|
+
module Definitions
|
|
7
|
+
# XSD-only: a complex type with simple content (a typed text
|
|
8
|
+
# body plus optional decorating attributes). The renderer emits
|
|
9
|
+
# `attribute :content, :<base_type>` and any additional
|
|
10
|
+
# attributes after the regular members. RNG leaves this nil.
|
|
11
|
+
class SimpleContent
|
|
12
|
+
attr_accessor :base_class, :additional_attributes
|
|
13
|
+
|
|
14
|
+
def initialize(base_class:, additional_attributes: [])
|
|
15
|
+
@base_class = base_class
|
|
16
|
+
@additional_attributes = additional_attributes
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Model
|
|
5
|
+
module Schema
|
|
6
|
+
module Definitions
|
|
7
|
+
# XSD-only value transform applied during cast. The expression
|
|
8
|
+
# is raw Ruby emitted as `value = <expression>` before super.
|
|
9
|
+
# E.g. "value.gsub(/[\\r\\n\\t]/, ' ')" or "value.upcase". RNG
|
|
10
|
+
# leaves this nil.
|
|
11
|
+
class TransformFacet
|
|
12
|
+
attr_accessor :expression
|
|
13
|
+
|
|
14
|
+
def initialize(expression:)
|
|
15
|
+
@expression = expression
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Model
|
|
5
|
+
module Schema
|
|
6
|
+
module Definitions
|
|
7
|
+
# Reference to a Ruby type used in a generated attribute.
|
|
8
|
+
# kind ∈ {:symbol, :class_ref, :w3c}.
|
|
9
|
+
class TypeRef
|
|
10
|
+
attr_accessor :kind, :value
|
|
11
|
+
|
|
12
|
+
def initialize(kind:, value:)
|
|
13
|
+
@kind = kind
|
|
14
|
+
@value = value
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Model
|
|
5
|
+
module Schema
|
|
6
|
+
module Definitions
|
|
7
|
+
# Union type — Lutaml::Model::Type::Value subclass whose cast
|
|
8
|
+
# tries each member type and returns the first that parses.
|
|
9
|
+
# cast_strategy ∈ {:resolve_type, :class_refs}.
|
|
10
|
+
class UnionType
|
|
11
|
+
attr_accessor :class_name, :members, :cast_strategy, :required_files,
|
|
12
|
+
:lazy_register, :keep_register_when_namespaced
|
|
13
|
+
|
|
14
|
+
def initialize(class_name:, members:, cast_strategy:,
|
|
15
|
+
required_files: [], lazy_register: false,
|
|
16
|
+
keep_register_when_namespaced: false)
|
|
17
|
+
@class_name = class_name
|
|
18
|
+
@members = members
|
|
19
|
+
@cast_strategy = cast_strategy
|
|
20
|
+
@required_files = required_files
|
|
21
|
+
@lazy_register = lazy_register
|
|
22
|
+
@keep_register_when_namespaced = keep_register_when_namespaced
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Model
|
|
5
|
+
module Schema
|
|
6
|
+
module Definitions
|
|
7
|
+
# XML root directive of a generated class.
|
|
8
|
+
# kind ∈ {:element, :type_name, :fragment}. name is nil for :fragment.
|
|
9
|
+
class XmlRoot
|
|
10
|
+
attr_accessor :kind, :name
|
|
11
|
+
|
|
12
|
+
def initialize(kind:, name: nil)
|
|
13
|
+
@kind = kind
|
|
14
|
+
@name = name
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Model
|
|
5
|
+
module Schema
|
|
6
|
+
module Definitions
|
|
7
|
+
autoload :GroupImport, "#{__dir__}/definitions/group_import"
|
|
8
|
+
autoload :MemberWalk, "#{__dir__}/definitions/member_walk"
|
|
9
|
+
autoload :TypeRef, "#{__dir__}/definitions/type_ref"
|
|
10
|
+
autoload :XmlRoot, "#{__dir__}/definitions/xml_root"
|
|
11
|
+
autoload :Attribute, "#{__dir__}/definitions/attribute"
|
|
12
|
+
autoload :Choice, "#{__dir__}/definitions/choice"
|
|
13
|
+
autoload :Sequence, "#{__dir__}/definitions/sequence"
|
|
14
|
+
autoload :Facet, "#{__dir__}/definitions/facet"
|
|
15
|
+
autoload :TransformFacet, "#{__dir__}/definitions/transform_facet"
|
|
16
|
+
autoload :SimpleContent, "#{__dir__}/definitions/simple_content"
|
|
17
|
+
autoload :Model, "#{__dir__}/definitions/model"
|
|
18
|
+
autoload :RestrictedType, "#{__dir__}/definitions/restricted_type"
|
|
19
|
+
autoload :UnionType, "#{__dir__}/definitions/union_type"
|
|
20
|
+
autoload :Namespace, "#{__dir__}/definitions/namespace"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
|
|
5
|
+
module Lutaml
|
|
6
|
+
module Model
|
|
7
|
+
module Schema
|
|
8
|
+
# Writes a CompiledOutput to disk: per-class .rb files in a module
|
|
9
|
+
# subdirectory plus a central `*_registry.rb` (when a module
|
|
10
|
+
# namespace is set). Shared between XSD and RNG compilers.
|
|
11
|
+
#
|
|
12
|
+
# The `registry_generator:` parameter selects which RegistryGenerator
|
|
13
|
+
# subclass to use (XSD has a 2-phase template; RNG uses the default).
|
|
14
|
+
class FileWriter
|
|
15
|
+
def self.write(output, dir, registry_generator: RegistryGenerator)
|
|
16
|
+
new(output, dir, registry_generator).write
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def initialize(output, dir, registry_generator)
|
|
20
|
+
@output = output
|
|
21
|
+
@dir = dir
|
|
22
|
+
@registry_generator = registry_generator
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def write
|
|
26
|
+
if @output.module_namespace
|
|
27
|
+
write_namespaced
|
|
28
|
+
else
|
|
29
|
+
write_flat
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def write_namespaced
|
|
36
|
+
module_path = @output.module_namespace.split("::").map(&:downcase).join("/")
|
|
37
|
+
full_dir = File.join(@dir, module_path)
|
|
38
|
+
FileUtils.mkdir_p(full_dir)
|
|
39
|
+
|
|
40
|
+
registry = @registry_generator.generate(
|
|
41
|
+
@output.models,
|
|
42
|
+
module_namespace: @output.module_namespace,
|
|
43
|
+
register_id: @output.register_id,
|
|
44
|
+
namespaces: @output.namespaces,
|
|
45
|
+
)
|
|
46
|
+
if registry
|
|
47
|
+
registry_name = module_path.split("/").last
|
|
48
|
+
File.write(File.join(@dir, "#{registry_name}_registry.rb"), registry)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
write_sources_to(full_dir)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def write_flat
|
|
55
|
+
FileUtils.mkdir_p(@dir)
|
|
56
|
+
write_sources_to(@dir)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def write_sources_to(target_dir)
|
|
60
|
+
@output.sources.each { |name, src| write_source(target_dir, name, src) }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def write_source(target_dir, name, src)
|
|
64
|
+
file_name = Utils.snake_case(Utils.last_of_split(name.to_s))
|
|
65
|
+
File.write(File.join(target_dir, "#{file_name}.rb"), src)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -19,9 +19,23 @@ module Lutaml
|
|
|
19
19
|
def process_attributes(collection, klass)
|
|
20
20
|
register = extract_register_from(klass)
|
|
21
21
|
klass.attributes.each_value do |attribute|
|
|
22
|
-
|
|
22
|
+
if attribute.union?
|
|
23
|
+
process_union_members(collection, attribute)
|
|
24
|
+
elsif attribute.serializable?(register)
|
|
25
|
+
process_attribute(collection, attribute, register)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# A union attribute's type is Type::Union, not a model, so collect
|
|
31
|
+
# definitions for each of its Serializable members directly (a
|
|
32
|
+
# union schema emits a $ref per model member).
|
|
33
|
+
def process_union_members(collection, attribute)
|
|
34
|
+
attribute.union_member_types.each do |member|
|
|
35
|
+
next unless member.is_a?(::Class) &&
|
|
36
|
+
member.include?(Lutaml::Model::Serialize)
|
|
23
37
|
|
|
24
|
-
|
|
38
|
+
collection.merge(DefinitionsCollection.from_class(member))
|
|
25
39
|
end
|
|
26
40
|
end
|
|
27
41
|
|
|
@@ -24,6 +24,8 @@ module Lutaml
|
|
|
24
24
|
inside_collection = options.fetch(:inside_collection, false)
|
|
25
25
|
if attr.collection? && !inside_collection
|
|
26
26
|
collection_schema(attr)
|
|
27
|
+
elsif attr.union?
|
|
28
|
+
union_schema(attr, include_null: include_null)
|
|
27
29
|
elsif attr.serializable?(register) && polymorphic?(attr)
|
|
28
30
|
polymorphic_schema(attr)
|
|
29
31
|
elsif attr.serializable?(register)
|
|
@@ -55,6 +57,22 @@ module Lutaml
|
|
|
55
57
|
schema["maxItems"] = range.end if range.end
|
|
56
58
|
end
|
|
57
59
|
|
|
60
|
+
# Union: anyOf (valid if the value matches at least one member), not
|
|
61
|
+
# oneOf (which would wrongly demand exactly one). A null branch is
|
|
62
|
+
# added for nullable attributes, mirroring primitive_schema, so an
|
|
63
|
+
# optional union still accepts null.
|
|
64
|
+
def union_schema(attr, include_null: true)
|
|
65
|
+
members = attr.union_member_types.map do |member|
|
|
66
|
+
if member.include?(Lutaml::Model::Serialize)
|
|
67
|
+
Ref.new(member).to_schema
|
|
68
|
+
else
|
|
69
|
+
{ "type" => get_type(member) }
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
members << { "type" => "null" } if include_null
|
|
73
|
+
{ "anyOf" => members }.merge(get_constraints(attr))
|
|
74
|
+
end
|
|
75
|
+
|
|
58
76
|
def polymorphic_schema(attr)
|
|
59
77
|
ref_schemas = attr.options[:polymorphic].map do |type|
|
|
60
78
|
Ref.new(type).to_schema
|
|
@@ -82,10 +100,9 @@ module Lutaml
|
|
|
82
100
|
constraints["pattern"] = attr.pattern.source if attr.pattern
|
|
83
101
|
|
|
84
102
|
# Add default value
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
constraints["default"] = resolver.default
|
|
103
|
+
resolved = attr.default_value(register, nil)
|
|
104
|
+
unless Lutaml::Model::Utils.uninitialized?(resolved)
|
|
105
|
+
constraints["default"] = attr.cast_value(resolved, register)
|
|
89
106
|
end
|
|
90
107
|
|
|
91
108
|
# Add enumeration values
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Model
|
|
5
|
+
module Schema
|
|
6
|
+
# Stateless helpers for emitting `module Foo ... end` wrappers around
|
|
7
|
+
# generated code, given an Array of module-name segments.
|
|
8
|
+
#
|
|
9
|
+
# Both helpers return a single newline-terminated block (no trailing
|
|
10
|
+
# newline) so ERB templates should use `<%= ... -%>` to avoid
|
|
11
|
+
# doubling the line break.
|
|
12
|
+
module ModuleNesting
|
|
13
|
+
module_function
|
|
14
|
+
|
|
15
|
+
def opening(modules)
|
|
16
|
+
return "" if modules.empty?
|
|
17
|
+
|
|
18
|
+
modules.map.with_index { |m, i| "#{' ' * i}module #{m}\n" }.join
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def closing(modules)
|
|
22
|
+
return "" if modules.empty?
|
|
23
|
+
|
|
24
|
+
modules.reverse.map.with_index do |_m, i|
|
|
25
|
+
"#{' ' * (modules.size - i - 1)}end\n"
|
|
26
|
+
end.join
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|