lutaml-model 0.8.16 → 0.8.17

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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/release.yml +0 -3
  3. data/.rubocop_todo.yml +70 -14
  4. data/docs/_guides/xml/namespace-semantics.adoc +2 -0
  5. data/docs/_guides/xml-namespace-qualification.adoc +142 -0
  6. data/docs/_tutorials/xml-element-attribute-namespace-guide.adoc +2 -0
  7. data/docs/_tutorials/xml-schema-primer-style-guide.adoc +2 -0
  8. data/docs/namespace-management.adoc +2 -0
  9. data/docs/xml-schema-qualification.md +6 -0
  10. data/lib/lutaml/jsonld.rb +1 -4
  11. data/lib/lutaml/model/choice.rb +34 -0
  12. data/lib/lutaml/model/compiled_rule.rb +7 -0
  13. data/lib/lutaml/model/version.rb +1 -1
  14. data/lib/lutaml/model.rb +1 -0
  15. data/lib/lutaml/{jsonld → rdf}/context.rb +1 -1
  16. data/lib/lutaml/{jsonld/transform.rb → rdf/linked_data_transform.rb} +33 -35
  17. data/lib/lutaml/{jsonld → rdf}/term_definition.rb +1 -1
  18. data/lib/lutaml/rdf.rb +3 -0
  19. data/lib/lutaml/xml/adapter_element.rb +2 -2
  20. data/lib/lutaml/xml/transformation/element_builder.rb +38 -23
  21. data/lib/lutaml/yamlld/adapter.rb +25 -0
  22. data/lib/lutaml/yamlld.rb +25 -0
  23. data/spec/lutaml/integration/multi_format_spec.rb +23 -0
  24. data/spec/lutaml/model/attribute_spec.rb +8 -1
  25. data/spec/lutaml/model/choice_restrict_spec.rb +225 -0
  26. data/spec/lutaml/model/custom_model_spec.rb +4 -4
  27. data/spec/lutaml/model/mixed_content_spec.rb +2 -2
  28. data/spec/lutaml/model/multiple_mapping_spec.rb +4 -4
  29. data/spec/lutaml/model/ordered_content_spec.rb +3 -3
  30. data/spec/lutaml/model/uninitialized_class_spec.rb +1 -1
  31. data/spec/lutaml/model/xsd_form_default_patterns_spec.rb +2 -2
  32. data/spec/lutaml/model/xsd_patterns_spec.rb +4 -4
  33. data/spec/lutaml/{jsonld → rdf}/context_spec.rb +2 -2
  34. data/spec/lutaml/{jsonld/transform_spec.rb → rdf/linked_data_transform_spec.rb} +15 -1
  35. data/spec/lutaml/{jsonld → rdf}/term_definition_spec.rb +2 -2
  36. data/spec/lutaml/turtle/transform_spec.rb +2 -2
  37. data/spec/lutaml/xml/enhanced_mapping_spec.rb +230 -1
  38. data/spec/lutaml/xml/mapping_spec.rb +4 -4
  39. data/spec/lutaml/xml/namespace_placement_spec.rb +11 -8
  40. data/spec/lutaml/xml/namespace_three_phase_spec.rb +1 -1
  41. data/spec/lutaml/xml/prefix_control_spec.rb +4 -4
  42. data/spec/lutaml/xml/serializable_namespace_spec.rb +6 -6
  43. data/spec/lutaml/xml/type_namespace_examples_spec.rb +1 -1
  44. data/spec/lutaml/xml/type_namespace_integration_spec.rb +3 -3
  45. data/spec/lutaml/yamlld/adapter_spec.rb +56 -0
  46. data/spec/lutaml/yamlld/registration_spec.rb +33 -0
  47. metadata +13 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0b419ac1cce55a5e56ca59dd217042047801d2420751f89ce0b875558e517f44
4
- data.tar.gz: ed7c6b7dcd749c35d7fa434d2adf4b9e036c7329afd80c2420ef75f57f65895f
3
+ metadata.gz: 8296b9832eb080f64ba1344b7fa929b179fedc1c4265d9743d6636cd408720f6
4
+ data.tar.gz: 533255e9702cd82fc93cacc78bf88fb90dba17b8543b5ecf22bb223eaacfa9a1
5
5
  SHA512:
6
- metadata.gz: c923ed6933f06ca678d4b76c19b46553f1196e0b30c0f202113a792619c91c0e6b3e3885963f4b910d101722d0cee22df8bf71c0c593fc7de4f0832d9062d87d
7
- data.tar.gz: cfc1cc06ebeb1affa7875ecc522d8625147f70ed81aa6ae2feb2b4debd92f652ce2cf565f9984e52170e65559a75f23139c85762df5018545b60187cb5974744
6
+ metadata.gz: 936d9c1ff8eefb9f2b861d3c29c4d032462f9f6f5624bb2d669ebfc77d49ade62ce2859f171052f3b473a00d0a83a88955ee1c4cbd47f5bbddfe2a625e62d1f8
7
+ data.tar.gz: 79f9f3b4cea4bff89c06df360e6a316507f62af37f2a277cd10dc54b38f72c374cadba6b4b995234e4c67f82dbaa71ecf751d3ec8ed280bf5fe0e64dca42534f
@@ -23,9 +23,6 @@ jobs:
23
23
  uses: metanorma/ci/.github/workflows/rubygems-release.yml@main
24
24
  with:
25
25
  next_version: ${{ github.event.inputs.next_version }}
26
- post_install: |
27
- git status
28
- cat .bundle/config
29
26
  secrets:
30
27
  rubygems-api-key: ${{ secrets.LUTAML_CI_RUBYGEMS_API_KEY }}
31
28
  pat_token: ${{ secrets.LUTAML_CI_PAT_TOKEN }}
data/.rubocop_todo.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2026-06-05 08:01:07 UTC using RuboCop version 1.87.0.
3
+ # on 2026-06-08 11:45:37 UTC using RuboCop version 1.87.0.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
@@ -11,13 +11,66 @@ Gemspec/RequiredRubyVersion:
11
11
  Exclude:
12
12
  - 'lutaml-model.gemspec'
13
13
 
14
- # Offense count: 3052
14
+ # Offense count: 1
15
+ # This cop supports safe autocorrection (--autocorrect).
16
+ # Configuration parameters: EnforcedStyle, IndentationWidth.
17
+ # SupportedStyles: with_first_argument, with_fixed_indentation
18
+ Layout/ArgumentAlignment:
19
+ Exclude:
20
+ - 'lib/lutaml/rdf/linked_data_transform.rb'
21
+
22
+ # Offense count: 2
23
+ # This cop supports safe autocorrection (--autocorrect).
24
+ # Configuration parameters: EnforcedStyleAlignWith.
25
+ # SupportedStylesAlignWith: either, start_of_block, start_of_line
26
+ Layout/BlockAlignment:
27
+ Exclude:
28
+ - 'spec/lutaml/model/choice_restrict_spec.rb'
29
+
30
+ # Offense count: 2
31
+ # This cop supports safe autocorrection (--autocorrect).
32
+ Layout/BlockEndNewline:
33
+ Exclude:
34
+ - 'spec/lutaml/model/choice_restrict_spec.rb'
35
+
36
+ # Offense count: 2
37
+ # This cop supports safe autocorrection (--autocorrect).
38
+ # Configuration parameters: EnforcedStyle, IndentationWidth.
39
+ # SupportedStyles: special_inside_parentheses, consistent, align_brackets
40
+ Layout/FirstArrayElementIndentation:
41
+ Exclude:
42
+ - 'spec/lutaml/model/choice_restrict_spec.rb'
43
+
44
+ # Offense count: 4
45
+ # This cop supports safe autocorrection (--autocorrect).
46
+ # Configuration parameters: Width, EnforcedStyleAlignWith, AllowedPatterns.
47
+ # SupportedStylesAlignWith: start_of_line, relative_to_receiver
48
+ Layout/IndentationWidth:
49
+ Exclude:
50
+ - 'spec/lutaml/model/choice_restrict_spec.rb'
51
+
52
+ # Offense count: 3071
15
53
  # This cop supports safe autocorrection (--autocorrect).
16
54
  # Configuration parameters: Max, AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, AllowRBSInlineAnnotation, AllowCopDirectives, AllowedPatterns, SplitStrings.
17
55
  # URISchemes: http, https
18
56
  Layout/LineLength:
19
57
  Enabled: false
20
58
 
59
+ # Offense count: 1
60
+ # This cop supports safe autocorrection (--autocorrect).
61
+ # Configuration parameters: EnforcedStyle.
62
+ # SupportedStyles: symmetrical, new_line, same_line
63
+ Layout/MultilineArrayBraceLayout:
64
+ Exclude:
65
+ - 'spec/lutaml/model/choice_restrict_spec.rb'
66
+
67
+ # Offense count: 1
68
+ # This cop supports safe autocorrection (--autocorrect).
69
+ # Configuration parameters: AllowInHeredoc.
70
+ Layout/TrailingWhitespace:
71
+ Exclude:
72
+ - 'lib/lutaml/rdf/linked_data_transform.rb'
73
+
21
74
  # Offense count: 21
22
75
  # Configuration parameters: AllowedMethods.
23
76
  # AllowedMethods: enums
@@ -232,7 +285,7 @@ RSpec/BeforeAfterAll:
232
285
  RSpec/ContextWording:
233
286
  Enabled: false
234
287
 
235
- # Offense count: 101
288
+ # Offense count: 102
236
289
  # Configuration parameters: IgnoredMetadata.
237
290
  RSpec/DescribeClass:
238
291
  Enabled: false
@@ -243,7 +296,7 @@ RSpec/DescribeMethod:
243
296
  - 'spec/lutaml/xml/schema/xsd/schema_helper_methods_spec.rb'
244
297
  - 'spec/lutaml/xml/serializable_namespace_spec.rb'
245
298
 
246
- # Offense count: 1328
299
+ # Offense count: 1343
247
300
  # Configuration parameters: CountAsOne.
248
301
  RSpec/ExampleLength:
249
302
  Max: 68
@@ -318,7 +371,7 @@ RSpec/MultipleDescribes:
318
371
  - 'spec/lutaml/xml/namespace_resolution_strategy_spec.rb'
319
372
  - 'spec/lutaml/xml/xml_space_type_spec.rb'
320
373
 
321
- # Offense count: 1560
374
+ # Offense count: 1576
322
375
  RSpec/MultipleExpectations:
323
376
  Max: 21
324
377
 
@@ -366,7 +419,7 @@ RSpec/RepeatedExampleGroupDescription:
366
419
  Exclude:
367
420
  - 'spec/lutaml/model/mixed_content_spec.rb'
368
421
 
369
- # Offense count: 40
422
+ # Offense count: 39
370
423
  # Configuration parameters: CustomTransform, IgnoreMethods, IgnoreMetadata, InflectorPath, EnforcedInflector.
371
424
  # SupportedInflectors: default, active_support
372
425
  RSpec/SpecFilePathFormat:
@@ -392,6 +445,17 @@ Security/MarshalLoad:
392
445
  Exclude:
393
446
  - 'scripts-xmi-profile/profile_xmi.rb'
394
447
 
448
+ # Offense count: 3
449
+ # This cop supports safe autocorrection (--autocorrect).
450
+ # Configuration parameters: EnforcedStyle, ProceduralMethods, FunctionalMethods, AllowedMethods, AllowedPatterns, AllowBracesOnProceduralOneLiners, BracesRequiredMethods.
451
+ # SupportedStyles: line_count_based, semantic, braces_for_chaining, always_braces
452
+ # ProceduralMethods: benchmark, bm, bmbm, create, each_with_object, measure, new, realtime, tap, with_object
453
+ # FunctionalMethods: let, let!, subject, watch
454
+ # AllowedMethods: lambda, proc, it
455
+ Style/BlockDelimiters:
456
+ Exclude:
457
+ - 'spec/lutaml/model/choice_restrict_spec.rb'
458
+
395
459
  # Offense count: 2
396
460
  # This cop supports unsafe autocorrection (--autocorrect-all).
397
461
  # Configuration parameters: AllowedMethods, AllowedPatterns.
@@ -484,11 +548,3 @@ Style/StringConcatenation:
484
548
  - 'lib/lutaml/model/schema/xml_compiler/complex_type.rb'
485
549
  - 'lib/lutaml/model/schema/xml_compiler/simple_type.rb'
486
550
  - 'lib/lutaml/model/schema/xml_compiler/xml_namespace_class.rb'
487
-
488
- # Offense count: 3
489
- # This cop supports safe autocorrection (--autocorrect).
490
- # Configuration parameters: EnforcedStyleForMultiline.
491
- # SupportedStylesForMultiline: comma, consistent_comma, diff_comma, no_comma
492
- Style/TrailingCommaInArguments:
493
- Exclude:
494
- - 'spec/lutaml/model/raw_element_spec.rb'
@@ -13,6 +13,8 @@ parent: XML namespaces
13
13
  Lutaml::Model implements W3C XML Namespace specification with a focus on
14
14
  **attribute-based namespace resolution**. Each XML element's namespace is
15
15
  determined by its attribute's type and parent context, following clear and
16
+
17
+ NOTE: This document covers the *core namespace resolution principles*. For the `form:` override option, the full qualification precedence ladder, and `form:` behavior on nested `Serializable` children, see the companion guide: link:../xml-namespace-qualification[XML Namespace Qualification and Prefix Control].
16
18
  consistent rules.
17
19
 
18
20
  This document explains the core namespace principles that govern how namespaces
@@ -509,6 +509,148 @@ Parent.new(child: Child.new(value: "test")).to_xml(prefix: true)
509
509
 
510
510
  All three elements (`parent`, `child`, `value`) use the prefix because all are qualified via `element_form_default: :qualified`.
511
511
 
512
+ === Form Override on Serializable Children
513
+
514
+ The `form:` option works on attributes of any type — including attributes whose value type is another `Serializable` model, not only simple types like `:string`. This matters when the parent's namespace declares `element_form_default :unqualified` (the W3C default) and you need a specific nested model element to be qualified anyway.
515
+
516
+ .Worked example: form: :qualified on a Serializable child
517
+ [example]
518
+ ====
519
+ [source,ruby]
520
+ ----
521
+ NS = Class.new(Lutaml::Model::XmlNamespace) do
522
+ uri "https://example.com/ns"
523
+ prefix_default "ex"
524
+ element_form_default :unqualified # W3C default for local elements
525
+ end
526
+
527
+ class Child < Lutaml::Model::Serializable
528
+ attribute :label, :string
529
+ xml do
530
+ element "child"
531
+ namespace NS
532
+ map_element "label", to: :label
533
+ end
534
+ end
535
+
536
+ class Parent < Lutaml::Model::Serializable
537
+ attribute :child, Child
538
+ xml do
539
+ element "item"
540
+ namespace NS
541
+ map_element "child", to: :child, form: :qualified # Force prefix
542
+ end
543
+ end
544
+
545
+ Parent.new(child: Child.new(label: "x")).to_xml
546
+ ----
547
+
548
+ *Output*:
549
+ [source,xml]
550
+ ----
551
+ <item xmlns="https://example.com/ns" xmlns:ex="https://example.com/ns">
552
+ <ex:child>
553
+ <label>x</label>
554
+ </ex:child>
555
+ </item>
556
+ ----
557
+
558
+ The `ex:` prefix on `<child>` is forced by `form: :qualified`, overriding the parent's `:unqualified` schema default. The inner `<label>` remains unprefixed because the `Child` model's own mapping rule for `label` has no `form:` override — see the next section.
559
+ ====
560
+
561
+ === Form Scope: Per-Rule, Not Transitive
562
+
563
+ `form:` is a per-mapping-rule override. It does **not** propagate transitively to grandchildren. Each level of the model tree applies its own rule against its own parent's `element_form_default`.
564
+
565
+ .Worked example: form on parent does not cascade to grandchild
566
+ [example]
567
+ ====
568
+ [source,ruby]
569
+ ----
570
+ NS = Class.new(Lutaml::Model::XmlNamespace) do
571
+ uri "https://example.com/ns"
572
+ prefix_default "ex"
573
+ element_form_default :unqualified
574
+ end
575
+
576
+ class Grandchild < Lutaml::Model::Serializable
577
+ attribute :value, :string
578
+ xml do
579
+ element "grandchild"
580
+ namespace NS
581
+ map_element "value", to: :value
582
+ end
583
+ end
584
+
585
+ class Child < Lutaml::Model::Serializable
586
+ attribute :inner, Grandchild
587
+ xml do
588
+ element "child"
589
+ namespace NS
590
+ map_element "grandchild", to: :inner # NO form: override
591
+ end
592
+ end
593
+
594
+ class Parent < Lutaml::Model::Serializable
595
+ attribute :child, Child
596
+ xml do
597
+ element "item"
598
+ namespace NS
599
+ map_element "child", to: :child, form: :qualified # Parent's rule only
600
+ end
601
+ end
602
+
603
+ Parent.new(child: Child.new(inner: Grandchild.new(value: "x"))).to_xml
604
+ ----
605
+
606
+ *Output*:
607
+ [source,xml]
608
+ ----
609
+ <item xmlns="https://example.com/ns" xmlns:ex="https://example.com/ns">
610
+ <ex:child>
611
+ <grandchild>
612
+ <value>x</value>
613
+ </grandchild>
614
+ </ex:child>
615
+ </item>
616
+ ----
617
+
618
+ * `<ex:child>` is qualified because the *Parent* rule has `form: :qualified`.
619
+ * `<grandchild>` is unprefixed because the *Child* rule has no `form:` and the Child's namespace is `:unqualified`.
620
+ * The Parent's `form: :qualified` does **not** cascade.
621
+ ====
622
+
623
+ To qualify every level, either set `form: :qualified` on each mapping rule that needs it, or set `element_form_default :qualified` on the namespace so inheritance handles it.
624
+
625
+ == Qualification Precedence
626
+
627
+ When serializing an element, the namespace is resolved by checking the following sources in priority order. The first match wins.
628
+
629
+ [cols="1,4", options="header"]
630
+ |===
631
+ |Priority |Source
632
+
633
+ |1 (highest)
634
+ |Type-level namespace: the attribute's value type (a `Type::Value` subclass) declares `xml_namespace`. Wins over everything else.
635
+
636
+ |2
637
+ |Rule-level namespace: the mapping rule has an explicit `namespace:` option. *However*, if the parent's `element_form_default` is `:unqualified` AND the rule's namespace matches the parent's, the namespace is overridden to blank — W3C unqualified semantics for local elements.
638
+
639
+ |3
640
+ |`form: :unqualified` on the rule: force the element into no namespace.
641
+
642
+ |4
643
+ |Parent's `element_form_default :qualified` inheritance: the child inherits the parent's namespace — but only if the child model itself declares a namespace (W3C: `elementFormDefault` applies to locally-declared elements only).
644
+
645
+ |5
646
+ |`form: :qualified` on the rule: inherit the parent's namespace.
647
+
648
+ |6 (lowest)
649
+ |No namespace.
650
+ |===
651
+
652
+ This ladder is implemented in `Lutaml::Xml::Transformation::ElementBuilder#determine_element_namespace`. The `ElementFormOptionRule` decision rule reads the propagated `form` value during the planning phase and forces prefix (`:qualified`) or default (`:unqualified`) format accordingly.
653
+
512
654
  == Type Namespaces
513
655
 
514
656
  Value types can define their own namespaces:
@@ -9,6 +9,8 @@ This guide explains how XML namespaces work, particularly the distinction betwee
9
9
  - **Qualified**: Element/attribute needs a namespace declared in the XML
10
10
  - **Unqualified**: Element/attribute does not need a namespace declared (uses parent's namespace)
11
11
 
12
+ > **Authoritative reference:** For the `element_form_default` / `form:` system, the full qualification precedence ladder, and `form:` behavior on nested `Serializable` children, see [docs/_guides/xml-namespace-qualification.adoc](../_guides/xml-namespace-qualification.adoc). This tutorial is a conceptual introduction.
13
+
12
14
  ## Default Namespace Inheritance
13
15
 
14
16
  **Critical Rule:** Default namespace inheritance ONLY applies to elements, NOT attributes.
@@ -441,6 +441,8 @@ end
441
441
 
442
442
  NOTE: The `<comment>` element uses `xmlns=""` to explicitly declare blank namespace (form: :unqualified override).
443
443
 
444
+ TIP: The `form:` option also works on attributes whose value type is a nested `Serializable` model, not just simple types like `:string`. It is a per-rule override and does not propagate transitively to grandchildren. For the full qualification precedence ladder and worked nested-model examples, see link:../_guides/xml-namespace-qualification/#form-override-on-serializable-children[XML Namespace Qualification and Prefix Control].
445
+
444
446
  == Section 3: Type Namespaces
445
447
 
446
448
  === General
@@ -6,6 +6,8 @@
6
6
 
7
7
  This guide provides comprehensive information about XML namespace management in Lutaml::Model, including the `namespace_scope` directive, W3C compliance, and best practices for multi-namespace documents.
8
8
 
9
+ NOTE: This guide focuses on namespace *declaration* and *scope*. For the `element_form_default` / `form:` qualification system — including the precedence ladder and `form:` behavior on nested `Serializable` children — see the companion guide: link:./_guides/xml-namespace-qualification[XML Namespace Qualification and Prefix Control].
10
+
9
11
  == Understanding XML Namespaces
10
12
 
11
13
  XML namespaces provide a method to avoid element name conflicts by qualifying names used in XML documents through a URI reference.
@@ -13,6 +13,12 @@ declared in the XML, "unqualified" means the ELEMENT/ATTRIBUTE does not need a
13
13
  namespace declared in the XML (i.e. we look to its parent's namespace to find
14
14
  its namespace).
15
15
 
16
+ > **Authoritative reference:** For the full qualification precedence ladder,
17
+ > `form:` behavior on nested `Serializable` children, per-rule vs transitive
18
+ > scope, and worked examples, see
19
+ > [docs/_guides/xml-namespace-qualification.adoc](./_guides/xml-namespace-qualification.adoc).
20
+ > This document is a conceptual overview; the guide is the canonical reference.
21
+
16
22
  When there is no XML Schema present:
17
23
  * element "acts like" unqualified (because of default namespace inheritance, does not need prefix)
18
24
  * attribute "acts like" qualified (because of no default namespace inheritance, requires prefix)
data/lib/lutaml/jsonld.rb CHANGED
@@ -5,9 +5,6 @@ require_relative "rdf"
5
5
 
6
6
  module Lutaml
7
7
  module JsonLd
8
- autoload :Context, "#{__dir__}/jsonld/context"
9
- autoload :TermDefinition, "#{__dir__}/jsonld/term_definition"
10
- autoload :Transform, "#{__dir__}/jsonld/transform"
11
8
  autoload :Adapter, "#{__dir__}/jsonld/adapter"
12
9
  end
13
10
  end
@@ -16,7 +13,7 @@ Lutaml::Model::FormatRegistry.register(
16
13
  :jsonld,
17
14
  mapping_class: Lutaml::Rdf::Mapping,
18
15
  adapter_class: Lutaml::JsonLd::Adapter,
19
- transformer: Lutaml::JsonLd::Transform,
16
+ transformer: Lutaml::Rdf::LinkedDataTransform,
20
17
  key_value: false,
21
18
  rdf: true,
22
19
  error_types: ["JSON::ParserError"],
@@ -36,6 +36,36 @@ module Lutaml
36
36
  @attributes << @model.attribute(name, type, options)
37
37
  end
38
38
 
39
+ # Restrict options on a predefined or imported attribute within this choice
40
+ #
41
+ # @param name [Symbol] The attribute name to restrict
42
+ # @param options [Hash] New options to merge
43
+ # @return [Symbol] The attribute name
44
+ def restrict(name, options = {})
45
+ @model.restrict(name, options)
46
+ attr = @model.attributes[name]
47
+ unless @attributes.include?(attr)
48
+ attr.options[:choice] = self
49
+ @attributes << attr
50
+ end
51
+ invalidate_cache!
52
+ name
53
+ end
54
+
55
+ # Remove an attribute from this choice block
56
+ #
57
+ # @param name [Symbol] The attribute name to remove
58
+ # @return [Boolean] true if the attribute was removed
59
+ def remove_attribute(name)
60
+ attr = @attributes.find { |a| !a.is_a?(Choice) && a.name == name }
61
+ return nil unless attr
62
+
63
+ @attributes.delete(attr)
64
+ attr.options.delete(:choice)
65
+ invalidate_cache!
66
+ attr
67
+ end
68
+
39
69
  def choice(min: 1, max: 1, &block)
40
70
  @attributes << Choice.new(@model, min, max, format: @format).tap do |c|
41
71
  c.instance_eval(&block)
@@ -163,6 +193,10 @@ register = nil)
163
193
 
164
194
  private
165
195
 
196
+ def invalidate_cache!
197
+ @flat_attributes = nil
198
+ end
199
+
166
200
  def raise_errors(choices_hash)
167
201
  flat_attr_names = flat_attributes.map { |attr| attr.name.to_s }
168
202
  choices_hash.each do |choice_attr, count|
@@ -98,6 +98,13 @@ module Lutaml
98
98
  !custom_methods.empty?
99
99
  end
100
100
 
101
+ # Check if form is explicitly set
102
+ #
103
+ # @return [Boolean] true if form option was provided
104
+ def form_set?
105
+ !form.nil?
106
+ end
107
+
101
108
  # Check if a name matches this rule (including alias names for multiple mappings)
102
109
  #
103
110
  # @param name [String, Symbol] The name to check
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- VERSION = "0.8.16"
5
+ VERSION = "0.8.17"
6
6
  end
7
7
  end
data/lib/lutaml/model.rb CHANGED
@@ -11,6 +11,7 @@ module Lutaml
11
11
  autoload :Yamls, "#{__dir__}/yamls"
12
12
  autoload :Xml, "#{__dir__}/xml"
13
13
  autoload :JsonLd, "#{__dir__}/jsonld"
14
+ autoload :YamlLd, "#{__dir__}/yamlld"
14
15
  autoload :Turtle, "#{__dir__}/turtle"
15
16
 
16
17
  module Model
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Lutaml
4
- module JsonLd
4
+ module Rdf
5
5
  class Context
6
6
  attr_reader :prefixes, :terms
7
7
 
@@ -1,34 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "json"
4
-
5
3
  module Lutaml
6
- module JsonLd
7
- class Transform < Lutaml::Rdf::Transform
8
- def model_to_data(instance, _format, options = {})
9
- mapping = extract_mapping(options)
4
+ module Rdf
5
+ class LinkedDataTransform < Lutaml::Rdf::Transform
6
+ def model_to_data(instance, format, options = {})
7
+ mapping = extract_mapping(format, options)
10
8
  return {} unless mapping
11
9
 
12
10
  if mapping.rdf_members.any?
13
- build_graph_document(mapping, instance)
11
+ build_graph_document(mapping, instance, format)
14
12
  else
15
- build_resource_object(mapping, instance)
13
+ build_resource_object(mapping, instance, format)
16
14
  end
17
15
  end
18
16
 
19
- def data_to_model(data, _format, options = {})
20
- mapping = extract_mapping(options)
17
+ def data_to_model(data, format, options = {})
18
+ mapping = extract_mapping(format, options)
21
19
  return model_class.new unless mapping
22
20
 
23
- hash = data.is_a?(String) ? JSON.parse(data) : data
21
+ hash = data.is_a?(Hash) ? data : {}
24
22
 
25
23
  if hash.key?("@graph") && hash["@graph"].is_a?(Array) && !hash["@graph"].empty?
26
- graph_data = hash["@graph"]
27
- first = graph_data.first
24
+ first = hash["@graph"].first
28
25
  hash = first.is_a?(Hash) ? first : {}
29
26
  end
30
27
 
31
- hash = strip_jsonld_keywords(hash)
28
+ hash = strip_linked_data_keywords(hash)
32
29
 
33
30
  attrs = {}
34
31
  mapping.rdf_predicates.each do |rule|
@@ -53,29 +50,29 @@ module Lutaml
53
50
 
54
51
  private
55
52
 
56
- def extract_mapping(options)
57
- options[:mappings] || mappings_for(:jsonld, lutaml_register)
53
+ def extract_mapping(format, options)
54
+ options[:mappings] || mappings_for(format, lutaml_register)
58
55
  end
59
56
 
60
- def build_graph_document(mapping, instance)
61
- context = build_merged_context_recursive(mapping, instance)
62
- graph = collect_resources(mapping, instance)
57
+ def build_graph_document(mapping, instance, format)
58
+ context = build_merged_context_recursive(mapping, instance, format)
59
+ graph = collect_resources(mapping, instance, format)
63
60
 
64
61
  { "@context" => context, "@graph" => graph }
65
62
  end
66
63
 
67
- def collect_resources(mapping, instance)
64
+ def collect_resources(mapping, instance, format)
68
65
  graph = []
69
66
 
70
- resource = build_resource_data(mapping, instance)
67
+ resource = build_resource_data(mapping, instance, format)
71
68
  graph << resource unless resource.empty?
72
69
 
73
70
  mapping.rdf_members.each do |member_rule|
74
71
  each_member(instance, member_rule) do |member|
75
- member_mapping = member_mapping_for(member, :jsonld)
72
+ member_mapping = member_mapping_for(member, format)
76
73
  next unless member_mapping
77
74
 
78
- child_resources = collect_resources(member_mapping, member)
75
+ child_resources = collect_resources(member_mapping, member, format)
79
76
  graph.concat(child_resources)
80
77
  end
81
78
  end
@@ -83,17 +80,18 @@ module Lutaml
83
80
  graph
84
81
  end
85
82
 
86
- def build_merged_context_recursive(mapping, instance)
83
+ def build_merged_context_recursive(mapping, instance, format)
87
84
  context_hash = build_context_from_mapping(mapping).to_hash
88
85
 
89
86
  mapping.rdf_members.each do |member_rule|
90
87
  each_member(instance, member_rule) do |member|
91
- member_mapping = member_mapping_for(member, :jsonld)
88
+ member_mapping = member_mapping_for(member, format)
92
89
  next unless member_mapping
93
90
 
94
91
  context_hash.merge!(build_context_from_mapping(member_mapping).to_hash)
95
92
 
96
- child_ctx = build_merged_context_recursive(member_mapping, member)
93
+ child_ctx = build_merged_context_recursive(member_mapping, member,
94
+ format)
97
95
  context_hash.merge!(child_ctx)
98
96
  end
99
97
  end
@@ -134,13 +132,13 @@ module Lutaml
134
132
  end
135
133
  end
136
134
 
137
- def build_resource_object(mapping, instance)
135
+ def build_resource_object(mapping, instance, format)
138
136
  context = build_context_from_mapping(mapping).to_hash
139
- data = build_resource_data(mapping, instance)
137
+ data = build_resource_data(mapping, instance, format)
140
138
  { "@context" => context }.merge(data)
141
139
  end
142
140
 
143
- def build_resource_data(mapping, instance)
141
+ def build_resource_data(mapping, instance, format)
144
142
  result = {}
145
143
 
146
144
  if mapping.rdf_type.any?
@@ -166,10 +164,10 @@ module Lutaml
166
164
  mapping.rdf_members.each do |member_rule|
167
165
  next unless member_rule.linked?
168
166
 
169
- member_refs = collect_member_references(instance, member_rule)
167
+ member_refs = collect_member_references(instance, member_rule, format)
170
168
  next if member_refs.empty?
171
169
 
172
- key = jsonld_member_key(member_rule)
170
+ key = member_key(member_rule)
173
171
  result[key] = member_refs
174
172
  end
175
173
 
@@ -178,10 +176,10 @@ module Lutaml
178
176
  result
179
177
  end
180
178
 
181
- def collect_member_references(instance, member_rule)
179
+ def collect_member_references(instance, member_rule, format)
182
180
  refs = []
183
181
  each_member(instance, member_rule) do |member|
184
- member_mapping = member_mapping_for(member, :jsonld)
182
+ member_mapping = member_mapping_for(member, format)
185
183
  next unless member_mapping
186
184
 
187
185
  refs << { "@id" => resolve_subject_uri(member_mapping, member) }
@@ -189,7 +187,7 @@ module Lutaml
189
187
  refs
190
188
  end
191
189
 
192
- def jsonld_member_key(member_rule)
190
+ def member_key(member_rule)
193
191
  if member_rule.predicate_name
194
192
  member_rule.predicate_name.to_s
195
193
  elsif member_rule.link.is_a?(String)
@@ -241,7 +239,7 @@ module Lutaml
241
239
  end
242
240
  end
243
241
 
244
- def strip_jsonld_keywords(data)
242
+ def strip_linked_data_keywords(data)
245
243
  return data unless data.is_a?(Hash)
246
244
 
247
245
  data.reject { |key, _| key.start_with?("@") }
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Lutaml
4
- module JsonLd
4
+ module Rdf
5
5
  class TermDefinition
6
6
  attr_reader :name, :id, :type, :container, :language, :reverse
7
7
 
data/lib/lutaml/rdf.rb CHANGED
@@ -15,5 +15,8 @@ module Lutaml
15
15
  autoload :MemberRule, "#{__dir__}/rdf/member_rule"
16
16
  autoload :Namespaces, "#{__dir__}/rdf/namespaces"
17
17
  autoload :Transform, "#{__dir__}/rdf/transform"
18
+ autoload :Context, "#{__dir__}/rdf/context"
19
+ autoload :TermDefinition, "#{__dir__}/rdf/term_definition"
20
+ autoload :LinkedDataTransform, "#{__dir__}/rdf/linked_data_transform"
18
21
  end
19
22
  end