class SXP::Reader::SPARQL

A SPARQL Syntax Expressions (SSE) parser.

Requires [RDF.rb](rdf.rubyforge.org/).

@see openjena.org/wiki/SSE

Constants

A

Alias for rdf:type

BASE

Base token, causes next URI to be treated as the `base_uri` for further URI expansion

BNODE_ID

BNode with identifier

BNODE_NEW
DECIMAL
DOUBLE
EVAR_ID

Distinguished existential variable

EXPONENT
FALSE
ND_EVAR

Non-distinguished existential variable

ND_VAR

Non-distinguished variable

NIL
PNAME

A QName, subject to expansion to URIs using {PREFIX}

PREFIX

Prefix token, causes following prefix and URI pairs to be used for transforming {PNAME} tokens into URIs.

RDF_TYPE
TRUE
VAR_ID

Distinguished variable

Attributes

base_uri[RW]

Base URI as specified or when parsing parsing a BASE token using the immediately following token, which must be a URI.

prefixes[RW]

Prefixes defined while parsing @return [Hash{Object => RDF::URI}]

Public Class Methods

new(input, **options, &block) click to toggle source

Initializes the reader.

@param [IO, StringIO, String] input @param [Hash{Symbol => Object}] options

Calls superclass method SXP::Reader::new
# File lib/sxp/reader/sparql.rb, line 73
def initialize(input, **options, &block)
  super { @prefixes = {}; @bnodes = {}; @list_depth = 0 }

  if block_given?
    case block.arity
      when 1 then block.call(self)
      else self.instance_eval(&block)
    end
  end
end

Public Instance Methods

prefix(name, uri = nil) click to toggle source

Defines the given named URI prefix for this parser.

@example Defining a URI prefix

parser.prefix :dc, RDF::URI('http://purl.org/dc/terms/')

@example Returning a URI prefix

parser.prefix(:dc)    #=> RDF::URI('http://purl.org/dc/terms/')

@param [Symbol, to_s] name @param [RDF::URI, to_s] uri @return [RDF::URI]

# File lib/sxp/reader/sparql.rb, line 63
def prefix(name, uri = nil)
  name = name.to_s.empty? ? nil : (name.respond_to?(:to_sym) ? name.to_sym : name.to_s.to_sym)
  uri.nil? ? @prefixes[name] : @prefixes[name] = uri
end
read_atom() click to toggle source

Reads an SSE Atom

Atoms parsed including `base`, `prefix`, `true`, `false`, numeric, BNodes and variables.

Creates {RDF::Literal}, RDF::Node, or {RDF::Query::Variable} instances where appropriate.

@return [Object]

# File lib/sxp/reader/sparql.rb, line 216
def read_atom
  case buffer = read_literal
    when '.'       then buffer.to_sym
    when A         then RDF_TYPE
    when BASE      then @parsed_base = true; buffer.to_sym
    when NIL       then nil
    when FALSE     then RDF::Literal(false)
    when TRUE      then RDF::Literal(true)
    when DOUBLE    then RDF::Literal::Double.new(buffer)
    when DECIMAL   then RDF::Literal::Decimal.new(buffer)
    when INTEGER   then RDF::Literal::Integer.new(buffer)
    when BNODE_ID  then @bnodes[$1] ||= RDF::Node($1)
    when BNODE_NEW then RDF::Node.new
    when ND_VAR    then variable($1, distinguished: false)
    when VAR_ID    then variable($1, distinguished: true)
    when ND_EVAR   then variable($1, existential: true, distinguished: false)
    when EVAR_ID   then variable($1, existential: true, distinguished: true)
    else buffer.to_sym
  end
end
read_rdf_literal() click to toggle source

Reads literals corresponding to SPARQL/Turtle/Notation-3 syntax

@example

"a plain literal"
"a literal with a language"@en
"a typed literal"^^<http://example/>
"a typed literal with a PNAME"^^xsd:string

@return [RDF::Literal]

# File lib/sxp/reader/sparql.rb, line 153
def read_rdf_literal
  value   = read_string
  options = case peek_char
    when ?@
      skip_char # '@'
      {language: read_atom.downcase}
    when ?^
      2.times { skip_char } # '^^'
      {datatype: read_token.last}
    else {}
  end
  RDF::Literal(value, **options)
end
read_rdf_uri() click to toggle source

Reads a URI in SPARQL/Turtle/Notation-3 syntax

@example

<http://example/>

@return [RDF::URI]

# File lib/sxp/reader/sparql.rb, line 174
def read_rdf_uri
  buffer = String.new
  skip_char # '<'
  return :< if (char = peek_char).nil? || char.chr !~ ATOM # FIXME: nasty special case for the '< symbol
  return :<= if peek_char.chr.eql?(?=.chr) && read_char    # FIXME: nasty special case for the '<= symbol
  until peek_char == ?>
    buffer << read_char # TODO: unescaping
  end
  skip_char # '>'

  # If we have a base URI, use that when constructing a new URI
  uri = if self.base_uri && RDF::URI(buffer).relative?
    u = self.base_uri.join(buffer)
    u.lexical = "<#{buffer}>" unless u.to_s == buffer  # So that it can be re-serialized properly
    u
  else
    RDF::URI(buffer)
  end
  
  # If we previously parsed a "BASE" element, then this URI is used to set that value
  if @parsed_base
    self.base_uri = uri
    @parsed_base = nil
  end
  
  # If we previously parsed a "PREFIX" element, associate this URI with the prefix
  if @parsed_prefix
    prefix(@parsed_prefix, uri)
    @parsed_prefix = nil
  end
  
  uri
end
read_token() click to toggle source

Reads SSE Tokens, including {RDF::Literal}, {RDF::URI} and RDF::Node.

Performs forward reference for prefix and base URI representations and saves in {#base_uri} and {#prefixes} accessors.

Transforms tokens matching a {PNAME} pattern into {RDF::URI} instances if a match is found with a previously identified {PREFIX}. @return [Object]

Calls superclass method SXP::Reader::Extended#read_token
# File lib/sxp/reader/sparql.rb, line 93
def read_token
  case peek_char
  when ?" then [:atom, read_rdf_literal] # "
  when ?< then [:atom, read_rdf_uri]
  else
    tok = super
    
    # If we just parsed "PREFIX", and this is an opening list, then
    # record list depth and process following as token, URI pairs
    #
    # Once we've closed the list, go out of prefix mode
    if tok.is_a?(Array) && tok[0] == :list
      if '(['.include?(tok[1])
        @list_depth += 1
      else
        @list_depth -= 1
        @prefix_depth = nil if @prefix_depth && @list_depth < @prefix_depth
      end
    end

    if tok.is_a?(Array) && tok[0] == :atom && tok[1].is_a?(Symbol)
      value = tok[1].to_s

      # We previously parsed a PREFIX, this will be the map value
      @parsed_prefix = value.chop if @prefix_depth && @prefix_depth > 0
      
      # If we just saw PREFIX, then this starts the parsing mode
      @prefix_depth = @list_depth + 1 if value =~ PREFIX
      
      # If the token is of the form 'prefix:suffix', create a URI and give it the
      # token as a QName
      if value.to_s =~ PNAME && base = prefix($1)
        suffix = $2
        #STDERR.puts "read_tok lexical: pfx: #{$1.inspect} => #{prefix($1).inspect}, sfx: #{suffix.inspect}"
        suffix = suffix.sub(/^\#/, "") if base.to_s.index("#")
        uri = RDF::URI(base.to_s + suffix)
        #STDERR.puts "read_tok lexical uri: #{uri.inspect}"

        # Cause URI to be serialized as a lexical
        uri.lexical = value
        [:atom, uri]
      else
        tok
      end
    else
      tok
    end
  end
end
skip_comments() click to toggle source

@return [void]

# File lib/sxp/reader/sparql.rb, line 239
def skip_comments
  until eof?
    case (char = peek_char).chr
      when /\s+/ then skip_char
      when /;/   then skip_line
      when /#/   then skip_line
      else break
    end
  end
end
variable(id, distinguished: true, existential: false) click to toggle source

Return variable allocated to an ID. If no ID is provided, a new variable is allocated. Otherwise, any previous assignment will be used.

The variable has a distinguished? method applied depending on if this is a disinguished or non-distinguished variable. Non-distinguished variables are effectively the same as BNodes. @return [RDF::Query::Variable]

# File lib/sxp/reader/sparql.rb, line 259
def variable(id, distinguished: true, existential: false)
  id = nil if id.to_s.empty?
  
  if id
    @vars ||= {}
    @vars[id] ||= begin
      RDF::Query::Variable.new(id, distinguished: distinguished, existential: existential)
    end
  else
    RDF::Query::Variable.new(distinguished: distinguished, existential: existential)
  end
end