lib/shex/algebra/node_constraint.rb in shex-0.2.0 vs lib/shex/algebra/node_constraint.rb in shex-0.3.0

- old
+ new

@@ -2,66 +2,75 @@ ## class NodeConstraint < Operator include Satisfiable NAME = :nodeConstraint + ## + # Creates an operator instance from a parsed ShExJ representation + # @param (see Operator#from_shexj) + # @return [Operator] + def self.from_shexj(operator, options = {}) + raise ArgumentError unless operator.is_a?(Hash) && operator['type'] == 'NodeConstraint' + super + end + # # S is a NodeConstraint and satisfies2(focus, se) as described below in Node Constraints. Note that testing if a node satisfies a node constraint does not require a graph or shapeMap. # @param (see Satisfiable#satisfies?) # @return (see Satisfiable#satisfies?) # @raise (see Satisfiable#satisfies?) - def satisfies?(focus) - status "" - satisfies_node_kind?(focus) && - satisfies_datatype?(focus) && - satisfies_string_facet?(focus) && - satisfies_numeric_facet?(focus) && - satisfies_values?(focus) && - satisfy + def satisfies?(focus, depth: 0) + status "", depth: depth + satisfies_node_kind?(focus, depth: depth + 1) && + satisfies_datatype?(focus, depth: depth + 1) && + satisfies_string_facet?(focus, depth: depth + 1) && + satisfies_numeric_facet?(focus, depth: depth + 1) && + satisfies_values?(focus, depth: depth + 1) && + satisfy(depth: depth) end private ## # Satisfies Node Kind Constraint # @return [Boolean] `true` if satisfied, `false` if it does not apply # @raise [ShEx::NotSatisfied] if not satisfied - def satisfies_node_kind?(value) - kind = case operands.first + def satisfies_node_kind?(value, depth: 0) + kind = case operands.detect {|o| o.is_a?(Symbol)} when :iri then RDF::URI when :bnode then RDF::Node when :literal then RDF::Literal when :nonliteral then RDF::Resource else return true end - not_satisfied "Node was #{value.inspect} expected kind #{kind}" unless + not_satisfied "Node was #{value.inspect} expected kind #{kind}", depth: depth unless value.is_a?(kind) - status "right kind: #{value}: #{kind}" + status "right kind: #{value}: #{kind}", depth: depth true end ## # Datatype Constraint # @return [Boolean] `true` if satisfied, `false` if it does not apply # @raise [ShEx::NotSatisfied] if not satisfied - def satisfies_datatype?(value) - dt = operands[1] if operands.first == :datatype + def satisfies_datatype?(value, depth: 0) + dt = op_fetch(:datatype) return true unless dt - not_satisfied "Node was #{value.inspect}, expected datatype #{dt}" unless + not_satisfied "Node was #{value.inspect}, expected datatype #{dt}", depth: depth unless value.is_a?(RDF::Literal) && value.datatype == RDF::URI(dt) - status "right datatype: #{value}: #{dt}" + status "right datatype: #{value}: #{dt}", depth: depth true end ## # String Facet Constraint # Checks all length/minlength/maxlength/pattern facets against the string representation of the value. # @return [Boolean] `true` if satisfied, `false` if it does not apply # @raise [ShEx::NotSatisfied] if not satisfied - def satisfies_string_facet?(value) + def satisfies_string_facet?(value, depth: 0) length = op_fetch(:length) minlength = op_fetch(:minlength) maxlength = op_fetch(:maxlength) pattern = op_fetch(:pattern) @@ -69,76 +78,76 @@ v_s = case value when RDF::Node then value.id else value.to_s end - not_satisfied "Node #{v_s.inspect} length not #{length}" if + not_satisfied "Node #{v_s.inspect} length not #{length}", depth: depth if length && v_s.length != length.to_i - not_satisfied"Node #{v_s.inspect} length < #{minlength}" if + not_satisfied"Node #{v_s.inspect} length < #{minlength}", depth: depth if minlength && v_s.length < minlength.to_i - not_satisfied "Node #{v_s.inspect} length > #{maxlength}" if + not_satisfied "Node #{v_s.inspect} length > #{maxlength}", depth: depth if maxlength && v_s.length > maxlength.to_i - not_satisfied "Node #{v_s.inspect} does not match #{pattern}" if + not_satisfied "Node #{v_s.inspect} does not match #{pattern}", depth: depth if pattern && !Regexp.new(pattern).match(v_s) - status "right string facet: #{value}" + status "right string facet: #{value}", depth: depth true end ## # Numeric Facet Constraint # Checks all numeric facets against the value. # @return [Boolean] `true` if satisfied, `false` if it does not apply # @raise [ShEx::NotSatisfied] if not satisfied - def satisfies_numeric_facet?(value) + def satisfies_numeric_facet?(value, depth: 0) mininclusive = op_fetch(:mininclusive) minexclusive = op_fetch(:minexclusive) maxinclusive = op_fetch(:maxinclusive) maxexclusive = op_fetch(:maxexclusive) totaldigits = op_fetch(:totaldigits) fractiondigits = op_fetch(:fractiondigits) return true if (mininclusive || minexclusive || maxinclusive || maxexclusive || totaldigits || fractiondigits).nil? - not_satisfied "Node #{value.inspect} not numeric" unless + not_satisfied "Node #{value.inspect} not numeric", depth: depth unless value.is_a?(RDF::Literal::Numeric) - not_satisfied "Node #{value.inspect} not decimal" if + not_satisfied "Node #{value.inspect} not decimal", depth: depth if (totaldigits || fractiondigits) && (!value.is_a?(RDF::Literal::Decimal) || value.invalid?) numeric_value = value.object case - when !mininclusive.nil? && numeric_value < mininclusive.object then not_satisfied("Node #{value.inspect} < #{mininclusive.object}") - when !minexclusive.nil? && numeric_value <= minexclusive.object then not_satisfied("Node #{value.inspect} not <= #{minexclusive.object}") - when !maxinclusive.nil? && numeric_value > maxinclusive.object then not_satisfied("Node #{value.inspect} > #{maxinclusive.object}") - when !maxexclusive.nil? && numeric_value >= maxexclusive.object then not_satisfied("Node #{value.inspect} >= #{maxexclusive.object}") + when !mininclusive.nil? && numeric_value < mininclusive.object then not_satisfied("Node #{value.inspect} < #{mininclusive.object}", depth: depth) + when !minexclusive.nil? && numeric_value <= minexclusive.object then not_satisfied("Node #{value.inspect} not <= #{minexclusive.object}", depth: depth) + when !maxinclusive.nil? && numeric_value > maxinclusive.object then not_satisfied("Node #{value.inspect} > #{maxinclusive.object}", depth: depth) + when !maxexclusive.nil? && numeric_value >= maxexclusive.object then not_satisfied("Node #{value.inspect} >= #{maxexclusive.object}", depth: depth) when !totaldigits.nil? md = value.canonicalize.to_s.match(/([1-9]\d*|0)?(?:\.(\d+)(?!0))?/) digits = md ? (md[1].to_s + md[2].to_s) : "" if digits.length > totaldigits.to_i - not_satisfied "Node #{value.inspect} total digits != #{totaldigits}" + not_satisfied "Node #{value.inspect} total digits != #{totaldigits}", depth: depth end when !fractiondigits.nil? md = value.canonicalize.to_s.match(/\.(\d+)(?!0)?/) num = md ? md[1].to_s : "" if num.length > fractiondigits.to_i - not_satisfied "Node #{value.inspect} fractional digits != #{fractiondigits}" + not_satisfied "Node #{value.inspect} fractional digits != #{fractiondigits}", depth: depth end end - status "right numeric facet: #{value}" + status "right numeric facet: #{value}", depth: depth true end ## # Value Constraint # Checks all numeric facets against the value. # @return [Boolean] `true` if satisfied, `false` if it does not apply # @raise [ShEx::NotSatisfied] if not satisfied - def satisfies_values?(value) + def satisfies_values?(value, depth: 0) values = operands.select {|op| op.is_a?(Value)} return true if values.empty? - matched_value = values.detect {|v| v.match?(value)} - not_satisfied "Node #{value.inspect} not expected" unless matched_value - status "right value: #{value}" + matched_value = values.detect {|v| v.match?(value, depth: depth + 1)} + not_satisfied "Value #{value.to_sxp} not expected, wanted #{values.to_sxp}", depth: depth unless matched_value + status "right value: #{value}", depth: depth true end # Returns the value of a particular facet def op_fetch(which)