lutaml-model 0.8.3 → 0.8.5

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 (132) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/dependent-tests.yml +3 -1
  3. data/.rubocop.yml +18 -0
  4. data/.rubocop_todo.yml +16 -22
  5. data/Gemfile +2 -0
  6. data/README.adoc +327 -3
  7. data/docs/_guides/document-validation.adoc +303 -0
  8. data/docs/_guides/index.adoc +19 -0
  9. data/docs/_guides/jsonld-serialization.adoc +217 -0
  10. data/docs/_guides/rdf-serialization.adoc +344 -0
  11. data/docs/_guides/turtle-serialization.adoc +224 -0
  12. data/docs/_guides/xml-mapping.adoc +9 -1
  13. data/docs/_guides/xml_mappings/07_best_practices.adoc +36 -0
  14. data/docs/_guides/xml_mappings/08_troubleshooting.adoc +89 -0
  15. data/docs/_pages/serialization_adapters.adoc +31 -0
  16. data/docs/_references/index.adoc +1 -0
  17. data/docs/_references/rdf-namespaces.adoc +243 -0
  18. data/docs/_tutorials/lutaml-xml-architecture.adoc +6 -1
  19. data/docs/index.adoc +3 -2
  20. data/lib/lutaml/jsonld/adapter.rb +23 -0
  21. data/lib/lutaml/jsonld/context.rb +69 -0
  22. data/lib/lutaml/jsonld/term_definition.rb +39 -0
  23. data/lib/lutaml/jsonld/transform.rb +174 -0
  24. data/lib/lutaml/jsonld.rb +23 -0
  25. data/lib/lutaml/model/attribute.rb +19 -1
  26. data/lib/lutaml/model/error/liquid_drop_already_registered_error.rb +11 -0
  27. data/lib/lutaml/model/error/ordered_content_mapping_error.rb +17 -0
  28. data/lib/lutaml/model/format_registry.rb +10 -1
  29. data/lib/lutaml/model/global_context.rb +1 -0
  30. data/lib/lutaml/model/liquefiable.rb +12 -15
  31. data/lib/lutaml/model/mapping/mapping_rule.rb +10 -2
  32. data/lib/lutaml/model/mapping_hash.rb +1 -1
  33. data/lib/lutaml/model/serialize/format_conversion.rb +17 -1
  34. data/lib/lutaml/model/services/transformer.rb +67 -32
  35. data/lib/lutaml/model/transform.rb +41 -4
  36. data/lib/lutaml/model/uninitialized_class.rb +11 -5
  37. data/lib/lutaml/model/validation/concerns/has_issues.rb +27 -0
  38. data/lib/lutaml/model/validation/context.rb +36 -0
  39. data/lib/lutaml/model/validation/issue.rb +62 -0
  40. data/lib/lutaml/model/validation/layer_result.rb +34 -0
  41. data/lib/lutaml/model/validation/profile.rb +66 -0
  42. data/lib/lutaml/model/validation/registry.rb +60 -0
  43. data/lib/lutaml/model/validation/remediation.rb +33 -0
  44. data/lib/lutaml/model/validation/remediation_result.rb +20 -0
  45. data/lib/lutaml/model/validation/report.rb +39 -0
  46. data/lib/lutaml/model/validation/rule.rb +59 -0
  47. data/lib/lutaml/model/validation.rb +2 -1
  48. data/lib/lutaml/model/validation_framework.rb +77 -0
  49. data/lib/lutaml/model/version.rb +1 -1
  50. data/lib/lutaml/model.rb +10 -0
  51. data/lib/lutaml/rdf/error.rb +7 -0
  52. data/lib/lutaml/rdf/iri.rb +44 -0
  53. data/lib/lutaml/rdf/language_tagged.rb +11 -0
  54. data/lib/lutaml/rdf/literal.rb +62 -0
  55. data/lib/lutaml/rdf/mapping.rb +71 -0
  56. data/lib/lutaml/rdf/mapping_rule.rb +35 -0
  57. data/lib/lutaml/rdf/member_rule.rb +13 -0
  58. data/lib/lutaml/rdf/namespace.rb +58 -0
  59. data/lib/lutaml/rdf/namespace_set.rb +69 -0
  60. data/lib/lutaml/rdf/namespaces/dcterms_namespace.rb +12 -0
  61. data/lib/lutaml/rdf/namespaces/owl_namespace.rb +12 -0
  62. data/lib/lutaml/rdf/namespaces/rdf_namespace.rb +14 -0
  63. data/lib/lutaml/rdf/namespaces/rdfs_namespace.rb +12 -0
  64. data/lib/lutaml/rdf/namespaces/skos_namespace.rb +12 -0
  65. data/lib/lutaml/rdf/namespaces/xsd_namespace.rb +12 -0
  66. data/lib/lutaml/rdf/namespaces.rb +14 -0
  67. data/lib/lutaml/rdf/transform.rb +36 -0
  68. data/lib/lutaml/rdf.rb +19 -0
  69. data/lib/lutaml/turtle/adapter.rb +35 -0
  70. data/lib/lutaml/turtle/mapping.rb +7 -0
  71. data/lib/lutaml/turtle/transform.rb +158 -0
  72. data/lib/lutaml/turtle.rb +22 -0
  73. data/lib/lutaml/xml/adapter/nokogiri_adapter.rb +9 -2
  74. data/lib/lutaml/xml/adapter/oga_adapter.rb +11 -3
  75. data/lib/lutaml/xml/adapter/ox_adapter.rb +5 -2
  76. data/lib/lutaml/xml/adapter/rexml_adapter.rb +10 -3
  77. data/lib/lutaml/xml/adapter_element.rb +26 -2
  78. data/lib/lutaml/xml/data_model.rb +14 -0
  79. data/lib/lutaml/xml/document.rb +3 -0
  80. data/lib/lutaml/xml/element.rb +8 -2
  81. data/lib/lutaml/xml/mapping.rb +9 -0
  82. data/lib/lutaml/xml/model_transform.rb +42 -0
  83. data/lib/lutaml/xml/schema/xsd/base.rb +4 -1
  84. data/lib/lutaml/xml/serialization/instance_methods.rb +3 -1
  85. data/lib/lutaml/xml/transformation/ordered_applier.rb +46 -2
  86. data/lib/lutaml/xml/transformation.rb +40 -1
  87. data/lib/lutaml/xml/xml_element.rb +8 -7
  88. data/lutaml-model.gemspec +1 -1
  89. data/spec/lutaml/integration/edge_cases_spec.rb +109 -0
  90. data/spec/lutaml/integration/multi_format_spec.rb +106 -0
  91. data/spec/lutaml/integration/round_trip_spec.rb +170 -0
  92. data/spec/lutaml/jsonld/adapter_spec.rb +46 -0
  93. data/spec/lutaml/jsonld/context_spec.rb +114 -0
  94. data/spec/lutaml/jsonld/term_definition_spec.rb +55 -0
  95. data/spec/lutaml/jsonld/transform_spec.rb +211 -0
  96. data/spec/lutaml/model/attribute_default_cache_spec.rb +58 -0
  97. data/spec/lutaml/model/liquefiable_spec.rb +22 -6
  98. data/spec/lutaml/model/liquid_compatibility_spec.rb +442 -0
  99. data/spec/lutaml/model/ordered_content_spec.rb +5 -5
  100. data/spec/lutaml/model/services/transformer_spec.rb +43 -0
  101. data/spec/lutaml/model/transform_cache_spec.rb +62 -0
  102. data/spec/lutaml/model/transform_dynamic_attributes_spec.rb +41 -0
  103. data/spec/lutaml/model/uninitialized_class_deep_dup_spec.rb +39 -0
  104. data/spec/lutaml/model/uninitialized_class_spec.rb +14 -2
  105. data/spec/lutaml/model/validation/concerns/has_issues_spec.rb +76 -0
  106. data/spec/lutaml/model/validation/context_spec.rb +60 -0
  107. data/spec/lutaml/model/validation/issue_spec.rb +77 -0
  108. data/spec/lutaml/model/validation/layer_result_spec.rb +66 -0
  109. data/spec/lutaml/model/validation/profile_spec.rb +134 -0
  110. data/spec/lutaml/model/validation/registry_spec.rb +94 -0
  111. data/spec/lutaml/model/validation/remediation_result_spec.rb +23 -0
  112. data/spec/lutaml/model/validation/remediation_spec.rb +72 -0
  113. data/spec/lutaml/model/validation/report_spec.rb +58 -0
  114. data/spec/lutaml/model/validation/rule_spec.rb +134 -0
  115. data/spec/lutaml/model/validation/uninitialized_class_validate_spec.rb +29 -0
  116. data/spec/lutaml/model/validation/validation_error_spec.rb +29 -0
  117. data/spec/lutaml/model/validation/validation_framework_spec.rb +110 -0
  118. data/spec/lutaml/rdf/graph_serialization_spec.rb +137 -0
  119. data/spec/lutaml/rdf/iri_spec.rb +73 -0
  120. data/spec/lutaml/rdf/literal_spec.rb +98 -0
  121. data/spec/lutaml/rdf/mapping_spec.rb +164 -0
  122. data/spec/lutaml/rdf/member_rule_spec.rb +17 -0
  123. data/spec/lutaml/rdf/namespace_set_spec.rb +115 -0
  124. data/spec/lutaml/rdf/namespace_spec.rb +241 -0
  125. data/spec/lutaml/rdf/rdf_transform_spec.rb +82 -0
  126. data/spec/lutaml/turtle/adapter_spec.rb +47 -0
  127. data/spec/lutaml/turtle/mapping_spec.rb +123 -0
  128. data/spec/lutaml/turtle/transform_spec.rb +273 -0
  129. data/spec/lutaml/xml/content_model_validation_spec.rb +157 -0
  130. data/spec/lutaml/xml/mapping_spec.rb +12 -7
  131. metadata +95 -7
  132. data/spec/fixtures/liquid_templates/_ceramics.liquid +0 -3
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module Validation
6
+ # Mutable validation context that accumulates errors and provides
7
+ # per-rule state. Used when validation needs imperative error
8
+ # accumulation (e.g., streaming SAX validation).
9
+ class Context
10
+ attr_reader :errors
11
+
12
+ def initialize
13
+ @errors = []
14
+ @per_rule_state = {}
15
+ end
16
+
17
+ def add_error(issue)
18
+ @errors << issue
19
+ end
20
+
21
+ def add_errors(issues)
22
+ @errors.concat(issues)
23
+ end
24
+
25
+ def rule_state(rule_code)
26
+ @per_rule_state[rule_code] ||= {}
27
+ end
28
+
29
+ def reset!
30
+ @errors.clear
31
+ @per_rule_state.clear
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module Validation
6
+ # Serializable validation issue with severity, code, location, and
7
+ # suggestion. Used by Rule#check to report problems and aggregated
8
+ # by LayerResult and Report.
9
+ class Issue < Lutaml::Model::Serializable
10
+ # Allowed severity levels for validation issues.
11
+ SEVERITIES = %w[error warning info notice].freeze
12
+
13
+ attribute :severity, :string
14
+ attribute :code, :string
15
+ attribute :message, :string
16
+ attribute :location, :string
17
+ attribute :line, :integer
18
+ attribute :suggestion, :string
19
+
20
+ json do
21
+ map "severity", to: :severity
22
+ map "code", to: :code
23
+ map "message", to: :message
24
+ map "location", to: :location
25
+ map "line", to: :line
26
+ map "suggestion", to: :suggestion
27
+ end
28
+
29
+ def initialize(attributes = {})
30
+ super
31
+ validate_severity!
32
+ end
33
+
34
+ def error?
35
+ severity == "error"
36
+ end
37
+
38
+ def warning?
39
+ severity == "warning"
40
+ end
41
+
42
+ def info?
43
+ severity == "info"
44
+ end
45
+
46
+ def notice?
47
+ severity == "notice"
48
+ end
49
+
50
+ private
51
+
52
+ def validate_severity!
53
+ return if severity.nil? || SEVERITIES.include?(severity)
54
+
55
+ raise ArgumentError,
56
+ "Invalid severity: #{severity}. " \
57
+ "Must be one of: #{SEVERITIES.join(', ')}"
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "issue"
4
+ require_relative "concerns/has_issues"
5
+
6
+ module Lutaml
7
+ module Model
8
+ module Validation
9
+ class LayerResult < Lutaml::Model::Serializable
10
+ include HasIssues
11
+
12
+ attribute :name, :string
13
+ attribute :status, :string
14
+ attribute :duration_ms, :integer
15
+ attribute :issues, Issue, collection: true
16
+
17
+ json do
18
+ map "name", to: :name
19
+ map "status", to: :status
20
+ map "duration_ms", to: :duration_ms
21
+ map "issues", to: :issues
22
+ end
23
+
24
+ def pass?
25
+ status == "pass"
26
+ end
27
+
28
+ def fail?
29
+ status == "fail"
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+
5
+ module Lutaml
6
+ module Model
7
+ module Validation
8
+ # Composable validation profile loaded from YAML. Selects which rules
9
+ # run during validation. Profiles can import other profiles for
10
+ # rule reuse across validation levels (e.g., basic → strict).
11
+ class Profile
12
+ attr_reader :name, :description, :rule_names, :imports
13
+
14
+ def initialize(name:, description: nil, rule_names: [], imports: [])
15
+ @name = name
16
+ @description = description
17
+ @rule_names = rule_names
18
+ @imports = imports
19
+ end
20
+
21
+ def self.load(path)
22
+ data = YAML.safe_load_file(path, symbolize_names: false)
23
+ unless data.is_a?(::Hash) && data["name"].is_a?(String)
24
+ raise ArgumentError,
25
+ "Profile YAML must contain a 'name' string key: #{path}"
26
+ end
27
+
28
+ new(
29
+ name: data["name"],
30
+ description: data["description"],
31
+ rule_names: data["rules"] || [],
32
+ imports: data["import"] || [],
33
+ )
34
+ end
35
+
36
+ def resolve(registry, profiles_by_name = {}, visited: Set.new)
37
+ if visited.include?(name)
38
+ raise ArgumentError,
39
+ "Circular profile import detected: #{name}"
40
+ end
41
+
42
+ resolved = resolve_imports(registry, profiles_by_name,
43
+ visited: visited + [name])
44
+ resolved | resolve_names(registry)
45
+ end
46
+
47
+ private
48
+
49
+ def resolve_names(registry)
50
+ rule_names.filter_map do |name|
51
+ registry.all.find { |r| r.class.name == name }
52
+ end
53
+ end
54
+
55
+ def resolve_imports(registry, profiles_by_name, visited: Set.new)
56
+ imports.flat_map do |import_name|
57
+ imported = profiles_by_name[import_name]
58
+ next [] unless imported
59
+
60
+ imported.resolve(registry, profiles_by_name, visited: visited)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module Validation
6
+ # Instance-based rule registry. Register rule classes, look up by
7
+ # code or category, and instantiate all registered rules for
8
+ # validation runs.
9
+ class Registry
10
+ def initialize
11
+ @rules = []
12
+ @mutex = Mutex.new
13
+ @all = nil
14
+ end
15
+
16
+ def register(rule_class)
17
+ @mutex.synchronize do
18
+ return if @rules.include?(rule_class)
19
+
20
+ @rules << rule_class
21
+ @all = nil
22
+ end
23
+ end
24
+
25
+ def auto_discover(dir, pattern: "**/*_rule.rb")
26
+ Dir.glob(File.join(dir, pattern)).each { |path| require path }
27
+ end
28
+
29
+ def all
30
+ @mutex.synchronize do
31
+ @all ||= @rules.map(&:new)
32
+ end
33
+ end
34
+
35
+ def for_category(category)
36
+ all.select { |r| r.category == category }
37
+ end
38
+
39
+ def find(code)
40
+ all.find { |r| r.code == code }
41
+ end
42
+
43
+ def reset!
44
+ @mutex.synchronize do
45
+ @rules.clear
46
+ @all = nil
47
+ end
48
+ end
49
+
50
+ def rule_classes
51
+ @rules.dup
52
+ end
53
+
54
+ def size
55
+ @rules.size
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module Validation
6
+ # Abstract base class for validation remediation. Subclass and
7
+ # override {#id}, {#targets}, {#applicable?}, {#fix}, and
8
+ # {#preview} to implement auto-fix logic for specific issue codes.
9
+ class Remediation
10
+ def id
11
+ nil
12
+ end
13
+
14
+ def targets
15
+ nil
16
+ end
17
+
18
+ def applicable?(_context, _report)
19
+ true
20
+ end
21
+
22
+ def fix(_context, _report)
23
+ raise NotImplementedError,
24
+ "#{self.class}#fix must be implemented by subclass"
25
+ end
26
+
27
+ def preview(_context, _report)
28
+ nil
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module Validation
6
+ # Result of a remediation fix attempt. Serializable to JSON.
7
+ class RemediationResult < Lutaml::Model::Serializable
8
+ attribute :success, :boolean
9
+ attribute :message, :string
10
+ attribute :fixed_codes, :string, collection: true
11
+
12
+ json do
13
+ map "success", to: :success
14
+ map "message", to: :message
15
+ map "fixed_codes", to: :fixed_codes
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "time"
4
+ require_relative "concerns/has_issues"
5
+ require_relative "issue"
6
+ require_relative "layer_result"
7
+
8
+ module Lutaml
9
+ module Model
10
+ module Validation
11
+ class Report < Lutaml::Model::Serializable
12
+ include HasIssues
13
+
14
+ attribute :source, :string
15
+ attribute :timestamp, :string
16
+ attribute :valid, :boolean
17
+ attribute :duration_ms, :integer
18
+ attribute :layers, LayerResult, collection: true
19
+
20
+ json do
21
+ map "source", to: :source
22
+ map "timestamp", to: :timestamp
23
+ map "valid", to: :valid
24
+ map "duration_ms", to: :duration_ms
25
+ map "layers", to: :layers
26
+ end
27
+
28
+ def initialize(attributes = {})
29
+ super
30
+ self.timestamp ||= Time.now.utc.iso8601
31
+ end
32
+
33
+ def issues
34
+ layers ? layers.flat_map(&:issues) : []
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module Validation
6
+ # Abstract base class for validation rules. Subclass and override
7
+ # {#code}, {#category}, {#severity}, {#applicable?}, and {#check}
8
+ # to implement domain-specific validation logic.
9
+ #
10
+ # Use the private {#issue} helper inside {#check} to create issues
11
+ # that inherit the rule's severity and code by default.
12
+ class Rule
13
+ def code
14
+ nil
15
+ end
16
+
17
+ def category
18
+ :general
19
+ end
20
+
21
+ def severity
22
+ "error"
23
+ end
24
+
25
+ def applicable?(_context)
26
+ true
27
+ end
28
+
29
+ def check(_context)
30
+ []
31
+ end
32
+
33
+ def needs_deferred?
34
+ false
35
+ end
36
+
37
+ def collect(_element, _context); end
38
+
39
+ def complete(_context)
40
+ []
41
+ end
42
+
43
+ private
44
+
45
+ def issue(message, location: nil, line: nil, suggestion: nil,
46
+ severity: nil, code: nil)
47
+ Issue.new(
48
+ severity: severity || self.severity,
49
+ code: code || self.code,
50
+ message: message,
51
+ location: location,
52
+ line: line,
53
+ suggestion: suggestion,
54
+ )
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -11,7 +11,8 @@ module Lutaml
11
11
 
12
12
  begin
13
13
  if value.respond_to?(:validate)
14
- errors.concat(value.validate)
14
+ sub_errors = value.validate
15
+ errors.concat(sub_errors) if sub_errors.is_a?(Array)
15
16
  else
16
17
  resolver = Lutaml::Model::Services::DefaultValueResolver.new(
17
18
  attr, register, self
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "validation/issue"
4
+ require_relative "validation/layer_result"
5
+ require_relative "validation/report"
6
+ require_relative "validation/context"
7
+ require_relative "validation/rule"
8
+ require_relative "validation/registry"
9
+ require_relative "validation/profile"
10
+ require_relative "validation/remediation"
11
+ require_relative "validation/remediation_result"
12
+
13
+ module Lutaml
14
+ module Model
15
+ # Document-level validation framework. Orthogonal to the existing
16
+ # attribute-level Validation module — this validates structural
17
+ # integrity, cross-references, and conformance against domain rules.
18
+ #
19
+ # @example Run all registered rules
20
+ # issues = Lutaml::Model::Validation.validate(context, registry)
21
+ # @example Run and raise on errors
22
+ # Lutaml::Model::Validation.validate!(context, registry)
23
+ module Validation
24
+ class << self
25
+ def new_registry
26
+ Registry.new
27
+ end
28
+
29
+ def validate(context, registry, profile: nil)
30
+ rules = if profile
31
+ profile.resolve(registry)
32
+ else
33
+ registry.all
34
+ end
35
+
36
+ all_issues = []
37
+ rules.each do |rule|
38
+ next unless rule.applicable?(context)
39
+
40
+ issues = rule.check(context)
41
+ if context.respond_to?(:add_error)
42
+ issues.each { |i| context.add_error(i) }
43
+ end
44
+ all_issues.concat(issues)
45
+ end
46
+
47
+ all_issues
48
+ end
49
+
50
+ def validate!(context, registry, profile: nil)
51
+ issues = validate(context, registry, profile: profile)
52
+ return if issues.empty?
53
+
54
+ errors = issues.select(&:error?)
55
+ unless errors.empty?
56
+ raise ValidationError.new(format_errors(errors), issues: errors)
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def format_errors(errors)
63
+ errors.map { |e| "[#{e.code}] #{e.message}" }.join("\n")
64
+ end
65
+ end
66
+
67
+ class ValidationError < StandardError
68
+ attr_reader :issues
69
+
70
+ def initialize(message, issues: [])
71
+ super(message)
72
+ @issues = issues
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- VERSION = "0.8.3"
5
+ VERSION = "0.8.5"
6
6
  end
7
7
  end
data/lib/lutaml/model.rb CHANGED
@@ -10,6 +10,8 @@ module Lutaml
10
10
  autoload :Jsonl, "#{__dir__}/jsonl"
11
11
  autoload :Yamls, "#{__dir__}/yamls"
12
12
  autoload :Xml, "#{__dir__}/xml"
13
+ autoload :JsonLd, "#{__dir__}/jsonld"
14
+ autoload :Turtle, "#{__dir__}/turtle"
13
15
 
14
16
  module Model
15
17
  # Autoloads for lazy loading - set up BEFORE any requires
@@ -115,6 +117,8 @@ module Lutaml
115
117
  "#{__dir__}/model/error/liquid_not_enabled_error"
116
118
  autoload :LiquidClassNotFoundError,
117
119
  "#{__dir__}/model/error/liquid_class_not_found_error"
120
+ autoload :LiquidDropAlreadyRegisteredError,
121
+ "#{__dir__}/model/error/liquid_drop_already_registered_error"
118
122
  autoload :NoAttributesDefinedLiquidError,
119
123
  "#{__dir__}/model/error/no_attributes_defined_liquid_error"
120
124
  autoload :IncorrectMappingArgumentsError,
@@ -184,6 +188,8 @@ module Lutaml
184
188
  "#{__dir__}/model/error/unresolvable_type_error"
185
189
  autoload :MixedContentCollectionError,
186
190
  "#{__dir__}/model/error/mixed_content_collection_error"
191
+ autoload :OrderedContentMappingError,
192
+ "#{__dir__}/model/error/ordered_content_mapping_error"
187
193
 
188
194
  # Error for passing incorrect model type
189
195
  #
@@ -237,6 +243,10 @@ require "#{__dir__}/jsonl"
237
243
  require "#{__dir__}/yamls"
238
244
  require "#{__dir__}/xml"
239
245
 
246
+ # Optional formats: require "lutaml/jsonld" or "lutaml/turtle" to enable.
247
+ # These are not eagerly loaded because they depend on optional gems
248
+ # (e.g., rdf-turtle) that most users don't need.
249
+
240
250
  # Prepend builder interface into Serialize
241
251
  # Builder must be prepended AFTER XML so its initialize runs first
242
252
  # (Builder -> XML InstanceMethods -> Serialize)
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Rdf
5
+ class Error < Lutaml::Model::Error; end
6
+ end
7
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Rdf
5
+ class Iri
6
+ include Comparable
7
+
8
+ attr_reader :value
9
+
10
+ def initialize(uri_string)
11
+ @value = uri_string.to_s.freeze
12
+ end
13
+
14
+ def expand(namespace_set)
15
+ namespace_set.resolve_compact_iri(value)
16
+ end
17
+
18
+ def compact(namespace_set)
19
+ namespace_set.compact(value)
20
+ end
21
+
22
+ def <=>(other)
23
+ other.is_a?(self.class) ? value <=> other.value : nil
24
+ end
25
+
26
+ def ==(other)
27
+ other.is_a?(self.class) && value == other.value
28
+ end
29
+ alias_method :eql?, :==
30
+
31
+ def hash
32
+ value.hash
33
+ end
34
+
35
+ def to_s
36
+ value
37
+ end
38
+
39
+ def inspect
40
+ "#<#{self.class.name} #{value}>"
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Rdf
5
+ module LanguageTagged
6
+ def language_tag
7
+ language
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Rdf
5
+ class Literal
6
+ include LanguageTagged
7
+
8
+ attr_reader :value, :datatype, :language
9
+
10
+ def initialize(value, datatype: nil, language: nil)
11
+ @value = value
12
+ @datatype = datatype
13
+ @language = language
14
+ end
15
+
16
+ def to_turtle
17
+ escaped = escape_turtle(value.to_s)
18
+ if language
19
+ "#{escaped}@#{language}"
20
+ elsif datatype
21
+ "#{escaped}^^<#{datatype}>"
22
+ else
23
+ escaped
24
+ end
25
+ end
26
+
27
+ def to_jsonld_term
28
+ if language
29
+ { "@value" => value, "@language" => language }
30
+ elsif datatype
31
+ { "@value" => value, "@type" => datatype.to_s }
32
+ else
33
+ value
34
+ end
35
+ end
36
+
37
+ def ==(other)
38
+ other.is_a?(self.class) &&
39
+ value == other.value &&
40
+ datatype == other.datatype &&
41
+ language == other.language
42
+ end
43
+ alias_method :eql?, :==
44
+
45
+ def hash
46
+ [value, datatype, language].hash
47
+ end
48
+
49
+ private
50
+
51
+ def escape_turtle(str)
52
+ escaped = str.gsub(/[\n\r\t"\\]/,
53
+ "\\" => "\\\\",
54
+ '"' => "\\\"",
55
+ "\n" => "\\n",
56
+ "\r" => "\\r",
57
+ "\t" => "\\t")
58
+ "\"#{escaped}\""
59
+ end
60
+ end
61
+ end
62
+ end