iq_rdf 0.2.2 → 0.3.0
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/CHANGELOG.md +37 -0
- data/iq_rdf.gemspec +4 -2
- data/lib/iq_rdf/collection.rb +3 -1
- data/lib/iq_rdf/document.rb +13 -9
- data/lib/iq_rdf/literal.rb +20 -4
- data/lib/iq_rdf/version.rb +1 -1
- data/test/ntriples_test.rb +0 -3
- data/test/test_helper.rb +2 -0
- data/test/turtle_test.rb +57 -0
- data/test/xml_test.rb +4 -4
- metadata +17 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 86d24605dfac28de70ab6dc47bbd296adb42c4e855697b137a226a2a92e19f45
|
|
4
|
+
data.tar.gz: 6d23a834fffa345374df50eac0fbb8c0671a92799adff48ad077cad61df81677
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e9816318bac2c2959053a30e6ec9c5ec531bc37089bb797d289887fc365524c83c02c460b93a25ba74a6287134ee4181d3002acab00627ec5a02d839b0494cb6
|
|
7
|
+
data.tar.gz: 5ca38d366409dea8d0cf6c3e0ee099a828223d00650c1068bb7986dc3087b3f77158037a0efcaf324ed232d00e8b53dc36f53feb8a0d28a0f4d1720345020774
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.3.0] - 2026-09-10
|
|
4
|
+
|
|
5
|
+
* omit `rdf:type rdf:List` from RDF collections. The list structure is fully
|
|
6
|
+
defined by `rdf:first`/`rdf:rest`/`rdf:nil`, and Turtle's `( ... )` form
|
|
7
|
+
cannot express the type at all, so emitting it made the same data count
|
|
8
|
+
differently per format. **Documents containing collections now serialize to
|
|
9
|
+
fewer triples**, and N-Triples, Turtle and RDF/XML agree on the count. In
|
|
10
|
+
RDF/XML, list nodes are now `rdf:Description` rather than `rdf:List`.
|
|
11
|
+
|
|
12
|
+
## [0.2.3] - 2026-09-10
|
|
13
|
+
|
|
14
|
+
* fix Turtle serialization of literals containing a carriage return. A long
|
|
15
|
+
string (`"""..."""`) was only used for newlines, so a lone carriage return
|
|
16
|
+
ended up raw inside a short string - invalid Turtle, which made parsers drop
|
|
17
|
+
the statement without warning. Quotes are now escaped inside long strings as
|
|
18
|
+
well, so a value ending in a quote or containing three of them no longer
|
|
19
|
+
breaks the output.
|
|
20
|
+
* serialize Turtle in linear rather than quadratic time. Building the output
|
|
21
|
+
with `+=` copied the whole string on every append, which made large documents
|
|
22
|
+
effectively impossible to serialize.
|
|
23
|
+
* serialize N-Triples faster by not counting a Hash with `Enumerable#count`,
|
|
24
|
+
which walks and allocates per entry. On a document with many blank nodes this
|
|
25
|
+
dominated the runtime.
|
|
26
|
+
* add `rdf-turtle` as a development dependency, so that tests can parse
|
|
27
|
+
generated output back instead of only comparing it to an expected string.
|
|
28
|
+
|
|
29
|
+
## [0.2.2] - 2026-07-31
|
|
30
|
+
|
|
31
|
+
* fix N-Triples serialization of literals containing newlines, tabs or
|
|
32
|
+
carriage returns
|
|
33
|
+
* drop support for end-of-life rubies
|
|
34
|
+
|
|
35
|
+
## [0.2.1]
|
|
36
|
+
|
|
37
|
+
* fix a frozen string literal warning
|
data/iq_rdf.gemspec
CHANGED
|
@@ -25,15 +25,17 @@ Gem::Specification.new do |s|
|
|
|
25
25
|
s.homepage = "http://github.com/innoq/iq_rdf"
|
|
26
26
|
s.summary = "IqRdf - A builder like rdf library for ruby and rails"
|
|
27
27
|
s.description = s.summary
|
|
28
|
-
s.extra_rdoc_files = ['README.md', 'LICENSE']
|
|
28
|
+
s.extra_rdoc_files = ['README.md', 'CHANGELOG.md', 'LICENSE']
|
|
29
29
|
|
|
30
30
|
s.add_dependency "bundler"
|
|
31
31
|
s.add_dependency "builder"
|
|
32
32
|
s.add_dependency "activesupport"
|
|
33
33
|
|
|
34
34
|
s.add_development_dependency "minitest"
|
|
35
|
+
# parses generated output back, so that tests catch invalid serializations
|
|
36
|
+
s.add_development_dependency "rdf-turtle"
|
|
35
37
|
|
|
36
|
-
s.files = %w(LICENSE README.md Rakefile iq_rdf.gemspec) + Dir.glob("{lib,rails,test}/**/*")
|
|
38
|
+
s.files = %w(LICENSE README.md CHANGELOG.md Rakefile iq_rdf.gemspec) + Dir.glob("{lib,rails,test}/**/*")
|
|
37
39
|
s.test_files = Dir.glob("{test}/**/*")
|
|
38
40
|
s.executables = Dir.glob("{bin}/**/*")
|
|
39
41
|
s.require_paths = ["lib"]
|
data/lib/iq_rdf/collection.rb
CHANGED
|
@@ -33,7 +33,9 @@ module IqRdf
|
|
|
33
33
|
elements ||= @elements.dup
|
|
34
34
|
block.call({},
|
|
35
35
|
lambda {
|
|
36
|
-
|
|
36
|
+
# rdf:Description, not rdf:List: a typed element would assert
|
|
37
|
+
# rdf:type rdf:List, which Turtle's ( ... ) form cannot express
|
|
38
|
+
xml.rdf :Description do
|
|
37
39
|
elements.shift.build_xml(xml) do |*args|
|
|
38
40
|
xml.rdf(:first, *args)
|
|
39
41
|
end
|
data/lib/iq_rdf/document.rb
CHANGED
|
@@ -62,7 +62,9 @@ module IqRdf
|
|
|
62
62
|
render_blank_node = lambda do |res|
|
|
63
63
|
node_id = blank_nodes[res]
|
|
64
64
|
unless node_id
|
|
65
|
-
|
|
65
|
+
# size, not count: Hash does not override Enumerable#count, which
|
|
66
|
+
# walks every entry - once per blank node that is quadratic
|
|
67
|
+
node_id = blank_nodes.size + 1
|
|
66
68
|
blank_nodes[res] = node_id
|
|
67
69
|
end
|
|
68
70
|
return "_:b#{node_id}"
|
|
@@ -75,7 +77,9 @@ module IqRdf
|
|
|
75
77
|
sublist = list
|
|
76
78
|
total = res.elements.length
|
|
77
79
|
res.elements.each_with_index do |current_element, i|
|
|
78
|
-
|
|
80
|
+
# No a rdf:List here: rdf:first/rdf:rest/rdf:nil already define the
|
|
81
|
+
# collection, and Turtle's ( ... ) form cannot express the type at
|
|
82
|
+
# all - emitting it made the same data count differently per format.
|
|
79
83
|
sublist::rdf.first(current_element) # _:b* rdf:first <...>
|
|
80
84
|
last = i + 1 == total
|
|
81
85
|
if last
|
|
@@ -143,18 +147,18 @@ module IqRdf
|
|
|
143
147
|
end
|
|
144
148
|
|
|
145
149
|
def to_turtle
|
|
146
|
-
s = ""
|
|
150
|
+
s = +""
|
|
147
151
|
@namespaces.values.sort{ |n1, n2| n1.turtle_token <=> n2.turtle_token }.each do |namespace|
|
|
148
|
-
s
|
|
152
|
+
s << "@prefix #{namespace.turtle_token}: <#{namespace.uri_prefix}>.\n"
|
|
149
153
|
end
|
|
150
|
-
s
|
|
154
|
+
s << "\n"
|
|
151
155
|
@nodes.each do |subject|
|
|
152
156
|
pref = subject.to_s(@document_language)
|
|
153
157
|
indent = "".ljust(pref.length)
|
|
154
158
|
|
|
155
159
|
# Render subject, if it is defined as a RDF-type.
|
|
156
160
|
if subject.rdf_type
|
|
157
|
-
s
|
|
161
|
+
s << "#{pref} a #{subject.rdf_type}"
|
|
158
162
|
pref = ";\n" + indent
|
|
159
163
|
end
|
|
160
164
|
|
|
@@ -163,13 +167,13 @@ module IqRdf
|
|
|
163
167
|
objects = predicate.nodes.map { |object| object.to_s(indent: indent, lang: predicate.lang || subject.lang || @document_language) }
|
|
164
168
|
.join(", ")
|
|
165
169
|
|
|
166
|
-
s
|
|
170
|
+
s << "#{pref} #{predicate.to_s} #{objects}"
|
|
167
171
|
|
|
168
172
|
pref = ";\n" + indent
|
|
169
173
|
end
|
|
170
|
-
s
|
|
174
|
+
s << ".\n"
|
|
171
175
|
if @config[:empty_line_between_triples]
|
|
172
|
-
s
|
|
176
|
+
s << "\n"
|
|
173
177
|
end
|
|
174
178
|
end
|
|
175
179
|
s
|
data/lib/iq_rdf/literal.rb
CHANGED
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
module IqRdf
|
|
16
16
|
class Literal
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
# Turtle accepts the same escapes as N-Triples, so both formats share this
|
|
19
|
+
ESCAPE_MAP = {
|
|
19
20
|
"\\" => "\\\\",
|
|
20
21
|
'"' => '\\"',
|
|
21
22
|
"\n" => "\\n",
|
|
@@ -23,6 +24,12 @@ module IqRdf
|
|
|
23
24
|
"\t" => "\\t"
|
|
24
25
|
}.freeze
|
|
25
26
|
|
|
27
|
+
# Inside """...""" line breaks and tabs are legal as they are
|
|
28
|
+
LONG_ESCAPE_MAP = {
|
|
29
|
+
"\\" => "\\\\",
|
|
30
|
+
'"' => '\\"'
|
|
31
|
+
}.freeze
|
|
32
|
+
|
|
26
33
|
def initialize(obj, lang = nil, datatype = nil)
|
|
27
34
|
raise "#{datatype.inspect} is not an URI" unless datatype.nil? || datatype.is_a?(::URI) || datatype.is_a?(IqRdf::Uri)
|
|
28
35
|
@obj = obj
|
|
@@ -45,7 +52,6 @@ module IqRdf
|
|
|
45
52
|
def to_s(options = {})
|
|
46
53
|
lang = @lang || options[:lang] # Use the Literals lang when given
|
|
47
54
|
lang = (lang && lang != :none) ? "@#{lang}" : ""
|
|
48
|
-
quote = @obj.to_s.include?("\n") ? '"""' : '"'
|
|
49
55
|
datatype = if @datatype.is_a?(::URI)
|
|
50
56
|
"^^<#{@datatype.to_s}>"
|
|
51
57
|
elsif @datatype.is_a?(IqRdf::Uri)
|
|
@@ -54,7 +60,17 @@ module IqRdf
|
|
|
54
60
|
""
|
|
55
61
|
end
|
|
56
62
|
|
|
57
|
-
|
|
63
|
+
value = @obj.to_s
|
|
64
|
+
|
|
65
|
+
if value.match?(/[\n\r]/)
|
|
66
|
+
# A long string keeps line breaks readable, which is the point of
|
|
67
|
+
# Turtle over N-Triples. Its grammar allows raw line breaks and tabs,
|
|
68
|
+
# so only backslashes and quotes need escaping - escaping every quote
|
|
69
|
+
# also rules out a run of three ending the string prematurely.
|
|
70
|
+
%("""#{value.gsub(/[\\"]/, LONG_ESCAPE_MAP)}"""#{lang}#{datatype})
|
|
71
|
+
else
|
|
72
|
+
%("#{value.gsub(/[\\"\n\r\t]/, ESCAPE_MAP)}"#{lang}#{datatype})
|
|
73
|
+
end
|
|
58
74
|
end
|
|
59
75
|
|
|
60
76
|
def to_ntriples(parent_lang = nil)
|
|
@@ -67,7 +83,7 @@ module IqRdf
|
|
|
67
83
|
(lang && lang != :none) ? "@#{lang}" : ""
|
|
68
84
|
end
|
|
69
85
|
|
|
70
|
-
"\"#{@obj.to_s.gsub(/[\\"\n\r\t]/,
|
|
86
|
+
"\"#{@obj.to_s.gsub(/[\\"\n\r\t]/, ESCAPE_MAP)}\"#{suffix}"
|
|
71
87
|
end
|
|
72
88
|
|
|
73
89
|
def build_xml(xml, &block)
|
data/lib/iq_rdf/version.rb
CHANGED
data/test/ntriples_test.rb
CHANGED
|
@@ -101,13 +101,10 @@ _:b2 <http://www.test.de/title> "blubb" .
|
|
|
101
101
|
document << IqRdf::testemann.testIt([IqRdf::hello, IqRdf::goodbye, "bla"])
|
|
102
102
|
|
|
103
103
|
assert_equal(<<-rdf.strip, document.to_ntriples.strip)
|
|
104
|
-
_:b2 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.w3.org/1999/02/22-rdf-syntax-ns#List> .
|
|
105
104
|
_:b2 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> <http://test.de/hello> .
|
|
106
105
|
_:b2 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> _:b3 .
|
|
107
|
-
_:b3 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.w3.org/1999/02/22-rdf-syntax-ns#List> .
|
|
108
106
|
_:b3 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> <http://test.de/goodbye> .
|
|
109
107
|
_:b3 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> _:b4 .
|
|
110
|
-
_:b4 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.w3.org/1999/02/22-rdf-syntax-ns#List> .
|
|
111
108
|
_:b4 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "bla" .
|
|
112
109
|
_:b4 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> <http://www.w3.org/1999/02/22-rdf-syntax-ns#nil> .
|
|
113
110
|
<http://test.de/testemann> <http://test.de/testIt> _:b2 .
|
data/test/test_helper.rb
CHANGED
data/test/turtle_test.rb
CHANGED
|
@@ -163,6 +163,63 @@ over two lines"""@de;
|
|
|
163
163
|
rdf
|
|
164
164
|
end
|
|
165
165
|
|
|
166
|
+
# A carriage return without a newline used to end up raw inside a short
|
|
167
|
+
# string, which is invalid Turtle and made parsers drop the statement.
|
|
168
|
+
def test_control_characters_in_literals
|
|
169
|
+
document = IqRdf::Document.new('http://www.test.de/', :lang => :none)
|
|
170
|
+
|
|
171
|
+
document << IqRdf::testemann do |t|
|
|
172
|
+
t.carriage_return("first\rsecond")
|
|
173
|
+
t.tab("left\tright")
|
|
174
|
+
t.long_with_quote("first\nends with \"")
|
|
175
|
+
t.long_with_three_quotes("first\nwith \"\"\" inside")
|
|
176
|
+
t.long_with_backslash("first\nC:\\temp")
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
assert_equal(<<rdf, document.to_turtle)
|
|
180
|
+
@prefix : <http://www.test.de/>.
|
|
181
|
+
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>.
|
|
182
|
+
|
|
183
|
+
:testemann :carriage_return """first\rsecond""";
|
|
184
|
+
:tab "left\\tright";
|
|
185
|
+
:long_with_quote """first
|
|
186
|
+
ends with \\\"""";
|
|
187
|
+
:long_with_three_quotes """first
|
|
188
|
+
with \\\"\\\"\\\" inside""";
|
|
189
|
+
:long_with_backslash """first
|
|
190
|
+
C:\\\\temp""".
|
|
191
|
+
rdf
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# The assertions above compare against an expected string, which cements the
|
|
195
|
+
# current output but says nothing about whether it is valid Turtle. Parsing
|
|
196
|
+
# the output back does: it catches escaping that a parser rejects, or that
|
|
197
|
+
# silently changes the value.
|
|
198
|
+
def test_literals_survive_a_round_trip
|
|
199
|
+
{
|
|
200
|
+
"plain" => "just text",
|
|
201
|
+
"newline" => "first\nsecond",
|
|
202
|
+
"carriage return" => "first\rsecond",
|
|
203
|
+
"carriage return and newline" => "first\r\nsecond",
|
|
204
|
+
"tab" => "left\tright",
|
|
205
|
+
"quote" => 'said "hello"',
|
|
206
|
+
"trailing quote" => 'ends with "',
|
|
207
|
+
"three quotes" => 'a """ b',
|
|
208
|
+
"backslash" => 'C:\\temp',
|
|
209
|
+
"newline and quote" => "first\nends with \"",
|
|
210
|
+
"newline and three quotes" => "first\nwith \"\"\" inside",
|
|
211
|
+
"newline and backslash" => "first\nC:\\temp",
|
|
212
|
+
}.each do |name, value|
|
|
213
|
+
document = IqRdf::Document.new('http://www.test.de/', :lang => :none)
|
|
214
|
+
document << IqRdf::testemann.some_predicate(value)
|
|
215
|
+
|
|
216
|
+
statements = RDF::Turtle::Reader.new(document.to_turtle, validate: true).to_a
|
|
217
|
+
assert_equal 1, statements.size, "expected one statement for #{name}"
|
|
218
|
+
assert_equal value, statements.first.object.value,
|
|
219
|
+
"#{name} did not survive the round trip"
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
166
223
|
def test_supress_if_empty_otpion
|
|
167
224
|
document = IqRdf::Document.new('http://www.test.de/')
|
|
168
225
|
document.namespaces :foaf => 'http://xmlns.com/foaf/0.1/'
|
data/test/xml_test.rb
CHANGED
|
@@ -103,15 +103,15 @@ rdf
|
|
|
103
103
|
</rdf:Description>
|
|
104
104
|
<rdf:Description rdf:about="http://www.umweltprobenbank.de/testemann">
|
|
105
105
|
<testIt>
|
|
106
|
-
<rdf:
|
|
106
|
+
<rdf:Description>
|
|
107
107
|
<rdf:first rdf:resource="http://www.umweltprobenbank.de/hello"/>
|
|
108
108
|
<rdf:rest>
|
|
109
|
-
<rdf:
|
|
109
|
+
<rdf:Description>
|
|
110
110
|
<rdf:first>bla</rdf:first>
|
|
111
111
|
<rdf:rest rdf:resource="http://www.w3.org/1999/02/22-rdf-syntax-ns#nil"/>
|
|
112
|
-
</rdf:
|
|
112
|
+
</rdf:Description>
|
|
113
113
|
</rdf:rest>
|
|
114
|
-
</rdf:
|
|
114
|
+
</rdf:Description>
|
|
115
115
|
</testIt>
|
|
116
116
|
</rdf:Description>
|
|
117
117
|
<rdf:Description rdf:about="http://www.upb.de/u1023">
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: iq_rdf
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Till Schulte-Coerne
|
|
@@ -65,15 +65,31 @@ dependencies:
|
|
|
65
65
|
- - ">="
|
|
66
66
|
- !ruby/object:Gem::Version
|
|
67
67
|
version: '0'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: rdf-turtle
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - ">="
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '0'
|
|
75
|
+
type: :development
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '0'
|
|
68
82
|
description: IqRdf - A builder like rdf library for ruby and rails
|
|
69
83
|
email:
|
|
70
84
|
- till.schulte-coerne@innoq.com
|
|
71
85
|
executables: []
|
|
72
86
|
extensions: []
|
|
73
87
|
extra_rdoc_files:
|
|
88
|
+
- CHANGELOG.md
|
|
74
89
|
- LICENSE
|
|
75
90
|
- README.md
|
|
76
91
|
files:
|
|
92
|
+
- CHANGELOG.md
|
|
77
93
|
- LICENSE
|
|
78
94
|
- README.md
|
|
79
95
|
- Rakefile
|