@@ -63,6 +63,12 @@ class Writer < RDF::Writer
6363 # @return [RDF::URI] Base URI used for relativizing URIs
6464 attr_accessor :base_uri
6565
66+ # @return [String] RDF Version to output, if any
67+ attr_accessor :version
68+
69+ # @return [Boolean] Set to true if any literal includes a base direction
70+ attr_accessor :has_direction
71+
6672 ##
6773 # RDF/XML Writer options
6874 # @see https://ruby-rdf.github.io/rdf/RDF/Writer#options-class_method
@@ -132,6 +138,9 @@ def initialize(output = $stdout, **options, &block)
132138 @uri_to_qname = { }
133139 @top_classes = options [ :top_classes ] || [ RDF ::RDFS . Class ]
134140
141+ # FIXME: If version is specified in media type, use it to set an explicit version
142+ @version = nil
143+
135144 block . call ( self ) if block_given?
136145 end
137146 end
@@ -159,6 +168,7 @@ def write_epilogue
159168 log_debug { "\n serialize: graph size: #{ @graph . size } " }
160169
161170 preprocess
171+
162172 # Prefixes
163173 prefix = prefixes . keys . map { |pk | "#{ pk } : #{ prefixes [ pk ] } " } . sort . join ( " " ) unless prefixes . empty?
164174 log_debug { "\n serialize: prefixes: #{ prefix . inspect } " }
@@ -188,12 +198,12 @@ def reset
188198 @subjects = { }
189199 end
190200
191- # Render document using `haml_template[:doc]` . Yields each subject to be rendered separately.
201+ # Render document. Yields each subject to be rendered separately.
192202 #
193203 # @param [Array<RDF::Resource>] subjects
194204 # Ordered list of subjects. Template must yield to each subject, which returns
195205 # the serialization of that subject (@see #subject_template)
196- # @param [Hash{Symbol => Object}] options Rendering options passed to Haml render .
206+ # @param [Hash{Symbol => Object}] options Rendering options.
197207 # @option options [RDF::URI] base (nil)
198208 # Base URI added to document, used for shortening URIs within the document.
199209 # @option options [Symbol, String] language (nil)
@@ -203,8 +213,6 @@ def reset
203213 # Value of html>head>title element.
204214 # @option options [String] prefix (nil)
205215 # Value of @prefix attribute.
206- # @option options [String] haml (haml_template[:doc])
207- # Haml template to render.
208216 # @yield [subject]
209217 # Yields each subject
210218 # @yieldparam [RDF::URI] subject
@@ -219,6 +227,8 @@ def render_document(subjects, lang: nil, base: nil, **options, &block)
219227 attrs = prefix_attrs
220228 attrs [ :"xml:lang" ] = lang if lang
221229 attrs [ :"xml:base" ] = base if base
230+ attrs [ :"rdf:version" ] = version . freeze if version
231+ attrs [ :"its:version" ] = "2.0" if has_direction
222232
223233 builder . rdf ( :RDF , **attrs ) do |b |
224234 subjects . each do |subject |
@@ -227,18 +237,18 @@ def render_document(subjects, lang: nil, base: nil, **options, &block)
227237 end
228238 end
229239
230- # Render a subject using `haml_template[:subject]` .
240+ # Render a subject.
231241 #
232242 # The _subject_ template may be called either as a top-level element, or recursively under another element if the _rel_ local is not nil.
233243 #
234- # For RDF/XML, removes from predicates those that can be rendered as attributes, and adds the `:attr_props` local for the Haml template , which includes all attributes to be rendered as properties.
244+ # For RDF/XML, removes from predicates those that can be rendered as attributes, and adds the `:attr_props` local, which includes all attributes to be rendered as properties.
235245 #
236246 # Yields each property to be rendered separately.
237247 #
238248 # @param [Array<RDF::Resource>] subject
239249 # Subject to render
240250 # @param [Builder::RdfXml] builder
241- # @param [Hash{Symbol => Object}] options Rendering options passed to Haml render .
251+ # @param [Hash{Symbol => Object}] options Rendering options passed to builder .
242252 # @option options [String] about (nil)
243253 # About description, a QName, URI or Node definition.
244254 # May be nil if no @about is rendered (e.g. unreferenced Nodes)
@@ -252,8 +262,6 @@ def render_document(subjects, lang: nil, base: nil, **options, &block)
252262 # If :about is nil, this defaults to the empty string ("").
253263 # @option options [:li, nil] element (nil)
254264 # Render with <li>, otherwise with template default.
255- # @option options [String] haml (haml_template[:subject])
256- # Haml template to render.
257265 # @yield [predicate]
258266 # Yields each predicate
259267 # @yieldparam [RDF::URI] predicate
@@ -303,9 +311,10 @@ def render_subject(subject, builder, **options, &block)
303311 # @param [Array<RDF::Resource>] objects
304312 # List of objects to render. If the list contains only a single element, the :property_value template will be used. Otherwise, the :property_values template is used.
305313 # @param [Builder::RdfXml] builder
306- # @param [Hash{Symbol => Object}] options Rendering options passed to Haml render .
314+ # @param [Hash{Symbol => Object}] options Rendering options.
307315 def render_property ( property , objects , builder , **options )
308316 log_debug { "render_property(#{ property } ): #{ objects . inspect } " }
317+ property = get_qname ( property ) if property . is_a? ( RDF ::URI )
309318
310319 # Separate out the objects which are lists and render separately
311320 lists = objects .
@@ -351,7 +360,12 @@ def render_property(property, objects, builder, **options)
351360 attrs = { }
352361 attrs [ :"xml:lang" ] = object . language if object . language?
353362 attrs [ :"rdf:datatype" ] = object . datatype if object . datatype?
363+ attrs [ :"its:dir" ] = object . direction if object . direction?
354364 builder . tag! ( property , object . value . to_s , **attrs )
365+ elsif object . statement?
366+ builder . tag! ( property , "rdf:parseType" : "Triple" ) do |b |
367+ render_triple_term ( object , b , **options )
368+ end
355369 elsif object . node?
356370 builder . tag! ( property , "rdf:nodeID" : object . id )
357371 else
@@ -367,6 +381,18 @@ def render_property(property, objects, builder, **options)
367381 end
368382 end
369383
384+ ##
385+ # Render a triple term, which may be recursive
386+ def render_triple_term ( term , builder , **options )
387+ attr_props = { }
388+ attr_props = attr_props . merge ( "rdf:nodeID" : term . subject . id ) if term . subject . node?
389+ attr_props = attr_props . merge ( "rdf:about" : term . subject . relativize ( base_uri ) ) if term . subject . uri?
390+
391+ builder . tag! ( "rdf:Description" , **attr_props ) do |b |
392+ render_property ( term . predicate , [ term . object ] , b )
393+ end
394+ end
395+
370396 ##
371397 # Render a collection, which may be included in a property declaration, or
372398 # may be recursive within another collection
@@ -441,6 +467,28 @@ def preprocess_statement(statement)
441467 ensure_qname ( statement . predicate )
442468 statement . predicate == RDF . type && statement . object . uri? ? ensure_qname ( statement . object ) : get_qname ( statement . object )
443469 get_qname ( statement . object . datatype ) if statement . object . literal? && statement . object . datatype?
470+
471+ # Base direction requires a prefix, used to set the its:version in the document
472+ if statement . object . literal? && statement . object . direction?
473+ prefix ( :its , RDF ::ITS . to_s )
474+ @has_direction = true # Indirectly adds its:version to document element
475+
476+ # It's an error if version is frozen and not at least "1.2-basic"
477+ if version && version . frozen?
478+ log_error ( "Literal direction is incompatible with required version #{ version } : #{ statement . object . direction } " ) if
479+ version == "1.1"
480+ elsif version . nil?
481+ @version = "1.2-basic"
482+ end
483+ elsif statement . object . statement?
484+ # It's an error if version is frozen and not at least "1.2-basic"
485+ if version && version . frozen?
486+ log_error ( "Triple terms are incompatible with required version #{ version } " ) if
487+ version != "1.2"
488+ else
489+ @version = "1.2"
490+ end
491+ end
444492 end
445493
446494 private
@@ -583,7 +631,7 @@ def get_qname(resource)
583631 nil
584632 end
585633 when RDF ::Node then resource . to_s
586- when RDF ::Literal then nil
634+ when RDF ::Literal , RDF :: Statement then nil
587635 else
588636 log_error ( "Getting QName for #{ resource . inspect } , which must be a resource" )
589637 nil
0 commit comments