diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..0589f05 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,72 @@ +# This workflow runs continuous CI across different versions of ruby on all branches and pull requests to develop. + +name: CI +on: + push: + branches: [ '**' ] + pull_request: + branches: [ develop ] + workflow_dispatch: + +jobs: + tests: + name: Ruby ${{ matrix.ruby }} + if: "contains(github.event.commits[0].message, '[ci skip]') == false" + runs-on: ubuntu-latest + env: + CI: true + ALLOW_FAILURES: ${{ endsWith(matrix.ruby, 'head') || matrix.ruby == 'jruby' }} + strategy: + fail-fast: false + matrix: + ruby: + - 2.6 + - 2.7 + - "3.0" + - 3.1 + - ruby-head + - jruby + steps: + - name: Clone repository + uses: actions/checkout@v2 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + - name: Install dependencies + run: bundle install --jobs 4 --retry 3 + - name: Run tests + run: ruby --version; bundle exec rspec spec || $ALLOW_FAILURES + - name: Coveralls GitHub Action + uses: coverallsapp/github-action@v1.1.2 + if: "matrix.ruby == '3.0'" + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + wintests: + name: Win64 Ruby ${{ matrix.ruby }} + if: "contains(github.event.commits[0].message, '[ci skip]') == false" + runs-on: windows-latest + env: + CI: true + ALLOW_FAILURES: ${{ endsWith(matrix.ruby, 'head') || matrix.ruby == 'jruby' }} + strategy: + fail-fast: false + matrix: + ruby: + - 3.1 + steps: + - name: Clone repository + uses: actions/checkout@v2 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + - name: Install dependencies + run: bundle install --jobs 4 --retry 3 + - name: Run tests + run: ruby --version; bundle exec rspec spec || $ALLOW_FAILURES + - name: Coveralls GitHub Action + uses: coverallsapp/github-action@v1.1.2 + if: "matrix.ruby == '3.0'" + with: + github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/generate-docs.yml b/.github/workflows/generate-docs.yml new file mode 100644 index 0000000..b8d16ed --- /dev/null +++ b/.github/workflows/generate-docs.yml @@ -0,0 +1,27 @@ +name: Build & deploy documentation +on: + push: + branches: + - master + workflow_dispatch: +jobs: + build: + runs-on: ubuntu-latest + name: Update gh-pages with docs + steps: + - name: Clone repository + uses: actions/checkout@v2 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: "3.1" + - name: Install required gem dependencies + run: gem install yard --no-document + - name: Build YARD Ruby Documentation + run: yardoc + - name: Deploy + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./doc/yard + publish_branch: gh-pages diff --git a/.gitignore b/.gitignore index e3200e0..5d653e2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,56 +1,12 @@ -*.gem -*.rbc -/.config -/coverage/ -/InstalledFiles -/pkg/ -/spec/reports/ -/spec/examples.txt -/test/tmp/ -/test/version_tmp/ -/tmp/ - -# Used by dotenv library to load environment variables. -# .env - -# Ignore Byebug command history file. -.byebug_history - -## Specific to RubyMotion: -.dat* -.repl_history -build/ -*.bridgesupport -build-iPhoneOS/ -build-iPhoneSimulator/ - -## Specific to RubyMotion (use of CocoaPods): -# -# We recommend against adding the Pods directory to your .gitignore. However -# you should judge for yourself, the pros and cons are mentioned at: -# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control -# -# vendor/Pods/ - -## Documentation cache and generated files: /.yardoc/ -/_yardoc/ /doc/ -/rdoc/ - -## Environment normalization: +/coverage/ +/*.gem +/coverage/ +/spec.html +/spec.pdf +/pkg/ +/.rbx/ /.bundle/ -/vendor/bundle -/lib/bundler/man/ - -# for a library or gem, you might want to ignore these files since the code is -# intended to run in multiple environments; otherwise, check them in: -# Gemfile.lock -# .ruby-version -# .ruby-gemset - -# unless supporting rvm < 1.11.0 or doing something fancy, ignore this: -.rvmrc - -# Used by RuboCop. Remote config files pulled in from inherit_from directive. -# .rubocop-https?--* +Gemfile.lock +/.byebug_history diff --git a/.yardopts b/.yardopts new file mode 100644 index 0000000..2dd92d7 --- /dev/null +++ b/.yardopts @@ -0,0 +1,11 @@ +--title "JSON-LD reader/writer for RDF.rb." +--output-dir doc/yard +--protected +--no-private +--hide-void-return +--markup markdown +--readme README.md +- +AUTHORS +VERSION +UNLICENSE diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..f6877e7 --- /dev/null +++ b/AUTHORS @@ -0,0 +1 @@ +* Gregg Kellogg diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..2d62cf8 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,38 @@ +# How to contribute + +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. + +## Development + +This repository uses [Git Flow](https://github.com/nvie/gitflow) to mange development and release activity. All submissions _must_ be on a feature branch based on the _develop_ branch to ease staging and integration. + +* create or respond to an issue on the [Github Repository](https://github.com/ruby-rdf/json-ld/issues) +* Fork and clone the repo: + `git clone git@github.com:your-username/json-ld.git` +* Install bundle: + `bundle install` +* Create tests in RSpec and make sure you achieve at least 90% code coverage for the feature your adding or behavior being modified. +* Push to your fork and [submit a pull request][pr]. + +## Do's and Dont's +* Do your best to adhere to the existing coding conventions and idioms. +* Don't use hard tabs, and don't leave trailing whitespace on any line. + Before committing, run `git diff --check` to make sure of this. +* Do document every method you add using [YARD][] annotations. Read the + [tutorial][YARD-GS] or just look at the existing code for examples. +* Don't touch the `.gemspec` or `VERSION` files. If you need to change them, + do so on your private branch only. +* Do feel free to add yourself to the `CREDITS` file and the + corresponding list in the the `README`. Alphabetical order applies. +* Don't touch the `AUTHORS` file. If your contributions are significant + enough, be assured we will eventually add you in there. +* Do note that in order for us to merge any non-trivial changes (as a rule + of thumb, additions larger than about 15 lines of code), we need an + explicit [public domain dedication][PDD] on record from you, + which you will be asked to agree to on the first commit to a repo within the organization. + Note that the agreement applies to all repos in the [Ruby RDF](https://github.com/ruby-rdf/) organization. + +[YARD]: https://yardoc.org/ +[YARD-GS]: https://rubydoc.info/docs/yard/file/docs/GettingStarted.md +[PDD]: https://unlicense.org/#unlicensing-contributions +[pr]: https://github.com/ruby-rdf/rdf/compare/ diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..34496e1 --- /dev/null +++ b/Gemfile @@ -0,0 +1,24 @@ +source "https://rubygems.org" + +gemspec +gem 'rdf', git: "https://github.com/ruby-rdf/rdf", branch: "develop" +gem 'json-ld', git: "https://github.com/ruby-rdf/json-ld", branch: "develop" + +group :development do + gem 'rdf-isomorphic', git: "https://github.com/ruby-rdf/rdf-isomorphic", branch: "develop" + gem 'rdf-spec', git: "https://github.com/ruby-rdf/rdf-spec", branch: "develop" + gem 'rdf-vocab', git: "https://github.com/ruby-rdf/rdf-vocab", branch: "develop" + gem 'rdf-xsd', git: "https://github.com/ruby-rdf/rdf-xsd", branch: "develop" + gem 'earl-report' + gem 'ruby-prof', platforms: :mri +end + +group :development, :test do + gem 'simplecov', '~> 0.21', platforms: :mri + gem 'simplecov-lcov', '~> 0.8', platforms: :mri + gem 'rake' +end + +group :debug do + gem "byebug", platforms: :mri +end diff --git a/README.md b/README.md index c4e9c22..1cf2b31 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,150 @@ -# yaml-ld -Ruby YAML-LD reader/writer for RDF.rb +# YAML-LD reader/writer + +Ruby [YAML-LD][] reader/writer for RDF.rb + +[![Gem Version](https://badge.fury.io/rb/yaml-ld.png)](https://rubygems.org/gems/yaml-ld) +[![Build Status](https://secure.travis-ci.org/ruby-rdf/yaml-ld.png?branch=develop)](https://github.com/ruby-rdf/yaml-ld/actions?query=workflow%3ACI) +[![Coverage Status](https://coveralls.io/repos/ruby-rdf/yaml-ld/badge.svg?branch=develop)](https://coveralls.io/github/ruby-rdf/yaml-ld?branch=develop) +[![Gitter chat](https://badges.gitter.im/ruby-rdf.png)](https://gitter.im/gitterHQ/gitter) + +## Features + +YAML_LD parses and serializes [YAML-LD][] into [RDF][]. + +As the specification is under development, this gem should be considered **experimental** and is subject to change at any time. + +YAML-LD documents may use frames or contexts described either using [JSON-LD][] or [YAML-LD][]. + +* Process YAML-LD source using JSON-LD Context or Frame. +* Process JSON-LD source using YAML-LD Context or Frame. + +## Examples + + require 'rubygems' + require 'yaml\_ld' + +### Expand a YAML-LD Document + + input = StringIO.new(%( + "@context": + "@vocab": http://xmlns.com/foaf/0.1/ + name: Gregg Kellogg + homepage: https://greggkellogg.net + depiction: http://www.gravatar.com/avatar/42f948adff3afaa52249d963117af7c8 + )) + + YAML_LD::API.expand(input) # => %( + %YAML 1.2 + --- + - http://xmlns.com/foaf/0.1/name: + - "@value": Gregg Kellogg + http://xmlns.com/foaf/0.1/homepage: + - "@value": https://greggkellogg.net + http://xmlns.com/foaf/0.1/depiction: + - "@value": http://www.gravatar.com/avatar/42f948adff3afaa52249d963117af7c8 + ) + +### Expand a YAML-LD Document to JSON-LD + + input = StringIO.new(%( + "@context": + "@vocab": http://xmlns.com/foaf/0.1/ + name: Gregg Kellogg + homepage: https://greggkellogg.net + depiction: http://www.gravatar.com/avatar/42f948adff3afaa52249d963117af7c8 + )) + + YAML_LD::API.expand(input, serializer: JSON::LD::API.method(:serializer)) # => %( + [ + { + "http://xmlns.com/foaf/0.1/name": [{ "@value": "Gregg Kellogg" }], + "http://xmlns.com/foaf/0.1/homepage": [{ "@value": "https://greggkellogg.net" }], + "http://xmlns.com/foaf/0.1/depiction": [{ + "@value": "http://www.gravatar.com/avatar/42f948adff3afaa52249d963117af7c8" + }] + } + ] + ) + +### Expand a JSON-LD Document to YAML-LD + + input = StringIO.new(%( + { + "@context": { + "@vocab":"http://xmlns.com/foaf/0.1/" + }, + "name": "Gregg Kellogg", + "homepage": "https://greggkellogg.net", + "depiction": "http://www.gravatar.com/avatar/42f948adff3afaa52249d963117af7c8" + } + )) + + YAML_LD::API.expand(input, content_type: 'application/ld+json') # => %( + %YAML 1.2 + --- + - http://xmlns.com/foaf/0.1/name: + - "@value": Gregg Kellogg + http://xmlns.com/foaf/0.1/homepage: + - "@value": https://greggkellogg.net + http://xmlns.com/foaf/0.1/depiction: + - "@value": http://www.gravatar.com/avatar/42f948adff3afaa52249d963117af7c8 + ) + +## Implementation + +The gem largely acts as a front-end for the [JSON-LD gem][] with differences largely in the serialization format only. + +In addition to the input, both a `context` and `frame` may be specified using either JSON-LD or YAML-LD. + +## Dependencies +* [Ruby](https://ruby-lang.org/) (>= 2.6) +* [JSON-LD](https://rubygems.org/gems/json-ld) (>= 3.2.2) +* [Psych](https://rubygems.org/gems/psych) (>= 4.0) +* [RDF.rb](https://rubygems.org/gems/rdf) (~> 3.2) + +## Installation +The recommended installation method is via [RubyGems](https://rubygems.org/). +To install the latest official release of the `JSON-LD` gem, do: + + % [sudo] gem install yaml-ld + +## Download +To get a local working copy of the development repository, do: + + % git clone git://github.com/ruby-rdf/yaml-ld.git + +## Mailing List +* + +## Author +* [Gregg Kellogg](https://github.com/gkellogg) - + +## Contributing +* Do your best to adhere to the existing coding conventions and idioms. +* Don't use hard tabs, and don't leave trailing whitespace on any line. +* Do document every method you add using [YARD][] annotations. Read the + [tutorial][YARD-GS] or just look at the existing code for examples. +* Don't touch the `json-ld.gemspec`, `VERSION` or `AUTHORS` files. If you need to + change them, do so on your private branch only. +* Do feel free to add yourself to the `CREDITS` file and the corresponding + list in the the `README`. Alphabetical order applies. +* Do note that in order for us to merge any non-trivial changes (as a rule + of thumb, additions larger than about 15 lines of code), we need an + explicit [public domain dedication][PDD] on record from you, + which you will be asked to agree to on the first commit to a repo within the organization. + Note that the agreement applies to all repos in the [Ruby RDF](https://github.com/ruby-rdf/) organization. + +## License + +This is free and unencumbered public domain software. For more information, +see or the accompanying {file:UNLICENSE} file. + +[Ruby]: https://ruby-lang.org/ +[RDF]: https://www.w3.org/RDF/ +[YARD]: https://yardoc.org/ +[YARD-GS]: https://rubydoc.info/docs/yard/file/docs/GettingStarted.md +[PDD]: https://unlicense.org/#unlicensing-contributions +[RDF.rb]: https://rubygems.org/gems/rdf +[JSON-LD gem]: https://rubygems.org/gems/json-ld +[JSON-LD]: https://www.w3.org/TR/json-ld11/ "JSON-LD 1.1" +[YAML-LD]: https://json-ld.github.io/yaml-ld/spec/ diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..08be412 --- /dev/null +++ b/Rakefile @@ -0,0 +1,15 @@ +require 'rubygems' + +task default: [ :spec ] + +namespace :gem do + desc "Build the yaml-ld-#{File.read('VERSION').chomp}.gem file" + task :build do + sh "gem build yaml-ld.gemspec && mv yaml-ld-#{File.read('VERSION').chomp}.gem pkg/" + end + + desc "Release the yaml-ld-#{File.read('VERSION').chomp}.gem file" + task :release do + sh "gem push pkg/yaml-ld-#{File.read('VERSION').chomp}.gem" + end +end diff --git a/LICENSE b/UNLICENSE similarity index 100% rename from LICENSE rename to UNLICENSE diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..8acdd82 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.0.1 diff --git a/etc/doap.nt b/etc/doap.nt new file mode 100644 index 0000000..8e78b80 --- /dev/null +++ b/etc/doap.nt @@ -0,0 +1,21 @@ + . + "YAML_LD" . + "YAML-LD support for Ruby."@en . + "YAML_LD parses and serializes YAML-LD by transforming to/from JSON-LD."@en . + . + . + . + "2022-07-19"^^ . + . + . + . + . + . + . + "Ruby" . + . + . + . + . + . + "Gregg Kellogg" . diff --git a/etc/doap.ttl b/etc/doap.ttl new file mode 100644 index 0000000..0fc4d3c --- /dev/null +++ b/etc/doap.ttl @@ -0,0 +1,31 @@ +@base . +@prefix dc: . +@prefix earl: . +@prefix doap: . +@prefix foaf: . +@prefix rdf: . +@prefix rdfs: . +@prefix xsd: . + +<> a doap:Project; + doap:name "YAML_LD"^^xsd:string; + doap:shortdesc "YAML-LD support for Ruby."@en; + doap:description "YAML_LD parses and serializes YAML-LD by transforming to/from JSON-LD."@en; + dc:creator ; + doap:blog ; + doap:bug-database ; + doap:created "2022-07-19"^^xsd:date; + doap:developer ; + doap:documenter ; + doap:homepage ; + doap:implements ; + doap:license ; + doap:maintainer ; + doap:programming-language "Ruby"; + foaf:maker . + + a foaf:Person; + rdfs:isDefinedBy ; + foaf:made <>; + foaf:mbox ; + foaf:name "Gregg Kellogg"^^xsd:string . diff --git a/etc/doap.yamlld b/etc/doap.yamlld new file mode 100644 index 0000000..aacdebc --- /dev/null +++ b/etc/doap.yamlld @@ -0,0 +1,63 @@ +--- +"@context": + dc: http://purl.org/dc/terms/ + doap: http://usefulinc.com/ns/doap# + foaf: http://xmlns.com/foaf/0.1/ + rdfs: http://www.w3.org/2000/01/rdf-schema# + xsd: http://www.w3.org/2001/XMLSchema# + dc:creator: + "@type": "@id" + doap:created: + "@type": xsd:date + doap:homepage: + "@type": "@id" + doap:bug-database: + "@type": "@id" + doap:blog: + "@type": "@id" + doap:developer: + "@type": "@id" + doap:implements: + "@type": "@id" + doap:maintainer: + "@type": "@id" + doap:documenter: + "@type": "@id" + doap:license: + "@type": "@id" + doap:description: + "@language": en + doap:shortdesc: + "@language": en + foaf:maker: + "@type": "@id" + foaf:mbox: + "@type": "@id" + foaf:made: + "@type": "@id" + rdfs:isDefinedBy: + "@type": "@id" +"@id": https://rubygems.org/gems/yaml-ld +"@type": doap:Project +doap:name: YAML_LD +doap:homepage: https://github.com/ruby-rdf/yaml-ld/ +doap:license: https://unlicense.org/1.0/ +doap:shortdesc: YAML-LD support for Ruby. +doap:description: YAML_LD parses and serializes YAML-LD by transforming to/from JSON-LD. +doap:created: '2022-07-19' +doap:programming-language: Ruby +doap:implements: +- https://json-ld.github.io/yaml-ld/spec/ +doap:bug-database: https://github.com/ruby-rdf/yaml-ld/issues +doap:blog: https://greggkellogg.net/ +doap:developer: https://greggkellogg.net/foaf#me +doap:maintainer: https://greggkellogg.net/foaf#me +doap:documenter: https://greggkellogg.net/foaf#me +foaf:maker: https://greggkellogg.net/foaf#me +dc:creator: + "@id": https://greggkellogg.net/foaf#me + "@type": foaf:Person + foaf:name: Gregg Kellogg + foaf:mbox: mailto:gregg@greggkellogg.net + foaf:made: https://rubygems.org/gems/yaml-ld + rdfs:isDefinedBy: https://greggkellogg.net/foaf diff --git a/lib/yaml_ld.rb b/lib/yaml_ld.rb new file mode 100644 index 0000000..85648db --- /dev/null +++ b/lib/yaml_ld.rb @@ -0,0 +1,37 @@ +# -*- encoding: utf-8 -*- +$:.unshift(File.expand_path("../ld", __FILE__)) +require 'rdf' # @see https://rubygems.org/gems/rdf +require 'json/ld' +require 'psych' +require 'yaml_ld/format' + +module YAML_LD + ## + # **`YAML_LD`** is a YAML-LD extension for RDF.rb. + # + # @example Requiring the `YAML_LD` module + # require 'yaml_ld' + # + # @example Parsing RDF statements from a YAML-LD file + # JSON::LD::Reader.open("etc/foaf.YAML_LD") do |reader| + # reader.each_statement do |statement| + # puts statement.inspect + # end + # end + # + # @see https://rubygems.org/gems/rdf + # @see http://www.w3.org/TR/REC-rdf-syntax/ + # + # @note Classes and module use `YAML_LD` instead of `YAML_LD`, as `Psych` squats on the `YAML` module. + # + # @author [Gregg Kellogg](http://greggkellogg.net/) + + autoload :API, 'yaml_ld/api' + autoload :Reader, 'yaml_ld/reader' + autoload :VERSION, 'yaml_ld/version' + autoload :Writer, 'yaml_ld/writer' + + # YAML-LD profiles + YAML_LD_NS = "http://www.w3.org/ns/yaml-ld#" + PROFILES = %w(extended).map {|p| YAML_LD_NS + p}.freeze +end diff --git a/lib/yaml_ld/api.rb b/lib/yaml_ld/api.rb new file mode 100644 index 0000000..48c98f9 --- /dev/null +++ b/lib/yaml_ld/api.rb @@ -0,0 +1,295 @@ +# -*- encoding: utf-8 -*- +# frozen_string_literal: true +require 'json/ld/api' + +module YAML_LD + ## + # A YAML-LD processor based on JSON-LD. + # + # @see https://www.w3.org/TR/json-ld11-api/#the-application-programming-interface + # @author [Gregg Kellogg](http://greggkellogg.net/) + class API < ::JSON::LD::API + + # The following constants are used to reduce object allocations + LINK_REL_CONTEXT = %w(rel http://www.w3.org/ns/yaml-ld#context).freeze + LINK_REL_ALTERNATE = %w(rel alternate).freeze + LINK_TYPE_JSONLD = %w(type application/ld+yaml).freeze + + ## + # Expands the given input according to the steps in the Expansion Algorithm. The input must be copied, expanded and returned if there are no errors. If the expansion fails, an appropriate exception must be thrown. + # + # The resulting `Array` either returned or yielded + # + # @param [String, #read, Hash, Array] input + # The YAML-LD object to copy and perform the expansion upon. + # @param [Proc] documentLoader + # The callback of the loader to be used to retrieve remote documents and contexts, and to parse IO objects. + # If specified, it must be used to retrieve remote documents and contexts; otherwise, if not specified, the processor's built-in loader must be used. See {documentLoader} for the method signature. + # The remote document returned must be parsed if it is YAML. + # @param [Proc] serializer (nil) + # A Serializer method used for generating the YAML serialization of the result. If absent, the internal Ruby objects are returned, which can be transformed to YAML externally via `#to_yaml`. + # See {YAML_LD::API.serializer}. + # @param [Hash{Symbol => Object}] options + # @raise [JsonLdError] + # @yield YAML_LD, base_iri + # @yieldparam [Array] yamlld + # The expanded YAML-LD document + # @yieldparam [RDF::URI + # The document base as determined during expansion + # @yieldreturn [String] returned YAML serialization + # @return [String] + # If a block is given, the result of evaluating the block is returned, otherwise, the expanded YAML-LD document + # @see https://www.w3.org/TR/json-ld11-api/#expansion-algorithm + def self.expand(input, + documentLoader: self.method(:documentLoader), + serializer: self.method(:serializer), + **options, + &block) + JSON::LD::API.expand(input, + documentLoader: documentLoader, + serializer: serializer, + **options, + &block) + end + + ## + # Compacts the given input according to the steps in the Compaction Algorithm. The input must be copied, compacted and returned if there are no errors. If the compaction fails, an appropirate exception must be thrown. + # + # If no context is provided, the input document is compacted using the top-level context of the document + # + # The resulting `Hash` is either returned or yielded, if a block is given. + # + # @param [String, #read, Hash, Array] input + # The YAML-LD object to copy and perform the expansion upon. + # @param [String, #read, Hash, Array, JSON::LD::Context] context + # The base context to use when compacting the input. + # @param [Proc] serializer (nil) + # A Serializer method used for generating the YAML serialization of the result. If absent, the internal Ruby objects are returned, which can be transformed to YAML externally via `#to_yaml`. + # See {YAML_LD::API.serializer}. + # @param [Boolean] expanded (false) Input is already expanded + # @param [Hash{Symbol => Object}] options + # @option options (see #initialize) + # @yield YAML_LD + # @yieldparam [Array] yamlld + # The expanded YAML-LD document + # @yieldreturn [String] returned YAML serialization + # @return [String] + # If a block is given, the result of evaluating the block is returned, otherwise, the expanded YAML-LD document + # @raise [JsonLdError] + # @see https://www.w3.org/TR/json-ld11-api/#compaction-algorithm + def self.compact(input, context, expanded: false, + documentLoader: self.method(:documentLoader), + serializer: self.method(:serializer), + **options, + &block) + JSON::LD::API.compact(input, context, expanded: expanded, + documentLoader: documentLoader, + serializer: serializer, + **options, + &block) + end + + ## + # This algorithm flattens an expanded YAML-LD document by collecting all properties of a node in a single object and labeling all blank nodes with blank node identifiers. This resulting uniform shape of the document, may drastically simplify the code required to process YAML-LD data in certain applications. + # + # The resulting `Array` is either returned, or yielded if a block is given. + # + # @param [String, #read, Hash, Array] input + # The YAML-LD object or array of JSON-LD objects to flatten or an IRI referencing the JSON-LD document to flatten. + # @param [String, #read, Hash, Array, JSON::LD::EvaluationContext] context + # An optional external context to use additionally to the context embedded in input when expanding the input. + # @param [Boolean] expanded (false) Input is already expanded + # @param [Proc] serializer (nil) + # A Serializer method used for generating the YAML serialization of the result. If absent, the internal Ruby objects are returned, which can be transformed to YAML externally via `#to_yaml`. + # See {YAML_LD::API.serializer}. + # @param [Hash{Symbol => Object}] options + # @option options (see #initialize) + # @option options [Boolean] :createAnnotations + # Unfold embedded nodes which can be represented using `@annotation`. + # @yield YAML_LD + # @yieldparam [Array] yamlld + # The expanded YAML-LD document + # @yieldreturn [String] returned YAML serialization + # @return [Object, Hash] + # If a block is given, the result of evaluating the block is returned, otherwise, the flattened JSON-LD document + # @see https://www.w3.org/TR/json-ld11-api/#framing-algorithm + def self.flatten(input, context, expanded: false, + documentLoader: self.method(:documentLoader), + serializer: self.method(:serializer), + **options, + &block) + JSON::LD::API.flatten(input, context, expanded: expanded, + documentLoader: documentLoader, + serializer: serializer, + **options, + &block) + end + + ## + # Frames the given input using the frame according to the steps in the Framing Algorithm. The input is used to build the framed output and is returned if there are no errors. If there are no matches for the frame, null must be returned. Exceptions must be thrown if there are errors. + # + # The resulting `Array` is either returned, or yielded if a block is given. + # + # @param [String, #read, Hash, Array] input + # The YAML-LD object or array of YAML-LD objects to flatten or an IRI referencing the JSON-LD document to flatten. + # @param [String, #read, Hash, Array] frame + # The frame to use when re-arranging the data. + # @param [Boolean] expanded (false) Input is already expanded + # @option options (see #initialize) + # @option options ['@always', '@link', '@once', '@never'] :embed ('@once') + # a flag specifying that objects should be directly embedded in the output, instead of being referred to by their IRI. + # @option options [Boolean] :explicit (false) + # a flag specifying that for properties to be included in the output, they must be explicitly declared in the framing context. + # @option options [Boolean] :requireAll (false) + # A flag specifying that all properties present in the input frame must either have a default value or be present in the JSON-LD input for the frame to match. + # @option options [Boolean] :omitDefault (false) + # a flag specifying that properties that are missing from the JSON-LD input should be omitted from the output. + # @option options [Boolean] :pruneBlankNodeIdentifiers (true) removes blank node identifiers that are only used once. + # @option options [Boolean] :omitGraph does not use `@graph` at top level unless necessary to describe multiple objects, defaults to `true` if processingMode is 1.1, otherwise `false`. + # @yield YAML_LD + # @yieldparam [Array] yamlld + # The expanded YAML-LD document + # @yieldreturn [String] returned YAML serialization + # @return [Object, Hash] + # If a block is given, the result of evaluating the block is returned, otherwise, the framed JSON-LD document + # @raise [InvalidFrame] + # @see https://www.w3.org/TR/json-ld11-api/#framing-algorithm + def self.frame(input, frame, expanded: false, + documentLoader: self.method(:documentLoader), + serializer: self.method(:serializer), + **options, + &block) + JSON::LD::API.frame(input, frame, expanded: expanded, + documentLoader: documentLoader, + serializer: serializer, + **options, + &block) + end + + ## + # Processes the input according to the RDF Conversion Algorithm, calling the provided callback for each triple generated. + # + # @param [String, #read, Hash, Array] input + # The YAML-LD object to process when outputting statements. + # @param [Boolean] expanded (false) Input is already expanded + # @option options (see #initialize) + # @option options [Boolean] :produceGeneralizedRdf (false) + # If true, output will include statements having blank node predicates, otherwise they are dropped. + # @raise [JsonLdError] + # @yield statement + # @yieldparam [RDF::Statement] statement + # @return [RDF::Enumerable] set of statements, unless a block is given. + def self.toRdf(input, expanded: false, + documentLoader: self.method(:documentLoader), + **options, + &block) + JSON::LD::API.toRdf(input, expanded: expanded, + documentLoader: documentLoader, + **options, + &block) + end + + ## + # Take an ordered list of RDF::Statements and turn them into a JSON-LD document. + # + # The resulting `Array` is either returned or yielded, if a block is given. + # + # @param [RDF::Enumerable] input + # @param [Boolean] useRdfType (false) + # If set to `true`, the JSON-LD processor will treat `rdf:type` like a normal property instead of using `@type`. + # @param [Boolean] useNativeTypes (false) use native representations + # @param [Proc] serializer (nil) + # A Serializer method used for generating the YAML serialization of the result. If absent, the internal Ruby objects are returned, which can be transformed to YAML externally via `#to_yaml`. + # See {YAML_LD::API.serializer}. + # @param [Hash{Symbol => Object}] options + # @option options (see #initialize) + # @yield jsonld + # @yieldparam [Hash] jsonld + # The JSON-LD document in expanded form + # @yieldreturn [Object] returned object + # @return [Object, Hash] + # If a block is given, the result of evaluating the block is returned, otherwise, the expanded JSON-LD document + def self.fromRdf(input, useRdfType: false, useNativeTypes: false, + documentLoader: self.method(:documentLoader), + serializer: self.method(:serializer), + **options, + &block) + JSON::LD::API.fromRdf(input, + useRdfType: useRdfType, + useNativeTypes: useNativeTypes, + documentLoader: documentLoader, + serializer: serializer, + **options, + &block) + end + + ## + # Default document loader for YAML_LD. + # @param [RDF::URI, String] url + # @param [Boolean] extractAllScripts + # If set to `true`, when extracting JSON-LD script elements from HTML, unless a specific fragment identifier is targeted, extracts all encountered JSON-LD script elements using an array form, if necessary. + # @param [String] profile + # When the resulting `contentType` is `text/html` or `application/xhtml+xml`, this option determines the profile to use for selecting a JSON-LD script elements. + # @param [String] requestProfile + # One or more IRIs to use in the request as a profile parameter. + # @param [Hash Object>] options + # @yield remote_document + # @yieldparam [RemoteDocument, RDF::Util::File::RemoteDocument] remote_document + # @raise [IOError] + def self.documentLoader(url, extractAllScripts: false, profile: nil, requestProfile: nil, **options, &block) + if url.respond_to?(:read) + base_uri = options[:base] + base_uri ||= url.base_uri if url.respond_to?(:base_uri) + content_type = options[:content_type] + content_type ||= url.content_type if url.respond_to?(:content_type) + context_url = if url.respond_to?(:links) && url.links && + # Any JSON type other than ld+json + (content_type == 'application/json' || content_type.match?(%r(application/(^ld)+json))) + link = url.links.find_link(JSON::LD::API::LINK_REL_CONTEXT) + link.href if link + elsif url.respond_to?(:links) && url.links && + # Any YAML type + content_type.match?(%r(application/(\w+\+)*yaml)) + link = url.links.find_link(LINK_REL_CONTEXT) + link.href if link + end + + content = case content_type + when nil, %r(application/(\w+\+)*yaml) + # Parse YAML + Psych.safe_load(url.read, aliases: true) + else + url.read + end + block.call(RemoteDocument.new(content, + documentUrl: base_uri, + contentType: content_type, + contextUrl: context_url)) + elsif url.to_s.match?(/\.yaml\w*$/) || content_type.to_s.match?(%r(application/(\w+\+)*yaml)) + # Parse YAML + block.call(RemoteDocument.new(Psych.load_file(url.to_s, aliases: true), + documentUrl: base_uri, + contentType: content_type, + contextUrl: context_url)) + else + RDF::Util::File.open_file(url, **options, &block) + end + end + + ## + # The default serializer for serialzing Ruby Objects to JSON. + # + # Defaults to `MultiJson.dump` + # + # @param [Object] object + # @param [Array] args + # other arguments that may be passed for some specific implementation. + # @param [Hash] options + # options passed from the invoking context. + def self.serializer(object, *args, **options) + # de-alias any objects to avoid the use of aliases and anchors + "%YAML 1.2\n" + Psych.dump(object, **options) + end + end +end + diff --git a/lib/yaml_ld/format.rb b/lib/yaml_ld/format.rb new file mode 100644 index 0000000..43f6290 --- /dev/null +++ b/lib/yaml_ld/format.rb @@ -0,0 +1,56 @@ +# -*- encoding: utf-8 -*- +# frozen_string_literal: true +module YAML_LD + ## + # YAML-LD format specification. + # + # @example Obtaining an YAML-LD format class + # RDF::Format.for(:YAML_LD) #=> YAML_LD::Format + # RDF::Format.for("etc/foaf.YAML_LD") + # RDF::Format.for(:file_name => "etc/foaf.YAML_LD") + # RDF::Format.for(file_extension: "YAML_LD") + # RDF::Format.for(:content_type => "application/ld+yaml") + # + # @example Obtaining serialization format MIME types + # RDF::Format.content_types #=> {"application/ld+yaml" => [YAML_LD::Format], + # + # @example Obtaining serialization format file extension mappings + # RDF::Format.file_extensions #=> {:YAML_LD => [YAML_LD::Format] } + # + # @see https://json-ld.github.io/yaml-ld/spec/ + # @see https://json-ld.github.io/yaml/tests/ + class Format < RDF::Format + content_type 'application/ld+yaml', + extension: :yamlld, + uri: 'http://www.w3.org/ns/formats/YAML-LD' + content_encoding 'utf-8' + + reader { YAML_LD::Reader } + writer { YAML_LD::Writer } + + ## + # Sample detection to see if it matches YAML-LD + # + # Use a text sample to detect the format of an input file. Sub-classes implement + # a matcher sufficient to detect probably format matches, including disambiguating + # between other similar formats. + # + # @param [String] sample Beginning several bytes (~ 1K) of input. + # @return [Boolean] + def self.detect(sample) + !!sample.match(/---/m) + end + + ## + # Override normal symbol generation + def self.to_sym + :yamlld + end + + ## + # Override normal format name + def self.name + "YAML-LD" + end + end +end diff --git a/lib/yaml_ld/reader.rb b/lib/yaml_ld/reader.rb new file mode 100644 index 0000000..0ac842f --- /dev/null +++ b/lib/yaml_ld/reader.rb @@ -0,0 +1,40 @@ +# -*- encoding: utf-8 -*- +require 'json/ld/reader' + +module YAML_LD + ## + # A YAML-LD parser in Ruby. + class Reader < JSON::LD::Reader + format Format + + ## + # Initializes the YAML-LD reader instance. + # + # @param [IO, File, String] input + # @param [Proc] documentLoader + # The callback of the loader to be used to retrieve remote documents and contexts, and to parse IO objects. + # If specified, it must be used to retrieve remote documents and contexts; otherwise, if not specified, the processor's built-in loader must be used. + # The remote document returned must be parsed if it is YAML. + # @param [Hash{Symbol => Object}] options + # any additional options (see `RDF::Reader#initialize` and `JSON::LD::API.initialize`) + # @yield [reader] `self` + # @yieldparam [RDF::Reader] reader + # @yieldreturn [void] ignored + # @raise [RDF::ReaderError] if the JSON document cannot be loaded + def initialize(input = $stdin, + documentLoader: YAML_LD::API.method(:documentLoader), + **options, &block) + input = StringIO.new(input) if input.is_a?(String) + super(input, documentLoader: documentLoader, **options, &block) + end + + ## + # @private + # @see RDF::Reader#each_statement + def each_statement(&block) + API.toRdf(@doc, **@options, &block) + rescue ::Psych::SyntaxError, ::JSON::LD::JsonLdError => e + log_fatal("Failed to parse input document: #{e.message}", exception: RDF::ReaderError) + end + end +end \ No newline at end of file diff --git a/lib/yaml_ld/version.rb b/lib/yaml_ld/version.rb new file mode 100644 index 0000000..a23b982 --- /dev/null +++ b/lib/yaml_ld/version.rb @@ -0,0 +1,20 @@ +# -*- encoding: utf-8 -*- +# frozen_string_literal: true +module YAML_LD::VERSION + VERSION_FILE = File.join(File.expand_path(File.dirname(__FILE__)), "..", "..", "..", "VERSION") + MAJOR, MINOR, TINY, EXTRA = File.read(VERSION_FILE).chomp.split(".") + + STRING = [MAJOR, MINOR, TINY, EXTRA].compact.join('.') + + ## + # @return [String] + def self.to_s() STRING end + + ## + # @return [String] + def self.to_str() STRING end + + ## + # @return [Array(Integer, Integer, Integer)] + def self.to_a() STRING.split(".") end +end diff --git a/lib/yaml_ld/writer.rb b/lib/yaml_ld/writer.rb new file mode 100644 index 0000000..de510f4 --- /dev/null +++ b/lib/yaml_ld/writer.rb @@ -0,0 +1,42 @@ +# -*- encoding: utf-8 -*- +require 'json/ld/writer' + +module YAML_LD + ## + # A YAML-LD serializer in Ruby. + class Writer < JSON::LD::Writer + ## + # Initializes the YAML-LD writer instance. + # + # @param [IO, File] output + # the output stream + # @param [Hash{Symbol => Object}] options + # any additional options + # @option options [Encoding] :encoding (Encoding::UTF_8) + # the encoding to use on the output stream (Ruby 1.9+) + # @option options [Boolean] :canonicalize (false) + # whether to canonicalize literals when serializing + # @option options [Hash] :prefixes ({}) + # the prefix mappings to use (not supported by all writers) + # @option options [Boolean] :standard_prefixes (false) + # Add standard prefixes to @prefixes, if necessary. + # @option options [IO, Array, Hash, String, Context] :context ({}) + # context to use when serializing. Constructed context for native serialization. + # @option options [IO, Array, Hash, String, Context] :frame ({}) + # frame to use when serializing. + # @option options [Boolean] :unique_bnodes (false) + # Use unique bnode identifiers, defaults to using the identifier which the node was originall initialized with (if any). + # @option options [Proc] serializer (YAML_LD::API.serializer) + # A Serializer method used for generating the YAML serialization of the result. + # @option options [Boolean] :stream (false) + # Do not attempt to optimize graph presentation, suitable for streaming large graphs. + # @yield [writer] `self` + # @yieldparam [RDF::Writer] writer + # @yieldreturn [void] + # @yield [writer] + # @yieldparam [RDF::Writer] writer + def initialize(output = $stdout, **options, &block) + super(output, **options.merge(serializer: YAML_LD::API.method(:serializer)), &block) + end + end +end \ No newline at end of file diff --git a/spec/api_spec.rb b/spec/api_spec.rb new file mode 100644 index 0000000..bbada1a --- /dev/null +++ b/spec/api_spec.rb @@ -0,0 +1,92 @@ + +# coding: utf-8 +require_relative 'spec_helper' + +describe YAML_LD::API do + let(:logger) {RDF::Spec.logger} + before {JSON::LD::Context::PRELOADED.clear} + + context "Test Files" do + %i(psych).each do |adapter| + Dir.glob(File.expand_path(File.join(File.dirname(__FILE__), 'test-files/*-input.*'))) do |filename| + test = File.basename(filename).sub(/-input\..*$/, '') + frame = filename.sub(/-input\..*$/, '-frame.jsonld') + framed = filename.sub(/-input\..*$/, '-framed.jsonld') + compacted = filename.sub(/-input\..*$/, '-compacted.jsonld') + context = filename.sub(/-input\..*$/, '-context.jsonld') + expanded = filename.sub(/-input\..*$/, '-expanded.jsonld') + expanded_yaml = filename.sub(/-input\..*$/, '-expanded.yamlld') + ttl = filename.sub(/-input\..*$/, '-rdf.ttl') + + context test do + around do |example| + @file = File.open(filename) + case filename + when /\.yamlld$/ + @file.define_singleton_method(:content_type) {'application/ld+yaml'} + when /.jsonld$/ + @file.define_singleton_method(:content_type) {'application/ld+json'} + end + if context + @ctx_io = File.open(context) + case context + when /\.yamlld$/ + @ctx_io.define_singleton_method(:content_type) {'application/ld+yaml'} + when /.jsonld$/ + @ctx_io.define_singleton_method(:content_type) {'application/ld+json'} + end + end + example.run + @file.close + @ctx_io.close if @ctx_io + end + + if File.exist?(expanded) + it "expands" do + options = {logger: logger, adapter: adapter} + options[:expandContext] = @ctx_io if context + yaml = described_class.expand(@file, **options) + expect(yaml).to be_a(String) + parsed_json = JSON.parse(File.read(expanded)) + expect(yaml).to produce_yamlld(parsed_json, logger) + end + end + + if File.exist?(expanded_yaml) + it "expands to YAML" do + options = {logger: logger, adapter: adapter} + options[:expandContext] = @ctx_io if context + yaml = described_class.expand(@file, **options) + expect(yaml).to be_a(String) + expect(yaml).to produce_yamlld(YAML.load_file(expanded_yaml), logger) + end + end + + if File.exist?(compacted) && File.exist?(context) + it "compacts" do + yaml = described_class.compact(@file, @ctx_io, adapter: adapter, logger: logger) + expect(yaml).to be_a(String) + parsed_json = JSON.parse(File.read(compacted)) + expect(yaml).to produce_yamlld(parsed_json, logger) + end + end + + if File.exist?(framed) && File.exist?(frame) + it "frames" do + File.open(frame) do |frame_io| + yaml = described_class.frame(@file, frame_io, adapter: adapter, logger: logger) + expect(yaml).to be_a(String) + parsed_json = JSON.parse(File.read(framed)) + expect(yaml).to produce_yamlld(parsed_json, logger) + end + end + end + + it "toRdf" do + expect(RDF::Repository.load(filename, format: :yamlld, adapter: adapter, logger: logger)).to be_equivalent_graph(RDF::Repository.load(ttl), logger: logger) + end if File.exist?(ttl) + end + end + end + end +end diff --git a/spec/compact_spec.rb b/spec/compact_spec.rb new file mode 100644 index 0000000..806c5f3 --- /dev/null +++ b/spec/compact_spec.rb @@ -0,0 +1,358 @@ +# coding: utf-8 +require_relative 'spec_helper' + +describe YAML_LD::API do + let(:logger) {RDF::Spec.logger} + + describe ".compact" do + { + "prefix" => { + input: %(--- + "@id": http://example.com/a + http://example.com/b: + "@id": http://example.com/c + ), + context: %({"ex": "http://example.com/"}), + output: %(%YAML 1.2\n--- + "@context": + ex: http://example.com/ + "@id": ex:a + ex:b: + "@id": ex:c + ) + }, + "term" => { + input: %(--- + "@id": http://example.com/a + http://example.com/b: + "@id": http://example.com/c + ), + context: %({"b": "http://example.com/b"}), + output: %(%YAML 1.2\n--- + "@context": + b: http://example.com/b + "@id": http://example.com/a + b: + "@id": http://example.com/c + ) + }, + "integer value" => { + input: %(--- + "@id": http://example.com/a + http://example.com/b: + "@value": 1 + ), + context: %({"b": "http://example.com/b"}), + output: %(%YAML 1.2\n--- + "@context": + b: http://example.com/b + "@id": http://example.com/a + b: 1 + ) + }, + "boolean value" => { + input: %(--- + "@id": http://example.com/a + http://example.com/b: + "@value": true + ), + context: %({"b": "http://example.com/b"}), + output: %(%YAML 1.2\n--- + "@context": + b: http://example.com/b + "@id": http://example.com/a + b: true + ) + }, + "@id" => { + input: %(--- + "@id": http://example.org/test#example + ), + context: {}, + output: %{%YAML 1.2 + --- {} + } + }, + "@id coercion" => { + input: %({ + "@id": "http://example.com/a", + "http://example.com/b": {"@id": "http://example.com/c"} + }), + context: %({"b": {"@id": "http://example.com/b", "@type": "@id"}}), + output: %(%YAML 1.2\n--- + "@context": + b: + "@id": http://example.com/b + "@type": "@id" + "@id": http://example.com/a + b: http://example.com/c + ) + }, + "xsd:date coercion" => { + input: %(--- + http://example.com/b: + "@value": '2012-01-04' + "@type": http://www.w3.org/2001/XMLSchema#date + ), + context: %({ + "xsd": "http://www.w3.org/2001/XMLSchema#", + "b": {"@id": "http://example.com/b", "@type": "xsd:date"} + }), + output: %(%YAML 1.2\n--- + "@context": + xsd: http://www.w3.org/2001/XMLSchema# + b: + "@id": http://example.com/b + "@type": xsd:date + b: '2012-01-04' + ) + }, + "default language" => { + input: %(--- + http://example.com/term: + - v5 + - "@value": plain literal + ), + context: %({ + "term5": {"@id": "http://example.com/term", "@language": null}, + "@language": "de" + }), + output: %(%YAML 1.2\n--- + "@context": + term5: + "@id": http://example.com/term + "@language": + "@language": de + term5: + - v5 + - plain literal + ) + }, + "default direction" => { + input: %(--- + http://example.com/term: + - v5 + - "@value": plain literal + ), + context: %({ + "term5": {"@id": "http://example.com/term", "@direction": null}, + "@direction": "ltr" + }), + output: %(%YAML 1.2\n--- + "@context": + term5: + "@id": http://example.com/term + "@direction": + "@direction": ltr + term5: + - v5 + - plain literal + ) + }, + }.each_pair do |title, params| + it(title) {run_compact(params)} + end + + context "with @type: @json" do + { + "true": { + output: %(%YAML 1.2\n--- + "@context": + "@version": 1.1 + e: + "@id": http://example.org/vocab#bool + "@type": "@json" + e: true + ), + input:%( + - http://example.org/vocab#bool: + - "@value": true + "@type": "@json" + ), + }, + "false": { + output: %(%YAML 1.2\n--- + "@context": + "@version": 1.1 + e: + "@id": http://example.org/vocab#bool + "@type": "@json" + e: false + ), + input:%( + - http://example.org/vocab#bool: + - "@value": false + "@type": "@json" + ) + }, + "double": { + output: %(%YAML 1.2\n--- + "@context": + "@version": 1.1 + e: + "@id": http://example.org/vocab#double + "@type": "@json" + e: 1.23 + ), + input:%( + - http://example.org/vocab#double: + - "@value": 1.23 + "@type": "@json" + ) + }, + "double-zero": { + output: %(%YAML 1.2\n--- + "@context": + "@version": 1.1 + e: + "@id": http://example.org/vocab#double + "@type": "@json" + e: 0.0e0 + ), + input:%( + - http://example.org/vocab#double: + - "@value": 0.0e0 + "@type": "@json" + ) + }, + "integer": { + output: %(%YAML 1.2\n--- + "@context": + "@version": 1.1 + e: + "@id": http://example.org/vocab#integer + "@type": "@json" + e: 123 + ), + input:%( + - http://example.org/vocab#integer: + - "@value": 123 + "@type": "@json" + ) + }, + "string": { + output: %(%YAML 1.2\n--- + "@context": + "@version": 1.1 + e: + "@id": http://example.org/vocab#string + "@type": "@json" + e: string + ), + input:%( + - http://example.org/vocab#string: + - "@value": string + "@type": "@json" + ) + }, + "null": { + output: %(%YAML 1.2\n--- + "@context": + "@version": 1.1 + e: + "@id": http://example.org/vocab#null + "@type": "@json" + e: + ), + input:%( + - http://example.org/vocab#null: + - "@value": null + "@type": "@json" + ) + }, + "object": { + output: %(%YAML 1.2\n--- + "@context": + "@version": 1.1 + e: + "@id": http://example.org/vocab#object + "@type": "@json" + e: + foo: bar + ), + input:%( + - http://example.org/vocab#object: + - "@value": + foo: bar + "@type": "@json" + ) + }, + "array": { + output: %(%YAML 1.2\n--- + "@context": + "@version": 1.1 + e: + "@id": http://example.org/vocab#object + "@type": "@json" + e: + - foo + - bar + ), + input:%( + - http://example.org/vocab#object: + - "@value": [foo, bar] + "@type": "@json" + ) + }, + "Already expanded object": { + output: %(%YAML 1.2\n--- + "@context": + "@version": 1.1 + http://example.org/vocab#object: + "@value": + foo: bar + "@type": "@json" + ), + input:%( + - http://example.org/vocab#object: + - "@value": {foo: bar} + "@type": "@json" + ) + }, + "Already expanded object with aliased keys": { + output: %(%YAML 1.2\n--- + "@context": + "@version": 1.1 + value: "@value" + type: "@type" + json: "@json" + http://example.org/vocab#object: + value: + foo: bar + type: json + ), + input:%( + - http://example.org/vocab#object: + - "@value": {foo: bar} + "@type": "@json" + ) + }, + }.each do |title, params| + it(title) {run_compact(processingMode: 'json-ld-1.1', **params)} + end + end + end + + def run_compact(params) + input, output, context = params[:input], params[:output], params[:context] + params[:base] ||= nil + context ||= output # Since it will have the context + input = StringIO.new(input.unindent) if input.is_a?(String) + input.define_singleton_method(:content_type) {'application/ld+yaml'} + context = Psych.safe_load(context.unindent, aliases: true) if context.is_a?(String) + context = context['@context'] if context.key?('@context') + pending params.fetch(:pending, "test implementation") unless input + if params[:exception] + expect {YAML_LD::API.compact(input, context, logger: logger, **params)}.to raise_error(params[:exception]) + else + yaml = nil + if params[:write] + expect{yaml = YAML_LD::API.compact(input, context, logger: logger, **params)}.to write(params[:write]).to(:error) + else + expect{yaml = YAML_LD::API.compact(input, context, logger: logger, **params)}.not_to write.to(:error) + end + + expect(yaml.strip).to eq output.unindent.strip + end + end +end diff --git a/spec/expand_spec.rb b/spec/expand_spec.rb new file mode 100644 index 0000000..2dffe16 --- /dev/null +++ b/spec/expand_spec.rb @@ -0,0 +1,565 @@ +# coding: utf-8 +require_relative 'spec_helper' + +describe JSON::LD::API do + let(:logger) {RDF::Spec.logger} + + describe ".expand" do + { + "empty doc": { + input: {}, + output: [] + }, + "@list coercion": { + input: %( + "@context": + foo: + "@id": http://example.com/foo + "@container": "@list" + foo: + - "@value": bar + ), + output: %( + - http://example.com/foo: + - "@list": + - "@value": bar + ) + }, + "native values in list": { + input: %( + http://example.com/foo: + "@list": + - 1 + - 2 + ), + output: %( + - http://example.com/foo: + - "@list": + - "@value": 1 + - "@value": 2 + ) + }, + "@graph": { + input: %( + "@context": + ex: http://example.com/ + "@graph": + - ex:foo: + "@value": foo + - ex:bar: + "@value": bar + ), + output: %( + - http://example.com/foo: + - "@value": foo + - http://example.com/bar: + - "@value": bar + ) + }, + "@graph value (expands to array form)": { + input: %( + "@context": + ex: http://example.com/ + ex:p: + "@id": ex:Sub1 + "@graph": + ex:q: foo + ), + output: %( + - http://example.com/p: + - "@id": http://example.com/Sub1 + "@graph": + - http://example.com/q: + - "@value": foo + ) + }, + "@type with CURIE": { + input: %( + "@context": + ex: http://example.com/ + "@type": ex:type + ), + output: %( + - "@type": + - http://example.com/type + ) + }, + "@type with CURIE and muliple values": { + input: %( + "@context": + ex: http://example.com/ + "@type": + - ex:type1 + - ex:type2 + ), + output: %( + - "@type": + - http://example.com/type1 + - http://example.com/type2 + ) + }, + "@value with false": { + input: %( + http://example.com/ex: + "@value": false + ), + output: %( + - http://example.com/ex: + - "@value": false + ) + }, + "compact IRI": { + input: %( + "@context": + ex: http://example.com/ + ex:p: + "@id": ex:Sub1 + ), + output: %( + - http://example.com/p: + - "@id": http://example.com/Sub1 + }]) + }, + }.each_pair do |title, params| + it(title) {run_expand params} + end + + context "keyword aliasing" do + { + "@id": { + input: %( + "@context": + id: "@id" + id: "" + "@type": http://www.w3.org/2000/01/rdf-schema#Resource + ), + output: %( + - "@id": "" + "@type": + - http://www.w3.org/2000/01/rdf-schema#Resource + ) + }, + "@type": { + input: %( + "@context": + type: "@type" + type: http://www.w3.org/2000/01/rdf-schema#Resource + http://example.com/foo: + "@value": bar + type: http://example.com/baz + ), + output: %( + - "@type": + - http://www.w3.org/2000/01/rdf-schema#Resource + http://example.com/foo: + - "@value": bar + "@type": http://example.com/baz + ) + }, + "@language": { + input: %( + "@context": + language: "@language" + http://example.com/foo: + "@value": bar + language: baz + ), + output: %( + - http://example.com/foo: + - "@value": bar + "@language": baz + ) + }, + "@value": { + input: %( + "@context": + literal: "@value" + http://example.com/foo: + literal: bar + ), + output: %( + - http://example.com/foo: + - "@value": bar + ) + }, + "@list": { + input: %( + "@context": + list: "@list" + http://example.com/foo: + list: + - bar + ), + output: %( + - http://example.com/foo: + - "@list": + - "@value": bar + ) + }, + }.each do |title, params| + it(title) {run_expand params} + end + end + + context "native types" do + { + "true": { + input: %( + "@context": + e: http://example.org/vocab# + e:bool: true + ), + output: %( + - http://example.org/vocab#bool: + - "@value": true + ) + }, + "false": { + input: %( + "@context": + e: http://example.org/vocab# + e:bool: false + ), + output: %( + - http://example.org/vocab#bool: + - "@value": false + ) + }, + "double": { + input: %( + "@context": + e: http://example.org/vocab# + e:double: 1.23 + ), + output: %( + - http://example.org/vocab#double: + - "@value": 1.23 + ) + }, + "double-zero": { + input: %( + "@context": + e: http://example.org/vocab# + e:double-zero: 0.0e+0 + ), + output: %(%YAML 1.2\n--- + - http://example.org/vocab#double-zero: + - "@value": 0.0e+0 + ) + }, + "integer": { + input: %( + "@context": + e: http://example.org/vocab# + e:integer: 123 + ), + output: %( + - http://example.org/vocab#integer: + - "@value": 123 + ) + }, + }.each do |title, params| + it(title) {run_expand params} + end + end + + context "with @type: @json" do + { + "true": { + input: %( + "@context": + "@version": 1.1 + e: + "@id": http://example.org/vocab#bool + "@type": "@json" + e: true + ), + output: %( + - http://example.org/vocab#bool: + - "@value": true + "@type": "@json" + ) + }, + "false": { + input: %( + "@context": + "@version": 1.1 + e: + "@id": http://example.org/vocab#bool + "@type": "@json" + e: false + ), + output: %( + - http://example.org/vocab#bool: + - "@value": false + "@type": "@json" + ) + }, + "double": { + input: %( + "@context": + "@version": 1.1 + e: + "@id": http://example.org/vocab#double + "@type": "@json" + e: 1.23 + ), + output: %( + - http://example.org/vocab#double: + - "@value": 1.23 + "@type": "@json" + ) + }, + "double-zero": { + input: %( + "@context": + "@version": 1.1 + e: + "@id": http://example.org/vocab#double + "@type": "@json" + e: 0.0e+0 + ), + output: %( + - http://example.org/vocab#double: + - "@value": 0.0e+0 + "@type": "@json" + ) + }, + "integer": { + input: %( + "@context": + "@version": 1.1 + e: + "@id": http://example.org/vocab#integer + "@type": "@json" + e: 123 + ), + output: %( + - http://example.org/vocab#integer: + - "@value": 123 + "@type": "@json" + ) + }, + "string": { + input: %( + "@context": + "@version": 1.1 + e: + "@id": http://example.org/vocab#string + "@type": "@json" + e: string + ), + output: %( + - http://example.org/vocab#string: + - "@value": string + "@type": "@json" + ) + }, + "null": { + input: %( + "@context": + "@version": 1.1 + e: + "@id": http://example.org/vocab#null + "@type": "@json" + e: null + ), + output: %( + - http://example.org/vocab#null: + - "@value": null + "@type": "@json" + ) + }, + "object": { + input: %( + "@context": + "@version": 1.1 + e: + "@id": http://example.org/vocab#object + "@type": "@json" + e: + foo: bar + ), + output: %( + - http://example.org/vocab#object: + - "@value": + foo: bar + "@type": "@json" + ) + }, + "array": { + input: %( + "@context": + "@version": 1.1 + e: + "@id": http://example.org/vocab#array + "@type": "@json" + e: + - foo: bar + ), + output: %( + - http://example.org/vocab#array: + - "@value": + - foo: bar + "@type": "@json" + ) + }, + "Does not expand terms inside json": { + input: %( + "@context": + "@version": 1.1 + e: + "@id": http://example.org/vocab#array + "@type": "@json" + e: + - e: bar + ), + output: %( + - http://example.org/vocab#array: + - "@value": + - e: bar + "@type": "@json" + ) + }, + "Already expanded object with aliased keys": { + input: %( + "@context": + "@version": 1.1 + value: "@value" + type: "@type" + json: "@json" + http://example.org/vocab#object: + - value: + foo: bar + type: json + ), + output: %( + - http://example.org/vocab#object: + - "@value": + foo: bar + "@type": "@json" + ) + }, + }.each do |title, params| + it(title) {run_expand params} + end + end + + context "@direction" do + { + "value with coerced null direction": { + input: %( + "@context": + "@direction": rtl + ex: http://example.org/vocab# + ex:ltr: + "@direction": ltr + ex:none: + "@direction": + ex:rtl: rtl + ex:ltr: ltr + ex:none: no direction + ), + output: %( + - http://example.org/vocab#rtl: + - "@value": rtl + "@direction": rtl + http://example.org/vocab#ltr: + - "@value": ltr + "@direction": ltr + http://example.org/vocab#none: + - "@value": no direction + ) + } + }.each_pair do |title, params| + it(title) {run_expand params} + end + end + + context "JSON-LD-star" do + { + "node with embedded subject without rdfstar option": { + input: %( + "@id": + "@id": ex:rei + ex:prop: value + ex:prop: value2 + ), + exception: JSON::LD::JsonLdError::InvalidIdValue + }, + "node with embedded subject with rdfstar option": { + input: %( + "@id": + "@id": ex:rei + ex:prop: value + ex:prop: value2 + ), + output: %( + - "@id": + "@id": ex:rei + ex:prop: + - "@value": value + ex:prop: + - "@value": value2 + ), + rdfstar: true + }, + "node object with @annotation property is ignored without rdfstar option": { + input: %( + "@id": ex:bob + ex:knows: + "@id": ex:fred + "@annotation": + ex:certainty: 0.8 + ), + output: %( + - "@id": ex:bob + ex:knows: + - "@id": ex:fred + ) + }, + "node object with @annotation property with rdfstar option": { + input: %( + "@id": ex:bob + ex:knows: + "@id": ex:fred + "@annotation": + ex:certainty: 0.8 + ), + output: %( + - "@id": ex:bob + ex:knows: + - "@id": ex:fred + "@annotation": + - ex:certainty: + - "@value": 0.8 + ), + rdfstar: true + }, + }.each do |title, params| + it(title) {run_expand params} + end + end + end + + def run_expand(params) + input, output = params[:input], params[:output] + params[:base] ||= nil + input = StringIO.new(input) if input.is_a?(String) + pending params.fetch(:pending, "test implementation") unless input + if params[:exception] + expect {YAML_LD::API.expand(input, **params)}.to raise_error(params[:exception]) + else + yld = nil + if params[:write] + expect{yld = YAML_LD::API.expand(input, logger: logger, **params)}.to write(params[:write]).to(:error) + else + expect{yld = YAML_LD::API.expand(input, logger: logger, **params)}.not_to write.to(:error) + end + expect(yld).to produce_yamlld(output, logger) + + # Also expect result to produce itself + expect(output).to produce_yamlld(output, logger) + end + end +end diff --git a/spec/flatten_spec.rb b/spec/flatten_spec.rb new file mode 100644 index 0000000..1f7d865 --- /dev/null +++ b/spec/flatten_spec.rb @@ -0,0 +1,225 @@ +# coding: utf-8 +require_relative 'spec_helper' + +describe JSON::LD::API do + let(:logger) {RDF::Spec.logger} + + describe ".flatten" do + { + "single object": { + input: %( + "@id": http://example.com + "@type": http://www.w3.org/2000/01/rdf-schema#Resource + ), + output: %( + - "@id": http://example.com + "@type": + - http://www.w3.org/2000/01/rdf-schema#Resource + ) + }, + "embedded object": { + input: %( + "@context": + foaf: http://xmlns.com/foaf/0.1/ + "@id": http://greggkellogg.net/foaf + "@type": http://xmlns.com/foaf/0.1/PersonalProfileDocument + foaf:primaryTopic: + - "@id": http://greggkellogg.net/foaf#me + "@type": http://xmlns.com/foaf/0.1/Person + ), + output: %( + - "@id": http://greggkellogg.net/foaf + "@type": + - http://xmlns.com/foaf/0.1/PersonalProfileDocument + http://xmlns.com/foaf/0.1/primaryTopic: + - "@id": http://greggkellogg.net/foaf#me + - "@id": http://greggkellogg.net/foaf#me + "@type": + - http://xmlns.com/foaf/0.1/Person + ) + }, + "embedded anon": { + input: %( + "@context": + foaf: http://xmlns.com/foaf/0.1/ + "@id": http://greggkellogg.net/foaf + "@type": foaf:PersonalProfileDocument + foaf:primaryTopic: + "@type": foaf:Person + ), + output: %( + - "@id": _:b0 + "@type": + - http://xmlns.com/foaf/0.1/Person + - "@id": http://greggkellogg.net/foaf + "@type": + - http://xmlns.com/foaf/0.1/PersonalProfileDocument + http://xmlns.com/foaf/0.1/primaryTopic: + - "@id": _:b0 + ) + }, + "reverse properties": { + input: %( + - "@id": http://example.com/people/markus + "@reverse": + http://xmlns.com/foaf/0.1/knows: + - "@id": http://example.com/people/dave + - "@id": http://example.com/people/gregg + http://xmlns.com/foaf/0.1/name: + "@value": Markus Lanthaler + ), + output: %( + - "@id": http://example.com/people/dave + http://xmlns.com/foaf/0.1/knows: + - "@id": http://example.com/people/markus + - "@id": http://example.com/people/gregg + http://xmlns.com/foaf/0.1/knows: + - "@id": http://example.com/people/markus + - "@id": http://example.com/people/markus + http://xmlns.com/foaf/0.1/name: + - "@value": Markus Lanthaler + ) + }, + "Simple named graph (Wikidata)": { + input: %q( + "@context": + rdf: http://www.w3.org/1999/02/22-rdf-syntax-ns# + ex: http://example.org/ + xsd: http://www.w3.org/2001/XMLSchema# + ex:locatedIn: + "@type": "@id" + ex:hasPopulaton: + "@type": xsd:integer + ex:hasReference: + "@type": "@id" + "@graph": + - "@id": http://example.org/ParisFact1 + "@type": rdf:Graph + "@graph": + "@id": http://example.org/location/Paris#this + ex:locatedIn: http://example.org/location/France#this + ex:hasReference: + - http://www.britannica.com/ + - http://www.wikipedia.org/ + - http://www.brockhaus.de/ + - "@id": http://example.org/ParisFact2 + "@type": rdf:Graph + "@graph": + "@id": http://example.org/location/Paris#this + ex:hasPopulation: 7000000 + ex:hasReference: http://www.wikipedia.org/ + ), + output: %q( + - "@id": http://example.org/ParisFact1 + "@type": + - http://www.w3.org/1999/02/22-rdf-syntax-ns#Graph + http://example.org/hasReference: + - "@id": http://www.britannica.com/ + - "@id": http://www.wikipedia.org/ + - "@id": http://www.brockhaus.de/ + "@graph": + - "@id": http://example.org/location/Paris#this + http://example.org/locatedIn: + - "@id": http://example.org/location/France#this + - "@id": http://example.org/ParisFact2 + "@type": + - http://www.w3.org/1999/02/22-rdf-syntax-ns#Graph + http://example.org/hasReference: + - "@id": http://www.wikipedia.org/ + "@graph": + - "@id": http://example.org/location/Paris#this + http://example.org/hasPopulation: + - "@value": 7000000 + ), + }, + "Test Manifest (shortened)": { + input: %q{ + "@id": '' + http://example/sequence: + "@list": + - "@id": "#t0001" + http://example/name: Keywords cannot be aliased to other keywords + http://example/input: + "@id": error-expand-0001-in.jsonld + }, + output: %q{ + - "@id": '' + http://example/sequence: + - "@list": + - "@id": "#t0001" + - "@id": "#t0001" + http://example/input: + - "@id": error-expand-0001-in.jsonld + http://example/name: + - "@value": Keywords cannot be aliased to other keywords + }, + }, + "@list with embedded object": { + input: %( + - http://example.com/foo: + - "@list": + - "@id": http://example.com/baz + http://example.com/bar: buz + ), + output: %( + - "@id": _:b0 + http://example.com/foo: + - "@list": + - "@id": http://example.com/baz + - "@id": http://example.com/baz + http://example.com/bar: + - "@value": buz + ) + }, + "coerced @list containing mixed list values": { + input: %( + "@context": + foo: + "@id": http://example.com/foo + "@container": "@list" + foo: + - - "@id": http://example/a + "@type": http://example/Bar + - "@id": http://example/b + "@type": http://example/Baz + ), + output: %( + - "@id": _:b0 + http://example.com/foo: + - "@list": + - "@list": + - "@id": http://example/a + - "@id": http://example/b + - "@id": http://example/a + "@type": + - http://example/Bar + - "@id": http://example/b + "@type": + - http://example/Baz + ) + }, + }.each do |title, params| + it(title) {run_flatten(params)} + end + end + + def run_flatten(params) + input, output, context = params[:input], params[:output], params[:context] + input = StringIO.new(input) if input.is_a?(String) + context = ::JSON.parse(context) if context.is_a?(String) + params[:base] ||= nil + pending params.fetch(:pending, "test implementation") unless input + if params[:exception] + expect {YAML_LD::API.flatten(input, context, logger: logger, **params)}.to raise_error(params[:exception]) + else + yld = nil + if params[:write] + expect{yld = YAML_LD::API.flatten(input, context, logger: logger, **params)}.to write(params[:write]).to(:error) + else + expect{yld = YAML_LD::API.flatten(input, context, logger: logger, **params)}.not_to write.to(:error) + end + + expect(yld).to produce_yamlld(output, logger) + end + end +end diff --git a/spec/format_spec.rb b/spec/format_spec.rb new file mode 100644 index 0000000..3937edd --- /dev/null +++ b/spec/format_spec.rb @@ -0,0 +1,54 @@ +# coding: utf-8 +require_relative 'spec_helper' +require 'rdf/spec/format' + +describe YAML_LD::Format do + it_behaves_like 'an RDF::Format' do + let(:format_class) {YAML_LD::Format} + end + + describe ".for" do + [ + :yamlld, + "etc/doap.yamlld", + {file_name: 'etc/doap.yamlld'}, + {file_extension: 'yamlld'}, + {content_type: 'application/ld+yaml'}, + ].each do |arg| + it "discovers with #{arg.inspect}" do + expect(RDF::Format.for(arg)).to eq described_class + end + end + + { + yamlld: %(--- + "@context": "foo" + ), + context: %(--- + "@context": { + ), + id: %(--- + "@id": "foo" + ), + type: %(--- + "@type": "foo" + ), + }.each do |sym, str| + it "detects #{sym}" do + expect(described_class.for {str}).to eq described_class + end + end + + it "should discover 'yamlld'" do + expect(RDF::Format.for(:yamlld).reader).to eq YAML_LD::Reader + end + end + + describe "#to_sym" do + specify {expect(described_class.to_sym).to eq :yamlld} + end + + describe "#to_uri" do + specify {expect(described_class.to_uri).to eq RDF::URI('http://www.w3.org/ns/formats/YAML-LD')} + end +end diff --git a/spec/frame_spec.rb b/spec/frame_spec.rb new file mode 100644 index 0000000..d471243 --- /dev/null +++ b/spec/frame_spec.rb @@ -0,0 +1,662 @@ +# coding: utf-8 +require_relative 'spec_helper' + +describe YAML_LD::API do + let(:logger) {RDF::Spec.logger} + + describe ".frame" do + { + "exact @type match": { + frame: %( + "@context": + ex: http://example.org/ + "@type": ex:Type1 + ), + input: %( + - "@context": + ex: http://example.org/ + "@id": ex:Sub1 + "@type": ex:Type1 + - "@context": + ex: http://example.org/ + "@id": ex:Sub2 + "@type": ex:Type2 + ), + output: %(--- + "@context": + ex: http://example.org/ + "@graph": + - "@id": ex:Sub1 + "@type": ex:Type1 + ) + }, + "wildcard @type match": { + frame: %( + "@context": + ex: http://example.org/ + "@type": {} + ), + input: %( + - "@context": + ex: http://example.org/ + "@id": ex:Sub1 + "@type": ex:Type1 + - "@context": + ex: http://example.org/ + "@id": ex:Sub2 + "@type": ex:Type2 + ), + output: %( + "@context": + ex: http://example.org/ + "@graph": + - "@id": ex:Sub1 + "@type": ex:Type1 + - "@id": ex:Sub2 + "@type": ex:Type2 + ) + }, + "match none @type match": { + frame: %( + "@context": + ex: http://example.org/ + "@type": [] + ), + input: %( + - "@context": + ex: http://example.org/ + "@id": ex:Sub1 + "@type": ex:Type1 + ex:p: Foo + - "@context": + ex: http://example.org/ + "@id": ex:Sub2 + ex:p: Bar + ), + output: %( + "@context": + ex: http://example.org/ + "@graph": + - "@id": ex:Sub2 + ex:p: Bar + ) + }, + "multiple matches on @type": { + frame: %( + "@context": + ex: http://example.org/ + "@type": ex:Type1 + ), + input: %( + - "@context": + ex: http://example.org/ + "@id": ex:Sub1 + "@type": ex:Type1 + - "@context": + ex: http://example.org/ + "@id": ex:Sub2 + "@type": ex:Type1 + - "@context": + ex: http://example.org/ + "@id": ex:Sub3 + "@type": + - ex:Type1 + - ex:Type2 + ), + output: %( + "@context": + ex: http://example.org/ + "@graph": + - "@id": ex:Sub1 + "@type": ex:Type1 + - "@id": ex:Sub2 + "@type": ex:Type1 + - "@id": ex:Sub3 + "@type": + - ex:Type1 + - ex:Type2 + ) + }, + "single @id match": { + frame: %( + "@context": + ex: http://example.org/ + "@id": ex:Sub1 + ), + input: %( + - "@context": + ex: http://example.org/ + "@id": ex:Sub1 + "@type": ex:Type1 + - "@context": + ex: http://example.org/ + "@id": ex:Sub2 + "@type": ex:Type2 + ), + output: %( + "@context": + ex: http://example.org/ + "@graph": + - "@id": ex:Sub1 + "@type": ex:Type1 + ) + }, + "multiple @id match": { + frame: %( + "@context": + ex: http://example.org/ + "@id": + - ex:Sub1 + - ex:Sub2 + ), + input: %( + - "@context": + ex: http://example.org/ + "@id": ex:Sub1 + "@type": ex:Type1 + - "@context": + ex: http://example.org/ + "@id": ex:Sub2 + "@type": ex:Type2 + - "@context": + ex: http://example.org/ + "@id": ex:Sub3 + "@type": ex:Type3 + ), + output: %( + "@context": + ex: http://example.org/ + "@graph": + - "@id": ex:Sub1 + "@type": ex:Type1 + - "@id": ex:Sub2 + "@type": ex:Type2 + ) + }, + "wildcard and match none": { + frame: %( + "@context": + ex: http://example.org/ + ex:p: [] + ex:q: {} + ), + input: %( + - "@context": + ex: http://example.org/ + "@id": ex:Sub1 + ex:q: bar + - "@context": + ex: http://example.org/ + "@id": ex:Sub2 + ex:p: foo + ex:q: bar + ), + output: %( + "@context": + ex: http://example.org/ + "@graph": + - "@id": ex:Sub1 + ex:p: + ex:q: bar + ) + }, + "match on any property if @requireAll is false": { + frame: %( + "@context": + ex: http://example.org/ + "@requireAll": false + ex:p: {} + ex:q: {} + ), + input: %( + - "@context": + ex: http://example.org/ + "@id": ex:Sub1 + ex:p: foo + - "@context": + ex: http://example.org/ + "@id": ex:Sub2 + ex:q: bar + ), + output: %( + "@context": + ex: http://example.org/ + "@graph": + - "@id": ex:Sub1 + ex:p: foo + ex:q: + - "@id": ex:Sub2 + ex:p: + ex:q: bar + ) + }, + "match on defeaults if @requireAll is true and at least one property matches": { + frame: %( + "@context": + ex: http://example.org/ + "@requireAll": true + ex:p: + "@default": Foo + ex:q: + "@default": Bar + ), + input: %( + - "@context": + ex: http://example.org/ + "@id": ex:Sub1 + ex:p: foo + - "@context": + ex: http://example.org/ + "@id": ex:Sub2 + ex:q: bar + - "@context": + ex: http://example.org/ + "@id": ex:Sub3 + ex:p: foo + ex:q: bar + - "@context": + ex: http://example.org/ + "@id": ex:Sub4 + ex:r: baz + ), + output: %( + "@context": + ex: http://example.org/ + "@graph": + - "@id": ex:Sub1 + ex:p: foo + ex:q: Bar + - "@id": ex:Sub2 + ex:p: Foo + ex:q: bar + - "@id": ex:Sub3 + ex:p: foo + ex:q: bar + ) + }, + "issue #40 - example": { + frame: %( + "@context": + "@version": 1.1 + "@vocab": https://schema.org/ + "@type": Person + "@requireAll": true + givenName: John + familyName: Doe + ), + input: %( + "@context": + "@version": 1.1 + "@vocab": https://schema.org/ + "@graph": + - "@id": '1' + "@type": Person + name: John Doe + givenName: John + familyName: Doe + - "@id": '2' + "@type": Person + name: Jane Doe + givenName: Jane + ), + output: %( + "@context": + "@version": 1.1 + "@vocab": https://schema.org/ + "@id": '1' + "@type": Person + familyName: Doe + givenName: John + name: John Doe + ), + processingMode: 'json-ld-1.1' + }, + "mixed content": { + frame: %( + "@context": + ex: http://example.org/ + ex:mixed: + "@embed": "@never" + ), + input: %( + "@context": + ex: http://example.org/ + "@id": ex:Sub1 + ex:mixed: + - "@id": ex:Sub2 + - literal1 + ), + output: %( + "@context": + ex: http://example.org/ + "@graph": + - "@id": ex:Sub1 + ex:mixed: + - "@id": ex:Sub2 + - literal1 + ) + }, + "framed list": { + frame: %( + "@context": + ex: http://example.org/ + list: + "@id": ex:list + "@container": "@list" + list: + - "@type": ex:Element + ), + input: %( + "@context": + ex: http://example.org/ + list: + "@id": ex:list + "@container": "@list" + "@id": ex:Sub1 + "@type": ex:Type1 + list: + - "@id": ex:Sub2 + "@type": ex:Element + - literal1 + ), + output: %( + "@context": + ex: http://example.org/ + list: + "@id": ex:list + "@container": "@list" + "@graph": + - "@id": ex:Sub1 + "@type": ex:Type1 + list: + - "@id": ex:Sub2 + "@type": ex:Element + - literal1 + ) + }, + "presentation example": { + frame: %( + "@context": + primaryTopic: + "@id": http://xmlns.com/foaf/0.1/primaryTopic + "@type": "@id" + sameAs: + "@id": http://www.w3.org/2002/07/owl#sameAs + "@type": "@id" + primaryTopic: + "@type": http://dbpedia.org/class/yago/Buzzwords + sameAs: {} + ), + input: %( + - "@id": http://en.wikipedia.org/wiki/Linked_Data + http://xmlns.com/foaf/0.1/primaryTopic: + "@id": http://dbpedia.org/resource/Linked_Data + - "@id": http://www4.wiwiss.fu-berlin.de/flickrwrappr/photos/Linked_Data + http://www.w3.org/2002/07/owl#sameAs: + "@id": http://dbpedia.org/resource/Linked_Data + - "@id": http://dbpedia.org/resource/Linked_Data + "@type": http://dbpedia.org/class/yago/Buzzwords + http://www.w3.org/2002/07/owl#sameAs: + "@id": http://rdf.freebase.com/ns/m/02r2kb1 + - "@id": http://mpii.de/yago/resource/Linked_Data + http://www.w3.org/2002/07/owl#sameAs: + "@id": http://dbpedia.org/resource/Linked_Data + ), + output: %( + "@context": + primaryTopic: + "@id": http://xmlns.com/foaf/0.1/primaryTopic + "@type": "@id" + sameAs: + "@id": http://www.w3.org/2002/07/owl#sameAs + "@type": "@id" + "@graph": + - "@id": http://en.wikipedia.org/wiki/Linked_Data + primaryTopic: + "@id": http://dbpedia.org/resource/Linked_Data + "@type": http://dbpedia.org/class/yago/Buzzwords + sameAs: http://rdf.freebase.com/ns/m/02r2kb1 + ) + }, + "library": { + frame: %( + "@context": + dc: http://purl.org/dc/elements/1.1/ + ex: http://example.org/vocab# + xsd: http://www.w3.org/2001/XMLSchema# + ex:contains: + "@type": "@id" + "@type": ex:Library + ex:contains: {} + ), + input: %( + "@context": + dc: http://purl.org/dc/elements/1.1/ + ex: http://example.org/vocab# + xsd: http://www.w3.org/2001/XMLSchema# + "@id": http://example.org/library + "@type": ex:Library + dc:name: Library + ex:contains: + "@id": http://example.org/library/the-republic + "@type": ex:Book + dc:creator: Plato + dc:title: The Republic + ex:contains: + "@id": http://example.org/library/the-republic#introduction + "@type": ex:Chapter + dc:description: An introductory chapter on The Republic. + dc:title: The Introduction + ), + output: %( + "@context": + dc: http://purl.org/dc/elements/1.1/ + ex: http://example.org/vocab# + xsd: http://www.w3.org/2001/XMLSchema# + ex:contains: + "@type": "@id" + "@graph": + - "@id": http://example.org/library + "@type": ex:Library + dc:name: Library + ex:contains: + "@id": http://example.org/library/the-republic + "@type": ex:Book + dc:creator: Plato + dc:title: The Republic + ex:contains: + "@id": http://example.org/library/the-republic#introduction + "@type": ex:Chapter + dc:description: An introductory chapter on The Republic. + dc:title: The Introduction + ) + } + }.each do |title, params| + it title do + do_frame(params) + end + end + + describe "@reverse" do + { + "embed matched frames with @reverse": { + frame: %( + "@context": + ex: http://example.org/ + "@type": ex:Type1 + "@reverse": + ex:includes: {} + ), + input: %( + - "@context": + ex: http://example.org/ + "@id": ex:Sub1 + "@type": ex:Type1 + - "@context": + ex: http://example.org/ + "@id": ex:Sub2 + "@type": ex:Type2 + ex:includes: + "@id": ex:Sub1 + ), + output: %( + "@context": + ex: http://example.org/ + "@graph": + - "@id": ex:Sub1 + "@type": ex:Type1 + "@reverse": + ex:includes: + "@id": ex:Sub2 + "@type": ex:Type2 + ex:includes: + "@id": ex:Sub1 + ) + }, + "embed matched frames with reversed property": { + frame: %( + "@context": + ex: http://example.org/ + excludes: + "@reverse": ex:includes + "@type": ex:Type1 + excludes: {} + ), + input: %( + - "@context": + ex: http://example.org/ + "@id": ex:Sub1 + "@type": ex:Type1 + - "@context": + ex: http://example.org/ + "@id": ex:Sub2 + "@type": ex:Type2 + ex:includes: + "@id": ex:Sub1 + ), + output: %( + "@context": + ex: http://example.org/ + excludes: + "@reverse": ex:includes + "@graph": + - "@id": ex:Sub1 + "@type": ex:Type1 + excludes: + "@id": ex:Sub2 + "@type": ex:Type2 + ex:includes: + "@id": ex:Sub1 + ) + }, + }.each do |title, params| + it title do + do_frame(params) + end + end + end + + context "omitGraph option" do + { + "Defaults to false in 1.0": { + input: %( + - http://example.org/prop: + - "@value": value + http://example.org/foo: + - "@value": bar + ), + frame: %( + "@context": + "@vocab": http://example.org/ + ), + output: %( + "@context": + "@vocab": http://example.org/ + "@graph": + - foo: bar + prop: value + ), + processingMode: "json-ld-1.0" + }, + "Set with option in 1.0": { + input: %( + - http://example.org/prop: + - "@value": value + http://example.org/foo: + - "@value": bar + ), + frame: %( + "@context": + "@vocab": http://example.org/ + ), + output: %( + "@context": + "@vocab": http://example.org/ + foo: bar + prop: value + ), + processingMode: "json-ld-1.0", + omitGraph: true + }, + "Defaults to true in 1.1": { + input: %( + - http://example.org/prop: + - "@value": value + http://example.org/foo: + - "@value": bar + ), + frame: %( + "@context": + "@vocab": http://example.org/ + ), + output: %( + "@context": + "@vocab": http://example.org/ + foo: bar + prop: value + ), + processingMode: "json-ld-1.1" + }, + "Set with option in 1.1": { + input: %( + - http://example.org/prop: + - "@value": value + http://example.org/foo: + - "@value": bar + ), + frame: %( + "@context": + "@vocab": http://example.org/ + ), + output: %( + "@context": + "@vocab": http://example.org/ + "@graph": + - foo: bar + prop: value + ), + processingMode: "json-ld-1.1", + omitGraph: false + }, + }.each do |title, params| + it(title) {do_frame(params.merge(pruneBlankNodeIdentifiers: true))} + end + end + end + + def do_frame(params) + begin + input, frame, output = params[:input], params[:frame], params[:output] + params = {processingMode: 'json-ld-1.0'}.merge(params) + input = StringIO.new(input) if input.is_a?(String) + frame = StringIO.new(frame) if frame.is_a?(String) + yld = nil + if params[:write] + expect{yld = YAML_LD::API.frame(input, frame, logger: logger, **params)}.to write(params[:write]).to(:error) + else + expect{yld = YAML_LD::API.frame(input, frame, logger: logger, **params)}.not_to write.to(:error) + end + expect(yld).to produce_yamlld(output, logger) + end + end +end diff --git a/spec/from_rdf_spec.rb b/spec/from_rdf_spec.rb new file mode 100644 index 0000000..851c133 --- /dev/null +++ b/spec/from_rdf_spec.rb @@ -0,0 +1,730 @@ +# coding: utf-8 +require_relative 'spec_helper' +require 'rdf/spec/writer' + +describe YAML_LD::API do + let(:logger) {RDF::Spec.logger} + + describe ".fromRdf" do + context "simple tests" do + it "One subject IRI object" do + input = %( .) + expect(serialize(input)).to produce_yamlld(%( + - "@id": http://a/b + http://a/c: + - "@id": http://a/d + ), logger) + end + + it "should generate object list" do + input = %(@prefix : . :b :c :d, :e .) + expect(serialize(input)). + to produce_yamlld(%( + - "@id": http://example.com/b + http://example.com/c: + - "@id": http://example.com/d + - "@id": http://example.com/e + ), logger) + end + + it "should generate property list" do + input = %(@prefix : . :b :c :d; :e :f .) + expect(serialize(input)). + to produce_yamlld(%( + - "@id": http://example.com/b + http://example.com/c: + - "@id": http://example.com/d + http://example.com/e: + - "@id": http://example.com/f + ), logger) + end + + it "serializes multiple subjects" do + input = %q( + @prefix : . + @prefix dc: . + a :TestCase . + a :TestCase . + ) + expect(serialize(input)). + to produce_yamlld(%( + - "@id": test-cases/0001 + "@type": + - http://www.w3.org/2006/03/test-description#TestCase + - "@id": test-cases/0002 + "@type": + - http://www.w3.org/2006/03/test-description#TestCase + ), logger) + end + end + + context "literals" do + context "coercion" do + it "typed literal" do + input = %(@prefix ex: . ex:a ex:b "foo"^^ex:d .) + expect(serialize(input)).to produce_yamlld(%( + - "@id": http://example.com/a + http://example.com/b: + - "@value": foo + "@type": http://example.com/d + ), logger) + end + + it "integer" do + input = %(@prefix ex: . ex:a ex:b 1 .) + expect(serialize(input, useNativeTypes: true)).to produce_yamlld(%( + - "@id": http://example.com/a + http://example.com/b: + - "@value": 1 + ), logger) + end + + it "integer (non-native)" do + input = %(@prefix ex: . ex:a ex:b 1 .) + expect(serialize(input, useNativeTypes: false)).to produce_yamlld(%( + - "@id": http://example.com/a + http://example.com/b: + - "@value": '1' + "@type": http://www.w3.org/2001/XMLSchema#integer + ), logger) + end + + it "boolean" do + input = %(@prefix ex: . ex:a ex:b true .) + expect(serialize(input, useNativeTypes: true)).to produce_yamlld(%( + - "@id": http://example.com/a + http://example.com/b: + - "@value": true + ), logger) + end + + it "boolean (non-native)" do + input = %(@prefix ex: . ex:a ex:b true .) + expect(serialize(input, useNativeTypes: false)).to produce_yamlld(%( + - "@id": http://example.com/a + http://example.com/b: + - "@value": 'true' + "@type": http://www.w3.org/2001/XMLSchema#boolean + ), logger) + end + + it "decmal" do + input = %(@prefix ex: . ex:a ex:b 1.0 .) + expect(serialize(input, useNativeTypes: true)).to produce_yamlld(%( + - "@id": http://example.com/a + http://example.com/b: + - "@value": '1.0' + "@type": http://www.w3.org/2001/XMLSchema#decimal + ), logger) + end + + it "double" do + input = %(@prefix ex: . ex:a ex:b 1.0e0 .) + expect(serialize(input, useNativeTypes: true)).to produce_yamlld(%( + - "@id": http://example.com/a + http://example.com/b: + - "@value": 1.0 + ), logger) + end + + it "double (non-native)" do + input = %(@prefix ex: . ex:a ex:b 1.0e0 .) + expect(serialize(input, useNativeTypes: false)).to produce_yamlld(%( + - "@id": http://example.com/a + http://example.com/b: + - "@value": 1.0E0 + "@type": http://www.w3.org/2001/XMLSchema#double + ), logger) + end + end + + context "datatyped (non-native)" do + { + integer: 1, + unsignedInteger: 1, + nonNegativeInteger: 1, + float: 1, + nonPositiveInteger: -1, + negativeInteger: -1, + }.each do |t, v| + it "#{t}" do + input = %( + @prefix xsd: . + @prefix ex: . + ex:a ex:b "#{v}"^^xsd:#{t} . + ) + expect(serialize(input, useNativeTypes: false)).to produce_yamlld(%( + - "@id": http://example.com/a + http://example.com/b: + - "@value": "#{v}" + "@type": http://www.w3.org/2001/XMLSchema##{t} + ), logger) + end + end + end + + it "encodes language literal" do + input = %(@prefix ex: . ex:a ex:b "foo"@en-us .) + expect(serialize(input)).to produce_yamlld(%( + - "@id": http://example.com/a + http://example.com/b: + - "@value": foo + "@language": en-us + ), logger) + end + + context "with @type: @json" do + { + "true": { + output: %( + - "@id": http://example.org/vocab#id + http://example.org/vocab#bool: + - "@value": true + "@type": "@json" + ), + input:%( + @prefix ex: . + @prefix rdf: . + ex:id ex:bool "true"^^rdf:JSON . + ) + }, + "false": { + output: %( + - "@id": http://example.org/vocab#id + http://example.org/vocab#bool: + - "@value": false + "@type": "@json" + ), + input: %( + @prefix ex: . + @prefix rdf: . + ex:id ex:bool "false"^^rdf:JSON . + ) + }, + "double": { + output: %( + - "@id": http://example.org/vocab#id + http://example.org/vocab#double: + - "@value": 1.23 + "@type": "@json" + ), + input: %( + @prefix ex: . + @prefix rdf: . + ex:id ex:double "1.23E0"^^rdf:JSON . + ) + }, + "double-zero": { + output: %( + - "@id": http://example.org/vocab#id + http://example.org/vocab#double: + - "@value": 0 + "@type": "@json" + ), + input: %( + @prefix ex: . + @prefix rdf: . + ex:id ex:double "0.0E0"^^rdf:JSON . + ) + }, + "integer": { + output: %( + - "@id": http://example.org/vocab#id + http://example.org/vocab#integer: + - "@value": 123 + "@type": "@json" + ), + input: %( + @prefix ex: . + @prefix rdf: . + ex:id ex:integer "123"^^rdf:JSON . + ) + }, + "string": { + output: %( + - "@id": http://example.org/vocab#id + http://example.org/vocab#string: + - "@value": string + "@type": "@json" + ), + input: %( + @prefix ex: . + @prefix rdf: . + ex:id ex:string "\\"string\\""^^rdf:JSON . + ) + }, + "null": { + output: %( + - "@id": http://example.org/vocab#id + http://example.org/vocab#null: + - "@value": + "@type": "@json" + ), + input: %( + @prefix ex: . + @prefix rdf: . + ex:id ex:null "null"^^rdf:JSON . + ) + }, + "object": { + output: %( + - "@id": http://example.org/vocab#id + http://example.org/vocab#object: + - "@value": + foo: bar + "@type": "@json" + ), + input: %( + @prefix ex: . + @prefix rdf: . + ex:id ex:object """{"foo":"bar"}"""^^rdf:JSON . + ) + }, + "array": { + output: %( + - "@id": http://example.org/vocab#id + http://example.org/vocab#array: + - "@value": + - foo: bar + "@type": "@json" + ), + input: %( + @prefix ex: . + @prefix rdf: . + ex:id ex:array """[{"foo":"bar"}]"""^^rdf:JSON . + ) + }, + }.each do |title, params| + params[:input] = RDF::Graph.new << RDF::Turtle::Reader.new(params[:input]) + it(title) {do_fromRdf(processingMode: "json-ld-1.1", **params)} + end + end + end + + context "@direction" do + context "rdfDirection: null" do + { + "no language rtl datatype": { + input: %q( + "no language"^^ . + ), + output: %q( + - "@id": http://example.com/a + http://example.org/label: + - "@value": no language + "@type": https://www.w3.org/ns/i18n#_rtl + ), + }, + "no language rtl compound-literal": { + input: %q( + @prefix rdf: . + _:cl1 . + + _:cl1 rdf:value "no language"; + rdf:direction "rtl" . + ), + output: %q( + - "@id": http://example.com/a + http://example.org/label: + - "@id": _:cl1 + - "@id": _:cl1 + http://www.w3.org/1999/02/22-rdf-syntax-ns#value: + - "@value": no language + http://www.w3.org/1999/02/22-rdf-syntax-ns#direction: + - "@value": rtl + ), + }, + "en-US rtl datatype": { + input: %q( + "en-US"^^ . + ), + output: %q( + - "@id": http://example.com/a + http://example.org/label: + - "@value": en-US + "@type": https://www.w3.org/ns/i18n#en-us_rtl + ), + }, + "en-US rtl compound-literal": { + input: %q( + @prefix rdf: . + _:cl1 . + + _:cl1 rdf:value "en-US"; + rdf:language "en-us"; + rdf:direction "rtl" . + ), + output: %q( + - "@id": http://example.com/a + http://example.org/label: + - "@id": _:cl1 + - "@id": _:cl1 + http://www.w3.org/1999/02/22-rdf-syntax-ns#value: + - "@value": en-US + http://www.w3.org/1999/02/22-rdf-syntax-ns#language: + - "@value": en-us + http://www.w3.org/1999/02/22-rdf-syntax-ns#direction: + - "@value": rtl + ), + } + }.each_pair do |name, params| + it name do + do_fromRdf(params.merge(reader: RDF::Turtle::Reader, rdfDirection: nil)) + end + end + end + + context "rdfDirection: i18n-datatype" do + { + "no language rtl datatype": { + input: %q( + "no language"^^ . + ), + output: %q( + - "@id": http://example.com/a + http://example.org/label: + - "@value": no language + "@direction": rtl + ), + }, + "no language rtl compound-literal": { + input: %q( + @prefix rdf: . + _:cl1 . + + _:cl1 rdf:value "no language"; + rdf:direction "rtl" . + ), + output: %q( + - "@id": http://example.com/a + http://example.org/label: + - "@id": _:cl1 + - "@id": _:cl1 + http://www.w3.org/1999/02/22-rdf-syntax-ns#value: + - "@value": no language + http://www.w3.org/1999/02/22-rdf-syntax-ns#direction: + - "@value": rtl + ), + }, + "en-US rtl datatype": { + input: %q( + "en-US"^^ . + ), + output: %q( + - "@id": http://example.com/a + http://example.org/label: + - "@value": en-US + "@language": en-US + "@direction": rtl + ), + }, + "en-US rtl compound-literal": { + input: %q( + @prefix rdf: . + _:cl1 . + + _:cl1 rdf:value "en-US"; + rdf:language "en-US"; + rdf:direction "rtl" . + ), + output: %q( + - "@id": http://example.com/a + http://example.org/label: + - "@id": _:cl1 + - "@id": _:cl1 + http://www.w3.org/1999/02/22-rdf-syntax-ns#value: + - "@value": en-US + http://www.w3.org/1999/02/22-rdf-syntax-ns#language: + - "@value": en-US + http://www.w3.org/1999/02/22-rdf-syntax-ns#direction: + - "@value": rtl + ), + } + }.each_pair do |name, params| + it name do + do_fromRdf(params.merge(reader: RDF::Turtle::Reader, rdfDirection: 'i18n-datatype', processingMode: 'json-ld-1.1')) + end + end + end + + context "rdfDirection: compound-literal" do + { + "no language rtl datatype": { + input: %q( + "no language"^^ . + ), + output: %q( + - "@id": http://example.com/a + http://example.org/label: + - "@value": no language + "@type": https://www.w3.org/ns/i18n#_rtl + ), + }, + "no language rtl compound-literal": { + input: %q( + @prefix rdf: . + _:cl1 . + + _:cl1 rdf:value "no language"; + rdf:direction "rtl" . + ), + output: %q( + - "@id": http://example.com/a + http://example.org/label: + - "@value": no language + "@direction": rtl + ), + }, + "en-US rtl datatype": { + input: %q( + "en-US"^^ . + ), + output: %q( + - "@id": http://example.com/a + http://example.org/label: + - "@value": en-US + "@type": https://www.w3.org/ns/i18n#en-us_rtl + ), + }, + "en-US rtl compound-literal": { + input: %q( + @prefix rdf: . + _:cl1 . + + _:cl1 rdf:value "en-US"; + rdf:language "en-us"; + rdf:direction "rtl" . + ), + output: %q( + - "@id": http://example.com/a + http://example.org/label: + - "@value": en-US + "@language": en-us + "@direction": rtl + ), + } + }.each_pair do |name, params| + it name do + do_fromRdf(params.merge(reader: RDF::Turtle::Reader, rdfDirection: 'compound-literal', processingMode: 'json-ld-1.1')) + end + end + end + end + + context "RDF-star" do + { + "subject-iii": { + input: RDF::Statement( + RDF::Statement( + RDF::URI('http://example/s1'), + RDF::URI('http://example/p1'), + RDF::URI('http://example/o1')), + RDF::URI('http://example/p'), + RDF::URI('http://example/o')), + output: %( + - "@id": + "@id": http://example/s1 + http://example/p1: + - "@id": http://example/o1 + http://example/p: + - "@id": http://example/o + ) + }, + "subject-iib": { + input: RDF::Statement( + RDF::Statement( + RDF::URI('http://example/s1'), + RDF::URI('http://example/p1'), + RDF::Node.new('o1')), + RDF::URI('http://example/p'), + RDF::URI('http://example/o')), + output: %( + - "@id": + "@id": http://example/s1 + http://example/p1: + - "@id": _:o1 + http://example/p: + - "@id": http://example/o + ) + }, + "subject-iil": { + input: RDF::Statement( + RDF::Statement( + RDF::URI('http://example/s1'), + RDF::URI('http://example/p1'), + RDF::Literal('o1')), + RDF::URI('http://example/p'), + RDF::URI('http://example/o')), + output: %( + - "@id": + "@id": http://example/s1 + http://example/p1: + - "@value": o1 + http://example/p: + - "@id": http://example/o + ) + }, + "subject-bii": { + input: RDF::Statement( + RDF::Statement( + RDF::Node('s1'), + RDF::URI('http://example/p1'), + RDF::URI('http://example/o1')), + RDF::URI('http://example/p'), + RDF::URI('http://example/o')), + output: %( + - "@id": + "@id": _:s1 + http://example/p1: + - "@id": http://example/o1 + http://example/p: + - "@id": http://example/o + ) + }, + "subject-bib": { + input: RDF::Statement( + RDF::Statement( + RDF::Node('s1'), + RDF::URI('http://example/p1'), + RDF::Node.new('o1')), + RDF::URI('http://example/p'), RDF::URI('http://example/o')), + output: %( + - "@id": + "@id": _:s1 + http://example/p1: + - "@id": _:o1 + http://example/p: + - "@id": http://example/o + ) + }, + "subject-bil": { + input: RDF::Statement( + RDF::Statement( + RDF::Node('s1'), + RDF::URI('http://example/p1'), + RDF::Literal('o1')), + RDF::URI('http://example/p'), + RDF::URI('http://example/o')), + output: %( + - "@id": + "@id": _:s1 + http://example/p1: + - "@value": o1 + http://example/p: + - "@id": http://example/o + ) + }, + "object-iii": { + input: RDF::Statement( + RDF::URI('http://example/s'), + RDF::URI('http://example/p'), + RDF::Statement( + RDF::URI('http://example/s1'), + RDF::URI('http://example/p1'), + RDF::URI('http://example/o1'))), + output: %( + - "@id": http://example/s + http://example/p: + - "@id": + "@id": http://example/s1 + http://example/p1: + - "@id": http://example/o1 + ) + }, + "object-iib": { + input: RDF::Statement( + RDF::URI('http://example/s'), + RDF::URI('http://example/p'), + RDF::Statement( + RDF::URI('http://example/s1'), + RDF::URI('http://example/p1'), + RDF::Node.new('o1'))), + output: %( + - "@id": http://example/s + http://example/p: + - "@id": + "@id": http://example/s1 + http://example/p1: + - "@id": _:o1 + ) + }, + "object-iil": { + input: RDF::Statement( + RDF::URI('http://example/s'), + RDF::URI('http://example/p'), + RDF::Statement( + RDF::URI('http://example/s1'), + RDF::URI('http://example/p1'), + RDF::Literal('o1'))), + output: %( + - "@id": http://example/s + http://example/p: + - "@id": + "@id": http://example/s1 + http://example/p1: + - "@value": o1 + ) + }, + "recursive-subject": { + input: RDF::Statement( + RDF::Statement( + RDF::Statement( + RDF::URI('http://example/s2'), + RDF::URI('http://example/p2'), + RDF::URI('http://example/o2')), + RDF::URI('http://example/p1'), + RDF::URI('http://example/o1')), + RDF::URI('http://example/p'), + RDF::URI('http://example/o')), + output: %( + - "@id": + "@id": + "@id": http://example/s2 + http://example/p2: + - "@id": http://example/o2 + http://example/p1: + - "@id": http://example/o1 + http://example/p: + - "@id": http://example/o + ) + }, + }.each do |name, params| + it name do + graph = RDF::Graph.new {|g| g << params[:input]} + do_fromRdf(params.merge(input: graph, prefixes: {ex: 'http://example/'})) + end + end + end + end + + def parse(input, **options) + reader = options[:reader] || RDF::TriG::Reader + reader.new(input, **options, &:each_statement).to_a.extend(RDF::Enumerable) + end + + # Serialize ntstr to a string and compare against regexps + def serialize(ntstr, **options) + logger.info ntstr if ntstr.is_a?(String) + g = ntstr.is_a?(String) ? parse(ntstr, **options) : ntstr + logger.info g.dump(:trig) + statements = g.each_statement.to_a + YAML_LD::API.fromRdf(statements, logger: logger, **options) + end + + def do_fromRdf(params) + begin + input, output = params[:input], params[:output] + jld = nil + if params[:write] + expect{jld = serialize(input, **params)}.to write(params[:write]).to(:error) + else + expect{jld = serialize(input, **params)}.not_to write.to(:error) + end + expect(jld).to produce_yamlld(output, logger) + rescue JSON::LD::JsonLdError => e + fail("#{e.class}: #{e.message}\n" + + "#{logger}\n" + + "Backtrace:\n#{e.backtrace.join("\n")}") + end + end +end diff --git a/spec/matchers.rb b/spec/matchers.rb new file mode 100644 index 0000000..ad18603 --- /dev/null +++ b/spec/matchers.rb @@ -0,0 +1,22 @@ +require 'rspec/matchers' # @see https://rubygems.org/gems/rspec +require_relative 'support/extensions' + +RSpec::Matchers.define :produce_yamlld do |expected, logger| + match do |actual| + actual = Psych.load(actual, aliases: true) if actual.is_a?(String) + expected = Psych.load(expected, aliases: true) if expected.is_a?(String) + expect(actual).to be_equivalent_structure expected + end + + failure_message do |actual| + "Expected: #{expected.is_a?(String) ? expected : expected.to_yaml rescue 'malformed structure'}\n" + + "Actual : #{actual.is_a?(String) ? actual : actual.to_yaml rescue 'malformed structure'}\n" + + "\nDebug:\n#{logger}" + end + + failure_message_when_negated do |actual| + "Expected not to produce the following:\n" + + "Actual : #{actual.is_a?(String) ? actual : actual.to_yaml rescue 'malformed structure'}\n" + + "\nDebug:\n#{logger}" + end +end diff --git a/spec/reader_spec.rb b/spec/reader_spec.rb new file mode 100644 index 0000000..849f5fa --- /dev/null +++ b/spec/reader_spec.rb @@ -0,0 +1,138 @@ +# coding: utf-8 +require_relative 'spec_helper' +require 'rdf/spec/reader' + +describe YAML_LD::Reader do + let!(:doap) {File.expand_path("../../etc/doap.yamlld", __FILE__)} + let!(:doap_nt) {File.expand_path("../../etc/doap.nt", __FILE__)} + let!(:doap_count) {File.open(doap_nt).each_line.to_a.length} + let(:logger) {RDF::Spec.logger} + + after(:each) {|example| puts logger.to_s if example.exception} + + it_behaves_like 'an RDF::Reader' do + let(:reader_input) {File.read(doap)} + let(:reader) {YAML_LD::Reader.new(reader_input)} + let(:reader_count) {doap_count} + end + + describe ".for" do + formats = [ + :yamlld, + "etc/doap.yamlld", + {file_name: 'etc/doap.yamlld'}, + {file_extension: 'yamlld'}, + {content_type: 'application/ld+yaml'}, + ].each do |arg| + it "discovers with #{arg.inspect}" do + expect(RDF::Reader.for(arg)).to eq YAML_LD::Reader + end + end + end + + context "when validating" do + it "detects invalid YAML" do + yaml = %( + --- + "@context": + dc: http://purl.org/dc/terms/ + - foo + ) + expect do |b| + described_class.new(StringIO.new(yaml), validate: true, logger: false).each_statement(&b) + end.to raise_error(RDF::ReaderError) + end + end + + context :interface do + { + plain: %q( + "@context": + foaf: + http://xmlns.com/foaf/0.1/ + "@id": + _:bnode1 + "@type": + foaf:Person + "foaf:homepage": + http://example.com/bob/ + "foaf:name": + Bob + }), + leading_comment: %q(--- + # A comment before content + "@context": + foaf: + http://xmlns.com/foaf/0.1/ + "@id": + _:bnode1 + "@type": + foaf:Person + "foaf:homepage": + http://example.com/bob/ + "foaf:name": + Bob + ), + yaml_version: %(%YAML 1.2\n--- + # A comment before content + "@context": + foaf: + http://xmlns.com/foaf/0.1/ + "@id": + _:bnode1 + "@type": + foaf:Person + "foaf:homepage": + http://example.com/bob/ + "foaf:name": + Bob + ), + }.each do |variant, src| + context variant do + subject {src} + + describe "#initialize" do + it "yields reader given string" do + inner = double("inner") + expect(inner).to receive(:called).with(YAML_LD::Reader) + YAML_LD::Reader.new(subject) do |reader| + inner.called(reader.class) + end + end + + it "yields reader given IO" do + inner = double("inner") + expect(inner).to receive(:called).with(YAML_LD::Reader) + YAML_LD::Reader.new(StringIO.new(subject)) do |reader| + inner.called(reader.class) + end + end + + it "returns reader" do + expect(YAML_LD::Reader.new(subject)).to be_a(YAML_LD::Reader) + end + end + + describe "#each_statement" do + it "yields statements" do + inner = double("inner") + expect(inner).to receive(:called).with(RDF::Statement).exactly(3) + YAML_LD::Reader.new(subject).each_statement do |statement| + inner.called(statement.class) + end + end + end + + describe "#each_triple" do + it "yields statements" do + inner = double("inner") + expect(inner).to receive(:called).exactly(3) + YAML_LD::Reader.new(subject).each_triple do |subject, predicate, object| + inner.called(subject.class, predicate.class, object.class) + end + end + end + end + end + end +end diff --git a/spec/spec.opts b/spec/spec.opts new file mode 100644 index 0000000..cf6add7 --- /dev/null +++ b/spec/spec.opts @@ -0,0 +1 @@ +--colour \ No newline at end of file diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..8e2eb09 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,265 @@ +$:.unshift(File.join("../../lib", __FILE__)) +$:.unshift File.dirname(__FILE__) + +require "bundler/setup" +require 'rspec' +require 'rdf' +require 'rdf/isomorphic' +require 'rdf/nquads' +require 'rdf/turtle' +require 'rdf/trig' +require 'rdf/vocab' +require 'rdf/spec' +require 'rdf/spec/matchers' +require_relative 'matchers' +require 'yaml' +begin + require 'simplecov' + require 'simplecov-lcov' + SimpleCov::Formatter::LcovFormatter.config do |config| + #Coveralls is coverage by default/lcov. Send info results + config.report_with_single_file = true + config.single_report_path = 'coverage/lcov.info' + end + + SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([ + SimpleCov::Formatter::HTMLFormatter, + SimpleCov::Formatter::LcovFormatter + ]) + SimpleCov.start do + add_filter "/spec/" + end +rescue LoadError +end + +require 'yaml_ld' + +# Left-justifies a block string +class ::String + def unindent + self.gsub(/^#{self.scan(/^[ \t]+(?=\S)/).min}/, '') + end +end + +# Heuristically detect the input stream +def detect_format(stream) + # Got to look into the file to see + if stream.respond_to?(:rewind) && stream.respond_to?(:read) + stream.rewind + string = stream.read(1000) + stream.rewind + else + string = stream.to_s + end + case string + when / v.to_s)} + + # Recursively replace blank nodes in actual with the bijection + replace_nodes(actual, bijection) + else + actual + end +end + +def replace_nodes(object, bijection) + case object + when Array + object.map {|o| replace_nodes(o, bijection)} + when Hash + object.inject({}) do |memo, (k, v)| + memo.merge(bijection.fetch(k, k) => replace_nodes(v, bijection)) + end + when String + bijection.fetch(object, object) + else + object + end +end + +LIBRARY_INPUT = JSON.parse(%([ + { + "@id": "http://example.org/library", + "@type": "http://example.org/vocab#Library", + "http://example.org/vocab#contains": {"@id": "http://example.org/library/the-republic"} + }, { + "@id": "http://example.org/library/the-republic", + "@type": "http://example.org/vocab#Book", + "http://purl.org/dc/elements/1.1/creator": "Plato", + "http://purl.org/dc/elements/1.1/title": "The Republic", + "http://example.org/vocab#contains": { + "@id": "http://example.org/library/the-republic#introduction", + "@type": "http://example.org/vocab#Chapter", + "http://purl.org/dc/elements/1.1/description": "An introductory chapter on The Republic.", + "http://purl.org/dc/elements/1.1/title": "The Introduction" + } + } +])) + +LIBRARY_EXPANDED = JSON.parse(%([ + { + "@id": "http://example.org/library", + "@type": ["http://example.org/vocab#Library"], + "http://example.org/vocab#contains": [{"@id": "http://example.org/library/the-republic"}] + }, { + "@id": "http://example.org/library/the-republic", + "@type": ["http://example.org/vocab#Book"], + "http://purl.org/dc/elements/1.1/creator": [{"@value": "Plato"}], + "http://purl.org/dc/elements/1.1/title": [{"@value": "The Republic"}], + "http://example.org/vocab#contains": [{ + "@id": "http://example.org/library/the-republic#introduction", + "@type": ["http://example.org/vocab#Chapter"], + "http://purl.org/dc/elements/1.1/description": [{"@value": "An introductory chapter on The Republic."}], + "http://purl.org/dc/elements/1.1/title": [{"@value": "The Introduction"}] + }] + } +])) + +LIBRARY_COMPACTED_DEFAULT = JSON.parse(%({ + "@context": "http://schema.org", + "@graph": [ + { + "id": "http://example.org/library", + "type": "http://example.org/vocab#Library", + "http://example.org/vocab#contains": {"id": "http://example.org/library/the-republic"} + }, { + "id": "http://example.org/library/the-republic", + "type": "http://example.org/vocab#Book", + "http://purl.org/dc/elements/1.1/creator": "Plato", + "http://purl.org/dc/elements/1.1/title": "The Republic", + "http://example.org/vocab#contains": { + "id": "http://example.org/library/the-republic#introduction", + "type": "http://example.org/vocab#Chapter", + "http://purl.org/dc/elements/1.1/description": "An introductory chapter on The Republic.", + "http://purl.org/dc/elements/1.1/title": "The Introduction" + } + } + ] +})) + +LIBRARY_COMPACTED = JSON.parse(%({ + "@context": "http://conneg.example.com/context", + "@graph": [ + { + "@id": "http://example.org/library", + "@type": "ex:Library", + "ex:contains": { + "@id": "http://example.org/library/the-republic" + } + }, + { + "@id": "http://example.org/library/the-republic", + "@type": "ex:Book", + "dc:creator": "Plato", + "dc:title": "The Republic", + "ex:contains": { + "@id": "http://example.org/library/the-republic#introduction", + "@type": "ex:Chapter", + "dc:description": "An introductory chapter on The Republic.", + "dc:title": "The Introduction" + } + } + ] +})) + +LIBRARY_FLATTENED_EXPANDED = JSON.parse(%([ + { + "@id": "http://example.org/library", + "@type": ["http://example.org/vocab#Library"], + "http://example.org/vocab#contains": [{"@id": "http://example.org/library/the-republic"}] + }, + { + "@id": "http://example.org/library/the-republic", + "@type": ["http://example.org/vocab#Book"], + "http://purl.org/dc/elements/1.1/creator": [{"@value": "Plato"}], + "http://purl.org/dc/elements/1.1/title": [{"@value": "The Republic"}], + "http://example.org/vocab#contains": [{"@id": "http://example.org/library/the-republic#introduction"}] + }, + { + "@id": "http://example.org/library/the-republic#introduction", + "@type": ["http://example.org/vocab#Chapter"], + "http://purl.org/dc/elements/1.1/description": [{"@value": "An introductory chapter on The Republic."}], + "http://purl.org/dc/elements/1.1/title": [{"@value": "The Introduction"}] + } +])) + +LIBRARY_FLATTENED_COMPACTED_DEFAULT = JSON.parse(%({ + "@context": "http://schema.org", + "@graph": [ + { + "id": "http://example.org/library", + "type": "http://example.org/vocab#Library", + "http://example.org/vocab#contains": {"id": "http://example.org/library/the-republic"} + }, + { + "id": "http://example.org/library/the-republic", + "type": "http://example.org/vocab#Book", + "http://purl.org/dc/elements/1.1/creator": "Plato", + "http://purl.org/dc/elements/1.1/title": "The Republic", + "http://example.org/vocab#contains": {"id": "http://example.org/library/the-republic#introduction"} + }, + { + "id": "http://example.org/library/the-republic#introduction", + "type": "http://example.org/vocab#Chapter", + "http://purl.org/dc/elements/1.1/description": "An introductory chapter on The Republic.", + "http://purl.org/dc/elements/1.1/title": "The Introduction" + } + ] +})) + +LIBRARY_FLATTENED_COMPACTED = JSON.parse(%({ + "@context": "http://conneg.example.com/context", + "@graph": [ + { + "@id": "http://example.org/library", + "@type": "ex:Library", + "ex:contains": {"@id": "http://example.org/library/the-republic"} + }, + { + "@id": "http://example.org/library/the-republic", + "@type": "ex:Book", + "dc:creator": "Plato", + "dc:title": "The Republic", + "ex:contains": {"@id": "http://example.org/library/the-republic#introduction"} + }, + { + "@id": "http://example.org/library/the-republic#introduction", + "@type": "ex:Chapter", + "dc:description": "An introductory chapter on The Republic.", + "dc:title": "The Introduction" + } + ] +})) + +LIBRARY_FRAMED = JSON.parse(%({ + "@context": { + "dc": "http://purl.org/dc/elements/1.1/", + "ex": "http://example.org/vocab#" + }, + "@id": "http://example.org/library", + "@type": "ex:Library", + "ex:contains": { + "@id": "http://example.org/library/the-republic", + "@type": "ex:Book", + "dc:creator": "Plato", + "dc:title": "The Republic", + "ex:contains": { + "@id": "http://example.org/library/the-republic#introduction", + "@type": "ex:Chapter", + "dc:description": "An introductory chapter on The Republic.", + "dc:title": "The Introduction" + } + } +})) diff --git a/spec/support/extensions.rb b/spec/support/extensions.rb new file mode 100644 index 0000000..84678b5 --- /dev/null +++ b/spec/support/extensions.rb @@ -0,0 +1,44 @@ +class Object + def equivalent_structure?(other, ordered: false) + self == other + end +end + +class Hash + def equivalent_structure?(other, ordered: false) + return false unless other.is_a?(Hash) && other.length == length + all? do |key, value| + # List values are still ordered + if key == '@language' && value.is_a?(String) + value.downcase.equivalent_structure?(other[key].to_s.downcase, ordered: key == '@list') + else + value.equivalent_structure?(other[key], ordered: key == '@list') + end + end + end + + def diff(other) + self.keys.inject({}) do |memo, key| + unless self[key] == other[key] + memo[key] = [self[key], other[key]] + end + memo + end + end +end + +class Array + def equivalent_structure?(other, ordered: false) + return false unless other.is_a?(Array) && other.length == length + if ordered + b = other.dup + # All elements must match in order + all? {|av| av.equivalent_structure?(b.shift)} + else + # Look for any element which matches + all? do |av| + other.any? {|bv| av.equivalent_structure?(bv)} + end + end + end +end diff --git a/spec/test-files/test-1-compacted.jsonld b/spec/test-files/test-1-compacted.jsonld new file mode 100644 index 0000000..043376c --- /dev/null +++ b/spec/test-files/test-1-compacted.jsonld @@ -0,0 +1,10 @@ +{ + "@context": { + "avatar": "http://xmlns.com/foaf/0.1/avatar", + "homepage": "http://xmlns.com/foaf/0.1/homepage", + "name": "http://xmlns.com/foaf/0.1/name" + }, + "avatar": "http://twitter.com/account/profile_image/manusporny", + "homepage": "http://manu.sporny.org/", + "name": "Manu Sporny" +} diff --git a/spec/test-files/test-1-context.jsonld b/spec/test-files/test-1-context.jsonld new file mode 100644 index 0000000..3ef9a86 --- /dev/null +++ b/spec/test-files/test-1-context.jsonld @@ -0,0 +1,7 @@ +{ + "@context": { + "name": "http://xmlns.com/foaf/0.1/name", + "homepage": "http://xmlns.com/foaf/0.1/homepage", + "avatar": "http://xmlns.com/foaf/0.1/avatar" + } +} diff --git a/spec/test-files/test-1-expanded.jsonld b/spec/test-files/test-1-expanded.jsonld new file mode 100644 index 0000000..94074de --- /dev/null +++ b/spec/test-files/test-1-expanded.jsonld @@ -0,0 +1,5 @@ +[{ + "http://xmlns.com/foaf/0.1/name": [{"@value": "Manu Sporny"}], + "http://xmlns.com/foaf/0.1/homepage": [{"@value": "http://manu.sporny.org/"}], + "http://xmlns.com/foaf/0.1/avatar": [{"@value": "http://twitter.com/account/profile_image/manusporny"}] +}] \ No newline at end of file diff --git a/spec/test-files/test-1-input.yamlld b/spec/test-files/test-1-input.yamlld new file mode 100644 index 0000000..eb2d8f9 --- /dev/null +++ b/spec/test-files/test-1-input.yamlld @@ -0,0 +1,8 @@ +--- +"@context": + name: http://xmlns.com/foaf/0.1/name + homepage: http://xmlns.com/foaf/0.1/homepage + avatar: http://xmlns.com/foaf/0.1/avatar +name: Manu Sporny +homepage: http://manu.sporny.org/ +avatar: http://twitter.com/account/profile_image/manusporny diff --git a/spec/test-files/test-1-rdf.ttl b/spec/test-files/test-1-rdf.ttl new file mode 100644 index 0000000..c03163b --- /dev/null +++ b/spec/test-files/test-1-rdf.ttl @@ -0,0 +1,8 @@ +@prefix avatar: . +@prefix homepage: . +@prefix name: . +@prefix xsd: . + + [ avatar: "http://twitter.com/account/profile_image/manusporny"; + homepage: "http://manu.sporny.org/"; + name: "Manu Sporny"] . diff --git a/spec/test-files/test-2-compacted.jsonld b/spec/test-files/test-2-compacted.jsonld new file mode 100644 index 0000000..6d8375f --- /dev/null +++ b/spec/test-files/test-2-compacted.jsonld @@ -0,0 +1,20 @@ +{ + "@context": { + "dc": "http://purl.org/dc/elements/1.1/", + "ex": "http://example.org/vocab#" + }, + "@id": "http://example.org/library", + "@type": "ex:Library", + "ex:contains": { + "@id": "http://example.org/library/the-republic", + "@type": "ex:Book", + "dc:creator": "Plato", + "dc:title": "The Republic", + "ex:contains": { + "@id": "http://example.org/library/the-republic#introduction", + "@type": "ex:Chapter", + "dc:description": "An introductory chapter on The Republic.", + "dc:title": "The Introduction" + } + } +} diff --git a/spec/test-files/test-2-context.jsonld b/spec/test-files/test-2-context.jsonld new file mode 100644 index 0000000..a5a152d --- /dev/null +++ b/spec/test-files/test-2-context.jsonld @@ -0,0 +1,7 @@ +{ + "@context": { + "dc": "http://purl.org/dc/elements/1.1/", + "ex": "http://example.org/vocab#" + } + +} diff --git a/spec/test-files/test-2-expanded.jsonld b/spec/test-files/test-2-expanded.jsonld new file mode 100644 index 0000000..eddcb8f --- /dev/null +++ b/spec/test-files/test-2-expanded.jsonld @@ -0,0 +1,16 @@ +[{ + "@id": "http://example.org/library", + "@type": ["http://example.org/vocab#Library"], + "http://example.org/vocab#contains": [{ + "@id": "http://example.org/library/the-republic", + "@type": ["http://example.org/vocab#Book"], + "http://purl.org/dc/elements/1.1/creator": [{"@value": "Plato"}], + "http://purl.org/dc/elements/1.1/title": [{"@value": "The Republic"}], + "http://example.org/vocab#contains": [{ + "@id": "http://example.org/library/the-republic#introduction", + "@type": ["http://example.org/vocab#Chapter"], + "http://purl.org/dc/elements/1.1/description": [{"@value": "An introductory chapter on The Republic."}], + "http://purl.org/dc/elements/1.1/title": [{"@value": "The Introduction"}] + }] + }] +}] \ No newline at end of file diff --git a/spec/test-files/test-2-input.yamlld b/spec/test-files/test-2-input.yamlld new file mode 100644 index 0000000..e9563e6 --- /dev/null +++ b/spec/test-files/test-2-input.yamlld @@ -0,0 +1,16 @@ +--- +"@context": + dc: http://purl.org/dc/elements/1.1/ + ex: http://example.org/vocab# +"@id": http://example.org/library +"@type": ex:Library +ex:contains: + "@id": http://example.org/library/the-republic + "@type": ex:Book + dc:creator: Plato + dc:title: The Republic + ex:contains: + "@id": http://example.org/library/the-republic#introduction + "@type": ex:Chapter + dc:description: An introductory chapter on The Republic. + dc:title: The Introduction diff --git a/spec/test-files/test-2-rdf.ttl b/spec/test-files/test-2-rdf.ttl new file mode 100644 index 0000000..409f882 --- /dev/null +++ b/spec/test-files/test-2-rdf.ttl @@ -0,0 +1,14 @@ +@prefix dc: . +@prefix ex: . + + a ex:Library; + ex:contains . + + a ex:Book; + ex:contains ; + dc:creator "Plato"; + dc:title "The Republic" . + + a ex:Chapter; + dc:description "An introductory chapter on The Republic."; + dc:title "The Introduction" . diff --git a/spec/test-files/test-3-compacted.jsonld b/spec/test-files/test-3-compacted.jsonld new file mode 100644 index 0000000..b0e56cb --- /dev/null +++ b/spec/test-files/test-3-compacted.jsonld @@ -0,0 +1,11 @@ +{ + "@context": { + "xsd": "http://www.w3.org/2001/XMLSchema#", + "name": "http://xmlns.com/foaf/0.1/name", + "age": {"@id": "http://xmlns.com/foaf/0.1/age", "@type": "xsd:integer"}, + "homepage": {"@id": "http://xmlns.com/foaf/0.1/homepage", "@type": "@id"} + }, + "name": "Manu Sporny", + "age": "41", + "homepage": "http://manu.sporny.org/" +} diff --git a/spec/test-files/test-3-context.jsonld b/spec/test-files/test-3-context.jsonld new file mode 100644 index 0000000..ddefcb4 --- /dev/null +++ b/spec/test-files/test-3-context.jsonld @@ -0,0 +1,8 @@ +{ + "@context": { + "xsd": "http://www.w3.org/2001/XMLSchema#", + "name": "http://xmlns.com/foaf/0.1/name", + "age": {"@id": "http://xmlns.com/foaf/0.1/age", "@type": "xsd:integer"}, + "homepage": {"@id": "http://xmlns.com/foaf/0.1/homepage", "@type": "@id"} + } +} diff --git a/spec/test-files/test-3-expanded.jsonld b/spec/test-files/test-3-expanded.jsonld new file mode 100644 index 0000000..7efecd2 --- /dev/null +++ b/spec/test-files/test-3-expanded.jsonld @@ -0,0 +1,10 @@ +[{ + "http://xmlns.com/foaf/0.1/name": [{"@value": "Manu Sporny"}], + "http://xmlns.com/foaf/0.1/age": [{ + "@type": "http://www.w3.org/2001/XMLSchema#integer", + "@value": "41" + }], + "http://xmlns.com/foaf/0.1/homepage": [{ + "@id": "http://manu.sporny.org/" + }] +}] diff --git a/spec/test-files/test-3-input.yamlld b/spec/test-files/test-3-input.yamlld new file mode 100644 index 0000000..22b44d1 --- /dev/null +++ b/spec/test-files/test-3-input.yamlld @@ -0,0 +1,13 @@ +--- +"@context": + xsd: http://www.w3.org/2001/XMLSchema# + name: http://xmlns.com/foaf/0.1/name + age: + "@id": http://xmlns.com/foaf/0.1/age + "@type": xsd:integer + homepage: + "@id": http://xmlns.com/foaf/0.1/homepage + "@type": "@id" +name: Manu Sporny +age: '41' +homepage: http://manu.sporny.org/ diff --git a/spec/test-files/test-3-rdf.ttl b/spec/test-files/test-3-rdf.ttl new file mode 100644 index 0000000..43e061d --- /dev/null +++ b/spec/test-files/test-3-rdf.ttl @@ -0,0 +1,8 @@ +@prefix age: . +@prefix homepage: . +@prefix name: . +@prefix xsd: . + + [ age: 41; + homepage: ; + name: "Manu Sporny"] . diff --git a/spec/test-files/test-4-compacted.jsonld b/spec/test-files/test-4-compacted.jsonld new file mode 100644 index 0000000..6a2a054 --- /dev/null +++ b/spec/test-files/test-4-compacted.jsonld @@ -0,0 +1,10 @@ +{ + "@context": { + "": "http://manu.sporny.org/", + "foaf": "http://xmlns.com/foaf/0.1/" + }, + "@id": ":#me", + "@type": "foaf:Person", + "foaf:name": "Manu Sporny", + "foaf:homepage": { "@id": "http://manu.sporny.org/" } +} diff --git a/spec/test-files/test-4-context.jsonld b/spec/test-files/test-4-context.jsonld new file mode 100644 index 0000000..fced953 --- /dev/null +++ b/spec/test-files/test-4-context.jsonld @@ -0,0 +1,7 @@ +{ + "@context": { + "": "http://manu.sporny.org/", + "foaf": "http://xmlns.com/foaf/0.1/" + } +} + diff --git a/spec/test-files/test-4-expanded.jsonld b/spec/test-files/test-4-expanded.jsonld new file mode 100644 index 0000000..119ca5d --- /dev/null +++ b/spec/test-files/test-4-expanded.jsonld @@ -0,0 +1,6 @@ +[{ + "@id": "http://manu.sporny.org/#me", + "@type": ["http://xmlns.com/foaf/0.1/Person"], + "http://xmlns.com/foaf/0.1/name": [{"@value": "Manu Sporny"}], + "http://xmlns.com/foaf/0.1/homepage": [{ "@id": "http://manu.sporny.org/" }] +}] \ No newline at end of file diff --git a/spec/test-files/test-4-input.yamlld b/spec/test-files/test-4-input.yamlld new file mode 100644 index 0000000..7526f10 --- /dev/null +++ b/spec/test-files/test-4-input.yamlld @@ -0,0 +1,9 @@ +--- +"@context": + foo: http://manu.sporny.org/ + foaf: http://xmlns.com/foaf/0.1/ +"@id": foo:#me +"@type": foaf:Person +foaf:name: Manu Sporny +foaf:homepage: + "@id": 'foo:' diff --git a/spec/test-files/test-4-rdf.ttl b/spec/test-files/test-4-rdf.ttl new file mode 100644 index 0000000..32cdeeb --- /dev/null +++ b/spec/test-files/test-4-rdf.ttl @@ -0,0 +1,5 @@ +@prefix foaf: . + + a foaf:Person; + foaf:homepage ; + foaf:name "Manu Sporny" . diff --git a/spec/test-files/test-5-compacted.jsonld b/spec/test-files/test-5-compacted.jsonld new file mode 100644 index 0000000..4f84d11 --- /dev/null +++ b/spec/test-files/test-5-compacted.jsonld @@ -0,0 +1,13 @@ +{ + "@context": { + "": "http://manu.sporny.org/", + "foaf": "http://xmlns.com/foaf/0.1/" + }, + "@id": ":#me", + "@type": "foaf:Person", + "foaf:name": "Manu Sporny", + "foaf:knows": { + "@type": "foaf:Person", + "foaf:name": "Gregg Kellogg" + } +} diff --git a/spec/test-files/test-5-context.jsonld b/spec/test-files/test-5-context.jsonld new file mode 100644 index 0000000..fced953 --- /dev/null +++ b/spec/test-files/test-5-context.jsonld @@ -0,0 +1,7 @@ +{ + "@context": { + "": "http://manu.sporny.org/", + "foaf": "http://xmlns.com/foaf/0.1/" + } +} + diff --git a/spec/test-files/test-5-expanded.jsonld b/spec/test-files/test-5-expanded.jsonld new file mode 100644 index 0000000..dcb5a31 --- /dev/null +++ b/spec/test-files/test-5-expanded.jsonld @@ -0,0 +1,9 @@ +[{ + "@id": "http://manu.sporny.org/#me", + "@type": ["http://xmlns.com/foaf/0.1/Person"], + "http://xmlns.com/foaf/0.1/name": [{"@value": "Manu Sporny"}], + "http://xmlns.com/foaf/0.1/knows": [{ + "@type": ["http://xmlns.com/foaf/0.1/Person"], + "http://xmlns.com/foaf/0.1/name": [{"@value": "Gregg Kellogg"}] + }] +}] diff --git a/spec/test-files/test-5-input.yamlld b/spec/test-files/test-5-input.yamlld new file mode 100644 index 0000000..e290495 --- /dev/null +++ b/spec/test-files/test-5-input.yamlld @@ -0,0 +1,10 @@ +--- +"@context": + foo: http://manu.sporny.org/ + foaf: http://xmlns.com/foaf/0.1/ +"@id": foo:#me +"@type": foaf:Person +foaf:name: Manu Sporny +foaf:knows: + "@type": foaf:Person + foaf:name: Gregg Kellogg diff --git a/spec/test-files/test-5-rdf.ttl b/spec/test-files/test-5-rdf.ttl new file mode 100644 index 0000000..2c8125e --- /dev/null +++ b/spec/test-files/test-5-rdf.ttl @@ -0,0 +1,7 @@ +@prefix foaf: . +@prefix xsd: . + + a foaf:Person; + foaf:knows [ a foaf:Person; + foaf:name "Gregg Kellogg"]; + foaf:name "Manu Sporny" . diff --git a/spec/test-files/test-6-compacted.jsonld b/spec/test-files/test-6-compacted.jsonld new file mode 100644 index 0000000..63bec62 --- /dev/null +++ b/spec/test-files/test-6-compacted.jsonld @@ -0,0 +1,10 @@ +{ + "@context": { + "": "http://manu.sporny.org/", + "foaf": "http://xmlns.com/foaf/0.1/" + }, + "@id": "http://example.org/people#joebob", + "@type": "foaf:Person", + "foaf:name": "Joe Bob", + "foaf:nick": { "@list": [ "joe", "bob", "jaybe" ] } +} diff --git a/spec/test-files/test-6-context.jsonld b/spec/test-files/test-6-context.jsonld new file mode 100644 index 0000000..085bc57 --- /dev/null +++ b/spec/test-files/test-6-context.jsonld @@ -0,0 +1,7 @@ +{ + "@context": { + "": "http://manu.sporny.org/", + "foaf": "http://xmlns.com/foaf/0.1/" + } + +} diff --git a/spec/test-files/test-6-expanded.jsonld b/spec/test-files/test-6-expanded.jsonld new file mode 100644 index 0000000..dcf0659 --- /dev/null +++ b/spec/test-files/test-6-expanded.jsonld @@ -0,0 +1,10 @@ +[{ + "@id": "http://example.org/people#joebob", + "@type": ["http://xmlns.com/foaf/0.1/Person"], + "http://xmlns.com/foaf/0.1/name": [{"@value": "Joe Bob"}], + "http://xmlns.com/foaf/0.1/nick": [{ "@list": [ + {"@value": "joe"}, + {"@value": "bob"}, + {"@value": "jaybe"} + ]}] +}] diff --git a/spec/test-files/test-6-input.yamlld b/spec/test-files/test-6-input.yamlld new file mode 100644 index 0000000..530dacf --- /dev/null +++ b/spec/test-files/test-6-input.yamlld @@ -0,0 +1,12 @@ +--- +"@context": + '': http://manu.sporny.org/ + foaf: http://xmlns.com/foaf/0.1/ +"@id": http://example.org/people#joebob +"@type": foaf:Person +foaf:name: Joe Bob +foaf:nick: + "@list": + - joe + - bob + - jaybe diff --git a/spec/test-files/test-6-rdf.ttl b/spec/test-files/test-6-rdf.ttl new file mode 100644 index 0000000..dd0c656 --- /dev/null +++ b/spec/test-files/test-6-rdf.ttl @@ -0,0 +1,6 @@ +@prefix foaf: . +@prefix xsd: . + + a foaf:Person; + foaf:name "Joe Bob"; + foaf:nick ("joe" "bob" "jaybe") . diff --git a/spec/test-files/test-7-compacted.jsonld b/spec/test-files/test-7-compacted.jsonld new file mode 100644 index 0000000..497c13b --- /dev/null +++ b/spec/test-files/test-7-compacted.jsonld @@ -0,0 +1,23 @@ +{ + "@context": { "foaf": "http://xmlns.com/foaf/0.1/" }, + "@graph": [ + { + "@id": "_:bnode1", + "@type": "foaf:Person", + "foaf:homepage": "http://example.com/bob/", + "foaf:name": "Bob" + }, + { + "@id": "_:bnode2", + "@type": "foaf:Person", + "foaf:homepage": "http://example.com/eve/", + "foaf:name": "Eve" + }, + { + "@id": "_:bnode3", + "@type": "foaf:Person", + "foaf:homepage": "http://example.com/manu/", + "foaf:name": "Manu" + } + ] +} diff --git a/spec/test-files/test-7-context.jsonld b/spec/test-files/test-7-context.jsonld new file mode 100644 index 0000000..7ca7f1b --- /dev/null +++ b/spec/test-files/test-7-context.jsonld @@ -0,0 +1,4 @@ +{ + "@context": { "foaf": "http://xmlns.com/foaf/0.1/" } +} + diff --git a/spec/test-files/test-7-expanded.jsonld b/spec/test-files/test-7-expanded.jsonld new file mode 100644 index 0000000..7d04b36 --- /dev/null +++ b/spec/test-files/test-7-expanded.jsonld @@ -0,0 +1,20 @@ +[ + { + "@id": "_:bnode1", + "@type": ["http://xmlns.com/foaf/0.1/Person"], + "http://xmlns.com/foaf/0.1/homepage": [{"@value": "http://example.com/bob/"}], + "http://xmlns.com/foaf/0.1/name": [{"@value": "Bob"}] + }, + { + "@id": "_:bnode2", + "@type": ["http://xmlns.com/foaf/0.1/Person"], + "http://xmlns.com/foaf/0.1/homepage": [{"@value": "http://example.com/eve/"}], + "http://xmlns.com/foaf/0.1/name": [{"@value": "Eve"}] + }, + { + "@id": "_:bnode3", + "@type": ["http://xmlns.com/foaf/0.1/Person"], + "http://xmlns.com/foaf/0.1/homepage": [{"@value": "http://example.com/manu/"}], + "http://xmlns.com/foaf/0.1/name": [{"@value": "Manu"}] + } +] \ No newline at end of file diff --git a/spec/test-files/test-7-input.yamlld b/spec/test-files/test-7-input.yamlld new file mode 100644 index 0000000..47f52e5 --- /dev/null +++ b/spec/test-files/test-7-input.yamlld @@ -0,0 +1,16 @@ +--- +"@context": + foaf: http://xmlns.com/foaf/0.1/ +"@graph": +- "@id": _:bnode1 + "@type": foaf:Person + foaf:homepage: http://example.com/bob/ + foaf:name: Bob +- "@id": _:bnode2 + "@type": foaf:Person + foaf:homepage: http://example.com/eve/ + foaf:name: Eve +- "@id": _:bnode3 + "@type": foaf:Person + foaf:homepage: http://example.com/manu/ + foaf:name: Manu diff --git a/spec/test-files/test-7-rdf.ttl b/spec/test-files/test-7-rdf.ttl new file mode 100644 index 0000000..47cd551 --- /dev/null +++ b/spec/test-files/test-7-rdf.ttl @@ -0,0 +1,14 @@ +@prefix foaf: . +@prefix xsd: . + + [ a foaf:Person; + foaf:homepage "http://example.com/manu/"; + foaf:name "Manu"] . + + [ a foaf:Person; + foaf:homepage "http://example.com/eve/"; + foaf:name "Eve"] . + + [ a foaf:Person; + foaf:homepage "http://example.com/bob/"; + foaf:name "Bob"] . diff --git a/spec/test-files/test-8-compacted.jsonld b/spec/test-files/test-8-compacted.jsonld new file mode 100644 index 0000000..1128b75 --- /dev/null +++ b/spec/test-files/test-8-compacted.jsonld @@ -0,0 +1,34 @@ +{ + "@context": { + "Book": "http://example.org/vocab#Book", + "Chapter": "http://example.org/vocab#Chapter", + "contains": { + "@id": "http://example.org/vocab#contains", + "@type": "@id" + }, + "creator": "http://purl.org/dc/terms/creator", + "description": "http://purl.org/dc/terms/description", + "Library": "http://example.org/vocab#Library", + "title": "http://purl.org/dc/terms/title" + }, + "@graph": [ + { + "@id": "http://example.com/library", + "@type": "Library", + "contains": "http://example.org/library/the-republic" + }, + { + "@id": "http://example.org/library/the-republic", + "@type": "Book", + "creator": "Plato", + "title": "The Republic", + "contains": "http://example.org/library/the-republic#introduction" + }, + { + "@id": "http://example.org/library/the-republic#introduction", + "@type": "Chapter", + "description": "An introductory chapter on The Republic.", + "title": "The Introduction" + } + ] +} \ No newline at end of file diff --git a/spec/test-files/test-8-context.jsonld b/spec/test-files/test-8-context.jsonld new file mode 100644 index 0000000..e78a617 --- /dev/null +++ b/spec/test-files/test-8-context.jsonld @@ -0,0 +1,11 @@ +{ + "@context": { + "Book": "http://example.org/vocab#Book", + "Chapter": "http://example.org/vocab#Chapter", + "contains": {"@id": "http://example.org/vocab#contains", "@type": "@id"}, + "creator": "http://purl.org/dc/terms/creator", + "description": "http://purl.org/dc/terms/description", + "Library": "http://example.org/vocab#Library", + "title": "http://purl.org/dc/terms/title" + } +} \ No newline at end of file diff --git a/spec/test-files/test-8-expanded.jsonld b/spec/test-files/test-8-expanded.jsonld new file mode 100644 index 0000000..eeb5fa5 --- /dev/null +++ b/spec/test-files/test-8-expanded.jsonld @@ -0,0 +1,24 @@ +[ + { + "@id": "http://example.com/library", + "@type": ["http://example.org/vocab#Library"], + "http://example.org/vocab#contains": [{ + "@id": "http://example.org/library/the-republic" + }] + }, + { + "@id": "http://example.org/library/the-republic", + "@type": ["http://example.org/vocab#Book"], + "http://purl.org/dc/terms/creator": [{"@value": "Plato"}], + "http://purl.org/dc/terms/title": [{"@value": "The Republic"}], + "http://example.org/vocab#contains": [{ + "@id": "http://example.org/library/the-republic#introduction" + }] + }, + { + "@id": "http://example.org/library/the-republic#introduction", + "@type": ["http://example.org/vocab#Chapter"], + "http://purl.org/dc/terms/description": [{"@value": "An introductory chapter on The Republic."}], + "http://purl.org/dc/terms/title": [{"@value": "The Introduction"}] + } +] \ No newline at end of file diff --git a/spec/test-files/test-8-frame.jsonld b/spec/test-files/test-8-frame.jsonld new file mode 100644 index 0000000..f028bff --- /dev/null +++ b/spec/test-files/test-8-frame.jsonld @@ -0,0 +1,18 @@ +{ + "@context": { + "Book": "http://example.org/vocab#Book", + "Chapter": "http://example.org/vocab#Chapter", + "contains": "http://example.org/vocab#contains", + "creator": "http://purl.org/dc/terms/creator", + "description": "http://purl.org/dc/terms/description", + "Library": "http://example.org/vocab#Library", + "title": "http://purl.org/dc/terms/title" + }, + "@type": "Library", + "contains": { + "@type": "Book", + "contains": { + "@type": "Chapter" + } + } +} diff --git a/spec/test-files/test-8-framed.jsonld b/spec/test-files/test-8-framed.jsonld new file mode 100644 index 0000000..1f366b6 --- /dev/null +++ b/spec/test-files/test-8-framed.jsonld @@ -0,0 +1,25 @@ +{ + "@context": { + "Book": "http://example.org/vocab#Book", + "Chapter": "http://example.org/vocab#Chapter", + "contains": "http://example.org/vocab#contains", + "creator": "http://purl.org/dc/terms/creator", + "description": "http://purl.org/dc/terms/description", + "Library": "http://example.org/vocab#Library", + "title": "http://purl.org/dc/terms/title" + }, + "@id": "http://example.com/library", + "@type": "Library", + "contains": { + "@id": "http://example.org/library/the-republic", + "@type": "Book", + "contains": { + "@id": "http://example.org/library/the-republic#introduction", + "@type": "Chapter", + "description": "An introductory chapter on The Republic.", + "title": "The Introduction" + }, + "creator": "Plato", + "title": "The Republic" + } +} diff --git a/spec/test-files/test-8-input.yamlld b/spec/test-files/test-8-input.yamlld new file mode 100644 index 0000000..0381d98 --- /dev/null +++ b/spec/test-files/test-8-input.yamlld @@ -0,0 +1,24 @@ +--- +"@context": + Book: http://example.org/vocab#Book + Chapter: http://example.org/vocab#Chapter + contains: + "@id": http://example.org/vocab#contains + "@type": "@id" + creator: http://purl.org/dc/terms/creator + description: http://purl.org/dc/terms/description + Library: http://example.org/vocab#Library + title: http://purl.org/dc/terms/title +"@graph": +- "@id": http://example.com/library + "@type": Library + contains: http://example.org/library/the-republic +- "@id": http://example.org/library/the-republic + "@type": Book + creator: Plato + title: The Republic + contains: http://example.org/library/the-republic#introduction +- "@id": http://example.org/library/the-republic#introduction + "@type": Chapter + description: An introductory chapter on The Republic. + title: The Introduction diff --git a/spec/test-files/test-8-rdf.ttl b/spec/test-files/test-8-rdf.ttl new file mode 100644 index 0000000..19b3221 --- /dev/null +++ b/spec/test-files/test-8-rdf.ttl @@ -0,0 +1,15 @@ +@prefix dc: . +@prefix rdf: . + + a ; + . + + a ; + dc:title "The Republic"; + ; + dc:creator "Plato" . + + a ; + dc:title "The Introduction"; + dc:description "An introductory chapter on The Republic." . + diff --git a/spec/test-files/test-9-compacted.jsonld b/spec/test-files/test-9-compacted.jsonld new file mode 100644 index 0000000..d4db790 --- /dev/null +++ b/spec/test-files/test-9-compacted.jsonld @@ -0,0 +1,20 @@ +{ + "@context": { + "id1": "http://example.com/id1", + "t1": "http://example.com/t1", + "t2": "http://example.com/t2", + "term1": "http://example.com/term", + "term2": {"@id": "http://example.com/term", "@type": "t2"}, + "term3": {"@id": "http://example.com/term", "@language": "en"}, + "term4": {"@id": "http://example.com/term", "@container": "@list"}, + "term5": {"@id": "http://example.com/term", "@language": null}, + "@language": "de" + }, + "@id": "http://example.com/id1", + "@type": "t1", + "term1": "v1", + "term2": "v2", + "term3": "v3", + "term4": [ 1, 2 ], + "term5": [ "v5", "plain literal" ] +} diff --git a/spec/test-files/test-9-context.jsonld b/spec/test-files/test-9-context.jsonld new file mode 100644 index 0000000..3cb7c10 --- /dev/null +++ b/spec/test-files/test-9-context.jsonld @@ -0,0 +1,13 @@ +{ + "@context": { + "id1": "http://example.com/id1", + "t1": "http://example.com/t1", + "t2": "http://example.com/t2", + "term1": "http://example.com/term", + "term2": {"@id": "http://example.com/term", "@type": "t2"}, + "term3": {"@id": "http://example.com/term", "@language": "en"}, + "term4": {"@id": "http://example.com/term", "@container": "@list"}, + "term5": {"@id": "http://example.com/term", "@language": null}, + "@language": "de" + } +} diff --git a/spec/test-files/test-9-expanded.jsonld b/spec/test-files/test-9-expanded.jsonld new file mode 100644 index 0000000..d18dc41 --- /dev/null +++ b/spec/test-files/test-9-expanded.jsonld @@ -0,0 +1,14 @@ +[ + { + "@id": "http://example.com/id1", + "@type": ["http://example.com/t1"], + "http://example.com/term": [ + {"@value": "v1","@language": "de"}, + {"@value": "v2","@type": "http://example.com/t2"}, + {"@value": "v3","@language": "en"}, + {"@list": [{"@value": 1},{"@value": 2}]}, + {"@value": "v5"}, + {"@value": "plain literal"} + ] + } +] diff --git a/spec/test-files/test-9-input.yamlld b/spec/test-files/test-9-input.yamlld new file mode 100644 index 0000000..e0a713e --- /dev/null +++ b/spec/test-files/test-9-input.yamlld @@ -0,0 +1,16 @@ +--- +- "@id": http://example.com/id1 + "@type": + - http://example.com/t1 + http://example.com/term: + - "@value": v1 + "@language": de + - "@value": v2 + "@type": http://example.com/t2 + - "@value": v3 + "@language": en + - "@list": + - 1 + - 2 + - "@value": v5 + - "@value": plain literal diff --git a/spec/to_rdf_spec.rb b/spec/to_rdf_spec.rb new file mode 100644 index 0000000..9c15ffd --- /dev/null +++ b/spec/to_rdf_spec.rb @@ -0,0 +1,556 @@ +# coding: utf-8 +require_relative 'spec_helper' + +describe YAML_LD::API do + let(:logger) {RDF::Spec.logger} + + context ".toRdf" do + it "should implement RDF::Enumerable" do + expect(YAML_LD::API.toRdf({})).to be_a(RDF::Enumerable) + end + + context "unnamed nodes" do + { + "no @id" => [ + %q( + http://example.com/foo: bar + ), + %q([ "bar"^^xsd:string] .) + ], + "@id with _:a" => [ + %q( + "@id": _:a + http://example.com/foo: bar + ), + %q([ "bar"^^xsd:string] .) + ], + "@id with _:a and reference" => [ + %q( + "@id": _:a + http://example.com/foo: + "@id": _:a + ), + %q(_:a _:a .) + ], + }.each do |title, (js, ttl)| + it title do + ttl = "@prefix xsd: . #{ttl}" + expect(parse(js)).to be_equivalent_graph(ttl, logger: logger, inputDocument: js) + end + end + end + + context "nodes with @id" do + { + "with IRI" => [ + %q( + "@id": http://example.com/a + http://example.com/foo: bar + ), + %q( "bar" .) + ], + }.each do |title, (js, ttl)| + it title do + ttl = "@prefix xsd: . #{ttl}" + expect(parse(js)).to be_equivalent_graph(ttl, logger: logger, inputDocument: js) + end + end + + context "with relative IRIs" do + { + "base" => [ + %( + "@id": '' + "@type": "#{RDF::RDFS.Resource}" + ), + %( a <#{RDF::RDFS.Resource}> .) + ], + "relative" => [ + %( + "@id": a/b + "@type": "#{RDF::RDFS.Resource}" + ), + %( a <#{RDF::RDFS.Resource}> .) + ], + "hash" => [ + %( + "@id": "#a" + "@type": "#{RDF::RDFS.Resource}" + ), + %( a <#{RDF::RDFS.Resource}> .) + ], + }.each do |title, (js, ttl)| + it title do + ttl = "@prefix xsd: . #{ttl}" + expect(parse(js, base: "http://example.org/")).to be_equivalent_graph(ttl, logger: logger, inputDocument: js) + end + end + end + end + + context "typed nodes" do + { + "one type" => [ + %q( + "@type": "http://example.com/foo" + ), + %q([ a ] .) + ], + "two types" => [ + %q( + "@type": + - http://example.com/foo + - http://example.com/baz + ), + %q([ a , ] .) + ], + "blank node type" => [ + %q( + "@type": _:foo + ), + %q([ a _:foo ] .) + ] + }.each do |title, (js, ttl)| + it title do + ttl = "@prefix xsd: . #{ttl}" + expect(parse(js)).to be_equivalent_graph(ttl, logger: logger, inputDocument: js) + end + end + end + + context "key/value" do + { + "string" => [ + %q( + http://example.com/foo: bar + ), + %q([ "bar"^^xsd:string ] .) + ], + "strings" => [ + %q( + http://example.com/foo: + - bar + - baz + }), + %q([ "bar"^^xsd:string, "baz"^^xsd:string ] .) + ], + "IRI" => [ + %q( + http://example.com/foo: + "@id": http://example.com/bar + }), + %q([ ] .) + ], + "IRIs" => [ + %q( + http://example.com/foo: + - "@id": http://example.com/bar + - "@id": http://example.com/baz + }), + %q([ , ] .) + ], + }.each do |title, (js, ttl)| + it title do + ttl = "@prefix xsd: . #{ttl}" + expect(parse(js)).to be_equivalent_graph(ttl, logger: logger, inputDocument: js) + end + end + end + + context "literals" do + { + "plain literal" => + [ + %q( + "@id": http://greggkellogg.net/foaf#me + http://xmlns.com/foaf/0.1/name: Gregg Kellogg + ), + %q( "Gregg Kellogg" .) + ], + "explicit plain literal" => + [ + %q( + http://xmlns.com/foaf/0.1/name: + "@value": Gregg Kellogg + ), + %q(_:a "Gregg Kellogg"^^xsd:string .) + ], + "language tagged literal" => + [ + %q( + http://www.w3.org/2000/01/rdf-schema#label: + "@value": A plain literal with a lang tag. + "@language": en-us + ), + %q(_:a "A plain literal with a lang tag."@en-us .) + ], + "I18N literal with language" => + [ + %q( + - "@id": http://greggkellogg.net/foaf#me + http://xmlns.com/foaf/0.1/knows: + "@id": http://www.ivan-herman.net/foaf#me + - "@id": http://www.ivan-herman.net/foaf#me + http://xmlns.com/foaf/0.1/name: + "@value": Herman Iván + "@language": hu + ), + %q( + . + "Herman Iv\u00E1n"@hu . + ) + ], + "explicit datatyped literal" => + [ + %q( + "@id": http://greggkellogg.net/foaf#me + http://purl.org/dc/terms/created: + "@value": '1957-02-27' + "@type": http://www.w3.org/2001/XMLSchema#date + ), + %q( + "1957-02-27"^^ . + ) + ], + }.each do |title, (js, ttl)| + it title do + ttl = "@prefix xsd: . #{ttl}" + expect(parse(js)).to be_equivalent_graph(ttl, logger: logger, inputDocument: js) + end + end + + context "with @type: @json" do + { + "true": { + input: %( + "@context": + "@version": 1.1 + e: + "@id": http://example.org/vocab#bool + "@type": "@json" + e: true + ), + output:%( + @prefix ex: . + @prefix rdf: . + [ex:bool "true"^^rdf:JSON] . + ) + }, + "false": { + input: %( + "@context": + "@version": 1.1 + e: + "@id": http://example.org/vocab#bool + "@type": "@json" + e: false + ), + output: %( + @prefix ex: . + @prefix rdf: . + [ex:bool "false"^^rdf:JSON] . + ) + }, + "double": { + input: %( + "@context": + "@version": 1.1 + e: + "@id": http://example.org/vocab#double + "@type": "@json" + e: 1.23 + ), + output: %( + @prefix ex: . + @prefix rdf: . + [ex:double "1.23"^^rdf:JSON] . + ) + }, + #"double-zero": { + # input: %( + # "@context": + # "@version": 1.1 + # e: + # "@id": http://example.org/vocab#double + # "@type": "@json" + # e: 0.0e0 + # ), + # output: %( + # @prefix ex: . + # @prefix rdf: . + # [ex:double "0.0e0"^^rdf:JSON] . + # ) + #}, + "integer": { + input: %( + "@context": + "@version": 1.1 + e: + "@id": http://example.org/vocab#integer + "@type": "@json" + e: 123 + ), + output: %( + @prefix ex: . + @prefix rdf: . + [ex:integer "123"^^rdf:JSON] . + ) + }, + "string": { + input: %( + "@context": + "@version": 1.1 + e: + "@id": http://example.org/vocab#string + "@type": "@json" + e: string + ), + output: %( + @prefix ex: . + @prefix rdf: . + [ex:string "\\"string\\""^^rdf:JSON] . + ) + }, + "null": { + input: %( + "@context": + "@version": 1.1 + e: + "@id": http://example.org/vocab#null + "@type": "@json" + e: null + ), + output: %( + @prefix ex: . + @prefix rdf: . + [ex:null "null"^^rdf:JSON] . + ) + }, + "object": { + input: %( + "@context": + "@version": 1.1 + e: + "@id": http://example.org/vocab#object + "@type": "@json" + e: + foo: bar + ), + output: %( + @prefix ex: . + @prefix rdf: . + [ex:object """{"foo":"bar"}"""^^rdf:JSON] . + ) + }, + "array": { + input: %( + "@context": + "@version": 1.1 + e: + "@id": http://example.org/vocab#array + "@type": "@json" + e: + - foo: bar + ), + output: %( + @prefix ex: . + @prefix rdf: . + [ex:array """[{"foo":"bar"}]"""^^rdf:JSON] . + ) + }, + "c14n-arrays": { + input: %( + "@context": + "@version": 1.1 + e: + "@id": http://example.org/vocab#c14n + "@type": "@json" + e: + - 56 + - '1': [] + '10': + d: true + ), + output: %( + @prefix ex: . + @prefix rdf: . + [ex:c14n """[56,{"1":[],"10":null,"d":true}]"""^^rdf:JSON] . + ) + }, + "c14n-french": { + input: %( + "@context": + "@version": 1.1 + e: + "@id": http://example.org/vocab#c14n + "@type": "@json" + e: + peach: This sorting order + péché: is wrong according to French + pêche: but canonicalization MUST + sin: ignore locale + ), + output: %( + @prefix ex: . + @prefix rdf: . + [ex:c14n """{"peach":"This sorting order","péché":"is wrong according to French","pêche":"but canonicalization MUST","sin":"ignore locale"}"""^^rdf:JSON] . + ) + }, + "c14n-structures": { + input: %( + "@context": + "@version": 1.1 + e: + "@id": http://example.org/vocab#c14n + "@type": "@json" + e: + '1': + f: + f: hi + F: 5 + " ": 56 + '10': {} + '111': + - e: 'yes' + E: 'no' + '': empty + a: {} + A: {} + ), + output: %( + @prefix ex: . + @prefix rdf: . + [ex:c14n """{"":"empty","1":{" ":56,"f":{"F":5,"f":"hi"}},"10":{},"111":[{"E":"no","e":"yes"}],"A":{},"a":{}}"""^^rdf:JSON] . + ) + }, + "c14n-unicode": { + input: %( + "@context": + "@version": 1.1 + e: + "@id": http://example.org/vocab#c14n + "@type": "@json" + e: + Unnormalized Unicode: Å + ), + output: %( + @prefix ex: . + @prefix rdf: . + [ex:c14n """{"Unnormalized Unicode":"Å"}"""^^rdf:JSON] . + ) + }, + }.each do |title, params| + it title do + params[:output] = RDF::Graph.new << RDF::Turtle::Reader.new(params[:output]) + run_to_rdf params + end + end + end + end + + context "overriding keywords" do + { + "'url' for @id, 'a' for @type" => [ + %q( + "@context": + url: "@id" + a: "@type" + name: http://schema.org/name + url: http://example.com/about#gregg + a: http://schema.org/Person + name: Gregg Kellogg + ), + %q( + . + "Gregg Kellogg"^^xsd:string . + ) + ], + }.each do |title, (js, ttl)| + it title do + ttl = "@prefix xsd: . #{ttl}" + expect(parse(js)).to be_equivalent_graph(ttl, logger: logger, inputDocument: js) + end + end + end + + context "@direction" do + context "rdfDirection: null" do + { + "no language rtl": [ + %q( + http://example.org/label: + "@value": no language + "@direction": rtl + ), + %q(_:a "no language" .) + ], + "en-US rtl": [ + %q( + http://example.org/label: + "@value": en-US + "@language": en-US + "@direction": rtl + ), + %q(_:a "en-US"@en-us .) + ] + }.each do |title, (js, ttl)| + it title do + ttl = "@prefix xsd: . #{ttl}" + expect(parse(js, rdfDirection: nil)).to be_equivalent_graph(ttl, logger: logger, inputDocument: js) + end + end + end + + context "rdfDirection: i18n-datatype" do + { + "no language rtl": [ + %q( + http://example.org/label: + "@value": no language + "@direction": rtl + ), + %q(_:a "no language"^^ .) + ], + "en-US rtl": [ + %q( + http://example.org/label: + "@value": en-US + "@language": en-US + "@direction": rtl + ), + %q(_:a "en-US"^^ .) + ] + }.each do |title, (js, ttl)| + it title do + ttl = "@prefix xsd: . #{ttl}" + expect(parse(js, rdfDirection: 'i18n-datatype')).to be_equivalent_graph(ttl, logger: logger, inputDocument: js) + end + end + end + end + end + + def parse(input, **options) + graph = options[:graph] || RDF::Graph.new + options = {logger: logger, validate: true, canonicalize: false}.merge(options) + YAML_LD::API.toRdf(StringIO.new(input), rename_bnodes: false, **options) {|st| graph << st} + graph + end + + def run_to_rdf(params) + input, output = params[:input], params[:output] + graph = params[:graph] || RDF::Graph.new + input = StringIO.new(input) if input.is_a?(String) + pending params.fetch(:pending, "test implementation") unless input + if params[:exception] + expect {YAML_LD::API.toRdf(input, **params)}.to raise_error(params[:exception]) + else + if params[:write] + expect{YAML_LD::API.toRdf(input, base: params[:base], logger: logger, rename_bnodes: false, **params) {|st| graph << st}}.to write(params[:write]).to(:error) + else + expect{YAML_LD::API.toRdf(input, base: params[:base], logger: logger, rename_bnodes: false, **params) {|st| graph << st}}.not_to write.to(:error) + end + expect(graph).to be_equivalent_graph(output, logger: logger, inputDocument: input) + end + end +end diff --git a/spec/writer_spec.rb b/spec/writer_spec.rb new file mode 100644 index 0000000..77128e5 --- /dev/null +++ b/spec/writer_spec.rb @@ -0,0 +1,441 @@ +# coding: utf-8 +require_relative 'spec_helper' +require 'rdf/spec/writer' + +describe YAML_LD::Writer do + let(:logger) {RDF::Spec.logger} + + after(:each) {|example| puts logger.to_s if example.exception} + + it_behaves_like 'an RDF::Writer' do + let(:writer) {YAML_LD::Writer.new(StringIO.new, logger: logger)} + end + + describe ".for" do + [ + :yamlld, + "etc/doap.yamlld", + {file_name: 'etc/doap.yamlld'}, + {file_extension: 'yamlld'}, + {content_type: 'application/ld+yaml'}, + ].each do |arg| + it "discovers with #{arg.inspect}" do + expect(RDF::Reader.for(arg)).to eq YAML_LD::Reader + end + end + end + + context "simple tests" do + it "should use full URIs without base" do + input = %( .) + expect(serialize(input)).to produce_yamlld(%( + - "@id": + http://a/b + http://a/c: + - "@id": + http://a/d + ), logger) + end + + it "should use qname URIs with standard prefix" do + input = %( .) + expect(serialize(input, standard_prefixes: true)).to produce_yamlld(%( + "@context": + foaf: http://xmlns.com/foaf/0.1/ + "@id": foaf:b + foaf:c: + "@id": foaf:d + ), logger) + end + + it "should use qname URIs with parsed prefix" do + input = %( + . + "Rhythm Paradise"@en . + "Rhythm Tengoku"@en . + "rhythm-tengoku" . + ) + expect(serialize(input, prefixes: { + dc: "http://purl.org/dc/terms/", + frbr: "http://vocab.org/frbr/core#", + senet: "https://senet.org/ns#", + })).to produce_yamlld(%( + "@context": + dc: http://purl.org/dc/terms/ + frbr: "http://vocab.org/frbr/core#" + senet: "https://senet.org/ns#" + "@id": https://senet.org/gm + "@type": frbr:Work + dc:title: + "@language": en + "@value": Rhythm Paradise + senet:unofficialTitle: + "@language": en + "@value": Rhythm Tengoku + senet:urlkey: rhythm-tengoku + ), logger) + end + + it "should use CURIEs with empty prefix" do + input = %( .) + begin + expect(serialize(input, prefixes: { "" => RDF::Vocab::FOAF})). + to produce_yamlld(%( + "@context": + "": http://xmlns.com/foaf/0.1/ + "@id": ":b" + ":c": + "@id": ":d" + ), logger) + rescue YAML_LD::JsonLdError, YAML_LD::JsonLdError, TypeError => e + fail("#{e.class}: #{e.message}\n" + + "#{logger}\n" + + "Backtrace:\n#{e.backtrace.join("\n")}") + end + end + + it "should not use terms if no suffix" do + input = %( .) + expect(serialize(input, standard_prefixes: true)). + not_to produce_yamlld(%( + "@context": + foaf: http://xmlns.com/foaf/0.1/ + "@id": foaf + foaf: + "@id": foaf + ), logger) + end + + it "should not use CURIE with illegal local part" do + input = %( + @prefix db: . + @prefix dbo: . + db:Michael_Jackson dbo:artistOf . + ) + + expect(serialize(input, prefixes: { + "db" => RDF::URI("http://dbpedia.org/resource/"), + "dbo" => RDF::URI("http://dbpedia.org/ontology/")})). + to produce_yamlld(%( + "@context": + db: http://dbpedia.org/resource/ + dbo: http://dbpedia.org/ontology/ + "@id": db:Michael_Jackson + dbo:artistOf: + "@id": db:%28I_Can%27t_Make_It%29_Another_Day + ), logger) + end + + it "should not use provided node identifiers if :unique_bnodes set" do + input = %(_:a _:b \.) + result = serialize(input, unique_bnodes: true, context: {}) + expect(result.to_yaml).to match(%r(_:g\w+)) + end + + it "serializes multiple subjects" do + input = %q( + @prefix : . + @prefix dc: . + a :TestCase . + a :TestCase . + ) + expect(serialize(input, prefixes: {"" => "http://www.w3.org/2006/03/test-description#"})). + to produce_yamlld(%( + "@context": + "": http://www.w3.org/2006/03/test-description# + dc: http://purl.org/dc/terms/ + "@graph": + - "@id": http://example.com/test-cases/0001 + "@type": ":TestCase" + - "@id": http://example.com/test-cases/0002 + "@type": ":TestCase" + ), logger) + end + + it "serializes Wikia OWL example" do + input = %q( + @prefix owl: . + @prefix rdf: . + @prefix rdfs: . + @prefix xsd: . + + a owl:Class; + rdfs:subClassOf _:a . + _:a a owl:Restriction; + owl:minQualifiedCardinality "1"^^xsd:nonNegativeInteger; + owl:onClass ; + owl:onProperty . + ) + expect(serialize(input, rename_bnodes: false, prefixes: { + owl: "http://www.w3.org/2002/07/owl#", + rdfs: "http://www.w3.org/2000/01/rdf-schema#", + xsd: "http://www.w3.org/2001/XMLSchema#" + })). + to produce_yamlld(%( + "@context": + owl: http://www.w3.org/2002/07/owl# + rdf: http://www.w3.org/1999/02/22-rdf-syntax-ns# + rdfs: http://www.w3.org/2000/01/rdf-schema# + xsd: http://www.w3.org/2001/XMLSchema# + "@graph": + - "@id": _:a + "@type": owl:Restriction + owl:minQualifiedCardinality: + "@value": "1" + "@type": xsd:nonNegativeInteger + owl:onClass: + "@id": http://data.wikia.com/terms#Element + owl:onProperty: + "@id": http://data.wikia.com/terms#characterIn + - "@id": http://data.wikia.com/terms#Character + "@type": owl:Class + rdfs:subClassOf: + "@id": _:a + ), logger) + end + end + + context "RDF-star" do + { + "subject-iii": { + input: RDF::Statement( + RDF::Statement( + RDF::URI('http://example/s1'), + RDF::URI('http://example/p1'), + RDF::URI('http://example/o1')), + RDF::URI('http://example/p'), + RDF::URI('http://example/o')), + output: %( + "@context": + ex: http://example/ + "@id": + "@id": ex:s1 + ex:p1: + "@id": ex:o1 + ex:p: + "@id": ex:o + ) + }, + "subject-iib": { + input: RDF::Statement( + RDF::Statement( + RDF::URI('http://example/s1'), + RDF::URI('http://example/p1'), + RDF::Node.new('o1')), + RDF::URI('http://example/p'), + RDF::URI('http://example/o')), + output: %( + "@context": + ex: http://example/ + "@id": + "@id": ex:s1 + ex:p1: + "@id": _:o1 + ex:p: + "@id": ex:o + ) + }, + "subject-iil": { + input: RDF::Statement( + RDF::Statement( + RDF::URI('http://example/s1'), + RDF::URI('http://example/p1'), + RDF::Literal('o1')), + RDF::URI('http://example/p'), + RDF::URI('http://example/o')), + output: %( + "@context": + ex: http://example/ + "@id": + "@id": ex:s1 + ex:p1: o1 + ex:p: + "@id": ex:o + ) + }, + "subject-bii": { + input: RDF::Statement( + RDF::Statement( + RDF::Node('s1'), + RDF::URI('http://example/p1'), + RDF::URI('http://example/o1')), + RDF::URI('http://example/p'), + RDF::URI('http://example/o')), + output: %( + "@context": + ex: http://example/ + "@id": + "@id": _:s1 + ex:p1: + "@id": ex:o1 + ex:p: + "@id": ex:o + ) + }, + "subject-bib": { + input: RDF::Statement( + RDF::Statement( + RDF::Node('s1'), + RDF::URI('http://example/p1'), + RDF::Node.new('o1')), + RDF::URI('http://example/p'), RDF::URI('http://example/o')), + output: %( + "@context": + ex: http://example/ + "@id": + "@id": _:s1 + ex:p1: + "@id": _:o1 + ex:p: + "@id": ex:o + ) + }, + "subject-bil": { + input: RDF::Statement( + RDF::Statement( + RDF::Node('s1'), + RDF::URI('http://example/p1'), + RDF::Literal('o1')), + RDF::URI('http://example/p'), + RDF::URI('http://example/o')), + output: %( + "@context": + ex: http://example/ + "@id": + "@id": _:s1 + ex:p1: o1 + ex:p: + "@id": ex:o + ) + }, + "object-iii": { + input: RDF::Statement( + RDF::URI('http://example/s'), + RDF::URI('http://example/p'), + RDF::Statement( + RDF::URI('http://example/s1'), + RDF::URI('http://example/p1'), + RDF::URI('http://example/o1'))), + output: %( + "@context": + ex: http://example/ + "@id": ex:s + ex:p: + "@id": + "@id": ex:s1 + ex:p1: + "@id": ex:o1 + ) + }, + "object-iib": { + input: RDF::Statement( + RDF::URI('http://example/s'), + RDF::URI('http://example/p'), + RDF::Statement( + RDF::URI('http://example/s1'), + RDF::URI('http://example/p1'), + RDF::Node.new('o1'))), + output: %( + "@context": + ex: http://example/ + "@id": ex:s + ex:p: + "@id": + "@id": ex:s1 + ex:p1: + "@id": _:o1 + ) + }, + "object-iil": { + input: RDF::Statement( + RDF::URI('http://example/s'), + RDF::URI('http://example/p'), + RDF::Statement( + RDF::URI('http://example/s1'), + RDF::URI('http://example/p1'), + RDF::Literal('o1'))), + output: %( + "@context": + ex: http://example/ + "@id": ex:s + ex:p: + "@id": + "@id": ex:s1 + ex:p1: o1 + ) + }, + "recursive-subject": { + input: RDF::Statement( + RDF::Statement( + RDF::Statement( + RDF::URI('http://example/s2'), + RDF::URI('http://example/p2'), + RDF::URI('http://example/o2')), + RDF::URI('http://example/p1'), + RDF::URI('http://example/o1')), + RDF::URI('http://example/p'), + RDF::URI('http://example/o')), + output: %( + "@context": + ex: http://example/ + "@id": + "@id": + "@id": ex:s2 + ex:p2: + "@id": ex:o2 + ex:p1: + "@id": ex:o1 + ex:p: + "@id": ex:o + ) + }, + }.each do |name, params| + it name do + graph = RDF::Graph.new {|g| g << params[:input]} + expect( + serialize(graph, rdfstar: true, prefixes: {ex: 'http://example/'}) + ).to produce_yamlld(params[:output], logger) + end + end + end + + #context "Writes fromRdf tests to isomorphic graph" do + # require 'suite_helper' + # m = Fixtures::SuiteTest::Manifest.open("#{Fixtures::SuiteTest::SUITE}fromRdf-manifest.jsonld") + # describe m.name do + # m.entries.each do |t| + # next unless t.positiveTest? && !t.property('input').include?('0016') + # specify "#{t.property('@id')}: #{t.name}" do + # logger.info "test: #{t.inspect}" + # logger.info "source: #{t.input}" + # t.logger = logger + # pending "Shared list BNode in different graphs" if t.property('input').include?("fromRdf-0021") + # repo = RDF::Repository.load(t.input_loc, format: :nquads) + # jsonld = YAML_LD::Writer.buffer(logger: t.logger, **t.options) do |writer| + # writer << repo + # end + # + # # And then, re-generate jsonld as RDF + # + # expect(parse(jsonld, format: :jsonld, **t.options)).to be_equivalent_graph(repo, t) + # end + # end + # end + #end unless ENV['CI'] + + def parse(input, format: :trig, **options) + reader = RDF::Reader.for(format) + RDF::Repository.new << reader.new(input, **options) + end + + # Serialize ntstr to a string and compare against regexps + def serialize(ntstr, **options) + g = ntstr.is_a?(String) ? parse(ntstr, **options) : ntstr + #logger.info g.dump(:ttl) + result = YAML_LD::Writer.buffer(logger: logger, **options) do |writer| + writer << g + end + + Psych.safe_load(result, aliases: true) + end +end diff --git a/yaml-ld.gemspec b/yaml-ld.gemspec new file mode 100755 index 0000000..26c9cfb --- /dev/null +++ b/yaml-ld.gemspec @@ -0,0 +1,46 @@ +#!/usr/bin/env ruby -rubygems +# -*- encoding: utf-8 -*- + +Gem::Specification.new do |gem| + gem.version = File.read('VERSION').chomp + gem.date = File.mtime('VERSION').strftime('%Y-%m-%d') + + gem.name = "yaml-ld" + gem.homepage = "https://github.com/ruby-rdf/yaml-ld" + gem.license = 'Unlicense' + gem.summary = "YAML-LD reader/writer for Ruby." + gem.description = "YAML_LD parses and serializes YAML-LD into RDF and implements expansion, compaction and framing API interfaces for the Ruby RDF.rb library suite." + gem.metadata = { + "documentation_uri" => "https://ruby-rdf.github.io/yaml-ld", + "bug_tracker_uri" => "https://github.com/ruby-rdf/yaml-ld/issues", + "homepage_uri" => "https://github.com/ruby-rdf/yaml-ld", + "mailing_list_uri" => "https://lists.w3.org/Archives/Public/public-rdf-ruby/", + "source_code_uri" => "https://github.com/ruby-rdf/yaml-ld", + } + + gem.authors = ['Gregg Kellogg'] + gem.email = 'public-linked-json@w3.org' + + gem.platform = Gem::Platform::RUBY + gem.files = %w(AUTHORS README.md UNLICENSE VERSION) + Dir.glob('lib/**/*.rb') + gem.bindir = %q(bin) + gem.require_paths = %w(lib) + gem.test_files = Dir.glob('spec/**/*.rb') + Dir.glob('spec/test-files/*') + + gem.required_ruby_version = '>= 2.6' + gem.requirements = [] + gem.add_runtime_dependency 'json-ld', '~> 3.2', '>= 3.2.2' + gem.add_runtime_dependency 'psych', '~> 4.0' + gem.add_runtime_dependency 'rdf', '~> 3.2' + gem.add_development_dependency 'rdf-isomorphic', '~> 3.2' + gem.add_development_dependency 'rdf-spec', '~> 3.2' + gem.add_development_dependency 'rdf-trig', '~> 3.2' + gem.add_development_dependency 'rdf-turtle', '~> 3.2' + gem.add_development_dependency 'rdf-vocab', '~> 3.2' + gem.add_development_dependency 'rdf-xsd', '~> 3.2' + gem.add_development_dependency 'rspec', '~> 3.10' + gem.add_development_dependency 'rspec-its', '~> 1.3' + gem.add_development_dependency 'yard' , '~> 0.9' + + gem.post_install_message = nil +end