yaml-ld 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/AUTHORS +1 -0
- data/README.md +150 -0
- data/UNLICENSE +24 -0
- data/VERSION +1 -0
- data/lib/yaml_ld/api.rb +295 -0
- data/lib/yaml_ld/format.rb +56 -0
- data/lib/yaml_ld/reader.rb +40 -0
- data/lib/yaml_ld/version.rb +20 -0
- data/lib/yaml_ld/writer.rb +42 -0
- data/lib/yaml_ld.rb +37 -0
- data/spec/api_spec.rb +92 -0
- data/spec/compact_spec.rb +358 -0
- data/spec/expand_spec.rb +565 -0
- data/spec/flatten_spec.rb +225 -0
- data/spec/format_spec.rb +54 -0
- data/spec/frame_spec.rb +662 -0
- data/spec/from_rdf_spec.rb +730 -0
- data/spec/matchers.rb +22 -0
- data/spec/reader_spec.rb +138 -0
- data/spec/spec_helper.rb +265 -0
- data/spec/support/extensions.rb +44 -0
- data/spec/test-files/test-1-compacted.jsonld +10 -0
- data/spec/test-files/test-1-context.jsonld +7 -0
- data/spec/test-files/test-1-expanded.jsonld +5 -0
- data/spec/test-files/test-1-input.yamlld +8 -0
- data/spec/test-files/test-1-rdf.ttl +8 -0
- data/spec/test-files/test-2-compacted.jsonld +20 -0
- data/spec/test-files/test-2-context.jsonld +7 -0
- data/spec/test-files/test-2-expanded.jsonld +16 -0
- data/spec/test-files/test-2-input.yamlld +16 -0
- data/spec/test-files/test-2-rdf.ttl +14 -0
- data/spec/test-files/test-3-compacted.jsonld +11 -0
- data/spec/test-files/test-3-context.jsonld +8 -0
- data/spec/test-files/test-3-expanded.jsonld +10 -0
- data/spec/test-files/test-3-input.yamlld +13 -0
- data/spec/test-files/test-3-rdf.ttl +8 -0
- data/spec/test-files/test-4-compacted.jsonld +10 -0
- data/spec/test-files/test-4-context.jsonld +7 -0
- data/spec/test-files/test-4-expanded.jsonld +6 -0
- data/spec/test-files/test-4-input.yamlld +9 -0
- data/spec/test-files/test-4-rdf.ttl +5 -0
- data/spec/test-files/test-5-compacted.jsonld +13 -0
- data/spec/test-files/test-5-context.jsonld +7 -0
- data/spec/test-files/test-5-expanded.jsonld +9 -0
- data/spec/test-files/test-5-input.yamlld +10 -0
- data/spec/test-files/test-5-rdf.ttl +7 -0
- data/spec/test-files/test-6-compacted.jsonld +10 -0
- data/spec/test-files/test-6-context.jsonld +7 -0
- data/spec/test-files/test-6-expanded.jsonld +10 -0
- data/spec/test-files/test-6-input.yamlld +12 -0
- data/spec/test-files/test-6-rdf.ttl +6 -0
- data/spec/test-files/test-7-compacted.jsonld +23 -0
- data/spec/test-files/test-7-context.jsonld +4 -0
- data/spec/test-files/test-7-expanded.jsonld +20 -0
- data/spec/test-files/test-7-input.yamlld +16 -0
- data/spec/test-files/test-7-rdf.ttl +14 -0
- data/spec/test-files/test-8-compacted.jsonld +34 -0
- data/spec/test-files/test-8-context.jsonld +11 -0
- data/spec/test-files/test-8-expanded.jsonld +24 -0
- data/spec/test-files/test-8-frame.jsonld +18 -0
- data/spec/test-files/test-8-framed.jsonld +25 -0
- data/spec/test-files/test-8-input.yamlld +24 -0
- data/spec/test-files/test-8-rdf.ttl +15 -0
- data/spec/test-files/test-9-compacted.jsonld +20 -0
- data/spec/test-files/test-9-context.jsonld +13 -0
- data/spec/test-files/test-9-expanded.jsonld +14 -0
- data/spec/test-files/test-9-input.yamlld +16 -0
- data/spec/to_rdf_spec.rb +556 -0
- data/spec/writer_spec.rb +441 -0
- metadata +350 -0
data/spec/matchers.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'rspec/matchers' # @see https://rubygems.org/gems/rspec
|
2
|
+
require_relative 'support/extensions'
|
3
|
+
|
4
|
+
RSpec::Matchers.define :produce_yamlld do |expected, logger|
|
5
|
+
match do |actual|
|
6
|
+
actual = Psych.load(actual, aliases: true) if actual.is_a?(String)
|
7
|
+
expected = Psych.load(expected, aliases: true) if expected.is_a?(String)
|
8
|
+
expect(actual).to be_equivalent_structure expected
|
9
|
+
end
|
10
|
+
|
11
|
+
failure_message do |actual|
|
12
|
+
"Expected: #{expected.is_a?(String) ? expected : expected.to_yaml rescue 'malformed structure'}\n" +
|
13
|
+
"Actual : #{actual.is_a?(String) ? actual : actual.to_yaml rescue 'malformed structure'}\n" +
|
14
|
+
"\nDebug:\n#{logger}"
|
15
|
+
end
|
16
|
+
|
17
|
+
failure_message_when_negated do |actual|
|
18
|
+
"Expected not to produce the following:\n" +
|
19
|
+
"Actual : #{actual.is_a?(String) ? actual : actual.to_yaml rescue 'malformed structure'}\n" +
|
20
|
+
"\nDebug:\n#{logger}"
|
21
|
+
end
|
22
|
+
end
|
data/spec/reader_spec.rb
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require_relative 'spec_helper'
|
3
|
+
require 'rdf/spec/reader'
|
4
|
+
|
5
|
+
describe YAML_LD::Reader do
|
6
|
+
let!(:doap) {File.expand_path("../../etc/doap.yamlld", __FILE__)}
|
7
|
+
let!(:doap_nt) {File.expand_path("../../etc/doap.nt", __FILE__)}
|
8
|
+
let!(:doap_count) {File.open(doap_nt).each_line.to_a.length}
|
9
|
+
let(:logger) {RDF::Spec.logger}
|
10
|
+
|
11
|
+
after(:each) {|example| puts logger.to_s if example.exception}
|
12
|
+
|
13
|
+
it_behaves_like 'an RDF::Reader' do
|
14
|
+
let(:reader_input) {File.read(doap)}
|
15
|
+
let(:reader) {YAML_LD::Reader.new(reader_input)}
|
16
|
+
let(:reader_count) {doap_count}
|
17
|
+
end
|
18
|
+
|
19
|
+
describe ".for" do
|
20
|
+
formats = [
|
21
|
+
:yamlld,
|
22
|
+
"etc/doap.yamlld",
|
23
|
+
{file_name: 'etc/doap.yamlld'},
|
24
|
+
{file_extension: 'yamlld'},
|
25
|
+
{content_type: 'application/ld+yaml'},
|
26
|
+
].each do |arg|
|
27
|
+
it "discovers with #{arg.inspect}" do
|
28
|
+
expect(RDF::Reader.for(arg)).to eq YAML_LD::Reader
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context "when validating" do
|
34
|
+
it "detects invalid YAML" do
|
35
|
+
yaml = %(
|
36
|
+
---
|
37
|
+
"@context":
|
38
|
+
dc: http://purl.org/dc/terms/
|
39
|
+
- foo
|
40
|
+
)
|
41
|
+
expect do |b|
|
42
|
+
described_class.new(StringIO.new(yaml), validate: true, logger: false).each_statement(&b)
|
43
|
+
end.to raise_error(RDF::ReaderError)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context :interface do
|
48
|
+
{
|
49
|
+
plain: %q(
|
50
|
+
"@context":
|
51
|
+
foaf:
|
52
|
+
http://xmlns.com/foaf/0.1/
|
53
|
+
"@id":
|
54
|
+
_:bnode1
|
55
|
+
"@type":
|
56
|
+
foaf:Person
|
57
|
+
"foaf:homepage":
|
58
|
+
http://example.com/bob/
|
59
|
+
"foaf:name":
|
60
|
+
Bob
|
61
|
+
}),
|
62
|
+
leading_comment: %q(---
|
63
|
+
# A comment before content
|
64
|
+
"@context":
|
65
|
+
foaf:
|
66
|
+
http://xmlns.com/foaf/0.1/
|
67
|
+
"@id":
|
68
|
+
_:bnode1
|
69
|
+
"@type":
|
70
|
+
foaf:Person
|
71
|
+
"foaf:homepage":
|
72
|
+
http://example.com/bob/
|
73
|
+
"foaf:name":
|
74
|
+
Bob
|
75
|
+
),
|
76
|
+
yaml_version: %(%YAML 1.2\n---
|
77
|
+
# A comment before content
|
78
|
+
"@context":
|
79
|
+
foaf:
|
80
|
+
http://xmlns.com/foaf/0.1/
|
81
|
+
"@id":
|
82
|
+
_:bnode1
|
83
|
+
"@type":
|
84
|
+
foaf:Person
|
85
|
+
"foaf:homepage":
|
86
|
+
http://example.com/bob/
|
87
|
+
"foaf:name":
|
88
|
+
Bob
|
89
|
+
),
|
90
|
+
}.each do |variant, src|
|
91
|
+
context variant do
|
92
|
+
subject {src}
|
93
|
+
|
94
|
+
describe "#initialize" do
|
95
|
+
it "yields reader given string" do
|
96
|
+
inner = double("inner")
|
97
|
+
expect(inner).to receive(:called).with(YAML_LD::Reader)
|
98
|
+
YAML_LD::Reader.new(subject) do |reader|
|
99
|
+
inner.called(reader.class)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
it "yields reader given IO" do
|
104
|
+
inner = double("inner")
|
105
|
+
expect(inner).to receive(:called).with(YAML_LD::Reader)
|
106
|
+
YAML_LD::Reader.new(StringIO.new(subject)) do |reader|
|
107
|
+
inner.called(reader.class)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
it "returns reader" do
|
112
|
+
expect(YAML_LD::Reader.new(subject)).to be_a(YAML_LD::Reader)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
describe "#each_statement" do
|
117
|
+
it "yields statements" do
|
118
|
+
inner = double("inner")
|
119
|
+
expect(inner).to receive(:called).with(RDF::Statement).exactly(3)
|
120
|
+
YAML_LD::Reader.new(subject).each_statement do |statement|
|
121
|
+
inner.called(statement.class)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
describe "#each_triple" do
|
127
|
+
it "yields statements" do
|
128
|
+
inner = double("inner")
|
129
|
+
expect(inner).to receive(:called).exactly(3)
|
130
|
+
YAML_LD::Reader.new(subject).each_triple do |subject, predicate, object|
|
131
|
+
inner.called(subject.class, predicate.class, object.class)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,265 @@
|
|
1
|
+
$:.unshift(File.join("../../lib", __FILE__))
|
2
|
+
$:.unshift File.dirname(__FILE__)
|
3
|
+
|
4
|
+
require "bundler/setup"
|
5
|
+
require 'rspec'
|
6
|
+
require 'rdf'
|
7
|
+
require 'rdf/isomorphic'
|
8
|
+
require 'rdf/nquads'
|
9
|
+
require 'rdf/turtle'
|
10
|
+
require 'rdf/trig'
|
11
|
+
require 'rdf/vocab'
|
12
|
+
require 'rdf/spec'
|
13
|
+
require 'rdf/spec/matchers'
|
14
|
+
require_relative 'matchers'
|
15
|
+
require 'yaml'
|
16
|
+
begin
|
17
|
+
require 'simplecov'
|
18
|
+
require 'simplecov-lcov'
|
19
|
+
SimpleCov::Formatter::LcovFormatter.config do |config|
|
20
|
+
#Coveralls is coverage by default/lcov. Send info results
|
21
|
+
config.report_with_single_file = true
|
22
|
+
config.single_report_path = 'coverage/lcov.info'
|
23
|
+
end
|
24
|
+
|
25
|
+
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([
|
26
|
+
SimpleCov::Formatter::HTMLFormatter,
|
27
|
+
SimpleCov::Formatter::LcovFormatter
|
28
|
+
])
|
29
|
+
SimpleCov.start do
|
30
|
+
add_filter "/spec/"
|
31
|
+
end
|
32
|
+
rescue LoadError
|
33
|
+
end
|
34
|
+
|
35
|
+
require 'yaml_ld'
|
36
|
+
|
37
|
+
# Left-justifies a block string
|
38
|
+
class ::String
|
39
|
+
def unindent
|
40
|
+
self.gsub(/^#{self.scan(/^[ \t]+(?=\S)/).min}/, '')
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Heuristically detect the input stream
|
45
|
+
def detect_format(stream)
|
46
|
+
# Got to look into the file to see
|
47
|
+
if stream.respond_to?(:rewind) && stream.respond_to?(:read)
|
48
|
+
stream.rewind
|
49
|
+
string = stream.read(1000)
|
50
|
+
stream.rewind
|
51
|
+
else
|
52
|
+
string = stream.to_s
|
53
|
+
end
|
54
|
+
case string
|
55
|
+
when /<html/i then RDF::RDFa::Reader
|
56
|
+
when /\{\s*\"@\"/i then JSON::LD::Reader
|
57
|
+
else RDF::Turtle::Reader
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Creates a bijection between the two objects and replaces nodes in actual from expected.
|
62
|
+
def remap_bnodes(actual, expected)
|
63
|
+
# Transform each to RDF and perform a blank node bijection.
|
64
|
+
# Replace the blank nodes in action with the mapping from bijection.
|
65
|
+
ds_actual = RDF::Repository.new << JSON::LD::API.toRdf(actual, rdfstar: true, rename_bnodes: false)
|
66
|
+
ds_expected = RDF::Repository.new << JSON::LD::API.toRdf(expected, rdfstar: true, rename_bnodes: false)
|
67
|
+
if bijection = ds_actual.bijection_to(ds_expected)
|
68
|
+
bijection = bijection.inject({}) {|memo, (k, v)| memo.merge(k.to_s => v.to_s)}
|
69
|
+
|
70
|
+
# Recursively replace blank nodes in actual with the bijection
|
71
|
+
replace_nodes(actual, bijection)
|
72
|
+
else
|
73
|
+
actual
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def replace_nodes(object, bijection)
|
78
|
+
case object
|
79
|
+
when Array
|
80
|
+
object.map {|o| replace_nodes(o, bijection)}
|
81
|
+
when Hash
|
82
|
+
object.inject({}) do |memo, (k, v)|
|
83
|
+
memo.merge(bijection.fetch(k, k) => replace_nodes(v, bijection))
|
84
|
+
end
|
85
|
+
when String
|
86
|
+
bijection.fetch(object, object)
|
87
|
+
else
|
88
|
+
object
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
LIBRARY_INPUT = JSON.parse(%([
|
93
|
+
{
|
94
|
+
"@id": "http://example.org/library",
|
95
|
+
"@type": "http://example.org/vocab#Library",
|
96
|
+
"http://example.org/vocab#contains": {"@id": "http://example.org/library/the-republic"}
|
97
|
+
}, {
|
98
|
+
"@id": "http://example.org/library/the-republic",
|
99
|
+
"@type": "http://example.org/vocab#Book",
|
100
|
+
"http://purl.org/dc/elements/1.1/creator": "Plato",
|
101
|
+
"http://purl.org/dc/elements/1.1/title": "The Republic",
|
102
|
+
"http://example.org/vocab#contains": {
|
103
|
+
"@id": "http://example.org/library/the-republic#introduction",
|
104
|
+
"@type": "http://example.org/vocab#Chapter",
|
105
|
+
"http://purl.org/dc/elements/1.1/description": "An introductory chapter on The Republic.",
|
106
|
+
"http://purl.org/dc/elements/1.1/title": "The Introduction"
|
107
|
+
}
|
108
|
+
}
|
109
|
+
]))
|
110
|
+
|
111
|
+
LIBRARY_EXPANDED = JSON.parse(%([
|
112
|
+
{
|
113
|
+
"@id": "http://example.org/library",
|
114
|
+
"@type": ["http://example.org/vocab#Library"],
|
115
|
+
"http://example.org/vocab#contains": [{"@id": "http://example.org/library/the-republic"}]
|
116
|
+
}, {
|
117
|
+
"@id": "http://example.org/library/the-republic",
|
118
|
+
"@type": ["http://example.org/vocab#Book"],
|
119
|
+
"http://purl.org/dc/elements/1.1/creator": [{"@value": "Plato"}],
|
120
|
+
"http://purl.org/dc/elements/1.1/title": [{"@value": "The Republic"}],
|
121
|
+
"http://example.org/vocab#contains": [{
|
122
|
+
"@id": "http://example.org/library/the-republic#introduction",
|
123
|
+
"@type": ["http://example.org/vocab#Chapter"],
|
124
|
+
"http://purl.org/dc/elements/1.1/description": [{"@value": "An introductory chapter on The Republic."}],
|
125
|
+
"http://purl.org/dc/elements/1.1/title": [{"@value": "The Introduction"}]
|
126
|
+
}]
|
127
|
+
}
|
128
|
+
]))
|
129
|
+
|
130
|
+
LIBRARY_COMPACTED_DEFAULT = JSON.parse(%({
|
131
|
+
"@context": "http://schema.org",
|
132
|
+
"@graph": [
|
133
|
+
{
|
134
|
+
"id": "http://example.org/library",
|
135
|
+
"type": "http://example.org/vocab#Library",
|
136
|
+
"http://example.org/vocab#contains": {"id": "http://example.org/library/the-republic"}
|
137
|
+
}, {
|
138
|
+
"id": "http://example.org/library/the-republic",
|
139
|
+
"type": "http://example.org/vocab#Book",
|
140
|
+
"http://purl.org/dc/elements/1.1/creator": "Plato",
|
141
|
+
"http://purl.org/dc/elements/1.1/title": "The Republic",
|
142
|
+
"http://example.org/vocab#contains": {
|
143
|
+
"id": "http://example.org/library/the-republic#introduction",
|
144
|
+
"type": "http://example.org/vocab#Chapter",
|
145
|
+
"http://purl.org/dc/elements/1.1/description": "An introductory chapter on The Republic.",
|
146
|
+
"http://purl.org/dc/elements/1.1/title": "The Introduction"
|
147
|
+
}
|
148
|
+
}
|
149
|
+
]
|
150
|
+
}))
|
151
|
+
|
152
|
+
LIBRARY_COMPACTED = JSON.parse(%({
|
153
|
+
"@context": "http://conneg.example.com/context",
|
154
|
+
"@graph": [
|
155
|
+
{
|
156
|
+
"@id": "http://example.org/library",
|
157
|
+
"@type": "ex:Library",
|
158
|
+
"ex:contains": {
|
159
|
+
"@id": "http://example.org/library/the-republic"
|
160
|
+
}
|
161
|
+
},
|
162
|
+
{
|
163
|
+
"@id": "http://example.org/library/the-republic",
|
164
|
+
"@type": "ex:Book",
|
165
|
+
"dc:creator": "Plato",
|
166
|
+
"dc:title": "The Republic",
|
167
|
+
"ex:contains": {
|
168
|
+
"@id": "http://example.org/library/the-republic#introduction",
|
169
|
+
"@type": "ex:Chapter",
|
170
|
+
"dc:description": "An introductory chapter on The Republic.",
|
171
|
+
"dc:title": "The Introduction"
|
172
|
+
}
|
173
|
+
}
|
174
|
+
]
|
175
|
+
}))
|
176
|
+
|
177
|
+
LIBRARY_FLATTENED_EXPANDED = JSON.parse(%([
|
178
|
+
{
|
179
|
+
"@id": "http://example.org/library",
|
180
|
+
"@type": ["http://example.org/vocab#Library"],
|
181
|
+
"http://example.org/vocab#contains": [{"@id": "http://example.org/library/the-republic"}]
|
182
|
+
},
|
183
|
+
{
|
184
|
+
"@id": "http://example.org/library/the-republic",
|
185
|
+
"@type": ["http://example.org/vocab#Book"],
|
186
|
+
"http://purl.org/dc/elements/1.1/creator": [{"@value": "Plato"}],
|
187
|
+
"http://purl.org/dc/elements/1.1/title": [{"@value": "The Republic"}],
|
188
|
+
"http://example.org/vocab#contains": [{"@id": "http://example.org/library/the-republic#introduction"}]
|
189
|
+
},
|
190
|
+
{
|
191
|
+
"@id": "http://example.org/library/the-republic#introduction",
|
192
|
+
"@type": ["http://example.org/vocab#Chapter"],
|
193
|
+
"http://purl.org/dc/elements/1.1/description": [{"@value": "An introductory chapter on The Republic."}],
|
194
|
+
"http://purl.org/dc/elements/1.1/title": [{"@value": "The Introduction"}]
|
195
|
+
}
|
196
|
+
]))
|
197
|
+
|
198
|
+
LIBRARY_FLATTENED_COMPACTED_DEFAULT = JSON.parse(%({
|
199
|
+
"@context": "http://schema.org",
|
200
|
+
"@graph": [
|
201
|
+
{
|
202
|
+
"id": "http://example.org/library",
|
203
|
+
"type": "http://example.org/vocab#Library",
|
204
|
+
"http://example.org/vocab#contains": {"id": "http://example.org/library/the-republic"}
|
205
|
+
},
|
206
|
+
{
|
207
|
+
"id": "http://example.org/library/the-republic",
|
208
|
+
"type": "http://example.org/vocab#Book",
|
209
|
+
"http://purl.org/dc/elements/1.1/creator": "Plato",
|
210
|
+
"http://purl.org/dc/elements/1.1/title": "The Republic",
|
211
|
+
"http://example.org/vocab#contains": {"id": "http://example.org/library/the-republic#introduction"}
|
212
|
+
},
|
213
|
+
{
|
214
|
+
"id": "http://example.org/library/the-republic#introduction",
|
215
|
+
"type": "http://example.org/vocab#Chapter",
|
216
|
+
"http://purl.org/dc/elements/1.1/description": "An introductory chapter on The Republic.",
|
217
|
+
"http://purl.org/dc/elements/1.1/title": "The Introduction"
|
218
|
+
}
|
219
|
+
]
|
220
|
+
}))
|
221
|
+
|
222
|
+
LIBRARY_FLATTENED_COMPACTED = JSON.parse(%({
|
223
|
+
"@context": "http://conneg.example.com/context",
|
224
|
+
"@graph": [
|
225
|
+
{
|
226
|
+
"@id": "http://example.org/library",
|
227
|
+
"@type": "ex:Library",
|
228
|
+
"ex:contains": {"@id": "http://example.org/library/the-republic"}
|
229
|
+
},
|
230
|
+
{
|
231
|
+
"@id": "http://example.org/library/the-republic",
|
232
|
+
"@type": "ex:Book",
|
233
|
+
"dc:creator": "Plato",
|
234
|
+
"dc:title": "The Republic",
|
235
|
+
"ex:contains": {"@id": "http://example.org/library/the-republic#introduction"}
|
236
|
+
},
|
237
|
+
{
|
238
|
+
"@id": "http://example.org/library/the-republic#introduction",
|
239
|
+
"@type": "ex:Chapter",
|
240
|
+
"dc:description": "An introductory chapter on The Republic.",
|
241
|
+
"dc:title": "The Introduction"
|
242
|
+
}
|
243
|
+
]
|
244
|
+
}))
|
245
|
+
|
246
|
+
LIBRARY_FRAMED = JSON.parse(%({
|
247
|
+
"@context": {
|
248
|
+
"dc": "http://purl.org/dc/elements/1.1/",
|
249
|
+
"ex": "http://example.org/vocab#"
|
250
|
+
},
|
251
|
+
"@id": "http://example.org/library",
|
252
|
+
"@type": "ex:Library",
|
253
|
+
"ex:contains": {
|
254
|
+
"@id": "http://example.org/library/the-republic",
|
255
|
+
"@type": "ex:Book",
|
256
|
+
"dc:creator": "Plato",
|
257
|
+
"dc:title": "The Republic",
|
258
|
+
"ex:contains": {
|
259
|
+
"@id": "http://example.org/library/the-republic#introduction",
|
260
|
+
"@type": "ex:Chapter",
|
261
|
+
"dc:description": "An introductory chapter on The Republic.",
|
262
|
+
"dc:title": "The Introduction"
|
263
|
+
}
|
264
|
+
}
|
265
|
+
}))
|
@@ -0,0 +1,44 @@
|
|
1
|
+
class Object
|
2
|
+
def equivalent_structure?(other, ordered: false)
|
3
|
+
self == other
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
class Hash
|
8
|
+
def equivalent_structure?(other, ordered: false)
|
9
|
+
return false unless other.is_a?(Hash) && other.length == length
|
10
|
+
all? do |key, value|
|
11
|
+
# List values are still ordered
|
12
|
+
if key == '@language' && value.is_a?(String)
|
13
|
+
value.downcase.equivalent_structure?(other[key].to_s.downcase, ordered: key == '@list')
|
14
|
+
else
|
15
|
+
value.equivalent_structure?(other[key], ordered: key == '@list')
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def diff(other)
|
21
|
+
self.keys.inject({}) do |memo, key|
|
22
|
+
unless self[key] == other[key]
|
23
|
+
memo[key] = [self[key], other[key]]
|
24
|
+
end
|
25
|
+
memo
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class Array
|
31
|
+
def equivalent_structure?(other, ordered: false)
|
32
|
+
return false unless other.is_a?(Array) && other.length == length
|
33
|
+
if ordered
|
34
|
+
b = other.dup
|
35
|
+
# All elements must match in order
|
36
|
+
all? {|av| av.equivalent_structure?(b.shift)}
|
37
|
+
else
|
38
|
+
# Look for any element which matches
|
39
|
+
all? do |av|
|
40
|
+
other.any? {|bv| av.equivalent_structure?(bv)}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
{
|
2
|
+
"@context": {
|
3
|
+
"avatar": "http://xmlns.com/foaf/0.1/avatar",
|
4
|
+
"homepage": "http://xmlns.com/foaf/0.1/homepage",
|
5
|
+
"name": "http://xmlns.com/foaf/0.1/name"
|
6
|
+
},
|
7
|
+
"avatar": "http://twitter.com/account/profile_image/manusporny",
|
8
|
+
"homepage": "http://manu.sporny.org/",
|
9
|
+
"name": "Manu Sporny"
|
10
|
+
}
|
@@ -0,0 +1,8 @@
|
|
1
|
+
@prefix avatar: <http://xmlns.com/foaf/0.1/avatar> .
|
2
|
+
@prefix homepage: <http://xmlns.com/foaf/0.1/homepage> .
|
3
|
+
@prefix name: <http://xmlns.com/foaf/0.1/name> .
|
4
|
+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
5
|
+
|
6
|
+
[ avatar: "http://twitter.com/account/profile_image/manusporny";
|
7
|
+
homepage: "http://manu.sporny.org/";
|
8
|
+
name: "Manu Sporny"] .
|
@@ -0,0 +1,20 @@
|
|
1
|
+
{
|
2
|
+
"@context": {
|
3
|
+
"dc": "http://purl.org/dc/elements/1.1/",
|
4
|
+
"ex": "http://example.org/vocab#"
|
5
|
+
},
|
6
|
+
"@id": "http://example.org/library",
|
7
|
+
"@type": "ex:Library",
|
8
|
+
"ex:contains": {
|
9
|
+
"@id": "http://example.org/library/the-republic",
|
10
|
+
"@type": "ex:Book",
|
11
|
+
"dc:creator": "Plato",
|
12
|
+
"dc:title": "The Republic",
|
13
|
+
"ex:contains": {
|
14
|
+
"@id": "http://example.org/library/the-republic#introduction",
|
15
|
+
"@type": "ex:Chapter",
|
16
|
+
"dc:description": "An introductory chapter on The Republic.",
|
17
|
+
"dc:title": "The Introduction"
|
18
|
+
}
|
19
|
+
}
|
20
|
+
}
|
@@ -0,0 +1,16 @@
|
|
1
|
+
[{
|
2
|
+
"@id": "http://example.org/library",
|
3
|
+
"@type": ["http://example.org/vocab#Library"],
|
4
|
+
"http://example.org/vocab#contains": [{
|
5
|
+
"@id": "http://example.org/library/the-republic",
|
6
|
+
"@type": ["http://example.org/vocab#Book"],
|
7
|
+
"http://purl.org/dc/elements/1.1/creator": [{"@value": "Plato"}],
|
8
|
+
"http://purl.org/dc/elements/1.1/title": [{"@value": "The Republic"}],
|
9
|
+
"http://example.org/vocab#contains": [{
|
10
|
+
"@id": "http://example.org/library/the-republic#introduction",
|
11
|
+
"@type": ["http://example.org/vocab#Chapter"],
|
12
|
+
"http://purl.org/dc/elements/1.1/description": [{"@value": "An introductory chapter on The Republic."}],
|
13
|
+
"http://purl.org/dc/elements/1.1/title": [{"@value": "The Introduction"}]
|
14
|
+
}]
|
15
|
+
}]
|
16
|
+
}]
|
@@ -0,0 +1,16 @@
|
|
1
|
+
---
|
2
|
+
"@context":
|
3
|
+
dc: http://purl.org/dc/elements/1.1/
|
4
|
+
ex: http://example.org/vocab#
|
5
|
+
"@id": http://example.org/library
|
6
|
+
"@type": ex:Library
|
7
|
+
ex:contains:
|
8
|
+
"@id": http://example.org/library/the-republic
|
9
|
+
"@type": ex:Book
|
10
|
+
dc:creator: Plato
|
11
|
+
dc:title: The Republic
|
12
|
+
ex:contains:
|
13
|
+
"@id": http://example.org/library/the-republic#introduction
|
14
|
+
"@type": ex:Chapter
|
15
|
+
dc:description: An introductory chapter on The Republic.
|
16
|
+
dc:title: The Introduction
|
@@ -0,0 +1,14 @@
|
|
1
|
+
@prefix dc: <http://purl.org/dc/elements/1.1/> .
|
2
|
+
@prefix ex: <http://example.org/vocab#> .
|
3
|
+
|
4
|
+
<http://example.org/library> a ex:Library;
|
5
|
+
ex:contains <http://example.org/library/the-republic> .
|
6
|
+
|
7
|
+
<http://example.org/library/the-republic> a ex:Book;
|
8
|
+
ex:contains <http://example.org/library/the-republic#introduction>;
|
9
|
+
dc:creator "Plato";
|
10
|
+
dc:title "The Republic" .
|
11
|
+
|
12
|
+
<http://example.org/library/the-republic#introduction> a ex:Chapter;
|
13
|
+
dc:description "An introductory chapter on The Republic.";
|
14
|
+
dc:title "The Introduction" .
|
@@ -0,0 +1,11 @@
|
|
1
|
+
{
|
2
|
+
"@context": {
|
3
|
+
"xsd": "http://www.w3.org/2001/XMLSchema#",
|
4
|
+
"name": "http://xmlns.com/foaf/0.1/name",
|
5
|
+
"age": {"@id": "http://xmlns.com/foaf/0.1/age", "@type": "xsd:integer"},
|
6
|
+
"homepage": {"@id": "http://xmlns.com/foaf/0.1/homepage", "@type": "@id"}
|
7
|
+
},
|
8
|
+
"name": "Manu Sporny",
|
9
|
+
"age": "41",
|
10
|
+
"homepage": "http://manu.sporny.org/"
|
11
|
+
}
|
@@ -0,0 +1,8 @@
|
|
1
|
+
{
|
2
|
+
"@context": {
|
3
|
+
"xsd": "http://www.w3.org/2001/XMLSchema#",
|
4
|
+
"name": "http://xmlns.com/foaf/0.1/name",
|
5
|
+
"age": {"@id": "http://xmlns.com/foaf/0.1/age", "@type": "xsd:integer"},
|
6
|
+
"homepage": {"@id": "http://xmlns.com/foaf/0.1/homepage", "@type": "@id"}
|
7
|
+
}
|
8
|
+
}
|
@@ -0,0 +1,10 @@
|
|
1
|
+
[{
|
2
|
+
"http://xmlns.com/foaf/0.1/name": [{"@value": "Manu Sporny"}],
|
3
|
+
"http://xmlns.com/foaf/0.1/age": [{
|
4
|
+
"@type": "http://www.w3.org/2001/XMLSchema#integer",
|
5
|
+
"@value": "41"
|
6
|
+
}],
|
7
|
+
"http://xmlns.com/foaf/0.1/homepage": [{
|
8
|
+
"@id": "http://manu.sporny.org/"
|
9
|
+
}]
|
10
|
+
}]
|
@@ -0,0 +1,13 @@
|
|
1
|
+
---
|
2
|
+
"@context":
|
3
|
+
xsd: http://www.w3.org/2001/XMLSchema#
|
4
|
+
name: http://xmlns.com/foaf/0.1/name
|
5
|
+
age:
|
6
|
+
"@id": http://xmlns.com/foaf/0.1/age
|
7
|
+
"@type": xsd:integer
|
8
|
+
homepage:
|
9
|
+
"@id": http://xmlns.com/foaf/0.1/homepage
|
10
|
+
"@type": "@id"
|
11
|
+
name: Manu Sporny
|
12
|
+
age: '41'
|
13
|
+
homepage: http://manu.sporny.org/
|
@@ -0,0 +1,8 @@
|
|
1
|
+
@prefix age: <http://xmlns.com/foaf/0.1/age> .
|
2
|
+
@prefix homepage: <http://xmlns.com/foaf/0.1/homepage> .
|
3
|
+
@prefix name: <http://xmlns.com/foaf/0.1/name> .
|
4
|
+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
5
|
+
|
6
|
+
[ age: 41;
|
7
|
+
homepage: <http://manu.sporny.org/>;
|
8
|
+
name: "Manu Sporny"] .
|