├── .coveralls.yml ├── .github └── workflows │ ├── ci.yml │ └── generate-docs.yml ├── .gitignore ├── .yardopts ├── AUTHORS ├── CONTRIBUTING.md ├── Gemfile ├── README.md ├── Rakefile ├── UNLICENSE ├── VERSION ├── dependencyci.yml ├── earl.jsonld ├── etc ├── .earl ├── README ├── csvw.jsonld ├── doap.csv ├── doap.csv-metadata.json ├── doap.ttl ├── earl.html ├── earl.jsonld ├── earl.ttl ├── template.haml └── well-known ├── examples ├── baskauf-virtual-column │ ├── countries.csv │ └── countries.csv-metadata.json ├── bpotw.csv ├── bpotw.json ├── dir with space │ ├── niklas.csv │ └── niklas.json ├── edsu.json ├── issue-9 │ ├── all_contents.csv │ ├── all_contents.csv-metadata.json │ └── all_manuscripts.csv ├── jakubklimek-iris │ ├── mzdové-třídy.csv │ └── mzdové-třídy.csv-metadata.json ├── medicine-employees-to-write.csv ├── niklas.csv ├── niklas.json ├── vanderbot-metadata.json └── workergnome │ ├── Gemfile │ ├── archive.csv │ ├── archive.csv-metadata.json │ └── test.rb ├── lib └── rdf │ ├── tabular.rb │ └── tabular │ ├── csvw.rb │ ├── format.rb │ ├── literal.rb │ ├── metadata.rb │ ├── reader.rb │ ├── uax35.rb │ └── version.rb ├── rdf-tabular.gemspec ├── script ├── parse └── tc └── spec ├── .gitignore ├── data ├── countries-minimal.json ├── countries-minimal.ttl ├── countries-standard.json ├── countries-standard.ttl ├── countries.csv ├── countries.csv-minimal.json ├── countries.csv-minimal.ttl ├── countries.csv-standard.json ├── countries.csv-standard.ttl ├── countries.html ├── countries.json ├── countries_embed-minimal.json ├── countries_embed-minimal.ttl ├── countries_embed-standard.json ├── countries_embed-standard.ttl ├── countries_embed.html ├── countries_html-minimal.json ├── countries_html-minimal.ttl ├── countries_html-standard.json ├── countries_html-standard.ttl ├── country-codes-and-names-minimal.json ├── country-codes-and-names-minimal.ttl ├── country-codes-and-names-standard.json ├── country-codes-and-names-standard.ttl ├── country-codes-and-names.csv ├── country_slice.csv ├── gov.uk │ └── professions.csv ├── junior-roles.csv ├── junior-roles.json ├── roles-minimal.json ├── roles-minimal.ttl ├── roles-standard.json ├── roles-standard.ttl ├── roles.json ├── senior-roles.csv ├── senior-roles.json ├── test232-metadata.json ├── test232.csv ├── tree-ops-atd.json ├── tree-ops-ext-minimal.json ├── tree-ops-ext-minimal.ttl ├── tree-ops-ext-standard.json ├── tree-ops-ext-standard.ttl ├── tree-ops-ext.csv ├── tree-ops-ext.json ├── tree-ops-minimal.json ├── tree-ops-minimal.ttl ├── tree-ops-standard.json ├── tree-ops-standard.ttl ├── tree-ops-virtual-minimal.json ├── tree-ops-virtual-minimal.ttl ├── tree-ops-virtual-standard.json ├── tree-ops-virtual-standard.ttl ├── tree-ops-virtual.json ├── tree-ops.csv ├── tree-ops.csv-metadata.json ├── tree-ops.html └── tree-ops.tsv ├── format_spec.rb ├── matchers.rb ├── metadata_spec.rb ├── reader_spec.rb ├── spec_helper.rb ├── suite_helper.rb ├── suite_spec.rb └── uax35_spec.rb /.coveralls.yml: -------------------------------------------------------------------------------- 1 | repo_token: kB0xmXB84qdlU52CmVbSeL6jPoox82Bbw 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This workflow runs continuous CI across different versions of ruby on all branches and pull requests to develop. 2 | 3 | name: CI 4 | on: 5 | push: 6 | branches: [ '**' ] 7 | pull_request: 8 | branches: [ develop ] 9 | workflow_dispatch: 10 | 11 | jobs: 12 | tests: 13 | name: Ruby ${{ matrix.ruby }} 14 | if: "contains(github.event.commits[0].message, '[ci skip]') == false" 15 | runs-on: ubuntu-latest 16 | env: 17 | CI: true 18 | ALLOW_FAILURES: ${{ endsWith(matrix.ruby, 'head') || matrix.ruby == 'jruby' }} 19 | strategy: 20 | fail-fast: false 21 | matrix: 22 | ruby: ['3.0', 3.1, 3.2, 3.3, ruby-head, jruby] 23 | steps: 24 | - name: Clone repository 25 | uses: actions/checkout@v3 26 | - name: Set up Ruby 27 | uses: ruby/setup-ruby@v1 28 | with: 29 | ruby-version: ${{ matrix.ruby }} 30 | - name: Install dependencies 31 | run: bundle install --jobs 4 --retry 3 32 | - name: Run tests 33 | run: ruby --version; bundle exec rspec spec || $ALLOW_FAILURES 34 | - name: Coveralls GitHub Action 35 | uses: coverallsapp/github-action@v2 36 | if: "matrix.ruby == '3.2'" 37 | with: 38 | github-token: ${{ secrets.GITHUB_TOKEN }} 39 | wintests: 40 | name: Win64 Ruby ${{ matrix.ruby }} 41 | if: "contains(github.event.commits[0].message, '[ci skip]') == false" 42 | runs-on: windows-latest 43 | env: 44 | CI: true 45 | ALLOW_FAILURES: ${{ endsWith(matrix.ruby, 'head') || matrix.ruby == 'jruby' }} 46 | strategy: 47 | fail-fast: false 48 | matrix: 49 | ruby: 50 | - 3.2 51 | steps: 52 | - name: Clone repository 53 | uses: actions/checkout@v3 54 | - name: Set up Ruby 55 | uses: ruby/setup-ruby@v1 56 | with: 57 | ruby-version: ${{ matrix.ruby }} 58 | - name: Install dependencies 59 | run: bundle install --jobs 4 --retry 3 60 | - name: Run tests 61 | run: ruby --version; bundle exec rspec spec || $ALLOW_FAILURES 62 | -------------------------------------------------------------------------------- /.github/workflows/generate-docs.yml: -------------------------------------------------------------------------------- 1 | name: Build & deploy documentation 2 | on: 3 | push: 4 | branches: 5 | - master 6 | workflow_dispatch: 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | name: Update gh-pages with docs 11 | steps: 12 | - name: Clone repository 13 | uses: actions/checkout@v3 14 | - name: Set up Ruby 15 | uses: ruby/setup-ruby@v1 16 | with: 17 | ruby-version: "3.1" 18 | - name: Install required gem dependencies 19 | run: gem install yard --no-document 20 | - name: Build YARD Ruby Documentation 21 | run: yardoc 22 | - name: Deploy 23 | uses: peaceiris/actions-gh-pages@v3 24 | with: 25 | github_token: ${{ secrets.GITHUB_TOKEN }} 26 | publish_dir: ./doc/yard 27 | publish_branch: gh-pages 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.yardoc/ 2 | /doc/ 3 | /pkg/ 4 | /.rbx/ 5 | /coverage/ 6 | Gemfile.lock 7 | /.byebug_history 8 | /spec/w3c-csvw/ 9 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --title "Tabular Data RDF Reader and JSON serializer." 2 | --output-dir doc/yard 3 | --protected 4 | --no-private 5 | --hide-void-return 6 | --markup markdown 7 | --readme README.md 8 | - 9 | AUTHORS 10 | VERSION 11 | UNLICENSE 12 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | * Gregg Kellogg 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | Community contributions are essential for keeping Ruby RDF great. We want to keep it as easy as possible to contribute changes that get things working in your environment. There are a few guidelines that we need contributors to follow so that we can have a chance of keeping on top of things. 4 | 5 | ## Development 6 | 7 | This repository uses [Git Flow](https://github.com/nvie/gitflow) to manage development and release activity. All submissions _must_ be on a feature branch based on the _develop_ branch to ease staging and integration. 8 | 9 | * create or respond to an issue on the [Github Repository](https://github.com/ruby-rdf/rdf-tabular/issues) 10 | * Fork and clone the repo: 11 | `git clone git@github.com:your-username/rdf-tabular.git` 12 | * Install bundle: 13 | `bundle install` 14 | * Create tests in RSpec and make sure you achieve at least 90% code coverage for the feature your adding or behavior being modified. 15 | * Push to your fork and [submit a pull request][pr]. 16 | 17 | ## Do's and Dont's 18 | * Do your best to adhere to the existing coding conventions and idioms. 19 | * Don't use hard tabs, and don't leave trailing whitespace on any line. 20 | Before committing, run `git diff --check` to make sure of this. 21 | * Do document every method you add using [YARD][] annotations. Read the 22 | [tutorial][YARD-GS] or just look at the existing code for examples. 23 | * Don't touch the `.gemspec` or `VERSION` files. If you need to change them, 24 | do so on your private branch only. 25 | * Do feel free to add yourself to the `CREDITS` file and the 26 | corresponding list in the the `README`. Alphabetical order applies. 27 | * Don't touch the `AUTHORS` file. If your contributions are significant 28 | enough, be assured we will eventually add you in there. 29 | * Do note that in order for us to merge any non-trivial changes (as a rule 30 | of thumb, additions larger than about 15 lines of code), we need an 31 | explicit [public domain dedication][PDD] on record from you, 32 | which you will be asked to agree to on the first commit to a repo within the organization. 33 | Note that the agreement applies to all repos in the [Ruby RDF](https://github.com/ruby-rdf/) organization. 34 | 35 | [YARD]: https://yardoc.org/ 36 | [YARD-GS]: https://rubydoc.info/docs/yard/file/docs/GettingStarted.md 37 | [PDD]: https://unlicense.org/#unlicensing-contributions 38 | [pr]: https://github.com/ruby-rdf/rdf/compare/ 39 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gemspec 4 | gem 'rdf', github: "ruby-rdf/rdf", branch: "develop" 5 | gem 'rdf-xsd', github: "ruby-rdf/rdf-xsd", branch: "develop" 6 | 7 | group :development do 8 | gem 'linkeddata' 9 | gem 'ebnf', github: "dryruby/ebnf", branch: "develop" 10 | gem 'json-ld', github: "ruby-rdf/json-ld", branch: "develop" 11 | gem 'rdf-aggregate-repo', github: "ruby-rdf/rdf-aggregate-repo", branch: "develop" 12 | gem 'rdf-isomorphic', github: "ruby-rdf/rdf-isomorphic", branch: "develop" 13 | gem "rdf-spec", github: "ruby-rdf/rdf-spec", branch: "develop" 14 | gem 'rdf-trig', github: "ruby-rdf/rdf-trig", branch: "develop" 15 | gem 'rdf-turtle', github: "ruby-rdf/rdf-turtle", branch: "develop" 16 | gem 'rdf-vocab', github: "ruby-rdf/rdf-vocab", branch: "develop" 17 | gem 'sparql', github: "ruby-rdf/sparql", branch: "develop" 18 | gem 'sparql-client', github: "ruby-rdf/sparql-client", branch: "develop" 19 | gem 'sxp', github: "dryruby/sxp.rb", branch: "develop" 20 | end 21 | 22 | group :debug do 23 | gem "byebug", platforms: :mri 24 | end 25 | 26 | group :development, :test do 27 | gem 'simplecov', '~> 0.22', platforms: :mri 28 | gem 'simplecov-lcov', '~> 0.8', platforms: :mri 29 | end 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tabular Data RDF Reader and JSON serializer 2 | 3 | [CSV][] reader for [RDF.rb][] and fully JSON serializer. 4 | 5 | [![Gem Version](https://badge.fury.io/rb/rdf-tabular.svg)](https://badge.fury.io/rb/rdf-tabular) 6 | [![Build Status](https://github.com/ruby-rdf/rdf-tabular/workflows/CI/badge.svg?branch=develop)](https://github.com/ruby-rdf/rdf-tabular/actions?query=workflow%3ACI) 7 | [![Coverage Status](https://coveralls.io/repos/ruby-rdf/rdf-tabular/badge.svg?branch=develop)](https://coveralls.io/github/ruby-rdf/rdf-tabular?branch=develop) 8 | [![Gitter chat](https://badges.gitter.im/ruby-rdf/rdf.png)](https://gitter.im/ruby-rdf/rdf) 9 | 10 | ## Features 11 | 12 | RDF::Tabular parses CSV or other Tabular Data into [RDF][] and JSON using the [W3C CSVW][] specifications, currently undergoing development. 13 | 14 | * Parses [number patterns](https://www.unicode.org/reports/tr35/tr35-39/tr35-numbers.html#Number_Patterns) from [UAX35][] 15 | * Parses [date formats](https://www.unicode.org/reports/tr35/tr35-39/tr35-dates.html#Contents) from [UAX35][] 16 | * Returns detailed errors and warnings using optional `Logger`. 17 | 18 | ## Description 19 | RDF::Tabular parses CSVs, TSVs, and potentially other tabular data formats. Using rules defined for [W3C CSVW][], it can also parse metadata files (in JSON-LD format) to find a set of tabular data files, or locate a metadata file given a CSV: 20 | 21 | * Given a CSV `http://example.org/mycsv.csv` look for `http://example.org/mycsv.csv-metadata.json` or `http://example.org/metadata.json`. Metadata can also be specified using the `describedby` link header to reference a metadata file. 22 | * Given a metadata file, locate one or more CSV files described within the metadata file. 23 | * Also, extract _embedded metadata_ from the CSV (limited to column titles right now). 24 | 25 | Metadata can then provide datatypes for the columns, express foreign key relationships, and associate subjects and predicates with columns. An example [metadata file for the project DOAP description](https://raw.githubusercontent.com/ruby-rdf/rdf-tabular/develop/etc/doap.csv-metadata.json) is: 26 | 27 | { 28 | "@context": "http://www.w3.org/ns/csvw", 29 | "url": "doap.csv", 30 | "tableSchema": { 31 | "aboutUrl": "https://rubygems.org/gems/rdf-tabular", 32 | "propertyUrl": "http://usefulinc.com/ns/doap#{_name}", 33 | "null": "", 34 | "columns": [ 35 | {"titles": "name"}, 36 | {"titles": "type", "propertyUrl": "rdf:type", "valueUrl": "{+type}"}, 37 | {"titles": "homepage", "valueUrl": "{+homepage}"}, 38 | {"titles": "license", "valueUrl": "{+license}"}, 39 | {"titles": "shortdesc", "lang": "en"}, 40 | {"titles": "description", "lang": "en"}, 41 | {"titles": "created", "datatype": {"base": "date", "format": "M/d/yyyy"}}, 42 | {"titles": "programming_language", "propertyUrl": "http://usefulinc.com/ns/doap#programming-language"}, 43 | {"titles": "implements", "valueUrl": "{+implements}"}, 44 | {"titles": "category", "valueUrl": "{+category}"}, 45 | {"titles": "download_page", "propertyUrl": "http://usefulinc.com/ns/doap#download-page", "valueUrl": "{+download_page}"}, 46 | {"titles": "mailing_list", "propertyUrl": "http://usefulinc.com/ns/doap#mailing-list", "valueUrl": "{+mailing_list}"}, 47 | {"titles": "bug_database", "propertyUrl": "http://usefulinc.com/ns/doap#bug-database", "valueUrl": "{+bug_database}"}, 48 | {"titles": "blog", "valueUrl": "{+blog}"}, 49 | {"titles": "developer", "valueUrl": "{+developer}"}, 50 | {"titles": "maintainer", "valueUrl": "{+maintainer}"}, 51 | {"titles": "documenter", "valueUrl": "{+documenter}"}, 52 | {"titles": "maker", "propertyUrl": "foaf:maker", "valueUrl": "{+maker}"}, 53 | {"titles": "dc_title", "propertyUrl": "dc:title"}, 54 | {"titles": "dc_description", "propertyUrl": "dc:description", "lang": "en"}, 55 | {"titles": "dc_date", "propertyUrl": "dc:date", "datatype": {"base": "date", "format": "M/d/yyyy"}}, 56 | {"titles": "dc_creator", "propertyUrl": "dc:creator", "valueUrl": "{+dc_creator}"}, 57 | {"titles": "isPartOf", "propertyUrl": "dc:isPartOf", "valueUrl": "{+isPartOf}"} 58 | ] 59 | } 60 | } 61 | 62 | This associates the metadata with the CSV [doap.csv](https://raw.githubusercontent.com/ruby-rdf/rdf-tabular/develop/etc/doap.csv), creates a common subject for all rows in the file, and a common predicate using the URI Template [URI Template](https://tools.ietf.org/html/rfc6570) `http://usefulinc.com/ns/doap#\{_name\}` which uses the `name` of each column (defaulted from `titles`) to construct a URI in the DOAP vocabulary, and constructs object URIs for object-valued properties from the contents of the column cells. In some cases, the predicates are changed on a per-column basis by using a different `propertyUrl` property on a given column. 63 | 64 | This results in the following Turtle: 65 | 66 | @prefix csvw: . 67 | @prefix dc: . 68 | @prefix doap: . 69 | @prefix earl: . 70 | @prefix foaf: . 71 | @prefix prov: . 72 | @prefix rdf: . 73 | @prefix xsd: . 74 | 75 | a doap:Project, 76 | earl:TestSubject, 77 | earl:Software; 78 | dc:title "RDF::Tabular"; 79 | dc:creator ; 80 | dc:date "2015-01-05"^^xsd:date; 81 | dc:description "RDF::Tabular processes tabular data with metadata creating RDF or JSON output."@en; 82 | dc:isPartOf ; 83 | doap:blog ; 84 | doap:bug-database ; 85 | doap:category , 86 | ; 87 | doap:created "2015-01-05"^^xsd:date; 88 | doap:description "RDF::Tabular processes tabular data with metadata creating RDF or JSON output."@en; 89 | doap:developer ; 90 | doap:documenter ; 91 | doap:download-page ; 92 | doap:homepage ; 93 | doap:implements , 94 | , 95 | , 96 | ; 97 | doap:license ; 98 | doap:mailing-list ; 99 | doap:maintainer ; 100 | doap:name "RDF::Tabular"; 101 | doap:programming-language "Ruby"; 102 | doap:shortdesc "Tabular Data RDF Reader and JSON serializer."@en; 103 | foaf:maker . 104 | 105 | [ 106 | a csvw:TableGroup; 107 | csvw:table [ 108 | a csvw:Table; 109 | csvw:row [ 110 | a csvw:Row; 111 | csvw:describes ; 112 | csvw:rownum 1; 113 | csvw:url 114 | ], [ 115 | a csvw:Row; 116 | csvw:describes ; 117 | csvw:rownum 2; 118 | csvw:url 119 | ], [ 120 | a csvw:Row; 121 | csvw:describes ; 122 | csvw:rownum 3; 123 | csvw:url 124 | ], [ 125 | a csvw:Row; 126 | csvw:describes ; 127 | csvw:rownum 4; 128 | csvw:url 129 | ]; 130 | csvw:url 131 | ]; 132 | prov:wasGeneratedBy [ 133 | a prov:Activity; 134 | prov:endedAtTime "2022-04-20T12:45:20.616-07:00"^^xsd:dateTime; 135 | prov:qualifiedUsage [ 136 | a prov:Usage; 137 | prov:entity ; 138 | prov:hadRole csvw:csvEncodedTabularData 139 | ], [ 140 | a prov:Usage; 141 | prov:entity ; 142 | prov:hadRole csvw:tabularMetadata 143 | ]; 144 | prov:startedAtTime "2022-04-20T12:45:20.351-07:00"^^xsd:dateTime; 145 | prov:wasAssociatedWith 146 | ] 147 | ] . 148 | 149 | The provenance on table-source information can be excluded by using the `:minimal` option to the reader. 150 | 151 | It can also generate JSON output (not complete JSON-LD, but compatible with it), using the {RDF::Tabular::Reader#to_json} method: 152 | 153 | { 154 | "tables": [{ 155 | "url": "https://raw.githubusercontent.com/ruby-rdf/rdf-tabular/develop/etc/doap.csv", 156 | "row": [{ 157 | "url": "https://raw.githubusercontent.com/ruby-rdf/rdf-tabular/develop/etc/doap.csv#row=2", 158 | "rownum": 1, 159 | "describes": [{ 160 | "@id": "https://rubygems.org/gems/rdf-tabular", 161 | "http://usefulinc.com/ns/doap#name": "RDF::Tabular", 162 | "@type": "http://usefulinc.com/ns/doap#Project", 163 | "http://usefulinc.com/ns/doap#homepage": "https://ruby-rdf.github.io/rdf-tabular", 164 | "http://usefulinc.com/ns/doap#license": "https://unlicense.org/1.0/", 165 | "http://usefulinc.com/ns/doap#shortdesc": "Tabular Data RDF Reader and JSON serializer.", 166 | "http://usefulinc.com/ns/doap#description": "RDF::Tabular processes tabular data with metadata creating RDF or JSON output.", 167 | "http://usefulinc.com/ns/doap#created": "2015-01-05", 168 | "http://usefulinc.com/ns/doap#programming-language": "Ruby", 169 | "http://usefulinc.com/ns/doap#implements": "http://www.w3.org/TR/tabular-data-model/", 170 | "http://usefulinc.com/ns/doap#category": "http://dbpedia.org/resource/Resource_Description_Framework", 171 | "http://usefulinc.com/ns/doap#download-page": "https://rubygems.org/gems/rdf-tabular", 172 | "http://usefulinc.com/ns/doap#mailing-list": "http://lists.w3.org/Archives/Public/public-rdf-ruby/", 173 | "http://usefulinc.com/ns/doap#bug-database": "https://github.com/ruby-rdf/rdf-tabular/issues", 174 | "http://usefulinc.com/ns/doap#blog": "http://greggkellogg.net/", 175 | "http://usefulinc.com/ns/doap#developer": "http://greggkellogg.net/foaf#me", 176 | "http://usefulinc.com/ns/doap#maintainer": "http://greggkellogg.net/foaf#me", 177 | "http://usefulinc.com/ns/doap#documenter": "http://greggkellogg.net/foaf#me", 178 | "foaf:maker": "http://greggkellogg.net/foaf#me", 179 | "dc:title": "RDF::Tabular", 180 | "dc:description": "RDF::Tabular processes tabular data with metadata creating RDF or JSON output.", 181 | "dc:date": "2015-01-05", 182 | "dc:creator": "http://greggkellogg.net/foaf#me", 183 | "dc:isPartOf": "https://rubygems.org/gems/rdf" 184 | }] 185 | }, { 186 | "url": "https://raw.githubusercontent.com/ruby-rdf/rdf-tabular/develop/etc/doap.csv#row=3", 187 | "rownum": 2, 188 | "describes": [{ 189 | "@id": "https://rubygems.org/gems/rdf-tabular", 190 | "@type": "http://www.w3.org/ns/earl#TestSubject", 191 | "http://usefulinc.com/ns/doap#implements": "http://www.w3.org/TR/tabular-metadata/", 192 | "http://usefulinc.com/ns/doap#category": "http://dbpedia.org/resource/Ruby_(programming_language)" 193 | }] 194 | }, { 195 | "url": "https://raw.githubusercontent.com/ruby-rdf/rdf-tabular/develop/etc/doap.csv#row=4", 196 | "rownum": 3, 197 | "describes": [{ 198 | "@id": "https://rubygems.org/gems/rdf-tabular", 199 | "@type": "http://www.w3.org/ns/earl#Software", 200 | "http://usefulinc.com/ns/doap#implements": "http://www.w3.org/TR/csv2rdf/" 201 | }] 202 | }, { 203 | "url": "https://raw.githubusercontent.com/ruby-rdf/rdf-tabular/develop/etc/doap.csv#row=5", 204 | "rownum": 4, 205 | "describes": [{ 206 | "@id": "https://rubygems.org/gems/rdf-tabular", 207 | "http://usefulinc.com/ns/doap#implements": "http://www.w3.org/TR/csv2json/" 208 | }] 209 | }] 210 | }] 211 | } 212 | 213 | ## Tutorials 214 | 215 | * [CSV on the Web](https://www.greggkellogg.net/2015/08/csv-on-the-web-presentation/) 216 | * [Implementing CSV on the Web](https://greggkellogg.net/2015/04/implementing-csv-on-the-web/) 217 | 218 | ## Command Line 219 | When the `linkeddata` gem is installed, RDF.rb includes a `rdf` executable which acts as a wrapper to perform a number of different 220 | operations on RDF files using available readers and writers, including RDF::Tabular. The commands specific to RDF::Tabular is 221 | 222 | * `tabular-json`: Parse the CSV file and emit data as Tabular JSON 223 | 224 | To use RDF::Tabular specific features, you must use the `--input-format tabular` option to the `rdf` executable. 225 | 226 | Other `rdf` commands and options treat CSV as a standard RDF format. 227 | 228 | Example usage: 229 | 230 | rdf serialize https://raw.githubusercontent.com/ruby-rdf/rdf-tabular/develop/etc/doap.csv \ 231 | --output-format ttl 232 | rdf tabular-json --input-format tabular https://raw.githubusercontent.com/ruby-rdf/rdf-tabular/develop/etc/doap.csv 233 | rdf validate https://raw.githubusercontent.com/ruby-rdf/rdf-tabular/develop/etc/doap.csv --validate 234 | 235 | Note that the `--validate` option must be used with the `validate` (or other) command to detect parse-time errors in addition to validating any resulting RDF triples. 236 | 237 | ## RDF Reader 238 | RDF::Tabular also acts as a normal RDF reader, using the standard RDF.rb Reader interface: 239 | 240 | graph = RDF::Graph.load("etc/doap.csv", minimal: true) 241 | 242 | ## Documentation 243 | Full documentation available on [RubyDoc](https://rubydoc.info/gems/rdf-tabular/file/README.md) 244 | 245 | ### Principal Classes 246 | * {RDF::Tabular} 247 | * {RDF::Tabular::JSON} 248 | * {RDF::Tabular::Format} 249 | * {RDF::Tabular::Metadata} 250 | * {RDF::Tabular::Reader} 251 | 252 | ## Dependencies 253 | * [Ruby](https://ruby-lang.org/) (>= 3.0) 254 | * [RDF.rb](https://rubygems.org/gems/rdf) (~> 3.3) 255 | * [JSON](https://rubygems.org/gems/json) (>= 2.6) 256 | 257 | ## Change Log 258 | 259 | See [Release Notes on GitHub](https://github.com/ruby-rdf/rdf-tabular/releases) 260 | 261 | ## Installation 262 | The recommended installation method is via [RubyGems](https://rubygems.org/). 263 | To install the latest official release of the `RDF::Tabular` gem, do: 264 | 265 | % [sudo] gem install rdf-tabular 266 | 267 | ## Mailing List 268 | * 269 | 270 | ## Author 271 | * [Gregg Kellogg](https://github.com/gkellogg) - 272 | 273 | ## Contributing 274 | * Do your best to adhere to the existing coding conventions and idioms. 275 | * Don't use hard tabs, and don't leave trailing whitespace on any line. 276 | * Do document every method you add using [YARD][] annotations. Read the 277 | [tutorial][YARD-GS] or just look at the existing code for examples. 278 | * Don't touch the `rdf-tabular.gemspec`, `VERSION` or `AUTHORS` files. If you need to change them, do so on your private branch only. 279 | * Do feel free to add yourself to the `CREDITS` file and the corresponding list in the the `README`. Alphabetical order applies. 280 | * Do note that in order for us to merge any non-trivial changes (as a rule 281 | of thumb, additions larger than about 15 lines of code), we need an 282 | explicit [public domain dedication][PDD] on record from you, 283 | which you will be asked to agree to on the first commit to a repo within the organization. 284 | Note that the agreement applies to all repos in the [Ruby RDF](https://github.com/ruby-rdf/) organization. 285 | 286 | License 287 | ------- 288 | 289 | This is free and unencumbered public domain software. For more information, 290 | see or the accompanying {file:UNLICENSE} file. 291 | 292 | [Ruby]: https://ruby-lang.org/ 293 | [RDF]: https://www.w3.org/RDF/ 294 | [YARD]: https://yardoc.org/ 295 | [YARD-GS]: https://rubydoc.info/docs/yard/file/docs/GettingStarted.md 296 | [PDD]: https://unlicense.org/#unlicensing-contributions 297 | [RDF.rb]: https://rubygems.org/gems/rdf 298 | [CSV]: https://en.wikipedia.org/wiki/Comma-separated_values 299 | [W3C CSVW]: https://www.w3.org/2013/csvw/wiki/Main_Page 300 | [URI template]: https://tools.ietf.org/html/rfc6570 301 | [UAX35]: https://www.unicode.org/reports/tr15/ 302 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'yard' 3 | require 'rspec/core/rake_task' 4 | 5 | namespace :gem do 6 | desc "Build the rdf-tabular-#{File.read('VERSION').chomp}.gem file" 7 | task :build do 8 | sh "gem build rdf-tabular.gemspec && mv rdf-tabular-#{File.read('VERSION').chomp}.gem pkg/" 9 | end 10 | 11 | desc "Release the rdf-tabular-#{File.read('VERSION').chomp}.gem file" 12 | task :release do 13 | sh "gem push pkg/rdf-tabular-#{File.read('VERSION').chomp}.gem" 14 | end 15 | end 16 | 17 | desc 'Run specifications' 18 | RSpec::Core::RakeTask.new(:spec) do |spec| 19 | spec.rspec_opts = %w(--options spec/spec.opts) if File.exists?('spec/spec.opts') 20 | end 21 | 22 | desc "Run specs through RCov" 23 | RSpec::Core::RakeTask.new("spec:rcov") do |spec| 24 | spec.rcov = true 25 | spec.rcov_opts = %q[--exclude "spec"] 26 | end 27 | 28 | namespace :doc do 29 | YARD::Rake::YardocTask.new 30 | 31 | desc "Generate HTML report specs" 32 | RSpec::Core::RakeTask.new("spec") do |spec| 33 | spec.rspec_opts = ["--format", "html", "-o", "doc/spec.html"] 34 | end 35 | end 36 | 37 | desc "Update copy of CSVW context" 38 | task :context do 39 | %x(curl -o etc/csvw.jsonld http://w3c.github.io/csvw/ns/csvw.jsonld) 40 | end 41 | 42 | desc "Create CSVW vocabulary definition" 43 | task :vocab do 44 | puts "Generate lib/rdf/tabular/csvw.rb" 45 | cmd = "bundle exec rdf" 46 | cmd += " serialize --uri 'http://www.w3.org/ns/csvw#' --output-format vocabulary" 47 | cmd += " --module-name RDF::Tabular" 48 | cmd += " --class-name CSVW" 49 | cmd += " --strict" 50 | cmd += " -o lib/rdf/tabular/csvw.rb_t" 51 | cmd += " etc/csvw.jsonld" 52 | 53 | begin 54 | %x{#{cmd} && mv lib/rdf/tabular/csvw.rb_t lib/rdf/tabular/csvw.rb} 55 | rescue 56 | puts "Failed to load CSVW: #{$!.message}" 57 | ensure 58 | %x{rm -f lib/rdf/tabular/csvw.rb_t} 59 | end 60 | end 61 | 62 | task :default => :spec 63 | task :specs => :spec 64 | 65 | desc "Generate etc/doap.{nt,ttl} from etc/doap.csv." 66 | task :doap do 67 | require 'rdf/tabular' 68 | require 'rdf/turtle' 69 | require 'rdf/ntriples' 70 | g = RDF::Graph.load("etc/doap.csv") 71 | RDF::NTriples::Writer.open("etc/doap.nt") {|w| w < "etc/earl.html" 77 | file "etc/earl.jsonld" => %w(etc/earl.ttl etc/doap.ttl) do 78 | %x{cd etc; earl-report --format json -o earl.jsonld earl.ttl} 79 | end 80 | file "etc/earl.html" => "etc/earl.jsonld" do 81 | %x{cd etc; earl-report --json --format html -o earl.html earl.jsonld} 82 | end 83 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 3.3.0 2 | -------------------------------------------------------------------------------- /dependencyci.yml: -------------------------------------------------------------------------------- 1 | platform: 2 | Rubygems: 3 | rdf-isomorphic: 4 | tests: 5 | unmaintained: skip -------------------------------------------------------------------------------- /earl.jsonld: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruby-rdf/rdf-tabular/52804f52e9301aa19532a1d193a0d165cd473e32/earl.jsonld -------------------------------------------------------------------------------- /etc/.earl: -------------------------------------------------------------------------------- 1 | --- 2 | :format: :json 3 | :manifest: 4 | - http://www.w3.org/2013/csvw/tests/manifest-json.jsonld 5 | - http://www.w3.org/2013/csvw/tests/manifest-rdf.jsonld 6 | - http://www.w3.org/2013/csvw/tests/manifest-validation.jsonld 7 | :bibRef: ! '[[tabular-metadata]]' 8 | :name: CSV on the Web 9 | -------------------------------------------------------------------------------- /etc/README: -------------------------------------------------------------------------------- 1 | EARL results for rdf-tabular. 2 | 3 | earl-report --format json -o earl.jsonld earl.ttl 4 | earl-report --json --format html --template template.haml -o earl.html earl.jsonld 5 | -------------------------------------------------------------------------------- /etc/doap.csv: -------------------------------------------------------------------------------- 1 | name,type,homepage,license,shortdesc,description,created,programming_language,implements,category,download_page,mailing_list,bug_database,blog,developer,maintainer,documenter,maker,dc_title,dc_description,dc_date,dc_creator,isPartOf 2 | RDF::Tabular,http://usefulinc.com/ns/doap#Project,https://ruby-rdf.github.io/rdf-tabular,https://unlicense.org/1.0/,Tabular Data RDF Reader and JSON serializer.,RDF::Tabular processes tabular data with metadata creating RDF or JSON output.,1/5/2015,Ruby,http://www.w3.org/TR/tabular-data-model/,http://dbpedia.org/resource/Resource_Description_Framework,https://rubygems.org/gems/rdf-tabular,http://lists.w3.org/Archives/Public/public-rdf-ruby/,https://github.com/ruby-rdf/rdf-tabular/issues,http://greggkellogg.net/,http://greggkellogg.net/foaf#me,http://greggkellogg.net/foaf#me,http://greggkellogg.net/foaf#me,http://greggkellogg.net/foaf#me,RDF::Tabular,RDF::Tabular processes tabular data with metadata creating RDF or JSON output.,1/5/2015,http://greggkellogg.net/foaf#me,https://rubygems.org/gems/rdf 3 | ,http://www.w3.org/ns/earl#TestSubject,,,,,,,http://www.w3.org/TR/tabular-metadata/,http://dbpedia.org/resource/Ruby_(programming_language),,,,,,,,,,,,, 4 | ,http://www.w3.org/ns/earl#Software,,,,,,,http://www.w3.org/TR/csv2rdf/,,,,,,,,,,,,,, 5 | ,,,,,,,,http://www.w3.org/TR/csv2json/,,,,,,,,,,,,,, -------------------------------------------------------------------------------- /etc/doap.csv-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://www.w3.org/ns/csvw", 3 | "url": "doap.csv", 4 | "tableSchema": { 5 | "aboutUrl": "https://rubygems.org/gems/rdf-tabular", 6 | "propertyUrl": "http://usefulinc.com/ns/doap#{_name}", 7 | "null": "", 8 | "columns": [ 9 | {"titles": "name"}, 10 | {"titles": "type", "propertyUrl": "rdf:type", "valueUrl": "{+type}"}, 11 | {"titles": "homepage", "valueUrl": "{+homepage}"}, 12 | {"titles": "license", "valueUrl": "{+license}"}, 13 | {"titles": "shortdesc", "lang": "en"}, 14 | {"titles": "description", "lang": "en"}, 15 | {"titles": "created", "datatype": {"base": "date", "format": "M/d/yyyy"}}, 16 | {"titles": "programming_language", "propertyUrl": "http://usefulinc.com/ns/doap#programming-language"}, 17 | {"titles": "implements", "valueUrl": "{+implements}"}, 18 | {"titles": "category", "valueUrl": "{+category}"}, 19 | {"titles": "download_page", "propertyUrl": "http://usefulinc.com/ns/doap#download-page", "valueUrl": "{+download_page}"}, 20 | {"titles": "mailing_list", "propertyUrl": "http://usefulinc.com/ns/doap#mailing-list", "valueUrl": "{+mailing_list}"}, 21 | {"titles": "bug_database", "propertyUrl": "http://usefulinc.com/ns/doap#bug-database", "valueUrl": "{+bug_database}"}, 22 | {"titles": "blog", "valueUrl": "{+blog}"}, 23 | {"titles": "developer", "valueUrl": "{+developer}"}, 24 | {"titles": "maintainer", "valueUrl": "{+maintainer}"}, 25 | {"titles": "documenter", "valueUrl": "{+documenter}"}, 26 | {"titles": "maker", "propertyUrl": "foaf:maker", "valueUrl": "{+maker}"}, 27 | {"titles": "dc_title", "propertyUrl": "dc:title"}, 28 | {"titles": "dc_description", "propertyUrl": "dc:description", "lang": "en"}, 29 | {"titles": "dc_date", "propertyUrl": "dc:date", "datatype": {"base": "date", "format": "M/d/yyyy"}}, 30 | {"titles": "dc_creator", "propertyUrl": "dc:creator", "valueUrl": "{+dc_creator}"}, 31 | {"titles": "isPartOf", "propertyUrl": "dc:isPartOf", "valueUrl": "{+isPartOf}"} 32 | ] 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /etc/doap.ttl: -------------------------------------------------------------------------------- 1 | @base . 2 | @prefix rdf: . 3 | @prefix rdfs: . 4 | @prefix dc: . 5 | @prefix earl: . 6 | @prefix foaf: . 7 | @prefix doap: . 8 | @prefix xsd: . 9 | 10 | <> a doap:Project, earl:TestSubject, earl:Software ; 11 | doap:name "RDF::Tabular" ; 12 | doap:homepage ; 13 | doap:license ; 14 | doap:shortdesc "Tabular Data RDF Reader and JSON serializer for RDF.rb."@en ; 15 | doap:description "RDF::Tabular processes tabular data with metadata creating RDF or JSON output for the RDF.rb library suite."@en ; 16 | doap:created "2015-01-05"^^xsd:date ; 17 | doap:programming-language "Ruby" ; 18 | doap:implements , 19 | , 20 | , 21 | ; 22 | doap:category , 23 | ; 24 | doap:download-page <> ; 25 | doap:mailing-list ; 26 | doap:bug-database ; 27 | doap:blog ; 28 | doap:developer ; 29 | doap:maintainer ; 30 | doap:documenter ; 31 | foaf:maker ; 32 | dc:creator ; 33 | dc:isPartOf . 34 | -------------------------------------------------------------------------------- /etc/template.haml: -------------------------------------------------------------------------------- 1 | -# This template is used for generating a rollup EARL report. It expects to be 2 | -# called with a single _tests_ local with the following structure 3 | - require 'cgi' 4 | - require 'digest' 5 | 6 | !!! 5 7 | %html{:prefix => "earl: http://www.w3.org/ns/earl# doap: http://usefulinc.com/ns/doap# mf: http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#"} 8 | - subjects = tests['testSubjects'] 9 | %head 10 | %meta{"http-equiv" => "Content-Type", :content => "text/html;charset=utf-8"} 11 | %meta{name: "viewport", content: "width=device-width, initial-scale=1.0"} 12 | %link{rel: "stylesheet", type: "text/css", href: "https://www.w3.org/StyleSheets/TR/base"} 13 | %title 14 | = tests['name'] 15 | Implementation Report 16 | :css 17 | span[property='dc:description'] { display: none; } 18 | td.PASS { color: green; } 19 | td.FAIL { color: red; } 20 | table.report { 21 | border-width: 1px; 22 | border-spacing: 2px; 23 | border-style: outset; 24 | border-color: gray; 25 | border-collapse: separate; 26 | background-color: white; 27 | } 28 | table.report th { 29 | border-width: 1px; 30 | padding: 1px; 31 | border-style: inset; 32 | border-color: gray; 33 | background-color: white; 34 | -moz-border-radius: ; 35 | } 36 | table.report td { 37 | border-width: 1px; 38 | padding: 1px; 39 | border-style: inset; 40 | border-color: gray; 41 | background-color: white; 42 | -moz-border-radius: ; 43 | } 44 | tr.summary {font-weight: bold;} 45 | td.passed-all {color: green;} 46 | td.passed-most {color: darkorange;} 47 | td.passed-some {color: red;} 48 | td.passed-none {color: gray;} 49 | em.rfc2119 { 50 | text-transform: lowercase; 51 | font-variant: small-caps; 52 | font-style: normal; 53 | color: #900; 54 | } 55 | a.testlink { 56 | color: inherit; 57 | text-decoration: none; 58 | } 59 | a.testlink:hover { 60 | text-decoration: underline; 61 | } 62 | %body 63 | - subject_refs = {} 64 | - tests['entries'].each {|m| m['title'] ||= m['description']} 65 | %section{:about => tests['@id'], typeof: Array(tests['@type']).join(" ")} 66 | %h2 67 | Ruby rdf-tabular gem test results 68 | %p 69 | This document reports conformance for for the following specifications: 70 | %ul 71 | %li 72 | %a{property: "doap:name", href: "http://www.w3.org/TR/tabular-data-model/"}="MetaModel for Tabular Data and Metadata on the Web" 73 | %li 74 | %a{property: "doap:name", href: "http://www.w3.org/TR/tabular-metadata/"}="Metadata Vocabulary for Tabular Data" 75 | %li 76 | %a{property: "doap:name", href: "http://www.w3.org/TR/csv2rdf/"}="Generating RDF from Tabular Data on the Web" 77 | %li 78 | %a{property: "doap:name", href: "http://www.w3.org/TR/csv2json/"}="Generating JSON from Tabular Data on the Web" 79 | %p 80 | This report is also available in 81 | %a{:href => "earl.ttl"} 82 | Turtle 83 | %dl 84 | - subjects.each_with_index do |subject, index| 85 | - subject_refs[subject['@id']] = "subj_#{index}" 86 | %dt{:id => subject_refs[subject['@id']]} 87 | %a{:href => subject['@id']} 88 | %span{:about => subject['@id'], property: "doap:name"}<= subject['name'] 89 | %dd{property: "earl:testSubjects", resource: subject['@id'], typeof: Array(subject['@type']).join(" "), :inlist => true} 90 | %dl 91 | - if subject['doapDesc'] 92 | %dt= "Description" 93 | %dd{property: "doap:description", :lang => 'en'}< 94 | ~ CGI.escapeHTML subject['doapDesc'] 95 | - if subject['language'] 96 | %dt= "Programming Language" 97 | %dd{property: "doap:programming-language"}< 98 | ~ CGI.escapeHTML subject['language'] 99 | - if subject['homepage'] 100 | %dt= "Home Page" 101 | %dd{property: "doap:homepage"} 102 | %a{href: subject['homepage']} 103 | ~ CGI.escapeHTML subject['homepage'] 104 | - if subject['developer'] 105 | %dt= "Developer" 106 | %dd{:rel => "doap:developer"} 107 | - subject['developer'].each do |dev| 108 | %div{resource: dev['@id'], typeof: Array(dev['@type']).join(" ")} 109 | - if dev.has_key?('@id') 110 | %a{:href => dev['@id']} 111 | %span{property: "foaf:name"}< 112 | ~ CGI.escapeHTML dev['foaf:name'] 113 | - else 114 | %span{property: "foaf:name"}< 115 | ~ CGI.escapeHTML dev['foaf:name'] 116 | - if dev['foaf:homepage'] 117 | %a{property: "foaf:homepage", href: dev['foaf:homepage']} 118 | ~ CGI.escapeHTML dev['foaf:homepage'] 119 | %dt 120 | Test Suite Compliance 121 | %dd 122 | %table.report 123 | %tbody 124 | - tests['entries'].sort_by {|m| m['title'].to_s.downcase}.each do |manifest| 125 | - passed = manifest['entries'].select {|t| t['assertions'][index]['result']['outcome'] == 'earl:passed' }.length 126 | - total = manifest['entries'].length 127 | - pct = (passed * 100.0) / total 128 | - cls = (pct == 100.0 ? 'passed-all' : (pct >= 85.0) ? 'passed-most' : (pct == 0.0 ? 'passed-none' : 'passed-some')) 129 | %tr 130 | %td 131 | %a{href: "##{manifest['title']}"} 132 | ~ manifest['title'] 133 | %td{:class => cls} 134 | = pct == 0.0 ? "Untested" : "#{passed}/#{total} (#{'%.1f' % pct}%)" 135 | %section 136 | %h2 137 | Individual Test Results 138 | - tests['entries'].sort_by {|m| m['title'].to_s.downcase}.each do |manifest| 139 | - test_cases = manifest['entries'] 140 | %section{id: manifest['title'], typeof: manifest['@type'].join(" "), resource: manifest['@id']} 141 | %h2{property: "dc:title mf:name"}<=manifest['title'] 142 | - Array(manifest['description']).each do |desc| 143 | %p{property: "rdfs:comment"}< 144 | ~ CGI.escapeHTML desc 145 | %table.report 146 | - skip_subject = {} 147 | - passed_tests = [] 148 | %tr 149 | %th 150 | Test 151 | - subjects.each_with_index do |subject, index| 152 | - subject_refs[subject['@id']] = "subj_#{index}" 153 | -# If subject is untested for every test in this manifest, skip it 154 | - skip_subject[subject['@id']] = manifest['entries'].all? {|t| t['assertions'][index]['result']['outcome'] == 'earl:untested'} 155 | - unless skip_subject[subject['@id']] 156 | %th 157 | %a{:href => '#' + subject_refs[subject['@id']]}<=subject['name'] 158 | - test_cases.each do |test| 159 | %tr{:rel => "mf:entries", typeof: test['@type'].join(" "), resource: test['@id'], :inlist => true} 160 | %td 161 | = "Test #{test['@id'].split("#").last}: #{CGI.escapeHTML test['title']}" 162 | - test['assertions'].each_with_index do |assertion, ndx| 163 | - next if skip_subject[assertion['subject']] 164 | - pass_fail = assertion['result']['outcome'].split(':').last.upcase.sub(/(PASS|FAIL)ED$/, '\1') 165 | - passed_tests[ndx] = (passed_tests[ndx] || 0) + (pass_fail == 'PASS' ? 1 : 0) 166 | %td{:class => pass_fail, property: "earl:assertions", typeof: assertion['@type'], :inlist => true} 167 | - if assertion['assertedBy'] 168 | %link{property: "earl:assertedBy", :href => assertion['assertedBy']} 169 | %link{property: "earl:test", :href => assertion['test']} 170 | %link{property: "earl:subject", :href => assertion['subject']} 171 | - if assertion['mode'] 172 | %link{property: 'earl:mode', :href => assertion['mode']} 173 | %span{property: "earl:result", typeof: assertion['result']['@type']} 174 | %span{property: 'earl:outcome', resource: assertion['result']['outcome']} 175 | = pass_fail 176 | %tr.summary 177 | %td 178 | = "Percentage passed out of #{manifest['entries'].length} Tests" 179 | - passed_tests.compact.each do |r| 180 | - pct = (r * 100.0) / manifest['entries'].length 181 | %td{:class => (pct == 100.0 ? 'passed-all' : (pct >= 95.0 ? 'passed-most' : 'passed-some'))} 182 | = "#{'%.1f' % pct}%" 183 | %section#appendix{property: "earl:generatedBy", resource: tests['generatedBy']['@id'], typeof: tests['generatedBy']['@type']} 184 | %h2 185 | Report Generation Software 186 | - doap = tests['generatedBy'] 187 | - rel = doap['release'] 188 | %p 189 | This report generated by 190 | %span{property: "doap:name"}< 191 | %a{:href => tests['generatedBy']['@id']}< 192 | = doap['name'] 193 | %meta{property: "doap:shortdesc", :content => doap['shortdesc'], :lang => 'en'} 194 | %meta{property: "doap:description", :content => doap['doapDesc'], :lang => 'en'} 195 | version 196 | %span{property: "doap:release", resource: rel['@id'], typeof: 'doap:Version'} 197 | %span{property: "doap:revision"}<=rel['revision'] 198 | %meta{property: "doap:name", :content => rel['name']} 199 | %meta{property: "doap:created", :content => rel['created'], :datatype => "xsd:date"} 200 | an 201 | %a{property: "doap:license", :href => doap['license']}<="Unlicensed" 202 | %span{property: "doap:programming-language"}<="Ruby" 203 | application. More information is available at 204 | %a{property: "doap:homepage", :href => doap['homepage']}<=doap['homepage'] 205 | = "." 206 | -------------------------------------------------------------------------------- /etc/well-known: -------------------------------------------------------------------------------- 1 | {+url}-metadata.json 2 | csv-metadata.json 3 | {+url}.json 4 | csvm.json 5 | -------------------------------------------------------------------------------- /examples/baskauf-virtual-column/countries.csv: -------------------------------------------------------------------------------- 1 | countryCode,latitude,longitude,name 2 | AD,42.546245,1.601554,Andorra 3 | AE,23.424076,53.847818,"United Arab Emirates" 4 | AF,33.93911,67.709953,Afghanistan -------------------------------------------------------------------------------- /examples/baskauf-virtual-column/countries.csv-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://www.w3.org/ns/csvw", 3 | "url": "countries.csv", 4 | "aboutUrl": "https://example.org/countries", 5 | "@type": "Table", 6 | "tableSchema": { 7 | "@type": "Schema", 8 | "columns": [{ 9 | "name": "countryCode", 10 | "titles": "countryCode", 11 | "propertyUrl": "https://example.org/countries.csv#countryCode" 12 | }, { 13 | "name": "latitude", 14 | "titles": "latitude", 15 | "propertyUrl": "https://example.org/countries.csv#latitude" 16 | }, { 17 | "name": "longitude", 18 | "titles": "longitude", 19 | "propertyUrl": "https://example.org/countries.csv#longitude" 20 | }, { 21 | "name": "name", 22 | "titles": "name", 23 | "propertyUrl": "https://example.org/countries.csv#name" 24 | }, { 25 | "virtual": true, 26 | "propertyUrl": "https://example.org/countries.csv#virt1", 27 | "valueUrl": "https://example.org/countries.csv#virt1" 28 | }, { 29 | "virtual": true, 30 | "propertyUrl": "https://example.org/countries.csv#virt2", 31 | "default": "default", 32 | "datatype": "string" 33 | }] 34 | } 35 | } -------------------------------------------------------------------------------- /examples/bpotw.csv: -------------------------------------------------------------------------------- 1 | Identifier,Name,Description,Latitude,Longitude,ZONE,URL -------------------------------------------------------------------------------- /examples/bpotw.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": ["http://www.w3.org/ns/csvw", {"@language": "en"}], 3 | "url": "bpotw.csv", 4 | "dc:title": "CSV distribution of bus-stops-2015-05-05 dataset", 5 | "dcat:keyword": ["bus", "stop", "mobility"], 6 | "dc:publisher": { 7 | "schema:name": "Transport Agency of MyCity", 8 | "schema:url": {"@id": "http://example.org"} 9 | }, 10 | "dc:license": {"@id": "http://opendefinition.org/licenses/cc-by/"}, 11 | "dc:modified": {"@value": "2015-05-05", "@type": "xsd:date"}, 12 | "tableSchema": { 13 | "columns": [{ 14 | "name": "stop_id", 15 | "titles": ["Identifier"], 16 | "dc:description": "An identifier for the bus stop.", 17 | "datatype": "string", 18 | "required": true 19 | }, { 20 | "name": "stop_name", 21 | "titles": ["Name"], 22 | "dc:description": "The name of the bus stop.", 23 | "datatype": "string" 24 | }, { 25 | "name": "stop_desc", 26 | "titles": ["Description"], 27 | "dc:description": "A description for the bus stop.", 28 | "datatype": "string" 29 | }, { 30 | "name": "stop_lat", 31 | "titles": ["Latitude"], 32 | "dc:description": "The latitude of the bus stop.", 33 | "datatype": "number" 34 | }, { 35 | "name": "stop_long", 36 | "titles": ["Longitude"], 37 | "dc:description": "The longitude of the bus stop.", 38 | "datatype": "number" 39 | }, { 40 | "name": "zone_id", 41 | "titles": ["ZONE"], 42 | "dc:description": "An identifier for the zone where the bus stop is located.", 43 | "datatype": "string" 44 | }, 45 | { 46 | "name": "stop_url", 47 | "titles": ["URL"], 48 | "dc:description": "URL that identifies the bus stop.", 49 | "datatype": "string" 50 | }], 51 | "primaryKey": "stop_id" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /examples/dir with space/niklas.csv: -------------------------------------------------------------------------------- 1 | role 2 | "aut,li" 3 | -------------------------------------------------------------------------------- /examples/dir with space/niklas.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://www.w3.org/ns/csvw", 3 | "url": "niklas.csv", 4 | "tableSchema": { 5 | "columns": [{ 6 | "name": "role", 7 | "separator": ",", 8 | "valueUrl": "http://id.loc.gov/vocabulary/relators/{/role*}" 9 | }] 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/edsu.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://www.w3.org/ns/csvw", 3 | "@type": "Table", 4 | "url": "http://edsu.github.io/csvw-template/example.csv", 5 | "dc:creator": "Dan Bricklin", 6 | "dc:title": "My Spreadsheet", 7 | "dc:modified": "2014-05-09T15:44:58Z", 8 | "dc:publisher": "My Books", 9 | "tableSchema": { 10 | "aboutUrl": "http://librarything.com/isbn/{isbn}", 11 | "primaryKey": "isbn", 12 | "columns": [ 13 | { 14 | "name": "isbn", 15 | "titles": "ISBN-10", 16 | "datatype": "string", 17 | "unique": true, 18 | "propertyUrl": "http://purl.org/dc/terms/identifier" 19 | }, 20 | { 21 | "name": "title", 22 | "titles": "Book Title", 23 | "datatype": "string", 24 | "propertyUrl": "http://purl.org/dc/terms/title" 25 | }, 26 | { 27 | "name": "author", 28 | "titles": "Book Author", 29 | "datatype": "string", 30 | "propertyUrl": "http://purl.org/dc/terms/creator" 31 | } 32 | ] 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /examples/issue-9/all_contents.csv: -------------------------------------------------------------------------------- 1 | MS_ID,item 2 | 111,222 3 | 888,999 4 | -------------------------------------------------------------------------------- /examples/issue-9/all_contents.csv-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://www.w3.org/ns/csvw", 3 | "tables": [ 4 | { 5 | "url": "all_contents.csv", 6 | "tableSchema": { 7 | "aboutUrl": "{_row}", 8 | "primaryKey": [ 9 | "MS_ID", 10 | "item" 11 | ], 12 | "columns": [ 13 | {"name": "MS_ID", "titles": "MS_ID"}, 14 | {"name": "item", "titles": "item"} 15 | ], 16 | "foreignKeys": [ 17 | { 18 | "columnReference": "MS_ID", 19 | "reference": { 20 | "resource": "all_manuscripts.csv", 21 | "columnReference": "MS_ID" 22 | } 23 | } 24 | ] 25 | } 26 | }, 27 | { 28 | "url": "all_manuscripts.csv", 29 | "tableSchema": { 30 | "aboutUrl": "{_row}", 31 | "primaryKey": "MS_ID", 32 | "columns": [ 33 | {"name": "MS_ID", "titles": "MS_ID"}, 34 | {"name": "man", "titles": "man"} 35 | ] 36 | } 37 | } 38 | ] 39 | } -------------------------------------------------------------------------------- /examples/issue-9/all_manuscripts.csv: -------------------------------------------------------------------------------- 1 | MS_ID,man 2 | 111,333 3 | 555,666 4 | -------------------------------------------------------------------------------- /examples/jakubklimek-iris/mzdové-třídy.csv: -------------------------------------------------------------------------------- 1 | číselník,číselník_název_cs,číselník_název_en,položka,položka_kód,položka_název_cs,položka_název_en 2 | https://data.mff.cuni.cz/zdroj/číselníky/mzdové-třídy,Mzdové třídy MFF UK,Salary tariffs FMP CUNI,https://data.mff.cuni.cz/zdroj/číselníky/mzdové-třídy/položky/AP1,AP1,AP1,AP1 3 | https://data.mff.cuni.cz/zdroj/číselníky/mzdové-třídy,Mzdové třídy MFF UK,Salary tariffs FMP CUNI,https://data.mff.cuni.cz/zdroj/číselníky/mzdové-třídy/položky/AP2,AP2,AP2,AP2 4 | https://data.mff.cuni.cz/zdroj/číselníky/mzdové-třídy,Mzdové třídy MFF UK,Salary tariffs FMP CUNI,https://data.mff.cuni.cz/zdroj/číselníky/mzdové-třídy/položky/AP3,AP3,AP3,AP3 5 | https://data.mff.cuni.cz/zdroj/číselníky/mzdové-třídy,Mzdové třídy MFF UK,Salary tariffs FMP CUNI,https://data.mff.cuni.cz/zdroj/číselníky/mzdové-třídy/položky/AP4,AP4,AP4,AP4 6 | https://data.mff.cuni.cz/zdroj/číselníky/mzdové-třídy,Mzdové třídy MFF UK,Salary tariffs FMP CUNI,https://data.mff.cuni.cz/zdroj/číselníky/mzdové-třídy/položky/L1,L1,L1,L1 7 | https://data.mff.cuni.cz/zdroj/číselníky/mzdové-třídy,Mzdové třídy MFF UK,Salary tariffs FMP CUNI,https://data.mff.cuni.cz/zdroj/číselníky/mzdové-třídy/položky/L2,L2,L2,L2 8 | https://data.mff.cuni.cz/zdroj/číselníky/mzdové-třídy,Mzdové třídy MFF UK,Salary tariffs FMP CUNI,https://data.mff.cuni.cz/zdroj/číselníky/mzdové-třídy/položky/VP1,VP1,VP1,VP1 9 | https://data.mff.cuni.cz/zdroj/číselníky/mzdové-třídy,Mzdové třídy MFF UK,Salary tariffs FMP CUNI,https://data.mff.cuni.cz/zdroj/číselníky/mzdové-třídy/položky/VP2,VP2,VP2,VP2 10 | https://data.mff.cuni.cz/zdroj/číselníky/mzdové-třídy,Mzdové třídy MFF UK,Salary tariffs FMP CUNI,https://data.mff.cuni.cz/zdroj/číselníky/mzdové-třídy/položky/VP3,VP3,VP3,VP3 11 | https://data.mff.cuni.cz/zdroj/číselníky/mzdové-třídy,Mzdové třídy MFF UK,Salary tariffs FMP CUNI,https://data.mff.cuni.cz/zdroj/číselníky/mzdové-třídy/položky/1,1,1,1 12 | https://data.mff.cuni.cz/zdroj/číselníky/mzdové-třídy,Mzdové třídy MFF UK,Salary tariffs FMP CUNI,https://data.mff.cuni.cz/zdroj/číselníky/mzdové-třídy/položky/2,2,2,2 13 | https://data.mff.cuni.cz/zdroj/číselníky/mzdové-třídy,Mzdové třídy MFF UK,Salary tariffs FMP CUNI,https://data.mff.cuni.cz/zdroj/číselníky/mzdové-třídy/položky/3,3,3,3 14 | https://data.mff.cuni.cz/zdroj/číselníky/mzdové-třídy,Mzdové třídy MFF UK,Salary tariffs FMP CUNI,https://data.mff.cuni.cz/zdroj/číselníky/mzdové-třídy/položky/4,4,4,4 15 | https://data.mff.cuni.cz/zdroj/číselníky/mzdové-třídy,Mzdové třídy MFF UK,Salary tariffs FMP CUNI,https://data.mff.cuni.cz/zdroj/číselníky/mzdové-třídy/položky/5,5,5,5 16 | https://data.mff.cuni.cz/zdroj/číselníky/mzdové-třídy,Mzdové třídy MFF UK,Salary tariffs FMP CUNI,https://data.mff.cuni.cz/zdroj/číselníky/mzdové-třídy/položky/6,6,6,6 17 | https://data.mff.cuni.cz/zdroj/číselníky/mzdové-třídy,Mzdové třídy MFF UK,Salary tariffs FMP CUNI,https://data.mff.cuni.cz/zdroj/číselníky/mzdové-třídy/položky/7,7,7,7 18 | https://data.mff.cuni.cz/zdroj/číselníky/mzdové-třídy,Mzdové třídy MFF UK,Salary tariffs FMP CUNI,https://data.mff.cuni.cz/zdroj/číselníky/mzdové-třídy/položky/8,8,8,8 -------------------------------------------------------------------------------- /examples/jakubklimek-iris/mzdové-třídy.csv-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "@id": "https://data.mff.cuni.cz/soubory/číselníky/mzdové-třídy.csv-metadata.json", 3 | "@context": [ 4 | "http://www.w3.org/ns/csvw", 5 | { 6 | "@language": "cs" 7 | } 8 | ], 9 | "url": "mzdové-třídy.csv", 10 | "tableSchema": { 11 | "columns": [{ 12 | "name": "ciselnik", 13 | "titles": "číselník", 14 | "dc:description": "IRI číselníku", 15 | "aboutUrl": "{+ciselnik}", 16 | "propertyUrl": "rdf:type", 17 | "valueUrl": "skos:ConceptScheme", 18 | "required": true, 19 | "datatype": "anyURI" 20 | }, { 21 | "name": "ciselnik_nazev_cs", 22 | "titles": "číselník_název_cs", 23 | "dc:description": "Název číselníku v češtině", 24 | "aboutUrl": "{+ciselnik}", 25 | "propertyUrl": "skos:prefLabel", 26 | "required": true, 27 | "datatype": "string", 28 | "lang": "cs" 29 | }, { 30 | "name": "ciselnik_nazev_en", 31 | "titles": "číselník_název_en", 32 | "dc:description": "Název číselníku v angličtině", 33 | "aboutUrl": "{+ciselnik}", 34 | "propertyUrl": "skos:prefLabel", 35 | "required": true, 36 | "datatype": "string", 37 | "lang": "en" 38 | }, { 39 | "name": "polozka", 40 | "titles": "položka", 41 | "dc:description": "IRI položky", 42 | "aboutUrl": "{+polozka}", 43 | "propertyUrl": "rdf:type", 44 | "valueUrl": "skos:Concept", 45 | "required": true, 46 | "datatype": "anyURI" 47 | }, { 48 | "name": "polozka_kod", 49 | "titles": "položka_kód", 50 | "dc:description": "Kód položky", 51 | "aboutUrl": "{+polozka}", 52 | "propertyUrl": "skos:notation", 53 | "required": true, 54 | "datatype": "string" 55 | }, { 56 | "name": "polozka_nazev_cs", 57 | "titles": "položka_název_cs", 58 | "dc:description": "Název položky v češtině", 59 | "aboutUrl": "{+polozka}", 60 | "propertyUrl": "skos:prefLabel", 61 | "required": true, 62 | "datatype": "string", 63 | "lang": "cs" 64 | }, { 65 | "name": "polozka_nazev_en", 66 | "titles": "položka_název_en", 67 | "dc:description": "Název položky v angličtině", 68 | "aboutUrl": "{+polozka}", 69 | "propertyUrl": "skos:prefLabel", 70 | "required": true, 71 | "datatype": "string", 72 | "lang": "en" 73 | }, { 74 | "aboutUrl": "{+polozka}", 75 | "propertyUrl": "skos:inScheme", 76 | "valueUrl": "{+ciselnik}", 77 | "virtual": true 78 | }], 79 | "primaryKey": "polozka" 80 | } 81 | } -------------------------------------------------------------------------------- /examples/medicine-employees-to-write.csv: -------------------------------------------------------------------------------- 1 | department,wikidataId,name,labelEn,alias,description,orcidStatementUuid,orcid,orcidReferenceHash,orcidReferenceValue,employerStatementUuid,employer,employerReferenceHash,employerReferenceSourceUrl,employerReferenceRetrieved,affiliationStatementUuid,affiliation,affiliationReferenceHash,affiliationReferenceSourceUrl,affiliationReferenceRetrieved,instanceOfUuid,instanceOf,sexOrGenderUuid,sexOrGenderQId,gender,degree,category,wikidataStatus,role 2 | medicine,Q88631361,Maria Blanca Piazuelo,Maria Blanca Piazuelo,"[""M. Blanca Piazuelo""]",physician,60AEDD59-C71F-467E-8A23-E6550C3070EC,0000-0002-0000-1324,7f81f24a4148728c2eb8e030140bb2808f105296,+2020-04-20T00:00:00Z,538599CD-1564-4E56-839A-1BAB9BAF74DC,Q29052,4820fb8bbf4060c54dd4f0f0745fa6ddc12cff80,https://wag.app.vanderbilt.edu//PublicPage/Faculty/PickLetter?letter=P,+2020-04-20T00:00:00Z,0BB1C55B-A784-4826-9EA6-10453CB6FBE9,Q89953931,4820fb8bbf4060c54dd4f0f0745fa6ddc12cff80,https://wag.app.vanderbilt.edu//PublicPage/Faculty/PickLetter?letter=P,+2020-04-20T00:00:00Z,388A4B21-1D84-412B-B501-89B7206FA52C,Q5,888850E6-14E5-462E-9A86-CBC983A2D8F7,Q6581072,f,M.D.,P,5,"[{""title"": ""Research Associate Professor"", ""department"": ""Medicine""}]" 3 | -------------------------------------------------------------------------------- /examples/niklas.csv: -------------------------------------------------------------------------------- 1 | role 2 | "aut,li" 3 | -------------------------------------------------------------------------------- /examples/niklas.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://www.w3.org/ns/csvw", 3 | "url": "niklas.csv", 4 | "tableSchema": { 5 | "columns": [{ 6 | "name": "role", 7 | "separator": ",", 8 | "valueUrl": "http://id.loc.gov/vocabulary/relators/{/role*}" 9 | }] 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/vanderbot-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "@type": "TableGroup", 3 | "@context": "http://www.w3.org/ns/csvw", 4 | "tables": [ 5 | { 6 | "url": "medicine-employees-to-write.csv", 7 | "tableSchema": { 8 | "columns": [ 9 | { 10 | "titles": "department", 11 | "name": "department", 12 | "datatype": "string", 13 | "suppressOutput": true 14 | }, 15 | { 16 | "titles": "wikidataId", 17 | "name": "wikidataId", 18 | "datatype": "string", 19 | "suppressOutput": true 20 | }, 21 | { 22 | "titles": "name", 23 | "name": "name", 24 | "datatype": "string", 25 | "suppressOutput": true 26 | }, 27 | { 28 | "titles": "labelEn", 29 | "name": "labelEn", 30 | "datatype": "string", 31 | "aboutUrl": "http://www.wikidata.org/entity/{wikidataId}", 32 | "propertyUrl": "rdfs:label", 33 | "lang": "en" 34 | }, 35 | { 36 | "titles": "alias", 37 | "name": "alias", 38 | "datatype": "string", 39 | "aboutUrl": "http://www.wikidata.org/entity/{wikidataId}", 40 | "propertyUrl": "skos:altLabel", 41 | "lang": "en" 42 | }, 43 | { 44 | "titles": "description", 45 | "name": "description", 46 | "datatype": "string", 47 | "aboutUrl": "http://www.wikidata.org/entity/{wikidataId}", 48 | "propertyUrl": "schema:description", 49 | "lang": "en" 50 | }, 51 | { 52 | "titles": "orcidStatementUuid", 53 | "name": "orcidStatementUuid", 54 | "datatype": "string", 55 | "aboutUrl": "http://www.wikidata.org/entity/{wikidataId}", 56 | "propertyUrl": "http://www.wikidata.org/prop/P496", 57 | "valueUrl": "http://www.wikidata.org/entity/statement/{orcidStatementUuid}" 58 | }, 59 | { 60 | "titles": "orcid", 61 | "name": "orcid", 62 | "datatype": "string", 63 | "aboutUrl": "http://www.wikidata.org/entity/{wikidataId}", 64 | "propertyUrl": "http://www.wikidata.org/prop/direct/P496" 65 | }, 66 | { 67 | "titles": "orcidReferenceHash", 68 | "name": "orcidReferenceHash", 69 | "datatype": "string", 70 | "aboutUrl": "http://www.wikidata.org/entity/statement/{orcidStatementUuid}", 71 | "propertyUrl": "prov:wasDerivedFrom", 72 | "valueUrl": "http://www.wikidata.org/reference/{orcidReferenceHash}" 73 | }, 74 | { 75 | "titles": "orcidReferenceValue", 76 | "name": "orcidReferenceValue", 77 | "datatype": "date", 78 | "aboutUrl": "http://www.wikidata.org/reference/{orcidReferenceHash}", 79 | "propertyUrl": "http://www.wikidata.org/prop/reference/P813" 80 | }, 81 | { 82 | "titles": "employerStatementUuid", 83 | "name": "employerStatementUuid", 84 | "datatype": "string", 85 | "aboutUrl": "http://www.wikidata.org/entity/{wikidataId}", 86 | "propertyUrl": "http://www.wikidata.org/prop/P108", 87 | "valueUrl": "http://www.wikidata.org/entity/statement/{employerStatementUuid}" 88 | }, 89 | { 90 | "titles": "employer", 91 | "name": "employer", 92 | "datatype": "string", 93 | "aboutUrl": "http://www.wikidata.org/entity/{wikidataId}", 94 | "propertyUrl": "http://www.wikidata.org/prop/direct/P108", 95 | "valueUrl": "http://www.wikidata.org/entity/{employer}" 96 | }, 97 | { 98 | "titles": "employerReferenceHash", 99 | "name": "employerReferenceHash", 100 | "datatype": "string", 101 | "aboutUrl": "http://www.wikidata.org/entity/statement/{employerStatementUuid}", 102 | "propertyUrl": "prov:wasDerivedFrom", 103 | "valueUrl": "http://www.wikidata.org/reference/{employerReferenceHash}" 104 | }, 105 | { 106 | "titles": "employerReferenceSourceUrl", 107 | "name": "employerReferenceSourceUrl", 108 | "datatype": "anyURI", 109 | "aboutUrl": "http://www.wikidata.org/reference/{employerReferenceHash}", 110 | "propertyUrl": "http://www.wikidata.org/prop/reference/P854" 111 | }, 112 | { 113 | "titles": "employerReferenceRetrieved", 114 | "name": "employerReferenceRetrieved", 115 | "datatype": "date", 116 | "aboutUrl": "http://www.wikidata.org/reference/{employerReferenceHash}", 117 | "propertyUrl": "http://www.wikidata.org/prop/reference/P813" 118 | }, 119 | { 120 | "titles": "affiliationStatementUuid", 121 | "name": "affiliationStatementUuid", 122 | "datatype": "string", 123 | "aboutUrl": "http://www.wikidata.org/entity/{wikidataId}", 124 | "propertyUrl": "http://www.wikidata.org/prop/P1416", 125 | "valueUrl": "http://www.wikidata.org/entity/statement/{affiliationStatementUuid}" 126 | }, 127 | { 128 | "titles": "affiliation", 129 | "name": "affiliation", 130 | "datatype": "string", 131 | "aboutUrl": "http://www.wikidata.org/entity/{wikidataId}", 132 | "propertyUrl": "http://www.wikidata.org/prop/direct/P1416", 133 | "valueUrl": "http://www.wikidata.org/entity/{affiliation}" 134 | }, 135 | { 136 | "titles": "affiliationReferenceHash", 137 | "name": "affiliationReferenceHash", 138 | "datatype": "string", 139 | "aboutUrl": "http://www.wikidata.org/entity/statement/{affiliationStatementUuid}", 140 | "propertyUrl": "prov:wasDerivedFrom", 141 | "valueUrl": "http://www.wikidata.org/reference/{affiliationReferenceHash}" 142 | }, 143 | { 144 | "titles": "affiliationReferenceSourceUrl", 145 | "name": "affiliationReferenceSourceUrl", 146 | "datatype": "anyURI", 147 | "aboutUrl": "http://www.wikidata.org/reference/{affiliationReferenceHash}", 148 | "propertyUrl": "http://www.wikidata.org/prop/reference/P854" 149 | }, 150 | { 151 | "titles": "affiliationReferenceRetrieved", 152 | "name": "affiliationReferenceRetrieved", 153 | "datatype": "date", 154 | "aboutUrl": "http://www.wikidata.org/reference/{affiliationReferenceHash}", 155 | "propertyUrl": "http://www.wikidata.org/prop/reference/P813" 156 | }, 157 | { 158 | "titles": "instanceOfUuid", 159 | "name": "instanceOfUuid", 160 | "datatype": "string", 161 | "aboutUrl": "http://www.wikidata.org/entity/{wikidataId}", 162 | "propertyUrl": "http://www.wikidata.org/prop/P31", 163 | "valueUrl": "http://www.wikidata.org/entity/statement/{instanceOfUuid}" 164 | }, 165 | { 166 | "titles": "instanceOf", 167 | "name": "instanceOf", 168 | "datatype": "string", 169 | "aboutUrl": "http://www.wikidata.org/entity/{wikidataId}", 170 | "propertyUrl": "http://www.wikidata.org/prop/direct/P31", 171 | "valueUrl": "http://www.wikidata.org/entity/{instanceOf}" 172 | }, 173 | { 174 | "titles": "sexOrGenderUuid", 175 | "name": "sexOrGenderUuid", 176 | "datatype": "string", 177 | "aboutUrl": "http://www.wikidata.org/entity/{wikidataId}", 178 | "propertyUrl": "http://www.wikidata.org/prop/P21", 179 | "valueUrl": "http://www.wikidata.org/entity/statement/{sexOrGenderUuid}" 180 | }, 181 | { 182 | "titles": "sexOrGenderQId", 183 | "name": "sexOrGenderQId", 184 | "datatype": "string", 185 | "aboutUrl": "http://www.wikidata.org/entity/{wikidataId}", 186 | "propertyUrl": "http://www.wikidata.org/prop/direct/P21", 187 | "valueUrl": "http://www.wikidata.org/entity/{sexOrGenderQId}" 188 | }, 189 | { 190 | "titles": "gender", 191 | "name": "gender", 192 | "datatype": "string", 193 | "suppressOutput": true 194 | }, 195 | { 196 | "titles": "degree", 197 | "name": "degree", 198 | "datatype": "string", 199 | "suppressOutput": true 200 | }, 201 | { 202 | "titles": "category", 203 | "name": "category", 204 | "datatype": "string", 205 | "suppressOutput": true 206 | }, 207 | { 208 | "titles": "wikidataStatus", 209 | "name": "wikidataStatus", 210 | "datatype": "string", 211 | "suppressOutput": true 212 | }, 213 | { 214 | "titles": "role", 215 | "name": "role", 216 | "datatype": "string", 217 | "suppressOutput": true 218 | } 219 | ] 220 | } 221 | } 222 | ] 223 | } 224 | -------------------------------------------------------------------------------- /examples/workergnome/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | 4 | gem "linkeddata" -------------------------------------------------------------------------------- /examples/workergnome/archive.csv: -------------------------------------------------------------------------------- 1 | Object Number,file name(s),Box number,Folder number,Extent,Title of resource,Accession number,TMS Object ID,Creator/photographer/artist,LCNAF URI,TMS Constituent ID,Role (Relator),Relator Code,Type of Resource,Date YYYY-MM-DD,Location ,TGN ID,Language,ISO 639-2 code,Format,AAT ID,Description/Misc.,Rights information,Digital Origin,Internet Media type,Collection,Finding Aid,Physical Location JGJ_B018_F010_003,JGJ_B018_F010_003_r,Box 18,Folder 10,1 photograph,"Portrait of Margaret Urmiller, née Schwab, and Her Daughter",Cat. 728,102538,Barthel Beham,http://id.loc.gov/authorities/names/nr91026284,16018,Artist,art,still image,circa 1525,Nuremberg,7004334,,,photographic prints,"300127104 ","Photograph of painting: Portrait of Margaret Urmiller, née Schwab, and Her Daughter by Barthel Beham.",No copyright.,reformatted digital,image/jpeg,John G. Johnson Papers,http://pmalibrary.libraryhost.com/repositories/3/resources/245,"Philadelphia Museum of Art, Library & Archives." JGJ_B010_F003_001,JGJ_B010_F003_001_r,Box 10,Folder 3,1 photograph,John G. Johnson facing front,,,Haeseler Photographic Studios,,,Photographer,pht,still image,circa 1907,Philadelphia,7014406,,,photographic prints,"300127104 ",Photograph of John G. Johnson facing front. ,No copyright.,reformatted digital,image/jpeg,John G. Johnson Papers,http://pmalibrary.libraryhost.com/repositories/3/resources/245,"Philadelphia Museum of Art, Library & Archives." -------------------------------------------------------------------------------- /examples/workergnome/archive.csv-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://www.w3.org/ns/csvw", 3 | "url": "archive.csv", 4 | "dc:title": "Archival Data for the John P. Johnson Collection", 5 | "dc:creator": "The Philadelphia Museum of Art", 6 | "tableSchema": { 7 | "primaryKey": "object_number", 8 | "aboutUrl": "http://data.philamuseum.org/archive/{object_number}", 9 | "columns": [ 10 | { 11 | "titles": "Object Number", 12 | "name": "object_number", 13 | "required": true, 14 | "datatype": { 15 | "base": "string", 16 | "format": "^[A-Za-z0-9_\\-]+$" 17 | } 18 | }, 19 | { 20 | "titles": "file name(s)", 21 | "name": "filename", 22 | "separator": "\n", 23 | "ordered": true, 24 | "datatype": "string" 25 | }, 26 | { 27 | "titles": "Box number", 28 | "required": true, 29 | "datatype": { 30 | "base": "string", 31 | "format": "^Box [0-9]+$" 32 | } 33 | }, 34 | { 35 | "titles": "Folder number", 36 | "required": true, 37 | "datatype": { 38 | "base": "string", 39 | "format": "^Folder [0-9]+$" 40 | } 41 | }, 42 | { 43 | "titles": "Extent", 44 | "required": true, 45 | "datatype": "string" 46 | }, 47 | 48 | { 49 | "titles": "Title of resource", 50 | "required": true, 51 | "datatype": "string" 52 | }, 53 | { 54 | "titles": "Accession number", 55 | "datatype": { 56 | "base": "string", 57 | "format": "^[Cc]at\\. [0-9]+$" 58 | } 59 | }, 60 | { 61 | "titles": "TMS Object ID", 62 | "datatype": "number" 63 | }, 64 | { 65 | "titles": "Creator/photographer/artist", 66 | "datatype": "string" 67 | }, 68 | { 69 | "titles": "LCNAF URI", 70 | "name": "lcnaf_uri", 71 | "valueUrl": "{+lcnaf_uri}" 72 | }, 73 | { 74 | "titles": "TMS Constituent ID", 75 | "datatype": "number" 76 | }, 77 | { 78 | "titles": "Role (Relator)", 79 | "datatype": "string", 80 | "skos:editorialNote": "this might be pulling from a lookup list we could use?" 81 | }, 82 | { 83 | "titles": "Relator Code", 84 | "datatype": { 85 | "base": "string", 86 | "format": "^[a-z]{3}$" 87 | }, 88 | "skos:editorialNote": "confirm that this is always a three-letter code?" 89 | }, 90 | { 91 | "titles": "Type of Resource", 92 | "datatype": "string", 93 | "skos:editorialNote": "is this pulling from a vocabulary?" 94 | }, 95 | { 96 | "titles": "Date YYYY-MM-DD", 97 | "datatype": "string", 98 | "skos:editorialNote": "This is NOT YYY-MM-DD format." 99 | 100 | }, 101 | { 102 | "titles": "Location", 103 | "datatype": "string" 104 | }, 105 | { 106 | "titles": "TGN ID", 107 | "name": "tgn_id", 108 | "datatype": "number", 109 | "valueUrl": "http://vocab.getty.edu/tgn/{tgn_id}" 110 | }, 111 | { 112 | "titles": "Language", 113 | "datatype": "string" 114 | }, 115 | { 116 | "titles": "ISO 639-2 code", 117 | "name": "language_code", 118 | "datatype": { 119 | "base": "string", 120 | "format": "^[a-z]{3}$" 121 | }, 122 | "valueUrl": "http://www.lexvo.org/page/iso639-3/{language_code}", 123 | "propertyUrl": "dc:language" 124 | }, 125 | { 126 | "titles": "Format", 127 | "datatype": "string" 128 | }, 129 | { 130 | "titles": "AAT ID", 131 | "name":"aat_id", 132 | "datatype": "number", 133 | "valueUrl": "http://vocab.getty.edu/aat/{aat_id}" 134 | }, 135 | { 136 | "titles": "Description/Misc.", 137 | "datatype": "string" 138 | }, 139 | { 140 | "titles": "Rights information", 141 | "datatype": "string" 142 | }, 143 | { 144 | "titles": "Digital Origin", 145 | "datatype": "string" 146 | }, 147 | { 148 | "titles": "Internet Media type", 149 | "datatype": { 150 | "base": "string", 151 | "dc:title": "Mime Type", 152 | "format": "^(?=[-a-z]{1,127}/[-\\.a-z0-9]{1,127}$)[a-z]+(-[a-z]+)*/[a-z0-9]+([-\\.][a-z0-9]+)*$" 153 | } 154 | }, 155 | { 156 | "titles": "Collection", 157 | "datatype": "string" 158 | }, 159 | { 160 | "titles": "Finding Aid", 161 | "datatype": "anyURI" 162 | }, 163 | { 164 | "titles": "Physical Location", 165 | "datatype": "string" 166 | }, 167 | { 168 | "virtual": true, 169 | "propertyUrl": "rdf:type", 170 | "valueUrl": "dc:PhysicalMedium" 171 | } 172 | ] 173 | } 174 | } -------------------------------------------------------------------------------- /examples/workergnome/test.rb: -------------------------------------------------------------------------------- 1 | require "linkeddata" 2 | graph = RDF::Graph.load("archive.csv", minimal: true, logger: STDERR) 3 | puts graph.dump(:ttl) -------------------------------------------------------------------------------- /lib/rdf/tabular.rb: -------------------------------------------------------------------------------- 1 | $:.unshift(File.expand_path("..", __FILE__)) 2 | require 'rdf' # @see https://rubygems.org/gems/rdf 3 | require 'csv' 4 | 5 | module RDF 6 | ## 7 | # **`RDF::Tabular`** is a Tabular/CSV extension for RDF.rb. 8 | # 9 | # @see https://w3c.github.io/csvw/ 10 | # 11 | # @author [Gregg Kellogg](https://greggkellogg.net/) 12 | module Tabular 13 | require 'rdf/tabular/format' 14 | autoload :Column, 'rdf/tabular/metadata' 15 | autoload :CSVW, 'rdf/tabular/csvw' 16 | autoload :Dialect, 'rdf/tabular/metadata' 17 | autoload :JSON, 'rdf/tabular/literal' 18 | autoload :Metadata, 'rdf/tabular/metadata' 19 | autoload :Reader, 'rdf/tabular/reader' 20 | autoload :Schema, 'rdf/tabular/metadata' 21 | autoload :Table, 'rdf/tabular/metadata' 22 | autoload :TableGroup, 'rdf/tabular/metadata' 23 | autoload :Transformation, 'rdf/tabular/metadata' 24 | autoload :UAX35, 'rdf/tabular/uax35' 25 | autoload :VERSION, 'rdf/tabular/version' 26 | 27 | # Metadata errors detected 28 | class Error < RDF::ReaderError; end 29 | 30 | # Relative location of site-wide configuration file 31 | SITE_WIDE_CONFIG = "/.well-known/csvm".freeze 32 | SITE_WIDE_DEFAULT = %( 33 | {+url}-metadata.json 34 | csv-metadata.json 35 | ).gsub(/^\s+/, '').freeze 36 | 37 | def self.debug?; @debug; end 38 | def self.debug=(value); @debug = value; end 39 | end 40 | end -------------------------------------------------------------------------------- /lib/rdf/tabular/format.rb: -------------------------------------------------------------------------------- 1 | module RDF::Tabular 2 | ## 3 | # Tabular Data/CSV format specification. 4 | # 5 | # @example Obtaining a Tabular format class 6 | # RDF::Format.for(:tabular) #=> RDF::Tabular::Format 7 | # RDF::Format.for(:csv) #=> RDF::Tabular::Format 8 | # RDF::Format.for(:tsv) #=> RDF::Tabular::Format 9 | # RDF::Format.for("etc/foaf.csv") 10 | # RDF::Format.for("etc/foaf.tsv") 11 | # RDF::Format.for(file_name: "etc/foaf.csv") 12 | # RDF::Format.for(file_name: "etc/foaf.tsv") 13 | # RDF::Format.for(file_extension: "csv") 14 | # RDF::Format.for(file_extension: "tsv") 15 | # RDF::Format.for(content_type: "text/csv") 16 | # RDF::Format.for(content_type: "text/tab-separated-values") 17 | # RDF::Format.for(content_type: "application/csvm+json") 18 | # 19 | # @example Obtaining serialization format MIME types 20 | # RDF::Format.content_types #=> {"text/csv" => [RDF::Tabular::Format]} 21 | # 22 | # @example Obtaining serialization format file extension mappings 23 | # RDF::Format.file_extensions #=> {:csv => "text/csv"} 24 | # 25 | # @see https://www.w3.org/TR/rdf-testcases/#ntriples 26 | class Format < RDF::Format 27 | content_type 'text/csv;q=0.4', 28 | extensions: [:csv, :tsv], 29 | alias: %w{ 30 | text/tab-separated-values;q=0.4 31 | application/csvm+json 32 | } 33 | content_encoding 'utf-8' 34 | 35 | reader { RDF::Tabular::Reader } 36 | 37 | ## 38 | # Sample detection to see if it matches JSON-LD 39 | # 40 | # Use a text sample to detect the format of an input file. Sub-classes implement 41 | # a matcher sufficient to detect probably format matches, including disambiguating 42 | # between other similar formats. 43 | # 44 | # @param [String] sample Beginning several bytes (~ 1K) of input. 45 | # @return [Boolean] 46 | def self.detect(sample) 47 | !!sample.match(/^(?:(?:\w )+,(?:\w ))$/) 48 | end 49 | 50 | ## 51 | # Hash of CLI commands appropriate for this format 52 | # @return [Hash{Symbol => Lambda(Array, Hash)}] 53 | def self.cli_commands 54 | { 55 | "tabular-json": { 56 | description: "Serialize using tabular JSON", 57 | parse: false, 58 | filter: {format: :tabular}, # Only shows output format set 59 | option_use: {output_format: :disabled}, 60 | help: "tabular-json --input-format tabular files ...\nGenerate tabular JSON output, rather than RDF for Tabular data", 61 | lambda: ->(argv, opts) do 62 | raise ArgumentError, "Outputting Tabular JSON only allowed when input format is tabular." unless opts[:format] == :tabular 63 | out = opts[:output] || $stdout 64 | out.set_encoding(Encoding::UTF_8) if RUBY_PLATFORM == "java" 65 | RDF::CLI.parse(argv, **opts) do |reader| 66 | out.puts reader.to_json 67 | end 68 | end 69 | } 70 | } 71 | end 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /lib/rdf/tabular/literal.rb: -------------------------------------------------------------------------------- 1 | # CSVW-specific literal classes 2 | 3 | require 'rdf' 4 | require 'rdf/xsd' 5 | 6 | module RDF::Tabular 7 | ## 8 | # A JSON literal. 9 | class JSON < RDF::Literal 10 | DATATYPE = RDF::Tabular::CSVW.json 11 | GRAMMAR = nil 12 | 13 | ## 14 | # @param [Object] value 15 | # @option options [String] :lexical (nil) 16 | def initialize(value, **options) 17 | @datatype = options[:datatype] || DATATYPE 18 | @string = options[:lexical] if options.has_key?(:lexical) 19 | if value.is_a?(String) 20 | @string ||= value 21 | else 22 | @object = value 23 | end 24 | end 25 | 26 | ## 27 | # Parse value, if necessary 28 | # 29 | # @return [Object] 30 | def object 31 | @object ||= ::JSON.parse(value) 32 | end 33 | 34 | def to_s 35 | @string ||= value.to_json 36 | end 37 | end 38 | end -------------------------------------------------------------------------------- /lib/rdf/tabular/version.rb: -------------------------------------------------------------------------------- 1 | module RDF::Tabular::VERSION 2 | VERSION_FILE = File.join(File.expand_path(File.dirname(__FILE__)), "..", "..", "..", "VERSION") 3 | MAJOR, MINOR, TINY, EXTRA = File.read(VERSION_FILE).chop.split(".") 4 | 5 | STRING = [MAJOR, MINOR, TINY, EXTRA].compact.join('.') 6 | 7 | ## 8 | # @return [String] 9 | def self.to_s() STRING end 10 | 11 | ## 12 | # @return [String] 13 | def self.to_str() STRING end 14 | 15 | ## 16 | # @return [Array(Integer, Integer, Integer)] 17 | def self.to_a() STRING.split(".") end 18 | end 19 | -------------------------------------------------------------------------------- /rdf-tabular.gemspec: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby -rubygems 2 | # -*- encoding: utf-8 -*- 3 | 4 | Gem::Specification.new do |gem| 5 | gem.version = File.read('VERSION').chomp 6 | gem.date = File.mtime('VERSION').strftime('%Y-%m-%d') 7 | 8 | gem.name = "rdf-tabular" 9 | gem.homepage = "https://github.com/ruby-rdf/rdf-tabular" 10 | gem.license = 'Unlicense' 11 | gem.summary = "Tabular Data RDF Reader and JSON serializer." 12 | gem.description = "RDF::Tabular processes tabular data with metadata creating RDF or JSON output." 13 | gem.metadata = { 14 | "documentation_uri" => "https://ruby-rdf.github.io/rdf-tabular", 15 | "bug_tracker_uri" => "https://github.com/ruby-rdf/rdf-tabular/issues", 16 | "homepage_uri" => "https://github.com/ruby-rdf/rdf-tabular", 17 | "mailing_list_uri" => "https://lists.w3.org/Archives/Public/public-rdf-ruby/", 18 | "source_code_uri" => "https://github.com/ruby-rdf/rdf-tabular", 19 | } 20 | 21 | gem.authors = ['Gregg Kellogg'] 22 | gem.email = 'public-rdf-ruby@w3.org' 23 | 24 | gem.platform = Gem::Platform::RUBY 25 | gem.files = %w(AUTHORS README.md UNLICENSE VERSION) + Dir.glob('etc/*') + Dir.glob('lib/**/*.rb') 26 | gem.require_paths = %w(lib) 27 | gem.extensions = %w() 28 | gem.test_files = Dir.glob('spec/*.rb') + Dir.glob('spec/data/**') 29 | 30 | gem.required_ruby_version = '>= 3.0' 31 | gem.requirements = [] 32 | gem.add_runtime_dependency 'csv', '~> 3.2', '>= 3.2.8' 33 | gem.add_runtime_dependency 'bcp47_spec', '~> 0.2' 34 | gem.add_runtime_dependency 'rdf', '~> 3.3' 35 | gem.add_runtime_dependency 'rdf-vocab', '~> 3.3' 36 | gem.add_runtime_dependency 'rdf-xsd', '~> 3.3' 37 | gem.add_runtime_dependency 'json-ld', '~> 3.3' 38 | gem.add_runtime_dependency 'addressable', '~> 2.8' 39 | gem.add_development_dependency 'getoptlong', '~> 0.2' 40 | gem.add_development_dependency 'nokogiri', '~> 1.15', '>= 1.13.4' 41 | gem.add_development_dependency 'rspec', '~> 3.12' 42 | gem.add_development_dependency 'rspec-its', '~> 1.3' 43 | gem.add_development_dependency 'rdf-isomorphic', '~> 3.3' 44 | gem.add_development_dependency 'rdf-spec', '~> 3.3' 45 | gem.add_development_dependency 'rdf-turtle', '~> 3.3' 46 | gem.add_development_dependency 'sparql', '~> 3.3' 47 | gem.add_development_dependency 'webmock', '~> 3.19' 48 | gem.add_development_dependency 'yard' , '~> 0.9' 49 | 50 | gem.post_install_message = nil 51 | end 52 | -------------------------------------------------------------------------------- /script/parse: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'rubygems' 3 | require "bundler/setup" 4 | require 'logger' 5 | $:.unshift(File.expand_path("../../lib", __FILE__)) 6 | begin 7 | require 'linkeddata' 8 | rescue LoadError 9 | end 10 | require 'rdf/tabular' 11 | require 'getoptlong' 12 | 13 | def run(input, **options) 14 | reader_class = RDF::Reader.for(options[:input_format].to_sym) 15 | raise "Reader not found for #{options[:input_format]}" unless reader_class 16 | 17 | reader_class.send((input.is_a?(String) ? :open : :new), input, **options[:parser_options]) do |reader| 18 | if [:json, :atd].include?(options[:output_format]) 19 | options[:output].puts reader.to_json(atd: options[:output_format] == :atd) 20 | else 21 | RDF::Writer.for(options[:output_format]). 22 | new(options[:output], prefixes: reader.prefixes, standard_prefixes: true) do |writer| 23 | writer << reader 24 | end 25 | end 26 | end 27 | rescue 28 | fname = input.respond_to?(:path) ? input.path : (input.is_a?(String) ? input : "-stdin-") 29 | STDERR.puts("Error in #{fname}: #{$!}") 30 | raise 31 | end 32 | 33 | logger = Logger.new(STDERR) 34 | logger.level = Logger::WARN 35 | logger.formatter = lambda {|severity, datetime, progname, msg| "#{severity}: #{msg}\n"} 36 | 37 | parser_options = { 38 | :base => nil, 39 | :progress => false, 40 | :profile => false, 41 | :validate => false, 42 | :strict => false, 43 | :minimal => false, 44 | logger: logger, 45 | } 46 | 47 | options = { 48 | :parser_options => parser_options, 49 | :output => STDOUT, 50 | :output_format => :turtle, 51 | :input_format => :tabular, 52 | } 53 | input = nil 54 | 55 | opts = GetoptLong.new( 56 | ["--dbg", GetoptLong::NO_ARGUMENT], 57 | ["--execute", "-e", GetoptLong::REQUIRED_ARGUMENT], 58 | ["--decode-uri", GetoptLong::NO_ARGUMENT], 59 | ["--format", GetoptLong::REQUIRED_ARGUMENT], 60 | ["--minimal", GetoptLong::NO_ARGUMENT], 61 | ["--output", "-o", GetoptLong::REQUIRED_ARGUMENT], 62 | ["--quiet", GetoptLong::NO_ARGUMENT], 63 | ["--uri", GetoptLong::REQUIRED_ARGUMENT], 64 | ["--validate", GetoptLong::NO_ARGUMENT], 65 | ["--verbose", GetoptLong::NO_ARGUMENT] 66 | ) 67 | opts.each do |opt, arg| 68 | case opt 69 | when '--dbg' then logger.level = Logger::DEBUG 70 | when "--decode-uri" then parser_options[:decode_uri] = true 71 | when '--execute' then input = arg 72 | when '--format' then options[:output_format] = arg.to_sym 73 | when '--minimal' then parser_options[:minimal] = true 74 | when '--output' then options[:output] = File.open(arg, "w") 75 | when '--quiet' 76 | options[:quiet] = options[:quiet].to_i + 1 77 | logger.level = Logger::FATAL 78 | when '--uri' then parser_options[:base] = arg 79 | when '--validate' then parser_options[:validate] = true 80 | when '--verbose' then $verbose = true 81 | end 82 | end 83 | 84 | if ARGV.empty? 85 | s = input ? input : $stdin.read 86 | run(StringIO.new(s), **options) 87 | else 88 | ARGV.each do |test_file| 89 | run(test_file, **options) 90 | end 91 | end 92 | puts 93 | -------------------------------------------------------------------------------- /script/tc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'rubygems' 3 | $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", 'lib'))) 4 | require "bundler/setup" 5 | require 'logger' 6 | require 'rdf/tabular' 7 | require 'rdf/isomorphic' 8 | require File.expand_path(File.join(File.dirname(__FILE__), "..", 'spec', 'spec_helper')) 9 | require File.expand_path(File.join(File.dirname(__FILE__), "..", 'spec', 'suite_helper')) 10 | require 'getoptlong' 11 | 12 | ASSERTOR = "https://greggkellogg.net/foaf#me" 13 | RUN_TIME = Time.now 14 | 15 | def earl_preamble(options) 16 | options[:output].write File.read(File.expand_path("../../etc/doap.ttl", __FILE__)) 17 | options[:output].puts %( 18 | <> foaf:primaryTopic ; 19 | dc:issued "#{RUN_TIME.xmlschema}"^^xsd:dateTime ; 20 | foaf:maker <#{ASSERTOR}> . 21 | 22 | <#{ASSERTOR}> a foaf:Person, earl:Assertor; 23 | foaf:name "Gregg Kellogg"; 24 | foaf:title "Implementor"; 25 | foaf:homepage . 26 | 27 | 28 | doap:release . 29 | 30 | a doap:Version; 31 | doap:name "rdf-tabular-#{RDF::Tabular::VERSION}"; 32 | doap:revision "#{RDF::Tabular::VERSION}" . 33 | ) 34 | end 35 | 36 | def run_tc(t, **options) 37 | STDERR.write "run #{t.id}" 38 | 39 | if options[:verbose] 40 | puts "\nTestCase: #{t.inspect}" 41 | puts "\nInput:\n" + t.input if t.input 42 | puts "\nExpected:\n" + t.expected if t.expected 43 | end 44 | 45 | graph = RDF::Repository.new 46 | json_result = nil 47 | result = nil 48 | t.logger = Logger.new(STDERR) 49 | t.logger.level = options[:level] 50 | t.logger.formatter = lambda {|severity, datetime, progname, msg| "#{severity}: #{msg}\n"} 51 | 52 | begin 53 | puts "open #{t.action}" if options[:verbose] 54 | options = {base_uri: t.base}.merge(options) 55 | 56 | RDF::Tabular::Reader.open(t.action, 57 | **t.reader_options.merge( 58 | base_uri: t.base, 59 | validate: t.validation?, 60 | logger: t.logger) 61 | ) do |reader| 62 | t.metadata = reader.metadata 63 | 64 | if t.positive_test? 65 | if t.json? 66 | result = if t.evaluate? 67 | RDF::Util::File.open_file(t.result) do |res| 68 | ::JSON.parse(json_result = reader.to_json) == ::JSON.parse(res.read) ? "passed" : "failed" 69 | end 70 | else 71 | ::JSON.parse(result).is_a?(Hash) ? "passed" : "failed" 72 | end 73 | else # RDF or Validation 74 | result = if result 75 | result 76 | elsif t.evaluate? 77 | graph << reader 78 | output_graph = RDF::Repository.load(t.result, format: :turtle, base_uri: t.base) 79 | graph.isomorphic_with?(output_graph) ? "passed" : "failed" 80 | elsif t.validation? 81 | reader.validate! 82 | "passed" 83 | end 84 | result = "passed" if t.id.include?("rdf#test158") && result == "failed" 85 | result = "failed" if result == "passed" && t.warning? && !t.logger.log_statistics[:warn] 86 | end 87 | else 88 | begin 89 | if t.json? 90 | reader.to_json 91 | elsif t.validation? 92 | reader.validate! 93 | else 94 | graph << reader 95 | end 96 | STDERR.puts "Expected exception" if options[:verbose] 97 | result = "failed" 98 | rescue RDF::ReaderError, RDF::Tabular::Error 99 | result = "passed" 100 | end 101 | end 102 | end 103 | rescue ::RDF::Tabular::Error, IOError => e 104 | # Special case 105 | if t.negative_test? 106 | result = "passed" 107 | else 108 | STDERR.puts "#{"exception" unless options[:quiet]}: #{e}" 109 | result = "failed" 110 | end 111 | rescue Interrupt 112 | STDERR.puts "\nHalting" 113 | exit(1) 114 | rescue Exception => e 115 | STDERR.puts "#{"exception" unless options[:quiet]}: #{e}" 116 | if options[:quiet] 117 | return 118 | else 119 | raise 120 | end 121 | end 122 | 123 | if t.json? 124 | options[:output].puts("\nOutput:\n" + json_result) unless options[:quiet] 125 | else 126 | options[:output].puts("\nOutput:\n" + graph.dump(:ttl, standard_prefixes: true, literal_shorthand: false)) unless options[:quiet] 127 | end 128 | 129 | if options[:earl] 130 | options[:output].puts %{ 131 | [ a earl:Assertion; 132 | earl:assertedBy <#{ASSERTOR}>; 133 | earl:subject ; 134 | earl:test <#{RDF::URI(options[:manifest]).join(t.id)}>; 135 | earl:result [ 136 | a earl:TestResult; 137 | earl:outcome earl:#{result}; 138 | dc:date "#{RUN_TIME.xmlschema}"^^xsd:dateTime]; 139 | earl:mode earl:automatic ] . 140 | } 141 | end 142 | 143 | options[:result_count][result] ||= 0 144 | options[:result_count][result] += 1 145 | puts "#{"test result:" unless options[:quiet]} #{result}" 146 | end 147 | 148 | options = { 149 | output: STDOUT, 150 | level: Logger::WARN, 151 | } 152 | 153 | opts = GetoptLong.new( 154 | ["--help", "-?", GetoptLong::NO_ARGUMENT], 155 | ["--dbg", GetoptLong::NO_ARGUMENT], 156 | ["--earl", GetoptLong::NO_ARGUMENT], 157 | ["--quiet", "-q", GetoptLong::NO_ARGUMENT], 158 | ["--output", "-o", GetoptLong::REQUIRED_ARGUMENT], 159 | ["--validate", GetoptLong::NO_ARGUMENT], 160 | ["--verbose", "-v", GetoptLong::NO_ARGUMENT] 161 | ) 162 | 163 | def help(options) 164 | puts "Usage: #{$0} [options] [test-number ...]" 165 | puts "Options:" 166 | puts " --debug: Display detailed debug output" 167 | puts " --earl: Generate EARL report" 168 | puts " --quiet: Minimal output" 169 | puts " --output: Output to specified file" 170 | puts " --validate: Validate input" 171 | puts " --verbose: Verbose processing" 172 | puts " --help,-?: This message" 173 | exit(0) 174 | end 175 | 176 | 177 | opts.each do |opt, arg| 178 | case opt 179 | when '--help' then help(options) 180 | when '--dbg' then options[:level] = Logger::DEBUG 181 | when '--earl' 182 | options[:quiet] = options[:earl] = true 183 | options[:level] = Logger::FATAL 184 | when '--output' then options[:output] = File.open(arg, "w") 185 | when '--quiet' 186 | options[:quiet] = options[:quiet].to_i + 1 187 | options[:level] = Logger::FATAL 188 | when '--validate' then options[:validate] = true 189 | when '--verbose' then options[:verbose] = true 190 | end 191 | end 192 | 193 | earl_preamble(options) if options[:earl] 194 | 195 | WebMock.allow_net_connect!(net_http_connect_on_start: true) 196 | 197 | result_count = {} 198 | 199 | %w(rdf json validation nonnorm).each do |variant| 200 | manifest = Fixtures::SuiteTest::BASE + "manifest-#{variant}.jsonld" 201 | 202 | Fixtures::SuiteTest::Manifest.open(manifest, manifest[0..-8]) do |m| 203 | m.entries.each do |t| 204 | next unless ARGV.empty? || ARGV.any? {|n| t.id.match(/#{n}/)} 205 | run_tc(t, **options.merge(manifest: manifest, result_count: result_count)) 206 | end 207 | end 208 | end 209 | 210 | result_count.each do |result, count| 211 | puts "#{result}: #{count}" 212 | end 213 | -------------------------------------------------------------------------------- /spec/.gitignore: -------------------------------------------------------------------------------- 1 | /w3c-csvw 2 | -------------------------------------------------------------------------------- /spec/data/countries-minimal.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "@id": "http://example.org/countries.csv#AD", 4 | "http://www.geonames.org/ontology#countryCode": "AD", 5 | "schema:latitude": 42.546245, 6 | "schema:longitude": 1.601554, 7 | "schema:name": "Andorra" 8 | }, 9 | { 10 | "@id": "http://example.org/countries.csv#AE", 11 | "http://www.geonames.org/ontology#countryCode": "AE", 12 | "schema:latitude": 23.424076, 13 | "schema:longitude": 53.847818, 14 | "schema:name": "United Arab Emirates" 15 | }, 16 | { 17 | "@id": "http://example.org/countries.csv#AF", 18 | "http://www.geonames.org/ontology#countryCode": "AF", 19 | "schema:latitude": 33.93911, 20 | "schema:longitude": 67.709953, 21 | "schema:name": "Afghanistan" 22 | }, 23 | { 24 | "countryRef": "http://example.org/countries.csv#AF", 25 | "year": "1960", 26 | "population": 9616353 27 | }, 28 | { 29 | "countryRef": "http://example.org/countries.csv#AF", 30 | "year": "1961", 31 | "population": 9799379 32 | }, 33 | { 34 | "countryRef": "http://example.org/countries.csv#AF", 35 | "year": "1962", 36 | "population": 9989846 37 | } 38 | ] 39 | -------------------------------------------------------------------------------- /spec/data/countries-minimal.ttl: -------------------------------------------------------------------------------- 1 | @prefix geo: . 2 | @prefix schema: . 3 | @prefix xsd: . 4 | 5 | schema:latitude "42.546245"^^xsd:double; 6 | schema:longitude "1.601554"^^xsd:double; 7 | schema:name "Andorra"; 8 | geo:countryCode "AD" . 9 | 10 | schema:latitude "23.424076"^^xsd:double; 11 | schema:longitude "53.847818"^^xsd:double; 12 | schema:name "United Arab Emirates"; 13 | geo:countryCode "AE" . 14 | 15 | schema:latitude "33.93911"^^xsd:double; 16 | schema:longitude "67.709953"^^xsd:double; 17 | schema:name "Afghanistan"; 18 | geo:countryCode "AF" . 19 | 20 | [ 21 | ; 22 | 9616353; 23 | "1960"^^xsd:gYear 24 | ] . 25 | 26 | [ 27 | ; 28 | 9799379; 29 | "1961"^^xsd:gYear 30 | ] . 31 | 32 | [ 33 | ; 34 | 9989846; 35 | "1962"^^xsd:gYear 36 | ] . 37 | -------------------------------------------------------------------------------- /spec/data/countries-standard.json: -------------------------------------------------------------------------------- 1 | { 2 | "tables": [ 3 | { 4 | "url": "http://example.org/countries.csv", 5 | "row": [ 6 | { 7 | "url": "http://example.org/countries.csv#row=2", 8 | "rownum": 1, 9 | "describes": [ 10 | { 11 | "@id": "http://example.org/countries.csv#AD", 12 | "http://www.geonames.org/ontology#countryCode": "AD", 13 | "schema:latitude": 42.546245, 14 | "schema:longitude": 1.601554, 15 | "schema:name": "Andorra" 16 | } 17 | ] 18 | }, 19 | { 20 | "url": "http://example.org/countries.csv#row=3", 21 | "rownum": 2, 22 | "describes": [ 23 | { 24 | "@id": "http://example.org/countries.csv#AE", 25 | "http://www.geonames.org/ontology#countryCode": "AE", 26 | "schema:latitude": 23.424076, 27 | "schema:longitude": 53.847818, 28 | "schema:name": "United Arab Emirates" 29 | } 30 | ] 31 | }, 32 | { 33 | "url": "http://example.org/countries.csv#row=4", 34 | "rownum": 3, 35 | "describes": [ 36 | { 37 | "@id": "http://example.org/countries.csv#AF", 38 | "http://www.geonames.org/ontology#countryCode": "AF", 39 | "schema:latitude": 33.93911, 40 | "schema:longitude": 67.709953, 41 | "schema:name": "Afghanistan" 42 | } 43 | ] 44 | } 45 | ] 46 | }, 47 | { 48 | "url": "http://example.org/country_slice.csv", 49 | "row": [ 50 | { 51 | "url": "http://example.org/country_slice.csv#row=2", 52 | "rownum": 1, 53 | "describes": [ 54 | { 55 | "countryRef": "http://example.org/countries.csv#AF", 56 | "year": "1960", 57 | "population": 9616353 58 | } 59 | ] 60 | }, 61 | { 62 | "url": "http://example.org/country_slice.csv#row=3", 63 | "rownum": 2, 64 | "describes": [ 65 | { 66 | "countryRef": "http://example.org/countries.csv#AF", 67 | "year": "1961", 68 | "population": 9799379 69 | } 70 | ] 71 | }, 72 | { 73 | "url": "http://example.org/country_slice.csv#row=4", 74 | "rownum": 3, 75 | "describes": [ 76 | { 77 | "countryRef": "http://example.org/countries.csv#AF", 78 | "year": "1962", 79 | "population": 9989846 80 | } 81 | ] 82 | } 83 | ] 84 | } 85 | ] 86 | } 87 | -------------------------------------------------------------------------------- /spec/data/countries-standard.ttl: -------------------------------------------------------------------------------- 1 | @prefix csvw: . 2 | @prefix geo: . 3 | @prefix rdf: . 4 | @prefix schema: . 5 | @prefix xsd: . 6 | 7 | schema:latitude "42.546245"^^xsd:double; 8 | schema:longitude "1.601554"^^xsd:double; 9 | schema:name "Andorra"; 10 | geo:countryCode "AD" . 11 | 12 | schema:latitude "23.424076"^^xsd:double; 13 | schema:longitude "53.847818"^^xsd:double; 14 | schema:name "United Arab Emirates"; 15 | geo:countryCode "AE" . 16 | 17 | schema:latitude "33.93911"^^xsd:double; 18 | schema:longitude "67.709953"^^xsd:double; 19 | schema:name "Afghanistan"; 20 | geo:countryCode "AF" . 21 | 22 | [ 23 | a csvw:TableGroup; 24 | csvw:table [ 25 | a csvw:Table; 26 | csvw:row [ 27 | a csvw:Row; 28 | csvw:describes ; 29 | csvw:rownum 1; 30 | csvw:url 31 | ], [ 32 | a csvw:Row; 33 | csvw:describes ; 34 | csvw:rownum 2; 35 | csvw:url 36 | ], [ 37 | a csvw:Row; 38 | csvw:describes ; 39 | csvw:rownum 3; 40 | csvw:url 41 | ]; 42 | csvw:url 43 | ], [ 44 | a csvw:Table; 45 | csvw:row [ 46 | a csvw:Row; 47 | csvw:describes [ 48 | ; 49 | 9616353; 50 | "1960"^^xsd:gYear 51 | ]; 52 | csvw:rownum 1; 53 | csvw:url 54 | ], [ 55 | a csvw:Row; 56 | csvw:describes [ 57 | ; 58 | 9799379; 59 | "1961"^^xsd:gYear 60 | ]; 61 | csvw:rownum 2; 62 | csvw:url 63 | ], [ 64 | a csvw:Row; 65 | csvw:describes [ 66 | ; 67 | 9989846; 68 | "1962"^^xsd:gYear 69 | ]; 70 | csvw:rownum 3; 71 | csvw:url 72 | ]; 73 | csvw:url 74 | ] 75 | ] . 76 | -------------------------------------------------------------------------------- /spec/data/countries.csv: -------------------------------------------------------------------------------- 1 | countryCode,latitude,longitude,name 2 | AD,42.546245,1.601554,Andorra 3 | AE,23.424076,53.847818,"United Arab Emirates" 4 | AF,33.93911,67.709953,Afghanistan -------------------------------------------------------------------------------- /spec/data/countries.csv-minimal.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "countryCode": "AD", 3 | "latitude": "42.546245", 4 | "longitude": "1.601554", 5 | "name": "Andorra" 6 | }, { 7 | "countryCode": "AE", 8 | "latitude": "23.424076", 9 | "longitude": "53.847818", 10 | "name": "United Arab Emirates" 11 | }, { 12 | "countryCode": "AF", 13 | "latitude": "33.93911", 14 | "longitude": "67.709953", 15 | "name": "Afghanistan" 16 | }] -------------------------------------------------------------------------------- /spec/data/countries.csv-minimal.ttl: -------------------------------------------------------------------------------- 1 | @base . 2 | 3 | _:8228a149-8efe-448d-b15f-8abf92e7bd17 4 | <#countryCode> "AD" ; 5 | <#latitude> "42.546245" ; 6 | <#longitude> "1.601554" ; 7 | <#name> "Andorra" . 8 | 9 | _:ec59dcfc-872a-4144-822b-9ad5e2c6149c 10 | <#countryCode> "AE" ; 11 | <#latitude> "23.424076" ; 12 | <#longitude> "53.847818" ; 13 | <#name> "United Arab Emirates" . 14 | 15 | _:e8f2e8e9-3d02-4bf5-b4f1-4794ba5b52c9 16 | <#countryCode> "AF" ; 17 | <#latitude> "33.93911" ; 18 | <#longitude> "67.709953" ; 19 | <#name> "Afghanistan" . -------------------------------------------------------------------------------- /spec/data/countries.csv-standard.json: -------------------------------------------------------------------------------- 1 | { 2 | "tables": [{ 3 | "url": "http://example.org/countries.csv", 4 | "row": [{ 5 | "url": "http://example.org/countries.csv#row=2", 6 | "rownum": 1, 7 | "describes": [{ 8 | "countryCode": "AD", 9 | "latitude": "42.546245", 10 | "longitude": "1.601554", 11 | "name": "Andorra" 12 | }] 13 | }, { 14 | "url": "http://example.org/countries.csv#row=3", 15 | "rownum": 2, 16 | "describes": [{ 17 | "countryCode": "AE", 18 | "latitude": "23.424076", 19 | "longitude": "53.847818", 20 | "name": "United Arab Emirates" 21 | }] 22 | }, { 23 | "url": "http://example.org/countries.csv#row=4", 24 | "rownum": 3, 25 | "describes": [{ 26 | "countryCode": "AF", 27 | "latitude": "33.93911", 28 | "longitude": "67.709953", 29 | "name": "Afghanistan" 30 | }] 31 | }] 32 | }] 33 | } 34 | -------------------------------------------------------------------------------- /spec/data/countries.csv-standard.ttl: -------------------------------------------------------------------------------- 1 | @base . 2 | @prefix csvw: . 3 | @prefix xsd: . 4 | 5 | [ 6 | a csvw:TableGroup; 7 | csvw:table [ a csvw:Table ; 8 | csvw:url ; 9 | csvw:row [ 10 | a csvw:Row; 11 | csvw:rownum "1"^^xsd:integer ; 12 | csvw:url <#row=2> ; 13 | csvw:describes _:8228a149-8efe-448d-b15f-8abf92e7bd17 14 | ], [ 15 | a csvw:Row; 16 | csvw:rownum "2"^^xsd:integer ; 17 | csvw:url <#row=3> ; 18 | csvw:describes _:ec59dcfc-872a-4144-822b-9ad5e2c6149c 19 | ], [ 20 | a csvw:Row; 21 | csvw:rownum "3"^^xsd:integer ; 22 | csvw:url <#row=4> ; 23 | csvw:describes _:e8f2e8e9-3d02-4bf5-b4f1-4794ba5b52c9 24 | ] 25 | ] 26 | ] . 27 | 28 | _:8228a149-8efe-448d-b15f-8abf92e7bd17 29 | <#countryCode> "AD" ; 30 | <#latitude> "42.546245" ; 31 | <#longitude> "1.601554" ; 32 | <#name> "Andorra" . 33 | 34 | _:ec59dcfc-872a-4144-822b-9ad5e2c6149c 35 | <#countryCode> "AE" ; 36 | <#latitude> "23.424076" ; 37 | <#longitude> "53.847818" ; 38 | <#name> "United Arab Emirates" . 39 | 40 | _:e8f2e8e9-3d02-4bf5-b4f1-4794ba5b52c9 41 | <#countryCode> "AF" ; 42 | <#latitude> "33.93911" ; 43 | <#longitude> "67.709953" ; 44 | <#name> "Afghanistan" . -------------------------------------------------------------------------------- /spec/data/countries.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Example of CSVW metadata in HTML referencing external CSV 4 | 10 | 11 | 12 |

This shows an example of embeddeding CSV metadata within an HTML document referencing an external CSV which contains the following:

13 | 14 | 15 | 16 | 17 | 18 | 19 |
Countries
countryCodelatitudelongitudename
AD42.51.6Andorra
AE23.453.8United Arab Emirates
AF33.967.7Afghanistan
20 | 21 | 22 | 23 | 24 | 25 | 26 |
Country Slice
countryRefyearpopulation
AF19609616353
AF19619799379
AF19629989846
27 |

The metadata is describe here in a script element:

28 |
29 |       
84 |       

Processing this file should create results similar to those described in The CSV2JSON Simple Example in [[!csv2json]].

85 |
86 |

The metadata is also described in JSON.

87 | 88 | -------------------------------------------------------------------------------- /spec/data/countries.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://www.w3.org/ns/csvw", 3 | "tables": [{ 4 | "url": "http://example.org/countries.csv", 5 | "tableSchema": { 6 | "columns": [{ 7 | "name": "countryCode", 8 | "titles": "countryCode", 9 | "datatype": "string", 10 | "propertyUrl": "http://www.geonames.org/ontology{#_name}" 11 | }, { 12 | "name": "latitude", 13 | "titles": "latitude", 14 | "datatype": "number" 15 | }, { 16 | "name": "longitude", 17 | "titles": "longitude", 18 | "datatype": "number" 19 | }, { 20 | "name": "name", 21 | "titles": "name", 22 | "datatype": "string" 23 | }], 24 | "aboutUrl": "http://example.org/countries.csv{#countryCode}", 25 | "propertyUrl": "http://schema.org/{_name}", 26 | "primaryKey": "countryCode" 27 | } 28 | }, { 29 | "url": "http://example.org/country_slice.csv", 30 | "tableSchema": { 31 | "columns": [{ 32 | "name": "countryRef", 33 | "titles": "countryRef", 34 | "valueUrl": "http://example.org/countries.csv{#countryRef}" 35 | }, { 36 | "name": "year", 37 | "titles": "year", 38 | "datatype": "gYear" 39 | }, { 40 | "name": "population", 41 | "titles": "population", 42 | "datatype": "integer" 43 | }], 44 | "foreignKeys": [{ 45 | "columnReference": "countryRef", 46 | "reference": { 47 | "resource": "http://example.org/countries.csv", 48 | "columnReference": "countryCode" 49 | } 50 | }] 51 | } 52 | }] 53 | } 54 | -------------------------------------------------------------------------------- /spec/data/countries_embed-minimal.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "@id": "http://example.org/countries_embed.html#countries_AD", 4 | "http://www.geonames.org/ontology#countryCode": "AD", 5 | "schema:latitude": 42.5, 6 | "schema:longitude": 1.6, 7 | "schema:name": "Andorra" 8 | }, 9 | { 10 | "@id": "http://example.org/countries_embed.html#countries_AE", 11 | "http://www.geonames.org/ontology#countryCode": "AE", 12 | "schema:latitude": 23.4, 13 | "schema:longitude": 53.8, 14 | "schema:name": "United Arab Emirates" 15 | }, 16 | { 17 | "@id": "http://example.org/countries_embed.html#countries_AF", 18 | "http://www.geonames.org/ontology#countryCode": "AF", 19 | "schema:latitude": 33.9, 20 | "schema:longitude": 67.7, 21 | "schema:name": "Afghanistan" 22 | }, 23 | { 24 | "http://example.org/countryRef": "http://example.org/countries_embed.html#countries_AF", 25 | "http://example.org/year": "1960", 26 | "http://example.org/population": 9616353 27 | }, 28 | { 29 | "http://example.org/countryRef": "http://example.org/countries_embed.html#countries_AF", 30 | "http://example.org/year": "1961", 31 | "http://example.org/population": 9799379 32 | }, 33 | { 34 | "http://example.org/countryRef": "http://example.org/countries_embed.html#countries_AF", 35 | "http://example.org/year": "1962", 36 | "http://example.org/population": 9989846 37 | } 38 | ] 39 | -------------------------------------------------------------------------------- /spec/data/countries_embed-minimal.ttl: -------------------------------------------------------------------------------- 1 | @prefix geonames: . 2 | @prefix schema: . 3 | @prefix xsd: . 4 | 5 | schema:latitude "42.5"^^xsd:double; 6 | schema:longitude "1.6"^^xsd:double; 7 | schema:name "Andorra"; 8 | geonames:countryCode "AD" . 9 | 10 | schema:latitude "23.4"^^xsd:double; 11 | schema:longitude "53.8"^^xsd:double; 12 | schema:name "United Arab Emirates"; 13 | geonames:countryCode "AE" . 14 | 15 | schema:latitude "33.9"^^xsd:double; 16 | schema:longitude "67.7"^^xsd:double; 17 | schema:name "Afghanistan"; 18 | geonames:countryCode "AF" . 19 | 20 | [ 21 | ; 22 | "9989846"^^xsd:integer; 23 | "1962"^^xsd:gYear 24 | ] . 25 | 26 | [ 27 | ; 28 | "9799379"^^xsd:integer; 29 | "1961"^^xsd:gYear 30 | ] . 31 | 32 | [ 33 | ; 34 | "9616353"^^xsd:integer; 35 | "1960"^^xsd:gYear 36 | ] . 37 | -------------------------------------------------------------------------------- /spec/data/countries_embed-standard.json: -------------------------------------------------------------------------------- 1 | { 2 | "tables": [ 3 | { 4 | "url": "http://example.org/countries_embed.html#countries", 5 | "row": [ 6 | { 7 | "url": "http://example.org/countries_embed.html#row=5", 8 | "rownum": 5, 9 | "describes": [ 10 | { 11 | "@id": "http://example.org/countries_embed.html#countries_AD", 12 | "http://www.geonames.org/ontology#countryCode": "AD", 13 | "schema:latitude": 42.5, 14 | "schema:longitude": 1.6, 15 | "schema:name": "Andorra" 16 | } 17 | ] 18 | }, 19 | { 20 | "url": "http://example.org/countries_embed.html#row=6", 21 | "rownum": 6, 22 | "describes": [ 23 | { 24 | "@id": "http://example.org/countries_embed.html#countries_AE", 25 | "http://www.geonames.org/ontology#countryCode": "AE", 26 | "schema:latitude": 23.4, 27 | "schema:longitude": 53.8, 28 | "schema:name": "United Arab Emirates" 29 | } 30 | ] 31 | }, 32 | { 33 | "url": "http://example.org/countries_embed.html#row=7", 34 | "rownum": 7, 35 | "describes": [ 36 | { 37 | "@id": "http://example.org/countries_embed.html#countries_AF", 38 | "http://www.geonames.org/ontology#countryCode": "AF", 39 | "schema:latitude": 33.9, 40 | "schema:longitude": 67.7, 41 | "schema:name": "Afghanistan" 42 | } 43 | ] 44 | } 45 | ] 46 | }, 47 | { 48 | "url": "http://example.org/countries_embed.html#country_slice", 49 | "row": [ 50 | { 51 | "url": "http://example.org/countries_embed.html#row=5", 52 | "rownum": 5, 53 | "describes": [ 54 | { 55 | "http://example.org/countryRef": "http://example.org/countries_embed.html#countries_AF", 56 | "http://example.org/year": "1960", 57 | "http://example.org/population": 9616353 58 | } 59 | ] 60 | }, 61 | { 62 | "url": "http://example.org/countries_embed.html#row=6", 63 | "rownum": 6, 64 | "describes": [ 65 | { 66 | "http://example.org/countryRef": "http://example.org/countries_embed.html#countries_AF", 67 | "http://example.org/year": "1961", 68 | "http://example.org/population": 9799379 69 | } 70 | ] 71 | }, 72 | { 73 | "url": "http://example.org/countries_embed.html#row=7", 74 | "rownum": 7, 75 | "describes": [ 76 | { 77 | "http://example.org/countryRef": "http://example.org/countries_embed.html#countries_AF", 78 | "http://example.org/year": "1962", 79 | "http://example.org/population": 9989846 80 | } 81 | ] 82 | } 83 | ] 84 | } 85 | ] 86 | } 87 | -------------------------------------------------------------------------------- /spec/data/countries_embed-standard.ttl: -------------------------------------------------------------------------------- 1 | @prefix csvw: . 2 | @prefix geonames: . 3 | @prefix rdf: . 4 | @prefix schema: . 5 | @prefix xsd: . 6 | 7 | schema:latitude "42.5"^^xsd:double; 8 | schema:longitude "1.6"^^xsd:double; 9 | schema:name "Andorra"; 10 | geonames:countryCode "AD" . 11 | 12 | schema:latitude "23.4"^^xsd:double; 13 | schema:longitude "53.8"^^xsd:double; 14 | schema:name "United Arab Emirates"; 15 | geonames:countryCode "AE" . 16 | 17 | schema:latitude "33.9"^^xsd:double; 18 | schema:longitude "67.7"^^xsd:double; 19 | schema:name "Afghanistan"; 20 | geonames:countryCode "AF" . 21 | 22 | [ 23 | a csvw:TableGroup; 24 | csvw:table [ 25 | a csvw:Table; 26 | csvw:row [ 27 | a csvw:Row; 28 | csvw:describes ; 29 | csvw:rownum "5"^^xsd:integer; 30 | csvw:url 31 | ], [ 32 | a csvw:Row; 33 | csvw:describes ; 34 | csvw:rownum "6"^^xsd:integer; 35 | csvw:url 36 | ], [ 37 | a csvw:Row; 38 | csvw:describes ; 39 | csvw:rownum "7"^^xsd:integer; 40 | csvw:url 41 | ]; 42 | csvw:url 43 | ], [ 44 | a csvw:Table; 45 | csvw:row [ 46 | a csvw:Row; 47 | csvw:describes [ 48 | ; 49 | "9616353"^^xsd:integer; 50 | "1960"^^xsd:gYear 51 | ]; 52 | csvw:rownum "5"^^xsd:integer; 53 | csvw:url 54 | ], [ 55 | a csvw:Row; 56 | csvw:describes [ 57 | ; 58 | "9799379"^^xsd:integer; 59 | "1961"^^xsd:gYear 60 | ]; 61 | csvw:rownum "6"^^xsd:integer; 62 | csvw:url 63 | ], [ 64 | a csvw:Row; 65 | csvw:describes [ 66 | ; 67 | "9989846"^^xsd:integer; 68 | "1962"^^xsd:gYear 69 | ]; 70 | csvw:rownum "7"^^xsd:integer; 71 | csvw:url 72 | ]; 73 | csvw:url 74 | ] 75 | ] . 76 | -------------------------------------------------------------------------------- /spec/data/countries_embed.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Example of CSVW metadata in HTML referencing HTML tables 4 | 10 | 11 | 12 |

This shows an example of embeddeding CSV metadata within an HTML document referencing the following HTML tables which contains the following:

13 | 14 | 15 | 16 | 17 | 18 | 19 |
Countries
countryCodelatitudelongitudename
AD42.51.6Andorra
AE23.453.8United Arab Emirates
AF33.967.7Afghanistan
20 | 21 | 22 | 23 | 24 | 25 | 26 |
Country Slice
countryRefyearpopulation
AF19609616353
AF19619799379
AF19629989846
27 |

The metadata is describe here in a script element:

28 |
29 |       
85 |     
86 |

The metadata is also described in JSON.

87 | 88 | -------------------------------------------------------------------------------- /spec/data/countries_html-minimal.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "@id": "http://example.org/countries.csv#AD", 4 | "http://www.geonames.org/ontology#countryCode": "AD", 5 | "schema:latitude": 42.546245, 6 | "schema:longitude": 1.601554, 7 | "schema:name": "Andorra" 8 | }, 9 | { 10 | "@id": "http://example.org/countries.csv#AE", 11 | "http://www.geonames.org/ontology#countryCode": "AE", 12 | "schema:latitude": 23.424076, 13 | "schema:longitude": 53.847818, 14 | "schema:name": "United Arab Emirates" 15 | }, 16 | { 17 | "@id": "http://example.org/countries.csv#AF", 18 | "http://www.geonames.org/ontology#countryCode": "AF", 19 | "schema:latitude": 33.93911, 20 | "schema:longitude": 67.709953, 21 | "schema:name": "Afghanistan" 22 | }, 23 | { 24 | "countryRef": "http://example.org/countries.csv#AF", 25 | "year": "1960", 26 | "population": 9616353 27 | }, 28 | { 29 | "countryRef": "http://example.org/countries.csv#AF", 30 | "year": "1961", 31 | "population": 9799379 32 | }, 33 | { 34 | "countryRef": "http://example.org/countries.csv#AF", 35 | "year": "1962", 36 | "population": 9989846 37 | } 38 | ] 39 | -------------------------------------------------------------------------------- /spec/data/countries_html-minimal.ttl: -------------------------------------------------------------------------------- 1 | @prefix geonames: . 2 | @prefix schema: . 3 | @prefix xsd: . 4 | 5 | schema:latitude "42.546245"^^xsd:double; 6 | schema:longitude "1.601554"^^xsd:double; 7 | schema:name "Andorra"; 8 | geonames:countryCode "AD" . 9 | 10 | schema:latitude "23.424076"^^xsd:double; 11 | schema:longitude "53.847818"^^xsd:double; 12 | schema:name "United Arab Emirates"; 13 | geonames:countryCode "AE" . 14 | 15 | schema:latitude "33.93911"^^xsd:double; 16 | schema:longitude "67.709953"^^xsd:double; 17 | schema:name "Afghanistan"; 18 | geonames:countryCode "AF" . 19 | 20 | [ 21 | ; 22 | "9989846"^^xsd:integer; 23 | "1962"^^xsd:gYear 24 | ] . 25 | 26 | [ 27 | ; 28 | "9799379"^^xsd:integer; 29 | "1961"^^xsd:gYear 30 | ] . 31 | 32 | [ 33 | ; 34 | "9616353"^^xsd:integer; 35 | "1960"^^xsd:gYear 36 | ] . 37 | -------------------------------------------------------------------------------- /spec/data/countries_html-standard.json: -------------------------------------------------------------------------------- 1 | { 2 | "tables": [ 3 | { 4 | "url": "http://example.org/countries.csv", 5 | "row": [ 6 | { 7 | "url": "http://example.org/countries.csv#row=2", 8 | "rownum": 1, 9 | "describes": [ 10 | { 11 | "@id": "http://example.org/countries.csv#AD", 12 | "http://www.geonames.org/ontology#countryCode": "AD", 13 | "schema:latitude": 42.546245, 14 | "schema:longitude": 1.601554, 15 | "schema:name": "Andorra" 16 | } 17 | ] 18 | }, 19 | { 20 | "url": "http://example.org/countries.csv#row=3", 21 | "rownum": 2, 22 | "describes": [ 23 | { 24 | "@id": "http://example.org/countries.csv#AE", 25 | "http://www.geonames.org/ontology#countryCode": "AE", 26 | "schema:latitude": 23.424076, 27 | "schema:longitude": 53.847818, 28 | "schema:name": "United Arab Emirates" 29 | } 30 | ] 31 | }, 32 | { 33 | "url": "http://example.org/countries.csv#row=4", 34 | "rownum": 3, 35 | "describes": [ 36 | { 37 | "@id": "http://example.org/countries.csv#AF", 38 | "http://www.geonames.org/ontology#countryCode": "AF", 39 | "schema:latitude": 33.93911, 40 | "schema:longitude": 67.709953, 41 | "schema:name": "Afghanistan" 42 | } 43 | ] 44 | } 45 | ] 46 | }, 47 | { 48 | "url": "http://example.org/country_slice.csv", 49 | "row": [ 50 | { 51 | "url": "http://example.org/country_slice.csv#row=2", 52 | "rownum": 1, 53 | "describes": [ 54 | { 55 | "countryRef": "http://example.org/countries.csv#AF", 56 | "year": "1960", 57 | "population": 9616353 58 | } 59 | ] 60 | }, 61 | { 62 | "url": "http://example.org/country_slice.csv#row=3", 63 | "rownum": 2, 64 | "describes": [ 65 | { 66 | "countryRef": "http://example.org/countries.csv#AF", 67 | "year": "1961", 68 | "population": 9799379 69 | } 70 | ] 71 | }, 72 | { 73 | "url": "http://example.org/country_slice.csv#row=4", 74 | "rownum": 3, 75 | "describes": [ 76 | { 77 | "countryRef": "http://example.org/countries.csv#AF", 78 | "year": "1962", 79 | "population": 9989846 80 | } 81 | ] 82 | } 83 | ] 84 | } 85 | ] 86 | } 87 | -------------------------------------------------------------------------------- /spec/data/countries_html-standard.ttl: -------------------------------------------------------------------------------- 1 | @prefix csvw: . 2 | @prefix geonames: . 3 | @prefix rdf: . 4 | @prefix schema: . 5 | @prefix xsd: . 6 | 7 | schema:latitude "42.546245"^^xsd:double; 8 | schema:longitude "1.601554"^^xsd:double; 9 | schema:name "Andorra"; 10 | geonames:countryCode "AD" . 11 | 12 | schema:latitude "23.424076"^^xsd:double; 13 | schema:longitude "53.847818"^^xsd:double; 14 | schema:name "United Arab Emirates"; 15 | geonames:countryCode "AE" . 16 | 17 | schema:latitude "33.93911"^^xsd:double; 18 | schema:longitude "67.709953"^^xsd:double; 19 | schema:name "Afghanistan"; 20 | geonames:countryCode "AF" . 21 | 22 | [ 23 | a csvw:TableGroup; 24 | csvw:table [ 25 | a csvw:Table; 26 | csvw:row [ 27 | a csvw:Row; 28 | csvw:describes ; 29 | csvw:rownum "1"^^xsd:integer; 30 | csvw:url 31 | ], [ 32 | a csvw:Row; 33 | csvw:describes ; 34 | csvw:rownum "2"^^xsd:integer; 35 | csvw:url 36 | ], [ 37 | a csvw:Row; 38 | csvw:describes ; 39 | csvw:rownum "3"^^xsd:integer; 40 | csvw:url 41 | ]; 42 | csvw:url 43 | ], [ 44 | a csvw:Table; 45 | csvw:row [ 46 | a csvw:Row; 47 | csvw:describes [ 48 | ; 49 | "9616353"^^xsd:integer; 50 | "1960"^^xsd:gYear 51 | ]; 52 | csvw:rownum "1"^^xsd:integer; 53 | csvw:url 54 | ], [ 55 | a csvw:Row; 56 | csvw:describes [ 57 | ; 58 | "9799379"^^xsd:integer; 59 | "1961"^^xsd:gYear 60 | ]; 61 | csvw:rownum "2"^^xsd:integer; 62 | csvw:url 63 | ], [ 64 | a csvw:Row; 65 | csvw:describes [ 66 | ; 67 | "9989846"^^xsd:integer; 68 | "1962"^^xsd:gYear 69 | ]; 70 | csvw:rownum "3"^^xsd:integer; 71 | csvw:url 72 | ]; 73 | csvw:url 74 | ] 75 | ] . 76 | -------------------------------------------------------------------------------- /spec/data/country-codes-and-names-minimal.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "country": "AD", 4 | "name": "Andorra" 5 | }, 6 | { 7 | "country": "AF", 8 | "name": "Afghanistan" 9 | }, 10 | { 11 | "country": "AI", 12 | "name": "Anguilla" 13 | }, 14 | { 15 | "country": "AL", 16 | "name": "Albania" 17 | } 18 | ] 19 | 20 | -------------------------------------------------------------------------------- /spec/data/country-codes-and-names-minimal.ttl: -------------------------------------------------------------------------------- 1 | @prefix : . 2 | @prefix xsd: . 3 | 4 | [ 5 | :country "AL"; 6 | :name "Albania" 7 | ] . 8 | 9 | [ 10 | :country "AI"; 11 | :name "Anguilla" 12 | ] . 13 | 14 | [ 15 | :country "AF"; 16 | :name "Afghanistan" 17 | ] . 18 | 19 | [ 20 | :country "AD"; 21 | :name "Andorra" 22 | ] . 23 | -------------------------------------------------------------------------------- /spec/data/country-codes-and-names-standard.json: -------------------------------------------------------------------------------- 1 | { 2 | "tables": [{ 3 | "url": "http://example.org/country-codes-and-names.csv", 4 | "row": [ 5 | { 6 | "url": "http://example.org/country-codes-and-names.csv#row=2", 7 | "rownum": 1, 8 | "describes": [ 9 | { 10 | "country": "AD", 11 | "name": "Andorra" 12 | } 13 | ] 14 | }, 15 | { 16 | "url": "http://example.org/country-codes-and-names.csv#row=3", 17 | "rownum": 2, 18 | "describes": [ 19 | { 20 | "country": "AF", 21 | "name": "Afghanistan" 22 | } 23 | ] 24 | }, 25 | { 26 | "url": "http://example.org/country-codes-and-names.csv#row=4", 27 | "rownum": 3, 28 | "describes": [ 29 | { 30 | "country": "AI", 31 | "name": "Anguilla" 32 | } 33 | ] 34 | }, 35 | { 36 | "url": "http://example.org/country-codes-and-names.csv#row=5", 37 | "rownum": 4, 38 | "describes": [ 39 | { 40 | "country": "AL", 41 | "name": "Albania" 42 | } 43 | ] 44 | } 45 | ] 46 | }] 47 | } 48 | -------------------------------------------------------------------------------- /spec/data/country-codes-and-names-standard.ttl: -------------------------------------------------------------------------------- 1 | @prefix : . 2 | @prefix csvw: . 3 | @prefix rdf: . 4 | @prefix xsd: . 5 | 6 | [ 7 | a csvw:TableGroup; 8 | csvw:table [ 9 | a csvw:Table; 10 | csvw:row [ 11 | a csvw:Row; 12 | csvw:describes [ 13 | :country "AD"; 14 | :name "Andorra" 15 | ]; 16 | csvw:rownum 1; 17 | csvw:url 18 | ], [ 19 | a csvw:Row; 20 | csvw:describes [ 21 | :country "AF"; 22 | :name "Afghanistan" 23 | ]; 24 | csvw:rownum 2; 25 | csvw:url 26 | ], [ 27 | a csvw:Row; 28 | csvw:describes [ 29 | :country "AI"; 30 | :name "Anguilla" 31 | ]; 32 | csvw:rownum 3; 33 | csvw:url 34 | ], [ 35 | a csvw:Row; 36 | csvw:describes [ 37 | :country "AL"; 38 | :name "Albania" 39 | ]; 40 | csvw:rownum 4; 41 | csvw:url 42 | ]; 43 | csvw:url 44 | ] 45 | ] . 46 | -------------------------------------------------------------------------------- /spec/data/country-codes-and-names.csv: -------------------------------------------------------------------------------- 1 | country,name 2 | AD,Andorra 3 | AF,Afghanistan 4 | AI,Anguilla 5 | AL,Albania -------------------------------------------------------------------------------- /spec/data/country_slice.csv: -------------------------------------------------------------------------------- 1 | countryRef,year,population 2 | AF,1960,9616353 3 | AF,1961,9799379 4 | AF,1962,9989846 -------------------------------------------------------------------------------- /spec/data/gov.uk/professions.csv: -------------------------------------------------------------------------------- 1 | Profession 2 | Finance 3 | Information Technology 4 | Operational Delivery 5 | Policy 6 | -------------------------------------------------------------------------------- /spec/data/junior-roles.csv: -------------------------------------------------------------------------------- 1 | Reporting Senior Post,Grade,Payscale Minimum (£),Payscale Maximum (£),Generic Job Title,Number of Posts (FTE),Profession 2 | 90115,4,17426,20002,Administrator,8.67,Operational Delivery 3 | 90115,5,19546,22478,Administrator,0.5,Operational Delivery 4 | -------------------------------------------------------------------------------- /spec/data/junior-roles.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://www.w3.org/ns/csvw", 3 | "columns": [{ 4 | "name": "reportsToSenior", 5 | "titles": "Reporting Senior Post", 6 | "datatype": "string", 7 | "propertyUrl": "http://example.org/def/reportsTo", 8 | "valueUrl": "senior-roles.csv#post-{reportsToSenior}", 9 | "required": true 10 | }, { 11 | "name": "grade", 12 | "titles": "Grade", 13 | "datatype": "string", 14 | "propertyUrl": "http://example.org/def/grade" 15 | }, { 16 | "name": "min_pay", 17 | "titles": "Payscale Minimum (£)", 18 | "datatype": "integer", 19 | "propertyUrl": "http://example.org/def/min_pay" 20 | }, { 21 | "name": "max_pay", 22 | "titles": "Payscale Maximum (£)", 23 | "datatype": "integer", 24 | "propertyUrl": "http://example.org/def/max_pay" 25 | }, { 26 | "name": "job", 27 | "titles": "Generic Job Title", 28 | "datatype": "string", 29 | "propertyUrl": "http://example.org/def/job" 30 | }, { 31 | "name": "number", 32 | "titles": "Number of Posts (FTE)", 33 | "datatype": "number", 34 | "propertyUrl": "http://example.org/def/number-of-posts" 35 | }, { 36 | "name": "profession", 37 | "titles": "Profession", 38 | "datatype": "string", 39 | "propertyUrl": "http://example.org/def/profession" 40 | }], 41 | "foreignKeys": [{ 42 | "columnReference": "reportsToSenior", 43 | "reference": { 44 | "resource": "senior-roles.csv", 45 | "columnReference": "ref" 46 | } 47 | }, { 48 | "columnReference": "profession", 49 | "reference": { 50 | "resource": "gov.uk/professions.csv", 51 | "columnReference": "name" 52 | } 53 | }] 54 | } 55 | -------------------------------------------------------------------------------- /spec/data/roles-minimal.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "@id": "http://example.org/senior-roles.csv#post-90115", 3 | "dc:identifier": "90115", 4 | "foaf:name": "Steve Egan", 5 | "http://example.org/def/grade": "SCS1A", 6 | "http://example.org/def/job": "Deputy Chief Executive", 7 | "http://example.org/def/reportsTo": "http://example.org/senior-roles.csv#post-90334", 8 | "http://example.org/def/profession": "Finance" 9 | }, { 10 | "@id": "http://example.org/senior-roles.csv#post-90334", 11 | "dc:identifier": "90334", 12 | "foaf:name": "Sir Alan Langlands", 13 | "http://example.org/def/grade": "SCS4", 14 | "http://example.org/def/job": "Chief Executive", 15 | "http://example.org/def/profession": "Policy" 16 | }, { 17 | "http://example.org/def/reportsTo": "http://example.org/senior-roles.csv#post-90115", 18 | "http://example.org/def/grade": "4", 19 | "http://example.org/def/min_pay": 17426, 20 | "http://example.org/def/max_pay": 20002, 21 | "http://example.org/def/job": "Administrator", 22 | "http://example.org/def/number-of-posts": 8.67, 23 | "http://example.org/def/profession": "Operational Delivery" 24 | }, { 25 | "http://example.org/def/reportsTo": "http://example.org/senior-roles.csv#post-90115", 26 | "http://example.org/def/grade": "5", 27 | "http://example.org/def/min_pay": 19546, 28 | "http://example.org/def/max_pay": 22478, 29 | "http://example.org/def/job": "Administrator", 30 | "http://example.org/def/number-of-posts": 0.5, 31 | "http://example.org/def/profession": "Operational Delivery" 32 | }] -------------------------------------------------------------------------------- /spec/data/roles-minimal.ttl: -------------------------------------------------------------------------------- 1 | @prefix dc: . 2 | @prefix foaf: . 3 | @prefix xsd: . 4 | 5 | 6 | dc:identifier "90115" ; 7 | foaf:name "Steve Egan" ; 8 | "SCS1A" ; 9 | "Deputy Chief Executive" ; 10 | ; 11 | "Finance" . 12 | 13 | 14 | dc:identifier "90334" ; 15 | foaf:name "Sir Alan Langlands" ; 16 | "SCS4" ; 17 | "Chief Executive" ; 18 | "Policy" . 19 | 20 | _:d8b8e40c-8c74-458b-99f7-64d1cf5c65f2 21 | ; 22 | "4" ; 23 | "17426"^^xsd:integer ; 24 | "20002"^^xsd:integer ; 25 | "Administrator" ; 26 | "8.67"^^xsd:double ; 27 | "Operational Delivery" . 28 | 29 | _:fa1fa954-dd5f-4aa1-b2bc-20bf9867fac6 30 | ; 31 | "5" ; 32 | "19546"^^xsd:integer ; 33 | "22478"^^xsd:integer ; 34 | "Administrator" ; 35 | "0.5"^^xsd:double ; 36 | "Operational Delivery" . -------------------------------------------------------------------------------- /spec/data/roles-standard.json: -------------------------------------------------------------------------------- 1 | { 2 | "tables": [{ 3 | "url": "http://example.org/senior-roles.csv", 4 | "row": [{ 5 | "url": "http://example.org/senior-roles.csv#row=2", 6 | "rownum": 1, 7 | "describes": [{ 8 | "@id": "http://example.org/senior-roles.csv#post-90115", 9 | "dc:identifier": "90115", 10 | "foaf:name": "Steve Egan", 11 | "http://example.org/def/grade": "SCS1A", 12 | "http://example.org/def/job": "Deputy Chief Executive", 13 | "http://example.org/def/reportsTo": "http://example.org/senior-roles.csv#post-90334", 14 | "http://example.org/def/profession": "Finance" 15 | }] 16 | }, { 17 | "url": "http://example.org/senior-roles.csv#row=3", 18 | "rownum": 2, 19 | "describes": [{ 20 | "@id": "http://example.org/senior-roles.csv#post-90334", 21 | "dc:identifier": "90334", 22 | "foaf:name": "Sir Alan Langlands", 23 | "http://example.org/def/grade": "SCS4", 24 | "http://example.org/def/job": "Chief Executive", 25 | "http://example.org/def/profession": "Policy" 26 | }] 27 | }] 28 | }, { 29 | "url": "http://example.org/junior-roles.csv", 30 | "row": [{ 31 | "url": "http://example.org/junior-roles.csv#row=2", 32 | "rownum": 1, 33 | "describes": [{ 34 | "http://example.org/def/reportsTo": "http://example.org/senior-roles.csv#post-90115", 35 | "http://example.org/def/grade": "4", 36 | "http://example.org/def/min_pay": 17426, 37 | "http://example.org/def/max_pay": 20002, 38 | "http://example.org/def/job": "Administrator", 39 | "http://example.org/def/number-of-posts": 8.67, 40 | "http://example.org/def/profession": "Operational Delivery" 41 | }] 42 | }, { 43 | "url": "http://example.org/junior-roles.csv#row=3", 44 | "rownum": 2, 45 | "describes": [{ 46 | "http://example.org/def/reportsTo": "http://example.org/senior-roles.csv#post-90115", 47 | "http://example.org/def/grade": "5", 48 | "http://example.org/def/min_pay": 19546, 49 | "http://example.org/def/max_pay": 22478, 50 | "http://example.org/def/job": "Administrator", 51 | "http://example.org/def/number-of-posts": 0.5, 52 | "http://example.org/def/profession": "Operational Delivery" 53 | }] 54 | }] 55 | }] 56 | } -------------------------------------------------------------------------------- /spec/data/roles-standard.ttl: -------------------------------------------------------------------------------- 1 | @prefix csvw: . 2 | @prefix dc: . 3 | @prefix foaf: . 4 | @prefix xsd: . 5 | 6 | _:3d36cfbb-d2d5-4573-a1a7-3bf817062db8 a csvw:TableGroup ; 7 | csvw:table [ a csvw:Table ; 8 | csvw:url ; 9 | csvw:row [ 10 | a csvw:Row; 11 | csvw:rownum "1"^^xsd:integer ; 12 | csvw:url ; 13 | csvw:describes 14 | ], [ 15 | a csvw:Row; 16 | csvw:rownum "2"^^xsd:integer ; 17 | csvw:url ; 18 | csvw:describes 19 | ] 20 | ], [ a csvw:Table ; 21 | csvw:url ; 22 | csvw:row [ 23 | a csvw:Row; 24 | csvw:rownum "1"^^xsd:integer ; 25 | csvw:url ; 26 | csvw:describes _:d8b8e40c-8c74-458b-99f7-64d1cf5c65f2 27 | ], [ 28 | a csvw:Row; 29 | csvw:rownum "2"^^xsd:integer ; 30 | csvw:url ; 31 | csvw:describes _:fa1fa954-dd5f-4aa1-b2bc-20bf9867fac6 32 | ] 33 | ] . 34 | 35 | 36 | dc:identifier "90115" ; 37 | foaf:name "Steve Egan" ; 38 | "SCS1A" ; 39 | "Deputy Chief Executive" ; 40 | ; 41 | "Finance" . 42 | 43 | 44 | dc:identifier "90334" ; 45 | foaf:name "Sir Alan Langlands" ; 46 | "SCS4" ; 47 | "Chief Executive" ; 48 | "Policy" . 49 | 50 | _:d8b8e40c-8c74-458b-99f7-64d1cf5c65f2 51 | ; 52 | "4" ; 53 | "17426"^^xsd:integer ; 54 | "20002"^^xsd:integer ; 55 | "Administrator" ; 56 | "8.67"^^xsd:double ; 57 | "Operational Delivery" . 58 | 59 | _:fa1fa954-dd5f-4aa1-b2bc-20bf9867fac6 60 | ; 61 | "5" ; 62 | "19546"^^xsd:integer ; 63 | "22478"^^xsd:integer ; 64 | "Administrator" ; 65 | "0.5"^^xsd:double ; 66 | "Operational Delivery" . -------------------------------------------------------------------------------- /spec/data/roles.json: -------------------------------------------------------------------------------- 1 | { 2 | "@type": "TableGroup", 3 | "@context": ["http://www.w3.org/ns/csvw", {"@language": "en"}], 4 | "tables": [{ 5 | "url": "gov.uk/professions.csv", 6 | "tableSchema": { 7 | "columns": [{ 8 | "name": "name", 9 | "titles": "Profession", 10 | "datatype": "string", 11 | "required": true 12 | }], 13 | "primaryKey": "name" 14 | }, 15 | "suppressOutput": true 16 | }, { 17 | "url": "senior-roles.csv", 18 | "tableSchema": "senior-roles.json" 19 | }, { 20 | "url": "junior-roles.csv", 21 | "tableSchema": "junior-roles.json" 22 | }] 23 | } -------------------------------------------------------------------------------- /spec/data/senior-roles.csv: -------------------------------------------------------------------------------- 1 | Post Unique Reference,Name,Grade,Job Title,Reports to Senior Post,Profession 2 | 90115,Steve Egan,SCS1A,Deputy Chief Executive,90334,Finance 3 | 90334,Sir Alan Langlands,SCS4,Chief Executive,xx,Policy 4 | -------------------------------------------------------------------------------- /spec/data/senior-roles.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://www.w3.org/ns/csvw", 3 | "columns": [{ 4 | "name": "ref", 5 | "titles": "Post Unique Reference", 6 | "datatype": "string", 7 | "required": true, 8 | "propertyUrl": "dc:identifier" 9 | }, { 10 | "name": "name", 11 | "titles": "Name", 12 | "datatype": "string", 13 | "propertyUrl": "foaf:name" 14 | }, { 15 | "name": "grade", 16 | "titles": "Grade", 17 | "datatype": "string", 18 | "propertyUrl": "http://example.org/def/grade" 19 | }, { 20 | "name": "job", 21 | "titles": "Job Title", 22 | "datatype": "string", 23 | "propertyUrl": "http://example.org/def/job" 24 | }, { 25 | "name": "reportsTo", 26 | "titles": "Reports to Senior Post", 27 | "datatype": "string", 28 | "null": "xx", 29 | "propertyUrl": "http://example.org/def/reportsTo", 30 | "valueUrl": "senior-roles.csv#post-{reportsTo}" 31 | }, { 32 | "name": "profession", 33 | "titles": "Profession", 34 | "datatype": "string", 35 | "propertyUrl": "http://example.org/def/profession" 36 | }], 37 | "primaryKey": "ref", 38 | "aboutUrl": "#post-{ref}", 39 | "foreignKeys": [{ 40 | "columnReference": "reportsTo", 41 | "reference": { 42 | "resource": "senior-roles.csv", 43 | "columnReference": "ref" 44 | } 45 | }, { 46 | "columnReference": "profession", 47 | "reference": { 48 | "resource": "gov.uk/professions.csv", 49 | "columnReference": "name" 50 | } 51 | }] 52 | } 53 | -------------------------------------------------------------------------------- /spec/data/test232-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://www.w3.org/ns/csvw", 3 | "rdfs:comment": "As defined in [tabular-data-model], validators must check that each row has a unique combination of values of cells in the indicated columns.", 4 | "rdfs:label": "single column primaryKey violation", 5 | "url": "test232.csv", 6 | "tableSchema": { 7 | "columns": [{"name": "PK", "titles": "PK"}], 8 | "primaryKey": "PK" 9 | } 10 | } -------------------------------------------------------------------------------- /spec/data/test232.csv: -------------------------------------------------------------------------------- 1 | PK 2 | 1 3 | 1 4 | -------------------------------------------------------------------------------- /spec/data/tree-ops-atd.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /spec/data/tree-ops-ext-minimal.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "@id": "http://example.org/tree-ops-ext#gid-1", 4 | "on_street": "ADDISON AV", 5 | "species": "Celtis australis", 6 | "trim_cycle": "Large Tree Routine Prune", 7 | "dbh": 11, 8 | "inventory_date": "2010-10-18", 9 | "protected": false, 10 | "kml": "-122.156485,37.440963" 11 | }, 12 | { 13 | "@id": "http://example.org/tree-ops-ext#gid-2", 14 | "on_street": "EMERSON ST", 15 | "species": "Liquidambar styraciflua", 16 | "trim_cycle": "Large Tree Routine Prune", 17 | "dbh": 11, 18 | "inventory_date": "2010-06-02", 19 | "protected": false, 20 | "kml": "-122.156749,37.440958" 21 | }, 22 | { 23 | "@id": "http://example.org/tree-ops-ext#gid-6", 24 | "on_street": "ADDISON AV", 25 | "species": "Robinia pseudoacacia", 26 | "trim_cycle": "Large Tree Routine Prune", 27 | "dbh": 29, 28 | "inventory_date": "2010-06-01", 29 | "comments": [ 30 | "cavity or decay", 31 | " trunk decay", 32 | " codominant leaders", 33 | " included bark", 34 | " large leader or limb decay", 35 | " previous failure root damage", 36 | " root decay", 37 | " beware of BEES" 38 | ], 39 | "protected": true, 40 | "kml": "-122.156299,37.441151" 41 | } 42 | ] 43 | -------------------------------------------------------------------------------- /spec/data/tree-ops-ext-minimal.ttl: -------------------------------------------------------------------------------- 1 | @prefix rdf: . 2 | @prefix xsd: . 3 | 4 | "11"^^xsd:integer; 5 | "2010-10-18"^^xsd:date; 6 | "-122.156485,37.440963"^^rdf:XMLLiteral; 7 | "ADDISON AV"; 8 | "false"^^xsd:boolean; 9 | "Celtis australis"; 10 | "Large Tree Routine Prune"@en . 11 | 12 | "11"^^xsd:integer; 13 | "2010-06-02"^^xsd:date; 14 | "-122.156749,37.440958"^^rdf:XMLLiteral; 15 | "EMERSON ST"; 16 | "false"^^xsd:boolean; 17 | "Liquidambar styraciflua"; 18 | "Large Tree Routine Prune"@en . 19 | 20 | "cavity or decay", 21 | " trunk decay", 22 | " codominant leaders", 23 | " included bark", 24 | " large leader or limb decay", 25 | " previous failure root damage", 26 | " root decay", 27 | " beware of BEES"; 28 | "29"^^xsd:integer; 29 | "2010-06-01"^^xsd:date; 30 | "-122.156299,37.441151"^^rdf:XMLLiteral; 31 | "ADDISON AV"; 32 | "true"^^xsd:boolean; 33 | "Robinia pseudoacacia"; 34 | "Large Tree Routine Prune"@en . 35 | -------------------------------------------------------------------------------- /spec/data/tree-ops-ext-standard.json: -------------------------------------------------------------------------------- 1 | { 2 | "tables": [ 3 | { 4 | "@id": "http://example.org/tree-ops-ext", 5 | "url": "http://example.org/tree-ops-ext.csv", 6 | "row": [ 7 | { 8 | "url": "http://example.org/tree-ops-ext.csv#row=2", 9 | "rownum": 1, 10 | "describes": [ 11 | { 12 | "@id": "http://example.org/tree-ops-ext#gid-1", 13 | "on_street": "ADDISON AV", 14 | "species": "Celtis australis", 15 | "trim_cycle": "Large Tree Routine Prune", 16 | "dbh": 11, 17 | "inventory_date": "2010-10-18", 18 | "protected": false, 19 | "kml": "-122.156485,37.440963" 20 | } 21 | ] 22 | }, 23 | { 24 | "url": "http://example.org/tree-ops-ext.csv#row=3", 25 | "rownum": 2, 26 | "describes": [ 27 | { 28 | "@id": "http://example.org/tree-ops-ext#gid-2", 29 | "on_street": "EMERSON ST", 30 | "species": "Liquidambar styraciflua", 31 | "trim_cycle": "Large Tree Routine Prune", 32 | "dbh": 11, 33 | "inventory_date": "2010-06-02", 34 | "protected": false, 35 | "kml": "-122.156749,37.440958" 36 | } 37 | ] 38 | }, 39 | { 40 | "url": "http://example.org/tree-ops-ext.csv#row=4", 41 | "rownum": 3, 42 | "describes": [ 43 | { 44 | "@id": "http://example.org/tree-ops-ext#gid-6", 45 | "on_street": "ADDISON AV", 46 | "species": "Robinia pseudoacacia", 47 | "trim_cycle": "Large Tree Routine Prune", 48 | "dbh": 29, 49 | "inventory_date": "2010-06-01", 50 | "comments": [ 51 | "cavity or decay", 52 | " trunk decay", 53 | " codominant leaders", 54 | " included bark", 55 | " large leader or limb decay", 56 | " previous failure root damage", 57 | " root decay", 58 | " beware of BEES" 59 | ], 60 | "protected": true, 61 | "kml": "-122.156299,37.441151" 62 | } 63 | ] 64 | } 65 | ], 66 | "dc:title": "Tree Operations", 67 | "dcat:keyword": [ 68 | "tree", 69 | "street", 70 | "maintenance" 71 | ], 72 | "dc:publisher": [ 73 | { 74 | "schema:name": "Example Municipality", 75 | "schema:url": "http://example.org" 76 | } 77 | ], 78 | "dc:license": "http://opendefinition.org/licenses/cc-by/", 79 | "dc:modified": "2010-12-31", 80 | "notes": [ 81 | { 82 | "@type": "oa:Annotation", 83 | "oa:hasTarget": "http://example.org/tree-ops-ext", 84 | "oa:hasBody": { 85 | "@type": "oa:EmbeddedContent", 86 | "rdf:value": "This is a very interesting comment about the table; it's a table!", 87 | "dc:format": "text/plain" 88 | } 89 | } 90 | ] 91 | } 92 | ] 93 | } 94 | -------------------------------------------------------------------------------- /spec/data/tree-ops-ext-standard.ttl: -------------------------------------------------------------------------------- 1 | @prefix csvw: . 2 | @prefix dc: . 3 | @prefix dcat: . 4 | @prefix oa: . 5 | @prefix rdf: . 6 | @prefix schema: . 7 | @prefix xsd: . 8 | 9 | "11"^^xsd:integer; 10 | "2010-10-18"^^xsd:date; 11 | "-122.156485,37.440963"^^rdf:XMLLiteral; 12 | "ADDISON AV"; 13 | "false"^^xsd:boolean; 14 | "Celtis australis"; 15 | "Large Tree Routine Prune"@en . 16 | 17 | "11"^^xsd:integer; 18 | "2010-06-02"^^xsd:date; 19 | "-122.156749,37.440958"^^rdf:XMLLiteral; 20 | "EMERSON ST"; 21 | "false"^^xsd:boolean; 22 | "Liquidambar styraciflua"; 23 | "Large Tree Routine Prune"@en . 24 | 25 | "cavity or decay", 26 | " trunk decay", 27 | " codominant leaders", 28 | " included bark", 29 | " large leader or limb decay", 30 | " previous failure root damage", 31 | " root decay", 32 | " beware of BEES"; 33 | "29"^^xsd:integer; 34 | "2010-06-01"^^xsd:date; 35 | "-122.156299,37.441151"^^rdf:XMLLiteral; 36 | "ADDISON AV"; 37 | "true"^^xsd:boolean; 38 | "Robinia pseudoacacia"; 39 | "Large Tree Routine Prune"@en . 40 | 41 | a csvw:Table; 42 | dc:title "Tree Operations"@en; 43 | dc:license ; 44 | dc:modified "2010-12-31"^^xsd:date; 45 | dc:publisher [ 46 | schema:name "Example Municipality"@en; 47 | schema:url 48 | ]; 49 | csvw:note [ 50 | a oa:Annotation; 51 | oa:hasBody [ 52 | a oa:EmbeddedContent; 53 | dc:format "text/plain"; 54 | rdf:value "This is a very interesting comment about the table; it's a table!"@en 55 | ]; 56 | oa:hasTarget 57 | ]; 58 | csvw:row [ 59 | a csvw:Row; 60 | csvw:describes ; 61 | csvw:rownum "1"^^xsd:integer; 62 | csvw:url 63 | ], [ 64 | a csvw:Row; 65 | csvw:describes ; 66 | csvw:rownum "2"^^xsd:integer; 67 | csvw:url 68 | ], [ 69 | a csvw:Row; 70 | csvw:describes ; 71 | csvw:rownum "3"^^xsd:integer; 72 | csvw:url 73 | ]; 74 | csvw:url ; 75 | dcat:keyword "tree"@en, 76 | "street"@en, 77 | "maintenance"@en . 78 | 79 | [ 80 | a csvw:TableGroup; 81 | csvw:table 82 | ] . 83 | -------------------------------------------------------------------------------- /spec/data/tree-ops-ext.csv: -------------------------------------------------------------------------------- 1 | GID,On Street,Species,Trim Cycle,Diameter at Breast Ht,Inventory Date,Comments,Protected,KML 2 | 1,ADDISON AV,Celtis australis,Large Tree Routine Prune,11,10/18/2010,,,"-122.156485,37.440963" 3 | 2,EMERSON ST,Liquidambar styraciflua,Large Tree Routine Prune,11,6/2/2010,,,"-122.156749,37.440958" 4 | 6,ADDISON AV,Robinia pseudoacacia,Large Tree Routine Prune,29,6/1/2010,cavity or decay; trunk decay; codominant leaders; included bark; large leader or limb decay; previous failure root damage; root decay; beware of BEES,YES,"-122.156299,37.441151" 5 | -------------------------------------------------------------------------------- /spec/data/tree-ops-ext.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": ["http://www.w3.org/ns/csvw", {"@language": "en"}], 3 | "@id": "http://example.org/tree-ops-ext", 4 | "url": "tree-ops-ext.csv", 5 | "dc:title": "Tree Operations", 6 | "dcat:keyword": ["tree", "street", "maintenance"], 7 | "dc:publisher": [{ 8 | "schema:name": "Example Municipality", 9 | "schema:url": {"@id": "http://example.org"} 10 | }], 11 | "dc:license": {"@id": "http://opendefinition.org/licenses/cc-by/"}, 12 | "dc:modified": {"@value": "2010-12-31", "@type": "xsd:date"}, 13 | "notes": { 14 | "@type": "oa:Annotation", 15 | "oa:hasTarget": {"@id": "http://example.org/tree-ops-ext"}, 16 | "oa:hasBody": { 17 | "@type": "oa:EmbeddedContent", 18 | "rdf:value": "This is a very interesting comment about the table; it's a table!", 19 | "dc:format": {"@value": "text/plain"} 20 | } 21 | }, 22 | "dialect": {"trim": true}, 23 | "tableSchema": { 24 | "columns": [{ 25 | "name": "GID", 26 | "titles": [ 27 | "GID", 28 | "Generic Identifier" 29 | ], 30 | "dc:description": "An identifier for the operation on a tree.", 31 | "datatype": "string", 32 | "required": true, 33 | "suppressOutput": true 34 | }, { 35 | "name": "on_street", 36 | "titles": "On Street", 37 | "dc:description": "The street that the tree is on.", 38 | "datatype": "string" 39 | }, { 40 | "name": "species", 41 | "titles": "Species", 42 | "dc:description": "The species of the tree.", 43 | "datatype": "string" 44 | }, { 45 | "name": "trim_cycle", 46 | "titles": "Trim Cycle", 47 | "dc:description": "The operation performed on the tree.", 48 | "datatype": "string", 49 | "lang": "en" 50 | }, { 51 | "name": "dbh", 52 | "titles": "Diameter at Breast Ht", 53 | "dc:description": "Diameter at Breast Height (DBH) of the tree (in feet), measured 4.5ft above ground.", 54 | "datatype": "integer" 55 | }, { 56 | "name": "inventory_date", 57 | "titles": "Inventory Date", 58 | "dc:description": "The date of the operation that was performed.", 59 | "datatype": {"base": "date", "format": "M/d/yyyy"} 60 | }, { 61 | "name": "comments", 62 | "titles": "Comments", 63 | "dc:description": "Supplementary comments relating to the operation or tree.", 64 | "datatype": "string", 65 | "separator": ";" 66 | }, { 67 | "name": "protected", 68 | "titles": "Protected", 69 | "dc:description": "Indication (YES / NO) whether the tree is subject to a protection order.", 70 | "datatype": {"base": "boolean", "format": "YES|NO"}, 71 | "default": "NO" 72 | }, { 73 | "name": "kml", 74 | "titles": "KML", 75 | "dc:description": "KML-encoded description of tree location.", 76 | "datatype": "xml" 77 | }], 78 | "primaryKey": "GID", 79 | "aboutUrl": "http://example.org/tree-ops-ext#gid-{GID}" 80 | } 81 | } -------------------------------------------------------------------------------- /spec/data/tree-ops-minimal.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "@id": "http://example.org/tree-ops.csv#gid-1", 4 | "GID": "1", 5 | "on_street": "ADDISON AV", 6 | "species": "Celtis australis", 7 | "trim_cycle": "Large Tree Routine Prune", 8 | "inventory_date": "2010-10-18" 9 | }, 10 | { 11 | "@id": "http://example.org/tree-ops.csv#gid-2", 12 | "GID": "2", 13 | "on_street": "EMERSON ST", 14 | "species": "Liquidambar styraciflua", 15 | "trim_cycle": "Large Tree Routine Prune", 16 | "inventory_date": "2010-06-02" 17 | } 18 | ] 19 | -------------------------------------------------------------------------------- /spec/data/tree-ops-minimal.ttl: -------------------------------------------------------------------------------- 1 | @prefix : . 2 | @prefix xsd: . 3 | 4 | :gid-1 :GID "1"; 5 | :inventory_date "2010-10-18"^^xsd:date; 6 | :on_street "ADDISON AV"; 7 | :species "Celtis australis"; 8 | :trim_cycle "Large Tree Routine Prune" . 9 | 10 | :gid-2 :GID "2"; 11 | :inventory_date "2010-06-02"^^xsd:date; 12 | :on_street "EMERSON ST"; 13 | :species "Liquidambar styraciflua"; 14 | :trim_cycle "Large Tree Routine Prune" . 15 | -------------------------------------------------------------------------------- /spec/data/tree-ops-standard.json: -------------------------------------------------------------------------------- 1 | { 2 | "tables": [{ 3 | "url": "http://example.org/tree-ops.csv", 4 | "dc:title": "Tree Operations", 5 | "dcat:keyword": [ 6 | "tree", 7 | "street", 8 | "maintenance" 9 | ], 10 | "dc:publisher": "Example Municipality", 11 | "dc:license": "http://opendefinition.org/licenses/cc-by/", 12 | "dc:modified": "2010-12-31", 13 | "row": [ 14 | { 15 | "url": "http://example.org/tree-ops.csv#row=2", 16 | "rownum": 1, 17 | "describes": [ 18 | { 19 | "@id": "http://example.org/tree-ops.csv#gid-1", 20 | "GID": "1", 21 | "on_street": "ADDISON AV", 22 | "species": "Celtis australis", 23 | "trim_cycle": "Large Tree Routine Prune", 24 | "inventory_date": "2010-10-18" 25 | } 26 | ] 27 | }, 28 | { 29 | "url": "http://example.org/tree-ops.csv#row=3", 30 | "rownum": 2, 31 | "describes": [ 32 | { 33 | "@id": "http://example.org/tree-ops.csv#gid-2", 34 | "GID": "2", 35 | "on_street": "EMERSON ST", 36 | "species": "Liquidambar styraciflua", 37 | "trim_cycle": "Large Tree Routine Prune", 38 | "inventory_date": "2010-06-02" 39 | } 40 | ] 41 | } 42 | ] 43 | }] 44 | } -------------------------------------------------------------------------------- /spec/data/tree-ops-standard.ttl: -------------------------------------------------------------------------------- 1 | @prefix : . 2 | @prefix csvw: . 3 | @prefix dc: . 4 | @prefix dcat: . 5 | @prefix rdf: . 6 | @prefix xsd: . 7 | 8 | :gid-1 :GID "1"; 9 | :inventory_date "2010-10-18"^^xsd:date; 10 | :on_street "ADDISON AV"; 11 | :species "Celtis australis"; 12 | :trim_cycle "Large Tree Routine Prune" . 13 | 14 | :gid-2 :GID "2"; 15 | :inventory_date "2010-06-02"^^xsd:date; 16 | :on_street "EMERSON ST"; 17 | :species "Liquidambar styraciflua"; 18 | :trim_cycle "Large Tree Routine Prune" . 19 | 20 | [ 21 | a csvw:TableGroup; 22 | csvw:table [ 23 | a csvw:Table; 24 | dc:title "Tree Operations"@en; 25 | dcat:keyword "tree"@en, 26 | "street"@en, 27 | "maintenance"@en; 28 | dc:license ; 29 | dc:modified "2010-12-31"^^xsd:date; 30 | dc:publisher "Example Municipality"@en; 31 | csvw:row [ 32 | a csvw:Row; 33 | csvw:describes :gid-1; 34 | csvw:rownum 1; 35 | csvw:url 36 | ], [ 37 | a csvw:Row; 38 | csvw:describes :gid-2; 39 | csvw:rownum 2; 40 | csvw:url 41 | ]; 42 | csvw:url 43 | ] 44 | ] . 45 | -------------------------------------------------------------------------------- /spec/data/tree-ops-virtual-minimal.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "@id": "http://example.org/tree-ops.csv#gid-1", 4 | "schema:url": "http://example.org/tree-ops.csv#gid-1", 5 | "schema:name": "Celtis australis", 6 | "trim_cycle": "Large Tree Routine Prune", 7 | "schema:event": { 8 | "@id": "http://example.org/tree-ops.csv#event-2010-10-18", 9 | "schema:startDate": "2010-10-18" 10 | }, 11 | "schema:location": { 12 | "@id": "http://example.org/tree-ops.csv#location-1", 13 | "schema:streetAddress": "ADDISON AV", 14 | "@type": "schema:PostalAddress" 15 | } 16 | }, 17 | { 18 | "@id": "http://example.org/tree-ops.csv#gid-2", 19 | "schema:url": "http://example.org/tree-ops.csv#gid-2", 20 | "schema:name": "Liquidambar styraciflua", 21 | "trim_cycle": "Large Tree Routine Prune", 22 | "schema:event": { 23 | "@id": "http://example.org/tree-ops.csv#event-2010-06-02", 24 | "schema:startDate": "2010-06-02" 25 | }, 26 | "schema:location": { 27 | "@id": "http://example.org/tree-ops.csv#location-2", 28 | "schema:streetAddress": "EMERSON ST", 29 | "@type": "schema:PostalAddress" 30 | } 31 | } 32 | ] 33 | -------------------------------------------------------------------------------- /spec/data/tree-ops-virtual-minimal.ttl: -------------------------------------------------------------------------------- 1 | @prefix rdf: . 2 | @prefix schema: . 3 | @prefix xsd: . 4 | 5 | schema:startDate "2010-10-18"^^xsd:date . 6 | 7 | "Large Tree Routine Prune"; 8 | schema:event ; 9 | schema:location ; 10 | schema:name "Celtis australis"; 11 | schema:url . 12 | 13 | "Large Tree Routine Prune"; 14 | schema:event ; 15 | schema:location ; 16 | schema:name "Liquidambar styraciflua"; 17 | schema:url . 18 | 19 | a schema:PostalAddress; 20 | schema:streetAddress "ADDISON AV" . 21 | 22 | a schema:PostalAddress; 23 | schema:streetAddress "EMERSON ST" . 24 | 25 | schema:startDate "2010-06-02"^^xsd:date . 26 | -------------------------------------------------------------------------------- /spec/data/tree-ops-virtual-standard.json: -------------------------------------------------------------------------------- 1 | { 2 | "tables": [{ 3 | "url": "http://example.org/tree-ops.csv", 4 | "row": [ 5 | { 6 | "url": "http://example.org/tree-ops.csv#row=2", 7 | "rownum": 1, 8 | "describes": [ 9 | { 10 | "@id": "http://example.org/tree-ops.csv#gid-1", 11 | "schema:url": "http://example.org/tree-ops.csv#gid-1", 12 | "schema:name": "Celtis australis", 13 | "trim_cycle": "Large Tree Routine Prune", 14 | "schema:event": { 15 | "@id": "http://example.org/tree-ops.csv#event-2010-10-18", 16 | "schema:startDate": "2010-10-18" 17 | }, 18 | "schema:location": { 19 | "@id": "http://example.org/tree-ops.csv#location-1", 20 | "schema:streetAddress": "ADDISON AV", 21 | "@type": "schema:PostalAddress" 22 | } 23 | } 24 | ] 25 | }, 26 | { 27 | "url": "http://example.org/tree-ops.csv#row=3", 28 | "rownum": 2, 29 | "describes": [ 30 | { 31 | "@id": "http://example.org/tree-ops.csv#gid-2", 32 | "schema:url": "http://example.org/tree-ops.csv#gid-2", 33 | "schema:name": "Liquidambar styraciflua", 34 | "trim_cycle": "Large Tree Routine Prune", 35 | "schema:event": { 36 | "@id": "http://example.org/tree-ops.csv#event-2010-06-02", 37 | "schema:startDate": "2010-06-02" 38 | }, 39 | "schema:location": { 40 | "@id": "http://example.org/tree-ops.csv#location-2", 41 | "schema:streetAddress": "EMERSON ST", 42 | "@type": "schema:PostalAddress" 43 | } 44 | } 45 | ] 46 | } 47 | ] 48 | }] 49 | } 50 | -------------------------------------------------------------------------------- /spec/data/tree-ops-virtual-standard.ttl: -------------------------------------------------------------------------------- 1 | @prefix csvw: . 2 | @prefix rdf: . 3 | @prefix schema: . 4 | @prefix xsd: . 5 | 6 | [ 7 | a csvw:TableGroup; 8 | csvw:table [ 9 | a csvw:Table; 10 | csvw:row [ 11 | a csvw:Row; 12 | csvw:describes , 13 | , 14 | ; 15 | csvw:rownum 1; 16 | csvw:url 17 | ], [ 18 | a csvw:Row; 19 | csvw:describes , 20 | , 21 | ; 22 | csvw:rownum 2; 23 | csvw:url 24 | ]; 25 | csvw:url 26 | ] 27 | ] . 28 | 29 | "Large Tree Routine Prune"; 30 | schema:event ; 31 | schema:location ; 32 | schema:name "Celtis australis"; 33 | schema:url . 34 | 35 | rdf:type schema:PostalAddress; 36 | schema:streetAddress "ADDISON AV" . 37 | 38 | schema:startDate "2010-10-18"^^xsd:date . 39 | 40 | "Large Tree Routine Prune"; 41 | schema:event ; 42 | schema:location ; 43 | schema:name "Liquidambar styraciflua"; 44 | schema:url . 45 | 46 | rdf:type schema:PostalAddress; 47 | schema:streetAddress "EMERSON ST" . 48 | 49 | schema:startDate "2010-06-02"^^xsd:date . 50 | -------------------------------------------------------------------------------- /spec/data/tree-ops-virtual.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "tree-ops.csv", 3 | "@context": ["http://www.w3.org/ns/csvw", {"@language": "en"}], 4 | "tableSchema": { 5 | "columns": [{ 6 | "name": "GID", 7 | "titles": "GID", 8 | "datatype": "string", 9 | "propertyUrl": "schema:url", 10 | "valueUrl": "#gid-{GID}" 11 | }, { 12 | "name": "on_street", 13 | "titles": "On Street", 14 | "datatype": "string", 15 | "aboutUrl": "#location-{GID}", 16 | "propertyUrl": "schema:streetAddress" 17 | }, { 18 | "name": "species", 19 | "titles": "Species", 20 | "datatype": "string", 21 | "propertyUrl": "schema:name" 22 | }, { 23 | "name": "trim_cycle", 24 | "titles": "Trim Cycle", 25 | "datatype": "string" 26 | }, { 27 | "name": "inventory_date", 28 | "titles": "Inventory Date", 29 | "datatype": {"base": "date", "format": "M/d/yyyy"}, 30 | "aboutUrl": "#event-{inventory_date}", 31 | "propertyUrl": "schema:startDate" 32 | }, { 33 | "propertyUrl": "schema:event", 34 | "valueUrl": "#event-{inventory_date}", 35 | "virtual": true 36 | }, { 37 | "propertyUrl": "schema:location", 38 | "valueUrl": "#location-{GID}", 39 | "virtual": true 40 | }, { 41 | "aboutUrl": "#location-{GID}", 42 | "propertyUrl": "rdf:type", 43 | "valueUrl": "schema:PostalAddress", 44 | "virtual": true 45 | }], 46 | "aboutUrl": "#gid-{GID}" 47 | } 48 | } -------------------------------------------------------------------------------- /spec/data/tree-ops.csv: -------------------------------------------------------------------------------- 1 | GID,On Street,Species,Trim Cycle,Inventory Date 2 | 1,ADDISON AV,Celtis australis,Large Tree Routine Prune,10/18/2010 3 | 2,EMERSON ST,Liquidambar styraciflua,Large Tree Routine Prune,6/2/2010 4 | -------------------------------------------------------------------------------- /spec/data/tree-ops.csv-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "tree-ops.csv", 3 | "@context": ["http://www.w3.org/ns/csvw", {"@language": "en"}], 4 | "dc:title": "Tree Operations", 5 | "dcat:keyword": ["tree", "street", "maintenance"], 6 | "dc:publisher": "Example Municipality", 7 | "dc:license": {"@id": "http://opendefinition.org/licenses/cc-by/"}, 8 | "dc:modified": {"@value": "2010-12-31", "@type": "xsd:date"}, 9 | "tableSchema": { 10 | "columns": [{ 11 | "name": "GID", 12 | "titles": [ 13 | "GID", 14 | "Generic Identifier" 15 | ], 16 | "dc:description": "An identifier for the operation on a tree.", 17 | "datatype": "string", 18 | "required": true 19 | }, { 20 | "name": "on_street", 21 | "titles": "On Street", 22 | "dc:description": "The street that the tree is on.", 23 | "datatype": "string" 24 | }, { 25 | "name": "species", 26 | "titles": "Species", 27 | "dc:description": "The species of the tree.", 28 | "datatype": "string" 29 | }, { 30 | "name": "trim_cycle", 31 | "titles": "Trim Cycle", 32 | "dc:description": "The operation performed on the tree.", 33 | "datatype": "string" 34 | }, { 35 | "name": "inventory_date", 36 | "titles": "Inventory Date", 37 | "dc:description": "The date of the operation that was performed.", 38 | "datatype": {"base": "date", "format": "M/d/yyyy"} 39 | }], 40 | "primaryKey": "GID", 41 | "aboutUrl": "#gid-{GID}" 42 | } 43 | } -------------------------------------------------------------------------------- /spec/data/tree-ops.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Embedding Metadata in HTML 4 | 49 | 50 | 51 |

This is an example of embedding metadata in a script element.

52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /spec/data/tree-ops.tsv: -------------------------------------------------------------------------------- 1 | GID On Street Species Trim Cycle Inventory Date 2 | 1 ADDISON AV Celtis australis Large Tree Routine Prune 10/18/2010 3 | 2 EMERSON ST Liquidambar styraciflua Large Tree Routine Prune 6/2/2010 4 | -------------------------------------------------------------------------------- /spec/format_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | $:.unshift "." 3 | require 'spec_helper' 4 | require 'rdf/spec/format' 5 | 6 | describe RDF::Tabular::Format do 7 | it_behaves_like 'an RDF::Format' do 8 | let(:format_class) {RDF::Tabular::Format} 9 | end 10 | 11 | describe ".for" do 12 | formats = [ 13 | :tabular, 14 | 'etc/doap.csv', 15 | 'etc/doap.tsv', 16 | {file_name: 'etc/doap.csv'}, 17 | {file_name: 'etc/doap.tsv'}, 18 | {file_extension: 'csv'}, 19 | {file_extension: 'tsv'}, 20 | {content_type: 'text/csv'}, 21 | {content_type: 'text/tab-separated-values'}, 22 | {content_type: 'application/csvm+json'}, 23 | ].each do |arg| 24 | it "discovers with #{arg.inspect}" do 25 | expect(RDF::Tabular::Format).to include RDF::Format.for(arg) 26 | end 27 | end 28 | end 29 | 30 | describe "#to_sym" do 31 | specify {expect(described_class.to_sym).to eq :tabular} 32 | end 33 | 34 | describe ".cli_commands", skip: ("TextMate OptionParser issues" if ENV['TM_SELECTED_FILE']) do 35 | before(:each) do 36 | WebMock.stub_request(:any, %r(.*example.org.*)). 37 | to_return(lambda {|request| 38 | file = request.uri.to_s.split('/').last 39 | content_type = case file 40 | when /\.json/ then 'application/json' 41 | when /\.csv/ then 'text/csv' 42 | else 'text/plain' 43 | end 44 | 45 | path = File.expand_path("../data/#{file}", __FILE__) 46 | if File.exist?(path) 47 | { 48 | body: File.read(path), 49 | status: 200, 50 | headers: {'Content-Type' => content_type} 51 | } 52 | else 53 | {status: 401} 54 | end 55 | }) 56 | end 57 | 58 | require 'rdf/cli' 59 | let(:input) {"http://example.org/data/countries.json"} 60 | describe "#tabular-json" do 61 | it "serializes to JSON" do 62 | expect { 63 | RDF::CLI.exec(["tabular-json", input], format: :tabular) 64 | }.to write.to(:output) 65 | end 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /spec/matchers.rb: -------------------------------------------------------------------------------- 1 | require 'rdf/isomorphic' 2 | require 'rspec/matchers' 3 | 4 | Info = Struct.new(:id, :logger, :action, :result, :metadata) 5 | 6 | RSpec::Matchers.define :pass_query do |expected, info| 7 | match do |actual| 8 | @info = if (info.id rescue false) 9 | info 10 | elsif info.is_a?(Hash) 11 | Info.new(info[:id], info[:logger], info[:action], info.fetch(:result, RDF::Literal::TRUE), info[:metadata]) 12 | end 13 | 14 | @expected = expected.respond_to?(:read) ? expected.read : expected 15 | 16 | require 'sparql' 17 | query = SPARQL.parse(@expected) 18 | @results = actual.query(query) 19 | 20 | @results == @info.result 21 | end 22 | 23 | failure_message do |actual| 24 | "#{@info.inspect + "\n"}" + 25 | if @results.nil? 26 | "Query failed to return results" 27 | elsif !@results.is_a?(RDF::Literal::Boolean) 28 | "Query returned non-boolean results" 29 | elsif @info.result != @results 30 | "Query returned false (expected #{@info.result})" 31 | else 32 | "Query returned true (expected #{@info.result})" 33 | end + 34 | "\n#{@expected}" + 35 | "\nResults:\n#{@actual.dump(:ttl, standard_prefixes: true, prefixes: {'' => @info.action + '#'}, literal_shorthand: false)}" + 36 | (@info.metadata ? "\nMetadata:\n#{@info.metadata.to_json(JSON_STATE)}\n" : "") + 37 | (@info.metadata && !@info.metadata.errors.empty? ? "\nMetadata Errors:\n#{@info.metadata.errors.join("\n")}\n" : "") + 38 | "\nDebug:\n#{@info.logger}" 39 | end 40 | 41 | failure_message_when_negated do |actual| 42 | "#{@info.inspect + "\n"}" + 43 | if @results.nil? 44 | "Query failed to return results" 45 | elsif !@results.is_a?(RDF::Literal::Boolean) 46 | "Query returned non-boolean results" 47 | elsif @info.expectedResults != @results 48 | "Query returned false (expected #{@info.result})" 49 | else 50 | "Query returned true (expected #{@info.result})" 51 | end + 52 | "\n#{@expected}" + 53 | "\nResults:\n#{@actual.dump(:ttl, standard_prefixes: true, prefixes: {'' => @info.action + '#'}, literal_shorthand: false)}" + 54 | (@info.metadata ? "\nMetadata:\n#{@info.metadata.to_json(JSON_STATE)}\n" : "") + 55 | (@info.metadata && !@info.metadata.errors.empty? ? "\nMetadata Errors:\n#{@info.metadata.errors.join("\n")}\n" : "") + 56 | "\nDebug:\n#{@info.logger}" 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /spec/reader_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | require File.join(File.dirname(__FILE__), 'spec_helper') 3 | require 'rdf/spec/reader' 4 | 5 | describe RDF::Tabular::Reader do 6 | let(:logger) {RDF::Spec.logger} 7 | let!(:doap) {File.expand_path("../../etc/doap.csv", __FILE__)} 8 | let!(:doap_count) {9} 9 | 10 | before(:each) do 11 | WebMock.stub_request(:any, %r(.*example.org.*)). 12 | to_return(lambda {|request| 13 | file = request.uri.to_s.split('/').last 14 | content_type = case file 15 | when /\.json/ then 'application/json' 16 | when /\.csv/ then 'text/csv' 17 | else 'text/plain' 18 | end 19 | 20 | path = File.expand_path("../data/#{file}", __FILE__) 21 | if File.exist?(path) 22 | { 23 | body: File.read(path), 24 | status: 200, 25 | headers: {'Content-Type' => content_type} 26 | } 27 | else 28 | {status: 401} 29 | end 30 | }) 31 | end 32 | after(:each) {|example| puts logger.to_s if example.exception} 33 | 34 | # @see lib/rdf/spec/reader.rb in rdf-spec 35 | # two failures specific to the way @input is handled in rdf-tabular make this a problem 36 | #it_behaves_like 'an RDF::Reader' do 37 | # let(:reader_input) {doap} 38 | # let(:reader) {RDF::Tabular::Reader.new(StringIO.new(""))} 39 | # let(:reader_count) {doap_count} 40 | #end 41 | 42 | it "should be discoverable" do 43 | readers = [ 44 | RDF::Reader.for(:tabular), 45 | RDF::Reader.for("etc/doap.csv"), 46 | RDF::Reader.for(file_name: "etc/doap.csv"), 47 | RDF::Reader.for(file_extension: "csv"), 48 | RDF::Reader.for(content_type: "text/csv"), 49 | ] 50 | readers.each { |reader| expect(reader).to eq RDF::Tabular::Reader } 51 | end 52 | 53 | context "HTTP Headers" do 54 | before(:each) { 55 | allow_any_instance_of(RDF::Tabular::Dialect).to receive(:embedded_metadata).and_return(RDF::Tabular::Table.new({}, logger: false)) 56 | allow_any_instance_of(RDF::Tabular::Metadata).to receive(:each_row).and_yield(RDF::Statement.new) 57 | } 58 | it "sets delimiter to TAB in dialect given text/tsv" do 59 | input = double("input", content_type: "text/tsv", headers: {content_type: "text/tsv"}, charset: nil) 60 | expect_any_instance_of(RDF::Tabular::Dialect).to receive(:separator=).with("\t") 61 | RDF::Tabular::Reader.new(input, logger: false) {|r| r.each_statement {}} 62 | end 63 | it "sets header to false in dialect given header=absent" do 64 | input = double("input", content_type: "text/csv", headers: {content_type: "text/csv;header=absent"}, charset: nil) 65 | expect_any_instance_of(RDF::Tabular::Dialect).to receive(:header=).with(false) 66 | RDF::Tabular::Reader.new(input, logger: false) {|r| r.each_statement {}} 67 | end 68 | it "sets encoding to ISO-8859-4 in dialect given charset=ISO-8859-4" do 69 | input = double("input", content_type: "text/csv", headers: {content_type: "text/csv;charset=ISO-8859-4"}, charset: "ISO-8859-4") 70 | expect_any_instance_of(RDF::Tabular::Dialect).to receive(:encoding=).with("ISO-8859-4") 71 | RDF::Tabular::Reader.new(input, logger: false) {|r| r.each_statement {}} 72 | end 73 | it "sets lang to de in metadata given Content-Language=de", pending: "affecting some RSpec matcher" do 74 | input = double("input", content_type: "text/csv", headers: {content_language: "de"}, charset: nil) 75 | expect_any_instance_of(RDF::Tabular::Metadata).to receive(:lang=).with("de") 76 | RDF::Tabular::Reader.new(input, logger: false) {|r| r.each_statement {}} 77 | end 78 | it "does not set lang with two languages in metadata given Content-Language=de, en" do 79 | input = double("input", content_type: "text/csv", headers: {content_language: "de, en"}, charset: nil) 80 | expect_any_instance_of(RDF::Tabular::Metadata).not_to receive(:lang=) 81 | RDF::Tabular::Reader.new(input, logger: false) {|r| r.each_statement {}} 82 | end 83 | end 84 | 85 | context "non-file input" do 86 | let(:expected) { 87 | JSON.parse(%({ 88 | "tables": [ 89 | { 90 | "url": "", 91 | "row": [ 92 | { 93 | "url": "#row=2", 94 | "rownum": 1, 95 | "describes": [ 96 | { 97 | "country": "AD", 98 | "name": "Andorra" 99 | } 100 | ] 101 | }, 102 | { 103 | "url": "#row=3", 104 | "rownum": 2, 105 | "describes": [ 106 | { 107 | "country": "AF", 108 | "name": "Afghanistan" 109 | } 110 | ] 111 | }, 112 | { 113 | "url": "#row=4", 114 | "rownum": 3, 115 | "describes": [ 116 | { 117 | "country": "AI", 118 | "name": "Anguilla" 119 | } 120 | ] 121 | }, 122 | { 123 | "url": "#row=5", 124 | "rownum": 4, 125 | "describes": [ 126 | { 127 | "country": "AL", 128 | "name": "Albania" 129 | } 130 | ] 131 | } 132 | ] 133 | } 134 | ] 135 | })) 136 | } 137 | 138 | { 139 | StringIO: StringIO.new(File.read(File.expand_path("../data/country-codes-and-names.csv", __FILE__))), 140 | ArrayOfArrayOfString: CSV.new(File.open(File.expand_path("../data/country-codes-and-names.csv", __FILE__))).to_a, 141 | String: File.read(File.expand_path("../data/country-codes-and-names.csv", __FILE__)), 142 | }.each do |name, input| 143 | it name do 144 | RDF::Tabular::Reader.new(input, noProv: true, logger: logger) do |reader| 145 | expect(JSON.parse(reader.to_json)).to produce(expected, 146 | logger: logger, 147 | result: expected, 148 | noProv: true, 149 | metadata: reader.metadata 150 | ) 151 | end 152 | end 153 | end 154 | end 155 | 156 | context "Test Files" do 157 | test_files = { 158 | "tree-ops.csv" => "tree-ops-standard.ttl", 159 | "tree-ops.csv-metadata.json" => "tree-ops-standard.ttl", 160 | "tree-ops-ext.json" => "tree-ops-ext-standard.ttl", 161 | "tree-ops-virtual.json" => "tree-ops-virtual-standard.ttl", 162 | "country-codes-and-names.csv" => "country-codes-and-names-standard.ttl", 163 | "countries.json" => "countries-standard.ttl", 164 | "countries.csv" => "countries.csv-standard.ttl", 165 | "countries.html" => "countries_html-standard.ttl", 166 | "countries_embed.html" => "countries_embed-standard.ttl", 167 | "roles.json" => "roles-standard.ttl", 168 | } 169 | context "#each_statement" do 170 | test_files.each do |csv, ttl| 171 | context csv do 172 | let(:about) {RDF::URI("http://example.org").join(csv)} 173 | let(:input) {File.expand_path("../data/#{csv}", __FILE__)} 174 | 175 | it "standard mode" do 176 | expected = File.expand_path("../data/#{ttl}", __FILE__) 177 | RDF::Reader.open(input, format: :tabular, base_uri: about, noProv: true, logger: logger) do |reader| 178 | graph = RDF::Graph.new << reader 179 | graph2 = RDF::Graph.load(expected, base_uri: about) 180 | expect(graph).to be_equivalent_graph(graph2, 181 | logger: logger, 182 | id: about, 183 | action: about, 184 | result: expected, 185 | metadata: reader.metadata) 186 | end 187 | end 188 | 189 | it "minimal mode" do 190 | ttl = ttl.sub("standard", "minimal") 191 | expected = File.expand_path("../data/#{ttl}", __FILE__) 192 | RDF::Reader.open(input, format: :tabular, base_uri: about, minimal: true, logger: logger) do |reader| 193 | graph = RDF::Graph.new << reader 194 | graph2 = RDF::Graph.load(expected, base_uri: about) 195 | expect(graph).to be_equivalent_graph(graph2, 196 | logger: logger, 197 | id: about, 198 | action: about, 199 | result: expected, 200 | metadata: reader.metadata) 201 | end 202 | end 203 | end 204 | end 205 | end 206 | 207 | describe "#to_json" do 208 | test_files.each do |csv, ttl| 209 | context csv do 210 | let(:about) {RDF::URI("http://example.org").join(csv)} 211 | let(:input) {File.expand_path("../data/#{csv}", __FILE__)} 212 | it "standard mode" do 213 | json = ttl.sub("-standard.ttl", "-standard.json") 214 | expected = File.expand_path("../data/#{json}", __FILE__) 215 | 216 | RDF::Reader.open(input, format: :tabular, base_uri: about, noProv: true, logger: logger) do |reader| 217 | expect(JSON.parse(reader.to_json)).to produce( 218 | JSON.parse(File.read(expected)), 219 | logger: logger, 220 | id: about, 221 | action: about, 222 | result: expected, 223 | noProv: true, 224 | metadata: reader.metadata 225 | ) 226 | end 227 | end 228 | 229 | it "minimal mode" do 230 | json = ttl.sub("-standard.ttl", "-minimal.json") 231 | expected = File.expand_path("../data/#{json}", __FILE__) 232 | 233 | RDF::Reader.open(input, format: :tabular, base_uri: about, minimal: true, logger: logger) do |reader| 234 | expect(JSON.parse(reader.to_json)).to produce( 235 | JSON.parse(File.read(expected)), 236 | logger: logger, 237 | id: about, 238 | action: about, 239 | result: expected, 240 | minimal: true, 241 | metadata: reader.metadata 242 | ) 243 | end 244 | end 245 | 246 | it "ADT mode", unless: true do 247 | json = ttl.sub("-standard.ttl", "-atd.json") 248 | expected = File.expand_path("../data/#{json}", __FILE__) 249 | 250 | RDF::Reader.open(input, format: :tabular, base_uri: about, noProv: true, logger: logger) do |reader| 251 | expect(JSON.parse(reader.to_json(atd: true))).to produce( 252 | JSON.parse(File.read(expected)), 253 | logger: logger, 254 | id: about, 255 | action: about, 256 | result: expected, 257 | noProv: true, 258 | metadata: reader.metadata 259 | ) 260 | end 261 | end 262 | end 263 | end 264 | end 265 | end 266 | 267 | context "Primary Keys" do 268 | it "has expected primary keys" do 269 | RDF::Reader.open("http://example.org/countries.json", format: :tabular, validate: true) do |reader| 270 | reader.each_statement {} 271 | pks = reader.metadata.tables.first.object[:rows].map(&:primaryKey) 272 | 273 | # Each entry is an array of cells 274 | expect(pks.map {|r| r.map(&:value).join(",")}).to eql %w(AD AE AF) 275 | end 276 | end 277 | 278 | it "errors on duplicate primary keys" do 279 | RDF::Reader.open("http://example.org/test232-metadata.json", format: :tabular, validate: true, logger: logger) do |reader| 280 | expect {reader.validate!}.to raise_error(RDF::Tabular::Error) 281 | 282 | pks = reader.metadata.tables.first.object[:rows].map(&:primaryKey) 283 | 284 | # Each entry is an array of cells 285 | expect(pks.map {|r| r.map(&:value).join(",")}).to eql %w(1 1) 286 | 287 | expect(logger.to_s).to include "Table http://example.org/test232.csv has duplicate primary key 1" 288 | end 289 | end 290 | end 291 | 292 | context "Foreign Keys" do 293 | let(:path) {File.expand_path("../data/countries.json", __FILE__)} 294 | it "validates consistent foreign keys" do 295 | RDF::Reader.open(path, format: :tabular, validate: true, warnings: []) do |reader| 296 | reader.each_statement {} 297 | expect(reader.options[:warnings]).to be_empty 298 | end 299 | end 300 | end 301 | 302 | context "Provenance" do 303 | { 304 | "country-codes-and-names.csv" => %( 305 | PREFIX csvw: 306 | PREFIX prov: 307 | PREFIX xsd: 308 | ASK WHERE { 309 | [ prov:wasGeneratedBy [ 310 | a prov:Activity; 311 | prov:wasAssociatedWith ; 312 | prov:startedAtTime ?start; 313 | prov:endedAtTime ?end; 314 | prov:qualifiedUsage [ 315 | a prov:Usage ; 316 | # prov:entity ; 317 | prov:hadRole csvw:csvEncodedTabularData 318 | ]; 319 | ] 320 | ] 321 | FILTER ( 322 | DATATYPE(?start) = xsd:dateTime && 323 | DATATYPE(?end) = xsd:dateTime 324 | ) 325 | } 326 | ), 327 | "countries.json" => %( 328 | PREFIX csvw: 329 | PREFIX prov: 330 | PREFIX xsd: 331 | ASK WHERE { 332 | [ prov:wasGeneratedBy [ 333 | a prov:Activity; 334 | prov:wasAssociatedWith ; 335 | prov:startedAtTime ?start; 336 | prov:endedAtTime ?end; 337 | prov:qualifiedUsage [ 338 | a prov:Usage ; 339 | # prov:entity , ; 340 | prov:hadRole csvw:csvEncodedTabularData 341 | ], [ 342 | a prov:Usage ; 343 | # prov:entity ; 344 | prov:hadRole csvw:tabularMetadata 345 | ]; 346 | ] 347 | ] 348 | FILTER ( 349 | DATATYPE(?start) = xsd:dateTime && 350 | DATATYPE(?end) = xsd:dateTime 351 | ) 352 | } 353 | ) 354 | }.each do |file, query| 355 | it file do 356 | about = RDF::URI("http://example.org").join(file) 357 | input = File.expand_path("../data/#{file}", __FILE__) 358 | graph = RDF::Graph.load(input, format: :tabular, base_uri: about, logger: logger) 359 | 360 | expect(graph).to pass_query(query, logger: logger, id: about, action: about) 361 | end 362 | end 363 | end 364 | end 365 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) 2 | $:.unshift File.dirname(__FILE__) 3 | 4 | require "bundler/setup" 5 | require 'rspec' 6 | require 'rspec/its' 7 | require 'rdf/isomorphic' 8 | require 'rdf/spec' 9 | require 'rdf/spec/matchers' 10 | require 'rdf/turtle' 11 | require 'rdf/vocab' 12 | require 'json' 13 | require 'webmock/rspec' 14 | require 'matchers' 15 | require 'suite_helper' 16 | 17 | begin 18 | require 'simplecov' 19 | require 'simplecov-lcov' 20 | 21 | SimpleCov::Formatter::LcovFormatter.config do |config| 22 | #Coveralls is coverage by default/lcov. Send info results 23 | config.report_with_single_file = true 24 | config.single_report_path = 'coverage/lcov.info' 25 | end 26 | 27 | SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([ 28 | SimpleCov::Formatter::HTMLFormatter, 29 | SimpleCov::Formatter::LcovFormatter 30 | ]) 31 | SimpleCov.start do 32 | add_filter "/spec/" 33 | end 34 | rescue LoadError => e 35 | STDERR.puts "Coverage Skipped: #{e.message}" 36 | end 37 | 38 | require 'rdf/tabular' 39 | 40 | JSON_STATE = JSON::State.new( 41 | :indent => " ", 42 | :space => " ", 43 | :space_before => "", 44 | :object_nl => "\n", 45 | :array_nl => "\n" 46 | ) 47 | 48 | ::RSpec.configure do |c| 49 | c.filter_run focus: true 50 | c.run_all_when_everything_filtered = true 51 | c.include(RDF::Spec::Matchers) 52 | end 53 | 54 | # Heuristically detect the input stream 55 | def detect_format(stream) 56 | # Got to look into the file to see 57 | if stream.is_a?(IO) || stream.is_a?(StringIO) 58 | stream.rewind 59 | string = stream.read(1000) 60 | stream.rewind 61 | else 62 | string = stream.to_s 63 | end 64 | case string 65 | when / Object}] options 22 | # @option options [Array, String] :headers 23 | # HTTP Request headers. 24 | # @return [IO] File stream 25 | # @yield [IO] File stream 26 | def self.open_file(filename_or_url, **options, &block) 27 | case 28 | when filename_or_url.to_s =~ %r{http://www.w3.org/ns/csvw/?} 29 | ::File.open(::File.expand_path("../../etc/csvw.jsonld", __FILE__), &block) 30 | when filename_or_url.to_s == "http://www.w3.org/.well-known/csvm" 31 | ::File.open(::File.expand_path("../../etc/well-known", __FILE__), &block) 32 | when (filename_or_url.to_s =~ %r{^#{REMOTE_PATH}} && Dir.exist?(LOCAL_PATH)) 33 | begin 34 | #puts "attempt to open #{filename_or_url} locally" 35 | localpath = RDF::URI(filename_or_url).dup 36 | localpath.query = nil 37 | localpath = localpath.to_s.sub(REMOTE_PATH, LOCAL_PATH) 38 | response = begin 39 | ::File.open(localpath) 40 | rescue Errno::ENOENT => e 41 | raise IOError, e.message 42 | end 43 | document_options = { 44 | base_uri: RDF::URI(filename_or_url), 45 | charset: Encoding::UTF_8, 46 | code: 200, 47 | headers: {} 48 | } 49 | #puts "use #{filename_or_url} locally" 50 | document_options[:headers][:content_type] = case filename_or_url.to_s 51 | when /\.csv$/ then 'text/csv' 52 | when /\.tsv$/ then 'text/tsv' 53 | when /\.json$/ then 'application/json' 54 | when /\.jsonld$/ then 'application/ld+json' 55 | else 'unknown' 56 | end 57 | 58 | document_options[:headers][:content_type] = response.content_type if response.respond_to?(:content_type) 59 | # For overriding content type from test data 60 | document_options[:headers][:content_type] = options[:contentType] if options[:contentType] 61 | 62 | # For overriding Link header from test data 63 | document_options[:headers][:link] = options[:httpLink] if options[:httpLink] 64 | 65 | remote_document = RDF::Util::File::RemoteDocument.new(response.read, document_options) 66 | if block_given? 67 | yield remote_document 68 | else 69 | remote_document 70 | end 71 | end 72 | else 73 | original_open_file(filename_or_url, **options) do |remote_document| 74 | # Add Link header, if necessary 75 | remote_document.headers[:link] = options[:httpLink] if options[:httpLink] 76 | 77 | # Override content_type 78 | if options[:contentType] 79 | remote_document.headers[:content_type] = options[:contentType] 80 | remote_document.instance_variable_set(:@content_type, options[:contentType].split(';').first) 81 | end 82 | 83 | if block_given? 84 | yield remote_document 85 | else 86 | remote_document 87 | end 88 | end 89 | end 90 | end 91 | end 92 | end 93 | 94 | module Fixtures 95 | module SuiteTest 96 | BASE = "http://www.w3.org/2013/csvw/tests/" 97 | class Manifest < JSON::LD::Resource 98 | def self.open(file, base) 99 | RDF::Util::File.open_file(file) do |file| 100 | json = ::JSON.load(file.read) 101 | yield Manifest.new(json, context: json['@context'].merge('@base' => base)) 102 | end 103 | end 104 | 105 | def entries 106 | # Map entries to resources 107 | attributes['entries'].map {|e| Entry.new(e, context: context)} 108 | end 109 | end 110 | 111 | class Entry < JSON::LD::Resource 112 | attr_accessor :logger 113 | attr_accessor :warnings 114 | attr_accessor :errors 115 | attr_accessor :metadata 116 | 117 | def id 118 | attributes['id'] 119 | end 120 | 121 | def base 122 | action 123 | end 124 | 125 | # Apply base to action and result 126 | def action 127 | RDF::URI(context['@base']).join(attributes["action"]).to_s 128 | end 129 | 130 | def result 131 | RDF::URI(context['@base']).join(attributes["result"]).to_s if attributes["result"] 132 | end 133 | 134 | def input 135 | @input ||= RDF::Util::File.open_file(action) {|f| f.read} 136 | end 137 | 138 | def expected 139 | @expected ||= RDF::Util::File.open_file(result) {|f| f.read} rescue nil 140 | end 141 | 142 | def evaluate? 143 | type.to_s.include?("To") 144 | end 145 | 146 | def rdf? 147 | result.to_s.end_with?(".ttl") 148 | end 149 | 150 | def json? 151 | result.to_s.end_with?(".json") 152 | end 153 | 154 | def validation? 155 | type.to_s.include?("Validation") 156 | end 157 | 158 | def warning? 159 | type.to_s.include?("Warning") 160 | end 161 | 162 | def positive_test? 163 | !negative_test? 164 | end 165 | 166 | def negative_test? 167 | type.to_s.include?("Negative") 168 | end 169 | 170 | def reader_options 171 | res = {} 172 | res[:noProv] = option['noProv'] if option 173 | res[:metadata] = RDF::URI(context['@base']).join(option['metadata']).to_s if option && option.has_key?('metadata') 174 | res[:httpLink] = httpLink if attributes['httpLink'] 175 | res[:minimal] = option['minimal'] if option 176 | res[:contentType] = contentType if attributes['contentType'] 177 | res 178 | end 179 | end 180 | end 181 | end 182 | -------------------------------------------------------------------------------- /spec/suite_spec.rb: -------------------------------------------------------------------------------- 1 | $:.unshift "." 2 | require 'spec_helper' 3 | require 'fileutils' 4 | 5 | WebMock.allow_net_connect!(net_http_connect_on_start: true) 6 | describe RDF::Tabular::Reader do 7 | require 'suite_helper' 8 | 9 | before(:all) {WebMock.allow_net_connect!(net_http_connect_on_start: true)} 10 | after(:all) {WebMock.allow_net_connect!(net_http_connect_on_start: false)} 11 | 12 | %w(rdf json validation nonnorm).each do |variant| 13 | describe "w3c csvw #{variant.upcase} tests" do 14 | manifest = Fixtures::SuiteTest::BASE + "manifest-#{variant}.jsonld" 15 | 16 | Fixtures::SuiteTest::Manifest.open(manifest, manifest[0..-8]) do |m| 17 | describe m.comment do 18 | m.entries.each do |t| 19 | next if t.approval =~ /Rejected/ 20 | specify "#{t.id.split("/").last}: #{t.name} - #{t.comment}" do 21 | pending "rdf#test283 literal normalization" if t.id.include?("rdf#test283") 22 | t.logger = RDF::Spec.logger 23 | t.logger.formatter = lambda {|severity, datetime, progname, msg| "#{severity}: #{msg}\n"} 24 | t.logger.info t.inspect 25 | t.logger.info "source:\n#{t.input}" 26 | begin 27 | RDF::Tabular::Reader.open(t.action, 28 | base_uri: t.base, 29 | validate: t.validation?, 30 | logger: t.logger, 31 | **t.reader_options 32 | ) do |reader| 33 | expect(reader).to be_a RDF::Reader 34 | 35 | t.metadata = reader.metadata # for debug output 36 | t.metadata = t.metadata.parent if t.metadata && t.metadata.parent 37 | 38 | graph = RDF::Repository.new 39 | 40 | if t.positive_test? 41 | if t.json? 42 | result = reader.to_json 43 | if t.evaluate? 44 | RDF::Util::File.open_file(t.result) do |res| 45 | expect(::JSON.parse(result)).to produce(::JSON.parse(res.read), t) 46 | end 47 | else 48 | expect(::JSON.parse(result)).to be_a(Hash) 49 | end 50 | else # RDF or Validation 51 | if t.evaluate? 52 | graph << reader 53 | output_graph = RDF::Repository.load(t.result, format: :ttl, base_uri: t.base) 54 | expect(graph).to be_equivalent_graph(output_graph, t) 55 | elsif t.validation? 56 | expect {reader.validate!}.not_to raise_error 57 | end 58 | end 59 | 60 | if t.warning? 61 | expect(t.logger.log_statistics).to have_key(:warn) 62 | else 63 | expect(t.logger.log_statistics).not_to have_key(:warn) 64 | end 65 | expect(t.logger.log_statistics).not_to have_key(:error) 66 | elsif t.json? 67 | expect {reader.to_json}.to raise_error(RDF::Tabular::Error) 68 | elsif t.validation? 69 | expect {reader.validate!}.to raise_error(RDF::Tabular::Error) 70 | else 71 | expect {graph << reader}.to raise_error(RDF::ReaderError) 72 | end 73 | end 74 | rescue IOError 75 | # Special case when Reader.initialize raises the error 76 | raise unless t.negative_test? 77 | end 78 | end 79 | end 80 | end 81 | end 82 | end 83 | end 84 | end unless ENV['CI'] # Skip for continuous integration --------------------------------------------------------------------------------