github.com/gopherd/gonum@v0.0.4/graph/formats/rdf/rdf_line_example_test.go (about)

     1  // Copyright ©2020 The Gonum Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package rdf_test
     6  
     7  import (
     8  	"fmt"
     9  	"log"
    10  	"strings"
    11  
    12  	"github.com/gopherd/gonum/graph"
    13  	"github.com/gopherd/gonum/graph/encoding"
    14  	"github.com/gopherd/gonum/graph/encoding/dot"
    15  	"github.com/gopherd/gonum/graph/formats/rdf"
    16  	"github.com/gopherd/gonum/graph/multi"
    17  )
    18  
    19  // foodNode implements graph.Node, dot.Node and encoding.Attributer
    20  // to allow the RDF term value to be given to the DOT encoder.
    21  type foodNode struct {
    22  	rdf.Term
    23  }
    24  
    25  func (n foodNode) DOTID() string {
    26  	text, _, kind, err := n.Term.Parts()
    27  	if err != nil {
    28  		return fmt.Sprintf("error:%s", n.Term.Value)
    29  	}
    30  	switch kind {
    31  	case rdf.Blank:
    32  		return n.Term.Value
    33  	case rdf.IRI:
    34  		return text
    35  	case rdf.Literal:
    36  		return fmt.Sprintf("%q", text)
    37  	default:
    38  		return fmt.Sprintf("invalid:%s", n.Term.Value)
    39  	}
    40  }
    41  
    42  func (n foodNode) Attributes() []encoding.Attribute {
    43  	_, qual, _, err := n.Term.Parts()
    44  	if err != nil {
    45  		return []encoding.Attribute{{Key: "error", Value: err.Error()}}
    46  	}
    47  	if qual == "" {
    48  		return nil
    49  	}
    50  	parts := strings.Split(qual, ":")
    51  	return []encoding.Attribute{{Key: parts[0], Value: parts[1]}}
    52  }
    53  
    54  // foodLine implements graph.Line and encoding.Attributer to
    55  // allow the line's RDF term value to be given to the DOT
    56  // encoder and for the nodes to be shimmed to the foodNode
    57  // type.
    58  //
    59  // It also implements line reversal for the semantics of
    60  // a food web with some taxonomic information.
    61  type foodLine struct {
    62  	*rdf.Statement
    63  }
    64  
    65  func (l foodLine) From() graph.Node { return foodNode{l.Subject} }
    66  func (l foodLine) To() graph.Node   { return foodNode{l.Object} }
    67  func (l foodLine) ReversedLine() graph.Line {
    68  	if l.Predicate.Value == "<tax:is>" {
    69  		// This should remain unreversed, so return as is.
    70  		return l
    71  	}
    72  	s := *l.Statement
    73  	// Reverse the line end points.
    74  	s.Subject, s.Object = s.Object, s.Subject
    75  	// Invert the semantics of the predicate.
    76  	switch s.Predicate.Value {
    77  	case "<eco:eats>":
    78  		s.Predicate.Value = "<eco:eaten-by>"
    79  	case "<eco:eaten-by>":
    80  		s.Predicate.Value = "<eco:eats>"
    81  	case "<tax:is-a>":
    82  		s.Predicate.Value = "<tax:includes>"
    83  	case "<tax:includes>":
    84  		s.Predicate.Value = "<tax:is-a>"
    85  	default:
    86  		panic("invalid predicate")
    87  	}
    88  	// All IDs returned by the RDF parser are positive, so
    89  	// sign reverse the edge ID to avoid any collisions.
    90  	s.Predicate.UID *= -1
    91  	return foodLine{&s}
    92  }
    93  
    94  func (l foodLine) Attributes() []encoding.Attribute {
    95  	text, _, _, err := l.Predicate.Parts()
    96  	if err != nil {
    97  		return []encoding.Attribute{{Key: "error", Value: err.Error()}}
    98  	}
    99  	parts := strings.Split(text, ":")
   100  	return []encoding.Attribute{{Key: parts[0], Value: parts[1]}}
   101  }
   102  
   103  // expand copies src into dst, adding the reversal of each line if it is
   104  // distinct.
   105  func expand(dst, src *multi.DirectedGraph) {
   106  	it := src.Edges()
   107  	for it.Next() {
   108  		lit := it.Edge().(multi.Edge)
   109  		for lit.Next() {
   110  			l := lit.Line()
   111  			r := l.ReversedLine()
   112  			dst.SetLine(l)
   113  			if l == r {
   114  				continue
   115  			}
   116  			dst.SetLine(r)
   117  		}
   118  	}
   119  }
   120  
   121  func ExampleStatement_ReversedLine() {
   122  	const statements = `
   123  _:wolf <tax:is-a> _:animal .
   124  _:wolf <tax:is> "Wolf"^^<tax:common> .
   125  _:wolf <tax:is> "Canis lupus"^^<tax:binomial> .
   126  _:wolf <eco:eats> _:sheep .
   127  _:sheep <tax:is-a> _:animal .
   128  _:sheep <tax:is> "Sheep"^^<tax:common> .
   129  _:sheep <tax:is> "Ovis aries"^^<tax:binomial> .
   130  _:sheep <eco:eats> _:grass .
   131  _:grass <tax:is-a> _:plant .
   132  _:grass <tax:is> "Grass"^^<tax:common> .
   133  _:grass <tax:is> "Lolium perenne"^^<tax:binomial> .
   134  _:grass <tax:is> "Festuca rubra"^^<tax:binomial> .
   135  _:grass <tax:is> "Poa pratensis"^^<tax:binomial> .
   136  `
   137  
   138  	// Decode the statement stream and insert the lines into a multigraph.
   139  	g := multi.NewDirectedGraph()
   140  	dec := rdf.NewDecoder(strings.NewReader(statements))
   141  	for {
   142  		l, err := dec.Unmarshal()
   143  		if err != nil {
   144  			break
   145  		}
   146  
   147  		// Wrap the line with a shim type to allow the RDF values
   148  		// to be passed to the DOT marshaling routine.
   149  		g.SetLine(foodLine{l})
   150  	}
   151  
   152  	h := multi.NewDirectedGraph()
   153  	expand(h, g)
   154  
   155  	// Marshal the graph into DOT.
   156  	b, err := dot.MarshalMulti(h, "food web", "", "\t")
   157  	if err != nil {
   158  		log.Fatal(err)
   159  	}
   160  	fmt.Printf("%s\n\n", b)
   161  
   162  	// Output:
   163  	//
   164  	// digraph "food web" {
   165  	// 	// Node definitions.
   166  	// 	"_:wolf";
   167  	// 	"_:animal";
   168  	// 	"Wolf" [tax=common];
   169  	// 	"Canis lupus" [tax=binomial];
   170  	// 	"_:sheep";
   171  	// 	"Sheep" [tax=common];
   172  	// 	"Ovis aries" [tax=binomial];
   173  	// 	"_:grass";
   174  	// 	"_:plant";
   175  	// 	"Grass" [tax=common];
   176  	// 	"Lolium perenne" [tax=binomial];
   177  	// 	"Festuca rubra" [tax=binomial];
   178  	// 	"Poa pratensis" [tax=binomial];
   179  	//
   180  	// 	// Edge definitions.
   181  	// 	"_:wolf" -> "_:animal" [tax="is-a"];
   182  	// 	"_:wolf" -> "Wolf" [tax=is];
   183  	// 	"_:wolf" -> "Canis lupus" [tax=is];
   184  	// 	"_:wolf" -> "_:sheep" [eco=eats];
   185  	// 	"_:animal" -> "_:wolf" [tax=includes];
   186  	// 	"_:animal" -> "_:sheep" [tax=includes];
   187  	// 	"_:sheep" -> "_:wolf" [eco="eaten-by"];
   188  	// 	"_:sheep" -> "_:animal" [tax="is-a"];
   189  	// 	"_:sheep" -> "Sheep" [tax=is];
   190  	// 	"_:sheep" -> "Ovis aries" [tax=is];
   191  	// 	"_:sheep" -> "_:grass" [eco=eats];
   192  	// 	"_:grass" -> "_:sheep" [eco="eaten-by"];
   193  	// 	"_:grass" -> "_:plant" [tax="is-a"];
   194  	// 	"_:grass" -> "Grass" [tax=is];
   195  	// 	"_:grass" -> "Lolium perenne" [tax=is];
   196  	// 	"_:grass" -> "Festuca rubra" [tax=is];
   197  	// 	"_:grass" -> "Poa pratensis" [tax=is];
   198  	// 	"_:plant" -> "_:grass" [tax=includes];
   199  	// }
   200  }