• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ruby-rdf / rdf-trig / 14785116514

01 May 2025 11:04PM UTC coverage: 92.887% (+0.03%) from 92.857%
14785116514

push

github

gkellogg
Updates for TriG grammar and 1.2 semantics.

1 of 1 new or added line in 1 file covered. (100.0%)

222 of 239 relevant lines covered (92.89%)

187.64 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.44
/lib/rdf/trig/writer.rb
1
require 'rdf/turtle'
2✔
2
require 'rdf/trig/streaming_writer'
2✔
3

4
module RDF::TriG
2✔
5
  ##
6
  # A TriG serialiser
7
  #
8
  # Note that the natural interface is to write a whole repository at a time.
9
  # Writing statements or Triples will create a repository to add them to
10
  # and then serialize the repository.
11
  #
12
  # @example Obtaining a TriG writer class
13
  #   RDF::Writer.for(:trig)         #=> RDF::TriG::Writer
14
  #   RDF::Writer.for("etc/test.trig")
15
  #   RDF::Writer.for(:file_name      => "etc/test.trig")
16
  #   RDF::Writer.for(file_extension: "trig")
17
  #   RDF::Writer.for(:content_type   => "application/trig")
18
  #
19
  # @example Serializing RDF repo into an TriG file
20
  #   RDF::TriG::Writer.open("etc/test.trig") do |writer|
21
  #     writer << repo
22
  #   end
23
  #
24
  # @example Serializing RDF statements into an TriG file
25
  #   RDF::TriG::Writer.open("etc/test.trig") do |writer|
26
  #     repo.each_statement do |statement|
27
  #       writer << statement
28
  #     end
29
  #   end
30
  #
31
  # @example Serializing RDF statements into an TriG string
32
  #   RDF::TriG::Writer.buffer do |writer|
33
  #     repo.each_statement do |statement|
34
  #       writer << statement
35
  #     end
36
  #   end
37
  #
38
  # @example Serializing RDF statements to a string in streaming mode
39
  #   RDF::TriG::Writer.buffer(stream: true) do |writer|
40
  #     repo.each_statement do |statement|
41
  #       writer << statement
42
  #     end
43
  #   end
44
  #
45
  # The writer will add prefix definitions, and use them for creating @prefix definitions, and minting QNames
46
  #
47
  # @example Creating @base and @prefix definitions in output
48
  #   RDF::TriG::Writer.buffer(base_uri: "http://example.com/", prefixes: {
49
  #       nil => "http://example.com/ns#",
50
  #       foaf: "http://xmlns.com/foaf/0.1/"}
51
  #   ) do |writer|
52
  #     repo.each_statement do |statement|
53
  #       writer << statement
54
  #     end
55
  #   end
56
  #
57
  # @author [Gregg Kellogg](https://greggkellogg.net/)
58
  class Writer < RDF::Turtle::Writer
2✔
59
    include StreamingWriter
2✔
60
    format RDF::TriG::Format
2✔
61
    
62
    ##
63
    # Initializes the TriG writer instance.
64
    #
65
    # @param  [IO, File] output
66
    #   the output stream
67
    # @param  [Hash{Symbol => Object}] options
68
    #   any additional options
69
    # @option options [Encoding] :encoding     (Encoding::UTF_8)
70
    #   the encoding to use on the output stream (Ruby 1.9+)
71
    # @option options [Boolean]  :canonicalize (false)
72
    #   whether to canonicalize literals when serializing
73
    # @option options [Hash]     :prefixes     (Hash.new)
74
    #   the prefix mappings to use (not supported by all writers)
75
    # @option options [#to_s]    :base_uri     (nil)
76
    #   the base URI to use when constructing relative URIs
77
    # @option options [Integer]  :max_depth      (3)
78
    #   Maximum depth for recursively defining resources, defaults to 3
79
    # @option options [Boolean]  :standard_prefixes   (false)
80
    #   Add standard prefixes to @prefixes, if necessary.
81
    # @option options [Boolean] :stream (false)
82
    #   Do not attempt to optimize graph presentation, suitable for streaming large repositories.
83
    # @option options [String]   :default_namespace (nil)
84
    #   URI to use as default namespace, same as `prefixes\[nil\]`
85
    # @yield  [writer] `self`
86
    # @yieldparam  [RDF::Writer] writer
87
    # @yieldreturn [void]
88
    # @yield  [writer]
89
    # @yieldparam [RDF::Writer] writer
90
    def initialize(output = $stdout, **options, &block)
2✔
91
      super do
208✔
92
        # Set both @repo and @graph to a new repository.
93
        @repo = @graph = RDF::Repository.new
208✔
94
        if block_given?
208✔
95
          case block.arity
168✔
96
            when 0 then instance_eval(&block)
×
97
            else block.call(self)
168✔
98
          end
99
        end
100
      end
101
    end
102

103
    ##
104
    # Adds a triple to be serialized
105
    # @param  [RDF::Resource] subject
106
    # @param  [RDF::URI]      predicate
107
    # @param  [RDF::Value]    object
108
    # @param  [RDF::Resource] graph_name
109
    # @return [void]
110
    def write_quad(subject, predicate, object, graph_name)
2✔
111
      statement = RDF::Statement.new(subject, predicate, object, graph_name: graph_name)
330✔
112
      if @options[:stream]
330✔
113
        stream_statement(statement)
38✔
114
      else
115
        @graph.insert(statement)
292✔
116
      end
117
    end
118

119
    ##
120
    # Write out declarations
121
    # @return [void] `self`
122
    def write_prologue
2✔
123
      case
124
      when @options[:stream]
208✔
125
        stream_prologue
24✔
126
      else
127
        super
184✔
128
      end
129
    end
130

131
    ##
132
    # Outputs the TriG representation of all stored triples.
133
    #
134
    # @return [void]
135
    # @see    #write_triple
136
    def write_epilogue
2✔
137
      case
138
      when @options[:stream]
208✔
139
        stream_epilogue
24✔
140
      else
141
        @max_depth = @options[:max_depth] || 3
184✔
142
        @base_uri = RDF::URI(@options[:base_uri])
184✔
143

144
        reset
184✔
145

146
        log_debug {"serialize: repo: #{@repo.size}"}
278✔
147

148
        preprocess
184✔
149
        start_document
184✔
150

151
        @graph_names = order_graphs
184✔
152
        @graph_names.each do |graph_name|
184✔
153
          log_depth do
112✔
154
            log_debug {"graph_name: #{graph_name.inspect}"}
216✔
155
            reset
112✔
156
            @options[:log_depth] = graph_name ? 1 : 0
112✔
157

158
            if graph_name
112✔
159
              @output.write("\n#{format_term(graph_name)} {")
22✔
160
            end
161

162
            # Restrict view to the particular graph
163
            @graph = @repo.project_graph(graph_name)
112✔
164

165
            # Pre-process statements again, but in the specified graph
166
            @graph.each {|st| preprocess_statement(st)}
404✔
167

168
            # Remove lists that are referenced and have non-list properties,
169
            # or are present in more than one graph, or have elements
170
            # that are present in more than one graph;
171
            # these are legal, but can't be serialized as lists
172
            @lists.reject! do |node, list|
112✔
173
              ref_count(node) > 0 && prop_count(node) > 0 ||
64✔
174
              list.subjects.any? {|elt| !resource_in_single_graph?(elt)}
78✔
175
            end
176

177
            order_subjects.each do |subject|
112✔
178
              unless is_done?(subject)
178✔
179
                statement(subject)
128✔
180
              end
181
            end
182

183
            @output.puts("}") if graph_name
112✔
184
          end
185
        end
186
      end
187
      raise RDF::WriterError, "Errors found during processing" if log_statistics[:error]
208✔
188
    end
189

190
    protected
2✔
191

192
    # Add additional constraint that the resource must be in a single graph
193
    # and must not be a graph name
194
    def blankNodePropertyList?(resource, position)
2✔
195
      super && resource_in_single_graph?(resource) && !@graph_names.include?(resource)
1,226✔
196
    end
197

198
    def resource_in_single_graph?(resource)
2✔
199
      graph_names = @repo.query({subject: resource}).map(&:graph_name)
164✔
200
      graph_names += @repo.query({object: resource}).map(&:graph_name)
164✔
201
      graph_names.uniq.length <= 1
164✔
202
    end
203

204
    # Order graphs for output
205
    def order_graphs
2✔
206
      log_debug("order_graphs") {@repo.graph_names.to_a.inspect}
278✔
207
      graph_names = @repo.graph_names.to_a.sort
184✔
208
      
209
      # include default graph, if necessary
210
      graph_names.unshift(nil) unless @repo.query({graph_name: false}).to_a.empty?
184✔
211
      
212
      graph_names
184✔
213
    end
214

215
    # Perform any statement preprocessing required. This is used to perform reference counts and determine required
216
    # prefixes.
217
    # @param [Statement] statement
218
    def preprocess_statement(statement, as_subject: true)
2✔
219
      super
680✔
220
      get_pname(statement.graph_name) if statement.has_graph?
680✔
221
    end
222
  end
223
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc