rdf-rdfa 0.0.1

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.
Files changed (128) hide show
  1. data/History.txt +2 -0
  2. data/README.rdoc +59 -0
  3. data/Rakefile +46 -0
  4. data/VERSION +1 -0
  5. data/example.rb +27 -0
  6. data/lib/rdfa/format.rb +19 -0
  7. data/lib/rdfa/reader.rb +798 -0
  8. data/lib/rdfa/reader/exceptions.rb +14 -0
  9. data/lib/rdfa/reader/namespace.rb +72 -0
  10. data/lib/rdfa/reader/version.rb +23 -0
  11. data/spec/namespaces_spec.rb +112 -0
  12. data/spec/rdfa-triples/0001.nt +1 -0
  13. data/spec/rdfa-triples/0006.nt +2 -0
  14. data/spec/rdfa-triples/0007.nt +3 -0
  15. data/spec/rdfa-triples/0008.nt +1 -0
  16. data/spec/rdfa-triples/0009.nt +1 -0
  17. data/spec/rdfa-triples/0010.nt +2 -0
  18. data/spec/rdfa-triples/0011.nt +3 -0
  19. data/spec/rdfa-triples/0012.nt +1 -0
  20. data/spec/rdfa-triples/0013.nt +1 -0
  21. data/spec/rdfa-triples/0014.nt +1 -0
  22. data/spec/rdfa-triples/0015.nt +2 -0
  23. data/spec/rdfa-triples/0017.nt +3 -0
  24. data/spec/rdfa-triples/0018.nt +1 -0
  25. data/spec/rdfa-triples/0019.nt +1 -0
  26. data/spec/rdfa-triples/0020.nt +1 -0
  27. data/spec/rdfa-triples/0021.nt +1 -0
  28. data/spec/rdfa-triples/0023.nt +1 -0
  29. data/spec/rdfa-triples/0025.nt +2 -0
  30. data/spec/rdfa-triples/0026.nt +1 -0
  31. data/spec/rdfa-triples/0027.nt +1 -0
  32. data/spec/rdfa-triples/0029.nt +1 -0
  33. data/spec/rdfa-triples/0030.nt +1 -0
  34. data/spec/rdfa-triples/0031.nt +1 -0
  35. data/spec/rdfa-triples/0032.nt +1 -0
  36. data/spec/rdfa-triples/0033.nt +2 -0
  37. data/spec/rdfa-triples/0034.nt +1 -0
  38. data/spec/rdfa-triples/0035.nt +1 -0
  39. data/spec/rdfa-triples/0036.nt +1 -0
  40. data/spec/rdfa-triples/0037.nt +1 -0
  41. data/spec/rdfa-triples/0038.nt +1 -0
  42. data/spec/rdfa-triples/0039.nt +1 -0
  43. data/spec/rdfa-triples/0040.nt +1 -0
  44. data/spec/rdfa-triples/0041.nt +1 -0
  45. data/spec/rdfa-triples/0042.nt +0 -0
  46. data/spec/rdfa-triples/0046.nt +3 -0
  47. data/spec/rdfa-triples/0047.nt +3 -0
  48. data/spec/rdfa-triples/0048.nt +3 -0
  49. data/spec/rdfa-triples/0049.nt +2 -0
  50. data/spec/rdfa-triples/0050.nt +2 -0
  51. data/spec/rdfa-triples/0051.nt +2 -0
  52. data/spec/rdfa-triples/0052.nt +1 -0
  53. data/spec/rdfa-triples/0053.nt +2 -0
  54. data/spec/rdfa-triples/0054.nt +2 -0
  55. data/spec/rdfa-triples/0055.nt +2 -0
  56. data/spec/rdfa-triples/0056.nt +3 -0
  57. data/spec/rdfa-triples/0057.nt +4 -0
  58. data/spec/rdfa-triples/0058.nt +6 -0
  59. data/spec/rdfa-triples/0059.nt +6 -0
  60. data/spec/rdfa-triples/0060.nt +2 -0
  61. data/spec/rdfa-triples/0061.nt +1 -0
  62. data/spec/rdfa-triples/0062.nt +1 -0
  63. data/spec/rdfa-triples/0063.nt +1 -0
  64. data/spec/rdfa-triples/0064.nt +1 -0
  65. data/spec/rdfa-triples/0065.nt +3 -0
  66. data/spec/rdfa-triples/0066.nt +1 -0
  67. data/spec/rdfa-triples/0067.nt +1 -0
  68. data/spec/rdfa-triples/0068.nt +1 -0
  69. data/spec/rdfa-triples/0069.nt +1 -0
  70. data/spec/rdfa-triples/0070.nt +1 -0
  71. data/spec/rdfa-triples/0071.nt +1 -0
  72. data/spec/rdfa-triples/0072.nt +1 -0
  73. data/spec/rdfa-triples/0073.nt +1 -0
  74. data/spec/rdfa-triples/0074.nt +1 -0
  75. data/spec/rdfa-triples/0075.nt +1 -0
  76. data/spec/rdfa-triples/0076.nt +23 -0
  77. data/spec/rdfa-triples/0077.nt +23 -0
  78. data/spec/rdfa-triples/0078.nt +6 -0
  79. data/spec/rdfa-triples/0079.nt +3 -0
  80. data/spec/rdfa-triples/0080.nt +1 -0
  81. data/spec/rdfa-triples/0081.nt +6 -0
  82. data/spec/rdfa-triples/0082.nt +8 -0
  83. data/spec/rdfa-triples/0083.nt +6 -0
  84. data/spec/rdfa-triples/0084.nt +8 -0
  85. data/spec/rdfa-triples/0085.nt +4 -0
  86. data/spec/rdfa-triples/0086.nt +0 -0
  87. data/spec/rdfa-triples/0087.nt +23 -0
  88. data/spec/rdfa-triples/0088.nt +3 -0
  89. data/spec/rdfa-triples/0089.nt +1 -0
  90. data/spec/rdfa-triples/0090.nt +1 -0
  91. data/spec/rdfa-triples/0091.nt +3 -0
  92. data/spec/rdfa-triples/0092.nt +3 -0
  93. data/spec/rdfa-triples/0093.nt +2 -0
  94. data/spec/rdfa-triples/0094.nt +3 -0
  95. data/spec/rdfa-triples/0099.nt +1 -0
  96. data/spec/rdfa-triples/0100.nt +3 -0
  97. data/spec/rdfa-triples/0101.nt +3 -0
  98. data/spec/rdfa-triples/0102.nt +1 -0
  99. data/spec/rdfa-triples/0103.nt +1 -0
  100. data/spec/rdfa-triples/0104.nt +3 -0
  101. data/spec/rdfa-triples/0105.nt +1 -0
  102. data/spec/rdfa-triples/0106.nt +1 -0
  103. data/spec/rdfa-triples/0107.nt +0 -0
  104. data/spec/rdfa-triples/0108.nt +1 -0
  105. data/spec/rdfa-triples/0109.nt +1 -0
  106. data/spec/rdfa-triples/0110.nt +1 -0
  107. data/spec/rdfa-triples/0111.nt +2 -0
  108. data/spec/rdfa-triples/0112.nt +1 -0
  109. data/spec/rdfa-triples/0113.nt +2 -0
  110. data/spec/rdfa-triples/0114.nt +3 -0
  111. data/spec/rdfa-triples/0115.nt +4 -0
  112. data/spec/rdfa-triples/0116.nt +2 -0
  113. data/spec/rdfa-triples/0117.nt +2 -0
  114. data/spec/rdfa-triples/0118.nt +1 -0
  115. data/spec/rdfa-triples/0119.nt +1 -0
  116. data/spec/rdfa-triples/0120.nt +1 -0
  117. data/spec/rdfa-triples/0121.nt +2 -0
  118. data/spec/rdfa-triples/0122.nt +1 -0
  119. data/spec/rdfa-triples/0123.nt +3 -0
  120. data/spec/rdfa-triples/0124.nt +4 -0
  121. data/spec/rdfa-triples/0125.nt +1 -0
  122. data/spec/rdfa-triples/0126.nt +3 -0
  123. data/spec/rdfa-triples/1001.nt +6 -0
  124. data/spec/rdfa_helper.rb +188 -0
  125. data/spec/rdfa_parser_spec.rb +146 -0
  126. data/spec/spec.opts +1 -0
  127. data/spec/spec_helper.rb +8 -0
  128. metadata +246 -0
@@ -0,0 +1,2 @@
1
+ === 0.0.1
2
+ First port from RdfContext version 0.5.4
@@ -0,0 +1,59 @@
1
+ = RDF::RDFa reader/writer
2
+
3
+ RDFa parser for RDF.rb.
4
+
5
+ == DESCRIPTION:
6
+
7
+ RDF::RDFa is an RDFa parser for Ruby using the RDF.rb library suite.
8
+
9
+ == FEATURES:
10
+ RDF::RDFa parses RDFa into a Graph object.
11
+
12
+ * Fully compliant XHTML/RDFa 1.0 parser.
13
+
14
+ Install with 'gem install rdf-rdfa'
15
+
16
+ == Usage:
17
+ Instantiate a parser and parse source, specifying type and base-URL
18
+
19
+ use RDF::RDFa
20
+ p = Parser.new
21
+ graph = p.parse(input, "http://example.com")
22
+
23
+ == Resources:
24
+ * Distiller[http://kellogg-assoc/distiller]
25
+ * RDoc[http://rdoc.info/projects/gkellogg/rdf-rdfa]
26
+ * History[http://github.com/gkellogg/rdf-rdfa/blob/master/History.txt]
27
+
28
+ == LICENSE:
29
+
30
+ (The MIT License)
31
+
32
+ Copyright (c) 2009-2010 Gregg Kellogg
33
+
34
+ Permission is hereby granted, free of charge, to any person obtaining
35
+ a copy of this software and associated documentation files (the
36
+ 'Software'), to deal in the Software without restriction, including
37
+ without limitation the rights to use, copy, modify, merge, publish,
38
+ distribute, sublicense, and/or sell copies of the Software, and to
39
+ permit persons to whom the Software is furnished to do so, subject to
40
+ the following conditions:
41
+
42
+ The above copyright notice and this permission notice shall be
43
+ included in all copies or substantial portions of the Software.
44
+
45
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
46
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
47
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
48
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
49
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
50
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
51
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
52
+
53
+ == FEEDBACK
54
+
55
+ * gregg@kellogg-assoc.com
56
+ * rubygems.org/rdf-rdfa
57
+ * github.com/gkellogg/rdf-rdfa
58
+ * gkellogg.lighthouseapp.com for bug reports
59
+ * public-rdf-ruby mailing list on w3.org
@@ -0,0 +1,46 @@
1
+ require 'rubygems'
2
+
3
+ begin
4
+ gem 'jeweler'
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gemspec|
7
+ gemspec.name = "rdf-rdfa"
8
+ gemspec.summary = "RDFa parser for RDF.rb."
9
+ gemspec.description = <<-DESCRIPTION
10
+ RDF::RDFa is an RDFa parser for Ruby using the RDF.rb library suite.
11
+ DESCRIPTION
12
+ gemspec.email = "gregg@kellogg-assoc.com"
13
+ gemspec.homepage = "http://github.com/gkellogg/rdf-rdfa"
14
+ gemspec.authors = ["Gregg Kellogg", "Nicholas Humfrey"]
15
+ gemspec.add_dependency('nokogiri', '>= 1.3.3')
16
+ gemspec.add_dependency('rdf', '>= 0.1.6')
17
+ gemspec.add_development_dependency('rspec')
18
+ gemspec.add_development_dependency('activesupport', '>= 2.3.0')
19
+ gemspec.extra_rdoc_files = %w(README.rdoc History.txt)
20
+ end
21
+ Jeweler::GemcutterTasks.new
22
+ rescue LoadError
23
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
24
+ end
25
+
26
+ require 'spec/rake/spectask'
27
+ Spec::Rake::SpecTask.new(:spec) do |spec|
28
+ spec.libs << 'lib' << 'spec'
29
+ spec.spec_files = FileList['spec/*_spec.rb']
30
+ end
31
+
32
+ desc "Run specs through RCov"
33
+ Spec::Rake::SpecTask.new("spec:rcov") do |spec|
34
+ spec.libs << 'lib' << 'spec'
35
+ spec.pattern = 'spec/*_spec.rb'
36
+ spec.rcov = true
37
+ end
38
+
39
+ desc "Generate HTML report specs"
40
+ Spec::Rake::SpecTask.new("doc:spec") do |spec|
41
+ spec.libs << 'lib' << 'spec'
42
+ spec.spec_files = FileList['spec/*_spec.rb']
43
+ spec.spec_opts = ["--format", "html:doc/spec.html"]
44
+ end
45
+
46
+ task :default => :spec
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift(File.join(File.dirname(__FILE__), 'lib'))
4
+
5
+ require 'rubygems'
6
+ require 'rdfa/reader'
7
+
8
+ data = <<-EOF;
9
+ <?xml version="1.0" encoding="UTF-8"?>
10
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML+RDFa 1.0//EN" "http://www.w3.org/MarkUp/DTD/xhtml-rdfa-1.dtd">
11
+ <html xmlns="http://www.w3.org/1999/xhtml"
12
+ xmlns:dc="http://purl.org/dc/elements/1.1/">
13
+ <head>
14
+ <title>Test 0001</title>
15
+ </head>
16
+ <body>
17
+ <p>This photo was taken by <span class="author" about="photo1.jpg" property="dc:creator">Mark Birbeck</span>.</p>
18
+ </body>
19
+ </html>
20
+ EOF
21
+
22
+ $DEBUG = false
23
+
24
+ reader = RDFa::Reader.new(data, :debug => false)
25
+ reader.each_statement do |statement|
26
+ statement.inspect!
27
+ end
@@ -0,0 +1,19 @@
1
+ module RDF::RDFa
2
+ ##
3
+ # N-Triples format specification.
4
+ #
5
+ # @example Obtaining an NTriples format class
6
+ # RDF::Format.for(:ntriples) #=> RDF::NTriples::Format
7
+ # RDF::Format.for("etc/doap.nt")
8
+ # RDF::Format.for(:file_name => "etc/doap.nt")
9
+ # RDF::Format.for(:file_extension => "nt")
10
+ # RDF::Format.for(:content_type => "text/plain")
11
+ #
12
+ # @see http://www.w3.org/TR/rdf-testcases/#ntriples
13
+ class Format < RDF::Format
14
+ content_type 'text/html', :extension => :html
15
+ content_encoding 'ascii'
16
+
17
+ reader { RDFa::Reader }
18
+ end
19
+ end
@@ -0,0 +1,798 @@
1
+ require 'nokogiri'
2
+ require 'rdf'
3
+
4
+ module RDF::RDFa
5
+ ##
6
+ # An RDFa parser in Ruby
7
+ #
8
+ # Based on processing rules described here:
9
+ # file:///Users/gregg/Projects/rdf_context/RDFa%20Core%201.1.html#sequence
10
+ #
11
+ # Ben Adida
12
+ # 2008-05-07
13
+ # Gregg Kellogg
14
+ # 2009-08-04
15
+ class Reader < RDF::Reader
16
+ autoload :Namespace, 'rdfa/reader/namespace'
17
+ autoload :VERSION, 'rdfa/reader/version'
18
+
19
+ NC_REGEXP = Regexp.new(
20
+ %{^
21
+ (?!\\\\u0301) # &#x301; is a non-spacing acute accent.
22
+ # It is legal within an XML Name, but not as the first character.
23
+ ( [a-zA-Z_]
24
+ | \\\\u[0-9a-fA-F]
25
+ )
26
+ ( [0-9a-zA-Z_\.-]
27
+ | \\\\u([0-9a-fA-F]{4})
28
+ )*
29
+ $},
30
+ Regexp::EXTENDED)
31
+
32
+ #XML_LITERAL = Literal::Encoding.xmlliteral
33
+ XML_LITERAL = RDF['XMLLiteral']
34
+
35
+
36
+ # FIXME: use RDF::URI.qname instead
37
+ RDF_NS = Namespace.new("http://www.w3.org/1999/02/22-rdf-syntax-ns#", "rdf")
38
+ RDFA_NS = Namespace.new("http://www.w3.org/ns/rdfa#", "rdfa")
39
+ RDFS_NS = Namespace.new("http://www.w3.org/2000/01/rdf-schema#", "rdfs")
40
+ XHV_NS = Namespace.new("http://www.w3.org/1999/xhtml/vocab#", "xhv")
41
+ XML_NS = Namespace.new("http://www.w3.org/XML/1998/namespace", "xml")
42
+ XSD_NS = Namespace.new("http://www.w3.org/2001/XMLSchema#", "xsd")
43
+ XSI_NS = Namespace.new("http://www.w3.org/2001/XMLSchema-instance", "xsi")
44
+ XH_MAPPING = {"" => Namespace.new("http://www.w3.org/1999/xhtml/vocab\#", nil)}
45
+
46
+
47
+ require 'rdfa/format'
48
+ format RDFa::Format
49
+
50
+ attr_reader :debug
51
+
52
+ ##
53
+ # @return [RDF::Graph]
54
+ attr_reader :graph
55
+
56
+ # Host language, One of:
57
+ # :xhtml_rdfa_1_0
58
+ # :xhtml_rdfa_1_1
59
+ attr_reader :host_language
60
+
61
+ # The Recursive Baggage
62
+ class EvaluationContext # :nodoc:
63
+ # The base. This will usually be the URL of the document being processed,
64
+ # but it could be some other URL, set by some other mechanism,
65
+ # such as the (X)HTML base element. The important thing is that it establishes
66
+ # a URL against which relative paths can be resolved.
67
+ attr :base, true
68
+ # The parent subject.
69
+ # The initial value will be the same as the initial value of base,
70
+ # but it will usually change during the course of processing.
71
+ attr :parent_subject, true
72
+ # The parent object.
73
+ # In some situations the object of a statement becomes the subject of any nested statements,
74
+ # and this property is used to convey this value.
75
+ # Note that this value may be a bnode, since in some situations a number of nested statements
76
+ # are grouped together on one bnode.
77
+ # This means that the bnode must be set in the containing statement and passed down,
78
+ # and this property is used to convey this value.
79
+ attr :parent_object, true
80
+ # A list of current, in-scope URI mappings.
81
+ attr :uri_mappings, true
82
+ # A list of incomplete triples. A triple can be incomplete when no object resource
83
+ # is provided alongside a predicate that requires a resource (i.e., @rel or @rev).
84
+ # The triples can be completed when a resource becomes available,
85
+ # which will be when the next subject is specified (part of the process called chaining).
86
+ attr :incomplete_triples, true
87
+ # The language. Note that there is no default language.
88
+ attr :language, true
89
+ # The term mappings, a list of terms and their associated URIs.
90
+ # This specification does not define an initial list.
91
+ # Host Languages may define an initial list.
92
+ # If a Host Language provides an initial list, it should do so via an RDFa Profile document.
93
+ attr :term_mappings, true
94
+ # The default vocabulary, a value to use as the prefix URI when a term is used.
95
+ # This specification does not define an initial setting for the default vocabulary.
96
+ # Host Languages may define an initial setting.
97
+ attr :default_vocabulary, true
98
+
99
+ def initialize(base, host_defaults)
100
+ # Initialize the evaluation context, [5.1]
101
+ @base = base
102
+ @parent_subject = @base
103
+ @parent_object = nil
104
+ @uri_mappings = {}
105
+ @incomplete_triples = []
106
+ @language = nil
107
+ @term_mappings = host_defaults.fetch(:term_mappings, {})
108
+ @default_voabulary = host_defaults.fetch(:voabulary, nil)
109
+ end
110
+
111
+ # Copy this Evaluation Context
112
+ def initialize_copy(from)
113
+ # clone the evaluation context correctly
114
+ @uri_mappings = from.uri_mappings.clone
115
+ @incomplete_triples = from.incomplete_triples.clone
116
+ end
117
+
118
+ def inspect
119
+ v = %w(base parent_subject parent_object language default_vocabulary).map {|a| "#{a}='#{self.send(a).nil? ? '<nil>' : self.send(a)}'"}
120
+ v << "uri_mappings[#{uri_mappings.keys.length}]"
121
+ v << "incomplete_triples[#{incomplete_triples.length}]"
122
+ v << "term_mappings[#{term_mappings.keys.length}]"
123
+ v.join(",")
124
+ end
125
+ end
126
+
127
+
128
+
129
+ # Parse XHTML+RDFa document from a string or input stream to closure or graph.
130
+ #
131
+ # If the parser is called with a block, triples are passed to the block rather
132
+ # than added to the graph.
133
+ #
134
+ # Optionally, the stream may be a Nokogiri::HTML::Document or Nokogiri::XML::Document
135
+ # With a block, yeilds each statement with URI, BNode or Literal elements
136
+ #
137
+ # @param [IO] stream:: the HTML+RDFa IO stream, string, Nokogiri::HTML::Document or Nokogiri::XML::Document
138
+ # @param [String] uri:: the URI of the document
139
+ # @param [Hash] options:: Parser options, one of
140
+ # <em>options[:debug]</em>:: Array to place debug messages
141
+ # <em>options[:strict]</em>:: Raise Error if true, continue with lax parsing, otherwise
142
+ # @return [Graph]:: Returns the graph containing parsed triples
143
+ # @raise [Error]:: Raises RdfError if _strict_
144
+
145
+ ##
146
+ # Initializes the RDFa reader instance.
147
+ #
148
+ # @param [IO, File, String] input
149
+ # @param [Hash{Symbol => Object}] options
150
+ # @yield [reader]
151
+ # @yieldparam [Reader] reader
152
+ def initialize(input = $stdin, options = {}, &block)
153
+ super
154
+
155
+ @graph = RDF::Graph.new
156
+ @debug = options[:debug]
157
+ @strict = options[:strict]
158
+ @base_uri = options[:base_uri]
159
+ @base_uri = RDF::URI.parse(@base_uri) if @base_uri.is_a?(String)
160
+ @named_bnodes = {}
161
+ @@vocabulary_cache ||= {}
162
+ @nsbinding = {}
163
+ @uri_binding = {}
164
+
165
+ @doc = case input
166
+ when Nokogiri::HTML::Document then input
167
+ when Nokogiri::XML::Document then input
168
+ else Nokogiri::XML.parse(input, @base_uri.to_s)
169
+ end
170
+
171
+ raise ParserException, "Empty document" if @doc.nil? && @strict
172
+ @callback = block
173
+
174
+ # Determine host language
175
+ # XXX - right now only XHTML defined
176
+ @host_language = case @doc.root.attributes["version"].to_s
177
+ when /XHTML+RDFa/ then :xhtml
178
+ end
179
+
180
+ # If none found, assume xhtml
181
+ @host_language ||= :xhtml
182
+
183
+ @host_defaults = {}
184
+ @host_defaults = case @host_language
185
+ when :xhtml
186
+ bind(XHV_NS)
187
+ {
188
+ :vocabulary => XHV_NS.uri,
189
+ :prefix => XHV_NS,
190
+ :term_mappings => %w(
191
+ alternate appendix bookmark cite chapter contents copyright first glossary help icon index
192
+ last license meta next p3pv1 prev role section stylesheet subsection start top up
193
+ ).inject({}) { |hash, term| hash[term] = XHV_NS.send("#{term}_"); hash },
194
+ }
195
+ else
196
+ {}
197
+ end
198
+
199
+ # parse
200
+ parse_whole_document(@doc, @base_uri)
201
+
202
+ block.call(self) if block_given?
203
+ end
204
+
205
+
206
+ ##
207
+ # Iterates the given block for each RDF statement in the input.
208
+ #
209
+ # @yield [statement]
210
+ # @yieldparam [RDF::Statement] statement
211
+ # @return [void]
212
+ def each_statement(&block)
213
+ @graph.each_statement(&block)
214
+ end
215
+
216
+ ##
217
+ # Iterates the given block for each RDF triple in the input.
218
+ #
219
+ # @yield [subject, predicate, object]
220
+ # @yieldparam [RDF::Resource] subject
221
+ # @yieldparam [RDF::URI] predicate
222
+ # @yieldparam [RDF::Value] object
223
+ # @return [void]
224
+ def each_triple(&block)
225
+ @graph.each_triple(&block)
226
+ end
227
+
228
+ # Bind namespace to store, returns bound namespace
229
+ def bind(namespace)
230
+ # Over-write an empty prefix
231
+ uri = namespace.uri.to_s
232
+ @uri_binding.delete(uri)
233
+ @nsbinding.delete_if {|prefix, ns| namespace.prefix == prefix}
234
+
235
+ @uri_binding[uri] = namespace
236
+ @nsbinding[namespace.prefix.to_s] = namespace
237
+ end
238
+
239
+ # Namespace for prefix
240
+ def namespace(prefix)
241
+ @nsbinding[prefix.to_s]
242
+ end
243
+
244
+ # Prefix for namespace
245
+ def prefix(namespace)
246
+ namespace.is_a?(Namespace) ? @uri_binding[namespace.uri.to_s].prefix : @uri_binding[namespace].prefix
247
+ end
248
+
249
+ private
250
+
251
+ # Figure out the document path, if it is a Nokogiri::XML::Element or Attribute
252
+ def node_path(node)
253
+ case node
254
+ when Nokogiri::XML::Element, Nokogiri::XML::Attr then "#{node_path(node.parent)}/#{node.name}"
255
+ when String then node
256
+ else ""
257
+ end
258
+ end
259
+
260
+ # Add debug event to debug array, if specified
261
+ #
262
+ # @param [XML Node, any] node:: XML Node or string for showing context
263
+ # @param [String] message::
264
+ def add_debug(node, message)
265
+ puts "#{node_path(node)}: #{message}" if $DEBUG
266
+ @debug << "#{node_path(node)}: #{message}" if @debug.is_a?(Array)
267
+ end
268
+
269
+ # add a triple, object can be literal or URI or bnode
270
+ #
271
+ # If the parser is called with a block, triples are passed to the block rather
272
+ # than added to the graph.
273
+ #
274
+ # @param [Nokogiri::XML::Node, any] node:: XML Node or string for showing context
275
+ # @param [URI, BNode] subject:: the subject of the triple
276
+ # @param [URI] predicate:: the predicate of the triple
277
+ # @param [URI, BNode, Literal] object:: the object of the triple
278
+ # @return [Array]:: An array of the triples (leaky abstraction? consider returning the graph instead)
279
+ # @raise [Error]:: Checks parameter types and raises if they are incorrect if parsing mode is _strict_.
280
+ def add_triple(node, subject, predicate, object)
281
+ triple = RDF::Statement.new(subject, predicate, object)
282
+ add_debug(node, "triple: #{triple}")
283
+ if @callback
284
+ @callback.call(triple) # Perform yield to saved block
285
+ else
286
+ @graph << triple
287
+ end
288
+ triple
289
+ # FIXME: rescue RdfException => e
290
+ rescue Exception => e
291
+ add_debug(node, "add_triple raised #{e.class}: #{e.message}")
292
+ puts e.backtrace if $DEBUG
293
+ raise if @strict
294
+ end
295
+
296
+
297
+ # Parsing an RDFa document (this is *not* the recursive method)
298
+ def parse_whole_document(doc, base)
299
+ # find if the document has a base element
300
+ # XXX - HTML specific
301
+ base_el = doc.css('html>head>base').first
302
+ if (base_el)
303
+ base = base_el.attributes['href']
304
+ # Strip any fragment from base
305
+ base = base.to_s.split("#").first
306
+ @base_uri = RDF::URI.new(base)
307
+ add_debug(base_el, "parse_whole_doc: base='#{base}'")
308
+ end
309
+
310
+ # initialize the evaluation context with the appropriate base
311
+ evaluation_context = EvaluationContext.new(base, @host_defaults)
312
+
313
+ traverse(doc.root, evaluation_context)
314
+ end
315
+
316
+ # Extract the XMLNS mappings from an element
317
+ def extract_mappings(element, uri_mappings, term_mappings)
318
+ # Process @profile
319
+ # Next the current element is parsed for any updates to the local term mappings and
320
+ # local list of URI mappings via @profile.
321
+ # If @profile is present, its value is processed as defined in RDFa Profiles.
322
+ element.attributes['profile'].to_s.split(/\s/).each do |profile|
323
+ # Don't try to open ourselves!
324
+ if @base_uri == profile
325
+ add_debug(element, "extract_mappings: skip recursive profile <#{profile}>")
326
+ @@vocabulary_cache[profile]
327
+ elsif @@vocabulary_cache.has_key?(profile)
328
+ add_debug(element, "extract_mappings: skip previously parsed profile <#{profile}>")
329
+ else
330
+ begin
331
+ add_debug(element, "extract_mappings: parse profile <#{profile}>")
332
+ @@vocabulary_cache[profile] = {
333
+ :uri_mappings => {},
334
+ :term_mappings => {}
335
+ }
336
+ um = @@vocabulary_cache[profile][:uri_mappings]
337
+ tm = @@vocabulary_cache[profile][:term_mappings]
338
+ add_debug(element, "extract_mappings: profile open <#{profile}>")
339
+ require 'patron' unless defined?(Patron)
340
+ sess = Patron::Session.new
341
+ sess.timeout = 10
342
+ resp = sess.get(profile)
343
+ raise RuntimeError, "HTTP returned status #{resp.status} when reading #{profile}" if resp.status >= 400
344
+
345
+ # Parse profile, and extract mappings from graph
346
+ old_debug, old_verbose, = $DEBUG, $verbose
347
+ $DEBUG, $verbose = false, false
348
+ p_graph = Parser.parse(resp.body, profile)
349
+ ttl = p_graph.serialize(:format => :ttl) if @debug || $DEBUG
350
+ $DEBUG, $verbose = old_debug, old_verbose
351
+ add_debug(element, ttl) if ttl
352
+ p_graph.subjects.each do |subject|
353
+ props = p_graph.properties(subject)
354
+ #puts props.inspect
355
+
356
+ # If one of the objects is not a Literal or if there are additional rdfa:uri or rdfa:term
357
+ # predicates sharing the same subject, no mapping is created.
358
+ uri = props[RDFA_NS.uri.to_s]
359
+ term = props[RDFA_NS.term_.to_s]
360
+ prefix = props[RDFA_NS.prefix_.to_s]
361
+ add_debug(element, "extract_mappings: uri=#{uri.inspect}, term=#{term.inspect}, prefix=#{prefix.inspect}")
362
+
363
+ next if !uri || (!term && !prefix)
364
+ raise ParserException, "multi-valued rdf:uri" if uri.length != 1
365
+ raise ParserException, "multi-valued rdf:term." if term && term.length != 1
366
+ raise ParserException, "multi-valued rdf:prefix" if prefix && prefix.length != 1
367
+
368
+ uri = uri.first
369
+ term = term.first if term
370
+ prefix = prefix.first if prefix
371
+ raise ParserException, "rdf:uri must be a Literal" unless uri.is_a?(Literal)
372
+ raise ParserException, "rdf:term must be a Literal" unless term.nil? || term.is_a?(Literal)
373
+ raise ParserException, "rdf:prefix must be a Literal" unless prefix.nil? || prefix.is_a?(Literal)
374
+
375
+ # For every extracted triple that is the common subject of an rdfa:prefix and an rdfa:uri
376
+ # predicate, create a mapping from the object literal of the rdfa:prefix predicate to the
377
+ # object literal of the rdfa:uri predicate. Add or update this mapping in the local list of
378
+ # URI mappings after transforming the 'prefix' component to lower-case.
379
+ # For every extracted
380
+ um[prefix.to_s.downcase] = bind(Namespace.new(uri.to_s, prefix.to_s.downcase)) if prefix
381
+
382
+ # triple that is the common subject of an rdfa:term and an rdfa:uri predicate, create a
383
+ # mapping from the object literal of the rdfa:term predicate to the object literal of the
384
+ # rdfa:uri predicate. Add or update this mapping in the local term mappings.
385
+ tm[term.to_s] = RDF::URI.new(uri.to_s) if term
386
+ end
387
+ rescue ParserException
388
+ add_debug(element, "extract_mappings: profile subject #{subject.to_s}: #{e.message}")
389
+ raise if @strict
390
+ rescue RuntimeError => e
391
+ add_debug(element, "extract_mappings: profile: #{e.message}")
392
+ raise if @strict
393
+ end
394
+ end
395
+
396
+ # Merge mappings from this vocabulary
397
+ uri_mappings.merge!(@@vocabulary_cache[profile][:uri_mappings])
398
+ term_mappings.merge!(@@vocabulary_cache[profile][:term_mappings])
399
+ end
400
+
401
+ # look for xmlns
402
+ # (note, this may be dependent on @host_language)
403
+ # Regardless of how the mapping is declared, the value to be mapped must be converted to lower case,
404
+ # and the URI is not processed in any way; in particular if it is a relative path it is
405
+ # not resolved against the current base.
406
+ element.namespaces.each do |attr_name, attr_value|
407
+ begin
408
+ abbr, prefix = attr_name.split(":")
409
+ uri_mappings[prefix.to_s.downcase] = bind(Namespace.new(attr_value, prefix.to_s.downcase)) if abbr.downcase == "xmlns" && prefix
410
+ # FIXME: rescue RdfException => e
411
+ rescue Exception => e
412
+ add_debug(element, "extract_mappings raised #{e.class}: #{e.message}")
413
+ raise if @strict
414
+ end
415
+ end
416
+
417
+ # Set mappings from @prefix
418
+ # prefix is a whitespace separated list of prefix-name URI pairs of the form
419
+ # NCName ':' ' '+ xs:anyURI
420
+ # SPEC Confusion: prefix is forced to lower-case in @profile, but not specified here.
421
+ mappings = element.attributes["prefix"].to_s.split(/\s+/)
422
+ while mappings.length > 0 do
423
+ prefix, uri = mappings.shift.downcase, mappings.shift
424
+ #puts "uri_mappings prefix #{prefix} <#{uri}>"
425
+ next unless prefix.match(/:$/)
426
+ prefix.chop!
427
+
428
+ uri_mappings[prefix] = bind(Namespace.new(uri, prefix))
429
+ end
430
+
431
+ add_debug(element, "uri_mappings: #{uri_mappings.values.map{|ns|ns.to_s}.join(", ")}")
432
+ add_debug(element, "term_mappings: #{term_mappings.keys.join(", ")}")
433
+ end
434
+
435
+ # The recursive helper function
436
+ def traverse(element, evaluation_context)
437
+ if element.nil?
438
+ add_debug(element, "traverse nil element")
439
+ raise ParserException, "Can't parse nil element" if @strict
440
+ return nil
441
+ end
442
+
443
+ add_debug(element, "traverse, ec: #{evaluation_context.inspect}")
444
+
445
+ # local variables [5.5 Step 1]
446
+ recurse = true
447
+ skip = false
448
+ new_subject = nil
449
+ current_object_resource = nil
450
+ uri_mappings = evaluation_context.uri_mappings.clone
451
+ incomplete_triples = []
452
+ language = evaluation_context.language
453
+ term_mappings = evaluation_context.term_mappings.clone
454
+ default_vocabulary = evaluation_context.default_vocabulary
455
+
456
+ current_object_literal = nil # XXX Not explicit
457
+
458
+ # shortcut
459
+ attrs = element.attributes
460
+
461
+ about = attrs['about']
462
+ src = attrs['src']
463
+ resource = attrs['resource']
464
+ href = attrs['href']
465
+ vocab = attrs['vocab']
466
+
467
+ # Pull out the attributes needed for the skip test.
468
+ property = attrs['property'].to_s if attrs['property']
469
+ typeof = attrs['typeof'].to_s if attrs['typeof']
470
+ datatype = attrs['datatype'].to_s if attrs['datatype']
471
+ content = attrs['content'].to_s if attrs['content']
472
+ rel = attrs['rel'].to_s if attrs['rel']
473
+ rev = attrs['rev'].to_s if attrs['rev']
474
+
475
+ # Default vocabulary [7.5 Step 2]
476
+ # First the current element is examined for any change to the default vocabulary via @vocab.
477
+ # If @vocab is present and contains a value, its value updates the local default vocabulary.
478
+ # If the value is empty, then the local default vocabulary must be reset to the Host Language defined default.
479
+ unless vocab.nil?
480
+ default_vocabulary = if vocab.to_s.empty?
481
+ # Set default_vocabulary to host language default
482
+ @host_defaults.fetch(:voabulary, nil)
483
+ else
484
+ vocab.to_s
485
+ end
486
+ add_debug(element, "[Step 2] traverse, default_vocaulary: #{default_vocabulary.inspect}")
487
+ end
488
+
489
+ # Local term mappings [7.5 Steps 3 & 4]
490
+ # Next the current element is parsed for any updates to the local term mappings and local list of URI mappings via @profile.
491
+ # If @profile is present, its value is processed as defined in RDFa Profiles.
492
+ extract_mappings(element, uri_mappings, term_mappings)
493
+
494
+ # Language information [7.5 Step 5]
495
+ # From HTML5 [3.2.3.3]
496
+ # If both the lang attribute in no namespace and the lang attribute in the XML namespace are set
497
+ # on an element, user agents must use the lang attribute in the XML namespace, and the lang
498
+ # attribute in no namespace must be ignored for the purposes of determining the element's
499
+ # language.
500
+ language = case
501
+ when element.at_xpath("@xml:lang", "xml" => XML_NS.uri.to_s)
502
+ element.at_xpath("@xml:lang", "xml" => XML_NS.uri.to_s).to_s
503
+ when element.at_xpath("lang")
504
+ element.at_xpath("lang").to_s
505
+ else
506
+ language
507
+ end
508
+ add_debug(element, "HTML5 [3.2.3.3] traverse, lang: #{language}") if attrs['lang']
509
+
510
+ # rels and revs
511
+ rels = process_uris(element, rel, evaluation_context, :uri_mappings => uri_mappings, :term_mappings => term_mappings, :vocab => default_vocabulary)
512
+ revs = process_uris(element, rev, evaluation_context, :uri_mappings => uri_mappings, :term_mappings => term_mappings, :vocab => default_vocabulary)
513
+
514
+ add_debug(element, "traverse, about: #{about.nil? ? 'nil' : about}, src: #{src.nil? ? 'nil' : src}, resource: #{resource.nil? ? 'nil' : resource}, href: #{href.nil? ? 'nil' : href}")
515
+ add_debug(element, "traverse, property: #{property.nil? ? 'nil' : property}, typeof: #{typeof.nil? ? 'nil' : typeof}, datatype: #{datatype.nil? ? 'nil' : datatype}, content: #{content.nil? ? 'nil' : content}")
516
+ add_debug(element, "traverse, rels: #{rels.join(" ")}, revs: #{revs.join(" ")}")
517
+
518
+ if !(rel || rev)
519
+ # Establishing a new subject if no rel/rev [7.5 Step 6]
520
+ # May not be valid, but can exist
521
+ if about
522
+ new_subject = process_uri(element, about, evaluation_context, :uri_mappings => uri_mappings)
523
+ elsif src
524
+ new_subject = process_uri(element, src, evaluation_context)
525
+ elsif resource
526
+ new_subject = process_uri(element, resource, evaluation_context, :uri_mappings => uri_mappings)
527
+ elsif href
528
+ new_subject = process_uri(element, href, evaluation_context)
529
+ end
530
+
531
+ # If no URI is provided by a resource attribute, then the first match from the following rules
532
+ # will apply:
533
+ # if @typeof is present, then new subject is set to be a newly created bnode.
534
+ # otherwise,
535
+ # if parent object is present, new subject is set to the value of parent object.
536
+ # Additionally, if @property is not present then the skip element flag is set to 'true';
537
+ if new_subject.nil?
538
+ if @host_language == :xhtml && element.name =~ /^(head|body)$/ && evaluation_context.base
539
+ # From XHTML+RDFa 1.1:
540
+ # if no URI is provided, then first check to see if the element is the head or body element.
541
+ # If it is, then act as if there is an empty @about present, and process it according to the rule for @about.
542
+ new_subject = RDF::URI.new(evaluation_context.base)
543
+ elsif element.attributes['typeof']
544
+ new_subject = RDF::Node.new
545
+ else
546
+ # if it's null, it's null and nothing changes
547
+ new_subject = evaluation_context.parent_object
548
+ skip = true unless property
549
+ end
550
+ end
551
+ add_debug(element, "[Step 6] new_subject: #{new_subject}, skip = #{skip}")
552
+ else
553
+ # [7.5 Step 7]
554
+ # If the current element does contain a @rel or @rev attribute, then the next step is to
555
+ # establish both a value for new subject and a value for current object resource:
556
+ if about
557
+ new_subject = process_uri(element, about, evaluation_context, :uri_mappings => uri_mappings)
558
+ elsif src
559
+ new_subject = process_uri(element, src, evaluation_context, :uri_mappings => uri_mappings)
560
+ end
561
+
562
+ # If no URI is provided then the first match from the following rules will apply
563
+ if new_subject.nil?
564
+ if @host_language == :xhtml && element.name =~ /^(head|body)$/
565
+ # From XHTML+RDFa 1.1:
566
+ # if no URI is provided, then first check to see if the element is the head or body element.
567
+ # If it is, then act as if there is an empty @about present, and process it according to the rule for @about.
568
+ new_subject = RDF::URI.new(evaluation_context.base)
569
+ elsif element.attributes['typeof']
570
+ new_subject = RDF::Node.new
571
+ else
572
+ # if it's null, it's null and nothing changes
573
+ new_subject = evaluation_context.parent_object
574
+ # no skip flag set this time
575
+ end
576
+ end
577
+
578
+ # Then the current object resource is set to the URI obtained from the first match from the following rules:
579
+ if resource
580
+ current_object_resource = process_uri(element, resource, evaluation_context, :uri_mappings => uri_mappings)
581
+ elsif href
582
+ current_object_resource = process_uri(element, href, evaluation_context)
583
+ end
584
+
585
+ add_debug(element, "[Step 7] new_subject: #{new_subject}, current_object_resource = #{current_object_resource.nil? ? 'nil' : current_object_resource}")
586
+ end
587
+
588
+ # Process @typeof if there is a subject [Step 8]
589
+ if new_subject and typeof
590
+ # Typeof is TERMorCURIEorURIs
591
+ types = process_uris(element, typeof, evaluation_context, :uri_mappings => uri_mappings, :term_mappings => term_mappings, :vocab => default_vocabulary)
592
+ add_debug(element, "typeof: #{typeof}")
593
+ types.each do |one_type|
594
+ add_triple(element, new_subject, RDF_TYPE, one_type)
595
+ end
596
+ end
597
+
598
+ # Generate triples with given object [Step 9]
599
+ if current_object_resource
600
+ rels.each do |r|
601
+ add_triple(element, new_subject, r, current_object_resource)
602
+ end
603
+
604
+ revs.each do |r|
605
+ add_triple(element, current_object_resource, r, new_subject)
606
+ end
607
+ elsif rel || rev
608
+ # Incomplete triples and bnode creation [Step 10]
609
+ add_debug(element, "[Step 10] incompletes: rels: #{rels}, revs: #{revs}")
610
+ current_object_resource = RDF::Node.new
611
+
612
+ rels.each do |r|
613
+ incomplete_triples << {:predicate => r, :direction => :forward}
614
+ end
615
+
616
+ revs.each do |r|
617
+ incomplete_triples << {:predicate => r, :direction => :reverse}
618
+ end
619
+ end
620
+
621
+ # Establish current object literal [Step 11]
622
+ if property
623
+ properties = process_uris(element, property, evaluation_context, :uri_mappings => uri_mappings, :term_mappings => term_mappings, :vocab => default_vocabulary)
624
+
625
+ # get the literal datatype
626
+ type = datatype
627
+ children_node_types = element.children.collect{|c| c.class}.uniq
628
+
629
+ # the following 3 IF clauses should be mutually exclusive. Written as is to prevent extensive indentation.
630
+ type_resource = process_uri(element, type, evaluation_context, :uri_mappings => uri_mappings, :term_mappings => term_mappings, :vocab => default_vocabulary) if type
631
+ if type and !type.empty? and (type_resource.to_s != XML_LITERAL.to_s)
632
+ # typed literal
633
+ add_debug(element, "[Step 11] typed literal")
634
+ current_object_literal = RDF::Literal.new(content || element.inner_text, :datatype => type_resource, :language => language)
635
+ elsif content or (children_node_types == [Nokogiri::XML::Text]) or (element.children.length == 0) or (type == '')
636
+ # plain literal
637
+ add_debug(element, "[Step 11] plain literal")
638
+ current_object_literal = RDF::Literal.new(content || element.inner_text, :language => language)
639
+ elsif children_node_types != [Nokogiri::XML::Text] and (type == nil or type_resource.to_s == XML_LITERAL.to_s)
640
+ # XML Literal
641
+ add_debug(element, "[Step 11] XML Literal: #{element.inner_html}")
642
+ current_object_literal = RDF::Literal.new(element.inner_html, :datatype => XML_LITERAL, :language => language, :namespaces => uri_mappings)
643
+ recurse = false
644
+ end
645
+
646
+ # add each property
647
+ properties.each do |p|
648
+ add_triple(element, new_subject, p, current_object_literal)
649
+ end
650
+ # SPEC CONFUSION: "the triple has been created" ==> there may be more than one
651
+ # set the recurse flag above in the IF about xmlliteral, as it is the only place that can happen
652
+ end
653
+
654
+ if not skip and new_subject && !evaluation_context.incomplete_triples.empty?
655
+ # Complete the incomplete triples from the evaluation context [Step 12]
656
+ add_debug(element, "[Step 12] complete incomplete triples: new_subject=#{new_subject}, completes=#{evaluation_context.incomplete_triples.inspect}")
657
+ evaluation_context.incomplete_triples.each do |trip|
658
+ if trip[:direction] == :forward
659
+ add_triple(element, evaluation_context.parent_subject, trip[:predicate], new_subject)
660
+ elsif trip[:direction] == :reverse
661
+ add_triple(element, new_subject, trip[:predicate], evaluation_context.parent_subject)
662
+ end
663
+ end
664
+ end
665
+
666
+ # Create a new evaluation context and proceed recursively [Step 13]
667
+ if recurse
668
+ if skip
669
+ if language == evaluation_context.language &&
670
+ uri_mappings == evaluation_context.uri_mappings &&
671
+ term_mappings == evaluation_context.term_mappings &&
672
+ default_vocabulary == evaluation_context.default_vocabulary &&
673
+ new_ec = evaluation_context
674
+ add_debug(element, "[Step 13] skip: reused ec")
675
+ else
676
+ new_ec = evaluation_context.clone
677
+ new_ec.language = language
678
+ new_ec.uri_mappings = uri_mappings
679
+ new_ec.term_mappings = term_mappings
680
+ new_ec.default_vocabulary = default_vocabulary
681
+ add_debug(element, "[Step 13] skip: cloned ec")
682
+ end
683
+ else
684
+ # create a new evaluation context
685
+ new_ec = EvaluationContext.new(evaluation_context.base, @host_defaults)
686
+ new_ec.parent_subject = new_subject || evaluation_context.parent_subject
687
+ new_ec.parent_object = current_object_resource || new_subject || evaluation_context.parent_subject
688
+ new_ec.uri_mappings = uri_mappings
689
+ new_ec.incomplete_triples = incomplete_triples
690
+ new_ec.language = language
691
+ new_ec.term_mappings = term_mappings
692
+ new_ec.default_vocabulary = default_vocabulary
693
+ add_debug(element, "[Step 13] new ec")
694
+ end
695
+
696
+ element.children.each do |child|
697
+ # recurse only if it's an element
698
+ traverse(child, new_ec) if child.class == Nokogiri::XML::Element
699
+ end
700
+ end
701
+ end
702
+
703
+ # space-separated TERMorCURIEorURI
704
+ def process_uris(element, value, evaluation_context, options)
705
+ return [] if value.to_s.empty?
706
+ add_debug(element, "process_uris: #{value}")
707
+ value.to_s.split(/\s+/).map {|v| process_uri(element, v, evaluation_context, options)}.compact
708
+ end
709
+
710
+ def process_uri(element, value, evaluation_context, options = {})
711
+ #return if value.to_s.empty?
712
+ #add_debug(element, "process_uri: #{value}")
713
+ options = {:uri_mappings => {}}.merge(options)
714
+ if !options[:term_mappings] && options[:uri_mappings] && value.to_s.match(/^\[(.*)\]$/)
715
+ # SafeCURIEorCURIEorURI
716
+ # When the value is surrounded by square brackets, then the content within the brackets is
717
+ # evaluated as a CURIE according to the CURIE Syntax definition. If it is not a valid CURIE, the
718
+ # value must be ignored.
719
+ uri = curie_to_resource_or_bnode(element, $1, options[:uri_mappings], evaluation_context.parent_subject)
720
+ add_debug(element, "process_uri: #{value} => safeCURIE => <#{uri}>")
721
+ uri
722
+ elsif options[:term_mappings] && NC_REGEXP.match(value.to_s)
723
+ # TERMorCURIEorURI
724
+ # If the value is an NCName, then it is evaluated as a term according to General Use of Terms in
725
+ # Attributes. Note that this step may mean that the value is to be ignored.
726
+ uri = process_term(value.to_s, options)
727
+ add_debug(element, "process_uri: #{value} => term => <#{uri}>")
728
+ uri
729
+ else
730
+ # SafeCURIEorCURIEorURI or TERMorCURIEorURI
731
+ # Otherwise, the value is evaluated as a CURIE.
732
+ # If it is a valid CURIE, the resulting URI is used; otherwise, the value will be processed as a URI.
733
+ uri = curie_to_resource_or_bnode(element, value, options[:uri_mappings], evaluation_context.parent_subject)
734
+ if uri
735
+ add_debug(element, "process_uri: #{value} => CURIE => <#{uri}>")
736
+ else
737
+ #FIXME: uri = URIRef.new(value, evaluation_context.base)
738
+ uri = RDF::URI.new(value)
739
+ add_debug(element, "process_uri: #{value} => URI => <#{uri}>")
740
+ end
741
+ uri
742
+ end
743
+ end
744
+
745
+ # [7.4.3] General Use of Terms in Attributes
746
+ #
747
+ # @param [String] term:: term
748
+ # @param [Hash] options:: Parser options, one of
749
+ # <em>options[:term_mappings]</em>:: Term mappings
750
+ # <em>options[:vocab]</em>:: Default vocabulary
751
+ def process_term(value, options)
752
+ case
753
+ when options[:term_mappings].is_a?(Hash) && options[:term_mappings].has_key?(value.to_s.downcase)
754
+ # If the term is in the local term mappings, use the associated URI.
755
+ # XXX Spec Confusion: are terms always downcased? Or only for XHTML Vocab?
756
+ options[:term_mappings][value.to_s.downcase]
757
+ when options[:vocab]
758
+ # Otherwise, if there is a local default vocabulary the URI is obtained by concatenating that value and the term.
759
+ options[:vocab] + value
760
+ else
761
+ # Finally, if there is no local default vocabulary, the term has no associated URI and must be ignored.
762
+ nil
763
+ end
764
+ end
765
+
766
+ # From section 6. CURIE Syntax Definition
767
+ def curie_to_resource_or_bnode(element, curie, uri_mappings, subject)
768
+ # URI mappings for CURIEs default to XH_MAPPING, rather than the default doc namespace
769
+ prefix, reference = curie.to_s.split(":")
770
+
771
+ # consider the bnode situation
772
+ if prefix == "_"
773
+ # we force a non-nil name, otherwise it generates a new name
774
+ # FIXME: BNode.new(reference || "", @named_bnodes)
775
+ RDF::Node.new(reference || nil)
776
+ elsif curie.to_s.match(/^:/)
777
+ # Default prefix
778
+ if uri_mappings[""]
779
+ uri_mappings[""].send("#{reference}_")
780
+ elsif @host_defaults[:prefix]
781
+ @host_defaults[:prefix].send("#{reference}_")
782
+ end
783
+ elsif !curie.to_s.match(/:/)
784
+ # No prefix, undefined (in this context, it is evaluated as a term elsewhere)
785
+ nil
786
+ else
787
+ # XXX Spec Confusion, are prefixes always downcased?
788
+ ns = uri_mappings[prefix.to_s.downcase]
789
+ if ns
790
+ ns + reference
791
+ else
792
+ add_debug(element, "curie_to_resource_or_bnode No namespace mapping for #{prefix.downcase}")
793
+ nil
794
+ end
795
+ end
796
+ end
797
+ end
798
+ end