class RDF::N3::Reader

A Notation-3/Turtle parser in Ruby

N3 Parser, based in librdf version of predictiveParser.py @see www.w3.org/2000/10/swap/grammar/predictiveParser.py @see www.w3.org/2000/10/swap/grammar/n3-selectors.n3

Separate pass to create branch_table from n3-selectors.n3

This implementation only supports quickVars at the document scope.

Non-distinguished blank node variables are created as part of reasoning.

@todo

@author [Gregg Kellogg](greggkellogg.net/)

Attributes

formula_nodes[R]

All nodes allocated to formulae

@return [Hash{RDF::Node => RDF::Graph}]

formulae[R]

Nodes used as Formulae graph names

@return [Array<RDF::Node>]

variables[R]

Allocated variables by formula

@return [Hash{Symbol => RDF::Node}]

Public Class Methods

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

Initializes the N3 reader instance.

@param [IO, File, String] input

the input stream to read

@option options [#to_s] :base_uri (nil)

the base URI to use when resolving relative URIs (not supported by
all readers)

@option options [Boolean] :validate (false)

whether to validate the parsed statements and values

@option options [Boolean] :canonicalize (false)

whether to canonicalize parsed literals and URIs.

@option options [Hash] :prefixes (Hash.new)

the prefix mappings to use (not supported by all readers)

@option options [Hash] :list_terms (false)

represent collections as an `RDF::Term`, rather than an rdf:first/rest ladder.

@return [reader] @yield [reader] ‘self` @yieldparam [RDF::Reader] reader @yieldreturn [void] ignored

@raise [Error]

Raises RDF::ReaderError if validating and an error is found

Calls superclass method
# File lib/rdf/n3/reader.rb, line 83
def initialize(input = $stdin, **options, &block)
  super do
    @options = {
      anon_base:  "b0",
      whitespace:  WS,
      depth: 0,
    }.merge(@options)
    @prod_stack = []

    @formulae = []
    @formula_nodes = {}
    @label_uniquifier = "0"
    @bnodes = {}
    @bn_labler = @options[:anon_base].dup
    @bn_mapper = {}
    @variables = {}

    if options[:base_uri]
      progress("base_uri") { base_uri.inspect}
      namespace(nil, iri(base_uri.to_s.match?(%r{[#/]$}) ? base_uri : "#{base_uri}#"))
    end

    # Prepopulate operator namespaces unless validating
    unless validate?
      namespace(:rdf, RDF.to_uri)
      namespace(:rdfs, RDF::RDFS.to_uri)
      namespace(:xsd, RDF::XSD.to_uri)
      namespace(:crypto, RDF::N3::Crypto.to_uri)
      namespace(:list, RDF::N3::List.to_uri)
      namespace(:log, RDF::N3::Log.to_uri)
      namespace(:math, RDF::N3::Math.to_uri)
      namespace(:rei, RDF::N3::Rei.to_uri)
      #namespace(:string, RDF::N3::String.to_uri)
      namespace(:time, RDF::N3::Time.to_uri)
    end
    progress("validate") {validate?.inspect}
    progress("canonicalize") {canonicalize?.inspect}

    @lexer = EBNF::LL1::Lexer.new(input, self.class.patterns, **@options)

    if block_given?
      case block.arity
        when 0 then instance_eval(&block)
        else block.call(self)
      end
    end
  end
end
options() click to toggle source

N3 Reader options @see ruby-rdf.github.io/rdf/RDF/Reader#options-class_method

Calls superclass method
# File lib/rdf/n3/reader.rb, line 50
def self.options
  super + [
    RDF::CLI::Option.new(
      symbol: :list_terms,
      datatype: TrueClass,
      default: true,
      control: :checkbox,
      on: ["--list-terms CONTEXT"],
      description: "Use native collections (lists), not first/rest ladder.")
  ]
end

Public Instance Methods

each_statement(&block) click to toggle source

Iterates the given block for each RDF statement in the input. @yield [statement] @yieldparam [RDF::Statement] statement @return [void]

# File lib/rdf/n3/reader.rb, line 141
def each_statement(&block)
  if block_given?
    log_recover
    @callback = block

    begin
      while (@lexer.first rescue true)
        read_n3Doc
      end
    rescue EBNF::LL1::Lexer::Error, SyntaxError, EOFError, Recovery
      # Terminate loop if EOF found while recovering
    end

    if validate? && log_statistics[:error]
      raise RDF::ReaderError, "Errors found during processing"
    end
  end
  enum_for(:each_statement)
end
each_triple() { |*to_triple| ... } click to toggle source

Iterates the given block for each RDF triple in the input.

@yield [subject, predicate, object] @yieldparam [RDF::Resource] subject @yieldparam [RDF::URI] predicate @yieldparam [RDF::Value] object @return [void]

# File lib/rdf/n3/reader.rb, line 169
def each_triple
  if block_given?
    each_statement do |statement|
      yield(*statement.to_triple)
    end
  end
  enum_for(:each_triple)
end
inspect() click to toggle source
# File lib/rdf/n3/reader.rb, line 132
def inspect
  sprintf("#<%s:%#0x(%s)>", self.class.name, __id__, base_uri.to_s)
end

Private Instance Methods

add_statement(node, subject, predicate, object) click to toggle source

add a pattern or statement

@param [any] node string for showing graph_name @param [RDF::Term] subject the subject of the statement @param [RDF::URI] predicate the predicate of the statement @param [RDF::Term] object the object of the statement @return [Statement] Added statement @raise [RDF::ReaderError] Checks parameter types and raises if they are incorrect if parsing mode is validate.

# File lib/rdf/n3/reader.rb, line 774
def add_statement(node, subject, predicate, object)
  statement = if @formulae.last
    # It's a pattern in a formula
    RDF::Query::Pattern.new(subject, predicate, object, graph_name: @formulae.last)
  else
    RDF::Statement(subject, predicate, object)
  end
  debug("statement(#{node})", depth: @options[:depth]) {statement.to_s}
  error("statement(#{node})", "Statement is invalid: #{statement.inspect}") if validate? && statement.invalid?
  @callback.call(statement)
end
bnode(label = nil) click to toggle source

Keep track of allocated BNodes. Blank nodes are allocated to the formula. Unnnamed bnodes are created using an incrementing labeler for repeatability.

# File lib/rdf/n3/reader.rb, line 743
def bnode(label = nil)
  form_id = formulae.last ? formulae.last.id : '_bn_ground'
  if label
    # Return previously allocated blank node for.
    @bn_mapper[form_id] ||= {}
    return @bn_mapper[form_id][label] if @bn_mapper[form_id][label]
  end

  # Get a fresh label
  @bn_labler.succ! while @bnodes[@bn_labler]

  bn = RDF::Node.intern(@bn_labler.to_sym)
  @bnodes[@bn_labler] = bn
  @bn_mapper[form_id][label] = bn if label
  bn
end
debug(*args, &block) click to toggle source
# File lib/rdf/n3/reader.rb, line 916
def debug(*args, &block)
  lineno = (options[:token].lineno if options[:token].respond_to?(:lineno)) || (@lexer && @lexer.lineno)
  opts = args.last.is_a?(Hash) ? args.pop : {}
  opts[:level] ||= 0
  opts[:lineno] ||= lineno
  log_debug(*args, **opts, &block)
end
error(*args) click to toggle source

Error information, used as level ‘0` debug messages.

@overload error(node, message, options)

@param [String] node Relevant location associated with message
@param [String] message Error string
@param [Hash] options
@option options [URI, #to_s] :production
@option options [Token] :token
@see {#debug}
# File lib/rdf/n3/reader.rb, line 934
def error(*args)
  ctx = ""
  ctx += "(found #{options[:token].inspect})" if options[:token]
  ctx += ", production = #{options[:production].inspect}" if options[:production]
  lineno = (options[:token].lineno if options[:token].respond_to?(:lineno)) || (@lexer && @lexer.lineno)
  log_error(*args, ctx,
    lineno:     lineno,
    token:      options[:token],
    production: options[:production],
    depth:      options[:depth],
    exception:  SyntaxError,)
end
find_var(name) click to toggle source

Find any variable that may be defined identified by ‘name` @param [RDF::Node] name of formula @return [RDF::Query::Variable]

# File lib/rdf/n3/reader.rb, line 842
def find_var(name)
  variables[name.to_s]
end
iri(value, append = nil) click to toggle source

Create IRIs

# File lib/rdf/n3/reader.rb, line 796
def iri(value, append = nil)
  value = RDF::URI(value)
  value = value.join(append) if append
  value.validate! if validate? && value.respond_to?(:validate)
  value.canonicalize! if canonicalize?

  # Variable substitution for in-scope variables. Variables are in scope if they are defined in anthing other than the current formula
  var = find_var(value)
  value = var if var

  value
rescue ArgumentError => e
  error("iri", e.message)
end
literal(value, **options) click to toggle source

Create a literal

# File lib/rdf/n3/reader.rb, line 812
def literal(value, **options)
  debug("literal", depth: @options[:depth]) do
    "value: #{value.inspect}, " +
    "options: #{options.inspect}, " +
    "validate: #{validate?.inspect}, " +
    "c14n?: #{canonicalize?.inspect}"
  end
  RDF::Literal.new(value, validate:  validate?, canonicalize: canonicalize?, **options)
rescue ArgumentError => e
  error("Argument Error #{e.message}", production: :literal, token: @lexer.first)
end
namespace(prefix, iri) click to toggle source
# File lib/rdf/n3/reader.rb, line 786
def namespace(prefix, iri)
  iri = iri.to_s
  if iri == '#'
    iri = prefix(nil).to_s + '#'
  end
  debug("namespace", depth: @options[:depth]) {"'#{prefix}' <#{iri}>"}
  prefix(prefix, iri(iri))
end
ns(prefix = nil, suffix = nil) click to toggle source

Decode a PName

# File lib/rdf/n3/reader.rb, line 825
def ns(prefix = nil, suffix = nil)
  namespace(nil, iri("#{base_uri}#")) if prefix.nil? && !prefix(nil)

  base = prefix(prefix).to_s
  suffix = suffix.to_s.sub(/^\#/, "") if base.index("#")
  iri(base + suffix.to_s)
end
process_iri(iri) click to toggle source
# File lib/rdf/n3/reader.rb, line 721
def process_iri(iri)
  iri(base_uri, iri.to_s)
end
process_path(path) click to toggle source

Process a path, such as:

:a!:b means [is :b of :a] => :a :b []
:a^:b means [:b :a]       => [] :b :a

Create triple and return property used for next iteration

Result is last created bnode

# File lib/rdf/n3/reader.rb, line 702
def process_path(path)
  pathitem, direction, pathtail = path[:pathitem], path[:direction], path[:pathtail]
  debug("process_path", depth: @options[:depth]) {path.inspect}

  while pathtail
    bnode = bnode()
    pred = pathtail.is_a?(RDF::Term) ? pathtail : pathtail[:pathitem]
    if direction == :reverse
      add_statement("process_path(reverse)", bnode, pred, pathitem)
    else
      add_statement("process_path(forward)", pathitem, pred, bnode)
    end
    pathitem = bnode
    direction = pathtail[:direction] if pathtail.is_a?(Hash)
    pathtail = pathtail.is_a?(Hash) && pathtail[:pathtail]
  end
  pathitem
end
process_pname(value) click to toggle source
# File lib/rdf/n3/reader.rb, line 725
def process_pname(value)
  prefix, name = value.split(":", 2)

  iri = if prefix(prefix)
    #debug('process_pname(ns)', depth: @options[:depth]) {"#{prefix(prefix)}, #{name}"}
    ns(prefix, name)
  elsif prefix && !prefix.empty?
    error("process_pname", "Use of undefined prefix #{prefix.inspect}")
    ns(nil, name)
  else
    ns(nil, name)
  end
  debug('process_pname', depth: @options[:depth]) {iri.inspect}
  iri
end
prod(production, recover_to = []) { || ... } click to toggle source
# File lib/rdf/n3/reader.rb, line 846
def prod(production, recover_to = [])
  @prod_stack << {prod: production, recover_to: recover_to}
  @options[:depth] += 1
  recover("#{production}(start)", depth: options[:depth], token: @lexer.first)
  yield
rescue EBNF::LL1::Lexer::Error, SyntaxError, Recovery =>  e
  # Lexer encountered an illegal token or the parser encountered
  # a terminal which is inappropriate for the current production.
  # Perform error recovery to find a reasonable terminal based
  # on the follow sets of the relevant productions. This includes
  # remaining terms from the current production and the stacked
  # productions
  case e
  when EBNF::LL1::Lexer::Error
    @lexer.recover
    begin
      error("Lexer error", "With input '#{e.input}': #{e.message}",
        production: production,
        token: e.token)
    rescue SyntaxError
    end
  end
  raise EOFError, "End of input found when recovering" if @lexer.first.nil?
  debug("recovery", "current token: #{@lexer.first.inspect}", depth: @options[:depth])

  unless e.is_a?(Recovery)
    # Get the list of follows for this sequence, this production and the stacked productions.
    debug("recovery", "stack follows:", depth: @options[:depth])
    @prod_stack.reverse.each do |prod|
      debug("recovery", level: 1, depth: @options[:depth]) {"  #{prod[:prod]}: #{prod[:recover_to].inspect}"}
    end
  end

  # Find all follows to the top of the stack
  follows = @prod_stack.map {|prod| Array(prod[:recover_to])}.flatten.compact.uniq

  # Skip tokens until one is found in follows
  while (token = (@lexer.first rescue @lexer.recover)) && follows.none? {|t| token === t}
    skipped = @lexer.shift
    debug("recovery", depth: @options[:depth]) {"skip #{skipped.inspect}"}
  end
  debug("recovery", depth: @options[:depth]) {"found #{token.inspect} in follows"}

  # Re-raise the error unless token is a follows of this production
  raise Recovery unless Array(recover_to).any? {|t| token === t}

  # Skip that token to get something reasonable to start the next production with
  @lexer.shift
ensure
  progress("#{production}(finish)", depth: options[:depth])
  @options[:depth] -= 1
  @prod_stack.pop
end
progress(*args, &block) click to toggle source
# File lib/rdf/n3/reader.rb, line 900
def progress(*args, &block)
  lineno = (options[:token].lineno if options[:token].respond_to?(:lineno)) || (@lexer && @lexer.lineno)
  opts = args.last.is_a?(Hash) ? args.pop : {}
  opts[:level] ||= 1
  opts[:lineno] ||= lineno
  log_info(*args, **opts, &block)
end
read_blankNode() click to toggle source

Read a blank node

[29] blankNode ::=  BLANK_NODE_LABEL | ANON

@return [RDF::Node]

# File lib/rdf/n3/reader.rb, line 667
def read_blankNode
  token = @lexer.first
  case token && token.type
  when :BLANK_NODE_LABEL then prod(:blankNode) {bnode(@lexer.shift.value[2..-1])}
  when :ANON then @lexer.shift && prod(:blankNode) {bnode}
  end
end
read_blankNodePropertyList() click to toggle source

Read a blankNodePropertyList

[20] blankNodePropertyList ::= '[' predicateObjectList ']'

@return [RDF::Node]

# File lib/rdf/n3/reader.rb, line 515
def read_blankNodePropertyList
  token = @lexer.first
  if token === '['
    prod(:blankNodePropertyList, %{]}) do
      @lexer.shift
      progress("blankNodePropertyList", depth: options[:depth], token: token)
      node = bnode
      debug("blankNodePropertyList: subject", depth: options[:depth]) {node.to_sxp}
      read_predicateObjectList(node)
      error("blankNodePropertyList", "Expected closing ']'") unless @lexer.first === ']'
      @lexer.shift
      node
    end
  end
end
read_collection() click to toggle source

Read a collection (‘RDF::List`)

[21] collection ::= '(' object* ')'

If the ‘list_terms` option is given, the resulting resource is a list, otherwise, it is the list subject, and the first/rest entries are also emitted. @return [RDF::Node]

# File lib/rdf/n3/reader.rb, line 560
def read_collection
  if @lexer.first === '('
    prod(:collection, %{)}) do
      @lexer.shift
      token = @lexer.first
      progress("collection", depth: options[:depth]) {"token: #{token.inspect}"}
      objects = []
      while @lexer.first.value != ')' && (object = read_path)
        objects << object
      end
      error("collection", "Expected closing ')'") unless @lexer.first === ')'
      @lexer.shift
      list = RDF::N3::List.new(values: objects)
      if options[:list_terms]
        list
      else
        list.each_statement do |statement|
          add_statement("collection", *statement.to_a)
        end
        list.subject
      end
    end
  end
end
read_directive() click to toggle source

Read base and prefix directives

[3]  n3Directive ::= prefixID | base

@return [void]

# File lib/rdf/n3/reader.rb, line 258
def read_directive
  prod(:directive, %w{.}) do
    token = @lexer.first
    case token.type
    when :BASE
      prod(:base) do
        @lexer.shift
        terminated = token.value == '@base'
        iri = @lexer.shift
        error("Expected IRIREF", production: :base, token: iri) unless iri === :IRIREF
        @options[:base_uri] = process_iri(iri.value[1..-2].gsub(/\s/, ''))
        namespace(nil, base_uri.to_s.end_with?('#') ? base_uri : iri("#{base_uri}#"))
        error("base", "#{token} should be downcased") if token.value.start_with?('@') && token.value != '@base'

        if terminated
          error("base", "Expected #{token} to be terminated") unless @lexer.first === '.'
          @lexer.shift
        elsif @lexer.first === '.'
          error("base", "Expected #{token} not to be terminated") 
        else
          true
        end
      end
    when :PREFIX
      prod(:prefixID, %w{.}) do
        @lexer.shift
        pfx, iri = @lexer.shift, @lexer.shift
        terminated = token.value == '@prefix'
        error("Expected PNAME_NS", production: :prefix, token: pfx) unless pfx === :PNAME_NS
        error("Expected IRIREF", production: :prefix, token: iri) unless iri === :IRIREF
        debug("prefixID", depth: options[:depth]) {"Defined prefix #{pfx.inspect} mapping to #{iri.inspect}"}
        namespace(pfx.value[0..-2], process_iri(iri.value[1..-2].gsub(/\s/, '')))
        error("prefixId", "#{token} should be downcased") if token.value.start_with?('@') && token.value != '@prefix'

        if terminated
          error("prefixID", "Expected #{token} to be terminated") unless @lexer.first === '.'
          @lexer.shift
        elsif @lexer.first === '.'
          error("prefixID", "Expected #{token} not to be terminated") 
        else
          true
        end
      end
    end
  end
end
read_formula() click to toggle source

Read a formula

[22] formula         ::= '{' formulaContent? '}'
[23] formulaContent  ::= n3Statement ('.' formulaContent?)?

@return [RDF::Node]

# File lib/rdf/n3/reader.rb, line 592
def read_formula
  if @lexer.first === '{'
    prod(:formula, %(})) do
      @lexer.shift
      node = RDF::Node.intern("_form_#{unique_label}")
      formulae.push(node)
      formula_nodes[node] = true
      debug(:formula, depth: @options[:depth]) {"id: #{node}, depth: #{formulae.length}"}

      read_formulaContent

      # Pop off the formula
      # Result is the BNode associated with the formula
      debug(:formula, depth: @options[:depth]) {"pop: #{formulae.last}, depth: #{formulae.length}"}
      error("collection", "Expected closing '}'") unless @lexer.shift === '}'

      formulae.pop
    end
  end
end
read_formulaContent() click to toggle source

Read formula content, similaer to n3Statement

[23] formulaContent  ::= n3Statement ('.' formulaContent?)?

@return [void]

# File lib/rdf/n3/reader.rb, line 619
def read_formulaContent
  return if @lexer.first === '}'  # Allow empty formula
  prod(:formulaContent, %w(. })) do
    loop do
      token = @lexer.first
      error("read_formulaContent", "Unexpected end of file") unless token
      case token.type
      when :BASE, :PREFIX
        read_directive || error("Failed to parse directive", production: :directive, token: token)
        break if @lexer.first === '}'
      else
        read_n3Statement
        token = @lexer.first
        case token.value
        when '.'
          @lexer.shift
          # '.' optional at end of formulaContent
          break if @lexer.first === '}'
        when '}'
          break
        else
          error("Expected '.' or '}' following n3Statement", production: :formulaContent, token: token)
        end
      end
    end
  end
end
read_iri() click to toggle source

Read an IRI

(rule iri "26" (alt IRIREF prefixedName))

@return [RDF::URI]

# File lib/rdf/n3/reader.rb, line 653
def read_iri
  token = @lexer.first
  case token && token.type
  when :IRIREF then prod(:iri)  {process_iri(@lexer.shift.value[1..-2].gsub(/\s+/m, ''))}
  when :PNAME_LN, :PNAME_NS then prod(:prefixedName) {process_pname(*@lexer.shift.value)}
  end
end
read_iriPropertyList() click to toggle source

Read a iriPropertyList

[21] iriPropertyList ::= IPLSTART iri predicateObjectList ']'

@return [RDF::Node]

# File lib/rdf/n3/reader.rb, line 537
def read_iriPropertyList
  token = @lexer.first
  if token.type == :IPLSTART
    prod(:iriPropertyList, %{]}) do
      @lexer.shift
      progress("iriPropertyList", depth: options[:depth], token: token)
      node = read_iri
      debug("iriPropertyList: subject", depth: options[:depth]) {node.to_sxp}
      read_predicateObjectList(node)
      error("iriPropertyList", "Expected closing ']'") unless @lexer.first === ']'
      @lexer.shift
      node
    end
  end
end
read_literal() click to toggle source

Read a literal

[19] literal ::= rdfLiteral | numericLiteral | BOOLEAN_LITERAL

@return [RDF::Literal]

# File lib/rdf/n3/reader.rb, line 466
def read_literal
  error("Unexpected end of file", production: :literal) unless token = @lexer.first
  case token.type || token.value
  when :INTEGER then prod(:literal) {literal(@lexer.shift.value, datatype:  RDF::XSD.integer, canonicalize: canonicalize?)}
  when :DECIMAL
    prod(:literal) do
      value = @lexer.shift.value
      value = "0#{value}" if value.start_with?(".")
      literal(value, datatype:  RDF::XSD.decimal, canonicalize: canonicalize?)
    end
  when :DOUBLE then prod(:literal) {literal(@lexer.shift.value.sub(/\.([eE])/, '.0\1'), datatype:  RDF::XSD.double, canonicalize: canonicalize?)}
  when "true", "false" then prod(:literal) {literal(@lexer.shift.value, datatype: RDF::XSD.boolean, canonicalize: canonicalize?)}
  when :STRING_LITERAL_QUOTE, :STRING_LITERAL_SINGLE_QUOTE
    prod(:literal) do
      value = @lexer.shift.value[1..-2]
      error("read_literal", "Unexpected end of file") unless token = @lexer.first
      case token.type || token.value
      when :LANGTAG
        literal(value, language: @lexer.shift.value[1..-1].to_sym)
      when '^^'
        @lexer.shift
        literal(value, datatype: read_iri)
      else
        literal(value)
      end
    end
  when :STRING_LITERAL_LONG_QUOTE, :STRING_LITERAL_LONG_SINGLE_QUOTE
    prod(:literal) do
      value = @lexer.shift.value[3..-4]
      error("read_literal", "Unexpected end of file") unless token = @lexer.first
      case token.type || token.value
      when :LANGTAG
        literal(value, language: @lexer.shift.value[1..-1].to_sym)
      when '^^'
        @lexer.shift
        literal(value, datatype: read_iri)
      else
        literal(value)
      end
    end
  end
end
read_n3Doc() click to toggle source

Read statements and directives

[1]  n3Doc ::= (n3Statement '.' | sparqlDirective)*

@return [void]

# File lib/rdf/n3/reader.rb, line 218
def read_n3Doc
  prod(:n3Doc, %w{.}) do
    error("read_n3Doc", "Unexpected end of file") unless token = @lexer.first
    case token.type
    when :BASE, :PREFIX
      read_directive || error("Failed to parse directive", production: :directive, token: token)
    else
      read_n3Statement
      if !log_recovering? || @lexer.first === '.'
        # If recovering, we will have eaten the closing '.'
        token = @lexer.shift
        unless token && token.value == '.'
          error("Expected '.' following n3Statement", production: :n3Statement, token: token)
        end
      end
    end
  end
end
read_n3Statement() click to toggle source

Read statements and directives

[2]  n3Statement ::= n3Directive | triples | existential | universal

@return [void]

# File lib/rdf/n3/reader.rb, line 244
def read_n3Statement
  prod(:n3Statement, %w{.}) do
    error("read_n3Doc", "Unexpected end of file") unless token = @lexer.first
    read_triples ||
    error("Expected token", production: :statement, token: token)
  end
end
read_objectList(subject, predicate, invert) click to toggle source

Read objectList

[11] objectList ::= object (',' object)*

@return [RDF::Term] the last matched subject

# File lib/rdf/n3/reader.rb, line 360
def read_objectList(subject, predicate, invert)
  prod(:objectList, %{,}) do
    last_object = nil
    while object = prod(:_objectList_2) {read_path}
      last_object = object

      if invert
         add_statement(:objectList, object, predicate, subject)
      else
        add_statement(:objectList, subject, predicate, object)
      end

      break unless @lexer.first === ','
      @lexer.shift while @lexer.first === ','
    end
    last_object
  end
end
read_path() click to toggle source

subjects, predicates and objects are all expressions, which are all paths

[13] subject       ::= expression
[14] predicate     ::= expression
[16] expression    ::= path
[17] path              ::= pathItem ('!' path | '^' path)?

@return [RDF::Resource]

# File lib/rdf/n3/reader.rb, line 431
def read_path
  return if @lexer.first.nil? || %w/. } ) ]/.include?(@lexer.first.value)
  prod(:path) do
    pathtail = path = {}
    loop do
      pathtail[:pathitem] = prod(:pathItem) do
        read_iri ||
        read_blankNode ||
        read_quickVar ||
        read_collection ||
        read_blankNodePropertyList ||
        read_iriPropertyList ||
        read_literal ||
        read_formula
      end

      break if @lexer.first.nil? || !%w(! ^).include?(@lexer.first.value)
      prod(:_path_2) do
        pathtail[:direction] = @lexer.shift.value == '!' ? :forward : :reverse
        pathtail = pathtail[:pathtail] = {}
      end
    end

    # Returns the first object in the path
    # FIXME: what if it's a verb?
    process_path(path)
  end
end
read_predicateObjectList(subject) click to toggle source

Read predicateObjectList

[10] predicateObjectList ::= verb objectList (';' (verb objectList)?)*

@param [RDF::Resource] subject @return [RDF::URI] the last matched verb

# File lib/rdf/n3/reader.rb, line 336
def read_predicateObjectList(subject)
  return if @lexer.first.nil? || %w(. }).include?(@lexer.first.value)
  prod(:predicateObjectList, %{;}) do
    last_verb = nil
    loop do
      verb, invert = read_verb
      break unless verb
      last_verb = verb
      prod(:_predicateObjectList_2) do
        read_objectList(subject, verb, invert) || error("Expected objectList", production: :predicateObjectList, token: @lexer.first)
      end
      break unless @lexer.first === ';'
      @lexer.shift while @lexer.first === ';'
    end
    last_verb
  end
end
read_quickVar() click to toggle source

Read a quickVar, having global scope.

[30] quickVar ::= QUICK_VAR_NAME

@return [RDF::Query::Variable]

# File lib/rdf/n3/reader.rb, line 681
def read_quickVar
  if @lexer.first.type == :QUICK_VAR_NAME
    prod(:quickVar) do
      token = @lexer.shift
      value = token.value.sub('?', '')
      variables[value] ||= RDF::Query::Variable.new(value)
    end
  end
end
read_triples() click to toggle source

Read triples

[9]  triples ::= subject predicateObjectList?

@return [Object] returns the last IRI matched, or subject BNode on predicateObjectList?

# File lib/rdf/n3/reader.rb, line 311
def read_triples
  prod(:triples, %w{.}) do
    error("read_triples", "Unexpected end of file") unless token = @lexer.first
    subject = case token.type || token.value
    when IPLSTART
      # iriPropertyList predicateObjectList?
      read_iriPropertyList || error("Failed to parse iriPropertyList", production: :triples, token: @lexer.first)
    when '['
      # blankNodePropertyList predicateObjectList?
      read_blankNodePropertyList || error("Failed to parse blankNodePropertyList", production: :triples, token: @lexer.first)
    else
      # subject predicateObjectList
      read_path || error("Failed to parse subject", production: :triples, token: @lexer.first)
    end
    read_predicateObjectList(subject) || subject
  end
end
read_verb() click to toggle source

Read a verb

[12] verb = predicate
          | 'a'
          | 'has' expression
          | 'is' expression 'of'
          | '<-' expression
          | '<='
          | '=>'
          | '='

@return [RDF::Resource, Boolean] verb and invert?

# File lib/rdf/n3/reader.rb, line 392
def read_verb
  invert = false
  error("read_verb", "Unexpected end of file") unless token = @lexer.first
  verb = case token.type || token.value
  when 'a' then prod(:verb) {@lexer.shift && RDF.type}
  when 'has' then prod(:verb) {@lexer.shift && read_path}
  when 'is' then prod(:verb) {
    @lexer.shift
    invert, v = true, read_path
    error( "Expected 'of'", production: :verb, token: @lexer.first) unless @lexer.first.value == 'of'
    @lexer.shift
    v
  }
  when '<-' then prod(:verb) {
    @lexer.shift
    invert = true
    read_path
  }
  when '<=' then prod(:verb) {
    @lexer.shift
    invert = true
    RDF::N3::Log.implies
  }
  when '=>' then prod(:verb) {@lexer.shift && RDF::N3::Log.implies}
  when '='  then prod(:verb) {@lexer.shift && RDF::OWL.sameAs}
  else read_path
  end
  [verb, invert]
end
recover(*args, &block) click to toggle source
# File lib/rdf/n3/reader.rb, line 908
def recover(*args, &block)
  lineno = (options[:token].lineno if options[:token].respond_to?(:lineno)) || (@lexer && @lexer.lineno)
  opts = args.last.is_a?(Hash) ? args.pop : {}
  opts[:level] ||= 1
  opts[:lineno] ||= lineno
  log_recover(*args, **opts, &block)
end
unique_label() click to toggle source

Returns a unique label

# File lib/rdf/n3/reader.rb, line 834
def unique_label
  label, @label_uniquifier = @label_uniquifier, @label_uniquifier.succ
  label
end
univar(label, scope:) click to toggle source

If not in ground formula, note scope, and if existential

# File lib/rdf/n3/reader.rb, line 761
def univar(label, scope:)
  value = label
  RDF::Query::Variable.new(value)
end