lutaml-model 0.8.4 → 0.8.6
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/dependent-tests.yml +5 -0
- data/.rubocop.yml +18 -0
- data/.rubocop_todo.yml +91 -22
- data/Gemfile +2 -0
- data/README.adoc +114 -2
- data/docs/_guides/index.adoc +18 -0
- data/docs/_guides/jsonld-serialization.adoc +217 -0
- data/docs/_guides/rdf-serialization.adoc +344 -0
- data/docs/_guides/turtle-serialization.adoc +224 -0
- data/docs/_migrations/0-8-0-namespace-restructuring.adoc +90 -0
- data/docs/_pages/serialization_adapters.adoc +31 -0
- data/docs/_references/index.adoc +1 -0
- data/docs/_references/rdf-namespaces.adoc +243 -0
- data/docs/index.adoc +3 -2
- data/lib/lutaml/jsonld/adapter.rb +23 -0
- data/lib/lutaml/jsonld/context.rb +69 -0
- data/lib/lutaml/jsonld/term_definition.rb +39 -0
- data/lib/lutaml/jsonld/transform.rb +174 -0
- data/lib/lutaml/jsonld.rb +23 -0
- data/lib/lutaml/model/format_registry.rb +10 -1
- data/lib/lutaml/model/serialize/format_conversion.rb +17 -1
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/model.rb +6 -0
- data/lib/lutaml/rdf/error.rb +7 -0
- data/lib/lutaml/rdf/iri.rb +44 -0
- data/lib/lutaml/rdf/language_tagged.rb +11 -0
- data/lib/lutaml/rdf/literal.rb +62 -0
- data/lib/lutaml/rdf/mapping.rb +71 -0
- data/lib/lutaml/rdf/mapping_rule.rb +35 -0
- data/lib/lutaml/rdf/member_rule.rb +13 -0
- data/lib/lutaml/rdf/namespace.rb +58 -0
- data/lib/lutaml/rdf/namespace_set.rb +69 -0
- data/lib/lutaml/rdf/namespaces/dcterms_namespace.rb +12 -0
- data/lib/lutaml/rdf/namespaces/owl_namespace.rb +12 -0
- data/lib/lutaml/rdf/namespaces/rdf_namespace.rb +14 -0
- data/lib/lutaml/rdf/namespaces/rdfs_namespace.rb +12 -0
- data/lib/lutaml/rdf/namespaces/skos_namespace.rb +12 -0
- data/lib/lutaml/rdf/namespaces/xsd_namespace.rb +12 -0
- data/lib/lutaml/rdf/namespaces.rb +14 -0
- data/lib/lutaml/rdf/transform.rb +36 -0
- data/lib/lutaml/rdf.rb +19 -0
- data/lib/lutaml/turtle/adapter.rb +35 -0
- data/lib/lutaml/turtle/mapping.rb +7 -0
- data/lib/lutaml/turtle/transform.rb +158 -0
- data/lib/lutaml/turtle.rb +22 -0
- data/lib/lutaml/xml/adapter/adapter_helpers.rb +1 -42
- data/lib/lutaml/xml/adapter/base_adapter.rb +48 -458
- data/lib/lutaml/xml/adapter/namespace_data.rb +0 -17
- data/lib/lutaml/xml/adapter/namespace_uri_collector.rb +71 -0
- data/lib/lutaml/xml/adapter/nokogiri_adapter.rb +5 -1110
- data/lib/lutaml/xml/adapter/oga_adapter.rb +6 -846
- data/lib/lutaml/xml/adapter/ox_adapter.rb +7 -884
- data/lib/lutaml/xml/adapter/plan_based_builder.rb +929 -0
- data/lib/lutaml/xml/adapter/rexml_adapter.rb +10 -864
- data/lib/lutaml/xml/adapter/xml_parser.rb +86 -0
- data/lib/lutaml/xml/adapter/xml_serializer.rb +291 -0
- data/lib/lutaml/xml/adapter.rb +0 -1
- data/lib/lutaml/xml/adapter_element.rb +7 -1
- data/lib/lutaml/xml/builder/base.rb +0 -1
- data/lib/lutaml/xml/data_model.rb +9 -1
- data/lib/lutaml/xml/document.rb +3 -1
- data/lib/lutaml/xml/element.rb +13 -10
- data/lib/lutaml/xml/serialization/format_conversion.rb +19 -42
- data/lib/lutaml/xml/serialization/instance_methods.rb +26 -35
- data/lib/lutaml/xml/transformation/custom_method_wrapper.rb +34 -55
- data/lib/lutaml/xml/transformation/rule_applier.rb +1 -1
- data/lib/lutaml/xml/xml_element.rb +24 -20
- data/spec/lutaml/integration/edge_cases_spec.rb +109 -0
- data/spec/lutaml/integration/multi_format_spec.rb +106 -0
- data/spec/lutaml/integration/round_trip_spec.rb +170 -0
- data/spec/lutaml/jsonld/adapter_spec.rb +46 -0
- data/spec/lutaml/jsonld/context_spec.rb +114 -0
- data/spec/lutaml/jsonld/term_definition_spec.rb +55 -0
- data/spec/lutaml/jsonld/transform_spec.rb +211 -0
- data/spec/lutaml/rdf/graph_serialization_spec.rb +137 -0
- data/spec/lutaml/rdf/iri_spec.rb +73 -0
- data/spec/lutaml/rdf/literal_spec.rb +98 -0
- data/spec/lutaml/rdf/mapping_spec.rb +164 -0
- data/spec/lutaml/rdf/member_rule_spec.rb +17 -0
- data/spec/lutaml/rdf/namespace_set_spec.rb +115 -0
- data/spec/lutaml/rdf/namespace_spec.rb +241 -0
- data/spec/lutaml/rdf/rdf_transform_spec.rb +82 -0
- data/spec/lutaml/turtle/adapter_spec.rb +47 -0
- data/spec/lutaml/turtle/mapping_spec.rb +123 -0
- data/spec/lutaml/turtle/transform_spec.rb +273 -0
- data/spec/lutaml/xml/adapter/base_adapter_regression_spec.rb +151 -0
- data/spec/lutaml/xml/adapter/order_spec.rb +150 -0
- data/spec/lutaml/xml/clear_parse_state_spec.rb +139 -0
- data/spec/lutaml/xml/doubly_defined_namespace_spec.rb +0 -2
- data/spec/lutaml/xml/schema/compiler_spec.rb +75 -69
- data/spec/lutaml/xml/transformation/custom_method_wrapper_spec.rb +213 -14
- metadata +58 -3
- data/lib/lutaml/xml/adapter/xml_serialization.rb +0 -145
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe "#clear_xml_parse_state!" do
|
|
6
|
+
before do
|
|
7
|
+
Lutaml::Model::GlobalContext.clear_caches
|
|
8
|
+
Lutaml::Model::TransformationRegistry.instance.clear
|
|
9
|
+
Lutaml::Model::GlobalRegister.instance.reset
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
let(:ns_class) do
|
|
13
|
+
Class.new(Lutaml::Xml::Namespace) do
|
|
14
|
+
uri "http://example.com/items"
|
|
15
|
+
prefix_default "a"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
let(:model_class) do
|
|
20
|
+
ns = ns_class
|
|
21
|
+
Class.new(Lutaml::Model::Serializable) do
|
|
22
|
+
attribute :item, :string
|
|
23
|
+
attribute :count, :integer
|
|
24
|
+
|
|
25
|
+
xml do
|
|
26
|
+
root "root"
|
|
27
|
+
namespace ns
|
|
28
|
+
map_element "item", to: :item
|
|
29
|
+
map_attribute "count", to: :count
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
let(:xml_with_ns) do
|
|
35
|
+
<<~XML
|
|
36
|
+
<root xmlns:xyz="http://example.com/items" count="3">
|
|
37
|
+
<xyz:item>hello</xyz:item>
|
|
38
|
+
</root>
|
|
39
|
+
XML
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
describe "clearing parse state" do
|
|
43
|
+
it "clears import_declaration_plan set by :eager mode" do
|
|
44
|
+
model = model_class.from_xml(xml_with_ns, import_declaration_plan: :eager)
|
|
45
|
+
expect(model.import_declaration_plan).to be_a(Lutaml::Xml::DeclarationPlan)
|
|
46
|
+
model.clear_xml_parse_state!
|
|
47
|
+
expect(model.import_declaration_plan).to be_nil
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it "clears pending_plan_root_element set by :lazy mode" do
|
|
51
|
+
model = model_class.from_xml(xml_with_ns)
|
|
52
|
+
expect(model.pending_plan_root_element).not_to be_nil
|
|
53
|
+
model.clear_xml_parse_state!
|
|
54
|
+
expect(model.pending_plan_root_element).to be_nil
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
describe "return value" do
|
|
59
|
+
it "returns self for chaining" do
|
|
60
|
+
model = model_class.from_xml(xml_with_ns)
|
|
61
|
+
expect(model.clear_xml_parse_state!).to equal(model)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
describe "idempotency" do
|
|
66
|
+
it "is safe to call multiple times" do
|
|
67
|
+
model = model_class.from_xml(xml_with_ns)
|
|
68
|
+
expect { 3.times { model.clear_xml_parse_state! } }.not_to raise_error
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it "is safe on freshly created instances with no parse state" do
|
|
72
|
+
model = model_class.new(item: "test", count: 1)
|
|
73
|
+
expect { model.clear_xml_parse_state! }.not_to raise_error
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
describe "user-facing attributes" do
|
|
78
|
+
it "does not clear element_order" do
|
|
79
|
+
model = model_class.from_xml(xml_with_ns)
|
|
80
|
+
original_order = model.element_order
|
|
81
|
+
model.clear_xml_parse_state!
|
|
82
|
+
expect(model.element_order).to eq(original_order)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it "does not clear encoding" do
|
|
86
|
+
model = model_class.from_xml(xml_with_ns, encoding: "UTF-8")
|
|
87
|
+
model.clear_xml_parse_state!
|
|
88
|
+
expect(model.encoding).to eq("UTF-8")
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it "does not clear model attributes" do
|
|
92
|
+
model = model_class.from_xml(xml_with_ns)
|
|
93
|
+
expect(model.item).to eq("hello")
|
|
94
|
+
expect(model.count).to eq(3)
|
|
95
|
+
model.clear_xml_parse_state!
|
|
96
|
+
expect(model.item).to eq("hello")
|
|
97
|
+
expect(model.count).to eq(3)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
describe "re-serialization after clearing" do
|
|
102
|
+
it "allows to_xml after clearing parse state" do
|
|
103
|
+
model = model_class.from_xml(xml_with_ns, import_declaration_plan: :eager)
|
|
104
|
+
model.clear_xml_parse_state!
|
|
105
|
+
result = model.to_xml
|
|
106
|
+
expect(result).to include("hello")
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
it "allows to_xml after clearing lazy parse state" do
|
|
110
|
+
model = model_class.from_xml(xml_with_ns)
|
|
111
|
+
model.clear_xml_parse_state!
|
|
112
|
+
result = model.to_xml
|
|
113
|
+
expect(result).to include("hello")
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
it "reflects model modifications after clearing" do
|
|
117
|
+
model = model_class.from_xml(xml_with_ns)
|
|
118
|
+
model.item = "modified"
|
|
119
|
+
model.clear_xml_parse_state!
|
|
120
|
+
result = model.to_xml
|
|
121
|
+
expect(result).to include("modified")
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
describe "Uniword parse-modify-clear-reserialize workflow" do
|
|
126
|
+
it "clears stale namespace state across multiple parts" do
|
|
127
|
+
part1 = model_class.from_xml(xml_with_ns, import_declaration_plan: :eager)
|
|
128
|
+
part2 = model_class.from_xml(xml_with_ns, import_declaration_plan: :eager)
|
|
129
|
+
|
|
130
|
+
part1.item = "reconciled"
|
|
131
|
+
part2.item = "also reconciled"
|
|
132
|
+
|
|
133
|
+
[part1, part2].each(&:clear_xml_parse_state!)
|
|
134
|
+
|
|
135
|
+
expect(part1.to_xml).to include("reconciled")
|
|
136
|
+
expect(part2.to_xml).to include("also reconciled")
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
@@ -375,7 +375,6 @@ RSpec.describe "Doubly-defined namespace prefixes" do
|
|
|
375
375
|
it "builds plan immediately during parsing" do
|
|
376
376
|
model = model_class.from_xml(prefixed_xml,
|
|
377
377
|
import_declaration_plan: :eager)
|
|
378
|
-
expect(model.pending_namespace_data).to be_nil
|
|
379
378
|
expect(model.import_declaration_plan).to be_a(Lutaml::Xml::DeclarationPlan)
|
|
380
379
|
end
|
|
381
380
|
end
|
|
@@ -384,7 +383,6 @@ RSpec.describe "Doubly-defined namespace prefixes" do
|
|
|
384
383
|
it "never builds plan" do
|
|
385
384
|
model = model_class.from_xml(prefixed_xml,
|
|
386
385
|
import_declaration_plan: :skip)
|
|
387
|
-
expect(model.pending_namespace_data).to be_nil
|
|
388
386
|
expect(model.import_declaration_plan).to be_nil
|
|
389
387
|
end
|
|
390
388
|
end
|
|
@@ -51,18 +51,14 @@ RSpec.describe Lutaml::Model::Schema::XmlCompiler do
|
|
|
51
51
|
context "with valid xml schema, it generates the models" do
|
|
52
52
|
before do
|
|
53
53
|
described_class.to_models(schema, output_dir: dir,
|
|
54
|
-
create_files: true, module_namespace:
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
end
|
|
54
|
+
create_files: true, module_namespace: "MathTestSpec")
|
|
55
|
+
require File.join(dir, "mathtestspec_registry.rb")
|
|
56
|
+
MathTestSpec.register_all
|
|
58
57
|
end
|
|
59
58
|
|
|
60
59
|
after do
|
|
61
60
|
FileUtils.rm_rf(dir)
|
|
62
|
-
|
|
63
|
-
Object.send(:remove_const, :CTMathTest) if defined?(CTMathTest)
|
|
64
|
-
Object.send(:remove_const, :StInteger255) if defined?(StInteger255)
|
|
65
|
-
Object.send(:remove_const, :Long) if defined?(Long)
|
|
61
|
+
Object.send(:remove_const, :MathTestSpec) if defined?(MathTestSpec)
|
|
66
62
|
end
|
|
67
63
|
|
|
68
64
|
let(:dir) { Dir.mktmpdir }
|
|
@@ -87,40 +83,30 @@ RSpec.describe Lutaml::Model::Schema::XmlCompiler do
|
|
|
87
83
|
INVALID_XML_EXAMPLE
|
|
88
84
|
end
|
|
89
85
|
|
|
90
|
-
it "validates if the files exist in the directory" do
|
|
91
|
-
|
|
92
|
-
expect(File).to exist("#{
|
|
93
|
-
expect(File).to exist("#{
|
|
86
|
+
it "validates if the files exist in the module directory" do
|
|
87
|
+
module_dir = File.join(dir, "mathtestspec")
|
|
88
|
+
expect(File).to exist("#{module_dir}/ct_math_test.rb")
|
|
89
|
+
expect(File).to exist("#{module_dir}/st_integer255.rb")
|
|
90
|
+
expect(File).to exist("#{module_dir}/long.rb")
|
|
94
91
|
end
|
|
95
92
|
|
|
96
93
|
it "validates if the CTMathTest class is loaded" do
|
|
97
|
-
expect(defined?(CTMathTest)).to eq("constant")
|
|
94
|
+
expect(defined?(MathTestSpec::CTMathTest)).to eq("constant")
|
|
98
95
|
end
|
|
99
96
|
|
|
100
97
|
it "creates the model files, requires them, and tests them with valid and invalid xml" do
|
|
101
|
-
expect(CTMathTest.from_xml(valid_value_xml_example).to_xml).to be_xml_equivalent_to(valid_value_xml_example)
|
|
98
|
+
expect(MathTestSpec::CTMathTest.from_xml(valid_value_xml_example).to_xml).to be_xml_equivalent_to(valid_value_xml_example)
|
|
102
99
|
end
|
|
103
100
|
|
|
104
101
|
it "raises error when processing invalid example" do
|
|
105
102
|
expect do
|
|
106
|
-
CTMathTest.from_xml(invalid_value_xml_example)
|
|
103
|
+
MathTestSpec::CTMathTest.from_xml(invalid_value_xml_example)
|
|
107
104
|
end.to raise_error(Lutaml::Model::Type::MinBoundError)
|
|
108
105
|
end
|
|
109
106
|
end
|
|
110
107
|
|
|
111
108
|
context "when processing examples from classes/files generated by valid xml schema" do
|
|
112
|
-
|
|
113
|
-
Dir.mktmpdir do |dir|
|
|
114
|
-
described_class.to_models(
|
|
115
|
-
File.read("spec/fixtures/xml/math_document_schema.xsd"),
|
|
116
|
-
output_dir: dir,
|
|
117
|
-
create_files: true,
|
|
118
|
-
module_namespace: nil,
|
|
119
|
-
)
|
|
120
|
-
require_relative "#{dir}/math_document"
|
|
121
|
-
end
|
|
122
|
-
end
|
|
123
|
-
|
|
109
|
+
let!(:math_doc_dir) { Dir.mktmpdir }
|
|
124
110
|
let(:valid_example) do
|
|
125
111
|
File.read("spec/fixtures/xml/valid_math_document.xml")
|
|
126
112
|
end
|
|
@@ -128,42 +114,39 @@ RSpec.describe Lutaml::Model::Schema::XmlCompiler do
|
|
|
128
114
|
File.read("spec/fixtures/xml/invalid_math_document.xml")
|
|
129
115
|
end
|
|
130
116
|
|
|
117
|
+
before do
|
|
118
|
+
described_class.to_models(
|
|
119
|
+
File.read("spec/fixtures/xml/math_document_schema.xsd"),
|
|
120
|
+
output_dir: math_doc_dir,
|
|
121
|
+
create_files: true,
|
|
122
|
+
module_namespace: "MathDocSpec",
|
|
123
|
+
)
|
|
124
|
+
require File.join(math_doc_dir, "mathdocspec_registry.rb")
|
|
125
|
+
MathDocSpec.register_all
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
after do
|
|
129
|
+
FileUtils.rm_rf(math_doc_dir)
|
|
130
|
+
Object.send(:remove_const, :MathDocSpec) if defined?(MathDocSpec)
|
|
131
|
+
end
|
|
132
|
+
|
|
131
133
|
it "does not raise error with valid example and creates files" do
|
|
132
|
-
expect(defined?(MathDocument)).to eq("constant")
|
|
133
|
-
parsed = MathDocument.from_xml(valid_example)
|
|
134
|
+
expect(defined?(MathDocSpec::MathDocument)).to eq("constant")
|
|
135
|
+
parsed = MathDocSpec::MathDocument.from_xml(valid_example)
|
|
134
136
|
expect(parsed.title).to eql("Example Title")
|
|
135
137
|
expect(parsed.ipv4_address).to eql("192.168.1.1")
|
|
136
138
|
expect(parsed.to_xml).to be_xml_equivalent_to(valid_example)
|
|
137
139
|
end
|
|
138
140
|
|
|
139
141
|
it "raises PatternNotMatchedError" do
|
|
140
|
-
expect(defined?(MathDocument)).to eq("constant")
|
|
141
|
-
expect { MathDocument.from_xml(invalid_example) }
|
|
142
|
+
expect(defined?(MathDocSpec::MathDocument)).to eq("constant")
|
|
143
|
+
expect { MathDocSpec::MathDocument.from_xml(invalid_example) }
|
|
142
144
|
.to raise_error(Lutaml::Model::Type::PatternNotMatchedError)
|
|
143
145
|
end
|
|
144
146
|
end
|
|
145
147
|
|
|
146
148
|
context "when processing example from lutaml-model#260" do
|
|
147
|
-
|
|
148
|
-
# Remove any existing Address constant to prevent test pollution
|
|
149
|
-
Object.send(:remove_const, :Address) if defined?(Address)
|
|
150
|
-
|
|
151
|
-
Dir.mktmpdir do |dir|
|
|
152
|
-
described_class.to_models(
|
|
153
|
-
File.read("spec/fixtures/xml/address_example_260.xsd"),
|
|
154
|
-
output_dir: dir,
|
|
155
|
-
create_files: true,
|
|
156
|
-
module_namespace: nil,
|
|
157
|
-
)
|
|
158
|
-
load "#{dir}/address.rb"
|
|
159
|
-
end
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
after do
|
|
163
|
-
# Clean up the Address constant to prevent test pollution
|
|
164
|
-
Object.send(:remove_const, :Address) if defined?(Address)
|
|
165
|
-
end
|
|
166
|
-
|
|
149
|
+
let!(:address_dir) { Dir.mktmpdir }
|
|
167
150
|
let(:address) do
|
|
168
151
|
<<~ADD
|
|
169
152
|
<Address>
|
|
@@ -174,43 +157,66 @@ RSpec.describe Lutaml::Model::Schema::XmlCompiler do
|
|
|
174
157
|
ADD
|
|
175
158
|
end
|
|
176
159
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
160
|
+
before do
|
|
161
|
+
described_class.to_models(
|
|
162
|
+
File.read("spec/fixtures/xml/address_example_260.xsd"),
|
|
163
|
+
output_dir: address_dir,
|
|
164
|
+
create_files: true,
|
|
165
|
+
module_namespace: "CompilerSpec260",
|
|
166
|
+
)
|
|
167
|
+
require File.join(address_dir, "compilerspec260_registry.rb")
|
|
168
|
+
CompilerSpec260.register_all
|
|
180
169
|
end
|
|
181
|
-
end
|
|
182
170
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
output_dir: dir,
|
|
189
|
-
create_files: true,
|
|
190
|
-
module_namespace: nil,
|
|
191
|
-
)
|
|
192
|
-
require_relative "#{dir}/product_catalog"
|
|
171
|
+
after do
|
|
172
|
+
FileUtils.rm_rf(address_dir)
|
|
173
|
+
if defined?(CompilerSpec260)
|
|
174
|
+
Object.send(:remove_const,
|
|
175
|
+
:CompilerSpec260)
|
|
193
176
|
end
|
|
194
177
|
end
|
|
195
178
|
|
|
179
|
+
it "matches parsed xml with input" do
|
|
180
|
+
expect(defined?(CompilerSpec260::Address)).to eq("constant")
|
|
181
|
+
expect(CompilerSpec260::Address.from_xml(address).to_xml).to be_xml_equivalent_to(address)
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
context "when processing example from files generated by schema -> product_catalog.xsd" do
|
|
186
|
+
let!(:catalog_dir) { Dir.mktmpdir }
|
|
196
187
|
let(:product_catalog) do
|
|
197
188
|
File.read("spec/fixtures/xml/examples/valid_catalog.xml")
|
|
198
189
|
end
|
|
199
|
-
|
|
200
190
|
let(:nested_category) do
|
|
201
191
|
File.read("spec/fixtures/xml/examples/nested_categories.xml")
|
|
202
192
|
end
|
|
203
193
|
|
|
194
|
+
before do
|
|
195
|
+
described_class.to_models(
|
|
196
|
+
File.read("spec/fixtures/xml/product_catalog.xsd"),
|
|
197
|
+
output_dir: catalog_dir,
|
|
198
|
+
create_files: true,
|
|
199
|
+
module_namespace: "CatalogSpec",
|
|
200
|
+
)
|
|
201
|
+
require File.join(catalog_dir, "catalogspec_registry.rb")
|
|
202
|
+
CatalogSpec.register_all
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
after do
|
|
206
|
+
FileUtils.rm_rf(catalog_dir)
|
|
207
|
+
Object.send(:remove_const, :CatalogSpec) if defined?(CatalogSpec)
|
|
208
|
+
end
|
|
209
|
+
|
|
204
210
|
it "confirms the ProductCatalog class is required" do
|
|
205
|
-
expect(defined?(ProductCatalog)).to eq("constant")
|
|
211
|
+
expect(defined?(CatalogSpec::ProductCatalog)).to eq("constant")
|
|
206
212
|
end
|
|
207
213
|
|
|
208
214
|
it "confirms that the from_xml and to_xml methods successfully handles xml for valid_catalog.xml" do
|
|
209
|
-
expect(ProductCatalog.from_xml(product_catalog).to_xml).to be_a(String)
|
|
215
|
+
expect(CatalogSpec::ProductCatalog.from_xml(product_catalog).to_xml).to be_a(String)
|
|
210
216
|
end
|
|
211
217
|
|
|
212
218
|
it "confirms that the from_xml and to_xml methods successfully handles xml for nested_categories.xml" do
|
|
213
|
-
expect(ProductCatalog.from_xml(nested_category).to_xml).to be_a(String)
|
|
219
|
+
expect(CatalogSpec::ProductCatalog.from_xml(nested_category).to_xml).to be_a(String)
|
|
214
220
|
end
|
|
215
221
|
end
|
|
216
222
|
|
|
@@ -4,30 +4,229 @@ require "spec_helper"
|
|
|
4
4
|
require "lutaml/xml"
|
|
5
5
|
|
|
6
6
|
RSpec.describe Lutaml::Xml::CustomMethodWrapper do
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
let(:parent) { Lutaml::Xml::DataModel::XmlElement.new("parent") }
|
|
8
|
+
let(:wrapper) { described_class.new(parent) }
|
|
9
|
+
|
|
10
|
+
describe "#create_element" do
|
|
11
|
+
it "returns a new XmlElement with the given name" do
|
|
12
|
+
el = wrapper.create_element("foo")
|
|
13
|
+
expect(el).to be_a(Lutaml::Xml::DataModel::XmlElement)
|
|
14
|
+
expect(el.name).to eq("foo")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it "does not add the element to any parent" do
|
|
18
|
+
wrapper.create_element("orphan")
|
|
19
|
+
expect(parent.children).to be_empty
|
|
20
|
+
end
|
|
21
|
+
end
|
|
11
22
|
|
|
12
|
-
|
|
13
|
-
|
|
23
|
+
describe "#add_element" do
|
|
24
|
+
it "adds an XmlElement child with single-argument form" do
|
|
25
|
+
child = Lutaml::Xml::DataModel::XmlElement.new("child")
|
|
26
|
+
result = wrapper.add_element(child)
|
|
27
|
+
expect(result).to eq(child)
|
|
28
|
+
expect(parent.children).to eq([child])
|
|
29
|
+
end
|
|
14
30
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
)
|
|
31
|
+
it "adds an XmlElement child with two-argument form" do
|
|
32
|
+
child = Lutaml::Xml::DataModel::XmlElement.new("child")
|
|
33
|
+
wrapper.add_element(parent, child)
|
|
34
|
+
expect(parent.children).to eq([child])
|
|
35
|
+
end
|
|
19
36
|
|
|
20
|
-
|
|
37
|
+
it "parses XML string fragments via Moxml" do
|
|
38
|
+
wrapper.add_element(parent, "<a title=\"copyright\">one</a><b>two</b>")
|
|
21
39
|
expect(parent.children.map(&:name)).to eq(%w[a b])
|
|
22
40
|
expect(parent.children[0].attributes.first.value).to eq("copyright")
|
|
23
41
|
expect(parent.children[0].text_content).to eq("one")
|
|
24
42
|
expect(parent.children[1].text_content).to eq("two")
|
|
25
43
|
end
|
|
44
|
+
|
|
45
|
+
it "raises TypeError for unsupported types" do
|
|
46
|
+
foreign = instance_double(Object)
|
|
47
|
+
expect do
|
|
48
|
+
wrapper.add_element(parent, foreign)
|
|
49
|
+
end.to raise_error(TypeError,
|
|
50
|
+
/add_element expects a String or XmlElement/)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it "parses nested XML elements recursively" do
|
|
54
|
+
wrapper.add_element(parent, "<outer><inner>text</inner></outer>")
|
|
55
|
+
outer = parent.children.first
|
|
56
|
+
expect(outer.name).to eq("outer")
|
|
57
|
+
expect(outer.children.first.name).to eq("inner")
|
|
58
|
+
expect(outer.children.first.text_content).to eq("text")
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
describe "#add_text" do
|
|
63
|
+
it "sets text_content on the given element" do
|
|
64
|
+
el = Lutaml::Xml::DataModel::XmlElement.new("p")
|
|
65
|
+
wrapper.add_text(el, "hello")
|
|
66
|
+
expect(el.text_content).to eq("hello")
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it "sets text on current context when element is nil" do
|
|
70
|
+
wrapper.add_text(nil, "fallback")
|
|
71
|
+
expect(parent.text_content).to eq("fallback")
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
it "sets text on current context when wrapper itself is passed" do
|
|
75
|
+
wrapper.add_text(wrapper, "via-doc")
|
|
76
|
+
expect(parent.text_content).to eq("via-doc")
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
describe "#add_attribute" do
|
|
81
|
+
it "adds an attribute to the element" do
|
|
82
|
+
el = Lutaml::Xml::DataModel::XmlElement.new("node")
|
|
83
|
+
wrapper.add_attribute(el, "id", "42")
|
|
84
|
+
expect(el.attributes.size).to eq(1)
|
|
85
|
+
expect(el.attributes.first.name).to eq("id")
|
|
86
|
+
expect(el.attributes.first.value).to eq("42")
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it "coerces name and value to strings" do
|
|
90
|
+
el = Lutaml::Xml::DataModel::XmlElement.new("node")
|
|
91
|
+
wrapper.add_attribute(el, :key, 123)
|
|
92
|
+
expect(el.attributes.first.name).to eq("key")
|
|
93
|
+
expect(el.attributes.first.value).to eq("123")
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
describe "#create_and_add_element" do
|
|
98
|
+
it "creates and adds element to current context" do
|
|
99
|
+
el = wrapper.create_and_add_element("item")
|
|
100
|
+
expect(el.name).to eq("item")
|
|
101
|
+
expect(parent.children).to eq([el])
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it "creates element with attributes" do
|
|
105
|
+
el = wrapper.create_and_add_element("item", attributes: { id: "1" })
|
|
106
|
+
expect(el.attributes.first.name).to eq("id")
|
|
107
|
+
expect(el.attributes.first.value).to eq("1")
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
it "yields ElementWrapper in block form" do
|
|
111
|
+
wrapper.create_and_add_element("outer") do |w|
|
|
112
|
+
w.add_text(w, "content")
|
|
113
|
+
end
|
|
114
|
+
outer = parent.children.first
|
|
115
|
+
expect(outer.text_content).to eq("content")
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
it "supports nested create_and_add_element in block" do
|
|
119
|
+
wrapper.create_and_add_element("outer") do |w|
|
|
120
|
+
w.create_and_add_element("inner") do |iw|
|
|
121
|
+
iw.add_text(iw, "deep")
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
outer = parent.children.first
|
|
125
|
+
inner = outer.children.first
|
|
126
|
+
expect(inner.name).to eq("inner")
|
|
127
|
+
expect(inner.text_content).to eq("deep")
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
it "restores context after block exits" do
|
|
131
|
+
wrapper.create_and_add_element("outer") do |_w|
|
|
132
|
+
# context is now "outer"
|
|
133
|
+
end
|
|
134
|
+
# context should be back to parent
|
|
135
|
+
el = wrapper.create_and_add_element("after")
|
|
136
|
+
expect(parent.children).to include(el)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
describe "#push_context / #pop_context" do
|
|
141
|
+
it "manages a stack of context elements" do
|
|
142
|
+
child = Lutaml::Xml::DataModel::XmlElement.new("child")
|
|
143
|
+
wrapper.push_context(child)
|
|
144
|
+
expect(wrapper.current_context).to eq(child)
|
|
145
|
+
wrapper.pop_context
|
|
146
|
+
expect(wrapper.current_context).to eq(parent)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
it "does not pop below the root context" do
|
|
150
|
+
wrapper.pop_context
|
|
151
|
+
expect(wrapper.current_context).to eq(parent)
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
describe ".build_element" do
|
|
156
|
+
it "creates an element with attributes" do
|
|
157
|
+
el = described_class.build_element("tag", { a: "1", b: "2" })
|
|
158
|
+
expect(el.name).to eq("tag")
|
|
159
|
+
expect(el.attributes.map(&:name)).to eq(%w[a b])
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
it "handles nil attributes hash" do
|
|
163
|
+
el = described_class.build_element("tag", nil)
|
|
164
|
+
expect(el.attributes).to be_empty
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
describe Lutaml::Xml::CustomMethodWrapper::ElementWrapper do
|
|
169
|
+
let(:element) { Lutaml::Xml::DataModel::XmlElement.new("el") }
|
|
170
|
+
let(:ew) { described_class.new(element) }
|
|
171
|
+
|
|
172
|
+
describe "#add_text" do
|
|
173
|
+
it "sets text and cdata flag" do
|
|
174
|
+
ew.add_text(ew, "data", cdata: true)
|
|
175
|
+
expect(element.text_content).to eq("data")
|
|
176
|
+
expect(element.cdata).to be true
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
it "handles cdata as hash format" do
|
|
180
|
+
ew.add_text(ew, "data", cdata: { cdata: true })
|
|
181
|
+
expect(element.cdata).to be true
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
it "defaults cdata to false" do
|
|
185
|
+
ew.add_text(ew, "data")
|
|
186
|
+
expect(element.cdata).to be false
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
describe "#create_and_add_element" do
|
|
191
|
+
it "adds child to wrapped element" do
|
|
192
|
+
child = ew.create_and_add_element("sub")
|
|
193
|
+
expect(element.children).to eq([child])
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
it "yields nested wrapper in block form" do
|
|
197
|
+
ew.create_and_add_element("sub") do |sub|
|
|
198
|
+
sub.add_text(sub, "text")
|
|
199
|
+
end
|
|
200
|
+
expect(element.children.first.text_content).to eq("text")
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
describe "XmlElement#add_child type guard" do
|
|
206
|
+
it "accepts XmlElement" do
|
|
207
|
+
child = Lutaml::Xml::DataModel::XmlElement.new("ok")
|
|
208
|
+
expect { parent.add_child(child) }.not_to raise_error
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
it "accepts String" do
|
|
212
|
+
expect { parent.add_child("text") }.not_to raise_error
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
it "accepts XmlComment" do
|
|
216
|
+
comment = Lutaml::Xml::DataModel::XmlComment.new("a comment")
|
|
217
|
+
expect { parent.add_child(comment) }.not_to raise_error
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
it "rejects foreign types" do
|
|
221
|
+
expect do
|
|
222
|
+
parent.add_child(42)
|
|
223
|
+
end.to raise_error(TypeError, /XmlElement#add_child expects/)
|
|
224
|
+
end
|
|
26
225
|
end
|
|
27
226
|
|
|
28
227
|
describe "RuleApplier integration" do
|
|
29
228
|
it "loads the wrapper through the public XML autoload path" do
|
|
30
|
-
|
|
229
|
+
parent_el = Lutaml::Xml::DataModel::XmlElement.new("parent")
|
|
31
230
|
model_class = Class.new do
|
|
32
231
|
def custom_to_xml(_model, parent, wrapper)
|
|
33
232
|
wrapper.add_text(parent, "ok")
|
|
@@ -40,9 +239,9 @@ RSpec.describe Lutaml::Xml::CustomMethodWrapper do
|
|
|
40
239
|
public :apply_custom_method
|
|
41
240
|
end.new
|
|
42
241
|
|
|
43
|
-
applier.apply_custom_method(
|
|
242
|
+
applier.apply_custom_method(parent_el, rule, model_class, Object.new)
|
|
44
243
|
|
|
45
|
-
expect(
|
|
244
|
+
expect(parent_el.text_content).to eq("ok")
|
|
46
245
|
end
|
|
47
246
|
end
|
|
48
247
|
end
|