shex 0.4.0 → 0.5.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/README.md +8 -8
- data/VERSION +1 -1
- data/etc/doap.ttl +1 -1
- data/lib/shex.rb +11 -11
- data/lib/shex/algebra.rb +43 -33
- data/lib/shex/algebra/node_constraint.rb +12 -3
- data/lib/shex/algebra/not.rb +1 -1
- data/lib/shex/algebra/operator.rb +20 -13
- data/lib/shex/algebra/schema.rb +106 -33
- data/lib/shex/algebra/shape_expression.rb +1 -1
- data/lib/shex/algebra/stem.rb +46 -2
- data/lib/shex/algebra/stem_range.rb +70 -2
- data/lib/shex/extensions/extension.rb +2 -2
- data/lib/shex/meta.rb +4282 -2334
- data/lib/shex/parser.rb +223 -72
- data/lib/shex/shex_context.rb +67 -68
- data/lib/shex/terminals.rb +36 -21
- metadata +2 -2
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: d66489982d96aa02cd6be18354daaa81934af243
         | 
| 4 | 
            +
              data.tar.gz: 7385a3e9933b1c81e09b7d0a126a1760de9aa4c2
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: d060ca866830927b3342352249337b6c06b25060677a8fe46e3a017d9c7a269749e5e1e792928eb6c4324bbaffb3bab91da1fb560d5e97ef33df57847df281e7
         | 
| 7 | 
            +
              data.tar.gz: 7bf7a3fb6bd9d121a00ba837a5b0725cb5b2732d04dc8c959a8ee37ff3a311340dd9ebdf4496254dc450c39ee139e92800773990a133d922ffb98e5e7f602160
         | 
    
        data/README.md
    CHANGED
    
    | @@ -38,7 +38,7 @@ The ShEx gem implements a [ShEx][ShExSpec] Shape Expression engine. | |
| 38 38 | 
             
                    (doap:name;doap:description|dc:title;dc:description)+;
         | 
| 39 39 | 
             
                    doap:category*;
         | 
| 40 40 | 
             
                    doap:developer IRI;
         | 
| 41 | 
            -
                    doap:implements    [< | 
| 41 | 
            +
                    doap:implements    [<http://shex.io/shex-semantics/>]
         | 
| 42 42 | 
             
                  }
         | 
| 43 43 | 
             
                )
         | 
| 44 44 | 
             
                graph = RDF::Graph.load("etc/doap.ttl")
         | 
| @@ -109,26 +109,26 @@ The ShEx gem implements a [ShEx][ShExSpec] Shape Expression engine. | |
| 109 109 | 
             
                                ]
         | 
| 110 110 | 
             
                              }
         | 
| 111 111 | 
             
                            ],
         | 
| 112 | 
            -
                            "min": 1, "max":  | 
| 112 | 
            +
                            "min": 1, "max": -1
         | 
| 113 113 | 
             
                          },
         | 
| 114 114 | 
             
                          {
         | 
| 115 115 | 
             
                            "type": "TripleConstraint",
         | 
| 116 116 | 
             
                            "predicate": "http://usefulinc.com/ns/doap#category",
         | 
| 117 117 | 
             
                            "valueExpr": {"type": "NodeConstraint", "nodeKind": "iri"},
         | 
| 118 | 
            -
                            "min": 0, "max":  | 
| 118 | 
            +
                            "min": 0, "max": -1
         | 
| 119 119 | 
             
                          },
         | 
| 120 120 | 
             
                          {
         | 
| 121 121 | 
             
                            "type": "TripleConstraint",
         | 
| 122 122 | 
             
                            "predicate": "http://usefulinc.com/ns/doap#developer",
         | 
| 123 123 | 
             
                            "valueExpr": {"type": "NodeConstraint", "nodeKind": "iri"},
         | 
| 124 | 
            -
                            "min": 1, "max":  | 
| 124 | 
            +
                            "min": 1, "max": -1
         | 
| 125 125 | 
             
                          },
         | 
| 126 126 | 
             
                          {
         | 
| 127 127 | 
             
                            "type": "TripleConstraint",
         | 
| 128 128 | 
             
                            "predicate": "http://usefulinc.com/ns/doap#implements",
         | 
| 129 129 | 
             
                            "valueExpr": {
         | 
| 130 130 | 
             
                              "type": "NodeConstraint",
         | 
| 131 | 
            -
                              "values": [" | 
| 131 | 
            +
                              "values": ["http://shex.io/shex-semantics/"]
         | 
| 132 132 | 
             
                            }
         | 
| 133 133 | 
             
                          }
         | 
| 134 134 | 
             
                        ]
         | 
| @@ -143,7 +143,7 @@ The ShEx gem implements a [ShEx][ShExSpec] Shape Expression engine. | |
| 143 143 | 
             
                # => true
         | 
| 144 144 |  | 
| 145 145 | 
             
            ## Extensions
         | 
| 146 | 
            -
            ShEx has an extension mechanism using [Semantic Actions]( | 
| 146 | 
            +
            ShEx has an extension mechanism using [Semantic Actions](http://shex.io/shex-semantics/#semantic-actions). Extensions may be implemented in Ruby ShEx by sub-classing {ShEx::Extension} and implementing {ShEx::Extension#visit} and possibly {ShEx::Extension#initialize}, {ShEx::Extension#enter}, {ShEx::Extension#exit}, and {ShEx::Extension#close}. The `#visit` method will be called as part of the `#satisfies?` operation.
         | 
| 147 147 |  | 
| 148 148 | 
             
                require 'shex'
         | 
| 149 149 | 
             
                class ShEx::Test < ShEx::Extension("http://shex.io/extensions/Test/")
         | 
| @@ -179,7 +179,7 @@ The result of parsing either ShExC or ShExJ is the creation of a set of executab | |
| 179 179 | 
             
            ## Dependencies
         | 
| 180 180 |  | 
| 181 181 | 
             
            * [Ruby](http://ruby-lang.org/) (>= 2.2.2)
         | 
| 182 | 
            -
            * [RDF.rb](http://rubygems.org/gems/rdf) ( | 
| 182 | 
            +
            * [RDF.rb](http://rubygems.org/gems/rdf) (~> 2.2)
         | 
| 183 183 |  | 
| 184 184 | 
             
            ## Installation
         | 
| 185 185 |  | 
| @@ -237,7 +237,7 @@ This repository uses [Git Flow](https://github.com/nvie/gitflow) to mange develo | |
| 237 237 | 
             
            This is free and unencumbered public domain software. For more information,
         | 
| 238 238 | 
             
            see <http://unlicense.org/> or the accompanying {file:LICENSE} file.
         | 
| 239 239 |  | 
| 240 | 
            -
            [ShExSpec]:      | 
| 240 | 
            +
            [ShExSpec]:     http://shex.io/shex-semantics/
         | 
| 241 241 | 
             
            [RDF]:          http://www.w3.org/RDF/
         | 
| 242 242 | 
             
            [RDF.rb]:       http://rubydoc.info/github/ruby-rdf/rdf
         | 
| 243 243 | 
             
            [EBNF]:         http://rubygems.org/gems/ebnf
         | 
    
        data/VERSION
    CHANGED
    
    | @@ -1 +1 @@ | |
| 1 | 
            -
            0. | 
| 1 | 
            +
            0.5.0
         | 
    
        data/etc/doap.ttl
    CHANGED
    
    | @@ -15,7 +15,7 @@ | |
| 15 15 | 
             
              doap:description   "ShEx is an Shape Expression engine for the RDF.rb library suite."@en ;
         | 
| 16 16 | 
             
              doap:created       "2016-12-09"^^xsd:date ;
         | 
| 17 17 | 
             
              doap:programming-language "Ruby" ;
         | 
| 18 | 
            -
              doap:implements    < | 
| 18 | 
            +
              doap:implements    <http://shex.io/shex-semantics/> ;
         | 
| 19 19 | 
             
              doap:category      <http://dbpedia.org/resource/Resource_Description_Framework>,
         | 
| 20 20 | 
             
                                 <http://dbpedia.org/resource/Ruby_(programming_language)> ;
         | 
| 21 21 | 
             
              doap:download-page <http://rubygems.org/gems/shex> ;
         | 
    
        data/lib/shex.rb
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ##
         | 
| 2 2 | 
             
            # A ShEx runtime for RDF.rb.
         | 
| 3 3 | 
             
            #
         | 
| 4 | 
            -
            # @see  | 
| 4 | 
            +
            # @see http://shex.io/shex-semantics/#shexc
         | 
| 5 5 | 
             
            module ShEx
         | 
| 6 6 | 
             
              autoload :Algebra,    'shex/algebra'
         | 
| 7 7 | 
             
              autoload :Meta,       'shex/meta'
         | 
| @@ -11,7 +11,7 @@ module ShEx | |
| 11 11 | 
             
              autoload :VERSION,    'shex/version'
         | 
| 12 12 |  | 
| 13 13 | 
             
              # Location of the ShEx JSON-LD context
         | 
| 14 | 
            -
              CONTEXT = " | 
| 14 | 
            +
              CONTEXT = "http://www.w3.org/ns/shex.jsonld"
         | 
| 15 15 |  | 
| 16 16 | 
             
              # Extensions defined in this gem
         | 
| 17 17 | 
             
              EXTENSIONS = %w{test}
         | 
| @@ -71,11 +71,11 @@ module ShEx | |
| 71 71 | 
             
              # @param (see ShEx::Algebra::Schema#execute)
         | 
| 72 72 | 
             
              # @return (see ShEx::Algebra::Schema#execute)
         | 
| 73 73 | 
             
              # @raise (see ShEx::Algebra::Schema#execute)
         | 
| 74 | 
            -
              def self.execute(expression, queryable,  | 
| 74 | 
            +
              def self.execute(expression, queryable, map, format: 'shexc', **options)
         | 
| 75 75 | 
             
                shex = self.parse(expression, options.merge(format: format))
         | 
| 76 76 | 
             
                queryable = queryable || RDF::Graph.new
         | 
| 77 77 |  | 
| 78 | 
            -
                shex.execute( | 
| 78 | 
            +
                shex.execute(queryable, map, options)
         | 
| 79 79 | 
             
              end
         | 
| 80 80 |  | 
| 81 81 | 
             
              ##
         | 
| @@ -89,11 +89,11 @@ module ShEx | |
| 89 89 | 
             
              # @param (see ShEx::Algebra::Schema#satisfies?)
         | 
| 90 90 | 
             
              # @return (see ShEx::Algebra::Schema#satisfies?)
         | 
| 91 91 | 
             
              # @raise (see ShEx::Algebra::Schema#satisfies?)
         | 
| 92 | 
            -
              def self.satisfies?(expression, queryable,  | 
| 92 | 
            +
              def self.satisfies?(expression, queryable, map, format: 'shexc', **options)
         | 
| 93 93 | 
             
                shex = self.parse(expression, options.merge(format: format))
         | 
| 94 94 | 
             
                queryable = queryable || RDF::Graph.new
         | 
| 95 95 |  | 
| 96 | 
            -
                shex.satisfies?( | 
| 96 | 
            +
                shex.satisfies?(queryable, map, options)
         | 
| 97 97 | 
             
              end
         | 
| 98 98 |  | 
| 99 99 | 
             
              ##
         | 
| @@ -129,14 +129,14 @@ module ShEx | |
| 129 129 | 
             
              class NotSatisfied < Error
         | 
| 130 130 | 
             
                ##
         | 
| 131 131 | 
             
                # The expression which was not satified
         | 
| 132 | 
            -
                # @return [ShEx:: | 
| 132 | 
            +
                # @return [ShEx::Algebra::ShapeExpression]
         | 
| 133 133 | 
             
                attr_reader :expression
         | 
| 134 134 |  | 
| 135 135 | 
             
                ##
         | 
| 136 136 | 
             
                # Initializes a new parser error instance.
         | 
| 137 137 | 
             
                #
         | 
| 138 | 
            -
                # @param  [String, #to_s] | 
| 139 | 
            -
                # @param  [ | 
| 138 | 
            +
                # @param  [String, #to_s]                   message
         | 
| 139 | 
            +
                # @param  [ShEx::Algebra::ShapeExpression]  expression
         | 
| 140 140 | 
             
                def initialize(message, expression: self)
         | 
| 141 141 | 
             
                  @expression = expression
         | 
| 142 142 | 
             
                  super(message.to_s)
         | 
| @@ -157,8 +157,8 @@ module ShEx | |
| 157 157 | 
             
                ##
         | 
| 158 158 | 
             
                # Initializes a new parser error instance.
         | 
| 159 159 | 
             
                #
         | 
| 160 | 
            -
                # @param  [String, #to_s] | 
| 161 | 
            -
                # @param  [ | 
| 160 | 
            +
                # @param  [String, #to_s]                   message
         | 
| 161 | 
            +
                # @param  [ShEx::Algebra::TripleExpression] expression
         | 
| 162 162 | 
             
                def initialize(message, expression: self)
         | 
| 163 163 | 
             
                  @expression = expression
         | 
| 164 164 | 
             
                  super(message.to_s)
         | 
    
        data/lib/shex/algebra.rb
    CHANGED
    
    | @@ -7,25 +7,31 @@ module ShEx | |
| 7 7 | 
             
              #
         | 
| 8 8 | 
             
              # @author [Gregg Kellogg](http://greggkellogg.net/)
         | 
| 9 9 | 
             
              module Algebra
         | 
| 10 | 
            -
                autoload :And, | 
| 11 | 
            -
                autoload :Annotation, | 
| 12 | 
            -
                autoload :EachOf, | 
| 13 | 
            -
                autoload :External, | 
| 14 | 
            -
                autoload : | 
| 15 | 
            -
                autoload : | 
| 16 | 
            -
                autoload : | 
| 17 | 
            -
                autoload : | 
| 18 | 
            -
                autoload : | 
| 19 | 
            -
                autoload : | 
| 20 | 
            -
                autoload : | 
| 21 | 
            -
                autoload : | 
| 22 | 
            -
                autoload : | 
| 23 | 
            -
                autoload : | 
| 24 | 
            -
                autoload : | 
| 25 | 
            -
                autoload : | 
| 10 | 
            +
                autoload :And,              'shex/algebra/and'
         | 
| 11 | 
            +
                autoload :Annotation,       'shex/algebra/annotation'
         | 
| 12 | 
            +
                autoload :EachOf,           'shex/algebra/each_of'
         | 
| 13 | 
            +
                autoload :External,         'shex/algebra/external'
         | 
| 14 | 
            +
                autoload :IriStem,          'shex/algebra/stem'
         | 
| 15 | 
            +
                autoload :IriStemRange,     'shex/algebra/stem_range'
         | 
| 16 | 
            +
                autoload :LanguageStem,     'shex/algebra/stem'
         | 
| 17 | 
            +
                autoload :LanguageStemRange,'shex/algebra/stem_range'
         | 
| 18 | 
            +
                autoload :LiteralStem,      'shex/algebra/stem'
         | 
| 19 | 
            +
                autoload :LiteralStemRange, 'shex/algebra/stem_range'
         | 
| 20 | 
            +
                autoload :NodeConstraint,   'shex/algebra/node_constraint'
         | 
| 21 | 
            +
                autoload :Not,              'shex/algebra/not'
         | 
| 22 | 
            +
                autoload :OneOf,            'shex/algebra/one_of'
         | 
| 23 | 
            +
                autoload :Operator,         'shex/algebra/operator'
         | 
| 24 | 
            +
                autoload :Or,               'shex/algebra/or'
         | 
| 25 | 
            +
                autoload :Schema,           'shex/algebra/schema'
         | 
| 26 | 
            +
                autoload :SemAct,           'shex/algebra/semact'
         | 
| 27 | 
            +
                autoload :Shape,            'shex/algebra/shape'
         | 
| 28 | 
            +
                autoload :ShapeExpression,  'shex/algebra/shape_expression'
         | 
| 29 | 
            +
                autoload :Start,            'shex/algebra/start'
         | 
| 30 | 
            +
                autoload :Stem,             'shex/algebra/stem'
         | 
| 31 | 
            +
                autoload :StemRange,        'shex/algebra/stem_range'
         | 
| 26 32 | 
             
                autoload :TripleConstraint, 'shex/algebra/triple_constraint'
         | 
| 27 33 | 
             
                autoload :TripleExpression, 'shex/algebra/triple_expression'
         | 
| 28 | 
            -
                autoload :Value, | 
| 34 | 
            +
                autoload :Value,            'shex/algebra/value'
         | 
| 29 35 |  | 
| 30 36 |  | 
| 31 37 | 
             
                ##
         | 
| @@ -46,22 +52,26 @@ module ShEx | |
| 46 52 | 
             
                def self.from_shexj(operator, options = {})
         | 
| 47 53 | 
             
                  raise ArgumentError unless operator.is_a?(Hash)
         | 
| 48 54 | 
             
                  klass = case operator['type']
         | 
| 49 | 
            -
                  when 'Annotation' | 
| 50 | 
            -
                  when 'EachOf' | 
| 51 | 
            -
                  when ' | 
| 52 | 
            -
                  when ' | 
| 53 | 
            -
                  when ' | 
| 54 | 
            -
                  when ' | 
| 55 | 
            -
                  when ' | 
| 56 | 
            -
                  when ' | 
| 57 | 
            -
                  when ' | 
| 58 | 
            -
                  when ' | 
| 59 | 
            -
                  when ' | 
| 60 | 
            -
                  when ' | 
| 61 | 
            -
                  when ' | 
| 62 | 
            -
                  when ' | 
| 63 | 
            -
                  when ' | 
| 64 | 
            -
                   | 
| 55 | 
            +
                  when 'Annotation'         then Annotation
         | 
| 56 | 
            +
                  when 'EachOf'             then EachOf
         | 
| 57 | 
            +
                  when 'IriStem'            then IriStem
         | 
| 58 | 
            +
                  when 'IriStemRange'       then IriStemRange
         | 
| 59 | 
            +
                  when 'LanguageStem'       then LanguageStem
         | 
| 60 | 
            +
                  when 'LanguageStemRange'  then LanguageStemRange
         | 
| 61 | 
            +
                  when 'LiteralStem'        then LiteralStem
         | 
| 62 | 
            +
                  when 'LiteralStemRange'   then LiteralStemRange
         | 
| 63 | 
            +
                  when 'NodeConstraint'     then NodeConstraint
         | 
| 64 | 
            +
                  when 'OneOf'              then OneOf
         | 
| 65 | 
            +
                  when 'Schema'             then Schema
         | 
| 66 | 
            +
                  when 'SemAct'             then SemAct
         | 
| 67 | 
            +
                  when 'Shape'              then Shape
         | 
| 68 | 
            +
                  when 'ShapeAnd'           then And
         | 
| 69 | 
            +
                  when 'ShapeExternal'      then External
         | 
| 70 | 
            +
                  when 'ShapeNot'           then Not
         | 
| 71 | 
            +
                  when 'ShapeOr'            then Or
         | 
| 72 | 
            +
                  when 'TripleConstraint'   then TripleConstraint
         | 
| 73 | 
            +
                  when 'Wildcard'           then StemRange
         | 
| 74 | 
            +
                  else raise ArgumentError, "unknown type #{operator['type'].inspect}"
         | 
| 65 75 | 
             
                  end
         | 
| 66 76 |  | 
| 67 77 | 
             
                  klass.from_shexj(operator, options)
         | 
| @@ -1,3 +1,4 @@ | |
| 1 | 
            +
            # -*- encoding: utf-8 -*-
         | 
| 1 2 | 
             
            module ShEx::Algebra
         | 
| 2 3 | 
             
              ##
         | 
| 3 4 | 
             
              class NodeConstraint < Operator
         | 
| @@ -58,7 +59,7 @@ module ShEx::Algebra | |
| 58 59 | 
             
                  return true unless dt
         | 
| 59 60 |  | 
| 60 61 | 
             
                  not_satisfied "Node was #{value.inspect}, expected datatype #{dt}", depth: depth unless
         | 
| 61 | 
            -
                    value.is_a?(RDF::Literal) && value.datatype == RDF::URI(dt)
         | 
| 62 | 
            +
                    value.is_a?(RDF::Literal) && value.datatype == RDF::URI(dt) && value.valid?
         | 
| 62 63 | 
             
                  status "right datatype: #{value}: #{dt}", depth: depth
         | 
| 63 64 | 
             
                  true
         | 
| 64 65 | 
             
                end
         | 
| @@ -68,11 +69,18 @@ module ShEx::Algebra | |
| 68 69 | 
             
                # Checks all length/minlength/maxlength/pattern facets against the string representation of the value.
         | 
| 69 70 | 
             
                # @return [Boolean] `true` if satisfied, `false` if it does not apply
         | 
| 70 71 | 
             
                # @raise [ShEx::NotSatisfied] if not satisfied
         | 
| 72 | 
            +
                # @todo using the XPath regexp engine supports additional flags "s" and "q"
         | 
| 71 73 | 
             
                def satisfies_string_facet?(value, depth: 0)
         | 
| 72 74 | 
             
                  length    = op_fetch(:length)
         | 
| 73 75 | 
             
                  minlength = op_fetch(:minlength)
         | 
| 74 76 | 
             
                  maxlength = op_fetch(:maxlength)
         | 
| 75 | 
            -
                   | 
| 77 | 
            +
                  pat = (operands.detect {|op| op.is_a?(Array) && op[0] == :pattern} || [])
         | 
| 78 | 
            +
                  pattern = pat[1]
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                  flags = 0
         | 
| 81 | 
            +
                  flags |= Regexp::EXTENDED   if pat[2].to_s.include?("x")
         | 
| 82 | 
            +
                  flags |= Regexp::IGNORECASE if pat[2].to_s.include?("i")
         | 
| 83 | 
            +
                  flags |= Regexp::MULTILINE  if pat[2].to_s.include?("m")
         | 
| 76 84 |  | 
| 77 85 | 
             
                  return true if (length || minlength || maxlength || pattern).nil?
         | 
| 78 86 |  | 
| @@ -80,6 +88,7 @@ module ShEx::Algebra | |
| 80 88 | 
             
                  when RDF::Node then value.id
         | 
| 81 89 | 
             
                  else value.to_s
         | 
| 82 90 | 
             
                  end
         | 
| 91 | 
            +
             | 
| 83 92 | 
             
                  not_satisfied "Node #{v_s.inspect} length not #{length}", depth: depth if
         | 
| 84 93 | 
             
                    length && v_s.length != length.to_i
         | 
| 85 94 | 
             
                  not_satisfied"Node #{v_s.inspect} length < #{minlength}", depth: depth if
         | 
| @@ -87,7 +96,7 @@ module ShEx::Algebra | |
| 87 96 | 
             
                  not_satisfied "Node #{v_s.inspect} length > #{maxlength}", depth: depth if
         | 
| 88 97 | 
             
                    maxlength && v_s.length > maxlength.to_i
         | 
| 89 98 | 
             
                  not_satisfied "Node #{v_s.inspect} does not match #{pattern}", depth: depth if
         | 
| 90 | 
            -
                    pattern && !Regexp.new(pattern).match(v_s)
         | 
| 99 | 
            +
                    pattern && !Regexp.new(pattern, flags).match(v_s)
         | 
| 91 100 | 
             
                  status "right string facet: #{value}", depth: depth
         | 
| 92 101 | 
             
                  true
         | 
| 93 102 | 
             
                end
         | 
    
        data/lib/shex/algebra/not.rb
    CHANGED
    
    | @@ -19,7 +19,7 @@ module ShEx::Algebra | |
| 19 19 | 
             
                # @param  (see ShapeExpression#satisfies?)
         | 
| 20 20 | 
             
                # @return (see ShapeExpression#satisfies?)
         | 
| 21 21 | 
             
                # @raise  (see ShapeExpression#satisfies?)
         | 
| 22 | 
            -
                # @see [ | 
| 22 | 
            +
                # @see [http://shex.io/shex-semantics/#shape-expression-semantics]
         | 
| 23 23 | 
             
                def satisfies?(focus, depth: 0)
         | 
| 24 24 | 
             
                  status ""
         | 
| 25 25 | 
             
                  op = expressions.last
         | 
| @@ -267,12 +267,16 @@ module ShEx::Algebra | |
| 267 267 |  | 
| 268 268 | 
             
                  operator.each do |k, v|
         | 
| 269 269 | 
             
                    case k
         | 
| 270 | 
            -
                    when /length| | 
| 270 | 
            +
                    when /length|clusive|digits/           then operands << [k.to_sym, RDF::Literal(v)]
         | 
| 271 271 | 
             
                    when 'id'                              then id = iri(v, options)
         | 
| 272 | 
            -
                    when ' | 
| 272 | 
            +
                    when 'flags'                            then ; # consumed in pattern below
         | 
| 273 | 
            +
                    when 'min', 'max'                      then operands << [k.to_sym, (v == -1 ? '*' : v)]
         | 
| 273 274 | 
             
                    when 'inverse', 'closed'               then operands << k.to_sym
         | 
| 274 275 | 
             
                    when 'nodeKind'                        then operands << v.to_sym
         | 
| 275 276 | 
             
                    when 'object'                          then operands << value(v, options)
         | 
| 277 | 
            +
                    when 'pattern'
         | 
| 278 | 
            +
                      # Include flags as well
         | 
| 279 | 
            +
                      operands << [:pattern, RDF::Literal(v), operator['flags']].compact
         | 
| 276 280 | 
             
                    when 'start'
         | 
| 277 281 | 
             
                      if v.is_a?(String)
         | 
| 278 282 | 
             
                        operands << Start.new(iri(v, options))
         | 
| @@ -291,7 +295,7 @@ module ShEx::Algebra | |
| 291 295 | 
             
                      end
         | 
| 292 296 | 
             
                    when 'stem', 'name'
         | 
| 293 297 | 
             
                      # Value may be :wildcard for stem
         | 
| 294 | 
            -
                      operands << (v.is_a?(Symbol) ? v :  | 
| 298 | 
            +
                      operands << (v.is_a?(Symbol) ? v : value(v, options))
         | 
| 295 299 | 
             
                    when 'predicate' then operands << [:predicate, iri(v, options)]
         | 
| 296 300 | 
             
                    when 'extra', 'datatype'
         | 
| 297 301 | 
             
                      v = [v] unless v.is_a?(Array)
         | 
| @@ -299,7 +303,7 @@ module ShEx::Algebra | |
| 299 303 | 
             
                    when 'exclusions'
         | 
| 300 304 | 
             
                      v = [v] unless v.is_a?(Array)
         | 
| 301 305 | 
             
                      operands << v.map do |op|
         | 
| 302 | 
            -
                        op.is_a?(Hash) ?
         | 
| 306 | 
            +
                        op.is_a?(Hash) && op.has_key?('type') ?
         | 
| 303 307 | 
             
                          ShEx::Algebra.from_shexj(op, options) :
         | 
| 304 308 | 
             
                          value(op, options)
         | 
| 305 309 | 
             
                      end.unshift(:exclusions)
         | 
| @@ -345,11 +349,12 @@ module ShEx::Algebra | |
| 345 349 | 
             
                    when Array
         | 
| 346 350 | 
             
                      # First element should be a symbol
         | 
| 347 351 | 
             
                      case sym = op.first
         | 
| 348 | 
            -
                      when :datatype | 
| 349 | 
            -
                           :pattern         then obj[op.first.to_s] = op.last.to_s
         | 
| 352 | 
            +
                      when :datatype        then obj['datatype'] = op.last.to_s
         | 
| 350 353 | 
             
                      when :exclusions      then obj['exclusions'] = Array(op[1..-1]).map {|v| serialize_value(v)}
         | 
| 351 354 | 
             
                      when :extra           then (obj['extra'] ||= []).concat Array(op[1..-1]).map(&:to_s)
         | 
| 352 | 
            -
             | 
| 355 | 
            +
                      when :pattern
         | 
| 356 | 
            +
                        obj['pattern'] = op[1]
         | 
| 357 | 
            +
                        obj['flags'] = op[2] if op[2]
         | 
| 353 358 | 
             
                      when :shapes          then obj['shapes'] = Array(op[1..-1]).map {|v| v.to_h}
         | 
| 354 359 | 
             
                      when :minlength,
         | 
| 355 360 | 
             
                           :maxlength,
         | 
| @@ -360,7 +365,7 @@ module ShEx::Algebra | |
| 360 365 | 
             
                           :maxexclusive,
         | 
| 361 366 | 
             
                           :totaldigits,
         | 
| 362 367 | 
             
                           :fractiondigits  then obj[op.first.to_s] = op.last.object
         | 
| 363 | 
            -
                      when :min, :max       then obj[op.first.to_s] = op.last
         | 
| 368 | 
            +
                      when :min, :max       then obj[op.first.to_s] = op.last == '*' ? -1 : op.last
         | 
| 364 369 | 
             
                      when :predicate       then obj[op.first.to_s] = op.last.to_s
         | 
| 365 370 | 
             
                      when :base, :prefix
         | 
| 366 371 | 
             
                        # Ignore base and prefix
         | 
| @@ -378,7 +383,7 @@ module ShEx::Algebra | |
| 378 383 | 
             
                      end
         | 
| 379 384 | 
             
                    when RDF::Value
         | 
| 380 385 | 
             
                      case self
         | 
| 381 | 
            -
                      when Stem, StemRange  then obj['stem'] = op | 
| 386 | 
            +
                      when Stem, StemRange  then obj['stem'] = serialize_value(op)
         | 
| 382 387 | 
             
                      when SemAct           then obj[op.is_a?(RDF::URI) ? 'name' : 'code'] = op.to_s
         | 
| 383 388 | 
             
                      when TripleConstraint then obj['valueExpr'] = op.to_s
         | 
| 384 389 | 
             
                      when Shape            then obj['expression'] = op.to_s
         | 
| @@ -517,10 +522,10 @@ module ShEx::Algebra | |
| 517 522 | 
             
                  case value
         | 
| 518 523 | 
             
                  when Hash
         | 
| 519 524 | 
             
                    # Either a value object or a node reference
         | 
| 520 | 
            -
                    if value['uri']
         | 
| 521 | 
            -
                      iri(value['uri'], options)
         | 
| 522 | 
            -
                    elsif value['value']
         | 
| 523 | 
            -
                      RDF::Literal(value['value'], datatype: value['type'], language: value['language'])
         | 
| 525 | 
            +
                    if value['uri'] || value['@id']
         | 
| 526 | 
            +
                      iri(value['uri'] || value['@id'], options)
         | 
| 527 | 
            +
                    elsif value['value'] || value['@value']
         | 
| 528 | 
            +
                      RDF::Literal(value['value'] || value['@value'], datatype: value['type'] || value['@type'], language: value['language'] || value['@language'])
         | 
| 524 529 | 
             
                    else
         | 
| 525 530 | 
             
                      ShEx::Algebra.from_shexj(value, options)
         | 
| 526 531 | 
             
                    end
         | 
| @@ -541,6 +546,8 @@ module ShEx::Algebra | |
| 541 546 | 
             
                      merge(value.has_language? ? {'language' => value.language.to_s} : {})
         | 
| 542 547 | 
             
                    when RDF::Resource
         | 
| 543 548 | 
             
                      value.to_s
         | 
| 549 | 
            +
                    when String
         | 
| 550 | 
            +
                      {'value' => value}
         | 
| 544 551 | 
             
                    else value.to_h
         | 
| 545 552 | 
             
                  end
         | 
| 546 553 | 
             
                end
         | 
    
        data/lib/shex/algebra/schema.rb
    CHANGED
    
    | @@ -36,19 +36,22 @@ module ShEx::Algebra | |
| 36 36 | 
             
                ##
         | 
| 37 37 | 
             
                # Match on schema. Finds appropriate shape for node, and matches that shape.
         | 
| 38 38 | 
             
                #
         | 
| 39 | 
            -
                # @param [RDF::Term] focus
         | 
| 40 39 | 
             
                # @param [RDF::Queryable] graph
         | 
| 41 | 
            -
                # @param [Hash{RDF:: | 
| 40 | 
            +
                # @param [Hash{RDF::Term => <RDF::Resource>}, Array<Array(RDF::Term, RDF::Resource)>] map
         | 
| 41 | 
            +
                #   A set of (`term`, `resource`) pairs where `term` is a node within `graph`, and `resource` identifies a shape
         | 
| 42 | 
            +
                # @param [Array<RDF::Term>] *focus
         | 
| 43 | 
            +
                # 	One or more nodes within `graph` for which to run the start expression.
         | 
| 42 44 | 
             
                # @param [Array<Schema, String>] shapeExterns ([])
         | 
| 43 45 | 
             
                #   One or more schemas, or paths to ShEx schema resources used for finding external shapes.
         | 
| 44 | 
            -
                # @return [ | 
| 46 | 
            +
                # @return [Hash{RDF::Term => Array<ShapeResult>}] Returns _ShapeResults_, a hash of graph nodes to the results of their associated shapes
         | 
| 45 47 | 
             
                # @param [Hash{Symbol => Object}] options
         | 
| 46 48 | 
             
                # @option options [String] :base_uri (for resolving focus)
         | 
| 47 | 
            -
                # @raise [ShEx::NotSatisfied] along with  | 
| 48 | 
            -
                def execute( | 
| 49 | 
            -
                  @graph, @shapes_entered = graph, {}
         | 
| 49 | 
            +
                # @raise [ShEx::NotSatisfied] along with individual shape results
         | 
| 50 | 
            +
                def execute(graph, map, focus: [], shapeExterns: [], depth: 0, **options)
         | 
| 51 | 
            +
                  @graph, @shapes_entered, results = graph, {}, {}
         | 
| 50 52 | 
             
                  @external_schemas = shapeExterns
         | 
| 51 | 
            -
                   | 
| 53 | 
            +
                  @extensions = {}
         | 
| 54 | 
            +
                  focus = Array(focus).map {|f| value(f, options)}
         | 
| 52 55 |  | 
| 53 56 | 
             
                  logger = options[:logger] || @options[:logger]
         | 
| 54 57 | 
             
                  each_descendant do |op|
         | 
| @@ -57,7 +60,6 @@ module ShEx::Algebra | |
| 57 60 | 
             
                  end
         | 
| 58 61 |  | 
| 59 62 | 
             
                  # Initialize Extensions
         | 
| 60 | 
            -
                  @extensions = {}
         | 
| 61 63 | 
             
                  each_descendant do |op|
         | 
| 62 64 | 
             
                    next unless op.is_a?(SemAct)
         | 
| 63 65 | 
             
                    name = op.operands.first.to_s
         | 
| @@ -67,36 +69,74 @@ module ShEx::Algebra | |
| 67 69 | 
             
                  end
         | 
| 68 70 |  | 
| 69 71 | 
             
                  # If `n` is a Blank Node, we won't find it through normal matching, find an equivalent node in the graph having the same id
         | 
| 70 | 
            -
                   | 
| 71 | 
            -
                   | 
| 72 | 
            -
             | 
| 73 | 
            -
             | 
| 74 | 
            -
             | 
| 72 | 
            +
                  @map = case map
         | 
| 73 | 
            +
                  when Hash
         | 
| 74 | 
            +
                    map.inject({}) do |memo, (node, shapes)|
         | 
| 75 | 
            +
                      gnode = graph.enum_term.detect {|t| t.node? && t.id == node.id} if node.is_a?(RDF::Node)
         | 
| 76 | 
            +
                      node = gnode if gnode
         | 
| 77 | 
            +
                      memo.merge(node => Array(shapes))
         | 
| 78 | 
            +
                    end
         | 
| 79 | 
            +
                  when Array
         | 
| 80 | 
            +
                    map.inject({}) do |memo, (node, shape)|
         | 
| 81 | 
            +
                      gnode = graph.enum_term.detect {|t| t.node? && t.id == node.id} if node.is_a?(RDF::Node)
         | 
| 82 | 
            +
                      node = gnode if gnode
         | 
| 83 | 
            +
                      (memo[node] ||= []).concat(Array(shape))
         | 
| 84 | 
            +
                      memo
         | 
| 85 | 
            +
                    end
         | 
| 86 | 
            +
                  when nil then {}
         | 
| 87 | 
            +
                  else
         | 
| 88 | 
            +
                    structure_error "Unrecognized shape map: #{map.inspect}"
         | 
| 89 | 
            +
                  end
         | 
| 75 90 |  | 
| 76 91 | 
             
                  # First, evaluate semantic acts
         | 
| 77 92 | 
             
                  semantic_actions.all? do |op|
         | 
| 78 93 | 
             
                    op.satisfies?([], depth: depth + 1)
         | 
| 79 94 | 
             
                  end
         | 
| 80 95 |  | 
| 81 | 
            -
                  # Keep a new Schema, specifically for recording actions
         | 
| 82 | 
            -
                  satisfied_schema = Schema.new
         | 
| 83 96 | 
             
                  # Next run any start expression
         | 
| 84 | 
            -
                  if  | 
| 85 | 
            -
                     | 
| 97 | 
            +
                  if !focus.empty?
         | 
| 98 | 
            +
                    if start
         | 
| 99 | 
            +
                      focus.each do |node|
         | 
| 100 | 
            +
                        node = graph.enum_term.detect {|t| t.node? && t.id == node.id} if node.is_a?(RDF::Node)
         | 
| 101 | 
            +
                        sr = ShapeResult.new(RDF::URI("http://www.w3.org/ns/shex#Start"))
         | 
| 102 | 
            +
                        (results[node] ||= []) << sr
         | 
| 103 | 
            +
                        begin
         | 
| 104 | 
            +
                          sr.expression = start.satisfies?(node, depth: depth + 1)
         | 
| 105 | 
            +
                          sr.result = true
         | 
| 106 | 
            +
                        rescue ShEx::NotSatisfied => e
         | 
| 107 | 
            +
                          sr.expression = e.expression
         | 
| 108 | 
            +
                          sr.result = false
         | 
| 109 | 
            +
                        end
         | 
| 110 | 
            +
                      end
         | 
| 111 | 
            +
                    else
         | 
| 112 | 
            +
                      structure_error "Focus nodes with no start"
         | 
| 113 | 
            +
                    end
         | 
| 86 114 | 
             
                  end
         | 
| 87 115 |  | 
| 88 | 
            -
                  # Add shape result(s)
         | 
| 89 | 
            -
                  satisfied_shapes = {}
         | 
| 90 | 
            -
                  satisfied_schema.operands << [:shapes, satisfied_shapes] unless shapes.empty?
         | 
| 91 | 
            -
             | 
| 92 116 | 
             
                  # Match against all shapes associated with the ids for focus
         | 
| 93 | 
            -
                   | 
| 94 | 
            -
                     | 
| 95 | 
            -
             | 
| 117 | 
            +
                  @map.each do |node, shapes|
         | 
| 118 | 
            +
                    results[node] ||= []
         | 
| 119 | 
            +
                    shapes.each do |id|
         | 
| 120 | 
            +
                      enter_shape(id, node) do |shape|
         | 
| 121 | 
            +
                        sr = ShapeResult.new(id)
         | 
| 122 | 
            +
                        results[node] << sr
         | 
| 123 | 
            +
                        begin
         | 
| 124 | 
            +
                          sr.expression = shape.satisfies?(node, depth: depth + 1)
         | 
| 125 | 
            +
                          sr.result = true
         | 
| 126 | 
            +
                        rescue ShEx::NotSatisfied => e
         | 
| 127 | 
            +
                          sr.expression = e.expression
         | 
| 128 | 
            +
                          sr.result = false
         | 
| 129 | 
            +
                        end
         | 
| 130 | 
            +
                      end
         | 
| 96 131 | 
             
                    end
         | 
| 97 132 | 
             
                  end
         | 
| 98 | 
            -
             | 
| 99 | 
            -
                   | 
| 133 | 
            +
             | 
| 134 | 
            +
                  if results.values.flatten.all? {|sr| sr.result}
         | 
| 135 | 
            +
                    status "schema satisfied", depth: depth
         | 
| 136 | 
            +
                    results
         | 
| 137 | 
            +
                  else
         | 
| 138 | 
            +
                    raise ShEx::NotSatisfied.new("Graph does not conform to schema", expression: results)
         | 
| 139 | 
            +
                  end
         | 
| 100 140 | 
             
                ensure
         | 
| 101 141 | 
             
                  # Close Semantic Action extensions
         | 
| 102 142 | 
             
                  @extensions.values.each {|ext| ext.close(schema: self, depth: depth, **options)}
         | 
| @@ -105,16 +145,12 @@ module ShEx::Algebra | |
| 105 145 | 
             
                ##
         | 
| 106 146 | 
             
                # Match on schema. Finds appropriate shape for node, and matches that shape.
         | 
| 107 147 | 
             
                #
         | 
| 108 | 
            -
                # @param  | 
| 109 | 
            -
                # @param [RDF::Queryable] graph
         | 
| 110 | 
            -
                # @param [Hash{RDF::Resource => RDF::Resource}] map
         | 
| 111 | 
            -
                # @param [Array<Schema, String>] shapeExterns ([])
         | 
| 112 | 
            -
                #   One or more schemas, or paths to ShEx schema resources used for finding external shapes.
         | 
| 148 | 
            +
                # @param (see ShEx::Algebra::Schema#execute)
         | 
| 113 149 | 
             
                # @param [Hash{Symbol => Object}] options
         | 
| 114 150 | 
             
                # @option options [String] :base_uri
         | 
| 115 151 | 
             
                # @return [Boolean]
         | 
| 116 | 
            -
                def satisfies?( | 
| 117 | 
            -
                  execute( | 
| 152 | 
            +
                def satisfies?(graph, map, **options)
         | 
| 153 | 
            +
                  execute(graph, map, **options)
         | 
| 118 154 | 
             
                rescue ShEx::NotSatisfied
         | 
| 119 155 | 
             
                  false
         | 
| 120 156 | 
             
                end
         | 
| @@ -198,4 +234,41 @@ module ShEx::Algebra | |
| 198 234 | 
             
                  super
         | 
| 199 235 | 
             
                end
         | 
| 200 236 | 
             
              end
         | 
| 237 | 
            +
             | 
| 238 | 
            +
              # A shape result
         | 
| 239 | 
            +
              class ShapeResult
         | 
| 240 | 
            +
                # The label of the shape within the schema, or a URI indicating a start shape
         | 
| 241 | 
            +
                # @return [RDF::Resource]
         | 
| 242 | 
            +
                attr_reader :shape
         | 
| 243 | 
            +
             | 
| 244 | 
            +
                # Does the node conform to the shape
         | 
| 245 | 
            +
                # @return [Boolean]
         | 
| 246 | 
            +
                attr_accessor :result
         | 
| 247 | 
            +
             | 
| 248 | 
            +
                # The annotated {Operator} indicating processing results
         | 
| 249 | 
            +
                # @return [ShEx::Algebra::Operator]
         | 
| 250 | 
            +
                attr_accessor :expression
         | 
| 251 | 
            +
             | 
| 252 | 
            +
                # Holds the result of processing a shape
         | 
| 253 | 
            +
                # @param [RDF::Resource] label
         | 
| 254 | 
            +
                # @return [ShapeResult]
         | 
| 255 | 
            +
                def initialize(shape)
         | 
| 256 | 
            +
                  @shape = shape
         | 
| 257 | 
            +
                end
         | 
| 258 | 
            +
             | 
| 259 | 
            +
                # The SXP of {#expression}
         | 
| 260 | 
            +
                # @return [String]
         | 
| 261 | 
            +
                def reason
         | 
| 262 | 
            +
                  SXP::Generator.string(expression.to_sxp_bin)
         | 
| 263 | 
            +
                end
         | 
| 264 | 
            +
             | 
| 265 | 
            +
                ##
         | 
| 266 | 
            +
                # Returns the binary S-Expression (SXP) representation of this result.
         | 
| 267 | 
            +
                #
         | 
| 268 | 
            +
                # @return [Array]
         | 
| 269 | 
            +
                # @see    https://en.wikipedia.org/wiki/S-expression
         | 
| 270 | 
            +
                def to_sxp_bin
         | 
| 271 | 
            +
                  [:ShapeResult, shape, result, expression].map(&:to_sxp_bin)
         | 
| 272 | 
            +
                end
         | 
| 273 | 
            +
              end
         | 
| 201 274 | 
             
            end
         |