iq_rdf 0.2.2 → 0.2.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e9b8113dea4da94252393b4f5d662fb83e20dd201b82f8d350d51ea13cf616a6
4
- data.tar.gz: a3c5e7299288526e0e8c042b37cfb6b3d20a500e9e361b464fd6667e07ca94f8
3
+ metadata.gz: e76fe4fdce925931dc7ec6d5edbbb31c205713e92162fddb028548cf13784fc6
4
+ data.tar.gz: 691b0e21d7db159fad7f95d478edf1fadb1884e136a761dd0d25f5166f7a7b5a
5
5
  SHA512:
6
- metadata.gz: aef36d79a240d34c0f3f35ce6dc431f02bfc84e836a5b6050714c86cbac1c5a4e138f43be74f3169a8750b4a730989a0c26fbeb1e53cad9520e80330360b7dcd
7
- data.tar.gz: d6844c6e75ca177e5707eb8e87ce181a0a3232c754ad288cb086a2608f9feb13c45bcc2fe138be613c465b9c4f032bac50c10c8a543c82da16307a2bdee15c65
6
+ metadata.gz: 9f5ec87e657fda841765741e59e717e6864e019229901e07c957317e7da80514330264ee106821ec24e786ebc5cdc1d31557258e7594f4ba4dbcd68ac95bd53c
7
+ data.tar.gz: 36b99255272b484d44c8fd95546c4626c85df4813d057d7d0b6c5fe2afa287d0ce6b642e653b5226c82f722dbad8af1f9205912725adf12d6b5292bd79c18d53
data/iq_rdf.gemspec CHANGED
@@ -32,6 +32,8 @@ Gem::Specification.new do |s|
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
38
  s.files = %w(LICENSE README.md Rakefile iq_rdf.gemspec) + Dir.glob("{lib,rails,test}/**/*")
37
39
  s.test_files = Dir.glob("{test}/**/*")
@@ -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
- node_id = blank_nodes.count + 1
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}"
@@ -143,18 +145,18 @@ module IqRdf
143
145
  end
144
146
 
145
147
  def to_turtle
146
- s = ""
148
+ s = +""
147
149
  @namespaces.values.sort{ |n1, n2| n1.turtle_token <=> n2.turtle_token }.each do |namespace|
148
- s += "@prefix #{namespace.turtle_token}: <#{namespace.uri_prefix}>.\n"
150
+ s << "@prefix #{namespace.turtle_token}: <#{namespace.uri_prefix}>.\n"
149
151
  end
150
- s += "\n"
152
+ s << "\n"
151
153
  @nodes.each do |subject|
152
154
  pref = subject.to_s(@document_language)
153
155
  indent = "".ljust(pref.length)
154
156
 
155
157
  # Render subject, if it is defined as a RDF-type.
156
158
  if subject.rdf_type
157
- s += "#{pref} a #{subject.rdf_type}"
159
+ s << "#{pref} a #{subject.rdf_type}"
158
160
  pref = ";\n" + indent
159
161
  end
160
162
 
@@ -163,13 +165,13 @@ module IqRdf
163
165
  objects = predicate.nodes.map { |object| object.to_s(indent: indent, lang: predicate.lang || subject.lang || @document_language) }
164
166
  .join(", ")
165
167
 
166
- s += "#{pref} #{predicate.to_s} #{objects}"
168
+ s << "#{pref} #{predicate.to_s} #{objects}"
167
169
 
168
170
  pref = ";\n" + indent
169
171
  end
170
- s += ".\n"
172
+ s << ".\n"
171
173
  if @config[:empty_line_between_triples]
172
- s += "\n"
174
+ s << "\n"
173
175
  end
174
176
  end
175
177
  s
@@ -15,7 +15,8 @@
15
15
  module IqRdf
16
16
  class Literal
17
17
 
18
- NTRIPLES_ESCAPE_MAP = {
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
- "#{quote}#{@obj.to_s.gsub("\\", "\\\\\\\\").gsub(/"/, "\\\"")}#{quote}#{lang}#{datatype}"
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]/, NTRIPLES_ESCAPE_MAP)}\"#{suffix}"
86
+ "\"#{@obj.to_s.gsub(/[\\"\n\r\t]/, ESCAPE_MAP)}\"#{suffix}"
71
87
  end
72
88
 
73
89
  def build_xml(xml, &block)
@@ -13,5 +13,5 @@
13
13
  # limitations under the License.
14
14
 
15
15
  module IqRdf
16
- VERSION = "0.2.2"
16
+ VERSION = "0.2.3"
17
17
  end
data/test/test_helper.rb CHANGED
@@ -19,3 +19,5 @@ $LOAD_PATH << File.join(File.dirname(__FILE__), "../lib")
19
19
  require 'iq_rdf'
20
20
 
21
21
  require 'minitest/autorun'
22
+
23
+ require 'rdf/turtle'
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/'
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.2.2
4
+ version: 0.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Till Schulte-Coerne
@@ -65,6 +65,20 @@ 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