{"id":32181886,"url":"https://github.com/ontodev/edn-ld","last_synced_at":"2025-10-21T22:54:51.691Z","repository":{"id":32329567,"uuid":"35904908","full_name":"ontodev/edn-ld","owner":"ontodev","description":"A simple linked data tool","archived":false,"fork":false,"pushed_at":"2020-04-18T14:18:42.000Z","size":40,"stargazers_count":37,"open_issues_count":0,"forks_count":3,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-08-02T09:49:21.149Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"http://try.edn-ld.com","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ontodev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-05-19T19:51:07.000Z","updated_at":"2024-11-19T17:57:00.000Z","dependencies_parsed_at":"2022-09-12T01:30:52.434Z","dependency_job_id":null,"html_url":"https://github.com/ontodev/edn-ld","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ontodev/edn-ld","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ontodev%2Fedn-ld","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ontodev%2Fedn-ld/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ontodev%2Fedn-ld/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ontodev%2Fedn-ld/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ontodev","download_url":"https://codeload.github.com/ontodev/edn-ld/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ontodev%2Fedn-ld/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":280348057,"owners_count":26315367,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-21T02:00:06.614Z","response_time":58,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-10-21T22:54:50.599Z","updated_at":"2025-10-21T22:54:51.682Z","avatar_url":"https://github.com/ontodev.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# EDN-LD\n\n[![Build Status](https://travis-ci.org/ontodev/edn-ld.svg?branch=master)](https://travis-ci.org/ontodev/edn-ld)\n\nEDN-LD is a set of conventions and a library for working with [Linked Data (LD)](http://linkeddata.org) using [Extensible Data Notation (EDN)](https://github.com/edn-format/edn) and the [Clojure programming language](http://clojure.org). EDN-LD builds on EDN and [JSON-LD](http://json-ld.org), but is not otherwise affiliated with those projects.\n\n**[Try EDN-LD online!](http://try.edn-ld.com)**\n\nThis project is in early development!\n\n\n## Linked Data\n\nLinked data is an approach to working with data on the Web:\n\n- instead of tables we have graphs -- networks of data\n- instead of rows we have resources -- nodes in the graph\n- the values in our cells are also nodes -- either resources or literals: strings, numbers, dates\n- and instead of columns we have named relations that link nodes to form the graph\n\nJust think of your tables as big sets of row-column-cell \"triples\". By switching from rigid tables to flexible graphs, we can easily merge data from across the web.\n\nLinked data is simple. The tools for working with it are powerful: big Java libraries such as [Jena](https://jena.apache.org), [Sesame](http://rdf4j.org), [OWLAPI](http://owlapi.sourceforge.net), etc. Unfortunately, most of the tools are not simple.\n\nEDN-LD is a simple linked data tool.\n\n\n## Install\n\nEDN-LD is a Clojure library. The easiest way to get started is to use [Leiningen](http://leiningen.org) and add this to your `project.clj` dependencies:\n\n    [edn-ld \"0.3.0\"]\n\n\n## Tutorial\n\nTry out EDN-LD with our [interactive online tutorial](http://try.edn-ld.com), or by cloning this project and starting a REPL:\n\n    $ git clone https://github.com/ontodev/edn-ld.git\n    $ cd edn-ld\n    $ lein repl\n    nREPL server started ...\n    user=\u003e (use 'edn-ld.core 'edn-ld.common)\n    nil\n    user=\u003e (require '[clojure.string :as string])\n    nil\n    user=\u003e \"Ready!\"\n    Ready!\n\nSay we have a (very small) table of books and their authors called `books.tsv`:\n\nTitle     | Author\n----------|-------\nThe Iliad | Homer\n\nA common way to represent this in Clojure is as a list of maps, with the column names as the keys. We can `slurp` and split the data until we get what we want:\n\n    user=\u003e (defn split-row [row] (string/split row #\"\\t\"))\n    #'user/split-row\n    user=\u003e (defn read-tsv [path] (-\u003e\u003e path slurp string/split-lines (drop 1) (mapv split-row)))\n    #'user/read-tsv\n    user=\u003e (def rows (read-tsv \"test-resources/books.tsv\"))\n    #'user/rows\n    user=\u003e rows\n    [[\"The Iliad\" \"Homer\"]]\n\nNow we use `zipmap` to associate keys with values:\n\n    user=\u003e (def data (mapv (partial zipmap [:title :author]) rows))\n    #'user/data\n    user=\u003e data\n    [{:title \"The Iliad\", :author \"Homer\"}]\n\nWe have the data in a convenient shape, but what does it mean? Well, there's some resource that has \"The Iliad\" as its title, and some guy named \"Homer\" who is the author of that resource. We also know from the context that it's a book.\n\nThe first thing to do is give names to our resources. Linked data names are [IRIs](https://en.wikipedia.org/wiki/Internationalized_resource_identifier): globally unique identifiers that generalize the familiar URL you see in your browser's location bar. We can use some standard names for our relations from the [Dublin Core](http://dublincore.org) metadata standard, and we'll make up some more.\n\nName      | IRI\n----------|-----------------------------------------\ntitle     | `http://purl.org/dc/elements/1.1/title`\nauthor    | `http://purl.org/dc/elements/1.1/author`\nThe Iliad | `http://example.com/the-iliad`\nHomer     | `http://example.com/Homer`\nbook      | `http://example.com/book`\n\nIRIs can be long and cumbersome, so let's define some prefixes that we can use to shorten them:\n\nPrefix | IRI\n-------|-----------------------------------\n`dc`   | `http://purl.org/dc/elements/1.1/`\n`ex`   | `http://example.com/`\n\nThe `ex` prefix will be our default. We use strings for full IRIs and keywords when we're using some sort of contraction.\n\nIRI                                      | Contraction\n-----------------------------------------|------------\n`http://purl.org/dc/elements/1.1/title`  | `:dc:title`\n`http://purl.org/dc/elements/1.1/author` | `:dc:author`\n`http://example.com/the-iliad`           | `:the-iliad`\n`http://example.com/Homer`               | `:Homer`\n`http://example.com/book`                | `:book`\n\nWe'll put this naming information in a *context* map:\n\n    user=\u003e (def context {:dc \"http://purl.org/dc/elements/1.1/\", :ex \"http://example.com/\", nil :ex, :title :dc:title, :author :dc:author})\n    #'user/context\n\nThe `nil` key indicates the default prefix `:ex`. Now we can use the context to expand contractions and to contract IRIs:\n\n    user=\u003e (expand context :title)\n    http://purl.org/dc/elements/1.1/title\n    user=\u003e (expand context :Homer)\n    http://example.com/Homer\n    user=\u003e (contract context \"http://purl.org/dc/elements/1.1/title\")\n    :title\n    user=\u003e (contract context \"http://purl.org/dc/elements/1.1/foo\")\n    :dc:foo\n    user=\u003e (expand-all context data)\n    [{\"http://purl.org/dc/elements/1.1/title\" \"The Iliad\", \"http://purl.org/dc/elements/1.1/author\" \"Homer\"}]\n\nSometimes we also want to *resolve* a name to an IRI. We can define a resources map from string to IRIs or contractions:\n\n    user=\u003e (def resources {\"Homer\" :Homer, \"The Iliad\" :the-iliad})\n    #'user/resources\n\nWe should include this information in our data by assigning a special `:subject-iri` to each of our maps. We can do this one at a time with `assoc`:\n\n    user=\u003e (def book (assoc (first data) :subject-iri :the-iliad))\n    #'user/book\n    user=\u003e book\n    {:title \"The Iliad\", :author \"Homer\", :subject-iri :the-iliad}\n\nOr we can use a higher-order function to find the title from the resources map:\n\n    user=\u003e (def books (mapv #(assoc % :subject-iri (get resources (:title %))) data))\n    #'user/books\n    user=\u003e books\n    [{:title \"The Iliad\", :author \"Homer\", :subject-iri :the-iliad}]\n\nNow it's time to convert our book data to \"triples\", i.e. statements about things to put in our graph. A triple consists of a subject, a predicate, and an object:\n\n- the subject is the name of a resource: an IRI\n- the predicate is the name of a relation: also an IRI\n- the object can either be an IRI or literal data.\n\nWe represent an IRI with a string, or a contracted IRI with a keyword. We represent literal data as a map with special keys:\n\n- `:value` is the string value (\"lexical value\") of the data, e.g. \"The Iliad\", \"100.31\"\n- `:type` is the IRI of a data type, with `xsd:string` as the default\n- `:lang` is an optional language code, e.g. \"en\", \"en-uk\"\n\nThe `literal` function is a convenient way to create a literal map:\n\n    user=\u003e (literal \"The Iliad\")\n    {:value \"The Iliad\"}\n    user=\u003e (literal 100.31)\n    {:value \"100.31\", :type :xsd:float}\n\nThe `objectify` function takes a resource map and a value, and determines whether to convert the value to an IRI or a literal:\n\n    user=\u003e (objectify resources \"Some string\")\n    {:value \"Some string\"}\n    user=\u003e (objectify resources \"Homer\")\n    :Homer\n\nNow we can treat each map as a set of statements about a resources, and `triplify` it to a lazy sequence of triples. The format will be \"flat triples\", a list with slots for: subject, predicate, object, type, and lang.\n\nThe `triplify` function takes our resource map and a map of data that includes a `:subject-iri` key. It returns a lazy sequence of triples.\n\n    user=\u003e (def triples (triplify resources book))\n    #'user/triples\n    user=\u003e (vec triples)\n    [[:the-iliad :title {:value \"The Iliad\"}] [:the-iliad :author :Homer]]\n\nYou'll notice that the subject `:the-iliad` is repeated here. With a larger set of triples the redundancy will be greater. Instead we can use a nested data structure:\n\n    user=\u003e (def subjects (subjectify triples))\n    #'user/subjects\n    user=\u003e subjects\n    {:the-iliad {:title #{{:value \"The Iliad\"}}, :author #{:Homer}}}\n\nFrom the inside out, it works like this:\n\n- object-set: the set of object with the same subject and predicate\n- predicate-map: a map from predicate IRIs to object sets\n- subject-map: map from subject IRIs to predicate sets\n\nWe work with these data structures like any other Clojure data, using `merge`, `assoc`, `update`, and the rest of the standard Clojure toolkit:\n\n    user=\u003e (def context+ (merge default-context context))\n    #'user/context+\n    user=\u003e (def subjects+ (assoc-in subjects [:the-iliad :rdf:type] #{:book}))\n    #'user/subjects+\n    user=\u003e (def triples+ (conj triples [:the-iliad :rdf:type :book]))\n    #'user/triples+\n\nNow, we can write to standard linked data formats, such as Turtle:\n\n    user=\u003e (def prefixes (assoc (get-prefixes context) :rdf rdf :xsd xsd))\n    #'user/prefixes\n    user=\u003e (def expanded-triples (map #(expand-all context+ %) triples+))\n    #'user/expanded-triples\n    user=\u003e (edn-ld.jena/write-triple-string prefixes expanded-triples)\n    @prefix ex:    \u003chttp://example.com/\u003e .\n    @prefix rdf:   \u003chttp://www.w3.org/1999/02/22-rdf-syntax-ns#\u003e .\n    @prefix xsd:   \u003chttp://www.w3.org/2001/XMLSchema#\u003e .\n    @prefix dc:    \u003chttp://purl.org/dc/elements/1.1/\u003e .\n\n    ex:the-iliad  a    ex:book ;\n            dc:author  ex:Homer ;\n            dc:title   \"The Iliad\"^^xsd:string .\n\nOne more thing before we're done: *named graphs*. A graph is just a set of triples. When we want to talk about a particular graph, we give it a name: an IRI, of course. Then we can talk about sets of named graphs when we want to compare them, merge them, etc. The official name for a set of graphs is an \"[RDF dataset](http://www.w3.org/TR/rdf11-concepts/#section-dataset)\". A dataset includes \"default graph\" with no name.\n\nBy adding the name of a graph, our *triples* become *quads* (\"quadruples\"). We define a quad and some new functions to handle them.\n\n    user=\u003e (def library [(assoc book :graph-iri :library)])\n    #'user/library\n    user=\u003e library\n    [{:title \"The Iliad\", :author \"Homer\", :subject-iri :the-iliad, :graph-iri :library}]\n    user=\u003e (def quads (quadruplify-all resources library))\n    #'user/quads\n    user=\u003e (vec quads)\n    [[:library :the-iliad :title {:value \"The Iliad\"}] [:library :the-iliad :author :Homer]]\n    user=\u003e (graphify quads)\n    {:library {:the-iliad {:title #{{:value \"The Iliad\"}}, :author #{:Homer}}}}\n\n\n## More\n\n- Conference paper about EDN-LD ([PDF](https://github.com/ontodev/icbo2015-edn-ld/blob/master/edn_ld.pdf), [source](https://github.com/ontodev/icbo2015-edn-ld))\n\n\n## Change Log\n\n- 0.3.0\n    - update to Jena 3.0.1\n- 0.2.2\n    - fix bug in blank node handling\n- 0.2.1\n    - fix bug in edn-ld.jena/make-node\n- 0.2.0\n    - use Apache Jena for reading and writing\n    - fix `triplify` functions to use `:subject-iri` key\n    - add `quadruplify` and `graphify` functions, using `:graph-iri` key\n    - rename `squash` functions to `flatten`\n    - fix `flatten` functions\n    - many more unit tests\n    - prefer Triples to FlatTriples\n- 0.1.0\n    - first release\n\n\n## To Do\n\n- finish streaming RDFXML reader and writer\n- ClojureScript support? Would require different libraries for reading and writing\n\n\n## License\n\nCopyright © 2015 James A. Overton\n\nDistributed under the BSD 3-Clause License.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fontodev%2Fedn-ld","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fontodev%2Fedn-ld","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fontodev%2Fedn-ld/lists"}