File Coverage

blib/lib/RDF/Trine/Exporter/GraphViz.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1 2     2   23860 use strict;
  2         4  
  2         78  
2 2     2   11 use warnings;
  2         3  
  2         136  
3             package RDF::Trine::Exporter::GraphViz;
4             {
5             $RDF::Trine::Exporter::GraphViz::VERSION = '0.141';
6             }
7             #ABSTRACT: Serialize RDF graphs as dot graph diagrams
8              
9 2     2   2380 use RDF::Trine;
  0            
  0            
10             use GraphViz qw(2.04);
11             use Scalar::Util qw(reftype blessed);
12             use Carp;
13             use RDF::NS;
14              
15             our $NS = RDF::NS->new('any');
16              
17             # TODO: create RDF::Trine::Exporter as base class
18             use base qw(RDF::Trine::Serializer);
19              
20             our %FORMATS = (
21             dot => 'text/plain',
22             ps => 'application/postscript',
23             hpgl => 'application/vnd.hp-hpgl',
24             pcl => 'application/vnd.hp-pcl',
25             mif => 'application/vnd.mif',
26             gif => 'image/gif',
27             jpeg => 'image/jpeg',
28             png => 'image/png',
29             wbmp => 'image/vnd.wap.wbmp',
30             cmapx => 'text/html',
31             imap => 'application/x-httpd-imap',
32             'map' => 'application/x-httpd-imap',
33             vrml => 'model/vrml',
34             fig => 'image/x-xfig',
35             svg => 'image/svg+xml',
36             svgz => 'image/svg+xml',
37             );
38              
39             sub new {
40             my ($class, %args) = @_;
41              
42             my $self = bless \%args, $class;
43              
44             $self->{as} ||= 'dot';
45             croak 'Unknown format ' . $self->{as}
46             unless $FORMATS{ $self->{as} };
47              
48             $self->{mime} ||= $FORMATS{ $self->{as} };
49              
50             $self->{style} ||= { rankdir => 'BT', concentrate => 1 };
51             $self->{node} ||= { shape => 'plaintext', color => 'gray' };
52             $self->{resource} ||= { shape => 'box', style => 'rounded',
53             fontcolor => 'blue' };
54             $self->{literal} ||= { shape => 'box' };
55             $self->{blank} ||= { label => '', shape => 'point',
56             fillcolor => 'white', color => 'gray', width => '0.3' };
57             $self->{variable} ||= { fontcolor => 'darkslategray' };
58             $self->{prevar} ||= '?';
59             $self->{alias} ||= { };
60              
61             if ( $self->{url} and (reftype($self->{url})||'') ne 'CODE' ) {
62             $self->{url} = sub { shift->uri };
63             }
64              
65             return $self;
66             }
67              
68             sub media_types {
69             my $self = shift;
70             return ($self->{mime});
71             }
72              
73             sub to_string {
74             my ($self, $rdf, %options) = @_;
75              
76             my $g = $self->as_graphviz($rdf, %options);
77              
78             my $format = ($options{as} || $self->{as});
79             die "Unknown serialization format $format" unless $FORMATS{$format};
80              
81             my $method = "as_$format";
82             $method = 'as_canon' if $method eq 'as_dot';
83             $method = 'as_imap' if $method eq 'as_map';
84              
85             my $data;
86             eval {
87             # TODO: Catch error message sent to STDOUT by dot if this fails.
88             $g->$method( \$data );
89             };
90              
91             return $data;
92             }
93              
94             sub to_file {
95             my ($self, $file, $rdf, %options) = @_;
96              
97             croak "missing file parameter" unless defined $file;
98              
99             if (!ref $file) {
100             $options{as} = $1 if $file =~ /\.([^.]+)$/ and $FORMATS{$1};
101             open (my $fh, '>', $file);
102             $file = $fh;
103             }
104              
105             print {$file} $self->to_string( $rdf, %options );
106             }
107              
108             sub serialize_model_to_string {
109             shift->to_string(@_);
110             }
111              
112             sub serialize_model_to_file {
113             shift->to_file(@_);
114             }
115              
116             sub serialize_iterator_to_string {
117             shift->to_string(@_);
118             }
119              
120             sub as_graphviz {
121             my ($self, $rdf, %options) = @_;
122             return unless blessed $rdf;
123             $rdf = $rdf->as_stream if $rdf->isa('RDF::Trine::Model');
124             return $self->iterator_as_graphviz( $rdf, %options );
125             }
126              
127             sub iterator_as_graphviz {
128             my ($self, $iter, %options) = @_;
129              
130             # We could make use of named graphs in a later version...
131             $options{title} ||= $self->{title};
132              
133             $options{namespaces} ||= $self->{namespaces} || $NS;
134             $options{root} ||= $self->{root};
135             $options{prevar} ||= $self->{prevar};
136              
137             my $get_alias = $options{alias} || $self->{alias} || { };
138             if (((reftype $get_alias) || '') eq 'HASH') {
139             my $hash = $get_alias;
140             $get_alias = sub { $hash->{shift} };
141             }
142              
143             # Basic options. Should be more configurable.
144             my %gopt = %{$self->{style}};
145             $gopt{node} ||= $self->{node};
146              
147             my %root_style = ( color => 'red' );
148              
149             $gopt{name} = $options{title} if defined $options{title};
150              
151             my $get_edge = $options{edge} || $self->{edge};
152              
153             my $g = GraphViz->new( %gopt );
154             my %nsprefix = reverse %{$options{namespaces}};
155              
156             my $get_label = sub {
157             my $resource = shift;
158             my $label = do { $_ = $resource->uri; $get_alias->( $_ ); };
159             if (!defined $label) {
160             my ($local, $qname) = eval { $resource->qname };
161             my $prefix = $local ? $nsprefix{$local} : "";
162             $label = $prefix ? "$prefix:$qname" : $resource->as_string;
163             }
164             return $label;
165             };
166              
167             my %seen;
168             while (my $t = $iter->next) {
169             my @nodes;
170             my $edge_options = { };
171             if ($get_edge) {
172             $_ = $t->predicate->uri;
173             $edge_options = $get_edge->( $t->predicate );
174             }
175             next unless defined $edge_options;
176              
177             foreach my $pos (qw(subject object)) {
178             my $n = $t->$pos();
179             my $label;
180             if ($n->is_literal) {
181             $label = $n->literal_value;
182             } elsif( $n->is_resource ) {
183             $label = $get_label->($n);
184             } elsif( $n->is_blank ) {
185             $label = $n->as_string;
186             } elsif( $n->is_variable ) {
187             $label = $options{prevar}.$n->name;
188             }
189             # TODO: what about nodes with equal label?
190             push(@nodes, $label);
191             next if ($seen{ $label }++);
192             if ( $n->is_literal ) {
193             # TODO: add language / datatype
194             $g->add_node( $label, %{$self->{literal}} );
195             } elsif ( $n->is_resource ) {
196             my %layout = %{$self->{resource}};
197             $layout{URL} = $self->{url}->( $n ) if $self->{url};
198             if ( ($options{'root'} || '') eq $n->uri ) {
199             $layout{$_} = $root_style{$_} for keys %root_style;
200             }
201             $g->add_node( $label, %layout );
202             } elsif ( $n->is_blank ) {
203             $g->add_node( $label, %{$self->{blank}} );
204             } elsif ( $n->is_variable ) {
205             $g->add_node( $label, %{$self->{variable}} );
206             }
207             }
208              
209             $edge_options->{label} //= $get_label->( $t->predicate );
210             $g->add_edge( @nodes, %$edge_options );
211             }
212              
213             return $g;
214             }
215              
216             1;
217              
218              
219             __END__
220             =pod
221              
222             =head1 NAME
223              
224             RDF::Trine::Exporter::GraphViz - Serialize RDF graphs as dot graph diagrams
225              
226             =head1 VERSION
227              
228             version 0.141
229              
230             =head1 SYNOPSIS
231              
232             use RDF::Trine::Exporter::GraphViz;
233              
234             my $ser = RDF::Trine::Exporter::GraphViz->new( as => 'dot' );
235             my $dot = $ser->to_string( $rdf );
236              
237             $ser->to_file( 'graph.svg', $rdf );
238              
239             # highly configurable
240             my $g = RDF::Trine::Exporter::GraphViz->new(
241             namespaces => {
242             foaf => 'http://xmlns.com/foaf/0.1/'
243             },
244             alias => {
245             'http://www.w3.org/2002/07/owl#sameAs' => '=',
246             },
247             prevar => '$', # variables as '$x' instead of '?x'
248             url => 1, # hyperlink all URIs
249              
250             # see below for more configuration options
251             );
252             $g->to_file( 'test.svg', $model );
253              
254             =head1 DESCRIPTION
255              
256             L<RDF::Trine::Model> includes a nice but somehow misplaced and non-customizable
257             method C<as_graphviz>. This module implements an extended version, put in a
258             extends this method in a RDF::Trine::Exporter object. (actually it is a
259             subclass of L<RDF::Trine::Serializer> as long as RDF::Trine has no common class
260             RDF::Trine::Exporter). This module also includes a command line script
261             L<rdfdot> to create graph diagrams from RDF data.
262              
263             =head1 METHODS
264              
265             This modules derives from L<RDF::Trine::Serializer> with all of its methods (a
266             future version may be derived from RDF::Trine::Exporter). The following methods
267             are of interest in particular:
268              
269             =head2 new ( %options )
270              
271             Creates a new serializer with L<configuration|/CONFIGURATION> options
272             as described below.
273              
274             =head2 to_file ( $file, $rdf [, %options ] )
275              
276             Serialize RDF data, provided as L<RDF::Trine::Iterator> or as
277             L<RDF::Trine::Model> to a file. C<$file> can be a filehandle or file name.
278             The serialization format is automatically derived from known file extensions.
279              
280             =head2 to_string( $rdf [ %options ] )
281              
282             Serialize RDF data, provided as L<RDF::Trine::Iterator> or as
283             L<RDF::Trine::Model> to a string.
284              
285             =head2 as_graphviz ( $rdf [, %options ] )
286              
287             Creates and returns a L<GraphViz> object for further processing. You must
288             provide RDF data as L<RDF::Trine::Iterator> or as L<RDF::Trine::Model>.
289              
290             =head2 media_types
291              
292             Returns the exporter's mime type. For instance if you create an exporter with
293             C<< as => 'svg' >>, this method returns C<< ('image/svg+xml') >>.
294              
295             =head2 serialize_model_to_file ( $file, $model [, %options ] )
296              
297             Provided as alias for C<to_file> for compatibility with other
298             C<RDF::Trine::Exporter> classes.
299              
300             =head2 serialize_model_to_string ( $model [, %options ] )
301              
302             Provided as alias for C<to_string> for compatibility with other
303             C<RDF::Trine::Exporter> classes.
304              
305             =head2 serialize_iterator_to_string ( $iterator [, %options ] )
306              
307             Serialize a L<RDF::Trine::Iterator> as graph diagram to a string.
308              
309             =head2 iterator_as_graphviz ( $iterator )
310              
311             Internal core method, used by C<to_string> and C<to_file>, which one should
312             better call instead.
313              
314             =head1 CONFIGURATION
315              
316             The following configuration options can be set when creating a new object.
317              
318             =over 4
319              
320             =item as
321              
322             Specific serialization format with C<dot> as default. Supported formats include
323             canonical DOT format (C<dot>), Graphics Interchange Format (C<gif>), JPEG File
324             Interchange Format (C<jpeg>), Portable Network Graphics (C<png>), Scalable
325             Vector Graphics (C<svg> and C<svgz>), server side HTML imape map (C<imap> or
326             C<map>), client side HTML image map (C<cmapx>), PostScript (C<ps>), Hewlett
327             Packard Graphic Language (C<hpgl>), Printer Command Language (C<pcl>), FIG
328             format (C<fig>), Maker Interchange Format (C<mif>), Wireless BitMap format
329             (C<wbmp>), and Virtual Reality Modeling Language (C<vrml>).
330              
331             =item mime
332              
333             Mime type. By default automatically set based on C<as>.
334              
335             =item style
336              
337             General graph style options as hash reference. Defaults to
338             C<< { rankdir => 'TB', concentrate => 1 } >>.
339              
340             =item node
341              
342             Hash reference with general options to style nodes. Defaults to
343             C<< { shape => 'plaintext', color => 'gray' } >>.
344              
345             =item resource
346              
347             Hash reference with options to style resource nodes. Defaults to
348             C<< { shape => 'box', style => 'rounded', fontcolor => 'blue' } >>.
349              
350             =item literal
351              
352             Hash reference with options to style literal nodes. Defaults to
353             C<< { shape => 'box' } >>.
354              
355             =item blank
356              
357             Hash reference with options to style blank nodes. Defaults to C<< { label => '',
358             shape => 'point', fillcolor => 'white', color => 'gray', width => '0.3' } >>.
359              
360             =item edge
361              
362             Code referece with a function that get passed a predicate and variable C<$_>
363             set to the predicate's URI. The function must return undef to skip the RDF
364             statement or a hash reference with options to style the edge.
365              
366             =item url
367              
368             Add clickable URLs to nodes You can either provide a boolean value or a code
369             reference that returns an URL when given a L<RDF::Trine::Node::Resource>.
370              
371             =item alias
372              
373             Hash reference with URL aliases to show as resource and predicate labels.
374              
375             =item variable
376              
377             Hash reference with options to style variable nodes. Defaults to C<< {
378             fontcolor => 'darkslategray' } >>.
379              
380             =item prevar
381              
382             Which character to prepend to variable names. Defaults to '?'. You can
383             also set it to '$'. By now the setting does not affect variables
384             in Notation3 formulas.
385              
386             =item root
387              
388             An URI that is marked as 'root' node.
389              
390             =item title
391              
392             Add a title to the graph.
393              
394             =item namespaces
395              
396             Hash reference with mapping from prefixes to URI namespaces to abbreviate URIs.
397             By default the prefix mapping from L<RDF::NS> is used.
398              
399             =back
400              
401             =head1 LIMITATIONS
402              
403             This serializer does not support C<negotiate> on purpose. It may optionally be
404             enabled in a future version. GraphViz may fail on large graphs, its error
405             message is not catched yet. Configuration in general is not fully covered by
406             unit tests. Identifiers of blank nodes are not included.
407              
408             =head1 AUTHOR
409              
410             Jakob Voß <voss@gbv.de>
411              
412             =head1 COPYRIGHT AND LICENSE
413              
414             This software is copyright (c) 2012 by Jakob Voß.
415              
416             This is free software; you can redistribute it and/or modify it under
417             the same terms as the Perl 5 programming language system itself.
418              
419             =cut
420