graph.go 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  1. package gold
  2. import (
  3. "bytes"
  4. "crypto/tls"
  5. "encoding/json"
  6. "fmt"
  7. "io"
  8. "io/ioutil"
  9. "log"
  10. "net/http"
  11. "net/url"
  12. "os"
  13. jsonld "github.com/linkeddata/gojsonld"
  14. crdf "github.com/presbrey/goraptor"
  15. )
  16. // AnyGraph defines methods common to Graph types
  17. type AnyGraph interface {
  18. Len() int
  19. URI() string
  20. Parse(io.Reader, string)
  21. Serialize(string) (string, error)
  22. JSONPatch(io.Reader) error
  23. SPARQLUpdate(*SPARQLUpdate) (int, error)
  24. IterTriples() chan *Triple
  25. ReadFile(string)
  26. WriteFile(*os.File, string) error
  27. }
  28. var (
  29. httpClient = &http.Client{
  30. Transport: &http.Transport{
  31. TLSClientConfig: &tls.Config{
  32. InsecureSkipVerify: true,
  33. },
  34. },
  35. }
  36. )
  37. // Graph structure
  38. type Graph struct {
  39. triples map[*Triple]bool
  40. uri string
  41. term Term
  42. }
  43. // NewGraph creates a Graph object
  44. func NewGraph(uri string) *Graph {
  45. if uri[:5] != "http:" && uri[:6] != "https:" {
  46. panic(uri)
  47. }
  48. return &Graph{
  49. triples: make(map[*Triple]bool),
  50. uri: uri,
  51. term: NewResource(uri),
  52. }
  53. }
  54. // Len returns the length of the graph as number of triples in the graph
  55. func (g *Graph) Len() int {
  56. return len(g.triples)
  57. }
  58. // Term returns a Graph Term object
  59. func (g *Graph) Term() Term {
  60. return g.term
  61. }
  62. // URI returns a Graph URI object
  63. func (g *Graph) URI() string {
  64. return g.uri
  65. }
  66. func term2term(term crdf.Term) Term {
  67. switch term := term.(type) {
  68. case *crdf.Blank:
  69. return NewBlankNode(term.String())
  70. case *crdf.Literal:
  71. if len(term.Datatype) > 0 {
  72. return NewLiteralWithLanguageAndDatatype(term.Value, term.Lang, NewResource(term.Datatype))
  73. }
  74. return NewLiteral(term.Value)
  75. case *crdf.Uri:
  76. return NewResource(term.String())
  77. }
  78. return nil
  79. }
  80. func jterm2term(term jsonld.Term) Term {
  81. switch term := term.(type) {
  82. case *jsonld.BlankNode:
  83. return NewBlankNode(term.RawValue())
  84. case *jsonld.Literal:
  85. if term.Datatype != nil && len(term.Datatype.String()) > 0 {
  86. return NewLiteralWithLanguageAndDatatype(term.Value, term.Language, NewResource(term.Datatype.RawValue()))
  87. }
  88. return NewLiteral(term.Value)
  89. case *jsonld.Resource:
  90. return NewResource(term.RawValue())
  91. }
  92. return nil
  93. }
  94. // One returns one triple based on a triple pattern of S, P, O objects
  95. func (g *Graph) One(s Term, p Term, o Term) *Triple {
  96. for triple := range g.IterTriples() {
  97. if isNilOrEquals(s, triple.Subject) && isNilOrEquals(p, triple.Predicate) && isNilOrEquals(o, triple.Object) {
  98. return triple
  99. }
  100. }
  101. return nil
  102. }
  103. // IterTriples iterates through all the triples in a graph
  104. func (g *Graph) IterTriples() (ch chan *Triple) {
  105. ch = make(chan *Triple)
  106. go func() {
  107. for triple := range g.triples {
  108. ch <- triple
  109. }
  110. close(ch)
  111. }()
  112. return ch
  113. }
  114. // Add is used to add a Triple object to the graph
  115. func (g *Graph) Add(t *Triple) {
  116. g.triples[t] = true
  117. }
  118. // AddTriple is used to add a triple made of individual S, P, O objects
  119. func (g *Graph) AddTriple(s Term, p Term, o Term) {
  120. g.triples[NewTriple(s, p, o)] = true
  121. }
  122. // Remove is used to remove a Triple object
  123. func (g *Graph) Remove(t *Triple) {
  124. delete(g.triples, t)
  125. }
  126. // All is used to return all triples that match a given pattern of S, P, O objects
  127. func (g *Graph) All(s Term, p Term, o Term) []*Triple {
  128. var triples []*Triple
  129. for triple := range g.IterTriples() {
  130. if s == nil && p == nil && o == nil {
  131. continue
  132. }
  133. if isNilOrEquals(s, triple.Subject) && isNilOrEquals(p, triple.Predicate) && isNilOrEquals(o, triple.Object) {
  134. triples = append(triples, triple)
  135. }
  136. }
  137. return triples
  138. }
  139. // AddStatement adds a Statement object
  140. func (g *Graph) AddStatement(st *crdf.Statement) {
  141. g.AddTriple(term2term(st.Subject), term2term(st.Predicate), term2term(st.Object))
  142. }
  143. // Parse is used to parse RDF data from a reader, using the provided mime type
  144. func (g *Graph) Parse(reader io.Reader, mime string) {
  145. parserName := mimeParser[mime]
  146. if len(parserName) == 0 {
  147. parserName = "guess"
  148. }
  149. if parserName == "jsonld" {
  150. buf := new(bytes.Buffer)
  151. if _, err := buf.ReadFrom(reader); err != nil {
  152. log.Println(err)
  153. return
  154. }
  155. jsonData, err := jsonld.ReadJSON(buf.Bytes())
  156. if err != nil {
  157. log.Println(err)
  158. return
  159. }
  160. options := &jsonld.Options{}
  161. options.Base = ""
  162. options.ProduceGeneralizedRdf = false
  163. dataSet, err := jsonld.ToRDF(jsonData, options)
  164. if err != nil {
  165. log.Println(err)
  166. return
  167. }
  168. for t := range dataSet.IterTriples() {
  169. g.AddTriple(jterm2term(t.Subject), jterm2term(t.Predicate), jterm2term(t.Object))
  170. }
  171. return
  172. }
  173. parser := crdf.NewParser(parserName)
  174. parser.SetLogHandler(func(level int, message string) {
  175. log.Println(message)
  176. })
  177. defer parser.Free()
  178. for s := range parser.Parse(reader, g.uri) {
  179. g.AddStatement(s)
  180. }
  181. }
  182. // ParseBase is used to parse RDF data from a reader, using the provided mime type and a base URI
  183. func (g *Graph) ParseBase(reader io.Reader, mime string, baseURI string) {
  184. if len(baseURI) < 1 {
  185. baseURI = g.uri
  186. }
  187. parserName := mimeParser[mime]
  188. if len(parserName) == 0 {
  189. parserName = "guess"
  190. }
  191. parser := crdf.NewParser(parserName)
  192. defer parser.Free()
  193. out := parser.Parse(reader, baseURI)
  194. for s := range out {
  195. g.AddStatement(s)
  196. }
  197. }
  198. // ReadFile is used to read RDF data from a file into the graph
  199. func (g *Graph) ReadFile(filename string) {
  200. stat, err := os.Stat(filename)
  201. if os.IsNotExist(err) {
  202. return
  203. }
  204. if stat.IsDir() {
  205. return
  206. }
  207. if !stat.IsDir() && err != nil {
  208. log.Println(err)
  209. return
  210. }
  211. f, err := os.OpenFile(filename, os.O_RDONLY, 0)
  212. defer f.Close()
  213. if err != nil {
  214. log.Println(err)
  215. return
  216. }
  217. g.Parse(f, "text/turtle")
  218. }
  219. // AppendFile is used to append RDF from a file, using a base URI
  220. func (g *Graph) AppendFile(filename string, baseURI string) {
  221. _, err := os.Stat(filename)
  222. if os.IsNotExist(err) {
  223. return
  224. } else if err != nil {
  225. log.Println(err)
  226. return
  227. }
  228. f, err := os.OpenFile(filename, os.O_RDONLY, 0)
  229. defer f.Close()
  230. if err != nil {
  231. log.Println(err)
  232. return
  233. }
  234. g.ParseBase(f, "text/turtle", baseURI)
  235. }
  236. // LoadURI is used to load RDF data from a specific URI
  237. func (g *Graph) LoadURI(uri string) (err error) {
  238. doc := defrag(uri)
  239. q, err := http.NewRequest("GET", doc, nil)
  240. if err != nil {
  241. return
  242. }
  243. q.Header.Set("Accept", "text/turtle,text/n3,application/rdf+xml")
  244. r, err := httpClient.Do(q)
  245. if err != nil {
  246. return
  247. }
  248. if r != nil {
  249. defer r.Body.Close()
  250. if r.StatusCode == 200 {
  251. g.ParseBase(r.Body, r.Header.Get("Content-Type"), doc)
  252. } else {
  253. err = fmt.Errorf("Could not fetch graph from %s - HTTP %d", uri, r.StatusCode)
  254. }
  255. }
  256. return
  257. }
  258. func term2C(t Term) crdf.Term {
  259. switch t := t.(type) {
  260. case *BlankNode:
  261. node := crdf.Blank(t.ID)
  262. return &node
  263. case *Resource:
  264. node := crdf.Uri(t.URI)
  265. return &node
  266. case *Literal:
  267. dt := ""
  268. if t.Datatype != nil {
  269. dt = t.Datatype.(*Resource).URI
  270. }
  271. node := crdf.Literal{
  272. Value: t.Value,
  273. Datatype: dt,
  274. Lang: t.Language,
  275. }
  276. return &node
  277. }
  278. return nil
  279. }
  280. func (g *Graph) serializeJSONLd() ([]byte, error) {
  281. r := []map[string]interface{}{}
  282. for elt := range g.IterTriples() {
  283. one := map[string]interface{}{
  284. "@id": elt.Subject.(*Resource).URI,
  285. }
  286. switch t := elt.Object.(type) {
  287. case *Resource:
  288. one[elt.Predicate.(*Resource).URI] = []map[string]string{
  289. {
  290. "@id": t.URI,
  291. },
  292. }
  293. break
  294. case *Literal:
  295. v := map[string]string{
  296. "@value": t.Value,
  297. }
  298. if t.Datatype != nil && len(t.Datatype.String()) > 0 {
  299. v["@type"] = t.Datatype.String()
  300. }
  301. if len(t.Language) > 0 {
  302. v["@language"] = t.Language
  303. }
  304. one[elt.Predicate.(*Resource).URI] = []map[string]string{v}
  305. }
  306. r = append(r, one)
  307. }
  308. return json.Marshal(r)
  309. }
  310. // Serialize is used to serialize a graph based on a given mime type
  311. func (g *Graph) Serialize(mime string) (string, error) {
  312. if mime == "application/ld+json" {
  313. b, err := g.serializeJSONLd()
  314. return string(b), err
  315. }
  316. serializerName := mimeSerializer[mime]
  317. if len(serializerName) == 0 {
  318. serializerName = "turtle"
  319. }
  320. serializer := crdf.NewSerializer(serializerName)
  321. defer serializer.Free()
  322. ch := make(chan *crdf.Statement, 1024)
  323. go func() {
  324. for triple := range g.IterTriples() {
  325. ch <- &crdf.Statement{
  326. Subject: term2C(triple.Subject),
  327. Predicate: term2C(triple.Predicate),
  328. Object: term2C(triple.Object),
  329. }
  330. }
  331. close(ch)
  332. }()
  333. return serializer.Serialize(ch, g.uri)
  334. }
  335. // WriteFile is used to dump RDF from a Graph into a file
  336. func (g *Graph) WriteFile(file *os.File, mime string) error {
  337. serializerName := mimeSerializer[mime]
  338. if len(serializerName) == 0 {
  339. serializerName = "turtle"
  340. }
  341. serializer := crdf.NewSerializer(serializerName)
  342. defer serializer.Free()
  343. err := serializer.SetFile(file, g.uri)
  344. if err != nil {
  345. return err
  346. }
  347. ch := make(chan *crdf.Statement, 1024)
  348. go func() {
  349. for triple := range g.IterTriples() {
  350. ch <- &crdf.Statement{
  351. Subject: term2C(triple.Subject),
  352. Predicate: term2C(triple.Predicate),
  353. Object: term2C(triple.Object),
  354. }
  355. }
  356. close(ch)
  357. }()
  358. serializer.AddN(ch)
  359. return nil
  360. }
  361. type jsonPatch map[string]map[string][]struct {
  362. Value string `json:"value"`
  363. Type string `json:"type"`
  364. }
  365. // JSONPatch is used to perform a PATCH operation on a Graph using data from the reader
  366. func (g *Graph) JSONPatch(r io.Reader) error {
  367. v := make(jsonPatch)
  368. data, err := ioutil.ReadAll(r)
  369. if err != nil {
  370. return err
  371. }
  372. err = json.Unmarshal(data, &v)
  373. if err != nil {
  374. return err
  375. }
  376. base, _ := url.Parse(g.uri)
  377. for s, sv := range v {
  378. su, _ := base.Parse(s)
  379. for p, pv := range sv {
  380. pu, _ := base.Parse(p)
  381. subject := NewResource(su.String())
  382. predicate := NewResource(pu.String())
  383. for _, triple := range g.All(subject, predicate, nil) {
  384. g.Remove(triple)
  385. }
  386. for _, o := range pv {
  387. switch o.Type {
  388. case "uri":
  389. g.AddTriple(subject, predicate, NewResource(o.Value))
  390. case "literal":
  391. g.AddTriple(subject, predicate, NewLiteral(o.Value))
  392. }
  393. }
  394. }
  395. }
  396. return nil
  397. }
  398. // isNilOrEquals is a helper function returns true if first term is nil, otherwise checks equality
  399. func isNilOrEquals(t1 Term, t2 Term) bool {
  400. if t1 == nil {
  401. return true
  402. }
  403. return t2.Equal(t1)
  404. }