From 1ab39639fc83a67649271232db84b919c4691aa8 Mon Sep 17 00:00:00 2001 From: "E. Lynette Rayle" Date: Wed, 20 Nov 2019 09:11:32 -0500 Subject: [PATCH] optionally return results as literals DEFAULT remains returning results as string or the specified data type Also... * remove pin of bundler * add example results to README --- README.md | 2 +- ldpath.gemspec | 1 - lib/ldpath/field_mapping.rb | 28 +++++++++++++------ lib/ldpath/program.rb | 6 ++--- lib/ldpath/result.rb | 9 +++++-- lib/ldpath/selectors.rb | 45 ++++++++++++++++--------------- lib/ldpath/tests.rb | 31 ++++++++++----------- spec/ldpath_program_spec.rb | 54 +++++++++++++++++++++++++++++++++++-- 8 files changed, 123 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index 2e28842..3df2354 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ context = RDF::Graph.new << [uri, RDF::Vocab::DC.title, "Some Title"] program = Ldpath::Program.parse my_program output = program.evaluate uri, context: context -# => { ... } +# => {"title"=>["Some Title"]} ``` ## Compatibility diff --git a/ldpath.gemspec b/ldpath.gemspec index 85af27f..6acac76 100644 --- a/ldpath.gemspec +++ b/ldpath.gemspec @@ -21,7 +21,6 @@ Gem::Specification.new do |spec| spec.add_dependency "linkeddata" # spec.add_development_dependency "bixby", "~> 1.0.0" - spec.add_development_dependency "bundler", "~> 1.5" spec.add_development_dependency "byebug" spec.add_development_dependency "rake" spec.add_development_dependency "rspec" diff --git a/lib/ldpath/field_mapping.rb b/lib/ldpath/field_mapping.rb index 9dd6a01..d1dae1f 100644 --- a/lib/ldpath/field_mapping.rb +++ b/lib/ldpath/field_mapping.rb @@ -9,13 +9,13 @@ def initialize(name:, selector:, field_type: nil, options: {}) @options = options end - def evaluate(program, uri, context) + def evaluate(program, uri, context, maintain_literals: false) case selector when Ldpath::Selector - return to_enum(:evaluate, program, uri, context) unless block_given? + return to_enum(:evaluate, program, uri, context, maintain_literals: maintain_literals) unless block_given? - selector.evaluate(program, uri, context).each do |value| - yield transform_value(value) + selector.evaluate(program, uri, context, maintain_literals: maintain_literals).each do |value| + yield transform_value(value, maintain_literals: maintain_literals) end when RDF::Literal Array(selector.canonicalize.object) @@ -26,18 +26,30 @@ def evaluate(program, uri, context) private - def transform_value(value) - v = if value.is_a? RDF::Literal + def transform_value(value, maintain_literals: false) + v = if value.is_a?(RDF::Literal) && !maintain_literals value.canonicalize.object else value end - if field_type - RDF::Literal.new(v.to_s, datatype: field_type).canonicalize.object + if field_type && !same_type(v, field_type) + v_literal = RDF::Literal.new(v.to_s, datatype: field_type) + maintain_literals ? v_literal : v_literal.canonicalize.object else v end end + + def same_type(object, field_type) + case object + when RDF::Literal + object.comperable_datatype? field_type + when RDF::URI + field_type.to_s.end_with? 'anyURI' + else + false + end + end end end diff --git a/lib/ldpath/program.rb b/lib/ldpath/program.rb index 99a3870..5053386 100644 --- a/lib/ldpath/program.rb +++ b/lib/ldpath/program.rb @@ -36,10 +36,10 @@ def initialize(mappings, default_loader: Ldpath::Loaders::Direct.new, prefixes: end - def evaluate(uri, context: nil, limit_to_context: false) - result = Ldpath::Result.new(self, uri, context: context, limit_to_context: limit_to_context) + def evaluate(uri, context: nil, limit_to_context: false, maintain_literals: false) + result = Ldpath::Result.new(self, uri, context: context, limit_to_context: limit_to_context, maintain_literals: maintain_literals) unless filters.empty? - return {} unless filters.all? { |f| f.evaluate(result, uri, result.context) } + return {} unless filters.all? { |f| f.evaluate(result, uri, result.context, maintain_literals: maintain_literals) } end result.to_hash diff --git a/lib/ldpath/result.rb b/lib/ldpath/result.rb index 78abb2b..a512251 100644 --- a/lib/ldpath/result.rb +++ b/lib/ldpath/result.rb @@ -3,13 +3,14 @@ class Result include Ldpath::Functions attr_reader :program, :uri, :cache, :loaded - def initialize(program, uri, cache: RDF::Util::Cache.new, context: nil, limit_to_context: false) + def initialize(program, uri, cache: RDF::Util::Cache.new, context: nil, limit_to_context: false, maintain_literals: false) @program = program @uri = uri @cache = cache @loaded = {} @context = context @limit_to_context = limit_to_context + @maintain_literals = maintain_literals end def loading(uri, context) @@ -59,7 +60,7 @@ def meta private def evaluate(mapping) - mapping.evaluate(self, uri, context) + mapping.evaluate(self, uri, context, maintain_literals: maintain_literals?) end def function_method?(function) @@ -73,5 +74,9 @@ def mappings def limit_to_context? @limit_to_context end + + def maintain_literals? + @maintain_literals + end end end diff --git a/lib/ldpath/selectors.rb b/lib/ldpath/selectors.rb index d511a1f..6c24c10 100644 --- a/lib/ldpath/selectors.rb +++ b/lib/ldpath/selectors.rb @@ -1,7 +1,7 @@ module Ldpath class Selector - def evaluate(program, uris, context) - return to_enum(:evaluate, program, uris, context) unless block_given? + def evaluate(program, uris, context, maintain_literals: false) + return to_enum(:evaluate, program, uris, context, maintain_literals: maintain_literals) unless block_given? enum_wrap(uris).map do |uri| loading program, uri, context enum_flatten_one(evaluate_one(uri, context)).each do |x| @@ -55,15 +55,15 @@ def initialize(fname, arguments = []) @arguments = Array(arguments) end - def evaluate(program, uris, context) - return to_enum(:evaluate, program, uris, context) unless block_given? + def evaluate(program, uris, context, maintain_literals: false) + return to_enum(:evaluate, program, uris, context, maintain_literals: maintain_literals) unless block_given? enum_wrap(uris).map do |uri| loading program, uri, context args = arguments.map do |i| case i when Selector - i.evaluate(program, uri, context) + i.evaluate(program, uri, context, maintain_literals: maintain_literals) else i end @@ -138,14 +138,14 @@ def initialize(property, repeat) @repeat = repeat end - def evaluate(program, uris, context) - return to_enum(:evaluate, program, uris, context) unless block_given? + def evaluate(program, uris, context, maintain_literals: false) + return to_enum(:evaluate, program, uris, context, maintain_literals: maintain_literals) unless block_given? input = enum_wrap(uris) (0..repeat.max).each_with_index do |i, idx| break if input.none? || (repeat.max == Ldpath::Transform::Infinity && idx > 25) # we're probably lost.. - input = property.evaluate program, input, context + input = property.evaluate program, input, context, maintain_literals: maintain_literals next unless idx >= repeat.min @@ -165,19 +165,20 @@ def initialize(left, right) end class PathSelector < CompoundSelector - def evaluate(program, uris, context, &block) - return to_enum(:evaluate, program, uris, context) unless block_given? + def evaluate(program, uris, context, maintain_literals: false, &block) + return to_enum(:evaluate, program, uris, context, maintain_literals: maintain_literals) unless block_given? - output = left.evaluate(program, uris, context) - right.evaluate(program, output, context, &block) + output = left.evaluate(program, uris, context, maintain_literals: maintain_literals) + right.evaluate(program, output, context, maintain_literals: maintain_literals, &block) end end class UnionSelector < CompoundSelector - def evaluate(program, uris, context) - return to_enum(:evaluate, program, uris, context) unless block_given? + def evaluate(program, uris, context, maintain_literals: false) + return to_enum(:evaluate, program, uris, context, maintain_literals: maintain_literals) unless block_given? - enum_union(left.evaluate(program, uris, context), right.evaluate(program, uris, context)).each do |x| + enum_union(left.evaluate(program, uris, context, maintain_literals: maintain_literals), + right.evaluate(program, uris, context, maintain_literals: maintain_literals)).each do |x| yield x end end @@ -198,10 +199,11 @@ def enum_union(left, right) end class IntersectionSelector < CompoundSelector - def evaluate(program, uris, context) - return to_enum(:evaluate, program, uris, context) unless block_given? + def evaluate(program, uris, context, maintain_literals: false) + return to_enum(:evaluate, program, uris, context, maintain_literals: maintain_literals) unless block_given? - result = left.evaluate(program, uris, context).to_a & right.evaluate(program, uris, context).to_a + result = left.evaluate(program, uris, context, maintain_literals: maintain_literals).to_a & + right.evaluate(program, uris, context, maintain_literals: maintain_literals).to_a result.each do |x| yield x @@ -216,10 +218,11 @@ def initialize(identifier, tap) @tap = tap end - def evaluate(program, uris, context) - return to_enum(:evaluate, program, uris, context) unless block_given? + def evaluate(program, uris, context, maintain_literals: false) + return to_enum(:evaluate, program, uris, context, maintain_literals: maintain_literals) unless block_given? - program.meta[identifier] = tap.evaluate(program, uris, context).map { |x| RDF::Literal.new(x.to_s).canonicalize.object } + program.meta[identifier] = tap.evaluate(program, uris, context, maintain_literals: maintain_literals) + .map { |x| RDF::Literal.new(x.to_s).canonicalize.object } enum_wrap(uris).map do |uri| loading program, uri, context diff --git a/lib/ldpath/tests.rb b/lib/ldpath/tests.rb index 48c420d..05ec16b 100644 --- a/lib/ldpath/tests.rb +++ b/lib/ldpath/tests.rb @@ -7,12 +7,12 @@ def initialize(delegate, test) @test = test end - def evaluate(program, uris, context) - return to_enum(:evaluate, program, uris, context) unless block_given? + def evaluate(program, uris, context, maintain_literals: false) + return to_enum(:evaluate, program, uris, context, maintain_literals: maintain_literals) unless block_given? - entries = delegate.evaluate program, uris, context + entries = delegate.evaluate program, uris, context, maintain_literals: maintain_literals entries.select do |uri| - result = enum_wrap(test.evaluate(program, uri, context)).any? do |x| + result = enum_wrap(test.evaluate(program, uri, context, maintain_literals: maintain_literals)).any? do |x| x end yield uri if result @@ -26,7 +26,7 @@ def initialize(lang) @lang = lang end - def evaluate(_program, uri, _context) + def evaluate(_program, uri, _context, maintain_literals: false) return unless uri.literal? uri if (lang.to_s == "none" && !uri.has_language?) || uri.language.to_s == lang.to_s @@ -39,7 +39,7 @@ def initialize(type) @type = type end - def evaluate(program, uri, _context) + def evaluate(program, uri, _context, maintain_literals: false) return unless uri.literal? uri if uri.has_datatype? && uri.datatype == type @@ -53,8 +53,8 @@ def initialize(delegate) @delegate = delegate end - def evaluate(program, uri, context) - !enum_wrap(delegate.evaluate(program, uri, context)).any? { |x| x } + def evaluate(program, uri, context, maintain_literals: false) + !enum_wrap(delegate.evaluate(program, uri, context, maintain_literals: maintain_literals)).any? { |x| x } end end @@ -66,8 +66,9 @@ def initialize(left, right) @right = right end - def evaluate(program, uri, context) - left.evaluate(program, uri, context).any? || right.evaluate(program, uri, context).any? + def evaluate(program, uri, context, maintain_literals: false) + left.evaluate(program, uri, context, maintain_literals: maintain_literals).any? || + right.evaluate(program, uri, context, maintain_literals: maintain_literals).any? end end @@ -79,9 +80,9 @@ def initialize(left, right) @right = right end - def evaluate(program, uri, context) - left.evaluate(program, uri, context).any? && - right.evaluate(program, uri, context).any? + def evaluate(program, uri, context, maintain_literals: false) + left.evaluate(program, uri, context, maintain_literals: maintain_literals).any? && + right.evaluate(program, uri, context, maintain_literals: maintain_literals).any? end end @@ -93,8 +94,8 @@ def initialize(left, right) @right = right end - def evaluate(program, uri, context) - left.evaluate(program, uri, context).include?(right) + def evaluate(program, uri, context, maintain_literals: false) + left.evaluate(program, uri, context, maintain_literals: maintain_literals).include?(right) end end end diff --git a/spec/ldpath_program_spec.rb b/spec/ldpath_program_spec.rb index 1e059c0..9a8f1e0 100644 --- a/spec/ldpath_program_spec.rb +++ b/spec/ldpath_program_spec.rb @@ -11,7 +11,8 @@ titles = dcterms:title | (dcterms:isPartOf / dcterms:title) | (^dcterms:isPartOf / dcterms:title) :: xsd:string ; no_titles = dcterms:title & (dcterms:isPartOf / dcterms:title) & (^dcterms:isPartOf / dcterms:title) :: xsd:string ; self = . :: xsd:string ; -wildcard = * ::xsd:string ; +str_wildcard = * ::xsd:string ; +uri_wildcard = * ::xsd:anyURI ; child_title = ^dcterms:isPartOf / dcterms:title :: xsd:string ; child_description_en = ^dcterms:isPartOf / dcterms:description[@en] :: xsd:string ; recursive = (dcterms:isPartOf)* ; @@ -57,7 +58,8 @@ expect(result["parent_title"]).to match_array ["Parent title", "Parent English!", "Parent French!"] expect(result["parent_title_en"]).to match_array "Parent English!" expect(result["self"]).to match_array(object) - expect(result["wildcard"]).to include "Hello, world!", parent + expect(result["str_wildcard"]).to include "Hello, world!", parent + expect(result["uri_wildcard"]).to include parent expect(result["child_title"]).to match_array "Child title" expect(result["titles"]).to match_array ["Hello, world!", "Parent title", "Child title", "Parent English!", "Parent French!"] expect(result["no_titles"]).to be_empty @@ -73,6 +75,54 @@ expect(result["is_test"]).to match_array(object) expect(result["is_not_test"]).to be_empty end + + context "when requesting literals" do + let(:title) { RDF::Literal.new("Hello, world!") } + let(:parent_title) { RDF::Literal.new("Parent title") } + let(:child_title) { RDF::Literal.new("Child title") } + let(:en_description) { RDF::Literal.new("English!", language: "en") } + let(:fr_description) { RDF::Literal.new("French!", language: "fr") } + let(:en_parent_title) { RDF::Literal.new("Parent English!", language: "en") } + let(:fr_parent_title) { RDF::Literal.new("Parent French!", language: "fr") } + + it "should return literals" do + graph << [object, RDF::Vocab::DC.title, title.canonicalize.object] + graph << [object, RDF::Vocab::DC.isPartOf, parent] + graph << [object, RDF::Vocab::DC.description, en_description] + graph << [object, RDF::Vocab::DC.description, fr_description] + graph << [object, RDF::URI.new("info:intProperty"), 1] + graph << [object, RDF::URI.new("info:intProperty"), "garbage"] + graph << [object, RDF::URI.new("info:numericProperty"), "1"] + graph << [parent, RDF::Vocab::DC.title, parent_title.canonicalize.object] + graph << [child, RDF::Vocab::DC.isPartOf, object] + graph << [child, RDF::Vocab::DC.title, child_title.canonicalize.object] + graph << [parent, RDF::Vocab::DC.title, en_parent_title] + graph << [parent, RDF::Vocab::DC.title, fr_parent_title] + graph << [parent, RDF::Vocab::DC.isPartOf, grandparent] + + result = subject.evaluate object, context: graph, maintain_literals: true + expect(result["title"]).to match_array RDF::Literal.new("Hello, world!") + expect(result["parent_title"]).to match_array [parent_title, en_parent_title, fr_parent_title] + expect(result["parent_title_en"]).to match_array en_parent_title + expect(result["self"]).to match_array(object.to_s) + expect(result["str_wildcard"]).to include title, parent.to_s + expect(result["uri_wildcard"]).to include parent + expect(result["child_title"]).to match_array child_title + expect(result["titles"]).to match_array [title, parent_title, child_title, en_parent_title, fr_parent_title] + expect(result["no_titles"]).to be_empty + expect(result["recursive"]).to match_array [parent, grandparent] + expect(result["en_description"].first.to_s).to eq "English!" + expect(result["conditional"]).to match_array parent + expect(result["conditional_false"]).to be_empty + expect(result["int_value"]).to match_array RDF::Literal::Integer.new("1") + expect(result["numeric_value"]).to match_array RDF::Literal::Integer.new("1") + expect(result["escaped_string"]).to match_array '\"' + expect(result["and_test"]).to be_empty + expect(result["or_test"]).to match_array(object) + expect(result["is_test"]).to match_array(object) + expect(result["is_not_test"]).to be_empty + end + end end describe "functions" do