lutaml-model 0.8.14 → 0.8.16

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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +14 -73
  3. data/Gemfile +3 -5
  4. data/README.adoc +188 -25
  5. data/docs/_guides/xml-mapping.adoc +178 -24
  6. data/docs/_pages/importable_models.adoc +7 -1
  7. data/lib/lutaml/jsonld/transform.rb +44 -13
  8. data/lib/lutaml/model/attribute.rb +4 -0
  9. data/lib/lutaml/model/liquefiable.rb +9 -0
  10. data/lib/lutaml/model/liquid/indexed_access.rb +33 -0
  11. data/lib/lutaml/model/liquid.rb +1 -0
  12. data/lib/lutaml/model/version.rb +1 -1
  13. data/lib/lutaml/model.rb +2 -1
  14. data/lib/lutaml/rdf/mapping.rb +10 -1
  15. data/lib/lutaml/rdf/member_rule.rb +29 -4
  16. data/lib/lutaml/turtle/transform.rb +55 -35
  17. data/lib/lutaml/xml/adapter/plan_based_builder.rb +3 -1
  18. data/lib/lutaml/xml/adapter/xml_serializer.rb +15 -5
  19. data/lib/lutaml/xml/adapter.rb +2 -1
  20. data/lib/lutaml/xml/builder/base.rb +2 -1
  21. data/lib/lutaml/xml/data_model.rb +19 -3
  22. data/lib/lutaml/xml/mapping.rb +3 -1
  23. data/lib/lutaml/xml/mapping_rule.rb +28 -2
  24. data/lib/lutaml/xml/model_transform.rb +9 -1
  25. data/lib/lutaml/xml/serialization/instance_methods.rb +16 -9
  26. data/lib/lutaml/xml/transformation/element_builder.rb +1 -3
  27. data/lib/lutaml/xml/transformation/rule_applier.rb +21 -0
  28. data/lib/lutaml/xml/transformation/rule_compiler.rb +12 -3
  29. data/lutaml-model.gemspec +1 -1
  30. data/spec/lutaml/jsonld/transform_spec.rb +149 -0
  31. data/spec/lutaml/model/liquid/indexed_access_spec.rb +135 -0
  32. data/spec/lutaml/model/mixed_content_spec.rb +48 -7
  33. data/spec/lutaml/model/raw_element_spec.rb +533 -0
  34. data/spec/lutaml/rdf/mapping_spec.rb +71 -6
  35. data/spec/lutaml/rdf/member_rule_spec.rb +103 -1
  36. data/spec/lutaml/turtle/transform_spec.rb +144 -0
  37. metadata +9 -6
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "spec_helper"
4
+ require_relative "../../../lib/lutaml/rdf"
4
5
 
5
6
  RSpec.describe Lutaml::Rdf::MemberRule do
6
7
  describe ".new" do
@@ -26,10 +27,19 @@ RSpec.describe Lutaml::Rdf::MemberRule do
26
27
  namespace: Lutaml::Rdf::Namespaces::SkosNamespace)
27
28
  expect(rule.predicate_name).to eq(:member)
28
29
  end
30
+
31
+ it "raises ArgumentError when predicate_name and link both given" do
32
+ expect do
33
+ described_class.new(:items,
34
+ predicate_name: :member,
35
+ namespace: Lutaml::Rdf::Namespaces::SkosNamespace,
36
+ link: "skos:member")
37
+ end.to raise_error(ArgumentError, /mutually exclusive/)
38
+ end
29
39
  end
30
40
 
31
41
  describe "#linked?" do
32
- it "returns false when no predicate_name" do
42
+ it "returns false when no linking option" do
33
43
  rule = described_class.new(:items)
34
44
  expect(rule.linked?).to be(false)
35
45
  end
@@ -40,6 +50,16 @@ RSpec.describe Lutaml::Rdf::MemberRule do
40
50
  namespace: Lutaml::Rdf::Namespaces::SkosNamespace)
41
51
  expect(rule.linked?).to be(true)
42
52
  end
53
+
54
+ it "returns true when link is a String" do
55
+ rule = described_class.new(:items, link: "skos:member")
56
+ expect(rule.linked?).to be(true)
57
+ end
58
+
59
+ it "returns true when link is a Proc" do
60
+ rule = described_class.new(:items, link: ->(m) { "skos:#{m}" })
61
+ expect(rule.linked?).to be(true)
62
+ end
43
63
  end
44
64
 
45
65
  describe "#linked_predicate_uri" do
@@ -54,5 +74,87 @@ RSpec.describe Lutaml::Rdf::MemberRule do
54
74
  namespace: Lutaml::Rdf::Namespaces::SkosNamespace)
55
75
  expect(rule.linked_predicate_uri).to eq("http://www.w3.org/2004/02/skos/core#member")
56
76
  end
77
+
78
+ it "returns nil when link is a String (not static)" do
79
+ rule = described_class.new(:items, link: "skos:member")
80
+ expect(rule.linked_predicate_uri).to be_nil
81
+ end
82
+ end
83
+
84
+ describe "#link_predicate_for" do
85
+ let(:mapping) do
86
+ instance = Lutaml::Rdf::Mapping.new
87
+ instance.namespace(
88
+ Lutaml::Rdf::Namespaces::SkosNamespace,
89
+ Lutaml::Rdf::Namespaces::DctermsNamespace,
90
+ )
91
+ instance
92
+ end
93
+
94
+ let(:resolver) { mapping.namespace_set.method(:resolve_compact_iri) }
95
+
96
+ it "resolves String link via resolver" do
97
+ rule = described_class.new(:items, link: "skos:member")
98
+ expect(rule.link_predicate_for(nil, resolver))
99
+ .to eq("http://www.w3.org/2004/02/skos/core#member")
100
+ end
101
+
102
+ it "resolves Proc link by calling with member" do
103
+ rule = described_class.new(:items,
104
+ link: ->(m) { "skos:#{m.type}" })
105
+ member = Struct.new(:type).new("Concept")
106
+ expect(rule.link_predicate_for(member, resolver))
107
+ .to eq("http://www.w3.org/2004/02/skos/core#Concept")
108
+ end
109
+
110
+ it "returns URI as-is from Proc when prefix not found" do
111
+ rule = described_class.new(:items,
112
+ link: ->(m) { "http://example.org/#{m.id}" })
113
+ member = Struct.new(:id).new("42")
114
+ expect(rule.link_predicate_for(member, resolver))
115
+ .to eq("http://example.org/42")
116
+ end
117
+
118
+ it "returns nil when no link" do
119
+ rule = described_class.new(:items)
120
+ expect(rule.link_predicate_for(nil, resolver)).to be_nil
121
+ end
122
+ end
123
+
124
+ describe "#resolve_link_uri" do
125
+ let(:mapping) do
126
+ instance = Lutaml::Rdf::Mapping.new
127
+ instance.namespace(Lutaml::Rdf::Namespaces::SkosNamespace)
128
+ instance
129
+ end
130
+
131
+ let(:resolver) { mapping.namespace_set.method(:resolve_compact_iri) }
132
+
133
+ it "uses linked_predicate_uri for static links" do
134
+ rule = described_class.new(:items,
135
+ predicate_name: :member,
136
+ namespace: Lutaml::Rdf::Namespaces::SkosNamespace)
137
+ expect(rule.resolve_link_uri(nil, resolver))
138
+ .to eq("http://www.w3.org/2004/02/skos/core#member")
139
+ end
140
+
141
+ it "uses link_predicate_for for String links" do
142
+ rule = described_class.new(:items, link: "skos:member")
143
+ expect(rule.resolve_link_uri(nil, resolver))
144
+ .to eq("http://www.w3.org/2004/02/skos/core#member")
145
+ end
146
+
147
+ it "uses link_predicate_for for Proc links" do
148
+ rule = described_class.new(:items,
149
+ link: ->(m) { "skos:#{m.type}" })
150
+ member = Struct.new(:type).new("Concept")
151
+ expect(rule.resolve_link_uri(member, resolver))
152
+ .to eq("http://www.w3.org/2004/02/skos/core#Concept")
153
+ end
154
+
155
+ it "returns nil when not linked" do
156
+ rule = described_class.new(:items)
157
+ expect(rule.resolve_link_uri(nil, resolver)).to be_nil
158
+ end
57
159
  end
58
160
  end
@@ -585,4 +585,148 @@ RSpec.describe Lutaml::Turtle::Transform do
585
585
  skip "Heterogeneous collection requires union-typed attribute (not yet supported)"
586
586
  end
587
587
  end
588
+
589
+ describe "dynamic link predicates" do
590
+ before do
591
+ stub_const("DynChild", Class.new(Lutaml::Model::Serializable) do
592
+ attribute :label, :string
593
+ attribute :cid, :string
594
+
595
+ turtle do
596
+ namespace Lutaml::Rdf::Namespaces::SkosNamespace
597
+
598
+ subject { |m| "http://example.org/child/#{m.cid}" } # rubocop:disable RSpec/NamedSubject, RSpec/MultipleSubjects
599
+
600
+ type "skos:Concept"
601
+
602
+ predicate :prefLabel,
603
+ namespace: Lutaml::Rdf::Namespaces::SkosNamespace,
604
+ to: :label
605
+ end
606
+ end)
607
+
608
+ stub_const("DynParent", Class.new(Lutaml::Model::Serializable) do
609
+ attribute :name, :string
610
+ attribute :children, DynChild, collection: true
611
+
612
+ turtle do
613
+ namespace Lutaml::Rdf::Namespaces::SkosNamespace
614
+
615
+ subject { |m| "http://example.org/parent/#{m.name}" }
616
+
617
+ type "skos:Collection"
618
+
619
+ predicate :prefLabel,
620
+ namespace: Lutaml::Rdf::Namespaces::SkosNamespace,
621
+ to: :name
622
+
623
+ members :children, link: "skos:member"
624
+ end
625
+ end)
626
+ end
627
+
628
+ it "generates linking triples from String link" do
629
+ parent = DynParent.new(
630
+ name: "p1",
631
+ children: [DynChild.new(label: "c1", cid: "a")],
632
+ )
633
+ result = parent.to_turtle
634
+ expect(result).to include("skos:member <http://example.org/child/a>")
635
+ end
636
+
637
+ it "includes child subgraph data" do
638
+ parent = DynParent.new(
639
+ name: "p1",
640
+ children: [DynChild.new(label: "c1", cid: "a")],
641
+ )
642
+ result = parent.to_turtle
643
+ expect(result).to include("skos:prefLabel \"c1\"")
644
+ end
645
+ end
646
+
647
+ describe "recursive prefix collection" do
648
+ before do
649
+ stub_const("SkosNs", Lutaml::Rdf::Namespaces::SkosNamespace)
650
+ stub_const("DctermsNs", Lutaml::Rdf::Namespaces::DctermsNamespace)
651
+
652
+ stub_const("LeafModel", Class.new(Lutaml::Model::Serializable) do
653
+ attribute :value, :string
654
+ attribute :lid, :string
655
+
656
+ turtle do
657
+ namespace DctermsNs
658
+
659
+ subject { |m| "http://example.org/leaf/#{m.lid}" } # rubocop:disable RSpec/NamedSubject, RSpec/MultipleSubjects
660
+
661
+ type "dcterms:Agent"
662
+
663
+ predicate :title, namespace: DctermsNs, to: :value
664
+ end
665
+ end)
666
+
667
+ stub_const("MidModel", Class.new(Lutaml::Model::Serializable) do
668
+ attribute :name, :string
669
+ attribute :mid, :string
670
+ attribute :leaves, LeafModel, collection: true
671
+
672
+ turtle do
673
+ namespace SkosNs, DctermsNs
674
+
675
+ subject { |m| "http://example.org/mid/#{m.mid}" } # rubocop:disable RSpec/NamedSubject, RSpec/MultipleSubjects
676
+
677
+ type "skos:Concept"
678
+
679
+ predicate :prefLabel, namespace: SkosNs, to: :name
680
+
681
+ members :leaves, link: "skos:member"
682
+ end
683
+ end)
684
+
685
+ stub_const("RootModel", Class.new(Lutaml::Model::Serializable) do
686
+ attribute :title, :string
687
+ attribute :mids, MidModel, collection: true
688
+
689
+ turtle do
690
+ namespace SkosNs
691
+
692
+ subject { |m| "http://example.org/root/#{m.title}" }
693
+
694
+ type "skos:Collection"
695
+
696
+ predicate :prefLabel, namespace: SkosNs, to: :title
697
+
698
+ members :mids, link: "skos:member"
699
+ end
700
+ end)
701
+ end
702
+
703
+ it "collects prefixes from all nesting levels" do
704
+ root = RootModel.new(
705
+ title: "r1",
706
+ mids: [MidModel.new(
707
+ name: "m1",
708
+ mid: "a",
709
+ leaves: [LeafModel.new(value: "l1", lid: "x")],
710
+ )],
711
+ )
712
+ result = root.to_turtle
713
+ expect(result).to include("@prefix skos:")
714
+ expect(result).to include("@prefix dcterms:")
715
+ end
716
+
717
+ it "emits triples from all nesting levels" do
718
+ root = RootModel.new(
719
+ title: "r1",
720
+ mids: [MidModel.new(
721
+ name: "m1",
722
+ mid: "a",
723
+ leaves: [LeafModel.new(value: "l1", lid: "x")],
724
+ )],
725
+ )
726
+ result = root.to_turtle
727
+ expect(result).to include("skos:prefLabel \"r1\"")
728
+ expect(result).to include("skos:prefLabel \"m1\"")
729
+ expect(result).to include("dcterms:title \"l1\"")
730
+ end
731
+ end
588
732
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lutaml-model
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.14
4
+ version: 0.8.16
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-05-31 00:00:00.000000000 Z
11
+ date: 2026-06-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64
@@ -90,16 +90,16 @@ dependencies:
90
90
  name: moxml
91
91
  requirement: !ruby/object:Gem::Requirement
92
92
  requirements:
93
- - - ">="
93
+ - - "~>"
94
94
  - !ruby/object:Gem::Version
95
- version: 0.1.22
95
+ version: 0.1.23
96
96
  type: :runtime
97
97
  prerelease: false
98
98
  version_requirements: !ruby/object:Gem::Requirement
99
99
  requirements:
100
- - - ">="
100
+ - - "~>"
101
101
  - !ruby/object:Gem::Version
102
- version: 0.1.22
102
+ version: 0.1.23
103
103
  - !ruby/object:Gem::Dependency
104
104
  name: ostruct
105
105
  requirement: !ruby/object:Gem::Requirement
@@ -476,6 +476,7 @@ files:
476
476
  - lib/lutaml/model/jsonl.rb
477
477
  - lib/lutaml/model/liquefiable.rb
478
478
  - lib/lutaml/model/liquid.rb
479
+ - lib/lutaml/model/liquid/indexed_access.rb
479
480
  - lib/lutaml/model/liquid/mapping.rb
480
481
  - lib/lutaml/model/mapping/listener.rb
481
482
  - lib/lutaml/model/mapping/mapping.rb
@@ -1611,6 +1612,7 @@ files:
1611
1612
  - spec/lutaml/model/key_value_mapping_spec.rb
1612
1613
  - spec/lutaml/model/lazy_collection_spec.rb
1613
1614
  - spec/lutaml/model/liquefiable_spec.rb
1615
+ - spec/lutaml/model/liquid/indexed_access_spec.rb
1614
1616
  - spec/lutaml/model/liquid_compatibility_spec.rb
1615
1617
  - spec/lutaml/model/logger_spec.rb
1616
1618
  - spec/lutaml/model/map_all_spec.rb
@@ -1626,6 +1628,7 @@ files:
1626
1628
  - spec/lutaml/model/ordered_content_spec.rb
1627
1629
  - spec/lutaml/model/polymorphic_spec.rb
1628
1630
  - spec/lutaml/model/processing_instruction_spec.rb
1631
+ - spec/lutaml/model/raw_element_spec.rb
1629
1632
  - spec/lutaml/model/register_key_value_spec.rb
1630
1633
  - spec/lutaml/model/register_methods_spec.rb
1631
1634
  - spec/lutaml/model/register_spec.rb