rdf-n3 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/.yardopts CHANGED
@@ -8,3 +8,4 @@
8
8
  -
9
9
  AUTHORS
10
10
  VERSION
11
+ History.txt
data/History.txt CHANGED
@@ -1,2 +1,11 @@
1
+ === 0.0.2
2
+ * N3 parsing and Turtle serialization substantially complete.
3
+ * A little more work needed on some tests and some lingering issues in RDF.rb to be resolved.
4
+ * Added script/console and script/parse
5
+ * Updates to reader to bring it in line with other readers. Implement uri() and ns() as helper functions for constructing URIs.
6
+ * Literal_normalization to override RDF::Literal.initialize and create Literal#valid?
7
+ * rdf_escape Literals when serializing via to_s
8
+ * Remove trailing "#" from URIs when normalizing.
9
+
1
10
  === 0.0.1
2
11
  * First port from RdfContext version 0.5.4
data/README.rdoc CHANGED
@@ -10,6 +10,8 @@ RDF::N3 is an Notation-3 (n3-rdf) parser for Ruby using the RDF.rb library suite
10
10
  RDF::N3 parses Notation-3, Turtle and N-Triples into statements or triples. It also serializes to Turtle.
11
11
 
12
12
  * Fully compliant N3-rdf parser (N3-rdf level)
13
+ * Also parses Turtle and N-Triples
14
+ * Turtle serializer
13
15
 
14
16
  Install with 'gem install rdf-n3'
15
17
 
@@ -25,8 +27,8 @@ Instantiate a parser and parse source, specifying type and base-URL
25
27
  end
26
28
 
27
29
  == Dependencies
28
- * [RDF.rb](http://rubygems.org/gems/rdf) (>= 0.1.6)
29
- * [Treetop](http://rubygems.org/gems/treetop) (>= 1.4.0) -- For generating parser only, not necessary for runtime
30
+ * [RDF.rb](http://rubygems.org/gems/rdf) (>= 0.2.0)
31
+ * [Treetop](http://rubygems.org/gems/treetop) (>= 1.4.0)
30
32
 
31
33
  == Resources:
32
34
  * Distiller[http://kellogg-assoc/distiller]
data/Rakefile CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'rubygems'
2
+ require 'yard'
2
3
 
3
4
  begin
4
5
  gem 'jeweler'
@@ -12,10 +13,13 @@ begin
12
13
  gemspec.email = "gregg@kellogg-assoc.com"
13
14
  gemspec.homepage = "http://github.com/gkellogg/rdf-rdfa"
14
15
  gemspec.authors = ["Gregg Kellogg"]
15
- gemspec.add_dependency('rdf', '>= 0.1.6')
16
+ gemspec.add_dependency('rdf', '>= 0.2.0')
17
+ gemspec.add_dependency('treetop', '>= 1.4.0')
16
18
  gemspec.add_development_dependency('rspec')
17
19
  gemspec.add_development_dependency('rdf-spec')
18
- gemspec.add_development_dependency('treetop', '>= 1.4.0')
20
+ gemspec.add_development_dependency('rdf-rdfxml', '>= 0.2.0')
21
+ gemspec.add_development_dependency('rdf-isomorphic')
22
+ gemspec.add_development_dependency('yard')
19
23
  gemspec.extra_rdoc_files = %w(README.rdoc History.txt AUTHORS)
20
24
  end
21
25
  Jeweler::GemcutterTasks.new
@@ -43,4 +47,35 @@ Spec::Rake::SpecTask.new("doc:spec") do |spec|
43
47
  spec.spec_opts = ["--format", "html:doc/spec.html"]
44
48
  end
45
49
 
50
+ YARD::Rake::YardocTask.new do |t|
51
+ t.files = %w(lib/**/*.rb README.rdoc History.txt AUTHORS) # optional
52
+ end
53
+
54
+ desc "Generate test manifest yaml"
55
+ namespace :spec do
56
+ task :prepare do
57
+ $:.unshift(File.join(File.dirname(__FILE__), 'lib'))
58
+ require 'rdf/rdfxml'
59
+ require 'rdf/n3'
60
+ require 'spec/rdf_helper'
61
+ require 'fileutils'
62
+
63
+ yaml = File.join(SWAP_DIR, "n3parser.yml")
64
+ FileUtils.rm_f(yaml)
65
+ RdfHelper::TestCase.to_yaml(SWAP_TEST, SWAP_DIR, yaml)
66
+
67
+ yaml = File.join(SWAP_DIR, "regression.yml")
68
+ FileUtils.rm_f(yaml)
69
+ RdfHelper::TestCase.to_yaml(CWM_TEST, SWAP_DIR, yaml)
70
+
71
+ yaml = File.join(TURTLE_DIR, "manifest.yml")
72
+ FileUtils.rm_f(yaml)
73
+ RdfHelper::TestCase.to_yaml(TURTLE_TEST, TURTLE_DIR, yaml)
74
+
75
+ yaml = File.join(TURTLE_DIR, "manifest-bad.yml")
76
+ FileUtils.rm_f(yaml)
77
+ RdfHelper::TestCase.to_yaml(TURTLE_TEST, TURTLE_DIR, yaml)
78
+ end
79
+ end
80
+
46
81
  task :default => :spec
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.1
1
+ 0.0.2
data/example.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  $:.unshift(File.join(File.dirname(__FILE__), 'lib'))
4
4
 
5
5
  require 'rubygems'
6
- require 'rdf/rdfa'
6
+ require 'rdf/n3'
7
7
 
8
8
  data = <<-EOF;
9
9
  @prefix dc: <http://purl.org/dc/elements/1.1/>.
data/lib/rdf/n3.rb CHANGED
@@ -1,4 +1,4 @@
1
- $:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
1
+ $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), '..')))
2
2
  require 'rdf'
3
3
 
4
4
  module RDF
@@ -20,11 +20,18 @@ module RDF
20
20
  #
21
21
  # @author [Gregg Kellogg](http://kellogg-assoc.com/)
22
22
  module N3
23
- require 'n3/format'
24
- require 'n3/vocab'
25
- require 'n3/patches/array_hacks'
26
- require 'n3/patches/rdf_escape'
23
+ require 'rdf/n3/format'
24
+ require 'rdf/n3/vocab'
25
+ require 'rdf/n3/patches/array_hacks'
26
+ require 'rdf/n3/patches/literal_hacks'
27
+ require 'rdf/n3/patches/literal_normalization'
28
+ require 'rdf/n3/patches/graph_properties'
29
+ require 'rdf/n3/patches/qname_hacks'
30
+ require 'rdf/n3/patches/rdf_escape'
31
+ require 'rdf/n3/patches/seq'
32
+ require 'rdf/n3/patches/uri_hacks'
27
33
  autoload :Reader, 'rdf/n3/reader'
28
34
  autoload :VERSION, 'rdf/n3/version'
35
+ autoload :Writer, 'rdf/n3/writer'
29
36
  end
30
37
  end
data/lib/rdf/n3/format.rb CHANGED
@@ -4,7 +4,6 @@ module RDF::N3
4
4
  #
5
5
  # @example Obtaining an RDFa format class
6
6
  # RDF::Format.for(:n3) #=> RDF::N3::Format
7
- # RDF::Format.for(:ttl) #=> RDF::N3::Format
8
7
  # RDF::Format.for("etc/foaf.ttl")
9
8
  # RDF::Format.for("etc/foaf.n3")
10
9
  # RDF::Format.for(:file_name => "etc/foaf.ttl")
@@ -1,131 +1,53 @@
1
- # coding: utf-8
2
- require 'iconv'
3
-
4
- class String
5
- #private
6
- # "Borrowed" from JSON utf8_to_json
7
- RDF_MAP = {
8
- "\x0" => '\u0000',
9
- "\x1" => '\u0001',
10
- "\x2" => '\u0002',
11
- "\x3" => '\u0003',
12
- "\x4" => '\u0004',
13
- "\x5" => '\u0005',
14
- "\x6" => '\u0006',
15
- "\x7" => '\u0007',
16
- "\b" => '\b',
17
- "\t" => '\t',
18
- "\n" => '\n',
19
- "\xb" => '\u000B',
20
- "\f" => '\f',
21
- "\r" => '\r',
22
- "\xe" => '\u000E',
23
- "\xf" => '\u000F',
24
- "\x10" => '\u0010',
25
- "\x11" => '\u0011',
26
- "\x12" => '\u0012',
27
- "\x13" => '\u0013',
28
- "\x14" => '\u0014',
29
- "\x15" => '\u0015',
30
- "\x16" => '\u0016',
31
- "\x17" => '\u0017',
32
- "\x18" => '\u0018',
33
- "\x19" => '\u0019',
34
- "\x1a" => '\u001A',
35
- "\x1b" => '\u001B',
36
- "\x1c" => '\u001C',
37
- "\x1d" => '\u001D',
38
- "\x1e" => '\u001E',
39
- "\x1f" => '\u001F',
40
- '"' => '\"',
41
- '\\' => '\\\\',
42
- '/' => '/',
43
- } # :nodoc:
44
-
45
- if defined?(::Encoding)
46
- # Funky way to define constant, but if parsed in 1.8 it generates an 'invalid regular expression' error otherwise
47
- eval %(ESCAPE_RE = %r([\u{80}-\u{10ffff}]))
48
- else
49
- ESCAPE_RE = %r(
50
- [\xc2-\xdf][\x80-\xbf] |
51
- [\xe0-\xef][\x80-\xbf]{2} |
52
- [\xf0-\xf4][\x80-\xbf]{3}
53
- )nx
54
- end
55
-
56
- # Convert a UTF8 encoded Ruby string _string_ to an escaped string, encoded with
57
- # UTF16 big endian characters as \U????, and return it.
58
- #
59
- # \\:: Backslash
60
- # \':: Single quote
61
- # \":: Double quot
62
- # \n:: ASCII Linefeed
63
- # \r:: ASCII Carriage Return
64
- # \t:: ASCCII Horizontal Tab
65
- # \uhhhh:: character in BMP with Unicode value U+hhhh
66
- # \U00hhhhhh:: character in plane 1-16 with Unicode value U+hhhhhh
67
- def rdf_escape
68
- string = self + '' # XXX workaround: avoid buffer sharing
69
- string.gsub!(/["\\\/\x0-\x1f]/) { RDF_MAP[$&] }
70
- if defined?(::Encoding)
71
- string.force_encoding(Encoding::UTF_8)
72
- string.gsub!(ESCAPE_RE) { |c|
73
- s = c.dump.sub(/\"\\u\{(.+)\}\"/, '\1').upcase
74
- (s.length <= 4 ? "\\u0000"[0,6-s.length] : "\\U00000000"[0,10-s.length]) + s
75
- }
76
- string.force_encoding(Encoding::ASCII_8BIT)
1
+ class Array
2
+ # http://wiki.rubygarden.org/Ruby/page/show/ArrayPermute
3
+ # Permute an array, and call a block for each permutation
4
+ # Author: Paul Battley
5
+ def permute(prefixed=[])
6
+ if (length < 2)
7
+ # there are no elements left to permute
8
+ yield(prefixed + self)
77
9
  else
78
- string.gsub!(ESCAPE_RE) { |c|
79
- s = Iconv.new('utf-16be', 'utf-8').iconv(c).unpack('H*').first.upcase
80
- "\\u" + s
81
- }
10
+ # recursively permute the remaining elements
11
+ each_with_index do |e, i|
12
+ (self[0,i]+self[(i+1)..-1]).permute(prefixed+[e]) { |a| yield a }
13
+ end
82
14
  end
83
- string
84
- end
15
+ end unless Array.method_defined?(:permute)
85
16
 
86
- # Unescape characters in strings.
87
- RDF_UNESCAPE_MAP = Hash.new { |h, k| h[k] = k.chr }
88
- RDF_UNESCAPE_MAP.update({
89
- ?" => '"',
90
- ?\\ => '\\',
91
- ?/ => '/',
92
- ?b => "\b",
93
- ?f => "\f",
94
- ?n => "\n",
95
- ?r => "\r",
96
- ?t => "\t",
97
- ?u => nil,
98
- })
17
+ # Converts the array to a comma-separated sentence where the last element is joined by the connector word. Options:
18
+ # * <tt>:words_connector</tt> - The sign or word used to join the elements in arrays with two or more elements (default: ", ")
19
+ # * <tt>:two_words_connector</tt> - The sign or word used to join the elements in arrays with two elements (default: " and ")
20
+ # * <tt>:last_word_connector</tt> - The sign or word used to join the last element in arrays with three or more elements (default: ", and ")
21
+ def to_sentence(options = {})
22
+ default_words_connector = ", "
23
+ default_two_words_connector = " and "
24
+ default_last_word_connector = ", and "
99
25
 
100
- if defined?(::Encoding)
101
- UNESCAPE_RE = %r(
102
- (?:\\[\\bfnrt"/]) # Escaped control characters, " and /
103
- |(?:\\U00\h{6}) # 6 byte escaped Unicode
104
- |(?:\\u\h{4}) # 4 byte escaped Unicode
105
- )x
106
- else
107
- UNESCAPE_RE = %r((?:\\[\\bfnrt"/]|(?:\\u(?:[A-Fa-f\d]{4}))+|\\[\x20-\xff]))n
108
- end
109
-
110
- # Reverse operation of escape
111
- # From JSON parser
112
- def rdf_unescape
113
- return '' if self.empty?
114
- string = self.gsub(UNESCAPE_RE) do |c|
115
- case c[1,1]
116
- when 'U'
117
- raise RdfException, "Long Unicode escapes no supported in Ruby 1.8" unless defined?(::Encoding)
118
- eval(c.sub(/\\U00(\h+)/, '"\u{\1}"'))
119
- when 'u'
120
- bytes = [c[2, 2].to_i(16), c[4, 2].to_i(16)]
121
- Iconv.new('utf-8', 'utf-16').iconv(bytes.pack("C*"))
26
+ # Try to emulate to_senteces previous to 2.3
27
+ if options.has_key?(:connector) || options.has_key?(:skip_last_comma)
28
+ ::ActiveSupport::Deprecation.warn(":connector has been deprecated. Use :words_connector instead", caller) if options.has_key? :connector
29
+ ::ActiveSupport::Deprecation.warn(":skip_last_comma has been deprecated. Use :last_word_connector instead", caller) if options.has_key? :skip_last_comma
30
+
31
+ skip_last_comma = options.delete :skip_last_comma
32
+ if connector = options.delete(:connector)
33
+ options[:last_word_connector] ||= skip_last_comma ? connector : ", #{connector}"
122
34
  else
123
- RDF_UNESCAPE_MAP[c[1]]
35
+ options[:last_word_connector] ||= skip_last_comma ? default_two_words_connector : default_last_word_connector
124
36
  end
125
37
  end
126
- string.force_encoding(Encoding::UTF_8) if defined?(::Encoding)
127
- string
128
- rescue Iconv::Failure => e
129
- raise RdfException, "Caught #{e.class}: #{e}"
130
- end
131
- end unless String.method_defined?(:rdf_escape)
38
+
39
+ # options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale)
40
+ options = {:words_connector => default_words_connector, :two_words_connector => default_two_words_connector, :last_word_connector => default_last_word_connector}.merge(options)
41
+
42
+ case length
43
+ when 0
44
+ ""
45
+ when 1
46
+ self[0].to_s
47
+ when 2
48
+ "#{self[0]}#{options[:two_words_connector]}#{self[1]}"
49
+ else
50
+ "#{self[0...-1].join(options[:words_connector])}#{options[:last_word_connector]}#{self[-1]}"
51
+ end
52
+ end unless Array.method_defined?(:to_sentence)
53
+ end
@@ -0,0 +1,34 @@
1
+ module RDF
2
+ class Graph
3
+ # Resource properties
4
+ #
5
+ # Properties arranged as a hash with the predicate Term as index to an array of resources or literals
6
+ #
7
+ # Example:
8
+ # graph.load(':foo a :bar; rdfs:label "An example" .', "http://example.com/")
9
+ # graph.resources(URI.new("http://example.com/subject")) =>
10
+ # {
11
+ # "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" => [<http://example.com/#bar>],
12
+ # "http://example.com/#label" => ["An example"]
13
+ # }
14
+ def properties(subject, recalc = false)
15
+ @properties ||= {}
16
+ @properties.delete(subject.to_s) if recalc
17
+ @properties[subject.to_s] ||= begin
18
+ hash = Hash.new
19
+ self.query(:subject => subject) do |statement|
20
+ pred = statement.predicate.to_s
21
+
22
+ hash[pred] ||= []
23
+ hash[pred] << statement.object
24
+ end
25
+ hash
26
+ end
27
+ end
28
+
29
+ # Get type(s) of subject, returns a list of symbols
30
+ def type_of(subject)
31
+ query(:subject => subject, :predicate => RDF.type).map {|st| st.object}
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,23 @@
1
+ module RDF
2
+ class Literal
3
+ # Support for XML Literals
4
+ # Is this an XMLLiteral?
5
+ def xmlliteral?
6
+ datatype == RDF['XMLLiteral']
7
+ end
8
+
9
+ def anonymous?; false; end unless respond_to?(:anonymous?)
10
+
11
+ ##
12
+ # Returns a string representation of this literal.
13
+ #
14
+ # @return [String]
15
+ def to_s
16
+ quoted = value # FIXME
17
+ output = "\"#{quoted.to_s.rdf_escape}\""
18
+ output << "@#{language}" if has_language? && !has_datatype?
19
+ output << "^^<#{datatype}>" if has_datatype?
20
+ output
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,120 @@
1
+ autoload :Date, 'date'
2
+ autoload :DateTime, 'date'
3
+ autoload :Time, 'time'
4
+
5
+ module RDF
6
+ class Literal
7
+ ##
8
+ # Re-define initialize/new to call _normalize_ on value.
9
+ # @param [Object]
10
+ # @option options [Symbol] :language (nil)
11
+ # @option options [URI] :datatype (nil)
12
+ # @option options[Hash] :namespaces ({})
13
+ def initialize_with_normalization(value, options = {})
14
+ initialize_without_normalization(value, options)
15
+ normalize(options)
16
+ end
17
+
18
+ alias_method :initialize_without_normalization, :initialize
19
+ alias_method :initialize, :initialize_with_normalization
20
+
21
+ def valid?
22
+ case datatype
23
+ when XSD.boolean then %w(1 true 0 false).include?(value.to_s.downcase)
24
+ when XSD.decimal then !!value.to_s.match(/^[\+\-]?\d+(\.\d*)?$/)
25
+ when XSD.double then !!value.to_s.match(/^[\+\-]?\d+(\.\d*([eE][\+\-]?\d+)?)?$/)
26
+ when XSD.integer then !!value.to_s.match(/^[\+\-]?\d+$/)
27
+ else true
28
+ end
29
+ end
30
+
31
+ protected
32
+
33
+ # Normalize literal value
34
+ #
35
+ # Options is a hash passed to initialize
36
+ def normalize(options = {})
37
+ return unless valid? # Only normalize valid value
38
+
39
+ @value = case datatype
40
+ when XSD.boolean then %(1 true).include?(@value.to_s.downcase) ? "true" : "false"
41
+ when XSD.integer then @value.to_i.to_s
42
+ when XSD.decimal then normalize_decimal(@value, options)
43
+ when XSD.double then normalize_double(@value, options)
44
+ when XSD.time then @value.is_a?(Time) ? @value.strftime("%H:%M:%S%Z").sub(/\+00:00|UTC/, "Z") : @value.to_s
45
+ when XSD.dateTime then @value.is_a?(DateTime) ? @value.strftime("%Y-%m-%dT%H:%M:%S%Z").sub(/\+00:00|UTC/, "Z") : @value.to_s
46
+ when XSD.date then @value.is_a?(Date) ? @value.strftime("%Y-%m-%d%Z").sub(/\+00:00|UTC/, "Z") : @value.to_s
47
+ when RDF.XMLLiteral then normalize_xmlliteral(@value, options)
48
+ else @value.to_s
49
+ end
50
+ end
51
+
52
+ def normalize_decimal(contents, options)
53
+ # Can't use simple %f transformation do to special requirements from N3 tests in representation
54
+ i, f = contents.to_s.split(".")
55
+ f = f.to_s[0,16] # Truncate after 15 decimal places
56
+ i.sub!(/^\+?0+(\d)$/, '\1')
57
+ f.sub!(/0*$/, '')
58
+ f = "0" if f.empty?
59
+ "#{i}.#{f}"
60
+ end
61
+
62
+ def normalize_double(contents, options)
63
+ i, f, e = ("%.16E" % contents.to_f).split(/[\.E]/)
64
+ f.sub!(/0*$/, '')
65
+ f = "0" if f.empty?
66
+ e.sub!(/^\+?0+(\d)$/, '\1')
67
+ "#{i}.#{f}E#{e}"
68
+ end
69
+
70
+ # Normalize an XML Literal, by adding necessary namespaces.
71
+ # This should be done as part of initialize
72
+ #
73
+ # namespaces is a hash of prefix => URIs
74
+ def normalize_xmlliteral(contents, options = {})
75
+ options[:namespaces] ||= {}
76
+
77
+ begin
78
+ # Only normalize if Nokogiri is included
79
+ require 'nokogiri' unless defined?(Nokogiri)
80
+ rescue LoadError => e
81
+ contents.to_s # No normalization
82
+ end
83
+
84
+ if contents.is_a?(String)
85
+ ns_hash = {}
86
+ options[:namespaces].each_pair do |prefix, uri|
87
+ attr = prefix.to_s.empty? ? "xmlns" : "xmlns:#{prefix}"
88
+ ns_hash[attr] = uri.to_s
89
+ end
90
+ ns_strs = []
91
+ ns_hash.each_pair {|a, u| ns_strs << "#{a}=\"#{u}\""}
92
+
93
+ # Add inherited namespaces to created root element so that they're inherited to sub-elements
94
+ contents = Nokogiri::XML::Document.parse("<foo #{ns_strs.join(" ")}>#{contents}</foo>").root.children
95
+ end
96
+
97
+ # Add already mapped namespaces and language
98
+ contents.map do |c|
99
+ if c.is_a?(Nokogiri::XML::Element)
100
+ c = Nokogiri::XML.parse(c.dup.to_xml(:save_with => Nokogiri::XML::Node::SaveOptions::NO_EMPTY_TAGS)).root
101
+ # Gather namespaces from self and decendant nodes
102
+ c.traverse do |n|
103
+ ns = n.namespace
104
+ next unless ns
105
+ prefix = ns.prefix ? "xmlns:#{ns.prefix}" : "xmlns"
106
+ c[prefix] = ns.href.to_s unless c.namespaces[prefix]
107
+ end
108
+
109
+ # Add lanuage
110
+ if options[:language] && c["lang"].to_s.empty?
111
+ c["xml:lang"] = options[:language]
112
+ end
113
+ end
114
+ c.to_xml(:save_with => Nokogiri::XML::Node::SaveOptions::NO_EMPTY_TAGS)
115
+ end.join("")
116
+ end
117
+ end
118
+
119
+ class NormalizationError < IOError; end
120
+ end