annotations2triannon 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/.env_example +44 -0
  3. data/.travis.yml +15 -0
  4. data/Gemfile +5 -0
  5. data/LICENSE +202 -0
  6. data/README.md +31 -0
  7. data/Rakefile +50 -0
  8. data/annotations2triannon.gemspec +58 -0
  9. data/bin/console +5 -0
  10. data/bin/ctags.rb +8 -0
  11. data/bin/dms.rb +175 -0
  12. data/bin/revs.rb +17 -0
  13. data/bin/revs_annotations2csv.sh +66 -0
  14. data/lib/annotations2triannon/annotation_list.rb +37 -0
  15. data/lib/annotations2triannon/configuration.rb +52 -0
  16. data/lib/annotations2triannon/iiif_annotation_list.rb +17 -0
  17. data/lib/annotations2triannon/iiif_collection.rb +56 -0
  18. data/lib/annotations2triannon/iiif_manifest.rb +32 -0
  19. data/lib/annotations2triannon/iiif_navigator.rb +172 -0
  20. data/lib/annotations2triannon/manifest.rb +86 -0
  21. data/lib/annotations2triannon/open_annotation.rb +262 -0
  22. data/lib/annotations2triannon/open_annotation_harvest.rb +37 -0
  23. data/lib/annotations2triannon/resource.rb +264 -0
  24. data/lib/annotations2triannon/revs.rb +263 -0
  25. data/lib/annotations2triannon/revs_db.rb +69 -0
  26. data/lib/annotations2triannon/shared_canvas_annotation_list.rb +18 -0
  27. data/lib/annotations2triannon/shared_canvas_manifest.rb +32 -0
  28. data/lib/annotations2triannon.rb +27 -0
  29. data/lib/rdf/vocab/Content.rb +112 -0
  30. data/lib/rdf/vocab/sc.rb +233 -0
  31. data/lib/requires.rb +69 -0
  32. data/log/.gitignore +4 -0
  33. data/spec/lib/annotations2triannon/configuration_spec.rb +24 -0
  34. data/spec/lib/annotations2triannon/open_annotation_spec.rb +176 -0
  35. data/spec/lib/annotations2triannon/resource_spec.rb +53 -0
  36. data/spec/lib/annotations2triannon_spec.rb +45 -0
  37. data/spec/spec_helper.rb +10 -0
  38. metadata +387 -0
@@ -0,0 +1,263 @@
1
+
2
+ require_relative 'revs_db'
3
+
4
+ module Annotations2triannon
5
+
6
+ class Revs
7
+
8
+ attr_accessor :db
9
+ attr_accessor :open_annotations
10
+
11
+ def initialize
12
+ @db = Annotations2triannon::RevsDb.new
13
+ end
14
+
15
+ def open_annotations
16
+ # convert all the annotations
17
+ user_id = nil
18
+ oa_list = []
19
+ @db.annotation_ids.each do |a|
20
+ # the annotation_ids are sorted by user, so there's no
21
+ # need to retrieve the user data for every annotation.
22
+ if user_id != a[:user_id]
23
+ user = user_rdf(a[:user_id])
24
+ end
25
+ annotation = @db.annotation(a[:id])
26
+ oa_list << annotation2oa(annotation, user)
27
+ end
28
+ oa_list
29
+ end
30
+
31
+ def open_annotations_as_jsonld
32
+ oa_list = open_annotations
33
+ oa_list.collect {|a| a.as_jsonld }
34
+ end
35
+
36
+ def open_annotation(id=nil)
37
+ # Not using a join, because some annotations may be anonymous.
38
+ # join = r.db.annotations.join_table(:inner, r.db.users, :id=>:user_id)
39
+ raise 'Invalid annotation ID' if id.nil?
40
+ # find and convert an annotation by id
41
+ annotation = @db.annotation(id)
42
+ raise "No annotation with id => #{id}" if annotation.nil?
43
+ user = user_rdf(annotation[:user_id])
44
+ annotation2oa(annotation, user)
45
+ end
46
+
47
+ def user_rdf(user_id)
48
+ user = @db.user(user_id)
49
+ user_id = sprintf 'revs_user_%04d', user[:id]
50
+ user_uri = RDF::URI.parse(user_id)
51
+ # avoid creation of blank nodes?
52
+ # user_node = RDF::Node.new(user_uri)
53
+ user_graph = RDF::Graph.new
54
+ user_graph.insert([user_uri, RDF.type, RDF::SCHEMA.Person])
55
+ # user_info = RDF::Literal.new("REVS user id: #{user[:id]}")
56
+ # user_graph.insert([user_uri, RDF::SCHEMA.description, user_info])
57
+ if user[:public]
58
+ unless user[:first_name].nil? || user[:first_name].empty?
59
+ # TODO: add language tags?
60
+ #fn = RDF::Literal.new(user[:first_name], :language => :en)
61
+ fn = RDF::Literal.new(user[:first_name])
62
+ user_graph.insert([user_uri, RDF::SCHEMA.givenName, fn])
63
+ end
64
+ unless user[:last_name].nil? || user[:last_name].empty?
65
+ #ln = RDF::Literal.new(user[:last_name], :language => :en)
66
+ ln = RDF::Literal.new(user[:last_name])
67
+ user_graph.insert([user_uri, RDF::SCHEMA.familyName, ln])
68
+ end
69
+ unless user[:bio].nil? || user[:bio].empty?
70
+ #description = RDF::Literal.new(user[:bio], :language => :en)
71
+ description = RDF::Literal.new(user[:bio])
72
+ user_graph.insert([user_uri, RDF::SCHEMA.description, description])
73
+ end
74
+ unless user[:email].nil? || user[:email].empty?
75
+ email = RDF::URI.parse('mailto:' + user[:email])
76
+ user_graph.insert([user_uri, RDF::SCHEMA.email, email])
77
+ end
78
+ unless user[:url].nil? || user[:url].empty?
79
+ url = user[:url]
80
+ url = url.start_with?('http://') ? url : 'http://' + url
81
+ url = RDF::URI.parse(url)
82
+ user_graph.insert([user_uri, RDF::SCHEMA.url, url])
83
+ end
84
+ unless user[:twitter].nil? || user[:twitter].empty?
85
+ url = user[:twitter]
86
+ unless (url.start_with? 'https://twitter.com') || (url.start_with? 'http://twitter.com')
87
+ url = 'https://twitter.com/' + url
88
+ end
89
+ url = RDF::URI.parse(url)
90
+ user_graph.insert([user_uri, RDF::SCHEMA.url, url])
91
+ end
92
+ end
93
+ {
94
+ :uri => user_uri,
95
+ # :node => user_node,
96
+ :graph => user_graph
97
+ }
98
+ end
99
+
100
+ # private
101
+
102
+ #
103
+ # Mapping a REVS annotation into an Open Annotation
104
+ #
105
+ def annotation2oa(annotation, user)
106
+
107
+ begin
108
+ # id --> part of URI for the annotation but, triannon POST will not accept an ID
109
+ annotation_id = sprintf 'revs_annotation_%04d', annotation[:id]
110
+
111
+ # convert the 'druid' into a PURL URI
112
+ purl = 'http://purl.stanford.edu/' + annotation[:druid]
113
+ purl_uri = RDF::URI.parse(purl)
114
+
115
+ # Commentary on the annotation json field
116
+ #
117
+ # > for each row of the annotation table (in mysql), can the 'shapes' array
118
+ # contain more than one entry?
119
+ #
120
+ # Shapes can currently only contain one entry, and are currently always
121
+ # rectangular. This data structure and shape implementation is a result of our
122
+ # use of the annotorious plugin, which is not guaranteed across projects or even
123
+ # in Revs in the long term.
124
+ #
125
+ # > if so, this suggests that a 'text' annotation might refer to more than
126
+ # one segment or region of a REVS image?
127
+ #
128
+ # Not at the moment. Not sure why it is an array. Perhaps so you can store
129
+ # multiple annotations about the same image in one row instead of many, but we
130
+ # do not do this for various reasons.
131
+ #
132
+ # > What is the 'context' field? Would you rather use the 'context' field than
133
+ # the 'src' field as the target of an open annotation (OA)?
134
+ #
135
+ # Context is just what annotorious uses for the src target. Again, we just used
136
+ # their vocabulary for ease of implementation. If we were to use a different
137
+ # back-end data store at some point, we could always transform into and out-of
138
+ # their specific json structure as needed.
139
+ #
140
+
141
+ # convert the 'json' field
142
+ annotation_json = JSON.parse(annotation[:json])
143
+ revs_uri = RDF::URI.parse(annotation_json['context'])
144
+ revs_img_src = annotation_json['src']
145
+ revs_img_uri = RDF::URI.parse(revs_img_src)
146
+ revs_fragments = []
147
+ annotation_json['shapes'].each do |shape|
148
+ # shapes are likely type 'rect'
149
+ if shape['type'] == 'rect'
150
+ # image annotation geometry
151
+ # x is % across from top left
152
+ # y is % down from top left
153
+ # width is % across from x
154
+ # height is % down from y
155
+ x = shape['geometry']['x'] * 100
156
+ y = shape['geometry']['y'] * 100
157
+ w = shape['geometry']['width'] * 100
158
+ h = shape['geometry']['height'] * 100
159
+ # media fragment: #xywh=percent:30.1,16.8,35.1,52.2
160
+ fragment = sprintf '#xywh=percent:%04.1f,%04.1f,%04.1f,%04.1f', x, y, w, h
161
+ revs_fragments << fragment
162
+ end
163
+ end
164
+ revs_img_graph = RDF::Graph.new
165
+ # revs_img_node = RDF::Node.new(revs_img_uri)
166
+ # revs_img_graph.insert([revs_img_node, RDF.type, RDF::Vocab::OA.SpecificResource])
167
+ # revs_img_graph.insert([revs_img_node, RDF::Vocab::OA.hasSource, revs_img_uri])
168
+ revs_img_graph.insert([revs_uri, RDF.type, RDF::Vocab::OA.SpecificResource])
169
+ revs_img_graph.insert([revs_uri, RDF::Vocab::OA.hasSource, revs_img_uri])
170
+ revs_img_graph.insert([revs_img_uri, RDF.type, RDF::DCMIType.Image])
171
+ # Note: it's most likely there is only one fragment in a REVS annotation.
172
+ revs_fragment_graphs = []
173
+ revs_fragments.each_with_index do |f, i|
174
+ # img_uri = RDF::URI.parse(revs_img_src + fragment)
175
+ # revs_img_uris << img_uri
176
+ f_id = sprintf '%s_fragment_%02d', annotation_id, i
177
+ f_uri = RDF::URI.parse(f_id)
178
+ f_graph = RDF::Graph.new
179
+ # avoid creation of blank nodes?
180
+ # f_node = RDF::Node.new(f_uri)
181
+ # f_graph.insert([f_node, RDF.type, RDF::Vocab::OA.FragmentSelector])
182
+ # f_graph.insert([f_node, RDF::DC.conformsTo, RDF::MA.MediaFragment])
183
+ # f_graph.insert([f_node, RDF.value, RDF::Literal.new(f)])
184
+ # revs_img_graph.insert([revs_img_node, RDF::Vocab::OA.hasSelector, f_node])
185
+ f_graph.insert([f_uri, RDF.type, RDF::Vocab::OA.FragmentSelector])
186
+ f_graph.insert([f_uri, RDF::DC.conformsTo, RDF::MA.MediaFragment])
187
+ f_graph.insert([f_uri, RDF.value, RDF::Literal.new(f)])
188
+ revs_img_graph.insert([revs_uri, RDF::Vocab::OA.hasSelector, f_uri])
189
+ revs_fragment_graphs << f_graph
190
+ end
191
+
192
+ # oa#hasBody
193
+ # text --> value of cnt:chars property of a ContentAsText body of the annotation
194
+ body_id = sprintf '%s_comment', annotation_id
195
+ body_uri = RDF::URI.parse(body_id)
196
+ # TODO: add a language tag?
197
+ #body_text = RDF::Literal.new(annotation[:text], :language => :en)
198
+ body_text = RDF::Literal.new(annotation[:text])
199
+ body_graph = RDF::Graph.new
200
+ # avoid creation of blank nodes?
201
+ # body_node = RDF::Node.uuid
202
+ # body_node = RDF::Node.new(annotation[:id])
203
+ body_graph.insert([body_uri, RDF.type, RDF::Content.ContentAsText])
204
+ body_graph.insert([body_uri, RDF.type, RDF::DCMIType.Text])
205
+ body_graph.insert([body_uri, RDF::Content.chars, body_text])
206
+ body_graph.insert([body_uri, RDF::Content.characterEncoding, 'UTF-8'])
207
+
208
+ # oa#annotatedAt
209
+ # created_at --> discard if updated_at is always present
210
+ # updated_at --> oa:annotatedAt
211
+ #
212
+ # > annotation[:created_at].class
213
+ # => Time
214
+ # > annotation[:created_at].utc
215
+ # => 2014-03-25 01:56:01 UTC
216
+ # > annotation[:created_at].to_i # unix time since epoch
217
+ # => 1395712561
218
+ # > [annotation[:created_at].utc, annotation[:updated_at].utc]
219
+ # => [2014-03-25 01:56:01 UTC, 2014-03-25 01:56:14 UTC]
220
+ #
221
+ # create an RDF literal with datatype, see
222
+ # http://rdf.greggkellogg.net/yard/RDF/Literal.html
223
+ # > RDF::Literal.new(annotation[:created_at]).datatype
224
+ # => #<RDF::Vocabulary::Term:0x3f86333d6ca8 URI:http://www.w3.org/2001/XMLSchema#time>
225
+ # However, this automatic conversion discards the date!
226
+ # > RDF::Literal.new(annotation[:created_at]).to_s
227
+ # => "01:56:01Z"
228
+ # So, an explicit datatype is required, i.e.:
229
+ # > RDF::Literal.new(annotation[:created_at], :datatype => RDF::XSD.dateTime).to_s
230
+ # => "2014-03-25T01:56:01Z"
231
+ created_datetime = RDF::Literal.new(annotation[:created_at].utc, :datatype => RDF::XSD.dateTime)
232
+ updated_datetime = RDF::Literal.new(annotation[:updated_at].utc, :datatype => RDF::XSD.dateTime)
233
+ annotation_datetime = updated_datetime #if annotation[:created_at].utc < annotation[:updated_at].utc
234
+
235
+ # Create and populate an Open Annotation instance.
236
+ oa = Annotations2triannon::OpenAnnotation.new
237
+ oa.insert_hasTarget(revs_uri)
238
+ oa.insert_hasTarget(purl_uri)
239
+ oa.insert_hasTarget(revs_img_uri)
240
+ # oa.insert_hasTarget(revs_img_node)
241
+ oa.graph.insert(revs_img_graph)
242
+ revs_fragment_graphs.each {|g| oa.graph.insert(g) }
243
+ # to enable the blank node, change body_graph to use body_node instead of body_uri
244
+ # oa.insert_hasBody(body_node)
245
+ oa.insert_hasBody(body_uri)
246
+ oa.graph.insert(body_graph)
247
+ oa.insert_annotatedAt(annotation_datetime)
248
+ # to enable the blank node, change user_graph to use user_node instead of user_uri
249
+ # oa.insert_annotatedBy(user[:node])
250
+ oa.insert_annotatedBy(user[:uri])
251
+ oa.graph.insert(user[:graph])
252
+ oa
253
+ rescue => e
254
+ puts e.message
255
+ # binding.pry
256
+ raise e
257
+ end
258
+ end
259
+
260
+ end
261
+
262
+ end
263
+
@@ -0,0 +1,69 @@
1
+ require 'logger'
2
+ require 'mysql2'
3
+ require 'sequel'
4
+ # An interface to the revs SQL database using Sequel
5
+ # @see http://sequel.jeremyevans.net/documentation.html Sequel RDoc
6
+ # @see http://sequel.jeremyevans.net/rdoc/files/README_rdoc.html Sequel README
7
+ # @see http://sequel.jeremyevans.net/rdoc/files/doc/code_order_rdoc.html Sequel code order
8
+
9
+ module Annotations2triannon
10
+
11
+ class RevsDb
12
+
13
+ @@log = Logger.new('log/revs_db.log')
14
+
15
+ attr_accessor :db
16
+ attr_accessor :db_config
17
+
18
+ def self.log_model_info(m)
19
+ @@log.info "table: #{m.table_name}, columns: #{m.columns}, pk: #{m.primary_key}"
20
+ end
21
+
22
+ def initialize
23
+ @db_config = {}
24
+ @db_config['host'] = ENV['REVS_DB_HOST'] || 'localhost'
25
+ @db_config['port'] = ENV['REVS_DB_PORT'] || '3306'
26
+ @db_config['user'] = ENV['REVS_DB_USER'] || 'revs'
27
+ @db_config['password'] = ENV['REVS_DB_PASS'] || ''
28
+ @db_config['database'] = ENV['REVS_DB_DATABASE'] || 'revs'
29
+ options = @db_config.merge(
30
+ {
31
+ :encoding => 'utf8',
32
+ :max_connections => 10,
33
+ :logger => @@log
34
+ })
35
+ @db = Sequel.mysql2(options)
36
+ @db.extension(:pagination)
37
+ # Ensure the connection is good on startup, raises exceptions on failure
38
+ @@log.info "#{@db} connected: #{@db.test_connection}"
39
+ end
40
+
41
+ def annotation(id)
42
+ @db[:annotations][:id => id]
43
+ end
44
+
45
+ def annotations
46
+ @db[:annotations]
47
+ end
48
+
49
+ def annotation_ids
50
+ @db[:annotations].order(:user_id).select(:user_id, :id)
51
+ end
52
+
53
+ def annotations_join_users
54
+ @db[:annotations].join_table(:inner, @db[:users], :id=>:user_id)
55
+ # @db[:annotations].join_table(:outer, @db[:users], :id=>:user_id)
56
+ end
57
+
58
+ def users
59
+ @db[:users]
60
+ end
61
+
62
+ def user(id)
63
+ @db[:users][:id => id]
64
+ end
65
+
66
+ end
67
+
68
+ end
69
+
@@ -0,0 +1,18 @@
1
+
2
+ module Annotations2triannon
3
+
4
+ # A filter to exclude any IIIF namespace content
5
+ class SharedCanvasAnnotationList < AnnotationList
6
+
7
+ def annotation_list?
8
+ sc_annotation_list?
9
+ end
10
+
11
+ def iiif_annotation_list?
12
+ false
13
+ end
14
+
15
+ end
16
+
17
+ end
18
+
@@ -0,0 +1,32 @@
1
+
2
+ module Annotations2triannon
3
+
4
+ # A filter to exclude any IIIF namespace content
5
+ class SharedCanvasManifest < Manifest
6
+
7
+ def manifest?
8
+ sc_manifest?
9
+ end
10
+
11
+ def iiif_manifest?
12
+ false
13
+ end
14
+
15
+ def annotation_lists
16
+ return @annotation_lists unless @annotation_lists.nil?
17
+ uris = collect_annotation_list_uris(query_sc_annotation_list)
18
+ @annotation_lists = uris.collect do |uri|
19
+ Annotations2triannon::AnnotationList.new(uri)
20
+ end
21
+ @annotation_lists
22
+ end
23
+
24
+ def iiif_annotation_lists
25
+ return @iiif_annotation_lists unless @iiif_annotation_lists.nil?
26
+ @iiif_annotation_lists = []
27
+ end
28
+
29
+ end
30
+
31
+ end
32
+
@@ -0,0 +1,27 @@
1
+ require 'requires'
2
+
3
+ module Annotations2triannon
4
+
5
+ AGENT = RDF::URI.parse('https://github.com/sul-dlss/annotations2triannon')
6
+
7
+ # configuration at the module level, see
8
+ # http://brandonhilkert.com/blog/ruby-gem-configuration-patterns/
9
+
10
+ class << self
11
+ attr_writer :configuration
12
+ end
13
+
14
+ def self.configuration
15
+ @configuration ||= Configuration.new
16
+ end
17
+
18
+ def self.reset
19
+ @configuration = Configuration.new
20
+ end
21
+
22
+ def self.configure
23
+ yield(configuration)
24
+ end
25
+
26
+ end
27
+
@@ -0,0 +1,112 @@
1
+ # -*- encoding: utf-8 -*-
2
+ # This file generated automatically using vocab-fetch from http://www.w3.org/2011/content
3
+ require 'rdf'
4
+ module RDF
5
+ class CONTENT < RDF::StrictVocabulary("http://www.w3.org/2011/content#")
6
+
7
+ # Class definitions
8
+ term :Content,
9
+ comment: %(The content.).freeze,
10
+ label: "Content".freeze,
11
+ type: ["rdfs:Class".freeze, "owl:Class".freeze]
12
+ term :ContentAsBase64,
13
+ comment: %(The base64 encoded content \(can be used for binary content\).).freeze,
14
+ label: "Base64 content".freeze,
15
+ subClassOf: "http://www.w3.org/2011/content#Content".freeze,
16
+ type: ["rdfs:Class".freeze, "owl:Class".freeze]
17
+ term :ContentAsText,
18
+ comment: %(The text content \(can be used for text content\).).freeze,
19
+ label: "Text content".freeze,
20
+ subClassOf: "http://www.w3.org/2011/content#Content".freeze,
21
+ type: ["rdfs:Class".freeze, "owl:Class".freeze]
22
+ term :ContentAsXML,
23
+ comment: %(The XML content \(can only be used for XML-wellformed content\).).freeze,
24
+ label: "XML content".freeze,
25
+ subClassOf: "http://www.w3.org/2011/content#Content".freeze,
26
+ type: ["rdfs:Class".freeze, "owl:Class".freeze]
27
+ term :DoctypeDecl,
28
+ comment: %(The document type declaration.).freeze,
29
+ label: "Document type declaration".freeze,
30
+ type: ["rdfs:Class".freeze, "owl:Class".freeze]
31
+
32
+ # Property definitions
33
+ property :bytes,
34
+ comment: %(The Base64 encoded byte sequence of the content.).freeze,
35
+ domain: "http://www.w3.org/2011/content#ContentAsBase64".freeze,
36
+ label: "Base64 encoded byte sequence".freeze,
37
+ range: "xsd:base64Binary".freeze,
38
+ type: ["rdf:Property".freeze, "owl:ObjectProperty".freeze]
39
+ property :characterEncoding,
40
+ comment: %(The character encoding used to create a character sequence from a byte sequence or vice versa.).freeze,
41
+ domain: "http://www.w3.org/2011/content#Content".freeze,
42
+ label: "Character encoding".freeze,
43
+ range: "rdfs:Literal".freeze,
44
+ type: ["rdf:Property".freeze, "owl:ObjectProperty".freeze]
45
+ property :chars,
46
+ comment: %(The character sequence of the text content.).freeze,
47
+ domain: "http://www.w3.org/2011/content#ContentAsText".freeze,
48
+ label: "Character sequence".freeze,
49
+ range: "rdfs:Literal".freeze,
50
+ type: ["rdf:Property".freeze, "owl:ObjectProperty".freeze]
51
+ property :declaredEncoding,
52
+ comment: %(The character encoding declared in the XML declaration.).freeze,
53
+ domain: "http://www.w3.org/2011/content#ContentAsXML".freeze,
54
+ label: "XML character encoding".freeze,
55
+ range: "rdfs:Literal".freeze,
56
+ type: ["rdf:Property".freeze, "owl:ObjectProperty".freeze]
57
+ property :doctypeName,
58
+ comment: %(The document type name.).freeze,
59
+ domain: "http://www.w3.org/2011/content#DoctypeDecl".freeze,
60
+ label: "Document type name".freeze,
61
+ range: "rdfs:Literal".freeze,
62
+ type: ["rdf:Property".freeze, "owl:ObjectProperty".freeze]
63
+ property :dtDecl,
64
+ comment: %(The document type declaration.).freeze,
65
+ domain: "http://www.w3.org/2011/content#ContentAsXML".freeze,
66
+ label: "Document type declaration".freeze,
67
+ range: "http://www.w3.org/2011/content#DoctypeDecl".freeze,
68
+ type: ["rdf:Property".freeze, "owl:ObjectProperty".freeze]
69
+ property :internalSubset,
70
+ comment: %(The internal document type definition subset within the document type declarations.).freeze,
71
+ domain: "http://www.w3.org/2011/content#DoctypeDecl".freeze,
72
+ label: "Internal DTD subset".freeze,
73
+ range: "rdfs:Literal".freeze,
74
+ type: ["rdf:Property".freeze, "owl:ObjectProperty".freeze]
75
+ property :leadingMisc,
76
+ comment: %(The XML content preceding the document type declaration.).freeze,
77
+ domain: "http://www.w3.org/2011/content#ContentAsXML".freeze,
78
+ label: "XML leading misc".freeze,
79
+ range: "rdfs:XMLLiteral".freeze,
80
+ type: ["rdf:Property".freeze, "owl:ObjectProperty".freeze]
81
+ property :publicId,
82
+ comment: %(The document type declarations's public identifier.).freeze,
83
+ domain: "http://www.w3.org/2011/content#DoctypeDecl".freeze,
84
+ label: "Public ID".freeze,
85
+ range: "rdfs:Literal".freeze,
86
+ type: ["rdf:Property".freeze, "owl:ObjectProperty".freeze]
87
+ property :rest,
88
+ comment: %(The XML content following the document type declaration.).freeze,
89
+ domain: "http://www.w3.org/2011/content#ContentAsXML".freeze,
90
+ label: "XML rest".freeze,
91
+ range: "rdfs:XMLLiteral".freeze,
92
+ type: ["rdf:Property".freeze, "owl:ObjectProperty".freeze]
93
+ property :standalone,
94
+ comment: %(The standalone declaration in the XML declaration.).freeze,
95
+ domain: "http://www.w3.org/2011/content#ContentAsXML".freeze,
96
+ label: "XML standalone document declaration".freeze,
97
+ range: "rdfs:Literal".freeze,
98
+ type: ["rdf:Property".freeze, "owl:ObjectProperty".freeze]
99
+ property :systemId,
100
+ comment: %(The document type declarations's system identifier \(typed: xsd:anyURI\)).freeze,
101
+ domain: "http://www.w3.org/2011/content#DoctypeDecl".freeze,
102
+ label: "System ID".freeze,
103
+ range: "xsd:anyURI".freeze,
104
+ type: ["rdf:Property".freeze, "owl:ObjectProperty".freeze]
105
+ property :version,
106
+ comment: %(The XML version declared in the XML declaration.).freeze,
107
+ domain: "http://www.w3.org/2011/content#ContentAsXML".freeze,
108
+ label: "XML version".freeze,
109
+ range: "rdfs:Literal".freeze,
110
+ type: ["rdf:Property".freeze, "owl:ObjectProperty".freeze]
111
+ end
112
+ end