rdf-tabular 0.4.0 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +24 -5
  3. data/VERSION +1 -1
  4. data/etc/csvw.jsonld +135 -50
  5. data/lib/rdf/tabular/csvw.rb +215 -181
  6. data/lib/rdf/tabular/format.rb +8 -6
  7. data/lib/rdf/tabular/literal.rb +1 -1
  8. data/lib/rdf/tabular/metadata.rb +61 -80
  9. data/lib/rdf/tabular/reader.rb +18 -15
  10. data/lib/rdf/tabular/uax35.rb +143 -38
  11. data/spec/data/countries-minimal.json +38 -0
  12. data/spec/data/countries-minimal.ttl +36 -0
  13. data/spec/data/countries-standard.json +86 -0
  14. data/spec/data/countries-standard.ttl +75 -0
  15. data/spec/data/countries.csv +4 -0
  16. data/spec/data/countries.csv-minimal.json +16 -0
  17. data/spec/data/countries.csv-minimal.ttl +19 -0
  18. data/spec/data/countries.csv-standard.json +33 -0
  19. data/spec/data/countries.csv-standard.ttl +44 -0
  20. data/spec/data/countries.html +88 -0
  21. data/spec/data/countries.json +53 -0
  22. data/spec/data/countries_embed-minimal.json +38 -0
  23. data/spec/data/countries_embed-minimal.ttl +36 -0
  24. data/spec/data/countries_embed-standard.json +86 -0
  25. data/spec/data/countries_embed-standard.ttl +75 -0
  26. data/spec/data/countries_embed.html +88 -0
  27. data/spec/data/countries_html-minimal.json +38 -0
  28. data/spec/data/countries_html-minimal.ttl +36 -0
  29. data/spec/data/countries_html-standard.json +86 -0
  30. data/spec/data/countries_html-standard.ttl +75 -0
  31. data/spec/data/country-codes-and-names-minimal.json +19 -0
  32. data/spec/data/country-codes-and-names-minimal.ttl +22 -0
  33. data/spec/data/country-codes-and-names-standard.json +47 -0
  34. data/spec/data/country-codes-and-names-standard.ttl +45 -0
  35. data/spec/data/country-codes-and-names.csv +5 -0
  36. data/spec/data/country_slice.csv +4 -0
  37. data/spec/data/junior-roles.csv +3 -0
  38. data/spec/data/junior-roles.json +54 -0
  39. data/spec/data/roles-minimal.json +32 -0
  40. data/spec/data/roles-minimal.ttl +36 -0
  41. data/spec/data/roles-standard.json +56 -0
  42. data/spec/data/roles-standard.ttl +66 -0
  43. data/spec/data/roles.json +23 -0
  44. data/spec/data/senior-roles.csv +3 -0
  45. data/spec/data/senior-roles.json +52 -0
  46. data/spec/data/test232-metadata.json +10 -0
  47. data/spec/data/test232.csv +3 -0
  48. data/spec/data/tree-ops-atd.json +1 -0
  49. data/spec/data/tree-ops-ext-minimal.json +42 -0
  50. data/spec/data/tree-ops-ext-minimal.ttl +34 -0
  51. data/spec/data/tree-ops-ext-standard.json +93 -0
  52. data/spec/data/tree-ops-ext-standard.ttl +82 -0
  53. data/spec/data/tree-ops-ext.csv +4 -0
  54. data/spec/data/tree-ops-ext.json +81 -0
  55. data/spec/data/tree-ops-minimal.json +18 -0
  56. data/spec/data/tree-ops-minimal.ttl +14 -0
  57. data/spec/data/tree-ops-standard.json +44 -0
  58. data/spec/data/tree-ops-standard.ttl +44 -0
  59. data/spec/data/tree-ops-virtual-minimal.json +32 -0
  60. data/spec/data/tree-ops-virtual-minimal.ttl +25 -0
  61. data/spec/data/tree-ops-virtual-standard.json +49 -0
  62. data/spec/data/tree-ops-virtual-standard.ttl +49 -0
  63. data/spec/data/tree-ops-virtual.json +48 -0
  64. data/spec/data/tree-ops.csv +3 -0
  65. data/spec/data/tree-ops.csv-metadata.json +43 -0
  66. data/spec/data/tree-ops.html +54 -0
  67. data/spec/data/tree-ops.tsv +3 -0
  68. data/spec/format_spec.rb +5 -4
  69. data/spec/metadata_spec.rb +10 -16
  70. data/spec/suite_helper.rb +2 -2
  71. data/spec/suite_spec.rb +5 -6
  72. data/spec/uax35_spec.rb +239 -0
  73. metadata +149 -36
  74. data/lib/rdf/tabular/json.rb +0 -0
@@ -24,10 +24,10 @@ module RDF::Tabular
24
24
  #
25
25
  # @see http://www.w3.org/TR/rdf-testcases/#ntriples
26
26
  class Format < RDF::Format
27
- content_type 'text/csv',
27
+ content_type 'text/csv;q=0.4',
28
28
  extensions: [:csv, :tsv],
29
29
  alias: %w{
30
- text/tab-separated-values
30
+ text/tab-separated-values;q=0.4
31
31
  application/csvm+json
32
32
  }
33
33
  content_encoding 'utf-8'
@@ -52,10 +52,12 @@ module RDF::Tabular
52
52
  # @return [Hash{Symbol => Lambda(Array, Hash)}]
53
53
  def self.cli_commands
54
54
  {
55
- :"tabular-json" => {
56
- description: "Generate tabular json output, rather than RDF for Tabular data",
57
- help: "tabulary-json --input-format tabular files ...",
58
- prase: false,
55
+ "tabular-json": {
56
+ description: "Serialize using tabular JSON",
57
+ parse: false,
58
+ filter: {format: :tabular}, # Only shows output format set
59
+ option_use: {output_format: :disabled},
60
+ help: "tabular-json --input-format tabular files ...\nGenerate tabular JSON output, rather than RDF for Tabular data",
59
61
  lambda: ->(argv, opts) do
60
62
  raise ArgumentError, "Outputting Tabular JSON only allowed when input format is tabular." unless opts[:format] == :tabular
61
63
  out = opts[:output] || $stdout
@@ -13,7 +13,7 @@ module RDF::Tabular
13
13
  ##
14
14
  # @param [Object] value
15
15
  # @option options [String] :lexical (nil)
16
- def initialize(value, options = {})
16
+ def initialize(value, **options)
17
17
  @datatype = options[:datatype] || DATATYPE
18
18
  @string = options[:lexical] if options.has_key?(:lexical)
19
19
  if value.is_a?(String)
@@ -136,15 +136,15 @@ module RDF::Tabular
136
136
  # see `RDF::Util::File.open_file` in RDF.rb and {new}
137
137
  # @yield [Metadata]
138
138
  # @raise [IOError] if file not found
139
- def self.open(path, options = {})
139
+ def self.open(path, **options)
140
140
  options = options.merge(
141
141
  headers: {
142
142
  'Accept' => 'application/ld+json, application/json'
143
143
  }
144
144
  )
145
145
  path = "file:" + path unless path =~ /^\w+:/
146
- RDF::Util::File.open_file(path, options) do |file|
147
- self.new(file, options.merge(base: path, filenames: path))
146
+ RDF::Util::File.open_file(path, **options) do |file|
147
+ self.new(file, **options.merge(base: path, filenames: path))
148
148
  end
149
149
  end
150
150
 
@@ -173,16 +173,16 @@ module RDF::Tabular
173
173
  # @option options [RDF::URI] :base
174
174
  # The Base URL to use when expanding the document. This overrides the value of `input` if it is a URL. If not specified and `input` is not an URL, the base URL defaults to the current document URL if in a browser context, or the empty string if there is no document context.
175
175
  # @return [Metadata]
176
- def self.for_input(input, options = {})
176
+ def self.for_input(input, **options)
177
177
  base = options[:base]
178
178
 
179
179
  # Use user metadata, if provided
180
180
  metadata = case options[:metadata]
181
181
  when Metadata then options[:metadata]
182
182
  when Hash
183
- Metadata.new(options[:metadata], options.merge(reason: "load user metadata: #{options[:metadata].inspect}"))
183
+ Metadata.new(options[:metadata], **options.merge(reason: "load user metadata: #{options[:metadata].inspect}"))
184
184
  when String, RDF::URI
185
- Metadata.open(options[:metadata], options.merge(filenames: options[:metadata], reason: "load user metadata: #{options[:metadata].inspect}"))
185
+ Metadata.open(options[:metadata], **options.merge(filenames: options[:metadata], reason: "load user metadata: #{options[:metadata].inspect}"))
186
186
  end
187
187
 
188
188
  # Search for metadata until found
@@ -191,13 +191,13 @@ module RDF::Tabular
191
191
  if !metadata && input.respond_to?(:links) &&
192
192
  link = input.links.find_link(%w(rel describedby))
193
193
  link_loc = RDF::URI(base).join(link.href).to_s
194
- md = Metadata.open(link_loc, options.merge(filenames: link_loc, reason: "load linked metadata: #{link_loc}"))
194
+ md = Metadata.open(link_loc, **options.merge(filenames: link_loc, reason: "load linked metadata: #{link_loc}"))
195
195
  if md
196
196
  # Metadata must describe file to be useful
197
197
  if md.describes_file?(base)
198
198
  metadata = md
199
199
  else
200
- log_warn("Found metadata at #{link_loc}, which does not describe #{base}, ignoring", options)
200
+ log_warn("Found metadata at #{link_loc}, which does not describe #{base}, ignoring", **options)
201
201
  end
202
202
  end
203
203
  end
@@ -206,28 +206,28 @@ module RDF::Tabular
206
206
  # If we still don't have metadata, load the site-wide configuration file and use templates found there as locations
207
207
  if !metadata && base
208
208
  templates = site_wide_config(base)
209
- log_debug("for_input", options) {"templates: #{templates.map(&:to_s).inspect}"}
209
+ log_debug("for_input", **options) {"templates: #{templates.map(&:to_s).inspect}"}
210
210
  locs = templates.map do |template|
211
211
  t = Addressable::Template.new(template)
212
212
  RDF::URI(base).join(t.expand(url: base).to_s)
213
213
  end
214
- log_debug("for_input", options) {"locs: #{locs.map(&:to_s).inspect}"}
214
+ log_debug("for_input", **options) {"locs: #{locs.map(&:to_s).inspect}"}
215
215
 
216
216
  locs.each do |loc|
217
217
  metadata ||= begin
218
- md = Metadata.open(loc, options.merge(filenames: loc, reason: "load found metadata: #{loc}"))
218
+ md = Metadata.open(loc, **options.merge(filenames: loc, reason: "load found metadata: #{loc}"))
219
219
  # Metadata must describe file to be useful
220
220
  if md
221
221
  # Metadata must describe file to be useful
222
222
  if md.describes_file?(base)
223
223
  md
224
224
  else
225
- log_warn("Found metadata at #{loc}, which does not describe #{base}, ignoring", options)
225
+ log_warn("Found metadata at #{loc}, which does not describe #{base}, ignoring", **options)
226
226
  nil
227
227
  end
228
228
  end
229
229
  rescue IOError
230
- log_debug("for_input", options) {"failed to load found metadata #{loc}: #{$!}"}
230
+ log_debug("for_input", **options) {"failed to load found metadata #{loc}: #{$!}"}
231
231
  nil
232
232
  end
233
233
  end
@@ -236,8 +236,8 @@ module RDF::Tabular
236
236
  # Return either the merge or user- and found-metadata, any of these, or an empty TableGroup
237
237
  metadata = case
238
238
  when metadata then metadata
239
- when base then TableGroup.new({"@context" => "http://www.w3.org/ns/csvw", tables: [{url: base}]}, options)
240
- else TableGroup.new({"@context" => "http://www.w3.org/ns/csvw", tables: [{url: nil}]}, options)
239
+ when base then TableGroup.new({"@context" => "http://www.w3.org/ns/csvw", tables: [{url: base}]}, **options)
240
+ else TableGroup.new({"@context" => "http://www.w3.org/ns/csvw", tables: [{url: nil}]}, **options)
241
241
  end
242
242
 
243
243
  # Make TableGroup, if not already
@@ -246,7 +246,7 @@ module RDF::Tabular
246
246
 
247
247
  ##
248
248
  # @private
249
- def self.new(input, options = {})
249
+ def self.new(input, **options)
250
250
  # Triveal case
251
251
  return input if input.is_a?(Metadata)
252
252
 
@@ -297,7 +297,7 @@ module RDF::Tabular
297
297
  end
298
298
 
299
299
  md = klass.allocate
300
- md.send(:initialize, object, options)
300
+ md.send(:initialize, object, **options)
301
301
  md
302
302
  rescue ::JSON::ParserError
303
303
  raise Error, "Expected input to be a JSON Object"
@@ -318,7 +318,7 @@ module RDF::Tabular
318
318
  # @option options [Boolean] :validate Strict metadata validation
319
319
  # @raise [Error]
320
320
  # @return [Metadata]
321
- def initialize(input, options = {})
321
+ def initialize(input, **options)
322
322
  @options = options.dup
323
323
 
324
324
  # Parent of this Metadata, if any
@@ -467,16 +467,16 @@ module RDF::Tabular
467
467
  object[:tableSchema] = case value
468
468
  when String
469
469
  link = context.base.join(value).to_s
470
- md = Schema.open(link, @options.merge(parent: self, context: nil, normalize: true))
470
+ md = Schema.open(link, **@options.merge(parent: self, context: nil, normalize: true))
471
471
  md[:@id] ||= link
472
472
  md
473
473
  when Hash
474
- Schema.new(value, @options.merge(parent: self, context: nil))
474
+ Schema.new(value, **@options.merge(parent: self, context: nil))
475
475
  when Schema
476
476
  value
477
477
  else
478
478
  log_warn "#{type} has invalid property 'tableSchema' (#{value.inspect}): expected a URL or object"
479
- Schema.new({}, @options.merge(parent: self, context: nil))
479
+ Schema.new({}, **@options.merge(parent: self, context: nil))
480
480
  end
481
481
  end
482
482
 
@@ -491,7 +491,7 @@ module RDF::Tabular
491
491
  when object[:dialect] then object[:dialect]
492
492
  when parent then parent.dialect
493
493
  when is_a?(Table) || is_a?(TableGroup)
494
- d = Dialect.new({}, @options.merge(parent: self, context: nil))
494
+ d = Dialect.new({}, **@options.merge(parent: self, context: nil))
495
495
  self.dialect = d unless self.parent
496
496
  d
497
497
  else
@@ -514,11 +514,11 @@ module RDF::Tabular
514
514
  @dialect = object[:dialect] = case value
515
515
  when String
516
516
  link = context.base.join(value).to_s
517
- md = Metadata.open(link, @options.merge(parent: self, context: nil, normalize: true))
517
+ md = Metadata.open(link, **@options.merge(parent: self, context: nil, normalize: true))
518
518
  md[:@id] ||= link
519
519
  md
520
520
  when Hash
521
- Dialect.new(value, @options.merge(parent: self, context: nil))
521
+ Dialect.new(value, **@options.merge(parent: self, context: nil))
522
522
  when Dialect
523
523
  value
524
524
  else
@@ -532,8 +532,8 @@ module RDF::Tabular
532
532
  # @raise [Error] if datatype is not valid
533
533
  def datatype=(value)
534
534
  val = case value
535
- when Hash then Datatype.new(value, @options.merge(parent: self))
536
- else Datatype.new({base: value}, @options.merge(parent: self))
535
+ when Hash then Datatype.new(value, **@options.merge(parent: self))
536
+ else Datatype.new({base: value}, **@options.merge(parent: self))
537
537
  end
538
538
 
539
539
  if val.valid? || value.is_a?(Hash)
@@ -564,7 +564,7 @@ module RDF::Tabular
564
564
  end
565
565
 
566
566
  ##
567
- # Validate metadata, raising an error containing all errors detected during validation
567
+ # Validate metadata and content, raising an error containing all errors detected during validation
568
568
  # @raise [Error] Raise error if metadata has any unexpected properties
569
569
  # @return [self]
570
570
  def validate
@@ -872,7 +872,7 @@ module RDF::Tabular
872
872
  csv << data unless data.empty?
873
873
  end
874
874
  else
875
- csv = ::CSV.new(input, csv_options)
875
+ csv = ::CSV.new(input, **csv_options)
876
876
  # Skip skipRows and headerRowCount
877
877
  skipped = (dialect.skipRows.to_i + dialect.headerRowCount)
878
878
  (1..skipped).each {csv.shift}
@@ -891,7 +891,7 @@ module RDF::Tabular
891
891
  next
892
892
  end
893
893
  number += 1
894
- row = Row.new(data, self, number, number + skipped, @options)
894
+ row = Row.new(data, self, number, number + skipped, **@options)
895
895
  (self.object[:rows] ||= []) << row if @options[:validate] # Keep track of rows when validating
896
896
  yield(row)
897
897
  end
@@ -1036,13 +1036,13 @@ module RDF::Tabular
1036
1036
  end
1037
1037
  index = 0
1038
1038
  object_columns.all? do |cb|
1039
- ca = non_virtual_columns[index] || Column.new({}, @options)
1039
+ ca = non_virtual_columns[index] || Column.new({}, **@options)
1040
1040
  ta = ca.titles || {}
1041
1041
  tb = cb.titles || {}
1042
1042
  if !ca.object.has_key?(:name) && !cb.object.has_key?(:name) && ta.empty? && tb.empty?
1043
1043
  true
1044
1044
  elsif ca.object.has_key?(:name) && cb.object.has_key?(:name)
1045
- raise Error, "Columns don't match on name: #{ca.name}, #{cb.name}" unless ca.name == cb.name
1045
+ raise Error, "Column #{index + 1} doesn't match on name: #{ca.name || 'no name'}, #{cb.name || 'no name'}" unless ca.name == cb.name
1046
1046
  elsif @options[:validate] || !ta.empty? && !tb.empty?
1047
1047
  # If validating, column compatibility requires strict match between titles
1048
1048
  titles_match = case
@@ -1066,10 +1066,10 @@ module RDF::Tabular
1066
1066
  true
1067
1067
  elsif !@options[:validate]
1068
1068
  # If not validating, columns don't match, but processing continues
1069
- log_warn "Columns don't match on titles: #{ca.titles.inspect} vs #{cb.titles.inspect}"
1069
+ log_warn "Column #{index + 1} doesn't match on titles: #{Array(ta['und']).join(',').inspect} vs #{Array(tb['und']).join(',').inspect}"
1070
1070
  true
1071
1071
  else
1072
- raise Error, "Columns don't match on titles: #{ca.titles.inspect} vs #{cb.titles.inspect}"
1072
+ raise Error, "Column #{index + 1} doesn't match on titles: #{Array(ta['und']).join(',').inspect} vs #{Array(tb['und']).join(',').inspect}"
1073
1073
  end
1074
1074
  end
1075
1075
  index += 1
@@ -1235,13 +1235,13 @@ module RDF::Tabular
1235
1235
  end
1236
1236
 
1237
1237
  # General setter for array properties
1238
- def set_array_value(key, value, klass, options={})
1238
+ def set_array_value(key, value, klass, **options)
1239
1239
  object[key] = case value
1240
1240
  when Array
1241
1241
  value.map do |v|
1242
1242
  case v
1243
1243
  when Hash
1244
- klass.new(v, @options.merge(options).merge(parent: self, context: nil))
1244
+ klass.new(v, **@options.merge(options).merge(parent: self, context: nil))
1245
1245
  else v
1246
1246
  end
1247
1247
  end
@@ -1282,11 +1282,11 @@ module RDF::Tabular
1282
1282
  class DebugContext
1283
1283
  include RDF::Util::Logger
1284
1284
  end
1285
- def self.log_debug(*args, &block)
1286
- DebugContext.new.log_debug(*args, &block)
1285
+ def self.log_debug(*args, **options, &block)
1286
+ DebugContext.new.log_debug(*args, **options, &block)
1287
1287
  end
1288
- def self.log_warn(*args)
1289
- DebugContext.new.log_warn(*args)
1288
+ def self.log_warn(*args, **options)
1289
+ DebugContext.new.log_warn(*args, **options)
1290
1290
  end
1291
1291
  end
1292
1292
 
@@ -1434,7 +1434,7 @@ module RDF::Tabular
1434
1434
  content['@context'] = object.delete(:@context) if object[:@context]
1435
1435
  ctx = @context
1436
1436
  remove_instance_variable(:@context) if instance_variables.include?(:@context)
1437
- tg = TableGroup.new(content, @options.merge(context: ctx, filenames: @filenames, base: base))
1437
+ tg = TableGroup.new(content, **@options.merge(context: ctx, filenames: @filenames, base: base))
1438
1438
  @parent = tg # Link from parent
1439
1439
  tg
1440
1440
  end
@@ -1489,7 +1489,7 @@ module RDF::Tabular
1489
1489
  number += 1
1490
1490
  case v
1491
1491
  when Hash
1492
- Column.new(v, @options.merge(
1492
+ Column.new(v, **@options.merge(
1493
1493
  table: (parent if parent.is_a?(Table)),
1494
1494
  parent: self,
1495
1495
  context: nil,
@@ -1621,8 +1621,8 @@ module RDF::Tabular
1621
1621
  def name
1622
1622
  self[:name] || if titles && (ts = titles[context.default_language || 'und'] || titles[self.lang || 'und'])
1623
1623
  n = Array(ts).first
1624
- n0 = URI.encode(n[0,1], /[^a-zA-Z0-9]/).encode("utf-8")
1625
- n1 = URI.encode(n[1..-1], /[^\w\.]/).encode("utf-8")
1624
+ n0 = RDF::URI.encode(n[0,1], /[^a-zA-Z0-9]/).encode("utf-8")
1625
+ n1 = RDF::URI.encode(n[1..-1], /[^\w\.]/).encode("utf-8")
1626
1626
  "#{n0}#{n1}"
1627
1627
  end || "_col.#{number}"
1628
1628
  end
@@ -1783,12 +1783,12 @@ module RDF::Tabular
1783
1783
  # @option options [String] :lang, language to set in table, if any
1784
1784
  # @return [Metadata] Tabular metadata
1785
1785
  # @see http://w3c.github.io/csvw/syntax/#parsing
1786
- def embedded_metadata(input, metadata, options = {})
1786
+ def embedded_metadata(input, metadata, **options)
1787
1787
  options = options.dup
1788
1788
  options.delete(:context) # Don't accidentally use a passed context
1789
1789
  # Normalize input to an IO object
1790
1790
  if input.is_a?(String)
1791
- return ::RDF::Util::File.open_file(input) {|f| embedded_metadata(f, metadata, options.merge(base: input.to_s))}
1791
+ return ::RDF::Util::File.open_file(input) {|f| embedded_metadata(f, metadata, **options.merge(base: input.to_s))}
1792
1792
  end
1793
1793
 
1794
1794
  table = {
@@ -1826,7 +1826,7 @@ module RDF::Tabular
1826
1826
  row.xpath('th').map(&:content).each_with_index do |value, index|
1827
1827
  # Skip columns
1828
1828
  skipCols = skipColumns.to_i
1829
- next if index < skipCols
1829
+ next if index < skipCols || value.to_s.empty?
1830
1830
 
1831
1831
  # Trim value
1832
1832
  value.lstrip! if %w(true start).include?(trim.to_s)
@@ -1837,11 +1837,11 @@ module RDF::Tabular
1837
1837
  column = columns[index - skipCols] ||= {
1838
1838
  "titles" => {lang => []},
1839
1839
  }
1840
- column["titles"][lang] << value
1840
+ column["titles"][lang] << value if value
1841
1841
  end
1842
1842
  end
1843
1843
  else
1844
- csv = ::CSV.new(input, csv_options)
1844
+ csv = ::CSV.new(input, **csv_options)
1845
1845
  (1..skipRows.to_i).each do
1846
1846
  value = csv.shift.join(delimiter) # Skip initial lines, these form comment annotations
1847
1847
  # Trim value
@@ -1858,7 +1858,7 @@ module RDF::Tabular
1858
1858
  Array(row_data).each_with_index do |value, index|
1859
1859
  # Skip columns
1860
1860
  skipCols = skipColumns.to_i
1861
- next if index < skipCols
1861
+ next if index < skipCols || value.to_s.empty?
1862
1862
 
1863
1863
  # Trim value
1864
1864
  value.lstrip! if %w(true start).include?(trim.to_s)
@@ -1876,7 +1876,7 @@ module RDF::Tabular
1876
1876
  log_debug("embedded_metadata") {"table: #{table.inspect}"}
1877
1877
  input.rewind if input.respond_to?(:rewind)
1878
1878
 
1879
- Table.new(table, options.merge(reason: "load embedded metadata: #{table['@id']}"))
1879
+ Table.new(table, **options.merge(reason: "load embedded metadata: #{table['@id']}"))
1880
1880
  end
1881
1881
  end
1882
1882
 
@@ -2026,7 +2026,7 @@ module RDF::Tabular
2026
2026
  # @param [Hash{Symbol => Object}] options ({})
2027
2027
  # @option options [Boolean] :validate check for PK/FK consistency
2028
2028
  # @return [Row]
2029
- def initialize(row, metadata, number, source_number, options = {})
2029
+ def initialize(row, metadata, number, source_number, **options)
2030
2030
  @table = metadata
2031
2031
  @number = number
2032
2032
  @sourceNumber = source_number
@@ -2058,13 +2058,13 @@ module RDF::Tabular
2058
2058
 
2059
2059
  # create column if necessary
2060
2060
  columns[index - skipColumns] ||=
2061
- Column.new({}, options.merge(table: metadata, parent: metadata.tableSchema, number: index + 1 - skipColumns))
2061
+ Column.new({}, **options.merge(table: metadata, parent: metadata.tableSchema, number: index + 1 - skipColumns))
2062
2062
 
2063
2063
  column = columns[index - skipColumns]
2064
2064
 
2065
2065
  @values << cell = Cell.new(metadata, column, self, value)
2066
2066
 
2067
- datatype = column.datatype || Datatype.new({base: "string"}, options.merge(parent: column))
2067
+ datatype = column.datatype || Datatype.new({base: "string"}, **options.merge(parent: column))
2068
2068
  value = value.gsub(/\r\n\t/, ' ') unless %w(string json xml html anyAtomicType).include?(datatype.base)
2069
2069
  value = value.strip.gsub(/\s+/, ' ') unless %w(string json xml html anyAtomicType normalizedString).include?(datatype.base)
2070
2070
  # if the resulting string is an empty string, apply the remaining steps to the string given by the default property
@@ -2110,7 +2110,7 @@ module RDF::Tabular
2110
2110
  # Map URLs for row
2111
2111
  @values.each_with_index do |cell, index|
2112
2112
  mapped_values = map_values.merge(
2113
- "_name" => URI.decode(cell.column.name),
2113
+ "_name" => CGI.unescape(cell.column.name),
2114
2114
  "_column" => cell.column.number,
2115
2115
  "_sourceColumn" => cell.column.sourceNumber
2116
2116
  )
@@ -2171,33 +2171,13 @@ module RDF::Tabular
2171
2171
  decimalChar = format["decimalChar"] || '.'
2172
2172
  pattern = format["pattern"]
2173
2173
 
2174
- if !datatype.parse_uax35_number(pattern, value, groupChar || ",", decimalChar)
2174
+ begin
2175
+ value = datatype.parse_uax35_number(pattern, value, groupChar || ",", decimalChar)
2176
+ rescue UAX35::ParseError
2175
2177
  value_errors << "#{value} does not match numeric pattern #{pattern ? pattern.inspect : 'default'}"
2176
2178
  end
2177
2179
 
2178
- # pattern facet failed
2179
- value_errors << "#{value} has repeating #{groupChar.inspect}" if groupChar && value.include?(groupChar*2)
2180
- value = value.gsub(groupChar || ',', '')
2181
- value = value.sub(decimalChar, '.')
2182
-
2183
- # Extract percent or per-mille sign
2184
- percent = permille = false
2185
- case value
2186
- when /%/
2187
- value = value.sub('%', '')
2188
- percent = true
2189
- when /‰/
2190
- value = value.sub('‰', '')
2191
- permille = true
2192
- end
2193
-
2194
2180
  lit = RDF::Literal(value, datatype: expanded_dt)
2195
- if percent || permille
2196
- o = lit.object
2197
- o = o / 100 if percent
2198
- o = o / 1000 if permille
2199
- lit = RDF::Literal(o, datatype: expanded_dt)
2200
- end
2201
2181
 
2202
2182
  if !lit.plain? && datatype.minimum && lit < datatype.minimum
2203
2183
  value_errors << "#{value} < minimum #{datatype.minimum}"
@@ -2238,10 +2218,11 @@ module RDF::Tabular
2238
2218
  end
2239
2219
  end
2240
2220
  when :date, :time, :dateTime, :dateTimeStamp, :datetime
2241
- if value = datatype.parse_uax35_date(format, value)
2221
+ begin
2222
+ value = datatype.parse_uax35_date(format, value)
2242
2223
  lit = RDF::Literal(value, datatype: expanded_dt)
2243
- else
2244
- value_errors << "#{original_value} does not match format #{format}"
2224
+ rescue UAX35::ParseError
2225
+ value_errors << "#{value} does not match format #{format}"
2245
2226
  end
2246
2227
  when :duration, :dayTimeDuration, :yearMonthDuration
2247
2228
  # SPEC CONFUSION: surely format also includes that for other duration types?