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
data/lib/lutaml/toml.rb
CHANGED
|
@@ -30,7 +30,7 @@ Lutaml::Model::FormatRegistry.register(
|
|
|
30
30
|
adapter_class: nil,
|
|
31
31
|
transformer: Lutaml::Toml::Adapter::Transform,
|
|
32
32
|
key_value: true,
|
|
33
|
-
adapter_options: if Lutaml::Model
|
|
33
|
+
adapter_options: if Lutaml::Model.opal?
|
|
34
34
|
nil
|
|
35
35
|
else
|
|
36
36
|
{
|
data/lib/lutaml/xml/adapter.rb
CHANGED
|
@@ -13,11 +13,11 @@ module Lutaml
|
|
|
13
13
|
autoload :NamespaceUriCollector,
|
|
14
14
|
"#{__dir__}/adapter/namespace_uri_collector"
|
|
15
15
|
autoload :OgaAdapter, "#{__dir__}/adapter/oga_adapter"
|
|
16
|
+
autoload :RexmlAdapter, "#{__dir__}/adapter/rexml_adapter"
|
|
16
17
|
Lutaml::Model::RuntimeCompatibility.autoload_native(
|
|
17
18
|
self,
|
|
18
19
|
NokogiriAdapter: "#{__dir__}/adapter/nokogiri_adapter",
|
|
19
20
|
OxAdapter: "#{__dir__}/adapter/ox_adapter",
|
|
20
|
-
RexmlAdapter: "#{__dir__}/adapter/rexml_adapter",
|
|
21
21
|
)
|
|
22
22
|
end
|
|
23
23
|
end
|
|
@@ -13,7 +13,7 @@ module Lutaml
|
|
|
13
13
|
# @param _adapter [String] The adapter format name ("xml")
|
|
14
14
|
# @param type [String] The normalized type name (e.g., "nokogiri_adapter")
|
|
15
15
|
def self.load_adapter_file(_adapter, type)
|
|
16
|
-
adapter_path = if Lutaml::Model
|
|
16
|
+
adapter_path = if Lutaml::Model.opal?
|
|
17
17
|
"lutaml/xml/adapter/#{type}"
|
|
18
18
|
else
|
|
19
19
|
File.join(File.dirname(__FILE__), "adapter", type)
|
data/lib/lutaml/xml/builder.rb
CHANGED
|
@@ -6,11 +6,11 @@ module Lutaml
|
|
|
6
6
|
module Builder
|
|
7
7
|
autoload :Base, "#{__dir__}/builder/base"
|
|
8
8
|
autoload :Oga, "#{__dir__}/builder/oga"
|
|
9
|
+
autoload :Rexml, "#{__dir__}/builder/rexml"
|
|
9
10
|
Lutaml::Model::RuntimeCompatibility.autoload_native(
|
|
10
11
|
self,
|
|
11
12
|
Nokogiri: "#{__dir__}/builder/nokogiri",
|
|
12
13
|
Ox: "#{__dir__}/builder/ox",
|
|
13
|
-
Rexml: "#{__dir__}/builder/rexml",
|
|
14
14
|
)
|
|
15
15
|
end
|
|
16
16
|
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Xml
|
|
5
|
+
module Error
|
|
6
|
+
# Collected when a model's generated XML does not conform to the
|
|
7
|
+
# XSD schema configured via the `validate_xml_with` macro.
|
|
8
|
+
#
|
|
9
|
+
# One instance represents one schema violation reported by the
|
|
10
|
+
# validating parser, so Lutaml::Model::ValidationError#error_messages
|
|
11
|
+
# lists each violation individually.
|
|
12
|
+
#
|
|
13
|
+
# Not to be confused with Schema::Xsd::SchemaValidationError, which
|
|
14
|
+
# reports problems in an XSD document itself.
|
|
15
|
+
class SchemaValidationError < XmlError
|
|
16
|
+
attr_reader :schema_path
|
|
17
|
+
|
|
18
|
+
def initialize(message, schema_path)
|
|
19
|
+
@schema_path = schema_path
|
|
20
|
+
super("XML does not conform to schema #{schema_path}: #{message}")
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -708,7 +708,9 @@ _effective_register)
|
|
|
708
708
|
base_cast_options[:resolved_type] = attr_type
|
|
709
709
|
|
|
710
710
|
children.each do |child|
|
|
711
|
-
if !rule_has_custom_method &&
|
|
711
|
+
if !rule_has_custom_method &&
|
|
712
|
+
(attr_type_is_serializable ||
|
|
713
|
+
(attr&.union? && ::Lutaml::Model::Type::Union.xml_structured?(child)))
|
|
712
714
|
# Performance: Build cast_options efficiently (dup + []= cheaper than merge)
|
|
713
715
|
cast_options = if (child_namespace_uri = child.namespace_uri)
|
|
714
716
|
ns_type = attr.type_with_namespace(effective_register,
|
|
@@ -984,6 +986,8 @@ effective_register = lutaml_register)
|
|
|
984
986
|
# @return [Boolean] true if a matching child likely exists
|
|
985
987
|
def child_matches_rule?(rule, child_names_set, default_namespace)
|
|
986
988
|
rule_names = rule.namespaced_names(default_namespace)
|
|
989
|
+
# Array#intersect? requires an Array argument; child_names_set may
|
|
990
|
+
# be a Set, so use the block form which works for any includable.
|
|
987
991
|
rule_names.any? { |rn| child_names_set.include?(rn) } ||
|
|
988
992
|
child_names_set.include?(rule.name.to_s)
|
|
989
993
|
end
|
|
@@ -31,6 +31,11 @@ module Lutaml
|
|
|
31
31
|
|
|
32
32
|
def self.generate_attributes(xml, klass, register)
|
|
33
33
|
klass.attributes.each do |name, attr|
|
|
34
|
+
if attr.union?
|
|
35
|
+
raise Lutaml::Model::UnionSchemaUnsupportedError.new(name,
|
|
36
|
+
"RelaxNG")
|
|
37
|
+
end
|
|
38
|
+
|
|
34
39
|
attr_type = attr.type(register)
|
|
35
40
|
if attr_type <= Lutaml::Model::Serialize
|
|
36
41
|
xml.ref(name: attr_type.name)
|
|
@@ -409,6 +409,10 @@ attr_name)
|
|
|
409
409
|
|
|
410
410
|
def self.get_attribute_xsd_type(attr, attr_type, register,
|
|
411
411
|
_mapping_rule = nil)
|
|
412
|
+
if attr.union?
|
|
413
|
+
raise Lutaml::Model::UnionSchemaUnsupportedError.new(attr.name, "XSD")
|
|
414
|
+
end
|
|
415
|
+
|
|
412
416
|
# 1. Check for deprecated attribute-level xsd_type override
|
|
413
417
|
return attr.options[:xsd_type] if attr.options[:xsd_type]
|
|
414
418
|
|
|
@@ -44,6 +44,50 @@ module Lutaml
|
|
|
44
44
|
@namespace_class&.prefix_default
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
+
# Configure one or more XSD schemas that the model's generated
|
|
48
|
+
# XML must conform to. Checked during validate/validate!
|
|
49
|
+
# (issue #264). Repeated calls (including in subclasses) append.
|
|
50
|
+
#
|
|
51
|
+
# Relative paths resolve against the file that declares the macro,
|
|
52
|
+
# so a model works regardless of the process working directory.
|
|
53
|
+
#
|
|
54
|
+
# @param paths [Array<String, Pathname>] paths to XSD schema files
|
|
55
|
+
def validate_xml_with(*paths)
|
|
56
|
+
base = File.dirname(caller_locations(1, 1).first.path)
|
|
57
|
+
@own_xml_schema_paths = own_xml_schema_paths +
|
|
58
|
+
paths.flatten.map { |path| File.expand_path(path.to_s, base) }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# All configured XSD schema paths, parent-first: inherited paths
|
|
62
|
+
# come before paths declared on this class.
|
|
63
|
+
#
|
|
64
|
+
# @return [Array<String>]
|
|
65
|
+
def xml_schema_paths
|
|
66
|
+
inherited = if superclass.respond_to?(:xml_schema_paths)
|
|
67
|
+
superclass.xml_schema_paths
|
|
68
|
+
else
|
|
69
|
+
[]
|
|
70
|
+
end
|
|
71
|
+
inherited + own_xml_schema_paths
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Validate a raw XML string against the configured schemas without
|
|
75
|
+
# instantiating a model — the explicit input-side counterpart to
|
|
76
|
+
# the validate/validate! output-side check.
|
|
77
|
+
#
|
|
78
|
+
# @param xml [String] an XML document
|
|
79
|
+
# @return [Array<Lutaml::Xml::Error::SchemaValidationError>]
|
|
80
|
+
def validate_xml(xml)
|
|
81
|
+
XsdValidator.validate(xml, xml_schema_paths)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# @raise [Lutaml::Model::ValidationError] when the XML does not
|
|
85
|
+
# conform to every configured schema
|
|
86
|
+
def validate_xml!(xml)
|
|
87
|
+
errors = validate_xml(xml)
|
|
88
|
+
raise Lutaml::Model::ValidationError.new(errors) if errors.any?
|
|
89
|
+
end
|
|
90
|
+
|
|
47
91
|
# Override choice to set format: :xml so Choice knows which format it belongs to.
|
|
48
92
|
def choice(min: 1, max: 1, format: :xml, &)
|
|
49
93
|
super
|
|
@@ -204,6 +248,10 @@ module Lutaml
|
|
|
204
248
|
|
|
205
249
|
private
|
|
206
250
|
|
|
251
|
+
def own_xml_schema_paths
|
|
252
|
+
@own_xml_schema_paths ||= []
|
|
253
|
+
end
|
|
254
|
+
|
|
207
255
|
# Handle XML mapping class inheritance pattern (e.g., `xml SomeMapping`)
|
|
208
256
|
#
|
|
209
257
|
# When a mapping class is passed, inherit mappings from it directly.
|
|
@@ -263,6 +263,19 @@ module Lutaml
|
|
|
263
263
|
self.class.mappings_for(:xml, register)&.element_sequence
|
|
264
264
|
end
|
|
265
265
|
|
|
266
|
+
# Append XSD schema errors (validate_xml_with macro) to the
|
|
267
|
+
# standard validation errors. No-op unless schemas are configured.
|
|
268
|
+
#
|
|
269
|
+
# @see Lutaml::Model::Validation#validate
|
|
270
|
+
def validate(register: Lutaml::Model::Config.default_register)
|
|
271
|
+
errors = super
|
|
272
|
+
return errors if self.class.xml_schema_paths.empty?
|
|
273
|
+
|
|
274
|
+
errors.concat(
|
|
275
|
+
self.class.validate_xml(to_xml(register: register)),
|
|
276
|
+
)
|
|
277
|
+
end
|
|
278
|
+
|
|
266
279
|
private
|
|
267
280
|
|
|
268
281
|
# Build declaration plan from stored element reference (lazy mode).
|
|
@@ -28,13 +28,22 @@ register_id, register)
|
|
|
28
28
|
# class AND the value is not a nil/empty marker from collection handlers.
|
|
29
29
|
# Nil and empty string markers should create simple elements, not attempt
|
|
30
30
|
# nested model transformation.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
# Union: dispatch on the value's own class (stateless). A model member
|
|
32
|
+
# becomes a nested element; a scalar member a simple value element.
|
|
33
|
+
union = Lutaml::Model::Type::Union.rule?(rule)
|
|
34
|
+
|
|
35
|
+
is_nested_model = !value.nil? &&
|
|
36
|
+
!(value.is_a?(String) && value.empty?) &&
|
|
37
|
+
if union
|
|
38
|
+
value.is_a?(Lutaml::Model::Serialize)
|
|
39
|
+
else
|
|
40
|
+
rule.attribute_type.is_a?(Class) &&
|
|
41
|
+
rule.attribute_type < Lutaml::Model::Serialize
|
|
42
|
+
end
|
|
35
43
|
|
|
36
44
|
if is_nested_model
|
|
37
|
-
create_nested_model_element(rule, value, options, register
|
|
45
|
+
create_nested_model_element(rule, value, options, register,
|
|
46
|
+
union: union)
|
|
38
47
|
else
|
|
39
48
|
create_simple_value_element(rule, value, options, model_class,
|
|
40
49
|
register_id)
|
|
@@ -119,7 +128,8 @@ parent_element_form_default)
|
|
|
119
128
|
# @param options [Hash] Options
|
|
120
129
|
# @param register [Register, nil] The register
|
|
121
130
|
# @return [::Lutaml::Xml::DataModel::XmlElement] The created element
|
|
122
|
-
def create_nested_model_element(rule, value, options, register
|
|
131
|
+
def create_nested_model_element(rule, value, options, register,
|
|
132
|
+
union: false)
|
|
123
133
|
# Resolve polymorphic configuration
|
|
124
134
|
polymorphic_config = rule.options[:polymorphic]
|
|
125
135
|
is_polymorphic = polymorphic_config.is_a?(::Hash) ? !polymorphic_config.empty? : !!polymorphic_config
|
|
@@ -130,8 +140,11 @@ parent_element_form_default)
|
|
|
130
140
|
actual_class = resolve_polymorphic_class(rule, value, is_polymorphic,
|
|
131
141
|
is_polymorphic_subtype)
|
|
132
142
|
|
|
133
|
-
# Get transformation for the actual class
|
|
134
|
-
|
|
143
|
+
# Get transformation for the actual class. Unions resolve the member
|
|
144
|
+
# from the value's own class, like polymorphism.
|
|
145
|
+
child_transformation = if union
|
|
146
|
+
value.class.transformation_for(:xml, register)
|
|
147
|
+
elsif is_polymorphic || is_polymorphic_subtype
|
|
135
148
|
actual_class.transformation_for(:xml,
|
|
136
149
|
register)
|
|
137
150
|
else
|
|
@@ -81,6 +81,12 @@ model_class, register_id)
|
|
|
81
81
|
# @param options [Hash] Options
|
|
82
82
|
# @yield Block to create element for value
|
|
83
83
|
def apply_element_rule_single(parent:, rule:, value:, options:)
|
|
84
|
+
# raw: :element — value is a complete XML element string, inject directly
|
|
85
|
+
if rule.raw == :element
|
|
86
|
+
add_raw_element_fragments(parent, value)
|
|
87
|
+
return
|
|
88
|
+
end
|
|
89
|
+
|
|
84
90
|
# Extract parent's namespace info for element_form_default inheritance
|
|
85
91
|
parent_ns_class = parent.namespace_class
|
|
86
92
|
# Only pass element_form_default VALUE if it was explicitly set
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "concurrent" unless RUBY_ENGINE == "opal"
|
|
4
|
+
|
|
5
|
+
module Lutaml
|
|
6
|
+
module Xml
|
|
7
|
+
# Validates a serialized XML string against an XSD schema file.
|
|
8
|
+
#
|
|
9
|
+
# Backs the `validate_xml_with` class macro (issue #264). Nokogiri is
|
|
10
|
+
# required lazily so models that do not use XSD validation keep working
|
|
11
|
+
# on platforms/adapters without it (Opal, ox-only installs).
|
|
12
|
+
#
|
|
13
|
+
# Compiled schemas are memoized by absolute path: the XSD files are
|
|
14
|
+
# constant for the life of the process, so each is read and compiled once
|
|
15
|
+
# and reused across every validate / validate_xml call.
|
|
16
|
+
#
|
|
17
|
+
# Not to be confused with Schema::Xsd::SchemaValidator, which checks
|
|
18
|
+
# that an XSD document itself is well-formed XSD.
|
|
19
|
+
module XsdValidator
|
|
20
|
+
# Concurrent (and a working Nokogiri) are unavailable under Opal, so
|
|
21
|
+
# the memoization store is only built off-Opal. validate raises a clear
|
|
22
|
+
# NotImplementedError under Opal before this store is ever consulted.
|
|
23
|
+
@schemas = ::Concurrent::Map.new unless RUBY_ENGINE == "opal"
|
|
24
|
+
|
|
25
|
+
# @param xml [String] an XML document
|
|
26
|
+
# @param schema_paths [Array<String>] paths to XSD schema files
|
|
27
|
+
# @return [Array<Error::SchemaValidationError>] one error per violation
|
|
28
|
+
def self.validate(xml, schema_paths)
|
|
29
|
+
paths = Array(schema_paths)
|
|
30
|
+
return [] if paths.empty?
|
|
31
|
+
|
|
32
|
+
if RUBY_ENGINE == "opal"
|
|
33
|
+
raise NotImplementedError,
|
|
34
|
+
"XSD schema validation (validate_xml_with) requires the " \
|
|
35
|
+
"nokogiri gem, which is not available under Opal."
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
ensure_nokogiri!
|
|
39
|
+
# Strict parsing: libxml2 recovery would otherwise repair malformed
|
|
40
|
+
# input and report it as schema-valid. NONET remains on by default.
|
|
41
|
+
document = ::Nokogiri::XML(xml, &:strict)
|
|
42
|
+
paths.flat_map do |path|
|
|
43
|
+
schema_for(path)
|
|
44
|
+
.validate(document)
|
|
45
|
+
.map { |error| Error::SchemaValidationError.new(error.message, path) }
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def self.schema_for(path)
|
|
50
|
+
@schemas.compute_if_absent(path) do
|
|
51
|
+
::Nokogiri::XML::Schema(File.read(path))
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def self.ensure_nokogiri!
|
|
56
|
+
require "nokogiri"
|
|
57
|
+
rescue LoadError
|
|
58
|
+
raise Error::XmlConfigurationError,
|
|
59
|
+
"XSD schema validation (validate_xml_with) requires the " \
|
|
60
|
+
"nokogiri gem; add `gem \"nokogiri\"` to your Gemfile"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private_class_method :ensure_nokogiri!, :schema_for
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
data/lib/lutaml/xml.rb
CHANGED
|
@@ -11,6 +11,15 @@ require_relative "model"
|
|
|
11
11
|
# XML requires moxml for parsing
|
|
12
12
|
require "moxml"
|
|
13
13
|
|
|
14
|
+
# Under Opal, moxml's autoloads don't fire (Opal ignores autoload).
|
|
15
|
+
# Pull in the eager-load boot that ships at lib/compat/opal/moxml_boot.rb
|
|
16
|
+
# so Moxml::Adapter::{Oga,Rexml} etc. are actually defined. Without
|
|
17
|
+
# this, `Config.adapter = :oga` later fails with
|
|
18
|
+
# `NameError: uninitialized constant Moxml::Adapter::Oga`.
|
|
19
|
+
if Lutaml::Model.opal?
|
|
20
|
+
require "moxml_boot"
|
|
21
|
+
end
|
|
22
|
+
|
|
14
23
|
module Lutaml
|
|
15
24
|
module Xml
|
|
16
25
|
# Error module for XML-specific errors
|
|
@@ -24,6 +33,8 @@ module Lutaml
|
|
|
24
33
|
"#{__dir__}/xml/error/xml_configuration_error"
|
|
25
34
|
autoload :NamespaceMismatchError,
|
|
26
35
|
"#{__dir__}/xml/error/namespace_mismatch_error"
|
|
36
|
+
autoload :SchemaValidationError,
|
|
37
|
+
"#{__dir__}/xml/error/schema_validation_error"
|
|
27
38
|
end
|
|
28
39
|
|
|
29
40
|
# XML Configuration modules
|
|
@@ -86,6 +97,7 @@ module Lutaml
|
|
|
86
97
|
autoload :CustomMethodWrapper,
|
|
87
98
|
"#{__dir__}/xml/transformation/custom_method_wrapper"
|
|
88
99
|
autoload :Transform, "#{__dir__}/xml/transform"
|
|
100
|
+
autoload :XsdValidator, "#{__dir__}/xml/xsd_validator"
|
|
89
101
|
autoload :Adapter, "#{__dir__}/xml/adapter"
|
|
90
102
|
autoload :XmlElement, "#{__dir__}/xml/xml_element"
|
|
91
103
|
autoload :AdapterElement, "#{__dir__}/xml/adapter_element"
|
|
@@ -143,12 +155,12 @@ module Lutaml
|
|
|
143
155
|
autoload :SharedDsl, "#{__dir__}/xml/shared_dsl"
|
|
144
156
|
|
|
145
157
|
autoload :Oga, "#{__dir__}/xml/oga"
|
|
158
|
+
autoload :Rexml, "#{__dir__}/xml/rexml"
|
|
146
159
|
Lutaml::Model::RuntimeCompatibility.autoload_native(
|
|
147
160
|
self,
|
|
148
161
|
NokogiriElement: "#{__dir__}/xml/nokogiri/element",
|
|
149
162
|
Nokogiri: "#{__dir__}/xml/nokogiri",
|
|
150
163
|
Ox: "#{__dir__}/xml/ox",
|
|
151
|
-
Rexml: "#{__dir__}/xml/rexml",
|
|
152
164
|
)
|
|
153
165
|
end
|
|
154
166
|
end
|
|
@@ -168,10 +180,15 @@ Lutaml::Model::FormatRegistry.register(
|
|
|
168
180
|
Ox::ParseError
|
|
169
181
|
REXML::ParseException
|
|
170
182
|
],
|
|
171
|
-
adapter_options: if Lutaml::Model
|
|
183
|
+
adapter_options: if Lutaml::Model.opal?
|
|
184
|
+
# Both Oga (vendored opal-oga fork, pure-Ruby lexer)
|
|
185
|
+
# and REXML (bundled stdlib gem, pure Ruby) work
|
|
186
|
+
# under Opal. Oga is the default because moxml's CI
|
|
187
|
+
# verifies it most thoroughly; REXML is selectable
|
|
188
|
+
# for callers that prefer a stdlib-only stack.
|
|
172
189
|
{
|
|
173
|
-
available: %i[rexml],
|
|
174
|
-
default: :
|
|
190
|
+
available: %i[oga rexml],
|
|
191
|
+
default: :oga,
|
|
175
192
|
}
|
|
176
193
|
else
|
|
177
194
|
{
|
|
@@ -210,18 +227,24 @@ Lutaml::Model::Serialize.prepend(
|
|
|
210
227
|
# to classes that included it before the prepend. Serializable includes
|
|
211
228
|
# Serialize during model boot, so make the XML instance API available on the
|
|
212
229
|
# concrete base class as well.
|
|
213
|
-
|
|
230
|
+
#
|
|
231
|
+
# Anonymous model classes (Class.new { include Serialize }) extend
|
|
232
|
+
# Serialize::ClassMethods rather than inheriting from Serializable, so the
|
|
233
|
+
# ModelImportExt override (root?) must land on ClassMethods too. The other
|
|
234
|
+
# two (FormatConversion, InstanceMethods) are already prepended unconditionally
|
|
235
|
+
# above, so Opal's no-double-prepend rule means we don't repeat them here.
|
|
236
|
+
if Lutaml::Model.opal?
|
|
214
237
|
Lutaml::Model::Serializable.singleton_class.prepend(
|
|
215
238
|
Lutaml::Xml::Serialization::ModelImportExt,
|
|
216
239
|
)
|
|
217
240
|
|
|
218
|
-
Lutaml::Model::Serializable.singleton_class.prepend(
|
|
219
|
-
Lutaml::Xml::Serialization::FormatConversion,
|
|
220
|
-
)
|
|
221
|
-
|
|
222
241
|
Lutaml::Model::Serializable.prepend(
|
|
223
242
|
Lutaml::Xml::Serialization::InstanceMethods,
|
|
224
243
|
)
|
|
244
|
+
|
|
245
|
+
Lutaml::Model::Serialize::ClassMethods.prepend(
|
|
246
|
+
Lutaml::Xml::Serialization::ModelImportExt,
|
|
247
|
+
)
|
|
225
248
|
end
|
|
226
249
|
|
|
227
250
|
# Register XML-specific attribute override warning names
|
|
@@ -240,7 +263,7 @@ Lutaml::Xml::Type::Serializers.register_all!
|
|
|
240
263
|
|
|
241
264
|
# Register XML schema methods
|
|
242
265
|
Lutaml::Model::Schema.register_method(:to_xsd) do |klass, options = {}|
|
|
243
|
-
if Lutaml::Model
|
|
266
|
+
if Lutaml::Model.opal?
|
|
244
267
|
raise NotImplementedError,
|
|
245
268
|
"XSD schema generation is not available under Opal."
|
|
246
269
|
end
|
|
@@ -249,7 +272,7 @@ Lutaml::Model::Schema.register_method(:to_xsd) do |klass, options = {}|
|
|
|
249
272
|
end
|
|
250
273
|
|
|
251
274
|
Lutaml::Model::Schema.register_method(:to_relaxng) do |klass, options = {}|
|
|
252
|
-
if Lutaml::Model
|
|
275
|
+
if Lutaml::Model.opal?
|
|
253
276
|
raise NotImplementedError,
|
|
254
277
|
"RELAX NG schema generation requires Nokogiri, " \
|
|
255
278
|
"which is not available under Opal."
|
|
@@ -259,7 +282,7 @@ Lutaml::Model::Schema.register_method(:to_relaxng) do |klass, options = {}|
|
|
|
259
282
|
end
|
|
260
283
|
|
|
261
284
|
Lutaml::Model::Schema.register_method(:from_xml) do |xml, options = {}|
|
|
262
|
-
if Lutaml::Model
|
|
285
|
+
if Lutaml::Model.opal?
|
|
263
286
|
raise NotImplementedError,
|
|
264
287
|
"XML schema compilation is not available under Opal."
|
|
265
288
|
end
|
|
@@ -267,6 +290,24 @@ Lutaml::Model::Schema.register_method(:from_xml) do |xml, options = {}|
|
|
|
267
290
|
Lutaml::Model::Schema::XmlCompiler.to_models(xml, options)
|
|
268
291
|
end
|
|
269
292
|
|
|
293
|
+
Lutaml::Model::Schema.register_method(:from_relaxng) do |rng, options = {}|
|
|
294
|
+
if Lutaml::Model::RuntimeCompatibility.opal?
|
|
295
|
+
raise NotImplementedError,
|
|
296
|
+
"RELAX NG schema compilation is not available under Opal."
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
Lutaml::Model::Schema::RngCompiler.to_models(rng, options)
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
Lutaml::Model::Schema.register_method(:from_rnc) do |rnc, options = {}|
|
|
303
|
+
if Lutaml::Model::RuntimeCompatibility.opal?
|
|
304
|
+
raise NotImplementedError,
|
|
305
|
+
"RNC schema compilation is not available under Opal."
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
Lutaml::Model::Schema::RncCompiler.to_models(rnc, options)
|
|
309
|
+
end
|
|
310
|
+
|
|
270
311
|
# Register XML namespace registry with GlobalContext
|
|
271
312
|
Lutaml::Model::GlobalContext.register_format_registry(
|
|
272
313
|
:xml, Lutaml::Xml::NamespaceClassRegistry.new
|
data/lutaml-model.gemspec
CHANGED
|
@@ -23,9 +23,11 @@ Gem::Specification.new do |spec|
|
|
|
23
23
|
# Specify which files should be added to the gem when it is released.
|
|
24
24
|
# The `git ls-files -z` loads the files in the
|
|
25
25
|
# RubyGem that have been added into git.
|
|
26
|
+
# vendor/ is excluded so the opal-oga / opal-ruby-ll submodule checkouts
|
|
27
|
+
# are not shipped in the published gem.
|
|
26
28
|
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
27
29
|
`git ls-files -z`.split("\x0").reject do |f|
|
|
28
|
-
f.match(%r{^(test|features)/})
|
|
30
|
+
f.match(%r{^(test|features|vendor/)/})
|
|
29
31
|
end
|
|
30
32
|
end
|
|
31
33
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
@@ -36,7 +38,7 @@ Gem::Specification.new do |spec|
|
|
|
36
38
|
spec.add_dependency "canon"
|
|
37
39
|
spec.add_dependency "concurrent-ruby"
|
|
38
40
|
spec.add_dependency "liquid", ">= 4.0", "< 6.0"
|
|
39
|
-
spec.add_dependency "moxml", "~> 0.1.
|
|
41
|
+
spec.add_dependency "moxml", "~> 0.1.25"
|
|
40
42
|
spec.add_dependency "ostruct"
|
|
41
43
|
spec.add_dependency "rubyzip", "~> 2.3"
|
|
42
44
|
spec.add_dependency "thor"
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
namespace a = "http://relaxng.org/ns/compatibility/annotations/1.0"
|
|
2
|
+
|
|
3
|
+
book =
|
|
4
|
+
element book {
|
|
5
|
+
attribute xml:lang { text }?,
|
|
6
|
+
[ a:defaultValue = "paperback" ]
|
|
7
|
+
attribute format { "hardcover" | "paperback" }?,
|
|
8
|
+
attribute anchor { xsd:ID }?,
|
|
9
|
+
attribute indent { text | "adaptive" }?,
|
|
10
|
+
title,
|
|
11
|
+
\list
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
title = element title { text }
|
|
15
|
+
\list = element list { item+ }
|
|
16
|
+
item = element item { text }
|
|
17
|
+
|
|
18
|
+
start |= book
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<grammar xmlns="http://relaxng.org/ns/structure/1.0">
|
|
3
|
+
<start>
|
|
4
|
+
<element name="addressBook">
|
|
5
|
+
<zeroOrMore>
|
|
6
|
+
<element name="card">
|
|
7
|
+
<ref name="cardContent"/>
|
|
8
|
+
</element>
|
|
9
|
+
</zeroOrMore>
|
|
10
|
+
</element>
|
|
11
|
+
</start>
|
|
12
|
+
<define name="cardContent">
|
|
13
|
+
<element name="name"><text/></element>
|
|
14
|
+
<element name="email"><text/></element>
|
|
15
|
+
</define>
|
|
16
|
+
</grammar>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!--
|
|
3
|
+
Exercises: <attribute>, <optional>, <oneOrMore>, <group>, <choice>,
|
|
4
|
+
<empty>, <data type="..."/>
|
|
5
|
+
-->
|
|
6
|
+
<grammar xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
|
|
7
|
+
<start>
|
|
8
|
+
<element name="book">
|
|
9
|
+
<attribute name="isbn"><data type="string"/></attribute>
|
|
10
|
+
<element name="title"><text/></element>
|
|
11
|
+
<optional>
|
|
12
|
+
<element name="subtitle"><text/></element>
|
|
13
|
+
</optional>
|
|
14
|
+
<oneOrMore>
|
|
15
|
+
<element name="author"><text/></element>
|
|
16
|
+
</oneOrMore>
|
|
17
|
+
<group>
|
|
18
|
+
<element name="publisher"><text/></element>
|
|
19
|
+
<element name="year"><data type="integer"/></element>
|
|
20
|
+
</group>
|
|
21
|
+
<choice>
|
|
22
|
+
<element name="hardcover"><empty/></element>
|
|
23
|
+
<element name="paperback"><empty/></element>
|
|
24
|
+
</choice>
|
|
25
|
+
</element>
|
|
26
|
+
</start>
|
|
27
|
+
</grammar>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<grammar xmlns="http://relaxng.org/ns/structure/1.0">
|
|
3
|
+
<start>
|
|
4
|
+
<element name="thing">
|
|
5
|
+
<attribute name="version">
|
|
6
|
+
<value>1.0</value>
|
|
7
|
+
</attribute>
|
|
8
|
+
<element name="name"><text/></element>
|
|
9
|
+
</element>
|
|
10
|
+
</start>
|
|
11
|
+
</grammar>
|