├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .rustfmt.toml ├── .vscode └── launch.json ├── Cargo.toml ├── LICENSE ├── README.md ├── generators └── rust │ ├── generator │ ├── Cargo.toml │ └── src │ │ ├── bin │ │ └── main.rs │ │ ├── lib.rs │ │ └── utils.rs │ ├── treeldr-rs-macros │ ├── Cargo.toml │ └── src │ │ ├── generate │ │ ├── de.rs │ │ ├── mod.rs │ │ └── ser.rs │ │ ├── lib.rs │ │ └── parse.rs │ └── treeldr-rs │ ├── Cargo.toml │ ├── src │ ├── datatypes.rs │ ├── de │ │ ├── matching.rs │ │ └── mod.rs │ ├── lib.rs │ ├── pattern.rs │ ├── rdf.rs │ ├── ser │ │ ├── environment.rs │ │ └── mod.rs │ └── utils.rs │ └── tests │ ├── id.rs │ ├── list.rs │ ├── literal.rs │ ├── record.rs │ └── sum.rs ├── language ├── README.md └── extensions │ ├── README.md │ └── vscode │ ├── .vscode │ └── launch.json │ ├── .vscodeignore │ ├── CHANGELOG.md │ ├── README.md │ ├── grammar.json │ ├── language-configuration.json │ ├── local-install.sh │ ├── main.js │ ├── package.json │ ├── snippets.json │ └── vscode ├── layouts ├── .gitignore ├── Cargo.toml ├── README.md ├── book │ ├── .gitignore │ ├── README.md │ ├── book.toml │ └── src │ │ ├── README.md │ │ ├── SUMMARY.md │ │ ├── abstract-layouts │ │ ├── README.md │ │ ├── intersection.md │ │ └── union.md │ │ ├── abstract-syntax │ │ └── README.md │ │ ├── algorithms │ │ ├── README.md │ │ ├── deserialization.md │ │ └── serialization.md │ │ ├── data-model │ │ ├── README.md │ │ ├── lists.md │ │ ├── literals.md │ │ ├── paths.md │ │ ├── records.md │ │ ├── types.md │ │ └── values.md │ │ ├── layouts │ │ ├── README.md │ │ ├── functional-layouts.md │ │ ├── lists.md │ │ ├── literals.md │ │ ├── product.md │ │ ├── record.md │ │ └── sum.md │ │ ├── rdf-basics.md │ │ └── rdf-vocabulary │ │ ├── README.md │ │ ├── layouts-of-layouts.md │ │ └── schema.md ├── examples │ ├── combine.rs │ └── record.json ├── prelude │ ├── boolean.json │ ├── i16.json │ ├── i32.json │ ├── i64.json │ ├── i8.json │ ├── id.json │ ├── string.json │ ├── u16.json │ ├── u32.json │ ├── u64.json │ ├── u8.json │ └── unit.json ├── src │ ├── abs │ │ ├── layout │ │ │ ├── always.rs │ │ │ ├── intersection.rs │ │ │ ├── list │ │ │ │ ├── mod.rs │ │ │ │ └── ordered.rs │ │ │ ├── literal │ │ │ │ ├── data.rs │ │ │ │ ├── id.rs │ │ │ │ └── mod.rs │ │ │ ├── mod.rs │ │ │ ├── never.rs │ │ │ ├── product.rs │ │ │ ├── sum.rs │ │ │ └── union.rs │ │ ├── mod.rs │ │ ├── regexp.rs │ │ └── syntax │ │ │ ├── build.rs │ │ │ ├── dataset.rs │ │ │ ├── layout │ │ │ ├── intersection.rs │ │ │ ├── list.rs │ │ │ ├── literal.rs │ │ │ ├── mod.rs │ │ │ ├── product.rs │ │ │ ├── sum.rs │ │ │ └── union.rs │ │ │ ├── mod.rs │ │ │ ├── pattern │ │ │ ├── compact_iri.rs │ │ │ ├── literal.rs │ │ │ ├── mod.rs │ │ │ └── variable.rs │ │ │ └── resource.rs │ ├── distill │ │ ├── de │ │ │ ├── data.rs │ │ │ └── mod.rs │ │ ├── hy │ │ │ ├── data.rs │ │ │ └── mod.rs │ │ └── mod.rs │ ├── format.rs │ ├── graph.rs │ ├── layout │ │ ├── always.rs │ │ ├── intersection.rs │ │ ├── list │ │ │ ├── mod.rs │ │ │ ├── ordered.rs │ │ │ ├── sized.rs │ │ │ └── unordered.rs │ │ ├── literal │ │ │ ├── data.rs │ │ │ ├── id.rs │ │ │ └── mod.rs │ │ ├── mod.rs │ │ ├── never.rs │ │ ├── product.rs │ │ ├── sum.rs │ │ └── union.rs │ ├── lib.rs │ ├── matching.rs │ ├── pattern.rs │ ├── prelude.rs │ ├── preset.rs │ ├── ref.rs │ ├── utils │ │ ├── automaton.rs │ │ └── mod.rs │ └── value │ │ ├── cbor │ │ ├── mod.rs │ │ └── serde_cbor.rs │ │ ├── de.rs │ │ ├── mod.rs │ │ ├── ser.rs │ │ └── serde_cbor.rs └── tests │ ├── distill.rs │ └── distill │ ├── e01-in.nq │ ├── e01-layout.json │ ├── e01-out.json │ ├── t01-in.nq │ ├── t01-layout.json │ ├── t01-out.json │ ├── t02-in.nq │ ├── t02-layout.json │ ├── t02-out.json │ ├── t03-in.nq │ ├── t03-layout.json │ ├── t03-out.json │ ├── t04-in.nq │ ├── t04-layout.json │ ├── t04-out.json │ ├── t05-in.nq │ ├── t05-layout.json │ ├── t05-out.json │ ├── t06-in.nq │ ├── t06-layout.json │ ├── t06-out.json │ ├── t07-in.nq │ ├── t07-layout.json │ ├── t07-out.json │ ├── t08-in.nq │ ├── t08-layout.json │ ├── t08-out.json │ ├── t09-in.nq │ ├── t09-layout.json │ ├── t09-out.json │ ├── t10-in.nq │ ├── t10-layout.json │ ├── t10-out.json │ ├── t11-in.nq │ ├── t11-layout.json │ ├── t11-out.json │ ├── t12-in.nq │ ├── t12-layout.json │ ├── t12-out.json │ ├── t13-in.nq │ ├── t13-layout.json │ ├── t13-out.json │ ├── t14-in.nq │ ├── t14-layout.json │ ├── t14-out.json │ ├── t15-in.nq │ ├── t15-layout.json │ ├── t15-out.json │ ├── t16-in.nq │ ├── t16-layout.json │ ├── t16-out.json │ ├── t17-in.nq │ ├── t17-layout.json │ ├── t17-out.json │ ├── t18-in.nq │ ├── t18-layout.json │ └── t18-out.json └── src ├── format ├── mod.rs ├── rdf.rs └── tree.rs ├── main.rs └── rdf.rs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | types: 9 | - opened 10 | - reopened 11 | - synchronize 12 | - ready_for_review 13 | 14 | env: 15 | CARGO_TERM_COLORS: always 16 | 17 | jobs: 18 | test: 19 | name: Test 20 | runs-on: ubuntu-latest 21 | if: ${{ !github.event.pull_request.draft }} 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v2 25 | - name: Retrieve MSRV 26 | id: msrv 27 | run: echo "MSRV=$(cat Cargo.toml | grep -Po '(?<=rust-version = ")([\d\.]+)')" >> $GITHUB_OUTPUT 28 | - name: Install Rust 29 | uses: dtolnay/rust-toolchain@master 30 | with: 31 | toolchain: ${{ steps.msrv.outputs.MSRV }} 32 | - name: Build 33 | run: cargo build --all-features --verbose 34 | - name: Run tests 35 | run: cargo test --all-features --verbose --all-targets 36 | 37 | lint: 38 | name: Lint 39 | runs-on: ubuntu-latest 40 | steps: 41 | - name: Checkout 42 | uses: actions/checkout@v2 43 | - name: Install Rust 44 | uses: dtolnay/rust-toolchain@stable 45 | with: 46 | components: rustfmt, clippy 47 | - name: Check formatting 48 | run: cargo fmt --all -- --check 49 | - name: Clippy 50 | run: cargo clippy --all-features -- -D warnings -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | hard_tabs = true -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "lldb", 6 | "request": "launch", 7 | "name": "Debug", 8 | "program": "${workspaceFolder}/", 9 | "args": [], 10 | "cwd": "${workspaceFolder}" 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tldr" 3 | description = "TreeLDR Command Line Interface" 4 | authors.workspace = true 5 | edition.workspace = true 6 | rust-version.workspace = true 7 | version.workspace = true 8 | license.workspace = true 9 | 10 | [workspace] 11 | members = [ 12 | "layouts", 13 | "generators/rust/treeldr-rs", 14 | "generators/rust/treeldr-rs-macros", 15 | "generators/rust/generator" 16 | ] 17 | resolver = "2" 18 | 19 | [workspace.package] 20 | authors = ["Spruce Systems Inc."] 21 | edition = "2021" 22 | rust-version = "1.74.0" 23 | version = "0.2.0" 24 | license = "Apache-2.0 OR MIT" 25 | 26 | [workspace.dependencies] 27 | treeldr-layouts = { path = "layouts", version = "0.2.0" } 28 | treeldr-macros = { path = "generators/rust/treeldr-rs-macros", version = "0.2.0" } 29 | treeldr-gen-rust = { path = "generators/rust/generator", version = "0.2.0" } 30 | 31 | log = "0.4" 32 | educe = "0.4.23" 33 | num-traits = "0.2" 34 | num-bigint = "0.4" 35 | num-rational = "0.4" 36 | iref = "3.1.4" 37 | static-iref = "3.0" 38 | rdf-types = "0.22.5" 39 | xsd-types = "0.9.2" 40 | btree-range-map = { version = "0.7.2", features = ["serde"] } 41 | langtag = "0.4.0" 42 | thiserror = "1.0.50" 43 | serde = "1.0.192" 44 | serde_json = { version = "1.0", features = ["arbitrary_precision"] } 45 | json-syntax = "0.12.3" 46 | serde_cbor = "0.11.2" 47 | codespan-reporting = "0.11.1" 48 | 49 | locspan = "0.8.2" 50 | nquads-syntax = "0.19.0" 51 | 52 | clap = "4.0" 53 | stderrlog = "0.6" 54 | 55 | syn = "2.0.29" 56 | proc-macro2 = "1.0.66" 57 | quote = "1.0.33" 58 | 59 | [dependencies] 60 | treeldr-layouts = { workspace = true, features = ["serde_cbor"] } 61 | clap = { workspace = true, features = ["derive"] } 62 | stderrlog.workspace = true 63 | nquads-syntax.workspace = true 64 | json-syntax.workspace = true 65 | serde_cbor = { workspace = true, features = ["tags"] } 66 | codespan-reporting.workspace = true 67 | thiserror.workspace = true 68 | iref.workspace = true 69 | rdf-types.workspace = true 70 | locspan.workspace = true 71 | utf8-decode = "1.0.1" 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TreeLDR 2 | 3 | TreeLDR is Linked-Data serialization framework providing: 4 | - RDF layouts to serialize and deserialize RDF datasets from/to tree data; 5 | - A concise schema definition language for RDF classes and layouts; 6 | - A set of boilerplate code generator. 7 | 8 | TreeLDR can be used to produce JSON Schemas, JSON-LD contexts, migration 9 | strategies, blockchain publishing routines, etc. and entire SDKs in various 10 | target programming languages such as Python, Java and more. This way, developers 11 | can define data structures in a familiar way and focus purely on the application 12 | level. 13 | 14 | > [!IMPORTANT] 15 | > TreeLDR has experienced major changes with version 0.2. In particular, we 16 | > formalized the data model of Layouts, changing their definition in a way that 17 | > is incompatible with our current implementation of the DSL and (some) 18 | > generators. We will reintroduce the missing DSL and generators in future 19 | > updates. 20 | 21 | ## Install 22 | 23 | You will need [Rust](https://rust-lang.org) 1.74 or later 24 | to install TreeLDR, with [cargo](https://doc.rust-lang.org/cargo/). 25 | 26 | TreeLDR can be installed from the source by first cloning 27 | the git repository: 28 | ```console 29 | $ git clone https://github.com/spruceid/treeldr.git 30 | ``` 31 | 32 | This repository contains the different libraries and executables composing 33 | the TreeLDR framework. 34 | You can then build everything from the repository root: 35 | ```console 36 | $ cargo build 37 | ``` 38 | 39 | Alternatively if you want to install the binaries on you computer, use 40 | ```console 41 | $ cargo install --path . 42 | ``` 43 | 44 | ## Usage 45 | 46 | The top-level package provides a command-line interface which can use TreeLDR 47 | layouts to serialize or deserialize tree value (like JSON), or generate code. 48 | If you want to use TreeLDR layouts directly in your code, use the 49 | [`treeldr-layouts` library](layouts). 50 | 51 | In this section, the `tldr` command can be replaced with `cargo run -- ` if 52 | you chose not to install the binary. 53 | 54 | ### Deserialization 55 | 56 | Use the `dehydrate` subcommand to turn any *tree value* (JSON) into an RDF 57 | dataset using a given layout. 58 | The input tree is read from the standard input, and the output written to the 59 | standard output. 60 | 61 | ```console 62 | $ tldr path/to/layout.json dehydrate 63 | ``` 64 | 65 | Example layouts are found in the `layouts/examples` folder. 66 | ```console 67 | $ echo '{"id": "http://example.org/#bob", "name": "Bob"}' | tldr layouts/examples/record.json dehydrate 68 | "Bob" . 69 | ``` 70 | 71 | You can specify the input (tree) format using the `-i` option after `dehydrate`. 72 | Similarly, you can specify the output (RDF) format using the `-o` option. 73 | By default the input is expected to be JSON and output is N-Quads. 74 | Supported formats are given in the [Supported Formats](#supported-formats) 75 | section below. 76 | 77 | ### Serialization 78 | 79 | Use `hydrate` subcommand to turn any *RDF dataset* into a tree value (JSON) 80 | using a given layout. 81 | The input dataset is read from the standard input, and the output written to the 82 | standard output. 83 | 84 | ```console 85 | $ tldr path/to/layout.json hydrate 86 | ``` 87 | 88 | Example layouts are found in the `layouts/examples` folder. 89 | ```console 90 | $ echo ' "Bob" .' | tldr layouts/examples/record.json hydrate 'http://example.org/#bob' 91 | {"id":"http://example.org/#bob","name":"Bob"} 92 | ``` 93 | 94 | You can specify the input (RDF) format using the `-i` option after `hydrate`. 95 | Similarly, you can specify the output (tree) format using the `-o` option. 96 | By default the input is expected to be N-Quads and output is JSON. 97 | Supported formats are given in the [Supported Formats](#supported-formats) 98 | section below. 99 | 100 | ### Supported formats 101 | 102 | The following table lists all the tree formats supported by TreeLDR. 103 | The "Option value" can be given to the `-i` option of the `dehydrate` 104 | subcommand, or the `-o` option of the `hydrate` subcommand. 105 | 106 | | Tree format | Option value(s) | 107 | | ----------- | ------------------------------------------------ | 108 | | JSON | `application/json`, `json` | 109 | | CBOR | `application/cbor`, `cbor` | 110 | 111 | The following table lists all the RDF formats supported by TreeLDR. 112 | The "Option value" can be given to the `-i` option of the `hydrate` subcommand, 113 | or the `-i` option of the `dehydrate` subcommand. 114 | 115 | | RDF format | Option value(s) | 116 | | ----------- | ------------------------------------------------ | 117 | | N-Quads | `application/n-quads`, `n-quads`, `nquads`, `nq` | 118 | 119 | ## Tesing 120 | 121 | To run all the tests, use the following command: 122 | ```console 123 | $ cargo test --workspace --all-features 124 | ``` -------------------------------------------------------------------------------- /generators/rust/generator/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "treeldr-gen-rust" 3 | description = "TreeLDR Layouts to Rust" 4 | version.workspace = true 5 | authors.workspace = true 6 | edition.workspace = true 7 | rust-version.workspace = true 8 | 9 | [dependencies] 10 | treeldr-layouts.workspace = true 11 | rdf-types.workspace = true 12 | iref.workspace = true 13 | syn.workspace = true 14 | proc-macro2.workspace = true 15 | quote.workspace = true 16 | log.workspace = true 17 | thiserror.workspace = true 18 | 19 | clap = { workspace = true, features = ["derive"] } 20 | stderrlog.workspace = true 21 | serde_json.workspace = true -------------------------------------------------------------------------------- /generators/rust/generator/src/bin/main.rs: -------------------------------------------------------------------------------- 1 | use iref::IriBuf; 2 | use rdf_types::Term; 3 | use std::{fs, path::PathBuf, process::ExitCode}; 4 | use treeldr_layouts::{abs, distill::RdfContext, layout::LayoutType, Ref}; 5 | 6 | #[derive(clap::Parser)] 7 | #[clap(name="tldr-rs", author, version, about, long_about = None)] 8 | struct Args { 9 | /// Input files. 10 | filenames: Vec, 11 | 12 | /// Layout to generate. 13 | #[clap(long, short)] 14 | layout: Option, 15 | 16 | /// Sets the level of verbosity. 17 | #[clap(short, long = "verbose", action = clap::ArgAction::Count)] 18 | verbosity: u8, 19 | } 20 | 21 | enum DefaultLayoutRef { 22 | Unknown, 23 | Some(Ref), 24 | None, 25 | } 26 | 27 | impl DefaultLayoutRef { 28 | pub fn set(&mut self, layout_ref: Ref) { 29 | match self { 30 | Self::Unknown => *self = Self::Some(layout_ref), 31 | Self::Some(_) => *self = Self::None, 32 | Self::None => (), 33 | } 34 | } 35 | } 36 | 37 | fn main() -> ExitCode { 38 | // Parse options. 39 | let args: Args = clap::Parser::parse(); 40 | 41 | // Initialize logger. 42 | stderrlog::new() 43 | .verbosity(args.verbosity as usize) 44 | .init() 45 | .unwrap(); 46 | 47 | // Initialize the layout builder. 48 | let mut builder = abs::Builder::new(); 49 | 50 | let mut default_layout_ref = DefaultLayoutRef::Unknown; 51 | 52 | for filename in args.filenames { 53 | let content = fs::read_to_string(filename).unwrap(); 54 | 55 | match serde_json::from_str::(&content) { 56 | Ok(abstract_layout) => match abstract_layout.build(&mut builder) { 57 | Ok(layout_ref) => default_layout_ref.set(layout_ref), 58 | Err(e) => { 59 | log::error!("compile error: {e}"); 60 | return ExitCode::FAILURE; 61 | } 62 | }, 63 | Err(e) => { 64 | log::error!("parse error: {e}") 65 | } 66 | } 67 | } 68 | 69 | let layouts = builder.build(); 70 | 71 | let layout_ref = match args.layout { 72 | Some(iri) => { 73 | let term = Term::iri(iri); 74 | if layouts.layout(&term).is_some() { 75 | Ref::new(term) 76 | } else { 77 | log::error!("unknown layout {term}"); 78 | return ExitCode::FAILURE; 79 | } 80 | } 81 | None => match default_layout_ref { 82 | DefaultLayoutRef::Some(layout_ref) => layout_ref, 83 | _ => { 84 | log::error!("missing layout"); 85 | return ExitCode::FAILURE; 86 | } 87 | }, 88 | }; 89 | 90 | let gen_options = treeldr_gen_rust::Options::new(); 91 | 92 | let result = 93 | treeldr_gen_rust::generate(RdfContext::default(), &layouts, &layout_ref, &gen_options); 94 | 95 | match result { 96 | Ok(r) => { 97 | println!("{r}"); 98 | ExitCode::SUCCESS 99 | } 100 | Err(e) => { 101 | log::error!("parse error: {e}"); 102 | ExitCode::FAILURE 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /generators/rust/generator/src/utils.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | use iref::Iri; 3 | 4 | pub fn ident_from_iri(iri: &Iri) -> Option { 5 | match iri.fragment() { 6 | Some(fragment) => syn::parse_str(PascalCase(fragment).to_string().as_str()).ok(), 7 | None => iri 8 | .path() 9 | .segments() 10 | .last() 11 | .and_then(|segment| syn::parse_str(PascalCase(segment).to_string().as_str()).ok()), 12 | } 13 | } 14 | 15 | pub struct PascalCase(pub T); 16 | 17 | impl> fmt::Display for PascalCase { 18 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 19 | let mut upcase = true; 20 | 21 | for c in self.0.as_ref().chars() { 22 | if c.is_whitespace() || c.is_control() || c == '_' { 23 | // ignore. 24 | upcase = true 25 | } else if upcase { 26 | c.to_uppercase().fmt(f)?; 27 | upcase = false 28 | } else { 29 | c.fmt(f)? 30 | } 31 | } 32 | 33 | Ok(()) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /generators/rust/treeldr-rs-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "treeldr-macros" 3 | description = "TreeLDR macros" 4 | version.workspace = true 5 | authors.workspace = true 6 | edition.workspace = true 7 | rust-version.workspace = true 8 | 9 | [lib] 10 | proc-macro = true 11 | 12 | [dependencies] 13 | treeldr-layouts.workspace = true 14 | treeldr-gen-rust.workspace = true 15 | thiserror.workspace = true 16 | rdf-types.workspace = true 17 | iref.workspace = true 18 | static-iref.workspace = true 19 | syn.workspace = true 20 | proc-macro2.workspace = true 21 | quote.workspace = true 22 | serde_json.workspace = true 23 | proc-macro-error = "1.0.4" -------------------------------------------------------------------------------- /generators/rust/treeldr-rs-macros/src/generate/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod de; 2 | pub mod ser; 3 | -------------------------------------------------------------------------------- /generators/rust/treeldr-rs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "treeldr" 3 | description = "TreeLDR interface with Rust" 4 | version.workspace = true 5 | authors.workspace = true 6 | edition.workspace = true 7 | rust-version.workspace = true 8 | 9 | [features] 10 | default = ["macros"] 11 | macros = ["treeldr-macros"] 12 | 13 | [dependencies] 14 | thiserror.workspace = true 15 | educe.workspace = true 16 | iref.workspace = true 17 | langtag.workspace = true 18 | rdf-types.workspace = true 19 | xsd-types.workspace = true 20 | treeldr-macros = { workspace = true, optional = true } -------------------------------------------------------------------------------- /generators/rust/treeldr-rs/src/de/matching.rs: -------------------------------------------------------------------------------- 1 | use rdf_types::{dataset::PatternMatchingDataset, pattern::CanonicalQuadPattern, Quad}; 2 | 3 | use crate::{pattern::Substitution, Pattern}; 4 | 5 | pub enum Error { 6 | Ambiguity, 7 | Empty, 8 | } 9 | 10 | pub struct Matching<'a, 'p, D, Q> 11 | where 12 | D: PatternMatchingDataset, 13 | { 14 | dataset: &'a D, 15 | stack: Vec>, 16 | } 17 | 18 | pub struct State<'a, 'p, D, Q> 19 | where 20 | D: 'a + PatternMatchingDataset, 21 | { 22 | substitution: Substitution, 23 | quad_state: Option>, 24 | rest: Q, 25 | } 26 | 27 | pub struct QuadState<'a, 'p, D> 28 | where 29 | D: 'a + PatternMatchingDataset, 30 | { 31 | pattern: Quad>, 32 | quad_matching: D::QuadPatternMatching<'a, 'p>, 33 | } 34 | 35 | impl<'a, D, Q> Matching<'a, '_, D, Q> 36 | where 37 | D: PatternMatchingDataset, 38 | { 39 | pub fn new(dataset: &'a D, substitution: Substitution, quads: Q) -> Self { 40 | Self { 41 | dataset, 42 | stack: vec![State { 43 | substitution, 44 | quad_state: None, 45 | rest: quads, 46 | }], 47 | } 48 | } 49 | } 50 | 51 | impl<'p, D, Q> Matching<'_, 'p, D, Q> 52 | where 53 | D: PatternMatchingDataset, 54 | D::Resource: Clone + PartialEq, 55 | Q: Clone + Iterator>>, 56 | { 57 | pub fn into_unique(mut self) -> Result>, Error> { 58 | match self.next() { 59 | Some(substitution) => { 60 | if self.next().is_some() { 61 | Err(Error::Ambiguity) 62 | } else { 63 | Ok(Some(substitution)) 64 | } 65 | } 66 | None => Ok(None), 67 | } 68 | } 69 | 70 | pub fn into_required_unique(self) -> Result, Error> { 71 | self.into_unique()?.ok_or(Error::Empty) 72 | } 73 | } 74 | 75 | impl<'p, D, Q> Iterator for Matching<'_, 'p, D, Q> 76 | where 77 | D: PatternMatchingDataset, 78 | D::Resource: Clone + PartialEq, 79 | Q: Clone + Iterator>>, 80 | { 81 | type Item = Substitution; 82 | 83 | fn next(&mut self) -> Option { 84 | loop { 85 | match self.stack.last_mut() { 86 | Some(state) => match &mut state.quad_state { 87 | Some(quad_state) => match quad_state.quad_matching.next() { 88 | Some(m) => { 89 | if let Some(substitution) = 90 | state.substitution.with_quad(quad_state.pattern, m) 91 | { 92 | let rest = state.rest.clone(); 93 | 94 | self.stack.push(State { 95 | substitution, 96 | quad_state: None, 97 | rest, 98 | }) 99 | } 100 | } 101 | None => { 102 | self.stack.pop(); 103 | } 104 | }, 105 | None => match state.rest.next() { 106 | Some(pattern) => { 107 | state.quad_state = Some(QuadState { 108 | pattern, 109 | quad_matching: self 110 | .dataset 111 | .quad_pattern_matching(quad_matching_pattern(pattern)), 112 | }) 113 | } 114 | None => { 115 | let state = self.stack.pop().unwrap(); 116 | break Some(state.substitution); 117 | } 118 | }, 119 | }, 120 | None => break None, 121 | } 122 | } 123 | } 124 | } 125 | 126 | fn quad_matching_pattern(pattern: Quad>) -> CanonicalQuadPattern<&R> { 127 | CanonicalQuadPattern::from_pattern(Quad( 128 | pattern.0.into(), 129 | pattern.1.into(), 130 | pattern.2.into(), 131 | pattern.3.map(Into::into), 132 | )) 133 | } 134 | -------------------------------------------------------------------------------- /generators/rust/treeldr-rs/src/de/mod.rs: -------------------------------------------------------------------------------- 1 | mod matching; 2 | 3 | pub use matching::Matching; 4 | use rdf_types::{ 5 | dataset::PatternMatchingDataset, 6 | interpretation::{ReverseTermInterpretation, TermInterpretation}, 7 | Vocabulary, 8 | }; 9 | 10 | use crate::{pattern::Substitution, Pattern, RdfContext}; 11 | 12 | pub fn select_inputs( 13 | inputs: &[Pattern; N], 14 | substitution: &Substitution, 15 | ) -> [R; N] { 16 | inputs 17 | .iter() 18 | .map(|p| p.apply(substitution).into_resource().unwrap()) 19 | .collect::>() 20 | .try_into() 21 | .ok() 22 | .unwrap() 23 | } 24 | 25 | pub fn select_graph( 26 | current_graph: Option<&R>, 27 | graph_pattern: &Option>>, 28 | substitution: &Substitution, 29 | ) -> Option { 30 | graph_pattern 31 | .as_ref() 32 | .map(|g| { 33 | g.as_ref() 34 | .map(|p| p.apply(substitution).into_resource().unwrap()) 35 | }) 36 | .unwrap_or_else(|| current_graph.cloned()) 37 | } 38 | 39 | #[derive(Debug, thiserror::Error)] 40 | pub enum Error { 41 | #[error("data ambiguity")] 42 | DataAmbiguity, 43 | 44 | #[error("missing required data")] 45 | MissingData, 46 | 47 | #[error("missing required field `{0}`")] 48 | MissingField(String), 49 | 50 | #[error("missing required resource identifier")] 51 | MissingId, 52 | 53 | #[error("ambiguous resource identifier")] 54 | AmbiguousId, 55 | 56 | #[error("invalid resource identifier")] 57 | InvalidId, 58 | 59 | #[error("ambiguous literal value")] 60 | AmbiguousLiteralValue, 61 | 62 | #[error("invalid literal value")] 63 | InvalidLiteralValue, 64 | 65 | #[error("literal type mismatch")] 66 | LiteralTypeMismatch, 67 | 68 | #[error("expected literal value")] 69 | ExpectedLiteral, 70 | } 71 | 72 | impl From for Error { 73 | fn from(value: matching::Error) -> Self { 74 | match value { 75 | matching::Error::Ambiguity => Self::DataAmbiguity, 76 | matching::Error::Empty => Self::MissingData, 77 | } 78 | } 79 | } 80 | 81 | pub trait DeserializeLd: Sized 82 | where 83 | V: Vocabulary, 84 | I: TermInterpretation 85 | + ReverseTermInterpretation, 86 | I::Resource: Clone + Ord, 87 | { 88 | fn deserialize_ld_with( 89 | rdf: RdfContext, 90 | dataset: &D, 91 | graph: Option<&I::Resource>, 92 | inputs: &[I::Resource; N], 93 | ) -> Result 94 | where 95 | D: PatternMatchingDataset; 96 | } 97 | 98 | pub struct InvalidLiteral(pub T); 99 | 100 | pub trait FromRdfLiteral: Sized { 101 | fn from_rdf_literal(value: &str) -> Result; 102 | } 103 | 104 | impl FromRdfLiteral for bool { 105 | fn from_rdf_literal(value: &str) -> Result { 106 | use xsd_types::ParseXsd; 107 | let value = 108 | xsd_types::Boolean::parse_xsd(value).map_err(|_| InvalidLiteral(value.to_owned()))?; 109 | Ok(value.0) 110 | } 111 | } 112 | 113 | macro_rules! xsd_from_rdf { 114 | ($($ty:ident),*) => { 115 | $( 116 | impl FromRdfLiteral for $ty { 117 | fn from_rdf_literal(value: &str) -> Result { 118 | xsd_types::ParseXsd::parse_xsd(value).map_err(|_| InvalidLiteral(value.to_owned())) 119 | } 120 | } 121 | )* 122 | }; 123 | } 124 | 125 | xsd_from_rdf!(u8, u16, u32, u64, i8, i16, i32, i64, String); 126 | -------------------------------------------------------------------------------- /generators/rust/treeldr-rs/src/lib.rs: -------------------------------------------------------------------------------- 1 | use iref::Iri; 2 | use rdf_types::BlankId; 3 | 4 | #[cfg(feature = "macros")] 5 | /// Embed TreeLDR layouts as Rust types in the given module. 6 | /// 7 | /// # Example 8 | /// 9 | /// ``` 10 | /// use treeldr::tldr; 11 | /// #[tldr("layouts/examples/record.json")] 12 | /// mod module { 13 | /// // a `SimpleLayout` type will be generated here. 14 | /// } 15 | /// ``` 16 | pub use treeldr_macros::tldr; 17 | 18 | #[cfg(feature = "macros")] 19 | /// Embed TreeLDR layouts as Rust types. 20 | /// 21 | /// # Example 22 | /// 23 | /// ``` 24 | /// # use treeldr_macros::tldr_include; 25 | /// tldr_include!("layouts/examples/record.json"); 26 | /// ``` 27 | pub use treeldr_macros::tldr_include; 28 | 29 | #[cfg(feature = "macros")] 30 | pub use treeldr_macros::{DeserializeLd, SerializeLd}; 31 | 32 | #[doc(hidden)] 33 | pub use iref; 34 | 35 | #[doc(hidden)] 36 | pub use rdf_types; 37 | 38 | mod datatypes; 39 | pub mod de; 40 | pub mod pattern; 41 | mod rdf; 42 | pub mod ser; 43 | pub mod utils; 44 | 45 | pub use de::{DeserializeLd, Error as DeserializeError}; 46 | pub use pattern::Pattern; 47 | pub use rdf::{RdfContext, RdfContextMut}; 48 | pub use ser::{Error as SerializeError, SerializeLd}; 49 | 50 | pub trait AsId { 51 | fn as_id(&self) -> rdf_types::Id<&Iri, &BlankId>; 52 | } 53 | 54 | impl AsId for rdf_types::Id { 55 | fn as_id(&self) -> rdf_types::Id<&Iri, &BlankId> { 56 | self.as_lexical_id_ref() 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /generators/rust/treeldr-rs/src/pattern.rs: -------------------------------------------------------------------------------- 1 | use rdf_types::{pattern::ResourceOrVar, Quad}; 2 | 3 | /// Quad of patterns. 4 | pub type PatternQuad = Quad, Pattern, Pattern, Pattern>; 5 | 6 | /// Pattern. 7 | /// 8 | /// Either a resource identifier or a variable. 9 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 10 | pub enum Pattern { 11 | /// Resource. 12 | Resource(R), 13 | 14 | /// Variable. 15 | Var(u32), 16 | } 17 | 18 | impl Pattern { 19 | pub fn apply(&self, substitution: &Substitution) -> Self 20 | where 21 | R: Clone, 22 | { 23 | match self { 24 | Self::Resource(r) => Self::Resource(r.clone()), 25 | Self::Var(x) => match substitution.get(*x) { 26 | Some(r) => Self::Resource(r.clone()), 27 | None => Self::Var(*x), 28 | }, 29 | } 30 | } 31 | 32 | pub fn as_ref(&self) -> Pattern<&R> { 33 | match self { 34 | Self::Resource(r) => Pattern::Resource(r), 35 | Self::Var(x) => Pattern::Var(*x), 36 | } 37 | } 38 | 39 | pub fn into_resource(self) -> Option { 40 | match self { 41 | Self::Resource(r) => Some(r), 42 | _ => None, 43 | } 44 | } 45 | } 46 | 47 | impl From> for ResourceOrVar { 48 | fn from(value: Pattern) -> Self { 49 | match value { 50 | Pattern::Resource(r) => ResourceOrVar::Resource(r), 51 | Pattern::Var(x) => ResourceOrVar::Var(x), 52 | } 53 | } 54 | } 55 | 56 | #[derive(Clone)] 57 | pub struct Substitution(Vec>); 58 | 59 | impl Substitution { 60 | pub fn new() -> Self { 61 | Self(Vec::new()) 62 | } 63 | 64 | pub fn from_inputs(inputs: &[R]) -> Self 65 | where 66 | R: Clone, 67 | { 68 | Self(inputs.iter().cloned().map(Some).collect()) 69 | } 70 | 71 | pub fn len(&self) -> u32 { 72 | self.0.len() as u32 73 | } 74 | 75 | pub fn is_empty(&self) -> bool { 76 | self.0.is_empty() 77 | } 78 | 79 | pub fn get(&self, i: u32) -> Option<&R> { 80 | self.0.get(i as usize).and_then(Option::as_ref) 81 | } 82 | 83 | /// Introduce `count` variables to the substitution. Returns the index of 84 | /// the first introduced variable. 85 | pub fn intro(&mut self, count: u32) -> u32 { 86 | let i = self.len(); 87 | self.0.resize_with(self.0.len() + count as usize, || None); 88 | i 89 | } 90 | 91 | pub fn push(&mut self, value: Option) -> u32 { 92 | let i = self.len(); 93 | self.0.push(value); 94 | i 95 | } 96 | 97 | pub fn set(&mut self, x: u32, value: Option) -> Option { 98 | std::mem::replace(&mut self.0[x as usize], value) 99 | } 100 | 101 | pub fn with_quad( 102 | &self, 103 | pattern: Quad, Pattern<&R>, Pattern<&R>, Pattern<&R>>, 104 | value: Quad<&R, &R, &R, &R>, 105 | ) -> Option 106 | where 107 | R: Clone + PartialEq, 108 | { 109 | let mut result = self.clone(); 110 | 111 | if let Pattern::Var(x) = pattern.0 { 112 | if let Some(old_value) = result.set(x, Some(value.0.clone())) { 113 | if old_value != *value.0 { 114 | return None; 115 | } 116 | } 117 | } 118 | 119 | if let Pattern::Var(x) = pattern.1 { 120 | if let Some(old_value) = result.set(x, Some(value.1.clone())) { 121 | if old_value != *value.1 { 122 | return None; 123 | } 124 | } 125 | } 126 | 127 | if let Pattern::Var(x) = pattern.2 { 128 | if let Some(old_value) = result.set(x, Some(value.2.clone())) { 129 | if old_value != *value.2 { 130 | return None; 131 | } 132 | } 133 | } 134 | 135 | if let Some(Pattern::Var(x)) = pattern.3 { 136 | let g = value.3.unwrap(); 137 | if let Some(old_value) = result.set(x, Some(g.clone())) { 138 | if old_value != *g { 139 | return None; 140 | } 141 | } 142 | } 143 | 144 | Some(result) 145 | } 146 | } 147 | 148 | impl Default for Substitution { 149 | fn default() -> Self { 150 | Self::new() 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /generators/rust/treeldr-rs/src/rdf.rs: -------------------------------------------------------------------------------- 1 | use educe::Educe; 2 | use iref::Iri; 3 | use rdf_types::{ 4 | interpretation::{ 5 | BlankIdInterpretationMut, IriInterpretation, IriInterpretationMut, LiteralInterpretation, 6 | LiteralInterpretationMut, 7 | }, 8 | vocabulary::{ 9 | BlankIdVocabularyMut, IriVocabulary, IriVocabularyMut, LiteralVocabulary, 10 | LiteralVocabularyMut, 11 | }, 12 | BlankId, Literal, LiteralType, 13 | }; 14 | 15 | #[derive(Educe)] 16 | #[educe(Clone, Copy)] 17 | pub struct RdfContext<'a, V, I> { 18 | pub vocabulary: &'a V, 19 | pub interpretation: &'a I, 20 | } 21 | 22 | impl<'a, V, I> RdfContext<'a, V, I> { 23 | pub fn new(vocabulary: &'a V, interpretation: &'a I) -> Self { 24 | Self { 25 | vocabulary, 26 | interpretation, 27 | } 28 | } 29 | 30 | pub fn iri_interpretation(&self, iri: &Iri) -> Option 31 | where 32 | V: IriVocabulary, 33 | I: IriInterpretation, 34 | { 35 | self.interpretation 36 | .lexical_iri_interpretation(self.vocabulary, iri) 37 | } 38 | 39 | pub fn literal_interpretation(&self, literal: Literal<&Iri>) -> Option 40 | where 41 | V: IriVocabulary + LiteralVocabulary, 42 | I: IriInterpretation + LiteralInterpretation, 43 | { 44 | let (value, type_) = literal.into_parts(); 45 | let type_ = match type_ { 46 | LiteralType::Any(iri) => LiteralType::Any(self.vocabulary.get(iri)?), 47 | LiteralType::LangString(tag) => LiteralType::LangString(tag), 48 | }; 49 | 50 | let lit = self 51 | .vocabulary 52 | .get_literal(Literal::new(value, type_).as_ref())?; 53 | self.interpretation.literal_interpretation(&lit) 54 | } 55 | } 56 | 57 | pub struct RdfContextMut<'a, V, I> { 58 | pub vocabulary: &'a mut V, 59 | pub interpretation: &'a mut I, 60 | } 61 | 62 | impl<'a, V, I> RdfContextMut<'a, V, I> { 63 | pub fn new(vocabulary: &'a mut V, interpretation: &'a mut I) -> Self { 64 | Self { 65 | vocabulary, 66 | interpretation, 67 | } 68 | } 69 | 70 | pub fn interpret_iri(&mut self, iri: &Iri) -> I::Resource 71 | where 72 | V: IriVocabularyMut, 73 | I: IriInterpretationMut, 74 | { 75 | self.interpretation 76 | .interpret_iri(self.vocabulary.insert(iri)) 77 | } 78 | 79 | pub fn interpret_blank_id(&mut self, blank_id: &BlankId) -> I::Resource 80 | where 81 | V: BlankIdVocabularyMut, 82 | I: BlankIdInterpretationMut, 83 | { 84 | self.interpretation 85 | .interpret_blank_id(self.vocabulary.insert_blank_id(blank_id)) 86 | } 87 | 88 | pub fn vocabulary_literal(&mut self, literal: Literal<&Iri>) -> V::Literal 89 | where 90 | V: IriVocabularyMut + LiteralVocabularyMut, 91 | I: IriInterpretationMut + LiteralInterpretationMut, 92 | { 93 | let literal = literal.insert_type_into_vocabulary(self.vocabulary); 94 | self.vocabulary.insert_owned_literal(literal) 95 | } 96 | 97 | pub fn vocabulary_literal_owned(&mut self, literal: Literal) -> V::Literal 98 | where 99 | V: IriVocabularyMut + LiteralVocabularyMut, 100 | I: IriInterpretationMut + LiteralInterpretationMut, 101 | { 102 | let literal = literal.insert_type_into_vocabulary(self.vocabulary); 103 | self.vocabulary.insert_owned_literal(literal) 104 | } 105 | 106 | pub fn interpret_literal(&mut self, literal: Literal<&Iri>) -> I::Resource 107 | where 108 | V: IriVocabularyMut + LiteralVocabularyMut, 109 | I: IriInterpretationMut + LiteralInterpretationMut, 110 | { 111 | let l = self.vocabulary_literal(literal); 112 | self.interpretation.interpret_literal(l) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /generators/rust/treeldr-rs/src/ser/environment.rs: -------------------------------------------------------------------------------- 1 | use crate::{pattern::PatternQuad, Pattern, RdfContextMut}; 2 | use rdf_types::{dataset::DatasetMut, InterpretationMut, Quad}; 3 | 4 | pub enum Environment<'a, R> { 5 | Root(&'a [R]), 6 | Child(&'a Environment<'a, R>, Vec), 7 | } 8 | 9 | impl Environment<'_, R> { 10 | pub fn get(&self, i: u32) -> Result<&R, u32> { 11 | match self { 12 | Self::Root(inputs) => match inputs.get(i as usize) { 13 | Some(r) => Ok(r), 14 | None => Err(i - inputs.len() as u32), 15 | }, 16 | Self::Child(parent, intros) => match parent.get(i) { 17 | Ok(r) => Ok(r), 18 | Err(j) => match intros.get(j as usize) { 19 | Some(r) => Ok(r), 20 | None => Err(j - intros.len() as u32), 21 | }, 22 | }, 23 | } 24 | } 25 | 26 | #[must_use] 27 | pub fn bind(&self, resources: [R; N]) -> Environment { 28 | Environment::Child(self, resources.into_iter().collect()) 29 | } 30 | 31 | #[must_use] 32 | pub fn intro(&self, rdf: &mut RdfContextMut, count: u32) -> Environment 33 | where 34 | I: InterpretationMut, 35 | { 36 | let mut intros = Vec::with_capacity(count as usize); 37 | for _ in 0..count { 38 | intros.push(rdf.interpretation.new_resource(rdf.vocabulary)) 39 | } 40 | 41 | Environment::Child(self, intros) 42 | } 43 | } 44 | 45 | impl Environment<'_, R> { 46 | pub fn instantiate_pattern(&self, pattern: &Pattern) -> R 47 | where 48 | // Q: Clone + Into, 49 | { 50 | match pattern { 51 | Pattern::Var(x) => self.get(*x).cloned().unwrap(), 52 | Pattern::Resource(r) => r.clone(), 53 | } 54 | } 55 | 56 | pub fn instantiate_patterns(&self, patterns: &[Pattern; N]) -> [R; N] 57 | where 58 | // Q: Clone + Into, 59 | { 60 | let mut result = Vec::with_capacity(patterns.len()); 61 | 62 | for p in patterns { 63 | result.push(self.instantiate_pattern(p)) 64 | } 65 | 66 | result.try_into().ok().unwrap() 67 | } 68 | 69 | pub fn instantiate_quad( 70 | &self, 71 | quad: Quad<&Pattern, &Pattern, &Pattern, &Pattern>, 72 | ) -> Quad 73 | where 74 | // Q: Clone + Into, 75 | { 76 | Quad( 77 | self.instantiate_pattern(quad.0), 78 | self.instantiate_pattern(quad.1), 79 | self.instantiate_pattern(quad.2), 80 | quad.3.map(|g| self.instantiate_pattern(g)), 81 | ) 82 | } 83 | 84 | pub fn instantiate_dataset(&self, input: &[PatternQuad], output: &mut D) 85 | where 86 | // Q: Clone + Into, 87 | D: DatasetMut, 88 | { 89 | for quad in input { 90 | output.insert(self.instantiate_quad(quad.as_ref())); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /generators/rust/treeldr-rs/src/ser/mod.rs: -------------------------------------------------------------------------------- 1 | mod environment; 2 | 3 | pub use environment::Environment; 4 | use rdf_types::{ 5 | dataset::BTreeDataset, 6 | interpretation::{ReverseTermInterpretationMut, TermInterpretationMut}, 7 | InterpretationMut, VocabularyMut, 8 | }; 9 | 10 | use crate::RdfContextMut; 11 | 12 | pub enum Error { 13 | InvalidId(String), 14 | } 15 | 16 | pub trait SerializeLd: Sized 17 | where 18 | V: VocabularyMut, 19 | I: InterpretationMut 20 | + TermInterpretationMut 21 | + ReverseTermInterpretationMut, 22 | I::Resource: Clone + Ord, 23 | { 24 | fn serialize_ld_with( 25 | &self, 26 | rdf: &mut RdfContextMut, 27 | inputs: &[I::Resource; N], 28 | current_graph: Option<&I::Resource>, 29 | output: &mut BTreeDataset, 30 | ) -> Result<(), Error>; 31 | } 32 | -------------------------------------------------------------------------------- /generators/rust/treeldr-rs/src/utils.rs: -------------------------------------------------------------------------------- 1 | use educe::Educe; 2 | use rdf_types::Quad; 3 | 4 | use crate::Pattern; 5 | 6 | pub trait QuadsExt<'a, R>: Sized { 7 | fn with_default_graph(self, graph: Option<&'a R>) -> QuadsWithDefaultGraph<'a, R, Self>; 8 | } 9 | 10 | impl<'a, R: 'a, I> QuadsExt<'a, R> for I 11 | where 12 | I: Iterator, &'a Pattern, &'a Pattern, &'a Pattern>>, 13 | { 14 | fn with_default_graph(self, graph: Option<&'a R>) -> QuadsWithDefaultGraph<'a, R, Self> { 15 | QuadsWithDefaultGraph { quads: self, graph } 16 | } 17 | } 18 | 19 | #[derive(Educe)] 20 | #[educe(Clone(bound = "I: Clone"))] 21 | pub struct QuadsWithDefaultGraph<'a, R, I> { 22 | quads: I, 23 | graph: Option<&'a R>, 24 | } 25 | 26 | impl<'a, R, I> Iterator for QuadsWithDefaultGraph<'a, R, I> 27 | where 28 | I: Iterator, &'a Pattern, &'a Pattern, &'a Pattern>>, 29 | { 30 | type Item = Quad, Pattern<&'a R>, Pattern<&'a R>, Pattern<&'a R>>; 31 | 32 | fn next(&mut self) -> Option { 33 | self.quads.next().map(|quad| { 34 | Quad( 35 | quad.0.as_ref(), 36 | quad.1.as_ref(), 37 | quad.2.as_ref(), 38 | quad.3 39 | .map(Pattern::as_ref) 40 | .or_else(|| self.graph.map(Pattern::Resource)), 41 | ) 42 | }) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /generators/rust/treeldr-rs/tests/id.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "macros")] 2 | #[test] 3 | fn id() { 4 | #[derive(treeldr::SerializeLd, treeldr::DeserializeLd)] 5 | #[tldr(id)] 6 | pub struct Id(rdf_types::Id); 7 | 8 | impl treeldr::AsId for Id { 9 | fn as_id(&self) -> rdf_types::Id<&iref::Iri, &rdf_types::BlankId> { 10 | self.0.as_lexical_id_ref() 11 | } 12 | } 13 | 14 | impl From for Id { 15 | fn from(value: rdf_types::Id) -> Self { 16 | Self(value) 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /generators/rust/treeldr-rs/tests/list.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "macros")] 2 | #[test] 3 | fn list_unordered() { 4 | #[derive(treeldr::SerializeLd, treeldr::DeserializeLd)] 5 | #[tldr(set)] 6 | pub struct UnorderedList(Vec); 7 | } 8 | 9 | #[cfg(feature = "macros")] 10 | #[test] 11 | fn list_ordered() { 12 | #[derive(treeldr::SerializeLd, treeldr::DeserializeLd)] 13 | #[tldr(list)] 14 | pub struct UnorderedList(Vec); 15 | } 16 | 17 | #[cfg(feature = "macros")] 18 | #[test] 19 | fn list_sized() { 20 | #[derive(treeldr::SerializeLd, treeldr::DeserializeLd)] 21 | pub struct SizedList(String, String, String); 22 | } 23 | -------------------------------------------------------------------------------- /generators/rust/treeldr-rs/tests/literal.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "macros")] 2 | #[test] 3 | fn literal_unit() { 4 | #[derive(treeldr::SerializeLd, treeldr::DeserializeLd)] 5 | pub struct Unit; 6 | } 7 | 8 | #[cfg(feature = "macros")] 9 | #[test] 10 | fn literal_boolean() { 11 | #[derive(treeldr::SerializeLd, treeldr::DeserializeLd)] 12 | #[tldr(boolean)] 13 | pub struct Boolean(bool); 14 | } 15 | 16 | #[cfg(feature = "macros")] 17 | #[test] 18 | fn literal_i32() { 19 | #[derive(treeldr::SerializeLd, treeldr::DeserializeLd)] 20 | #[tldr(prefix("xsd" = "http://www.w3.org/2001/XMLSchema"))] 21 | #[tldr(number, datatype("xsd:int"))] 22 | pub struct I32(i32); 23 | } 24 | 25 | #[cfg(feature = "macros")] 26 | #[test] 27 | fn literal_string() { 28 | #[derive(treeldr::SerializeLd, treeldr::DeserializeLd)] 29 | #[tldr(prefix("xsd" = "http://www.w3.org/2001/XMLSchema"))] 30 | #[tldr(number, datatype("xsd:string"))] 31 | pub struct TestString(String); 32 | } 33 | -------------------------------------------------------------------------------- /generators/rust/treeldr-rs/tests/record.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "macros")] 2 | #[test] 3 | fn record() { 4 | #[derive(treeldr::SerializeLd, treeldr::DeserializeLd)] 5 | #[tldr(prefix("ex" = "http://example.org/#"))] 6 | pub struct Record { 7 | #[tldr("ex:foo")] 8 | foo: String, 9 | 10 | #[tldr("ex:bar")] 11 | optional: Option, 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /generators/rust/treeldr-rs/tests/sum.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "macros")] 2 | #[test] 3 | fn sum() { 4 | #[derive(treeldr::SerializeLd, treeldr::DeserializeLd)] 5 | #[tldr(prefix("ex" = "http://example.org/#"))] 6 | pub enum Sum { 7 | Foo(String), 8 | Bar(String), 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /language/README.md: -------------------------------------------------------------------------------- 1 | # TreeLDR Schema Definition Language -------------------------------------------------------------------------------- /language/extensions/README.md: -------------------------------------------------------------------------------- 1 | # IDE extensions for the TreeLDR language -------------------------------------------------------------------------------- /language/extensions/vscode/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that launches the extension inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "args": [ 13 | "--extensionDevelopmentPath=${workspaceFolder}", 14 | "--disable-extensions" 15 | ] 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /language/extensions/vscode/.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | .gitignore -------------------------------------------------------------------------------- /language/extensions/vscode/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to the "treeldr" extension will be documented in this file. 4 | 5 | Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. 6 | 7 | ## [Unreleased] 8 | 9 | - Initial release -------------------------------------------------------------------------------- /language/extensions/vscode/README.md: -------------------------------------------------------------------------------- 1 | # TreeLDR language support for VSCode. -------------------------------------------------------------------------------- /language/extensions/vscode/grammar.json: -------------------------------------------------------------------------------- 1 | { 2 | "scopeName": "source.treeldr", 3 | "patterns": [ 4 | { 5 | "name": "comment.line.documentation.treeldr", 6 | "begin": "///", 7 | "end": "\n" 8 | }, 9 | { 10 | "name": "comment.line.double-slash.treeldr", 11 | "begin": "//", 12 | "end": "\n" 13 | }, 14 | { 15 | "name": "keyword.treeldr", 16 | "match": "(?" 38 | }, 39 | { 40 | "name": "string.quoted.double.treeldr", 41 | "match": "\"[^\"\\\\]*(?:\\\\.[^\"\\\\]*)*\"", 42 | "patterns": [ 43 | { 44 | "name": "constant.character.escape.treeldr", 45 | "match": "\\." 46 | } 47 | ] 48 | }, 49 | { 50 | "name": "string.regexp.treeldr", 51 | "match": "/[^/\\\\]*(?:\\\\.[^/\\\\]*)*/", 52 | "patterns": [ 53 | { 54 | "name": "constant.character.escape.treeldr", 55 | "match": "\\." 56 | } 57 | ] 58 | } 59 | ] 60 | } -------------------------------------------------------------------------------- /language/extensions/vscode/language-configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | // symbol used for single line comment. Remove this entry if your language does not support line comments 4 | "lineComment": "//" 5 | }, 6 | // symbols used as brackets 7 | "brackets": [ 8 | ["{", "}"], 9 | ["[", "]"], 10 | ["(", ")"] 11 | ], 12 | // symbols that are auto closed when typing 13 | "autoClosingPairs": [ 14 | ["{", "}"], 15 | ["[", "]"], 16 | ["(", ")"], 17 | ["<", ">"], 18 | ["\"", "\""] 19 | ], 20 | // symbols that can be used to surround a selection 21 | "surroundingPairs": [ 22 | ["{", "}"], 23 | ["[", "]"], 24 | ["(", ")"], 25 | ["<", ">"] 26 | ] 27 | } -------------------------------------------------------------------------------- /language/extensions/vscode/local-install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | EXTENSIONS_PATH=`codium --verbose --list-extensions | grep -Po "(?<='extensions-dir': ')([a-zA-Z0-9\\-:./]+)(?=')"` 3 | HERE=`pwd` 4 | sudo ln -s $HERE $EXTENSIONS_PATH/spruceid-treeldr -------------------------------------------------------------------------------- /language/extensions/vscode/main.js: -------------------------------------------------------------------------------- 1 | const vscode = require("vscode"); 2 | 3 | function activate(context) { 4 | context.subscriptions.push(configureLanguage()); 5 | } 6 | 7 | function deactivate() {} 8 | 9 | module.exports = { 10 | activate, 11 | deactivate 12 | }; 13 | 14 | /** 15 | * Sets up additional language configuration that's impossible to do via a 16 | * separate language-configuration.json file. See [1] for more information. 17 | * 18 | * [1]: https://github.com/Microsoft/vscode/issues/11514#issuecomment-244707076 19 | */ 20 | function configureLanguage() { 21 | return vscode.languages.setLanguageConfiguration('treeldr', { 22 | onEnterRules: [ 23 | { 24 | // Doc single-line comment 25 | // e.g. ///| 26 | beforeText: /^\s*\/{3}.*$/, 27 | action: { indentAction: vscode.IndentAction.None, appendText: '/// ' }, 28 | }, 29 | { 30 | // Parent doc single-line comment 31 | // e.g. //!| 32 | beforeText: /^\s*\/{2}\!.*$/, 33 | action: { indentAction: vscode.IndentAction.None, appendText: '//! ' }, 34 | } 35 | ], 36 | }); 37 | } -------------------------------------------------------------------------------- /language/extensions/vscode/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "treeldr", 3 | "displayName": "TreeLDR", 4 | "description": "TreeLDR schema definition language for Visual Studio Code", 5 | "version": "0.0.1", 6 | "engines": { 7 | "vscode": "^1.64.0" 8 | }, 9 | "categories": [ 10 | "Programming Languages", 11 | "Snippets" 12 | ], 13 | "activationEvents": [ 14 | "onLanguage:treeldr" 15 | ], 16 | "main": "./main.js", 17 | "contributes": { 18 | "languages": [{ 19 | "id": "treeldr", 20 | "aliases": ["TreeLDR", "treeldr"], 21 | "extensions": [".tldr"], 22 | "configuration": "./language-configuration.json" 23 | }], 24 | "grammars": [{ 25 | "language": "treeldr", 26 | "scopeName": "source.treeldr", 27 | "path": "./grammar.json" 28 | }], 29 | "snippets": [ 30 | { 31 | "language": "treeldr", 32 | "path": "./snippets.json" 33 | } 34 | ] 35 | } 36 | } -------------------------------------------------------------------------------- /language/extensions/vscode/snippets.json: -------------------------------------------------------------------------------- 1 | { 2 | "Type": { 3 | "scope": "treeldr", 4 | "prefix": "type", 5 | "body": [ 6 | "type ${1:Name} {", 7 | "\t$0", 8 | "}" 9 | ], 10 | "description": "Type definition." 11 | }, 12 | "Layout": { 13 | "scope": "treeldr", 14 | "prefix": "layout", 15 | "body": [ 16 | "layout ${1:Name} {", 17 | "\t$0", 18 | "}" 19 | ], 20 | "description": "Layout definition." 21 | } 22 | } -------------------------------------------------------------------------------- /language/extensions/vscode/vscode: -------------------------------------------------------------------------------- 1 | /home/timothee/Projets/utils/treeldr/lang/vscode -------------------------------------------------------------------------------- /layouts/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock -------------------------------------------------------------------------------- /layouts/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "treeldr-layouts" 3 | description = "TreeLDR Layouts" 4 | version.workspace = true 5 | authors.workspace = true 6 | edition.workspace = true 7 | rust-version.workspace = true 8 | license.workspace = true 9 | 10 | [features] 11 | default = [] 12 | 13 | # CBOR extension. 14 | cbor = [] 15 | 16 | # Implements conversion functions between `Value` and `serde_cbor::Value`. 17 | serde_cbor = ["cbor", "dep:serde_cbor"] 18 | 19 | [dependencies] 20 | educe.workspace = true 21 | num-traits.workspace = true 22 | num-bigint.workspace = true 23 | num-rational.workspace = true 24 | iref = { workspace = true, features = ["serde"] } 25 | static-iref.workspace = true 26 | langtag = "0.4.0" 27 | rdf-types = { workspace = true, features = ["serde"] } 28 | xsd-types.workspace = true 29 | btree-range-map.workspace = true 30 | serde = { workspace = true, features = ["derive"] } 31 | thiserror.workspace = true 32 | locspan.workspace = true 33 | serde_json = "1.0" 34 | json-syntax.workspace = true 35 | lazy_static = "1.4.0" 36 | static_assertions = "1.1.0" 37 | 38 | serde_cbor = { workspace = true, optional = true } 39 | 40 | [dev-dependencies] 41 | nquads-syntax.workspace = true 42 | serde_json.workspace = true 43 | paste = "1.0" 44 | -------------------------------------------------------------------------------- /layouts/README.md: -------------------------------------------------------------------------------- 1 | # TreeLDR Layouts 2 | 3 | 4 | 5 | TreeLDR's RDF Layouts are a powerful tool to map structured data to RDF datasets. 6 | This library provides core types to define layouts, an abstract syntax to 7 | describe layouts and "distillation" functions to serialize/deserialize data 8 | using layouts. 9 | 10 | ## Basic usage 11 | 12 | The following example shows how to create a layout from its abstract syntax 13 | representation (using JSON), compile it and use it to serialize an RDF 14 | dataset into a structured value. 15 | 16 | ```rust 17 | use static_iref::iri; 18 | use rdf_types::{Quad, Term, Literal, literal::Type}; 19 | use xsd_types::XSD_STRING; 20 | use serde_json::json; 21 | 22 | // Create a layout builder. 23 | let mut builder = treeldr_layouts::abs::Builder::new(); 24 | 25 | // Parse the layout definition, here from JSON. 26 | let layout: treeldr_layouts::abs::syntax::Layout = serde_json::from_value( 27 | json!({ 28 | "type": "record", 29 | "fields": { 30 | "id": { 31 | "value": { 32 | "layout": { "type": "id" }, 33 | "input": "_:self" 34 | } 35 | }, 36 | "name": { 37 | "value": { "type": "string" }, 38 | "property": "https://schema.org/name" 39 | } 40 | } 41 | }) 42 | ).unwrap(); 43 | 44 | // Build the layout. 45 | let layout_ref = layout.build(&mut builder).unwrap(); // returns a `Ref` to the layout. 46 | 47 | // Get the compiled layouts collection. 48 | let layouts = builder.build(); 49 | 50 | // Create an RDF dataset with a single triple. 51 | let dataset: grdf::BTreeDataset = [ 52 | Quad( 53 | Term::iri(iri!("https://example.org/#john.smith").to_owned()), 54 | Term::iri(iri!("https://schema.org/name").to_owned()), 55 | Term::Literal(Literal::new("John Smith".to_owned(), Type::Any(XSD_STRING.to_owned()))), 56 | None 57 | ) 58 | ].into_iter().collect(); 59 | 60 | // Hydrate the dataset to get a structured data value. 61 | let value = treeldr_layouts::hydrate( 62 | &layouts, 63 | &dataset, 64 | &layout_ref, 65 | &[Term::iri(iri!("https://example.org/#john.smith").to_owned())] 66 | ).unwrap().into_untyped(); // we don't care about types here. 67 | 68 | // Create a structured data value with the expected result. 69 | // Parse the layout definition, here from JSON. 70 | let expected: treeldr_layouts::Value = serde_json::from_value( 71 | json!({ 72 | "id": "https://example.org/#john.smith", 73 | "name": "John Smith" 74 | }) 75 | ).unwrap(); 76 | 77 | // Check equality. 78 | assert_eq!(value, expected) 79 | ``` 80 | 81 | ## The `Layout` types 82 | 83 | Layouts come in several forms: 84 | - `abs::syntax::Layout`: represents a 85 | layout definition in the abstract syntax. In this representation 86 | variables have names and layouts can be nested. 87 | - `abs::Layout`: represents an abstract layout with 88 | stripped variable names and flattened layouts. These layouts are 89 | managed by the layout [`Builder`](https://docs.rs/treeldr-layouts/latest/treeldr_layouts/abs/struct.Builder.html). 90 | - `Layout`: the most optimized and compact form, used 91 | by the distillation functions. Such layouts are stored in a 92 | [`Layouts`](https://docs.rs/treeldr-layouts/latest/treeldr_layouts/struct.Layouts.html) collection. 93 | 94 | 95 | -------------------------------------------------------------------------------- /layouts/book/.gitignore: -------------------------------------------------------------------------------- 1 | /target -------------------------------------------------------------------------------- /layouts/book/README.md: -------------------------------------------------------------------------------- 1 | # The TreeLDR Layout Book 2 | 3 | This book is a in-depth definition of TreeLDR's layouts. 4 | 5 | ## Build 6 | 7 | Install [`mdBook`](https://github.com/rust-lang/mdBook#mdbook) and run the 8 | following command: 9 | ``` 10 | mdbook build 11 | ``` -------------------------------------------------------------------------------- /layouts/book/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | title = "TreeLDR Layouts" 3 | 4 | [build] 5 | build-dir = "target" 6 | 7 | [preprocessor.graphviz] 8 | command = "mdbook-graphviz" -------------------------------------------------------------------------------- /layouts/book/src/README.md: -------------------------------------------------------------------------------- 1 | # TreeLDR Layouts 2 | 3 | TreeLDR Layouts are a data serialization and deserialization tool for the 4 | Resource Description Framework (RDF). 5 | It can be used to convert RDF graphs into tree-like values (such as JSON), 6 | and back. 7 | The idea behind layouts is simple: each layout describes the expected shape of a 8 | tree value. 9 | This shape can be either a record (sometimes called object, in JSON for 10 | instance), a list, a number, etc. Each part of this shape is then associated to 11 | a subset of the represented RDF dataset. 12 | 13 | ## Basic layout 14 | 15 | Here is an example is a very simple TreeLDR layout: 16 | ```json 17 | { 18 | "type": "record", // The shape of the layout (a record here). 19 | "fields": { // A description of the record's fields. 20 | "name": { // A field called `name`. 21 | "value": { "type": "string" }, // The `name` value is a string. 22 | }, 23 | "age": { // A field called `age` 24 | "value": { "type": "number" }, // The `age` value is a number. 25 | } 26 | } 27 | } 28 | ``` 29 | 30 | This layout matches any record value that may contain the fields `name` and 31 | `age`. The layout of `name` is `{ "type": "string" }`, meaning its value must 32 | be a text string. The layout of `age` is `{ "type": "number" }`, meaning its 33 | value must be a number. 34 | 35 | Here is an example of a tree value (here in JSON) matching this layout: 36 | ```json 37 | { 38 | "name": "John Smith", 39 | "age": 30 40 | } 41 | ``` 42 | 43 | ## Adding RDF to the Layout 44 | 45 | TreeLDR layouts are meant to define a transformation between tree values and 46 | RDF datasets. 47 | The layout above is a pattern for tree values, but there is not yet mention 48 | of RDF. 49 | The simplest way to add RDF information to our layout is to use the `property` 50 | attribute to bind each record field to an RDF property: 51 | 52 | ```json 53 | { 54 | "type": "record", 55 | "fields": { 56 | "name": { 57 | "value": { "type": "string" }, 58 | // We bind the name `field` to the property. 59 | "property": "http://example.org/#name" 60 | }, 61 | "age": { 62 | "value": { "type": "number" }, 63 | // We bind the name `age` to the property. 64 | "property": "http://example.org/#age" 65 | } 66 | } 67 | } 68 | ``` 69 | 70 | With the `property` attributes this layout now maps each matching tree value to 71 | a unique RDF data. 72 | For example, our previous JSON value `{ "name": "John Smith", "age": 30 }` is 73 | mapped to the following RDF dataset (written in N-Triples here): 74 | ```n-triples 75 | _:0 "John Smith" . 76 | _:0 "30"^^ . 77 | ``` 78 | 79 | Here is a walk-through of this dataset: 80 | - It contains two triples, one for each field of the original tree value 81 | - `_:0` is the subject of those triples. It is the RDF resource represented 82 | by the top-level layout. A layout can represent any number of resources, 83 | called **input** resources. By default a layout has only one input. 84 | The next section goes over layout inputs in more details. 85 | - `"John Smith"` is a text string literal value associated to the property 86 | `http://example.org/#name`, as prescribed by the layout `name` field. 87 | In RDF each literal value has a type. Here the type is implicitly the XSD 88 | datatype `http://www.w3.org/2001/XMLSchema#string`. It is possible to 89 | manually set this datatype. 90 | - `"30"` is the literal value associated to the property 91 | `http://example.org/#age`, as prescribed by the layout `age` field. 92 | Because the `number` layout was used, the default datatype for this 93 | literal is `http://www.w3.org/2001/XMLSchema#decimal`. It is possible to 94 | manually set this datatype. 95 | 96 | The layout can be used to go from the RDF form to the structured value form by 97 | **serialization**. 98 | It can also be used in the opposite direction from the structured value form to 99 | the pure RDF form by **deserialization**. 100 | 101 | ## Layout inputs 102 | 103 | TODO -------------------------------------------------------------------------------- /layouts/book/src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | [Introduction](README.md) 4 | 5 | - [RDF Basics](rdf-basics.md) 6 | - [Data Model](data-model/README.md) 7 | - [Literals](data-model/literals.md) 8 | - [Records (Objects)](data-model/records.md) 9 | - [Lists](data-model/lists.md) 10 | - [Types](data-model/types.md) 11 | 12 | - [Layouts](layouts/README.md) 13 | - [Literals](layouts/literals.md) 14 | - [Product (Record)](layouts/record.md) 15 | - [Sum](layouts/sum.md) 16 | - [Lists](layouts/lists.md) 17 | 18 | 21 | 22 | - [Algorithms](algorithms/README.md) 23 | - [Serialization](algorithms/serialization.md) 24 | - [Deserialization](algorithms/deserialization.md) 25 | - [RDF Vocabulary](rdf-vocabulary/README.md) 26 | - [Schema](rdf-vocabulary/schema.md) 27 | - [Layouts of Layouts](rdf-vocabulary/layouts-of-layouts.md) -------------------------------------------------------------------------------- /layouts/book/src/abstract-layouts/README.md: -------------------------------------------------------------------------------- 1 | # Abstract Layouts 2 | 3 | Abstract layouts are layouts defined by composition. 4 | 5 | TODO -------------------------------------------------------------------------------- /layouts/book/src/abstract-layouts/intersection.md: -------------------------------------------------------------------------------- 1 | # Layout Intersection 2 | 3 | TODO -------------------------------------------------------------------------------- /layouts/book/src/abstract-layouts/union.md: -------------------------------------------------------------------------------- 1 | # Layout Union 2 | 3 | TODO -------------------------------------------------------------------------------- /layouts/book/src/abstract-syntax/README.md: -------------------------------------------------------------------------------- 1 | # Abstract Syntax 2 | 3 | TODO -------------------------------------------------------------------------------- /layouts/book/src/algorithms/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spruceid/treeldr/82f86519141c1188fd69a2fd3730388973218207/layouts/book/src/algorithms/README.md -------------------------------------------------------------------------------- /layouts/book/src/algorithms/deserialization.md: -------------------------------------------------------------------------------- 1 | # Deserialization 2 | 3 | TODO -------------------------------------------------------------------------------- /layouts/book/src/algorithms/serialization.md: -------------------------------------------------------------------------------- 1 | # Serialization 2 | 3 | TODO -------------------------------------------------------------------------------- /layouts/book/src/data-model/README.md: -------------------------------------------------------------------------------- 1 | # Data Model 2 | 3 | Layouts define a mapping from RDF datasets to tree data. 4 | The RDF dataset model is already specified by the RDF specification. 5 | 6 | This section specifies all the *structured values* that can be processed and/or produced using TreeLDR layouts. A value can be either: 7 | - a **literal** value, representing any atomic value; 8 | - a **record**, representing a collection of key-value pairs; 9 | - a **list**, representing a sequence of values. 10 | 11 | This data-model is close to the JSON data model, with some notable exceptions: 12 | - The value space of numbers is all the rational numbers, and not just decimal numbers; 13 | - Surrogate Unicode code points are not allowed in the lexical representation of text strings; 14 | - There is a dedicated datatype for binary strings. 15 | 16 | This chapter details the data model for tree values. 17 | 18 | # Syntax 19 | 20 | In addition, to write the formal specification of layouts, we also define 21 | a syntax for values, along with a type system. 22 | 23 | ```abnf 24 | value = literal | record | list 25 | ``` -------------------------------------------------------------------------------- /layouts/book/src/data-model/lists.md: -------------------------------------------------------------------------------- 1 | # Lists 2 | 3 | The list datatype contains all the finite sequences of values. 4 | 5 | ## Syntax 6 | 7 | ```abnf 8 | list-type = "[" [items] ws "]" 9 | items = ws value | ws value ws "," ws items 10 | ``` -------------------------------------------------------------------------------- /layouts/book/src/data-model/literals.md: -------------------------------------------------------------------------------- 1 | # Literals 2 | 3 | A literal value can be: 4 | - the unit value **unit** written `()` 5 | - a **boolean** value, either `true` or `false`, 6 | - a **number**, written as a decimal number (e.g. `12.9`), 7 | - a **binary string** written as an hexadecimal value preceded by a `#` character, 8 | - a **text string**, written between double quotes `"`. 9 | 10 | ## Syntax 11 | 12 | The ABNF grammar of literals is as follows: 13 | 14 | ```abnf 15 | literal = unit | boolean | number | bytes | string 16 | ``` 17 | 18 | # Unit 19 | 20 | Unit is a singleton datatype. It contains one unique value, the unit value. 21 | This value is very similar to JSON's `null` value. 22 | 23 | ## Syntax 24 | 25 | The unit value is written using a pair of parentheses `()`. 26 | 27 | ```abnf 28 | unit = "()" 29 | ``` 30 | 31 | # Boolean 32 | 33 | The boolean datatype contains the two values `true` and `false`. 34 | 35 | ## Syntax 36 | 37 | The ABNF grammar of boolean values is as follows: 38 | 39 | ```abnf 40 | boolean = "true" | "false" 41 | ``` 42 | 43 | # Number 44 | 45 | The number datatype contains all the [rational numbers (ℚ)](https://en.wikipedia.org/wiki/Rational_number). 46 | 47 | ## Syntax 48 | 49 | Numbers are written either as decimal numbers, or as fractions of two integer 50 | numbers. 51 | 52 | ```abnf 53 | number = decimal | fraction 54 | decimal = +DIGIT [ "." DIGIT ] 55 | fraction = +DIGIT "\" NZDIGIT +DIGIT 56 | 57 | NZDIGIT = "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" 58 | ``` 59 | 60 | # Binary String 61 | 62 | The binary string datatype contains any string of bytes. 63 | 64 | ## Syntax 65 | 66 | ```abnf 67 | bytes = "#" *( HEXDIGIT HEXDIGIT ) 68 | ``` 69 | 70 | # Text String 71 | 72 | The text string datatype contains any string of [Unicode scalar value](https://www.unicode.org/glossary/#unicode_scalar_value), which is any [Unicode code point](https://www.unicode.org/glossary/#code_point) other than a [surrogate code point](https://www.unicode.org/glossary/#surrogate_code_point). 73 | 74 | ## Syntax 75 | 76 | A string is written as a sequence of characters between double quotes. Any 77 | Unicode scalar value is allowed starting from U+0020 inclusive (the whitespace 78 | character) except for U+0022 (the quotation mark) and U+005C (the reverse 79 | solidus) which must be escaped along with control characters before U+0020. 80 | 81 | ```abnf 82 | string = quotation-mark *char quotation-mark 83 | 84 | char = unescaped 85 | / escape ( 86 | %x22 / ; " quotation mark U+0022 87 | %x5C / ; \ reverse solidus U+005C 88 | %x2F / ; / solidus U+002F 89 | %x62 / ; b backspace U+0008 90 | %x66 / ; f form feed U+000C 91 | %x6E / ; n line feed U+000A 92 | %x72 / ; r carriage return U+000D 93 | %x74 / ; t tab U+0009 94 | %x75 "{" 1*6HEXDIG "}" ; u{XXXX} U+XXXX 95 | ) 96 | 97 | escape = %x5C ; \ 98 | quotation-mark = %x22 ; " 99 | unescaped = %x20-21 / %x23-5B / %x5D-D7FF / %xE000-10FFFF 100 | ``` -------------------------------------------------------------------------------- /layouts/book/src/data-model/paths.md: -------------------------------------------------------------------------------- 1 | # Path 2 | 3 | A **path** is a sequence of *segments* leading to a unique node value in a tree. 4 | Each segment correspond to a tree branch, such as a field name or list index. 5 | 6 | For instance, consider the following tree value: 7 | ```json 8 | { 9 | "foo": [ 10 | { "bar": 1 }, 11 | { "bar": 2 } 12 | ] 13 | } 14 | ``` 15 | 16 | The following path leads to the value `2`: 17 | ``` 18 | foo/1/bar 19 | ``` 20 | 21 | ## Path validation 22 | 23 | TreeLDR layouts can be seen as type definitions for trees. 24 | We can use the layout definition to validate a path before using it to access 25 | an actual tree value. 26 | 27 | For instance, consider the following layout for the above value: 28 | ```json 29 | { 30 | "type": "record", 31 | "fields": { 32 | "foo": { 33 | "value": { 34 | "type": "list", 35 | "node": { 36 | "value": { 37 | "type": "record", 38 | "fields": { 39 | "bar": { 40 | "value": { "type": "number" } 41 | } 42 | } 43 | } 44 | } 45 | } 46 | } 47 | } 48 | } 49 | ``` 50 | 51 | This layout defines the following path family (a regular expression): 52 | ``` 53 | (foo(/[0-9]+(/bar)?)?)? 54 | ``` -------------------------------------------------------------------------------- /layouts/book/src/data-model/records.md: -------------------------------------------------------------------------------- 1 | # Records 2 | 3 | The record datatype contains all finite [partial functions](https://en.wikipedia.org/wiki/Partial_function) from keys to values, where keys are text string literals. 4 | 5 | ## Syntax 6 | 7 | ```abnf 8 | record = "{" [bindings] ws "}" 9 | bindings = ws binding | ws binding ws "," ws bindings 10 | binding = key ws ":" ws value 11 | key = string 12 | ws = *WS 13 | ``` -------------------------------------------------------------------------------- /layouts/book/src/data-model/types.md: -------------------------------------------------------------------------------- 1 | # Types 2 | 3 | The data-model presented so far in this chapter is fundamentally *untyped*. 4 | However, it can (and will in the next chapter) be useful to formally describe 5 | a subset of values sharing a given *shape*. 6 | In this section we define types as sets of tree values. 7 | 8 | There are three primary sorts of types: 9 | - Data-types, describing sets of literal values; 10 | - Record types: describing sets of record values; 11 | - List types: describing sets of list values. 12 | 13 | In addition, it is possible to compose new types by union or intersection. 14 | 15 | ## Syntax 16 | 17 | Just like for the data-model itself, we define a syntax for types. 18 | 19 | ```abnf 20 | type = datatype | record-type | list-type 21 | type-ref = ALPHA *(ALPHA | DIGIT) 22 | type-expr = type-ref | type 23 | ``` 24 | 25 | ### Type references and expressions 26 | 27 | A type reference, corresponding to the `type-ref` production in the above 28 | grammar, is a name referring to a type definition. 29 | A type expression (`type-expr` production) is either a type reference or 30 | definition. 31 | 32 | ## Datatype 33 | 34 | The following core type references are always defined: 35 | - `unit` 36 | - `boolean`, 37 | - `number`, 38 | - `bytes` (byte string), 39 | - `string` (text string) 40 | 41 | ## Record 42 | 43 | ### Syntax 44 | 45 | ```abnf 46 | record-type = "{" [ binding-types ] ws "}" 47 | binding-types = ws binding-type | ws binding-type ws ":" ws "," 48 | binding-type = key ws ":" ws type-expr 49 | ``` 50 | 51 | For example: 52 | 53 | ```ts 54 | { 55 | "id": string, 56 | "name": string 57 | } 58 | ``` 59 | 60 | ## List 61 | 62 | ### Syntax 63 | 64 | ```abnf 65 | list-type = "[" ws type-expr ws "]" 66 | ``` -------------------------------------------------------------------------------- /layouts/book/src/data-model/values.md: -------------------------------------------------------------------------------- 1 | # Values 2 | -------------------------------------------------------------------------------- /layouts/book/src/layouts/README.md: -------------------------------------------------------------------------------- 1 | # Layouts 2 | 3 | A layout defines a bidirectional transformation from/to RDF datasets and 4 | tree values (as defined in the [Values](/data-model/values.md) section). 5 | Using a layout to transform an RDF dataset to a value is called *serialization*. 6 | The inverse transformation, from value to RDF dataset, is called 7 | *deserialization*. 8 | 9 | TODO illustration 10 | 11 | ## Inputs 12 | 13 | Each layout has a set of inputs specifying which RDF resources are subject to 14 | the transformation. 15 | 16 | TODO example 17 | 18 | ## Variable Introduction 19 | 20 | TODO 21 | 22 | ## Type Definition 23 | 24 | ```ts 25 | type Layout = LiteralLayout | ProductLayout | SumLayout | ListLayout | Always | Never ; 26 | ``` -------------------------------------------------------------------------------- /layouts/book/src/layouts/functional-layouts.md: -------------------------------------------------------------------------------- 1 | # Functional Layout 2 | 3 | A functional layout is a layout where each tree node matching this layout 4 | represents exactly one RDF resource. 5 | In other words, such layout defines *function* from tree nodes to RDF resources. 6 | A functional layout and all its referenced layouts must have exactly **one** input (`self`). 7 | 8 | ## Addressing 9 | 10 | We can use [tree paths](../data-model/paths.md) to address tree nodes. 11 | In a functional layout, each tree node maps to one RDF resource. 12 | This means we can use the tree paths to address RDF resources (and their layout). -------------------------------------------------------------------------------- /layouts/book/src/layouts/lists.md: -------------------------------------------------------------------------------- 1 | # List Layouts 2 | 3 | ## Unordered List 4 | 5 | TODO 6 | 7 | ## Ordered List 8 | 9 | TODO 10 | 11 | ## Sized List 12 | 13 | TODO -------------------------------------------------------------------------------- /layouts/book/src/layouts/literals.md: -------------------------------------------------------------------------------- 1 | # Literal Layouts 2 | 3 | A literal layout matches any literal tree value and any RDF literal lexical 4 | representation satisfying a given set of constraints. 5 | Literal layouts are refined further into five categories corresponding to the 6 | five primitive data-types defined by TreeLDR's data-model (unit, boolean, 7 | number, binary string and text string). 8 | The following table summarizes what matches a literal layout in the tree space, 9 | and RDF space. 10 | 11 | | Literal layout type | Tree space | RDF space | 12 | | ------------------- | ---------- | --------- | 13 | | Unit | Unit, or any predefined constant | Any resource | 14 | | Boolean | Any boolean | Any resource with a literal representation of type | 15 | | Number | Any number | Any resource with a literal representation of type | 16 | | Binary string | Any binary string | Any resource with a literal representation of type or | 17 | | Text string | Any text string | Any resource with a literal representation | 18 | 19 | Literal layouts are represented by values of the following type: 20 | 21 | ```ts 22 | type LiteralLayout = 23 | UnitLayout 24 | | BooleanLayout 25 | | NumberLayout 26 | | BinaryStringLayout 27 | | TextStringLayout 28 | ``` 29 | 30 | ## Unit 31 | 32 | The unit layout matches, in the tree space, the unit value (or any given 33 | constant) and in the RDF space, any resource. 34 | 35 | Unit layouts are represented by values of the following type: 36 | 37 | ```ts 38 | type UnitLayout = LayoutDefinition & { 39 | "type": "unit", 40 | "const"?: Any 41 | } 42 | ``` 43 | 44 | The optional `const` attribute specifies which tree value matches the layout. 45 | The default value for the `const` attribute is the unit value `()`. 46 | 47 | ## Boolean 48 | 49 | ```ts 50 | type BooleanLayout = LayoutDefinition & { 51 | "type": "boolean", 52 | "resource": Resource 53 | } 54 | ``` 55 | 56 | ## Number 57 | 58 | ```ts 59 | type NumberLayout = LayoutDefinition & { 60 | "type": "number", 61 | "resource": Resource 62 | } 63 | ``` 64 | 65 | ## Binary String 66 | 67 | ```ts 68 | type BinaryStringLayout = LayoutDefinition & { 69 | "type": "bytes", 70 | "resource": Resource 71 | } 72 | ``` 73 | 74 | ## Text String 75 | 76 | ```ts 77 | type TextStringLayout = LayoutDefinition & { 78 | "type": "string", 79 | "resource": Resource, 80 | "pattern"?: Regex 81 | } 82 | ``` -------------------------------------------------------------------------------- /layouts/book/src/layouts/product.md: -------------------------------------------------------------------------------- 1 | # Product Layout 2 | 3 | TODO -------------------------------------------------------------------------------- /layouts/book/src/layouts/record.md: -------------------------------------------------------------------------------- 1 | # Product (Record) 2 | -------------------------------------------------------------------------------- /layouts/book/src/layouts/sum.md: -------------------------------------------------------------------------------- 1 | # Sum Layout 2 | 3 | TODO -------------------------------------------------------------------------------- /layouts/book/src/rdf-basics.md: -------------------------------------------------------------------------------- 1 | # RDF Basics 2 | 3 | The Resource Description Framework (RDF) is a very simple but powerful data-model designed for the Semantic Web. 4 | In this model, every piece of data is a node in a labeled directed graph. Each 5 | node is called a **Resource**, and resources are connected together using 6 | *properties*, which are resources themselves. 7 | 8 |
9 | 10 | ```dot process 11 | digraph { 12 | a -> b [label = "d"] 13 | a -> c [label = "e"] 14 | } 15 | ``` 16 |
17 | 18 | In this example, `a`, `b`, `c`, `d` and `e` are all resources. 19 | 20 | ## Lexical Representations 21 | 22 | Resources are given *lexical representations* that uniquely identify them across 23 | the Web and give them meaning. 24 | There are three kind of lexical representations a resource can have: 25 | - [International Resource Identifiers (IRI)][iri], similar to URLs but with 26 | international characters; 27 | Example: `https://example.org/Ῥόδος` 28 | - [Literal values][literals] (a text string, a number, etc.); 29 | - [Blank node identifiers][blank-ids], that locally identifies a resource in a 30 | given RDF document. Blank node identifiers do not cross document boundaries 31 | and are solely used to give a temporary name to nameless resources. 32 | Such identifiers are similar to IRIs with `_` as scheme. 33 | Example: `_:SomeAnonymousResource`. 34 | 35 | [iri]: 36 | [literals]: 37 | [blank-ids]: 38 | 39 |
40 | 41 | ```dot process 42 | digraph { 43 | "https://example.org/#Alice" -> "_:Bob" [label = "https://example.org/#knows"] 44 | "https://example.org/#Alice" -> "29" [label = "https://example.org/#age"] 45 | "_:Bob" -> "https://example.org/#Alice" 46 | } 47 | ``` 48 |
49 | 50 | Lexical representation are also used to define of some proper textual syntaxes 51 | for RDF, such as [N-Triples][n-triples] or [RDF-Turtle][rdf-turtle]. 52 | In the document, we will primarily use the N-Triples syntax to write RDF 53 | datasets. 54 | In this syntax, a dataset is described by enumerating every edge of the graph, 55 | a **triple** of the form `subject predicate object`, in sequence. 56 | The `subject` is the starting point of the edge, the predicate the label of 57 | the edge, and the object the endpoint of the edge. 58 | 59 | [n-triples]: 60 | [rdf-turtle]: 61 | 62 | Here is the above graph written as N-Triples: 63 | ``` 64 | _:Bob . 65 | "29"^^http://www.w3.org/2001/XMLSchema#integer . 66 | _:Bob . 67 | ``` 68 | 69 | ## Interpretations 70 | 71 | The mapping from lexical representation to resource is called an 72 | **interpretation**. It is a partial function mapping the set of lexical 73 | representations to the set of resources. 74 | For instance, the following lexical dataset: 75 | 76 |
77 | 78 | ```dot process 79 | digraph { 80 | rankdir="LR" 81 | "_:Superman" -> "_:ClarkKent" [label = "http://www.w3.org/2002/07/owl#sameAs"] 82 | } 83 | ``` 84 |
85 | 86 | can be *interpreted* into the following interpreted resource graph when the 87 | terms `_:Superman` and `_:ClarkKent` are interpreted as the same resource 88 | following the semantics of `http://www.w3.org/2002/07/owl#sameAs`: 89 |
90 | 91 | ```dot process 92 | digraph { 93 | rankdir="LR" 94 | a [label=""] 95 | a -> a 96 | } 97 | ``` 98 |
99 | 100 | As shown in this example, the same resource may have more than one lexical 101 | representation. 102 | In this case the shape of the lexical representation of a graph may differ from 103 | its actual shape. 104 | Here since `_:Superman` and `_:ClarkKent` are interpreted as the same resource, 105 | the lexical form of the graph (on top) contains two nodes, while its interpreted 106 | form (on the bottom) contains only a single node. 107 | 108 | ## Datasets 109 | 110 | An RDF dataset is a collection of RDF graphs. A graph can be either the *default 111 | graph*, or a *named graph*. A named graph is also a resource. 112 | 113 | TODO example 114 | 115 | The N-Quads syntax can be used to represent RDF datasets. 116 | It is similar to the N-Triples syntax showed previously above, but lists *quads* 117 | instead of *triple*, where an optional fourth parameter is here to specify in 118 | which named graph the triple occurs. If no fourth parameter is specified, it 119 | means the triple appears in the default graph. 120 | 121 | TODO example -------------------------------------------------------------------------------- /layouts/book/src/rdf-vocabulary/README.md: -------------------------------------------------------------------------------- 1 | # RDF Vocabulary 2 | 3 | This chapter defines an RDF vocabulary for TreeLDR Layouts. This vocabulary defines a schema for abstract layouts, completed with layout definitions. -------------------------------------------------------------------------------- /layouts/book/src/rdf-vocabulary/layouts-of-layouts.md: -------------------------------------------------------------------------------- 1 | # Layouts of Layouts 2 | 3 | This section provides TreeLDR layout definitions for abstract layouts. 4 | 5 | TODO -------------------------------------------------------------------------------- /layouts/book/src/rdf-vocabulary/schema.md: -------------------------------------------------------------------------------- 1 | # Schema 2 | 3 | TODO -------------------------------------------------------------------------------- /layouts/examples/combine.rs: -------------------------------------------------------------------------------- 1 | use rdf_types::{dataset::IndexedBTreeDataset, BlankIdBuf, Id, RdfDisplay, Term}; 2 | use serde_json::json; 3 | use treeldr_layouts::{ 4 | abs::{syntax::Layout, Builder}, 5 | distill::de::Options, 6 | }; 7 | 8 | fn main() { 9 | let mut builder = Builder::default(); 10 | 11 | let nested_json = json!( 12 | { 13 | "nested": { "egg": 1 } 14 | } 15 | ); 16 | 17 | let unnested_json = json!( 18 | { 19 | "unnested": { "bird": true } 20 | } 21 | ); 22 | let nested_layout: Layout = serde_json::from_value(json!({ 23 | "id": "https://example.org#nestedResult", 24 | "type": "record", 25 | "fields": { 26 | "nested": { 27 | "value": { 28 | "id": "https://example.org#nested", 29 | "type": "record", 30 | "fields": { 31 | "egg": { 32 | "value": { 33 | "type": "number", 34 | "id": "https://example.org#egg", 35 | "datatype": "http://www.w3.org/2001/XMLSchema#nonNegativeInteger" 36 | }, 37 | "property": "https://example.org#egg" 38 | } 39 | } 40 | }, 41 | "property": "https://example.org#nested" 42 | } 43 | } 44 | })) 45 | .unwrap(); 46 | println!("Nested layout unwrapped!"); 47 | 48 | let unnested_layout: Layout = serde_json::from_value(json!({ 49 | "id": "https://example.org#unnestedResult", 50 | "type": "record", 51 | "fields": { 52 | "unnested": { 53 | "value": { 54 | "id": "https://example.org#unnested", 55 | "type": "record", 56 | "fields": { 57 | "bird": { 58 | "value": { 59 | "type": "boolean", 60 | "id": "https://example.org#bird" 61 | }, 62 | "property": "https://example.org#bird" 63 | }, 64 | } 65 | }, 66 | "property": "https://example.org#unnested" 67 | } 68 | } 69 | })) 70 | .unwrap(); 71 | println!("Unnested layout unwrapped!"); 72 | 73 | let final_layout: Layout = serde_json::from_value(json!({ 74 | "id": "https://example.org#anOutputLayout", 75 | "type": "record", 76 | "fields": { 77 | "nested": { 78 | "value": "https://example.org#nested", 79 | "property": "https://example.org#nested" 80 | }, 81 | "bird": { 82 | "intro": ["unnested", "bird"], 83 | "value": { 84 | "layout": "https://example.org#bird", 85 | "input": "_:bird" 86 | }, 87 | "dataset": [ 88 | ["_:self", "https://example.org#unnested", "_:unnested"], 89 | ["_:unnested", "https://example.org#bird", "_:bird"] 90 | ] 91 | } 92 | } 93 | })) 94 | .unwrap(); 95 | println!("Final layout unwrapped!"); 96 | 97 | let nested_ref = nested_layout.build(&mut builder).unwrap(); 98 | println!("Made nested ref: {nested_ref:?}"); 99 | 100 | let unnested_ref = unnested_layout.build(&mut builder).unwrap(); 101 | println!("Made unnested ref: {unnested_ref:?}"); 102 | 103 | let final_ref = final_layout.build(&mut builder).unwrap(); 104 | println!("Made final ref: {final_ref:?}"); 105 | 106 | let layouts = builder.build(); 107 | println!("Built the layouts"); 108 | 109 | let mut generator = rdf_types::generator::Blank::new(); 110 | 111 | let mut dataset = IndexedBTreeDataset::default(); 112 | let (nested_dataset, _) = treeldr_layouts::distill::de::dehydrate( 113 | &layouts, 114 | &nested_json.into(), 115 | &nested_ref, 116 | Options::default().with_generator(&mut generator), 117 | ) 118 | .unwrap(); 119 | 120 | dataset.extend(nested_dataset); 121 | 122 | let (unnested_dataset, _) = treeldr_layouts::distill::de::dehydrate( 123 | &layouts, 124 | &unnested_json.into(), 125 | &unnested_ref, 126 | Options::default().with_generator(&mut generator), 127 | ) 128 | .unwrap(); 129 | 130 | dataset.extend(unnested_dataset); 131 | 132 | println!("Dataset Built:"); 133 | for quad in &dataset { 134 | println!("{} .", quad.rdf_display()); 135 | } 136 | 137 | let v = treeldr_layouts::hydrate( 138 | &layouts, 139 | &dataset, 140 | &final_ref, 141 | &[Term::Id(Id::Blank( 142 | BlankIdBuf::from_suffix("input0").unwrap(), 143 | ))], 144 | ) 145 | .unwrap() 146 | .into_untyped(); 147 | 148 | println!( 149 | "Hydrated value:\n{}", 150 | serde_json::to_string_pretty(&v).unwrap() 151 | ); 152 | } 153 | -------------------------------------------------------------------------------- /layouts/examples/record.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "https://example.org/#RecordLayout", 3 | "type": "record", 4 | "prefixes": { "tldr": "https://treeldr.org/prelude#" }, 5 | "fields": { 6 | "id": { 7 | "intro": [], 8 | "value": { 9 | "layout": "tldr:id", 10 | "input": ["_:self"] 11 | } 12 | }, 13 | "name": { 14 | "value": "tldr:string", 15 | "property": "https://schema.org/name" 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /layouts/prelude/boolean.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "https://treeldr.org/prelude#boolean", 3 | "type": "boolean" 4 | } -------------------------------------------------------------------------------- /layouts/prelude/i16.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "https://treeldr.org/prelude#i16", 3 | "type": "number", 4 | "datatype": "http://www.w3.org/2001/XMLSchema#short" 5 | } -------------------------------------------------------------------------------- /layouts/prelude/i32.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "https://treeldr.org/prelude#i32", 3 | "type": "number", 4 | "datatype": "http://www.w3.org/2001/XMLSchema#int" 5 | } -------------------------------------------------------------------------------- /layouts/prelude/i64.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "https://treeldr.org/prelude#i64", 3 | "type": "number", 4 | "datatype": "http://www.w3.org/2001/XMLSchema#long" 5 | } -------------------------------------------------------------------------------- /layouts/prelude/i8.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "https://treeldr.org/prelude#i8", 3 | "type": "number", 4 | "datatype": "http://www.w3.org/2001/XMLSchema#byte" 5 | } -------------------------------------------------------------------------------- /layouts/prelude/id.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "https://treeldr.org/prelude#id", 3 | "type": "id" 4 | } -------------------------------------------------------------------------------- /layouts/prelude/string.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "https://treeldr.org/prelude#string", 3 | "type": "string" 4 | } -------------------------------------------------------------------------------- /layouts/prelude/u16.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "https://treeldr.org/prelude#u16", 3 | "type": "number", 4 | "datatype": "http://www.w3.org/2001/XMLSchema#unsignedShort" 5 | } -------------------------------------------------------------------------------- /layouts/prelude/u32.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "https://treeldr.org/prelude#u32", 3 | "type": "number", 4 | "datatype": "http://www.w3.org/2001/XMLSchema#unsignedInt" 5 | } -------------------------------------------------------------------------------- /layouts/prelude/u64.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "https://treeldr.org/prelude#u64", 3 | "type": "number", 4 | "datatype": "http://www.w3.org/2001/XMLSchema#unsignedLong" 5 | } -------------------------------------------------------------------------------- /layouts/prelude/u8.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "https://treeldr.org/prelude#u8", 3 | "type": "number", 4 | "datatype": "http://www.w3.org/2001/XMLSchema#unsignedByte" 5 | } -------------------------------------------------------------------------------- /layouts/prelude/unit.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "https://treeldr.org/prelude#unit", 3 | "type": "unit" 4 | } -------------------------------------------------------------------------------- /layouts/src/abs/layout/always.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spruceid/treeldr/82f86519141c1188fd69a2fd3730388973218207/layouts/src/abs/layout/always.rs -------------------------------------------------------------------------------- /layouts/src/abs/layout/intersection.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /layouts/src/abs/layout/list/mod.rs: -------------------------------------------------------------------------------- 1 | pub use crate::layout::list::{ 2 | ItemLayout, ListLayout, ListLayoutType, SizedListLayout, UnorderedListLayout, 3 | }; 4 | 5 | pub mod ordered; 6 | 7 | pub use ordered::OrderedListLayout; 8 | -------------------------------------------------------------------------------- /layouts/src/abs/layout/list/ordered.rs: -------------------------------------------------------------------------------- 1 | pub use crate::layout::list::ordered::{NodeLayout, OrderedListLayout}; 2 | -------------------------------------------------------------------------------- /layouts/src/abs/layout/literal/data.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | 3 | use crate::{Dataset, Pattern}; 4 | 5 | pub use crate::layout::{BooleanLayout, ByteStringLayout, NumberLayout, UnitLayout}; 6 | 7 | use crate::abs::RegExp; 8 | 9 | /// Data layout. 10 | #[derive(Clone)] 11 | pub enum DataLayout { 12 | Unit(UnitLayout), 13 | Boolean(BooleanLayout), 14 | Number(NumberLayout), 15 | ByteString(ByteStringLayout), 16 | TextString(TextStringLayout), 17 | } 18 | 19 | impl DataLayout { 20 | pub fn build(&self) -> crate::layout::DataLayout { 21 | match self { 22 | Self::Unit(layout) => crate::layout::DataLayout::Unit(layout.clone()), 23 | Self::Boolean(layout) => crate::layout::DataLayout::Boolean(layout.clone()), 24 | Self::Number(layout) => crate::layout::DataLayout::Number(layout.clone()), 25 | Self::ByteString(layout) => crate::layout::DataLayout::ByteString(layout.clone()), 26 | Self::TextString(layout) => crate::layout::DataLayout::TextString(layout.build()), 27 | } 28 | } 29 | } 30 | 31 | #[derive(Clone)] 32 | pub struct TextStringLayout { 33 | pub input: u32, 34 | 35 | pub intro: u32, 36 | 37 | pub pattern: Option, 38 | 39 | pub dataset: Dataset, 40 | 41 | pub resource: Pattern, 42 | 43 | pub datatype: R, 44 | 45 | pub properties: BTreeMap, 46 | } 47 | 48 | impl TextStringLayout { 49 | pub fn build(&self) -> crate::layout::TextStringLayout { 50 | crate::layout::TextStringLayout { 51 | input: self.input, 52 | intro: self.intro, 53 | pattern: self.pattern.as_ref().map(|e| e.build()), 54 | dataset: self.dataset.clone(), 55 | resource: self.resource.clone(), 56 | datatype: self.datatype.clone(), 57 | extra_properties: self.properties.clone(), 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /layouts/src/abs/layout/literal/id.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | 3 | use crate::{Dataset, Pattern}; 4 | 5 | use crate::abs::RegExp; 6 | 7 | pub use crate::layout::literal::id::IdLayoutType; 8 | 9 | pub struct IdLayout { 10 | pub input: u32, 11 | 12 | pub intro: u32, 13 | 14 | pub dataset: Dataset, 15 | 16 | pub pattern: Option, 17 | 18 | pub resource: Pattern, 19 | 20 | /// Additional properties. 21 | pub properties: BTreeMap, 22 | } 23 | 24 | impl IdLayout { 25 | pub fn build(&self) -> crate::layout::IdLayout { 26 | crate::layout::IdLayout { 27 | input: self.input, 28 | intro: self.intro, 29 | dataset: self.dataset.clone(), 30 | pattern: self.pattern.as_ref().map(RegExp::build), 31 | resource: self.resource.clone(), 32 | extra_properties: self.properties.clone(), 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /layouts/src/abs/layout/literal/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod data; 2 | pub mod id; 3 | 4 | pub use data::{ 5 | BooleanLayout, ByteStringLayout, DataLayout, NumberLayout, TextStringLayout, UnitLayout, 6 | }; 7 | pub use id::{IdLayout, IdLayoutType}; 8 | 9 | pub struct LiteralLayoutType; 10 | 11 | pub enum LiteralLayout { 12 | Data(DataLayout), 13 | Id(IdLayout), 14 | } 15 | 16 | impl LiteralLayout { 17 | pub fn build(&self) -> crate::layout::LiteralLayout { 18 | match self { 19 | Self::Data(layout) => crate::layout::LiteralLayout::Data(layout.build()), 20 | Self::Id(layout) => crate::layout::LiteralLayout::Id(layout.build()), 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /layouts/src/abs/layout/mod.rs: -------------------------------------------------------------------------------- 1 | mod intersection; 2 | pub mod list; 3 | mod literal; 4 | pub mod product; 5 | pub mod sum; 6 | mod r#union; 7 | 8 | use crate::{layout::LayoutType, Ref}; 9 | pub use list::{ 10 | ListLayout, ListLayoutType, OrderedListLayout, SizedListLayout, UnorderedListLayout, 11 | }; 12 | pub use literal::{ 13 | BooleanLayout, ByteStringLayout, DataLayout, IdLayout, IdLayoutType, LiteralLayout, 14 | LiteralLayoutType, NumberLayout, TextStringLayout, UnitLayout, 15 | }; 16 | pub use product::ProductLayout; 17 | pub use sum::SumLayout; 18 | 19 | /// Pre-built layout. 20 | /// 21 | /// This layout representation lays between the abstract syntax representation 22 | /// and the fully compiled layout representation. 23 | /// 24 | /// In this representation, variable names a stripped and nested layout are 25 | /// flattened. However contrarily to fully compiled layouts, intersection and 26 | /// union layouts are not yet computed. 27 | pub enum Layout { 28 | /// Matches nothing. 29 | Never, 30 | 31 | /// Matches literal values. 32 | Literal(LiteralLayout), 33 | 34 | /// Matches objects/records. 35 | Product(ProductLayout), 36 | 37 | /// Matches lists. 38 | List(ListLayout), 39 | 40 | /// Matches exactly one of the given layouts. 41 | Sum(SumLayout), 42 | 43 | /// Matches anything. 44 | Always, 45 | 46 | /// Layout union. 47 | Union(Vec>), 48 | 49 | /// Layout intersection. 50 | Intersection(Vec>), 51 | } 52 | 53 | impl Layout { 54 | pub fn build(&self) -> crate::Layout { 55 | match self { 56 | Self::Never => crate::Layout::Never, 57 | Self::Literal(layout) => crate::Layout::Literal(layout.build()), 58 | Self::Product(layout) => crate::Layout::Product(layout.clone()), 59 | Self::List(layout) => crate::Layout::List(layout.clone()), 60 | Self::Sum(layout) => crate::Layout::Sum(layout.clone()), 61 | Self::Always => crate::Layout::Always, 62 | Self::Union(_layout) => { 63 | todo!() 64 | } 65 | Self::Intersection(_layout) => { 66 | todo!() 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /layouts/src/abs/layout/never.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spruceid/treeldr/82f86519141c1188fd69a2fd3730388973218207/layouts/src/abs/layout/never.rs -------------------------------------------------------------------------------- /layouts/src/abs/layout/product.rs: -------------------------------------------------------------------------------- 1 | pub use crate::layout::product::{Field, ProductLayout, ProductLayoutType}; 2 | -------------------------------------------------------------------------------- /layouts/src/abs/layout/sum.rs: -------------------------------------------------------------------------------- 1 | pub use crate::layout::sum::{SumLayout, SumLayoutType, Variant}; 2 | -------------------------------------------------------------------------------- /layouts/src/abs/layout/union.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /layouts/src/abs/mod.rs: -------------------------------------------------------------------------------- 1 | //! Abstract syntax implementation for layouts. 2 | pub mod layout; 3 | pub mod regexp; 4 | pub mod syntax; 5 | 6 | use std::collections::BTreeMap; 7 | 8 | use crate::{layout::LayoutType, Ref}; 9 | pub use layout::Layout; 10 | use rdf_types::Interpretation; 11 | pub use regexp::RegExp; 12 | 13 | /// Layout builder. 14 | /// 15 | /// Stores all the pre-built layouts. Can be used to build a 16 | /// [`Layouts`](crate::Layouts) collection using the [`build`](Self::build) 17 | /// method. 18 | pub struct Builder { 19 | /// Pre-built layouts. 20 | layouts: BTreeMap>, 21 | } 22 | 23 | impl Builder { 24 | /// Creates a new empty layout builder. 25 | pub fn new() -> Self { 26 | Self { 27 | layouts: BTreeMap::new(), 28 | } 29 | } 30 | 31 | /// Borrows the builder with an RDF interpretation. 32 | pub fn with_interpretation_mut<'a, V, I: Interpretation>( 33 | &'a mut self, 34 | vocabulary: &'a mut V, 35 | interpretation: &'a mut I, 36 | ) -> BuilderWithInterpretationMut<'a, V, I> { 37 | BuilderWithInterpretationMut { 38 | vocabulary, 39 | interpretation, 40 | builder: self, 41 | } 42 | } 43 | } 44 | 45 | impl Builder { 46 | /// Borrows the builder with a the lexical RDF interpretation (`()`) 47 | /// combined with a node identifier generator. 48 | pub fn with_generator_mut(&mut self, generator: G) -> BuilderWithGeneratorMut { 49 | BuilderWithGeneratorMut { 50 | builder: self, 51 | generator, 52 | } 53 | } 54 | } 55 | 56 | pub type InsertResult = (Ref, Option>); 57 | 58 | impl Builder { 59 | pub fn insert(&mut self, id: R, layout: Layout) -> InsertResult { 60 | self.insert_with(id, |_| layout) 61 | } 62 | 63 | pub fn insert_with( 64 | &mut self, 65 | id: R, 66 | builder: impl FnOnce(&Ref) -> Layout, 67 | ) -> InsertResult { 68 | let layout_ref = Ref::new(id.clone()); 69 | let layout = builder(&layout_ref); 70 | 71 | let old_layout = self.layouts.insert(id, layout); 72 | 73 | (layout_ref, old_layout) 74 | } 75 | 76 | pub fn get_or_insert_with( 77 | &mut self, 78 | layout_ref: Ref, 79 | builder: impl FnOnce(&Ref) -> Layout, 80 | ) -> &Layout { 81 | self.layouts 82 | .entry(layout_ref.into_id()) 83 | .or_insert_with_key(|id| builder(Ref::new_ref(id))) 84 | } 85 | 86 | pub fn build(&self) -> crate::Layouts { 87 | let mut result = crate::Layouts::new(); 88 | 89 | for (id, layout) in &self.layouts { 90 | result.insert(id.clone(), layout.build()); 91 | } 92 | 93 | result 94 | } 95 | } 96 | 97 | impl Default for Builder { 98 | fn default() -> Self { 99 | Self::new() 100 | } 101 | } 102 | 103 | pub struct BuilderWithGeneratorMut<'a, G> { 104 | builder: &'a mut Builder, 105 | generator: G, 106 | } 107 | 108 | pub struct BuilderWithInterpretationMut<'a, V, I: Interpretation> { 109 | vocabulary: &'a mut V, 110 | interpretation: &'a mut I, 111 | builder: &'a mut Builder, 112 | } 113 | 114 | fn is_false(b: &bool) -> bool { 115 | !*b 116 | } 117 | -------------------------------------------------------------------------------- /layouts/src/abs/syntax/dataset.rs: -------------------------------------------------------------------------------- 1 | use json_syntax::TryFromJson; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | use super::{expect_array, Build, BuildError, Context, Error, Pattern, Scope}; 5 | 6 | #[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] 7 | #[serde(transparent)] 8 | pub struct Dataset(Vec); 9 | 10 | impl Dataset { 11 | pub fn is_empty(&self) -> bool { 12 | self.0.is_empty() 13 | } 14 | } 15 | 16 | impl From> for Dataset { 17 | fn from(value: Vec) -> Self { 18 | Self(value) 19 | } 20 | } 21 | 22 | impl TryFromJson for Dataset { 23 | type Error = Error; 24 | 25 | fn try_from_json_at( 26 | json: &json_syntax::Value, 27 | code_map: &json_syntax::CodeMap, 28 | offset: usize, 29 | ) -> Result { 30 | Vec::try_from_json_at(json, code_map, offset).map(Self) 31 | } 32 | } 33 | 34 | impl Build for Dataset 35 | where 36 | C::Resource: Clone, 37 | { 38 | type Target = crate::Dataset; 39 | 40 | fn build(&self, context: &mut C, scope: &Scope) -> Result { 41 | let mut dataset = crate::Dataset::new(); 42 | for quad in &self.0 { 43 | dataset.insert(quad.build(context, scope)?); 44 | } 45 | 46 | Ok(dataset) 47 | } 48 | } 49 | 50 | #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] 51 | pub struct Quad( 52 | pub Pattern, 53 | pub Pattern, 54 | pub Pattern, 55 | #[serde(default, skip_serializing_if = "Option::is_none")] pub Option, 56 | ); 57 | 58 | impl TryFromJson for Quad { 59 | type Error = Error; 60 | 61 | fn try_from_json_at( 62 | json: &json_syntax::Value, 63 | code_map: &json_syntax::CodeMap, 64 | offset: usize, 65 | ) -> Result { 66 | let array = expect_array(json, offset)?; 67 | 68 | if array.len() < 3 { 69 | return Err(Error::MissingQuadPattern(offset)); 70 | } 71 | 72 | if array.len() > 4 { 73 | return Err(Error::TooManyQuadPatterns(offset)); 74 | } 75 | 76 | let mut component_offset = offset + 1; 77 | let s = Pattern::try_from_json_at(&array[0], code_map, component_offset)?; 78 | component_offset += code_map.get(component_offset).unwrap().volume; 79 | let p = Pattern::try_from_json_at(&array[1], code_map, component_offset)?; 80 | component_offset += code_map.get(component_offset).unwrap().volume; 81 | let o = Pattern::try_from_json_at(&array[2], code_map, component_offset)?; 82 | component_offset += code_map.get(component_offset).unwrap().volume; 83 | let g = array 84 | .get(3) 85 | .map(|g| Pattern::try_from_json_at(g, code_map, component_offset)) 86 | .transpose()?; 87 | 88 | Ok(Self(s, p, o, g)) 89 | } 90 | } 91 | 92 | impl Build for Pattern { 93 | type Target = crate::Pattern; 94 | 95 | fn build(&self, context: &mut C, scope: &Scope) -> Result { 96 | match self { 97 | Self::Var(name) => Ok(crate::Pattern::Var(scope.variable(name)?)), 98 | Self::Iri(compact_iri) => { 99 | let iri = compact_iri.resolve(scope)?; 100 | Ok(crate::Pattern::Resource(context.iri_resource(&iri))) 101 | } 102 | Self::Literal(l) => Ok(crate::Pattern::Resource( 103 | context.literal_resource(&l.value, l.type_.resolve(scope)?.as_lexical_type_ref()), 104 | )), 105 | } 106 | } 107 | } 108 | 109 | impl Build for Quad { 110 | type Target = rdf_types::Quad< 111 | crate::Pattern, 112 | crate::Pattern, 113 | crate::Pattern, 114 | crate::Pattern, 115 | >; 116 | 117 | fn build(&self, context: &mut C, scope: &Scope) -> Result { 118 | Ok(rdf_types::Quad( 119 | self.0.build(context, scope)?, 120 | self.1.build(context, scope)?, 121 | self.2.build(context, scope)?, 122 | self.3 123 | .as_ref() 124 | .map(|g| g.build(context, scope)) 125 | .transpose()?, 126 | )) 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /layouts/src/abs/syntax/layout/intersection.rs: -------------------------------------------------------------------------------- 1 | use json_syntax::TryFromJsonObject; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | use crate::{ 5 | abs::syntax::{check_type, Build, BuildError, Context, Error, ObjectUnusedEntries, Scope}, 6 | layout::LayoutType, 7 | Ref, 8 | }; 9 | 10 | use super::{IntersectionLayoutType, LayoutHeader}; 11 | 12 | #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] 13 | #[serde(deny_unknown_fields)] 14 | pub struct IntersectionLayout { 15 | #[serde(rename = "type")] 16 | pub type_: IntersectionLayoutType, 17 | 18 | #[serde(flatten)] 19 | pub header: LayoutHeader, 20 | } 21 | 22 | impl TryFromJsonObject for IntersectionLayout { 23 | type Error = Error; 24 | 25 | fn try_from_json_object_at( 26 | object: &json_syntax::Object, 27 | code_map: &json_syntax::CodeMap, 28 | offset: usize, 29 | ) -> Result { 30 | let mut unused_entries = ObjectUnusedEntries::new(object, code_map, offset); 31 | check_type( 32 | object, 33 | IntersectionLayoutType::NAME, 34 | &mut unused_entries, 35 | code_map, 36 | offset, 37 | )?; 38 | let result = Self { 39 | type_: IntersectionLayoutType, 40 | header: LayoutHeader::try_from_json_object_at( 41 | object, 42 | &mut unused_entries, 43 | code_map, 44 | offset, 45 | )?, 46 | }; 47 | unused_entries.check()?; 48 | Ok(result) 49 | } 50 | } 51 | 52 | impl Build for IntersectionLayout { 53 | type Target = Vec>; 54 | 55 | fn build(&self, _context: &mut C, _scope: &Scope) -> Result { 56 | unimplemented!() 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /layouts/src/abs/syntax/layout/product.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | 3 | use json_syntax::{TryFromJson, TryFromJsonObject}; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | use crate::{ 7 | abs::{ 8 | self, 9 | syntax::{ 10 | check_type, expect_object, get_entry, require_entry, Build, BuildError, Context, 11 | Dataset, Error, ObjectUnusedEntries, Pattern, Scope, ValueFormatOrLayout, ValueIntro, 12 | }, 13 | }, 14 | Value, 15 | }; 16 | 17 | use super::{LayoutHeader, ProductLayoutType}; 18 | 19 | #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] 20 | #[serde(deny_unknown_fields)] 21 | pub struct ProductLayout { 22 | #[serde(rename = "type")] 23 | pub type_: ProductLayoutType, 24 | 25 | #[serde(flatten)] 26 | pub header: LayoutHeader, 27 | 28 | #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] 29 | pub fields: BTreeMap, 30 | } 31 | 32 | impl TryFromJson for ProductLayout { 33 | type Error = Error; 34 | 35 | fn try_from_json_at( 36 | json: &json_syntax::Value, 37 | code_map: &json_syntax::CodeMap, 38 | offset: usize, 39 | ) -> Result { 40 | let object = expect_object(json, offset)?; 41 | Self::try_from_json_object_at(object, code_map, offset) 42 | } 43 | } 44 | 45 | impl TryFromJsonObject for ProductLayout { 46 | type Error = Error; 47 | 48 | fn try_from_json_object_at( 49 | object: &json_syntax::Object, 50 | code_map: &json_syntax::CodeMap, 51 | offset: usize, 52 | ) -> Result { 53 | let mut unused_entries = ObjectUnusedEntries::new(object, code_map, offset); 54 | check_type( 55 | object, 56 | ProductLayoutType::NAME, 57 | &mut unused_entries, 58 | code_map, 59 | offset, 60 | )?; 61 | let fields: BTreeMap = 62 | get_entry(object, "fields", &mut unused_entries, code_map, offset)?.unwrap_or_default(); 63 | let result = Self { 64 | type_: ProductLayoutType, 65 | header: LayoutHeader::try_from_json_object_at( 66 | object, 67 | &mut unused_entries, 68 | code_map, 69 | offset, 70 | )?, 71 | fields: fields 72 | .into_iter() 73 | .map(|(k, v)| (Value::string(k), v)) 74 | .collect(), 75 | }; 76 | unused_entries.check()?; 77 | Ok(result) 78 | } 79 | } 80 | 81 | impl Build for ProductLayout 82 | where 83 | C::Resource: Clone, 84 | { 85 | type Target = abs::layout::ProductLayout; 86 | 87 | fn build(&self, context: &mut C, scope: &Scope) -> Result { 88 | let (header, scope) = self.header.build(context, scope)?; 89 | 90 | let mut fields = BTreeMap::new(); 91 | 92 | for (name, field) in &self.fields { 93 | let scope = scope.with_intro(field.intro.as_slice())?; 94 | 95 | let mut dataset = field.dataset.build(context, &scope)?; 96 | 97 | if let Some(property) = &field.property { 98 | if self.header.input.is_empty() { 99 | return Err(BuildError::NoPropertySubject); 100 | } else { 101 | let subject = crate::Pattern::Var(0); 102 | if field.intro.is_empty() { 103 | return Err(BuildError::NoPropertyObject); 104 | } else { 105 | let object = crate::Pattern::Var( 106 | (self.header.input.len() + self.header.intro.len()) as u32, 107 | ); 108 | let predicate = property.build(context, &scope)?; 109 | dataset.insert(rdf_types::Quad(subject, predicate, object, None)); 110 | } 111 | } 112 | } 113 | 114 | fields.insert( 115 | name.to_owned(), 116 | crate::layout::product::Field { 117 | intro: field.intro.len() as u32, 118 | value: field.value.build(context, &scope)?, 119 | dataset, 120 | required: field.required, 121 | }, 122 | ); 123 | } 124 | 125 | Ok(abs::layout::ProductLayout { 126 | input: header.input, 127 | intro: header.intro, 128 | fields, 129 | dataset: header.dataset, 130 | extra_properties: header.properties, 131 | }) 132 | } 133 | } 134 | 135 | #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] 136 | #[serde(deny_unknown_fields)] 137 | pub struct Field { 138 | #[serde(default, skip_serializing_if = "ValueIntro::is_default")] 139 | pub intro: ValueIntro, 140 | 141 | pub value: ValueFormatOrLayout, 142 | 143 | #[serde(default, skip_serializing_if = "Dataset::is_empty")] 144 | pub dataset: Dataset, 145 | 146 | #[serde(default, skip_serializing_if = "Option::is_none")] 147 | pub property: Option, 148 | 149 | #[serde(default, skip_serializing_if = "crate::abs::is_false")] 150 | pub required: bool, 151 | } 152 | 153 | impl TryFromJson for Field { 154 | type Error = Error; 155 | 156 | fn try_from_json_at( 157 | json: &json_syntax::Value, 158 | code_map: &json_syntax::CodeMap, 159 | offset: usize, 160 | ) -> Result { 161 | let object = expect_object(json, offset)?; 162 | Self::try_from_json_object_at(object, code_map, offset) 163 | } 164 | } 165 | 166 | impl TryFromJsonObject for Field { 167 | type Error = Error; 168 | 169 | fn try_from_json_object_at( 170 | object: &json_syntax::Object, 171 | code_map: &json_syntax::CodeMap, 172 | offset: usize, 173 | ) -> Result { 174 | let mut unused_entries = ObjectUnusedEntries::new(object, code_map, offset); 175 | let result = Self { 176 | intro: get_entry(object, "intro", &mut unused_entries, code_map, offset)? 177 | .unwrap_or_default(), 178 | value: require_entry(object, "value", &mut unused_entries, code_map, offset)?, 179 | dataset: get_entry(object, "dataset", &mut unused_entries, code_map, offset)? 180 | .unwrap_or_default(), 181 | property: get_entry(object, "property", &mut unused_entries, code_map, offset)?, 182 | required: get_entry(object, "required", &mut unused_entries, code_map, offset)? 183 | .unwrap_or_default(), 184 | }; 185 | unused_entries.check()?; 186 | Ok(result) 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /layouts/src/abs/syntax/layout/union.rs: -------------------------------------------------------------------------------- 1 | use json_syntax::TryFromJsonObject; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | use crate::{ 5 | abs::syntax::{check_type, Build, BuildError, Context, Error, ObjectUnusedEntries, Scope}, 6 | layout::LayoutType, 7 | Ref, 8 | }; 9 | 10 | use super::{LayoutHeader, UnionLayoutType}; 11 | 12 | #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] 13 | #[serde(deny_unknown_fields)] 14 | pub struct UnionLayout { 15 | #[serde(rename = "type")] 16 | pub type_: UnionLayoutType, 17 | 18 | #[serde(flatten)] 19 | pub header: LayoutHeader, 20 | } 21 | 22 | impl TryFromJsonObject for UnionLayout { 23 | type Error = Error; 24 | 25 | fn try_from_json_object_at( 26 | object: &json_syntax::Object, 27 | code_map: &json_syntax::CodeMap, 28 | offset: usize, 29 | ) -> Result { 30 | let mut unused_entries = ObjectUnusedEntries::new(object, code_map, offset); 31 | check_type( 32 | object, 33 | UnionLayoutType::NAME, 34 | &mut unused_entries, 35 | code_map, 36 | offset, 37 | )?; 38 | let result = Self { 39 | type_: UnionLayoutType, 40 | header: LayoutHeader::try_from_json_object_at( 41 | object, 42 | &mut unused_entries, 43 | code_map, 44 | offset, 45 | )?, 46 | }; 47 | unused_entries.check()?; 48 | Ok(result) 49 | } 50 | } 51 | 52 | impl Build for UnionLayout { 53 | type Target = Vec>; 54 | 55 | fn build(&self, _context: &mut C, _scope: &Scope) -> Result { 56 | unimplemented!() 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /layouts/src/abs/syntax/pattern/compact_iri.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use iref::{IriBuf, IriRef, IriRefBuf}; 4 | use json_syntax::TryFromJson; 5 | use serde::{Deserialize, Serialize}; 6 | use xsd_types::XSD_STRING; 7 | 8 | use crate::abs::syntax::{expect_string, Build, BuildError, Context, Error, Scope}; 9 | 10 | #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] 11 | #[serde(transparent)] 12 | pub struct CompactIri(pub IriRefBuf); 13 | 14 | impl CompactIri { 15 | pub fn resolve(&self, scope: &Scope) -> Result { 16 | match self.0.as_iri() { 17 | Some(iri) => match scope.iri_prefix(iri.scheme().as_str()) { 18 | Some(prefix) => { 19 | let suffix = iri.split_once(':').unwrap().1; 20 | IriBuf::new(format!("{prefix}{suffix}")) 21 | .map_err(|e| BuildError::InvalidIri(e.0)) 22 | } 23 | None => Ok(iri.to_owned()), 24 | }, 25 | None => match &scope.base_iri() { 26 | Some(base_iri) => Ok(self.0.resolved(base_iri)), 27 | None => Err(BuildError::NoBaseIri(self.0.clone())), 28 | }, 29 | } 30 | } 31 | 32 | pub fn is_xsd_string(&self) -> bool { 33 | self.0 == XSD_STRING 34 | } 35 | 36 | pub fn xsd_string() -> Self { 37 | Self(XSD_STRING.to_owned().into()) 38 | } 39 | } 40 | 41 | #[derive(Debug, thiserror::Error)] 42 | #[error("invalid compact IRI `{0}`")] 43 | pub struct InvalidCompactIri(pub String); 44 | 45 | impl FromStr for CompactIri { 46 | type Err = InvalidCompactIri; 47 | 48 | fn from_str(value: &str) -> Result { 49 | match IriRef::new(value) { 50 | Ok(iri_ref) => Ok(Self(iri_ref.to_owned())), 51 | Err(_) => Err(InvalidCompactIri(value.to_owned())), 52 | } 53 | } 54 | } 55 | 56 | impl From for CompactIri { 57 | fn from(value: IriBuf) -> Self { 58 | Self(value.into()) 59 | } 60 | } 61 | 62 | impl TryFromJson for CompactIri { 63 | type Error = Error; 64 | 65 | fn try_from_json_at( 66 | json: &json_syntax::Value, 67 | _code_map: &json_syntax::CodeMap, 68 | offset: usize, 69 | ) -> Result { 70 | match IriRef::new(expect_string(json, offset)?) { 71 | Ok(iri_ref) => Ok(Self(iri_ref.to_owned())), 72 | Err(e) => Err(Error::InvalidCompactIri(offset, e.0.to_owned())), 73 | } 74 | } 75 | } 76 | 77 | impl Build for CompactIri { 78 | type Target = C::Resource; 79 | 80 | /// Build this layout fragment using the given `context` in the given 81 | /// `scope`. 82 | fn build(&self, context: &mut C, scope: &Scope) -> Result { 83 | let iri = self.resolve(scope)?; 84 | Ok(context.iri_resource(&iri)) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /layouts/src/abs/syntax/pattern/literal.rs: -------------------------------------------------------------------------------- 1 | use iref::IriBuf; 2 | use langtag::LangTagBuf; 3 | use rdf_types::XSD_STRING; 4 | 5 | use crate::abs::syntax::{get_entry, require_entry, BuildError, Error, ObjectUnusedEntries, Scope}; 6 | 7 | use super::CompactIri; 8 | 9 | #[derive( 10 | Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize, 11 | )] 12 | pub struct LiteralValue { 13 | pub value: String, 14 | 15 | #[serde(flatten)] 16 | pub type_: LiteralType, 17 | } 18 | 19 | impl LiteralValue { 20 | pub fn try_from_json_object_at( 21 | object: &json_syntax::Object, 22 | code_map: &json_syntax::CodeMap, 23 | offset: usize, 24 | ) -> Result { 25 | let mut unused_entries = ObjectUnusedEntries::new(object, code_map, offset); 26 | let type_ = match get_entry(object, "type", &mut unused_entries, code_map, offset)? { 27 | Some(ty) => { 28 | // TODO check if language is present. 29 | LiteralType::Iri(LiteralTypeIri { type_: ty }) 30 | } 31 | None => { 32 | let language: String = 33 | require_entry(object, "language", &mut unused_entries, code_map, offset)?; 34 | match LangTagBuf::new(language) { 35 | Ok(language) => LiteralType::Language(LiteralTypeLanguage { language }), 36 | Err(e) => return Err(Error::InvalidLangTag(offset, e.0)), 37 | } 38 | } 39 | }; 40 | 41 | let value = require_entry(object, "value", &mut unused_entries, code_map, offset)?; 42 | unused_entries.check()?; 43 | 44 | Ok(Self { value, type_ }) 45 | } 46 | } 47 | 48 | impl From for LiteralValue { 49 | fn from(value: rdf_types::Literal) -> Self { 50 | Self { 51 | value: value.value, 52 | type_: value.type_.into(), 53 | } 54 | } 55 | } 56 | 57 | #[derive( 58 | Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize, 59 | )] 60 | #[serde(untagged)] 61 | pub enum LiteralType { 62 | Iri(LiteralTypeIri), 63 | Language(LiteralTypeLanguage), 64 | } 65 | 66 | impl Default for LiteralType { 67 | fn default() -> Self { 68 | Self::Iri(LiteralTypeIri { 69 | type_: XSD_STRING.to_owned().into(), 70 | }) 71 | } 72 | } 73 | 74 | impl LiteralType { 75 | pub fn resolve(&self, scope: &Scope) -> Result { 76 | match self { 77 | Self::Iri(iri) => Ok(rdf_types::LiteralType::Any(iri.resolve(scope)?)), 78 | Self::Language(lang) => Ok(rdf_types::LiteralType::LangString(lang.language.clone())), 79 | } 80 | } 81 | } 82 | 83 | impl From for LiteralType { 84 | fn from(value: rdf_types::LiteralType) -> Self { 85 | match value { 86 | rdf_types::LiteralType::Any(iri) => Self::Iri(LiteralTypeIri { type_: iri.into() }), 87 | rdf_types::LiteralType::LangString(tag) => { 88 | Self::Language(LiteralTypeLanguage { language: tag }) 89 | } 90 | } 91 | } 92 | } 93 | 94 | #[derive( 95 | Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize, 96 | )] 97 | pub struct LiteralTypeIri { 98 | #[serde( 99 | rename = "type", 100 | skip_serializing_if = "CompactIri::is_xsd_string", 101 | default = "CompactIri::xsd_string" 102 | )] 103 | pub type_: CompactIri, 104 | } 105 | 106 | impl LiteralTypeIri { 107 | pub fn resolve(&self, scope: &Scope) -> Result { 108 | self.type_.resolve(scope) 109 | } 110 | } 111 | 112 | #[derive( 113 | Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize, 114 | )] 115 | pub struct LiteralTypeLanguage { 116 | pub language: LangTagBuf, 117 | } 118 | -------------------------------------------------------------------------------- /layouts/src/abs/syntax/pattern/mod.rs: -------------------------------------------------------------------------------- 1 | mod compact_iri; 2 | mod literal; 3 | mod variable; 4 | 5 | use core::fmt; 6 | 7 | pub use compact_iri::*; 8 | use iref::{IriRef, IriRefBuf}; 9 | use json_syntax::{Kind, TryFromJson}; 10 | pub use literal::*; 11 | use rdf_types::{BlankId, BlankIdBuf, Id, Term, RDF_NIL}; 12 | use serde::{Deserialize, Serialize}; 13 | pub use variable::*; 14 | 15 | use super::{BuildError, Error, Scope}; 16 | 17 | #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 18 | pub enum Pattern { 19 | Var(VariableNameBuf), 20 | Iri(CompactIri), 21 | Literal(LiteralValue), 22 | } 23 | 24 | impl Pattern { 25 | pub fn is_variable(&self, name: &str) -> bool { 26 | match self { 27 | Self::Var(x) => x == name, 28 | _ => false, 29 | } 30 | } 31 | 32 | pub fn default_head() -> Self { 33 | Self::Var(VariableNameBuf("self".to_string())) 34 | } 35 | 36 | pub fn is_default_head(&self) -> bool { 37 | match self { 38 | Self::Var(x) => x == "self", 39 | _ => false, 40 | } 41 | } 42 | 43 | pub fn default_tail() -> Self { 44 | Self::Iri(CompactIri(RDF_NIL.to_owned().into())) 45 | } 46 | 47 | pub fn is_default_tail(&self) -> bool { 48 | match self { 49 | Self::Iri(CompactIri(iri_ref)) => iri_ref == RDF_NIL, 50 | _ => false, 51 | } 52 | } 53 | 54 | pub fn from_term(term: Term) -> Self { 55 | match term { 56 | Term::Id(Id::Iri(iri)) => Self::Iri(iri.into()), 57 | Term::Id(Id::Blank(b)) => Self::Var(VariableNameBuf(b.suffix().to_owned())), 58 | Term::Literal(l) => Self::Literal(l.into()), 59 | } 60 | } 61 | 62 | pub fn to_term(&self, scope: &Scope) -> Result { 63 | match self { 64 | Self::Var(name) => Ok(Term::blank(BlankIdBuf::from_suffix(name).unwrap())), 65 | Self::Iri(compact_iri) => compact_iri.resolve(scope).map(Term::iri), 66 | Self::Literal(l) => Ok(Term::Literal(rdf_types::Literal::new( 67 | l.value.clone(), 68 | l.type_.resolve(scope)?, 69 | ))), 70 | } 71 | } 72 | } 73 | 74 | impl TryFromJson for Pattern { 75 | type Error = Error; 76 | 77 | fn try_from_json_at( 78 | json: &json_syntax::Value, 79 | code_map: &json_syntax::CodeMap, 80 | offset: usize, 81 | ) -> Result { 82 | match json { 83 | json_syntax::Value::String(value) => match BlankId::new(value) { 84 | Ok(blank_id) => Ok(Pattern::Var(VariableNameBuf(blank_id.suffix().to_owned()))), 85 | Err(_) => match IriRef::new(value) { 86 | Ok(iri_ref) => Ok(Pattern::Iri(CompactIri(iri_ref.to_owned()))), 87 | Err(_) => Err(Error::InvalidPattern(offset, value.to_string())), 88 | }, 89 | }, 90 | json_syntax::Value::Object(value) => Ok(Self::Literal( 91 | LiteralValue::try_from_json_object_at(value, code_map, offset)?, 92 | )), 93 | other => Err(Error::Unexpected { 94 | offset, 95 | expected: Kind::String | Kind::Object, 96 | found: other.kind(), 97 | }), 98 | } 99 | } 100 | } 101 | 102 | impl Serialize for Pattern { 103 | fn serialize(&self, serializer: S) -> Result 104 | where 105 | S: serde::Serializer, 106 | { 107 | match self { 108 | Self::Var(name) => format!("_:{name}").serialize(serializer), 109 | Self::Iri(compact_iri) => compact_iri.serialize(serializer), 110 | Self::Literal(l) => l.serialize(serializer), 111 | } 112 | } 113 | } 114 | 115 | impl<'de> Deserialize<'de> for Pattern { 116 | fn deserialize(deserializer: D) -> Result 117 | where 118 | D: serde::Deserializer<'de>, 119 | { 120 | use serde::de::Error; 121 | 122 | #[derive(Serialize, Deserialize)] 123 | #[serde(untagged)] 124 | pub enum StringOrLiteral { 125 | String(String), 126 | Literal(LiteralValue), 127 | } 128 | 129 | struct Expected; 130 | 131 | impl serde::de::Expected for Expected { 132 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 133 | write!(formatter, "an IRI, blank node identifier or literal value") 134 | } 135 | } 136 | 137 | match StringOrLiteral::deserialize(deserializer)? { 138 | StringOrLiteral::String(v) => match BlankIdBuf::new(v) { 139 | Ok(blank_id) => Ok(Pattern::Var(VariableNameBuf(blank_id.suffix().to_owned()))), 140 | Err(e) => match IriRefBuf::new(e.0) { 141 | Ok(iri_ref) => Ok(Pattern::Iri(CompactIri(iri_ref))), 142 | Err(e) => Err(D::Error::invalid_value( 143 | serde::de::Unexpected::Str(&e.0), 144 | &Expected, 145 | )), 146 | }, 147 | }, 148 | StringOrLiteral::Literal(l) => Ok(Self::Literal(l)), 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /layouts/src/abs/syntax/pattern/variable.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | use std::borrow::Borrow; 3 | 4 | #[derive(Debug, thiserror::Error)] 5 | #[error("invalid variable name `{0}`")] 6 | pub struct InvalidVariableName(pub T); 7 | 8 | /// Variable name. 9 | /// 10 | /// Subset of `str` that can serve as a variable name in the abstract syntax. 11 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 12 | #[repr(transparent)] 13 | pub struct VariableName(str); 14 | 15 | impl VariableName { 16 | pub const SELF: &'static Self = unsafe { Self::new_unchecked("self") }; 17 | 18 | pub const VALUE: &'static Self = unsafe { Self::new_unchecked("value") }; 19 | 20 | /// Parses the given string and turns it into a variable name. 21 | pub fn new(s: &str) -> Result<&Self, InvalidVariableName<&str>> { 22 | if check_variable_name(s.chars()) { 23 | Ok(unsafe { Self::new_unchecked(s) }) 24 | } else { 25 | Err(InvalidVariableName(s)) 26 | } 27 | } 28 | 29 | /// Converts the given string into a variable name without parsing. 30 | /// 31 | /// # Safety 32 | /// 33 | /// The input string **must** be a valid variable name. 34 | pub const unsafe fn new_unchecked(s: &str) -> &Self { 35 | std::mem::transmute(s) 36 | } 37 | 38 | pub fn as_str(&self) -> &str { 39 | &self.0 40 | } 41 | } 42 | 43 | impl ToOwned for VariableName { 44 | type Owned = VariableNameBuf; 45 | 46 | fn to_owned(&self) -> Self::Owned { 47 | VariableNameBuf(self.0.to_owned()) 48 | } 49 | } 50 | 51 | impl PartialEq for VariableName { 52 | fn eq(&self, other: &str) -> bool { 53 | &self.0 == other 54 | } 55 | } 56 | 57 | impl std::ops::Deref for VariableName { 58 | type Target = str; 59 | 60 | fn deref(&self) -> &Self::Target { 61 | self.as_str() 62 | } 63 | } 64 | 65 | impl Borrow for VariableName { 66 | fn borrow(&self) -> &str { 67 | self.as_str() 68 | } 69 | } 70 | 71 | impl AsRef for VariableName { 72 | fn as_ref(&self) -> &str { 73 | self.as_str() 74 | } 75 | } 76 | 77 | fn check_variable_name>(mut chars: C) -> bool { 78 | match chars.next() { 79 | Some(c) if c.is_ascii_digit() || is_pn_char_u(c) => { 80 | for c in chars { 81 | if !is_pn_char(c) { 82 | return false; 83 | } 84 | } 85 | 86 | true 87 | } 88 | _ => false, 89 | } 90 | } 91 | 92 | fn is_pn_char_base(c: char) -> bool { 93 | matches!(c, 'A'..='Z' | 'a'..='z' | '\u{00c0}'..='\u{00d6}' | '\u{00d8}'..='\u{00f6}' | '\u{00f8}'..='\u{02ff}' | '\u{0370}'..='\u{037d}' | '\u{037f}'..='\u{1fff}' | '\u{200c}'..='\u{200d}' | '\u{2070}'..='\u{218f}' | '\u{2c00}'..='\u{2fef}' | '\u{3001}'..='\u{d7ff}' | '\u{f900}'..='\u{fdcf}' | '\u{fdf0}'..='\u{fffd}' | '\u{10000}'..='\u{effff}') 94 | } 95 | 96 | fn is_pn_char_u(c: char) -> bool { 97 | is_pn_char_base(c) || matches!(c, '_' | ':') 98 | } 99 | 100 | fn is_pn_char(c: char) -> bool { 101 | is_pn_char_u(c) 102 | || matches!(c, '-' | '0'..='9' | '\u{00b7}' | '\u{0300}'..='\u{036f}' | '\u{203f}'..='\u{2040}') 103 | } 104 | 105 | /// Variable name buffer. 106 | /// 107 | /// Subset of [`String`] that can serve as a variable name in the abstract syntax. 108 | #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 109 | pub struct VariableNameBuf(pub(crate) String); 110 | 111 | impl VariableNameBuf { 112 | /// Parses the given string to create a new variable name. 113 | pub fn new(s: String) -> Result { 114 | if check_variable_name(s.chars()) { 115 | Ok(Self(s)) 116 | } else { 117 | Err(InvalidVariableName(s)) 118 | } 119 | } 120 | 121 | pub fn default_head() -> Self { 122 | Self("self".to_string()) 123 | } 124 | 125 | /// Converts the given string into a variable name without parsing. 126 | /// 127 | /// # Safety 128 | /// 129 | /// The input string **must** be a valid variable name. 130 | pub unsafe fn new_unchecked(s: String) -> Self { 131 | Self(s) 132 | } 133 | 134 | pub fn as_variable_name(&self) -> &VariableName { 135 | unsafe { VariableName::new_unchecked(&self.0) } 136 | } 137 | 138 | pub fn into_string(self) -> String { 139 | self.0 140 | } 141 | } 142 | 143 | impl std::ops::Deref for VariableNameBuf { 144 | type Target = VariableName; 145 | 146 | fn deref(&self) -> &Self::Target { 147 | self.as_variable_name() 148 | } 149 | } 150 | 151 | impl Borrow for VariableNameBuf { 152 | fn borrow(&self) -> &VariableName { 153 | self.as_variable_name() 154 | } 155 | } 156 | 157 | impl AsRef for VariableNameBuf { 158 | fn as_ref(&self) -> &VariableName { 159 | self.as_variable_name() 160 | } 161 | } 162 | 163 | impl PartialEq for VariableNameBuf { 164 | fn eq(&self, other: &str) -> bool { 165 | self.0 == other 166 | } 167 | } 168 | 169 | impl fmt::Display for VariableNameBuf { 170 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 171 | self.0.fmt(f) 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /layouts/src/abs/syntax/resource.rs: -------------------------------------------------------------------------------- 1 | use json_syntax::{Kind, TryFromJson}; 2 | use rdf_types::LexicalLiteralTypeRef; 3 | use serde::{Deserialize, Serialize}; 4 | use xsd_types::{XSD_BOOLEAN, XSD_STRING}; 5 | 6 | use super::{ 7 | require_entry, Build, BuildError, CompactIri, Context, Error, ObjectUnusedEntries, Scope, 8 | }; 9 | 10 | /// RDF Resource description. 11 | #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] 12 | #[serde(untagged)] 13 | pub enum Resource { 14 | /// Boolean value. 15 | Boolean(bool), 16 | 17 | /// Decimal number value. 18 | Number(i64), 19 | 20 | /// Simple string literal. 21 | String(String), 22 | 23 | /// Typed string. 24 | TypedString(TypedString), 25 | } 26 | 27 | impl TryFromJson for Resource { 28 | type Error = Error; 29 | 30 | fn try_from_json_at( 31 | json: &json_syntax::Value, 32 | code_map: &json_syntax::CodeMap, 33 | offset: usize, 34 | ) -> Result { 35 | match json { 36 | json_syntax::Value::Boolean(b) => Ok(Self::Boolean(*b)), 37 | json_syntax::Value::Number(n) => { 38 | if n.contains('.') { 39 | Err(Error::ExpectedInteger(offset, n.clone())) 40 | } else { 41 | Ok(Self::Number( 42 | n.parse() 43 | .map_err(|_| Error::IntegerOverflow(offset, n.clone()))?, 44 | )) 45 | } 46 | } 47 | json_syntax::Value::String(s) => Ok(Self::String(s.to_string())), 48 | json_syntax::Value::Object(object) => { 49 | // Typed string. 50 | Ok(Self::TypedString(TypedString::try_from_json_object_at( 51 | object, code_map, offset, 52 | )?)) 53 | } 54 | other => Err(Error::Unexpected { 55 | offset, 56 | expected: Kind::Boolean | Kind::Number | Kind::String | Kind::Object, 57 | found: other.kind(), 58 | }), 59 | } 60 | } 61 | } 62 | 63 | impl Build for Resource { 64 | type Target = C::Resource; 65 | 66 | fn build(&self, context: &mut C, scope: &Scope) -> Result { 67 | match self { 68 | &Self::Boolean(b) => { 69 | let value = if b { "true" } else { "false" }; 70 | 71 | Ok(context.literal_resource(value, LexicalLiteralTypeRef::Any(XSD_BOOLEAN))) 72 | } 73 | &Self::Number(n) => { 74 | let value: xsd_types::Decimal = n.into(); 75 | let type_ = value.decimal_type(); 76 | 77 | Ok(context.literal_resource( 78 | value.lexical_representation(), 79 | LexicalLiteralTypeRef::Any(type_.iri()), 80 | )) 81 | } 82 | Self::String(value) => { 83 | Ok(context.literal_resource(value, LexicalLiteralTypeRef::Any(XSD_STRING))) 84 | } 85 | Self::TypedString(t) => t.build(context, scope), 86 | } 87 | } 88 | } 89 | 90 | /// Typed string literal. 91 | #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] 92 | pub struct TypedString { 93 | /// Literal value. 94 | pub value: String, 95 | 96 | /// Literal type. 97 | #[serde(rename = "type")] 98 | pub type_: CompactIri, 99 | } 100 | 101 | impl TypedString { 102 | fn try_from_json_object_at( 103 | object: &json_syntax::Object, 104 | code_map: &json_syntax::CodeMap, 105 | offset: usize, 106 | ) -> Result { 107 | let mut unused_entries = ObjectUnusedEntries::new(object, code_map, offset); 108 | let result = Self { 109 | value: require_entry(object, "value", &mut unused_entries, code_map, offset)?, 110 | type_: require_entry(object, "type", &mut unused_entries, code_map, offset)?, 111 | }; 112 | unused_entries.check()?; 113 | Ok(result) 114 | } 115 | } 116 | 117 | impl Build for TypedString { 118 | type Target = C::Resource; 119 | 120 | fn build(&self, context: &mut C, scope: &Scope) -> Result { 121 | let type_ = self.type_.resolve(scope)?; 122 | Ok(context.literal_resource(&self.value, LexicalLiteralTypeRef::Any(&type_))) 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /layouts/src/distill/de/data.rs: -------------------------------------------------------------------------------- 1 | use rdf_types::{ 2 | interpretation::ReverseIriInterpretation, 3 | vocabulary::{IriVocabulary, LiteralVocabulary}, 4 | LiteralType, 5 | }; 6 | 7 | use crate::{distill::RdfContextMut, value::Number}; 8 | 9 | use super::{Error, RdfLiteral}; 10 | 11 | pub fn dehydrate_boolean( 12 | rdf: &RdfContextMut, 13 | value: bool, 14 | type_: &I::Resource, 15 | ) -> Result, Error> 16 | where 17 | V: LiteralVocabulary, 18 | V::Iri: Clone, 19 | I: ReverseIriInterpretation, 20 | { 21 | for i in rdf.interpretation.iris_of(type_) { 22 | let iri = rdf.vocabulary.iri(i).unwrap(); 23 | if iri == xsd_types::XSD_BOOLEAN { 24 | return Ok(rdf_types::Literal::new( 25 | xsd_types::lexical::BooleanBuf::from(value).into_string(), 26 | LiteralType::Any(i.clone()), 27 | )); 28 | } 29 | } 30 | 31 | todo!() 32 | } 33 | 34 | pub fn dehydrate_number( 35 | rdf: &RdfContextMut, 36 | value: &Number, 37 | type_: &I::Resource, 38 | ) -> Result, Error> 39 | where 40 | V: LiteralVocabulary, 41 | V::Iri: Clone, 42 | I: ReverseIriInterpretation, 43 | { 44 | for i in rdf.interpretation.iris_of(type_) { 45 | let iri = rdf.vocabulary.iri(i).unwrap(); 46 | if let Some(xsd_types::Datatype::Decimal(_)) = xsd_types::Datatype::from_iri(iri) { 47 | if let Ok(decimal) = xsd_types::Decimal::try_from(value.as_big_rational().clone()) { 48 | // TODO better support for XSD decimal datatype. 49 | return Ok(rdf_types::Literal::new( 50 | decimal.to_string(), 51 | LiteralType::Any(i.clone()), 52 | )); 53 | } 54 | } 55 | } 56 | 57 | todo!() 58 | } 59 | 60 | pub fn dehydrate_byte_string( 61 | _rdf: &RdfContextMut, 62 | _value: &[u8], 63 | _type_: &I::Resource, 64 | ) -> Result, Error> 65 | where 66 | V: LiteralVocabulary, 67 | V::Iri: Clone, 68 | I: ReverseIriInterpretation, 69 | { 70 | todo!() 71 | } 72 | 73 | pub fn dehydrate_text_string( 74 | rdf: &RdfContextMut, 75 | value: &str, 76 | type_: &I::Resource, 77 | ) -> Result, Error> 78 | where 79 | V: LiteralVocabulary, 80 | V::Iri: Clone, 81 | I: ReverseIriInterpretation, 82 | { 83 | match rdf.interpretation.iris_of(type_).next() { 84 | Some(i) => Ok(rdf_types::Literal::new( 85 | value.to_owned(), 86 | LiteralType::Any(i.clone()), 87 | )), 88 | None => todo!(), 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /layouts/src/distill/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod de; 2 | pub mod hy; 3 | 4 | pub use de::{dehydrate, dehydrate_with}; 5 | use educe::Educe; 6 | pub use hy::{hydrate, hydrate_with}; 7 | 8 | /// RDF context, providing the RDF vocabulary and interpretation. 9 | #[derive(Educe)] 10 | #[educe(Clone, Copy)] 11 | pub struct RdfContext<'a, V, I> { 12 | /// Vocabulary storing the lexical representations of terms. 13 | pub vocabulary: &'a V, 14 | 15 | /// RDF interpretation, mapping resources to terms. 16 | pub interpretation: &'a I, 17 | } 18 | 19 | impl Default for RdfContext<'static, (), ()> { 20 | fn default() -> Self { 21 | RdfContext { 22 | vocabulary: &(), 23 | interpretation: &(), 24 | } 25 | } 26 | } 27 | 28 | impl<'a, V, I> RdfContext<'a, V, I> { 29 | /// Creates a new RDF context. 30 | pub fn new(vocabulary: &'a V, interpretation: &'a I) -> Self { 31 | Self { 32 | vocabulary, 33 | interpretation, 34 | } 35 | } 36 | } 37 | 38 | /// Mutable RDF context, providing the mutable RDF vocabulary and 39 | /// interpretation. 40 | pub struct RdfContextMut<'a, V, I> { 41 | /// Vocabulary storing the lexical representations of terms. 42 | pub vocabulary: &'a mut V, 43 | 44 | /// RDF interpretation, mapping resources to terms. 45 | pub interpretation: &'a mut I, 46 | } 47 | 48 | impl<'a, V, I> RdfContextMut<'a, V, I> { 49 | /// Creates a new mutable RDF context. 50 | pub fn new(vocabulary: &'a mut V, interpretation: &'a mut I) -> Self { 51 | Self { 52 | vocabulary, 53 | interpretation, 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /layouts/src/format.rs: -------------------------------------------------------------------------------- 1 | use crate::{layout::LayoutType, Pattern, Ref}; 2 | 3 | #[derive( 4 | Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize, 5 | )] 6 | pub struct ValueFormat { 7 | /// Layout. 8 | pub layout: Ref, 9 | 10 | /// Layout inputs. 11 | pub input: Vec>, 12 | 13 | /// Graph in which the layout is evaluated. 14 | pub graph: Option>>, 15 | } 16 | 17 | impl ValueFormat { 18 | pub fn visit_dependencies<'a>(&'a self, mut f: impl FnMut(&'a Ref)) { 19 | (f)(&self.layout) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /layouts/src/graph.rs: -------------------------------------------------------------------------------- 1 | use crate::Pattern; 2 | 3 | pub type Graph = rdf_types::dataset::BTreeGraph>; 4 | 5 | pub type Dataset = rdf_types::dataset::BTreeDataset>; 6 | -------------------------------------------------------------------------------- /layouts/src/layout/always.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spruceid/treeldr/82f86519141c1188fd69a2fd3730388973218207/layouts/src/layout/always.rs -------------------------------------------------------------------------------- /layouts/src/layout/intersection.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /layouts/src/layout/list/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod ordered; 2 | pub mod sized; 3 | pub mod unordered; 4 | 5 | use educe::Educe; 6 | pub use ordered::OrderedListLayout; 7 | pub use sized::SizedListLayout; 8 | use std::{collections::BTreeMap, hash::Hash}; 9 | pub use unordered::UnorderedListLayout; 10 | 11 | use crate::{graph::Dataset, Ref, ValueFormat}; 12 | 13 | use super::LayoutType; 14 | 15 | pub struct ListLayoutType; 16 | 17 | #[derive(Debug, Clone, Educe, serde::Serialize, serde::Deserialize)] 18 | #[educe( 19 | PartialEq(bound = "R: Ord"), 20 | Eq(bound = "R: Ord"), 21 | Ord(bound = "R: Ord"), 22 | Hash(bound = "R: Ord + Hash") 23 | )] 24 | #[serde(bound(deserialize = "R: Clone + Ord + serde::Deserialize<'de>"))] 25 | pub enum ListLayout { 26 | Unordered(UnorderedListLayout), 27 | Ordered(OrderedListLayout), 28 | Sized(SizedListLayout), 29 | } 30 | 31 | impl ListLayout { 32 | pub fn visit_dependencies<'a>(&'a self, f: impl FnMut(&'a Ref)) { 33 | match self { 34 | Self::Unordered(l) => l.visit_dependencies(f), 35 | Self::Ordered(l) => l.visit_dependencies(f), 36 | Self::Sized(l) => l.visit_dependencies(f), 37 | } 38 | } 39 | 40 | pub fn input_count(&self) -> u32 { 41 | match self { 42 | Self::Unordered(l) => l.input, 43 | Self::Ordered(l) => l.input, 44 | Self::Sized(l) => l.input, 45 | } 46 | } 47 | 48 | pub fn extra_properties(&self) -> &BTreeMap { 49 | match self { 50 | Self::Unordered(l) => &l.extra_properties, 51 | Self::Ordered(l) => &l.extra_properties, 52 | Self::Sized(l) => &l.extra_properties, 53 | } 54 | } 55 | } 56 | 57 | impl PartialOrd for ListLayout { 58 | fn partial_cmp(&self, other: &Self) -> Option { 59 | Some(self.cmp(other)) 60 | } 61 | } 62 | 63 | #[derive(Debug, Clone, Educe, serde::Serialize, serde::Deserialize)] 64 | #[educe( 65 | PartialEq(bound = "R: Ord"), 66 | Eq(bound = "R: Ord"), 67 | Ord(bound = "R: Ord"), 68 | Hash(bound = "R: Ord + Hash") 69 | )] 70 | #[serde(bound(deserialize = "R: Clone + Ord + serde::Deserialize<'de>"))] 71 | pub struct ItemLayout { 72 | /// Intros. 73 | pub intro: u32, 74 | 75 | /// Format. 76 | pub value: ValueFormat, 77 | 78 | /// Dataset. 79 | pub dataset: Dataset, 80 | } 81 | 82 | impl ItemLayout { 83 | pub fn visit_dependencies<'a>(&'a self, f: impl FnMut(&'a Ref)) { 84 | self.value.visit_dependencies(f) 85 | } 86 | } 87 | 88 | impl PartialOrd for ItemLayout { 89 | fn partial_cmp(&self, other: &Self) -> Option { 90 | Some(self.cmp(other)) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /layouts/src/layout/list/ordered.rs: -------------------------------------------------------------------------------- 1 | use educe::Educe; 2 | use std::{collections::BTreeMap, hash::Hash}; 3 | 4 | use crate::{graph::Dataset, layout::LayoutType, Pattern, Ref, ValueFormat}; 5 | 6 | #[derive(Debug, Clone, Educe, serde::Serialize, serde::Deserialize)] 7 | #[educe( 8 | PartialEq(bound = "R: Ord"), 9 | Eq(bound = "R: Ord"), 10 | Ord(bound = "R: Ord"), 11 | Hash(bound = "R: Ord + Hash") 12 | )] 13 | #[serde(bound(deserialize = "R: Clone + Ord + serde::Deserialize<'de>"))] 14 | pub struct OrderedListLayout { 15 | pub input: u32, 16 | 17 | pub intro: u32, 18 | 19 | /// List node layout description. 20 | pub node: NodeLayout, 21 | 22 | /// Head pattern. 23 | pub head: Pattern, 24 | 25 | /// Tail pattern. 26 | pub tail: Pattern, 27 | 28 | pub dataset: Dataset, 29 | 30 | /// Additional properties. 31 | pub extra_properties: BTreeMap, 32 | } 33 | 34 | impl OrderedListLayout { 35 | pub fn visit_dependencies<'a>(&'a self, f: impl FnMut(&'a Ref)) { 36 | self.node.value.visit_dependencies(f) 37 | } 38 | } 39 | 40 | impl PartialOrd for OrderedListLayout { 41 | fn partial_cmp(&self, other: &Self) -> Option { 42 | Some(self.cmp(other)) 43 | } 44 | } 45 | 46 | #[derive(Debug, Clone, Educe, serde::Serialize, serde::Deserialize)] 47 | #[educe( 48 | PartialEq(bound = "R: Ord"), 49 | Eq(bound = "R: Ord"), 50 | Ord(bound = "R: Ord"), 51 | Hash(bound = "R: Ord + Hash") 52 | )] 53 | #[serde(bound(deserialize = "R: Clone + Ord + serde::Deserialize<'de>"))] 54 | pub struct NodeLayout { 55 | pub intro: u32, 56 | 57 | /// Node format. 58 | /// 59 | /// The layout must take one input which corresponds to the list node, 60 | /// and intro at least one variable corresponding to the rest of the list. 61 | pub value: ValueFormat, 62 | 63 | pub dataset: Dataset, 64 | } 65 | 66 | impl PartialOrd for NodeLayout { 67 | fn partial_cmp(&self, other: &Self) -> Option { 68 | Some(self.cmp(other)) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /layouts/src/layout/list/sized.rs: -------------------------------------------------------------------------------- 1 | use educe::Educe; 2 | use std::{collections::BTreeMap, hash::Hash}; 3 | 4 | use crate::{layout::LayoutType, Dataset, Ref}; 5 | 6 | use super::ItemLayout; 7 | 8 | #[derive(Debug, Clone, Educe, serde::Serialize, serde::Deserialize)] 9 | #[educe( 10 | PartialEq(bound = "R: Ord"), 11 | Eq(bound = "R: Ord"), 12 | Ord(bound = "R: Ord"), 13 | Hash(bound = "R: Ord + Hash") 14 | )] 15 | #[serde(bound(deserialize = "R: Clone + Ord + serde::Deserialize<'de>"))] 16 | pub struct SizedListLayout { 17 | pub input: u32, 18 | 19 | pub intro: u32, 20 | 21 | pub items: Vec>, 22 | 23 | pub dataset: Dataset, 24 | 25 | /// Additional properties. 26 | pub extra_properties: BTreeMap, 27 | } 28 | 29 | impl SizedListLayout { 30 | pub fn visit_dependencies<'a>(&'a self, mut f: impl FnMut(&'a Ref)) { 31 | for item in &self.items { 32 | item.visit_dependencies(&mut f) 33 | } 34 | } 35 | } 36 | 37 | impl PartialOrd for SizedListLayout { 38 | fn partial_cmp(&self, other: &Self) -> Option { 39 | Some(self.cmp(other)) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /layouts/src/layout/list/unordered.rs: -------------------------------------------------------------------------------- 1 | use educe::Educe; 2 | use std::{collections::BTreeMap, hash::Hash}; 3 | 4 | use crate::{layout::LayoutType, Dataset, Ref}; 5 | 6 | use super::ItemLayout; 7 | 8 | #[derive(Debug, Clone, Educe, serde::Serialize, serde::Deserialize)] 9 | #[educe( 10 | PartialEq(bound = "R: Ord"), 11 | Eq(bound = "R: Ord"), 12 | Ord(bound = "R: Ord"), 13 | Hash(bound = "R: Ord + Hash") 14 | )] 15 | #[serde(bound(deserialize = "R: Clone + Ord + serde::Deserialize<'de>"))] 16 | pub struct UnorderedListLayout { 17 | pub input: u32, 18 | 19 | pub intro: u32, 20 | 21 | pub item: ItemLayout, 22 | 23 | pub dataset: Dataset, 24 | 25 | /// Additional properties. 26 | pub extra_properties: BTreeMap, 27 | } 28 | 29 | impl UnorderedListLayout { 30 | pub fn visit_dependencies<'a>(&'a self, f: impl FnMut(&'a Ref)) { 31 | self.item.visit_dependencies(f) 32 | } 33 | } 34 | 35 | impl PartialOrd for UnorderedListLayout { 36 | fn partial_cmp(&self, other: &Self) -> Option { 37 | Some(self.cmp(other)) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /layouts/src/layout/literal/data.rs: -------------------------------------------------------------------------------- 1 | use educe::Educe; 2 | use std::{collections::BTreeMap, hash::Hash}; 3 | 4 | use crate::{utils::DetAutomaton, Dataset, Pattern, Value}; 5 | 6 | pub struct DataLayoutType; 7 | 8 | pub struct UnitLayoutType; 9 | 10 | pub struct BooleanLayoutType; 11 | 12 | pub struct NumberLayoutType; 13 | 14 | pub struct ByteStringLayoutType; 15 | 16 | pub struct TextStringLayoutType; 17 | 18 | /// Data layout. 19 | #[derive(Debug, Clone, Educe, serde::Serialize, serde::Deserialize)] 20 | #[educe( 21 | PartialEq(bound = "R: Ord"), 22 | Eq(bound = "R: Ord"), 23 | Ord(bound = "R: Ord"), 24 | Hash(bound = "R: Ord + Hash") 25 | )] 26 | #[serde(bound(deserialize = "R: Clone + Ord + serde::Deserialize<'de>"))] 27 | pub enum DataLayout { 28 | Unit(UnitLayout), 29 | Boolean(BooleanLayout), 30 | Number(NumberLayout), 31 | ByteString(ByteStringLayout), 32 | TextString(TextStringLayout), 33 | } 34 | 35 | impl DataLayout { 36 | pub fn extra_properties(&self) -> &BTreeMap { 37 | match self { 38 | Self::Unit(l) => &l.extra_properties, 39 | Self::Boolean(l) => &l.extra_properties, 40 | Self::Number(l) => &l.extra_properties, 41 | Self::ByteString(l) => &l.extra_properties, 42 | Self::TextString(l) => &l.extra_properties, 43 | } 44 | } 45 | } 46 | 47 | impl PartialOrd for DataLayout { 48 | fn partial_cmp(&self, other: &Self) -> Option { 49 | Some(self.cmp(other)) 50 | } 51 | } 52 | 53 | #[derive(Debug, Clone, Educe, serde::Serialize, serde::Deserialize)] 54 | #[educe( 55 | PartialEq(bound = "R: Ord"), 56 | Eq(bound = "R: Ord"), 57 | Ord(bound = "R: Ord"), 58 | Hash(bound = "R: Ord + Hash") 59 | )] 60 | #[serde(bound(deserialize = "R: Clone + Ord + serde::Deserialize<'de>"))] 61 | pub struct UnitLayout { 62 | pub input: u32, 63 | 64 | pub intro: u32, 65 | 66 | pub dataset: Dataset, 67 | 68 | #[serde(rename = "const", default, skip_serializing_if = "Value::is_unit")] 69 | pub const_: Value, 70 | 71 | /// Additional properties. 72 | pub extra_properties: BTreeMap, 73 | } 74 | 75 | impl PartialOrd for UnitLayout { 76 | fn partial_cmp(&self, other: &Self) -> Option { 77 | Some(self.cmp(other)) 78 | } 79 | } 80 | 81 | #[derive(Debug, Clone, Educe, serde::Serialize, serde::Deserialize)] 82 | #[educe( 83 | PartialEq(bound = "R: Ord"), 84 | Eq(bound = "R: Ord"), 85 | Ord(bound = "R: Ord"), 86 | Hash(bound = "R: Ord + Hash") 87 | )] 88 | #[serde(bound(deserialize = "R: Clone + Ord + serde::Deserialize<'de>"))] 89 | pub struct BooleanLayout { 90 | pub input: u32, 91 | 92 | pub intro: u32, 93 | 94 | pub dataset: Dataset, 95 | 96 | pub resource: Pattern, 97 | 98 | pub datatype: R, 99 | 100 | /// Additional properties. 101 | pub extra_properties: BTreeMap, 102 | } 103 | 104 | impl PartialOrd for BooleanLayout { 105 | fn partial_cmp(&self, other: &Self) -> Option { 106 | Some(self.cmp(other)) 107 | } 108 | } 109 | 110 | #[derive(Debug, Clone, Educe, serde::Serialize, serde::Deserialize)] 111 | #[educe( 112 | PartialEq(bound = "R: Ord"), 113 | Eq(bound = "R: Ord"), 114 | Ord(bound = "R: Ord"), 115 | Hash(bound = "R: Ord + Hash") 116 | )] 117 | #[serde(bound(deserialize = "R: Clone + Ord + serde::Deserialize<'de>"))] 118 | pub struct NumberLayout { 119 | pub input: u32, 120 | 121 | pub intro: u32, 122 | 123 | pub dataset: Dataset, 124 | 125 | pub resource: Pattern, 126 | 127 | pub datatype: R, 128 | 129 | /// Additional properties. 130 | pub extra_properties: BTreeMap, 131 | } 132 | 133 | impl PartialOrd for NumberLayout { 134 | fn partial_cmp(&self, other: &Self) -> Option { 135 | Some(self.cmp(other)) 136 | } 137 | } 138 | 139 | #[derive(Debug, Clone, Educe, serde::Serialize, serde::Deserialize)] 140 | #[educe( 141 | PartialEq(bound = "R: Ord"), 142 | Eq(bound = "R: Ord"), 143 | Ord(bound = "R: Ord"), 144 | Hash(bound = "R: Ord + Hash") 145 | )] 146 | #[serde(bound(deserialize = "R: Clone + Ord + serde::Deserialize<'de>"))] 147 | pub struct ByteStringLayout { 148 | pub input: u32, 149 | 150 | pub intro: u32, 151 | 152 | pub dataset: Dataset, 153 | 154 | pub resource: Pattern, 155 | 156 | pub datatype: R, 157 | 158 | /// Additional properties. 159 | pub extra_properties: BTreeMap, 160 | } 161 | 162 | impl PartialOrd for ByteStringLayout { 163 | fn partial_cmp(&self, other: &Self) -> Option { 164 | Some(self.cmp(other)) 165 | } 166 | } 167 | 168 | #[derive(Debug, Clone, Educe, serde::Serialize, serde::Deserialize)] 169 | #[educe( 170 | PartialEq(bound = "R: Ord"), 171 | Eq(bound = "R: Ord"), 172 | Ord(bound = "R: Ord"), 173 | Hash(bound = "R: Ord + Hash") 174 | )] 175 | #[serde(bound(deserialize = "R: Clone + Ord + serde::Deserialize<'de>"))] 176 | pub struct TextStringLayout { 177 | pub input: u32, 178 | 179 | pub intro: u32, 180 | 181 | pub pattern: Option>, 182 | 183 | pub dataset: Dataset, 184 | 185 | pub resource: Pattern, 186 | 187 | pub datatype: R, 188 | 189 | /// Additional properties. 190 | pub extra_properties: BTreeMap, 191 | } 192 | 193 | impl PartialOrd for TextStringLayout { 194 | fn partial_cmp(&self, other: &Self) -> Option { 195 | Some(self.cmp(other)) 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /layouts/src/layout/literal/id.rs: -------------------------------------------------------------------------------- 1 | use educe::Educe; 2 | use std::{collections::BTreeMap, hash::Hash}; 3 | 4 | use crate::{utils::DetAutomaton, Dataset, Pattern}; 5 | 6 | pub struct IdLayoutType; 7 | 8 | #[derive(Debug, Clone, Educe, serde::Serialize, serde::Deserialize)] 9 | #[educe( 10 | PartialEq(bound = "R: Ord"), 11 | Eq(bound = "R: Ord"), 12 | Ord(bound = "R: Ord"), 13 | Hash(bound = "R: Ord + Hash") 14 | )] 15 | #[serde(bound(deserialize = "R: Clone + Ord + serde::Deserialize<'de>"))] 16 | pub struct IdLayout { 17 | pub input: u32, 18 | 19 | pub intro: u32, 20 | 21 | pub dataset: Dataset, 22 | 23 | pub pattern: Option>, 24 | 25 | pub resource: Pattern, 26 | 27 | /// Additional properties. 28 | pub extra_properties: BTreeMap, 29 | } 30 | 31 | impl PartialOrd for IdLayout { 32 | fn partial_cmp(&self, other: &Self) -> Option { 33 | Some(self.cmp(other)) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /layouts/src/layout/literal/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod data; 2 | pub mod id; 3 | 4 | pub use data::{ 5 | BooleanLayout, BooleanLayoutType, ByteStringLayout, ByteStringLayoutType, DataLayout, 6 | DataLayoutType, NumberLayout, NumberLayoutType, TextStringLayout, TextStringLayoutType, 7 | UnitLayout, UnitLayoutType, 8 | }; 9 | use educe::Educe; 10 | pub use id::{IdLayout, IdLayoutType}; 11 | use std::{collections::BTreeMap, hash::Hash}; 12 | 13 | pub struct LiteralLayoutType; 14 | 15 | #[derive(Debug, Clone, Educe, serde::Serialize, serde::Deserialize)] 16 | #[educe( 17 | PartialEq(bound = "R: Ord"), 18 | Eq(bound = "R: Ord"), 19 | Ord(bound = "R: Ord"), 20 | Hash(bound = "R: Ord + Hash") 21 | )] 22 | #[serde(bound(deserialize = "R: Clone + Ord + serde::Deserialize<'de>"))] 23 | pub enum LiteralLayout { 24 | Data(DataLayout), 25 | Id(IdLayout), 26 | } 27 | 28 | impl LiteralLayout { 29 | pub fn extra_properties(&self) -> &BTreeMap { 30 | match self { 31 | Self::Data(d) => d.extra_properties(), 32 | Self::Id(d) => &d.extra_properties, 33 | } 34 | } 35 | } 36 | 37 | impl PartialOrd for LiteralLayout { 38 | fn partial_cmp(&self, other: &Self) -> Option { 39 | Some(self.cmp(other)) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /layouts/src/layout/never.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spruceid/treeldr/82f86519141c1188fd69a2fd3730388973218207/layouts/src/layout/never.rs -------------------------------------------------------------------------------- /layouts/src/layout/product.rs: -------------------------------------------------------------------------------- 1 | use educe::Educe; 2 | use std::collections::BTreeMap; 3 | use std::hash::Hash; 4 | 5 | use crate::{Dataset, Ref, Value, ValueFormat}; 6 | 7 | use super::LayoutType; 8 | 9 | pub struct ProductLayoutType; 10 | 11 | #[derive(Debug, Clone, Educe, serde::Serialize, serde::Deserialize)] 12 | #[educe( 13 | PartialEq(bound = "R: Ord"), 14 | Eq(bound = "R: Ord"), 15 | Ord(bound = "R: Ord"), 16 | Hash(bound = "R: Ord + Hash") 17 | )] 18 | #[serde(bound(deserialize = "R: Clone + Ord + serde::Deserialize<'de>"))] 19 | pub struct ProductLayout { 20 | /// Number of inputs. 21 | pub input: u32, 22 | 23 | /// Number of introduced variables. 24 | pub intro: u32, 25 | 26 | /// Fields. 27 | pub fields: BTreeMap>, 28 | 29 | /// Dataset. 30 | pub dataset: Dataset, 31 | 32 | /// Additional properties. 33 | pub extra_properties: BTreeMap, 34 | } 35 | 36 | impl ProductLayout { 37 | pub fn visit_dependencies<'a>(&'a self, mut f: impl FnMut(&'a Ref)) { 38 | for field in self.fields.values() { 39 | field.value.visit_dependencies(&mut f) 40 | } 41 | } 42 | } 43 | 44 | impl PartialOrd for ProductLayout { 45 | fn partial_cmp(&self, other: &Self) -> Option { 46 | Some(self.cmp(other)) 47 | } 48 | } 49 | 50 | #[derive(Debug, Clone, Educe, serde::Serialize, serde::Deserialize)] 51 | #[educe( 52 | PartialEq(bound = "R: Ord"), 53 | Eq(bound = "R: Ord"), 54 | Ord(bound = "R: Ord"), 55 | Hash(bound = "R: Ord + Hash") 56 | )] 57 | #[serde(bound(deserialize = "R: Clone + Ord + serde::Deserialize<'de>"))] 58 | pub struct Field { 59 | /// Intros. 60 | pub intro: u32, 61 | 62 | /// Format. 63 | pub value: ValueFormat, 64 | 65 | /// Dataset. 66 | pub dataset: Dataset, 67 | 68 | /// Whether or not the field is required. 69 | pub required: bool, 70 | } 71 | 72 | impl PartialOrd for Field { 73 | fn partial_cmp(&self, other: &Self) -> Option { 74 | Some(self.cmp(other)) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /layouts/src/layout/sum.rs: -------------------------------------------------------------------------------- 1 | use educe::Educe; 2 | use std::{collections::BTreeMap, hash::Hash}; 3 | 4 | use crate::{Dataset, Ref, ValueFormat}; 5 | 6 | use super::LayoutType; 7 | 8 | pub struct SumLayoutType; 9 | 10 | #[derive(Debug, Clone, Educe, serde::Serialize, serde::Deserialize)] 11 | #[educe( 12 | PartialEq(bound = "R: Ord"), 13 | Eq(bound = "R: Ord"), 14 | Ord(bound = "R: Ord"), 15 | Hash(bound = "R: Ord + Hash") 16 | )] 17 | #[serde(bound(deserialize = "R: Clone + Ord + serde::Deserialize<'de>"))] 18 | pub struct SumLayout { 19 | pub input: u32, 20 | 21 | /// Number of introduced variables. 22 | pub intro: u32, 23 | 24 | /// Variants. 25 | pub variants: Vec>, 26 | 27 | /// Graph. 28 | pub dataset: Dataset, 29 | 30 | /// Additional properties. 31 | pub extra_properties: BTreeMap, 32 | } 33 | 34 | impl SumLayout { 35 | pub fn visit_dependencies<'a>(&'a self, mut f: impl FnMut(&'a Ref)) { 36 | for variant in &self.variants { 37 | variant.value.visit_dependencies(&mut f) 38 | } 39 | } 40 | } 41 | 42 | impl PartialOrd for SumLayout { 43 | fn partial_cmp(&self, other: &Self) -> Option { 44 | Some(self.cmp(other)) 45 | } 46 | } 47 | 48 | #[derive(Debug, Clone, Educe, serde::Serialize, serde::Deserialize)] 49 | #[educe( 50 | PartialEq(bound = "R: Ord"), 51 | Eq(bound = "R: Ord"), 52 | Ord(bound = "R: Ord"), 53 | Hash(bound = "R: Ord + Hash") 54 | )] 55 | #[serde(bound(deserialize = "R: Clone + Ord + serde::Deserialize<'de>"))] 56 | pub struct Variant { 57 | /// Name. 58 | pub name: String, 59 | 60 | /// Intros. 61 | pub intro: u32, 62 | 63 | /// Format. 64 | pub value: ValueFormat, 65 | 66 | /// Graph. 67 | pub dataset: Dataset, 68 | } 69 | 70 | impl PartialOrd for Variant { 71 | fn partial_cmp(&self, other: &Self) -> Option { 72 | Some(self.cmp(other)) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /layouts/src/layout/union.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /layouts/src/matching.rs: -------------------------------------------------------------------------------- 1 | use rdf_types::{dataset::PatternMatchingDataset, pattern::CanonicalQuadPattern, Quad}; 2 | 3 | use crate::pattern::{PatternRefQuad, Substitution}; 4 | 5 | /// Pattern matching error. 6 | pub enum Error { 7 | /// The input pattern matches more than one fragment of the dataset, where 8 | /// the caller requires at most one match. 9 | Ambiguity, 10 | 11 | /// The input pattern does not match any fragment of the dataset, where the 12 | /// caller requires at least one match. 13 | Empty, 14 | } 15 | 16 | /// Pattern matching. 17 | /// 18 | /// Iterates over all the substitutions mapping the input pattern to a fragment 19 | /// of the given RDF dataset. 20 | pub struct Matching<'a, 'p, R, D, Q> 21 | where 22 | D: PatternMatchingDataset, 23 | { 24 | /// Dataset on which the pattern matching is performed. 25 | dataset: &'a D, 26 | 27 | /// Working stack. 28 | stack: Vec>, 29 | } 30 | 31 | pub struct State<'a, 'p, R, D, Q> 32 | where 33 | D: 'a + PatternMatchingDataset, 34 | { 35 | substitution: Substitution, 36 | quad_state: Option>, 37 | rest: Q, 38 | } 39 | 40 | pub struct QuadState<'a, 'p, R, D> 41 | where 42 | D: 'a + PatternMatchingDataset, 43 | { 44 | pattern: PatternRefQuad<'p, R>, 45 | quad_matching: D::QuadPatternMatching<'a, 'p>, 46 | } 47 | 48 | impl<'a, R, D, Q> Matching<'a, '_, R, D, Q> 49 | where 50 | D: PatternMatchingDataset, 51 | { 52 | /// Starts a pattern matching on the given `dataset` using the input partial 53 | /// `substitution` and `patterns` (an iterator of [`PatternRefQuad`]). 54 | /// 55 | /// This will return an iterator over all the complete substitutions such 56 | /// that the substituted patterns form an existing fragment of the dataset. 57 | /// 58 | /// All the variables appearing in `patterns` *must* be declared in the 59 | /// initial partial `substitution`. If a variable is already bound in the 60 | /// initial substitution, only the substitutions preserving the same bound 61 | /// will be iterated over. 62 | pub fn new(dataset: &'a D, substitution: Substitution, patterns: Q) -> Self { 63 | Self { 64 | dataset, 65 | stack: vec![State { 66 | substitution, 67 | quad_state: None, 68 | rest: patterns, 69 | }], 70 | } 71 | } 72 | } 73 | 74 | impl<'a, 'p, R, D, Q> Matching<'a, 'p, R, D, Q> 75 | where 76 | R: Clone + PartialEq + 'a, 77 | D: PatternMatchingDataset, 78 | Q: Clone + Iterator>, 79 | { 80 | pub fn into_unique(mut self) -> Result>, Error> { 81 | match self.next() { 82 | Some(substitution) => { 83 | if self.next().is_some() { 84 | Err(Error::Ambiguity) 85 | } else { 86 | Ok(Some(substitution)) 87 | } 88 | } 89 | None => Ok(None), 90 | } 91 | } 92 | 93 | pub fn into_required_unique(self) -> Result, Error> { 94 | self.into_unique()?.ok_or(Error::Empty) 95 | } 96 | } 97 | 98 | impl<'a, 'p, R, D, Q> Iterator for Matching<'a, 'p, R, D, Q> 99 | where 100 | R: Clone + PartialEq + 'a, 101 | D: PatternMatchingDataset, 102 | Q: Clone + Iterator>, 103 | { 104 | type Item = Substitution; 105 | 106 | fn next(&mut self) -> Option { 107 | loop { 108 | match self.stack.last_mut() { 109 | Some(state) => match &mut state.quad_state { 110 | Some(quad_state) => match quad_state.quad_matching.next() { 111 | Some(m) => { 112 | if let Some(substitution) = 113 | state.substitution.with_quad(quad_state.pattern, m) 114 | { 115 | let rest = state.rest.clone(); 116 | 117 | self.stack.push(State { 118 | substitution, 119 | quad_state: None, 120 | rest, 121 | }) 122 | } 123 | } 124 | None => { 125 | self.stack.pop(); 126 | } 127 | }, 128 | None => match state.rest.next() { 129 | Some(pattern) => { 130 | state.quad_state = Some(QuadState { 131 | pattern, 132 | quad_matching: self 133 | .dataset 134 | .quad_pattern_matching(quad_matching_pattern(pattern)), 135 | }) 136 | } 137 | None => { 138 | let state = self.stack.pop().unwrap(); 139 | break Some(state.substitution); 140 | } 141 | }, 142 | }, 143 | None => break None, 144 | } 145 | } 146 | } 147 | } 148 | 149 | fn quad_matching_pattern(pattern: PatternRefQuad) -> CanonicalQuadPattern<&R> { 150 | CanonicalQuadPattern::from_pattern(Quad( 151 | pattern.0.into(), 152 | pattern.1.into(), 153 | pattern.2.into(), 154 | pattern.3.map(Into::into), 155 | )) 156 | } 157 | -------------------------------------------------------------------------------- /layouts/src/pattern.rs: -------------------------------------------------------------------------------- 1 | use rdf_types::{pattern::ResourceOrVar, Quad}; 2 | 3 | /// A quad of patterns referencing their resources. 4 | pub type PatternRefQuad<'p, R> = Quad>; 5 | 6 | /// Pattern. 7 | /// 8 | /// Either a resource identifier or a variable. 9 | #[derive( 10 | Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize, 11 | )] 12 | pub enum Pattern { 13 | /// Resource. 14 | Resource(R), 15 | 16 | /// Variable. 17 | Var(u32), 18 | } 19 | 20 | impl Pattern { 21 | pub fn apply(&self, substitution: &Substitution) -> Self 22 | where 23 | R: Clone, 24 | { 25 | match self { 26 | Self::Resource(r) => Self::Resource(r.clone()), 27 | Self::Var(x) => match substitution.get(*x) { 28 | Some(r) => Self::Resource(r.clone()), 29 | None => Self::Var(*x), 30 | }, 31 | } 32 | } 33 | 34 | pub fn as_ref(&self) -> Pattern<&R> { 35 | match self { 36 | Self::Resource(r) => Pattern::Resource(r), 37 | Self::Var(x) => Pattern::Var(*x), 38 | } 39 | } 40 | 41 | pub fn into_resource(self) -> Option { 42 | match self { 43 | Self::Resource(r) => Some(r), 44 | _ => None, 45 | } 46 | } 47 | } 48 | 49 | impl From> for ResourceOrVar { 50 | fn from(value: Pattern) -> Self { 51 | match value { 52 | Pattern::Resource(r) => Self::Resource(r), 53 | Pattern::Var(x) => Self::Var(x), 54 | } 55 | } 56 | } 57 | 58 | /// Pattern substitution. 59 | /// 60 | /// Maps some or all variables from `0` to [`Self::len()`] (called the 61 | /// *declared* variables) to resources (`R`). 62 | /// If all declared variables are bound to a resource, the substitution is 63 | /// *complete*, or otherwise *partial*. 64 | #[derive(Clone)] 65 | pub struct Substitution(Vec>); 66 | 67 | impl Substitution { 68 | /// Create a new empty substitution without declared variables. 69 | pub fn new() -> Self { 70 | Self(Vec::new()) 71 | } 72 | 73 | /// Creates a new substitution from the given input resources. 74 | /// 75 | /// The resulting substitution has `inputs.len()` declared variables bound 76 | /// to there respective resource in `inputs`. 77 | pub fn from_inputs(inputs: &[R]) -> Self 78 | where 79 | R: Clone, 80 | { 81 | Self(inputs.iter().cloned().map(Some).collect()) 82 | } 83 | 84 | /// Returns the number of variables declared in the substitution. 85 | pub fn len(&self) -> u32 { 86 | self.0.len() as u32 87 | } 88 | 89 | /// Checks if the substitution is empty (no declared variables). 90 | pub fn is_empty(&self) -> bool { 91 | self.0.is_empty() 92 | } 93 | 94 | /// Returns the resource bound to the variable `i`, if any. 95 | pub fn get(&self, i: u32) -> Option<&R> { 96 | self.0.get(i as usize).and_then(Option::as_ref) 97 | } 98 | 99 | /// Introduce `count` variables to the substitution. Returns the index of 100 | /// the first introduced variable. 101 | pub fn intro(&mut self, count: u32) -> u32 { 102 | let i = self.len(); 103 | self.0.resize_with(self.0.len() + count as usize, || None); 104 | i 105 | } 106 | 107 | /// Introduce one more variable to the substitution and bind it to the given 108 | /// resource. 109 | /// 110 | /// Returns the index of the newly declared variable. 111 | pub fn push(&mut self, value: Option) -> u32 { 112 | let i = self.len(); 113 | self.0.push(value); 114 | i 115 | } 116 | 117 | /// Sets the binding of the variable `x` to `value`. 118 | /// 119 | /// The variable `x` *must* be declared in the substitution. 120 | /// 121 | /// Returns the previous binding of the variable. 122 | /// 123 | /// ## Panics 124 | /// 125 | /// Panics if the variable `x` if not declared in the substitution. 126 | pub fn set(&mut self, x: u32, value: Option) -> Option { 127 | std::mem::replace(&mut self.0[x as usize], value) 128 | } 129 | 130 | /// Copies and sets the variables of the substitution by matching the 131 | /// `value` quad against the given `pattern` quad. 132 | /// 133 | /// For each `pattern` quad component: 134 | /// - if the pattern is a variable, sets the variable to the equivalent 135 | /// resource in `value` using [`Self::set`]. If the variable was already 136 | /// bound to another resource, a mismatch is found. 137 | /// - if the pattern is a resource, checks that it is equal to the 138 | /// corresponding resource in `value`, otherwise a mismatch is found. 139 | /// 140 | /// Return the updated substitution if no mismatch is found, or `None` 141 | /// otherwise. 142 | pub fn with_quad( 143 | &self, 144 | pattern: Quad, Pattern<&R>, Pattern<&R>, Pattern<&R>>, 145 | value: Quad<&R, &R, &R, &R>, 146 | ) -> Option 147 | where 148 | R: Clone + PartialEq, 149 | { 150 | let mut result = self.clone(); 151 | 152 | if let Pattern::Var(x) = pattern.0 { 153 | if let Some(old_value) = result.set(x, Some(value.0.clone())) { 154 | if old_value != *value.0 { 155 | return None; 156 | } 157 | } 158 | } 159 | 160 | if let Pattern::Var(x) = pattern.1 { 161 | if let Some(old_value) = result.set(x, Some(value.1.clone())) { 162 | if old_value != *value.1 { 163 | return None; 164 | } 165 | } 166 | } 167 | 168 | if let Pattern::Var(x) = pattern.2 { 169 | if let Some(old_value) = result.set(x, Some(value.2.clone())) { 170 | if old_value != *value.2 { 171 | return None; 172 | } 173 | } 174 | } 175 | 176 | if let Some(Pattern::Var(x)) = pattern.3 { 177 | let g = value.3.unwrap(); 178 | if let Some(old_value) = result.set(x, Some(g.clone())) { 179 | if old_value != *g { 180 | return None; 181 | } 182 | } 183 | } 184 | 185 | Some(result) 186 | } 187 | } 188 | 189 | impl Default for Substitution { 190 | fn default() -> Self { 191 | Self::new() 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /layouts/src/prelude.rs: -------------------------------------------------------------------------------- 1 | use std::sync::OnceLock; 2 | 3 | use rdf_types::{ 4 | interpretation::{IriInterpretationMut, LiteralInterpretationMut}, 5 | InterpretationMut, VocabularyMut, 6 | }; 7 | 8 | use crate::{ 9 | abs::{self, Builder}, 10 | layout::LayoutType, 11 | Layout, LayoutRegistry, Layouts, Ref, 12 | }; 13 | 14 | const LAYOUTS: [&str; 12] = [ 15 | include_str!("../prelude/unit.json"), 16 | include_str!("../prelude/boolean.json"), 17 | include_str!("../prelude/u8.json"), 18 | include_str!("../prelude/u16.json"), 19 | include_str!("../prelude/u32.json"), 20 | include_str!("../prelude/u64.json"), 21 | include_str!("../prelude/i8.json"), 22 | include_str!("../prelude/i16.json"), 23 | include_str!("../prelude/i32.json"), 24 | include_str!("../prelude/i64.json"), 25 | include_str!("../prelude/string.json"), 26 | include_str!("../prelude/id.json"), 27 | ]; 28 | 29 | pub struct Prelude; 30 | 31 | impl Prelude { 32 | pub fn build() -> Layouts { 33 | let mut builder = Builder::new(); 34 | for json in LAYOUTS { 35 | let layout: abs::syntax::Layout = serde_json::from_str(json).unwrap(); 36 | layout.build(&mut builder).unwrap(); 37 | } 38 | 39 | builder.build() 40 | } 41 | 42 | pub fn build_with(vocabulary: &mut V, interpretation: &mut I) -> Layouts 43 | where 44 | V: VocabularyMut, 45 | I: IriInterpretationMut 46 | + LiteralInterpretationMut 47 | + InterpretationMut, 48 | I::Resource: Clone + Eq + Ord + std::fmt::Debug, 49 | { 50 | let mut builder = Builder::::new(); 51 | for json in LAYOUTS { 52 | let layout: abs::syntax::Layout = serde_json::from_str(json).unwrap(); 53 | layout 54 | .build_with_interpretation(vocabulary, interpretation, &mut builder) 55 | .unwrap(); 56 | } 57 | 58 | builder.build() 59 | } 60 | } 61 | 62 | impl LayoutRegistry for Prelude { 63 | fn get(&self, id: &Ref) -> Option<&Layout> { 64 | static LAYOUTS: OnceLock = OnceLock::new(); 65 | LAYOUTS.get_or_init(Self::build).get(id) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /layouts/src/preset.rs: -------------------------------------------------------------------------------- 1 | use iref::Iri; 2 | use static_iref::iri; 3 | 4 | const ID_LAYOUT: &Iri = iri!("https://treeldr.org/prelude#id"); 5 | const UNIT_LAYOUT: &Iri = iri!("https://treeldr.org/prelude#unit"); 6 | const BOOLEAN_LAYOUT: &Iri = iri!("https://treeldr.org/prelude#boolean"); 7 | const U8_LAYOUT: &Iri = iri!("https://treeldr.org/prelude#u8"); 8 | const U16_LAYOUT: &Iri = iri!("https://treeldr.org/prelude#u16"); 9 | const U32_LAYOUT: &Iri = iri!("https://treeldr.org/prelude#u32"); 10 | const U64_LAYOUT: &Iri = iri!("https://treeldr.org/prelude#u64"); 11 | const I8_LAYOUT: &Iri = iri!("https://treeldr.org/prelude#i8"); 12 | const I16_LAYOUT: &Iri = iri!("https://treeldr.org/prelude#i16"); 13 | const I32_LAYOUT: &Iri = iri!("https://treeldr.org/prelude#i32"); 14 | const I64_LAYOUT: &Iri = iri!("https://treeldr.org/prelude#i64"); 15 | const STRING_LAYOUT: &Iri = iri!("https://treeldr.org/prelude#string"); 16 | 17 | pub enum PresetLayout { 18 | Id, 19 | Unit, 20 | Boolean, 21 | U8, 22 | U16, 23 | U32, 24 | U64, 25 | I8, 26 | I16, 27 | I32, 28 | I64, 29 | String, 30 | } 31 | 32 | impl PresetLayout { 33 | pub fn from_iri(iri: &Iri) -> Option { 34 | if iri == ID_LAYOUT { 35 | Some(Self::Id) 36 | } else if iri == UNIT_LAYOUT { 37 | Some(Self::Unit) 38 | } else if iri == BOOLEAN_LAYOUT { 39 | Some(Self::Boolean) 40 | } else if iri == U8_LAYOUT { 41 | Some(Self::U8) 42 | } else if iri == U16_LAYOUT { 43 | Some(Self::U16) 44 | } else if iri == U32_LAYOUT { 45 | Some(Self::U32) 46 | } else if iri == U64_LAYOUT { 47 | Some(Self::U64) 48 | } else if iri == I8_LAYOUT { 49 | Some(Self::I8) 50 | } else if iri == I16_LAYOUT { 51 | Some(Self::I16) 52 | } else if iri == I32_LAYOUT { 53 | Some(Self::I32) 54 | } else if iri == I64_LAYOUT { 55 | Some(Self::I64) 56 | } else if iri == STRING_LAYOUT { 57 | Some(Self::String) 58 | } else { 59 | None 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /layouts/src/ref.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | use std::marker::PhantomData; 3 | 4 | use educe::Educe; 5 | 6 | /// Typed RDF resource identifier. 7 | /// 8 | /// An RDF resource identified by the type parameter `R` can be anything. 9 | /// This is a simple wrapper around an RDF resource identifier (`R`), with the 10 | /// addition of some type information (`T`) giving some hint about the expected 11 | /// type of the resource. The most common type found in this library is 12 | /// [`LayoutType`](crate::LayoutType), meaning that the referenced resource is 13 | /// a TreeLDR layout. However sometimes the type can be more precise (a layout 14 | /// sub-type for instance). 15 | /// 16 | /// Adding type information gives more legibility to the code and allows the use 17 | /// of some handy functions taking care of type conversions. For instance, 18 | /// the [`DerefResource`] trait provides a method to dereference a resource, 19 | /// returning the definition matching the provided type information (e.g. 20 | /// dereferencing a `Ref` gives a `Layout`, dereferencing a 21 | /// `Ref` gives a `RecordLayout`, etc.). 22 | /// 23 | /// The default resource identifier is [`Term`], meaning that the resource is 24 | /// identified by its lexical RDF representation (an IRI, a blank node 25 | /// identifier or a literal value). This default parameter is easy to use but 26 | /// beware of the following: 27 | /// - A resource may have more than one lexical representation. Hence the 28 | /// [`Term`] type is not adequate as a unique resource identifier. 29 | /// - A term is basically a text string, it requires allocation when created 30 | /// and cloned, and comparison is done in linear time (`O(n)`). 31 | /// For these reasons, it is advised to use a more optimized/unique identifier 32 | /// type for resources, using [`Vocabulary`](rdf_types::Vocabulary) to store 33 | /// the actual lexical representations and 34 | /// [`Interpretation`](rdf_types::Interpretation) to map lexical representations 35 | /// to resources. 36 | /// 37 | /// [`Term`]: rdf_types::Term 38 | #[derive(Educe)] 39 | #[educe( 40 | Debug(bound = "R: std::fmt::Debug"), 41 | Clone(bound = "R: Clone"), 42 | PartialEq(bound = "R: PartialEq"), 43 | Eq(bound = "R: Eq"), 44 | PartialOrd(bound = "R: PartialOrd"), 45 | Ord(bound = "R: Ord"), 46 | Hash(bound = "R: std::hash::Hash") 47 | )] 48 | #[repr(transparent)] 49 | #[derive(serde::Serialize, serde::Deserialize)] 50 | #[serde( 51 | transparent, 52 | bound( 53 | serialize = "R: serde::Serialize", 54 | deserialize = "R: serde::Deserialize<'de>" 55 | ) 56 | )] 57 | pub struct Ref(R, PhantomData); 58 | 59 | impl Copy for Ref {} 60 | 61 | impl Ref { 62 | /// Creates a new typed resource identifier from the untyped resource 63 | /// identifier. 64 | /// 65 | /// The actual type of the resource does not matter here, type checking is 66 | /// done when the resource is dereferenced. 67 | pub fn new(id: R) -> Self { 68 | Self(id, PhantomData) 69 | } 70 | 71 | /// Creates a new typed resource identifier reference from the untyped 72 | /// resource identifier reference. 73 | pub fn new_ref(id: &R) -> &Self { 74 | unsafe { 75 | // SAFETY: `Ref` uses `repr(transparent)` over `R`. 76 | std::mem::transmute(id) 77 | } 78 | } 79 | 80 | /// Returns the untyped resource identifier. 81 | pub fn id(&self) -> &R { 82 | &self.0 83 | } 84 | 85 | /// Consumes the typed identifier and returns the untyped resource 86 | /// identifier. 87 | pub fn into_id(self) -> R { 88 | self.0 89 | } 90 | 91 | /// Changes the type of the identifier. 92 | /// 93 | /// It is always possible to change the type of a typed resource identifier: 94 | /// the actual type of the resource does not matter here, type checking is 95 | /// done when the resource is dereferenced. 96 | pub fn cast(self) -> Ref { 97 | Ref(self.0, PhantomData) 98 | } 99 | 100 | /// Returns a copy of this resource identifier with a different type. 101 | /// 102 | /// It is always possible to change the type of a typed resource identifier: 103 | /// the actual type of the resource does not matter here, type checking is 104 | /// done when the resource is dereferenced. 105 | pub fn casted(&self) -> Ref 106 | where 107 | R: Clone, 108 | { 109 | Ref(self.0.clone(), PhantomData) 110 | } 111 | 112 | /// Returns this resource identifier as resource identifier with a 113 | /// different type. 114 | /// 115 | /// It is always possible to change the type of a typed resource identifier: 116 | /// the actual type of the resource does not matter here, type checking is 117 | /// done when the resource is dereferenced. 118 | pub fn as_casted(&self) -> &Ref { 119 | unsafe { 120 | // SAFETY: this is safe because the `T` type parameter is not 121 | // actually stored in `self`. 122 | std::mem::transmute(self) 123 | } 124 | } 125 | 126 | /// Maps the resource identifier. 127 | pub fn map(self, f: impl FnOnce(R) -> S) -> Ref { 128 | Ref(f(self.0), PhantomData) 129 | } 130 | } 131 | 132 | impl fmt::Display for Ref { 133 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 134 | self.0.fmt(f) 135 | } 136 | } 137 | 138 | /// Context able to fetch (dereference) the definition of a resource for the 139 | /// given type `T`. 140 | /// 141 | /// Resources are identified by the type parameter `R`. 142 | pub trait DerefResource: Sized { 143 | /// Type of the value returned upon dereferencing the resource identifier. 144 | type Target<'c> 145 | where 146 | Self: 'c, 147 | R: 'c; 148 | 149 | /// Returns the definition of the `resource` for the type `T`, if any. 150 | /// 151 | /// This function returns `Some(value)` if the definition is found or `None` 152 | /// if the resource is unknown or if it has no known definition for the type 153 | /// `T` (type mismatch). 154 | fn deref_resource<'c>(&'c self, resource: &Ref) -> Option>; 155 | } 156 | -------------------------------------------------------------------------------- /layouts/src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod automaton; 2 | 3 | pub use automaton::{Automaton, DetAutomaton}; 4 | 5 | use btree_range_map::RangeSet; 6 | use educe::Educe; 7 | use iref::IriBuf; 8 | use locspan::{Meta, Span}; 9 | use rdf_types::{GraphLabel, Id, Object, Quad, Term}; 10 | 11 | use crate::Pattern; 12 | 13 | /// Computes the intersection of two character sets. 14 | pub fn charset_intersection(a: &RangeSet, b: &RangeSet) -> RangeSet { 15 | let mut result = a.clone(); 16 | 17 | for r in b.gaps() { 18 | result.remove(r.cloned()); 19 | } 20 | 21 | result 22 | } 23 | 24 | pub trait QuadsExt<'a, R>: Sized { 25 | fn with_default_graph(self, graph: Option<&'a R>) -> QuadsWithDefaultGraph<'a, R, Self>; 26 | } 27 | 28 | impl<'a, R: 'a, I> QuadsExt<'a, R> for I 29 | where 30 | I: Iterator, &'a Pattern, &'a Pattern, &'a Pattern>>, 31 | { 32 | fn with_default_graph(self, graph: Option<&'a R>) -> QuadsWithDefaultGraph<'a, R, Self> { 33 | QuadsWithDefaultGraph { quads: self, graph } 34 | } 35 | } 36 | 37 | #[derive(Educe)] 38 | #[educe(Clone(bound = "I: Clone"))] 39 | pub struct QuadsWithDefaultGraph<'a, R, I> { 40 | quads: I, 41 | graph: Option<&'a R>, 42 | } 43 | 44 | impl<'a, R, I> Iterator for QuadsWithDefaultGraph<'a, R, I> 45 | where 46 | I: Iterator, &'a Pattern, &'a Pattern, &'a Pattern>>, 47 | { 48 | type Item = Quad, Pattern<&'a R>, Pattern<&'a R>, Pattern<&'a R>>; 49 | 50 | fn next(&mut self) -> Option { 51 | self.quads.next().map(|quad| { 52 | Quad( 53 | quad.0.as_ref(), 54 | quad.1.as_ref(), 55 | quad.2.as_ref(), 56 | quad.3 57 | .map(Pattern::as_ref) 58 | .or_else(|| self.graph.map(Pattern::Resource)), 59 | ) 60 | }) 61 | } 62 | } 63 | 64 | pub type MetaQuad = Meta< 65 | rdf_types::Quad, Meta, Meta, Meta>, 66 | Span, 67 | >; 68 | 69 | /// Strips the input RDF `quad` of its metadata information and returns it as a 70 | /// gRDF quad (a quad where all components are [`Term`](rdf_types::Term)s). 71 | pub fn strip_rdf_quad( 72 | locspan::Meta(Quad( 73 | locspan::Meta(s, _), 74 | locspan::Meta(p, _), 75 | locspan::Meta(o, _), 76 | g 77 | ), _): MetaQuad, 78 | ) -> Quad { 79 | Quad( 80 | Term::Id(s), 81 | Term::iri(p), 82 | o, 83 | g.map(|locspan::Meta(g, _)| Term::Id(g)), 84 | ) 85 | } 86 | -------------------------------------------------------------------------------- /layouts/src/value/cbor/mod.rs: -------------------------------------------------------------------------------- 1 | use iref::{Iri, IriBuf}; 2 | use rdf_types::{ 3 | interpretation::{IriInterpretation, ReverseLiteralInterpretation}, 4 | Vocabulary, 5 | }; 6 | use static_iref::iri; 7 | 8 | use crate::{layout::LayoutType, LayoutRegistry, Ref}; 9 | 10 | #[cfg(feature = "serde_cbor")] 11 | mod serde_cbor; 12 | 13 | /// CBOR extension `tag` property. 14 | /// 15 | /// Allows one to specifies the CBOR tag of a TreeLDR layout. 16 | pub const CBOR_TAG_IRI: &Iri = iri!("https://schema.treeldr.org/cbor#tag"); 17 | 18 | /// Error type returned by the [`get_layout_tag`] function when the value 19 | /// of the [CBOR extension `tag` property](CBOR_TAG_IRI) is invalid. 20 | #[derive(Debug, thiserror::Error)] 21 | pub enum InvalidTag { 22 | #[error("non literal tag value")] 23 | NonLiteral, 24 | 25 | #[error("invalid tag value: {0}")] 26 | Value(String), 27 | 28 | #[error("invalid tag type: {0}")] 29 | Type(IriBuf), 30 | } 31 | 32 | /// Returns the CBOR tag of a given layout (reference). 33 | pub fn get_layout_tag( 34 | vocabulary: &V, 35 | interpretation: &I, 36 | layouts: &impl LayoutRegistry, 37 | layout_ref: &Ref, 38 | ) -> Result, InvalidTag> 39 | where 40 | V: Vocabulary, 41 | I: IriInterpretation + ReverseLiteralInterpretation, 42 | I::Resource: Ord, 43 | { 44 | let layout = layouts.get(layout_ref).expect("missing layout definition"); 45 | match interpretation.lexical_iri_interpretation(vocabulary, CBOR_TAG_IRI) { 46 | Some(prop) => { 47 | match layout.extra_properties().get(&prop) { 48 | Some(value) => { 49 | for l in interpretation.literals_of(value) { 50 | if let Some(literal) = vocabulary.literal(l) { 51 | if let rdf_types::LiteralTypeRef::Any(ty) = literal.type_ { 52 | if let Some(ty_iri) = vocabulary.iri(ty) { 53 | return match xsd_types::UnsignedLongDatatype::from_iri(ty_iri) { 54 | Some(_) => literal.value.parse().map(Some).map_err(|_| { 55 | InvalidTag::Value(literal.value.to_owned()) 56 | }), 57 | None => Err(InvalidTag::Type(ty_iri.to_owned())), 58 | }; 59 | } 60 | } 61 | } 62 | } 63 | 64 | Err(InvalidTag::NonLiteral) 65 | } 66 | None => Ok(None), 67 | } 68 | } 69 | None => Ok(None), 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /layouts/src/value/de.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::BTreeMap, fmt}; 2 | 3 | use super::{Literal, Number, Value}; 4 | 5 | impl<'de> serde::Deserialize<'de> for Value { 6 | #[inline] 7 | fn deserialize(deserializer: D) -> Result 8 | where 9 | D: serde::Deserializer<'de>, 10 | { 11 | struct ValueVisitor; 12 | 13 | impl<'de> serde::de::Visitor<'de> for ValueVisitor { 14 | type Value = Value; 15 | 16 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 17 | formatter.write_str("any valid TreeLDR value") 18 | } 19 | 20 | #[inline] 21 | fn visit_bool(self, value: bool) -> Result { 22 | Ok(Value::Literal(Literal::Boolean(value))) 23 | } 24 | 25 | #[inline] 26 | fn visit_i64(self, value: i64) -> Result { 27 | Ok(Value::Literal(Literal::Number(value.into()))) 28 | } 29 | 30 | #[inline] 31 | fn visit_u64(self, value: u64) -> Result { 32 | Ok(Value::Literal(Literal::Number(value.into()))) 33 | } 34 | 35 | #[inline] 36 | fn visit_f64(self, value: f64) -> Result 37 | where 38 | E: serde::de::Error, 39 | { 40 | match Number::try_from(value) { 41 | Ok(value) => Ok(Value::Literal(Literal::Number(value))), 42 | Err(_) => Err(E::invalid_value(serde::de::Unexpected::Float(value), &self)), 43 | } 44 | } 45 | 46 | fn visit_bytes(self, value: &[u8]) -> Result 47 | where 48 | E: serde::de::Error, 49 | { 50 | self.visit_byte_buf(value.to_vec()) 51 | } 52 | 53 | fn visit_byte_buf(self, value: Vec) -> Result 54 | where 55 | E: serde::de::Error, 56 | { 57 | Ok(Value::Literal(Literal::ByteString(value))) 58 | } 59 | 60 | #[inline] 61 | fn visit_str(self, value: &str) -> Result 62 | where 63 | E: serde::de::Error, 64 | { 65 | self.visit_string(String::from(value)) 66 | } 67 | 68 | #[inline] 69 | fn visit_string(self, value: String) -> Result { 70 | Ok(Value::Literal(Literal::TextString(value))) 71 | } 72 | 73 | #[inline] 74 | fn visit_none(self) -> Result { 75 | Ok(Value::Literal(Literal::Unit)) 76 | } 77 | 78 | #[inline] 79 | fn visit_some(self, deserializer: D) -> Result 80 | where 81 | D: serde::Deserializer<'de>, 82 | { 83 | serde::Deserialize::deserialize(deserializer) 84 | } 85 | 86 | #[inline] 87 | fn visit_unit(self) -> Result { 88 | Ok(Value::Literal(Literal::Unit)) 89 | } 90 | 91 | #[inline] 92 | fn visit_seq(self, mut visitor: V) -> Result 93 | where 94 | V: serde::de::SeqAccess<'de>, 95 | { 96 | let mut vec = Vec::new(); 97 | 98 | while let Some(elem) = visitor.next_element()? { 99 | vec.push(elem); 100 | } 101 | 102 | Ok(Value::List(vec)) 103 | } 104 | 105 | #[inline] 106 | fn visit_map(self, mut visitor: V) -> Result 107 | where 108 | V: serde::de::MapAccess<'de>, 109 | { 110 | let mut map = BTreeMap::new(); 111 | 112 | while let Some((key, value)) = visitor.next_entry()? { 113 | map.insert(key, value); 114 | } 115 | 116 | Ok(Value::Map(map)) 117 | } 118 | } 119 | 120 | deserializer.deserialize_any(ValueVisitor) 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /layouts/src/value/ser.rs: -------------------------------------------------------------------------------- 1 | use super::{Literal, NativeNumber, Value}; 2 | 3 | impl serde::Serialize for Value { 4 | fn serialize(&self, serializer: S) -> Result 5 | where 6 | S: serde::Serializer, 7 | { 8 | match self { 9 | Self::Literal(literal) => literal.serialize(serializer), 10 | Self::Map(record) => { 11 | use serde::ser::SerializeMap; 12 | let mut map = serializer.serialize_map(Some(record.len()))?; 13 | 14 | for (key, value) in record { 15 | map.serialize_entry(key, value)?; 16 | } 17 | 18 | map.end() 19 | } 20 | Self::List(list) => { 21 | use serde::ser::SerializeSeq; 22 | let mut seq = serializer.serialize_seq(Some(list.len()))?; 23 | 24 | for item in list { 25 | seq.serialize_element(item)?; 26 | } 27 | 28 | seq.end() 29 | } 30 | } 31 | } 32 | } 33 | 34 | impl serde::Serialize for Literal { 35 | fn serialize(&self, serializer: S) -> Result 36 | where 37 | S: serde::Serializer, 38 | { 39 | match self { 40 | Self::Unit => serializer.serialize_unit(), 41 | Self::Boolean(b) => serializer.serialize_bool(*b), 42 | Self::Number(n) => match n.as_native() { 43 | NativeNumber::U64(u) => serializer.serialize_u64(u), 44 | NativeNumber::I64(i) => serializer.serialize_i64(i), 45 | NativeNumber::F64(f) => serializer.serialize_f64(f), 46 | }, 47 | Self::TextString(s) => serializer.serialize_str(s), 48 | Self::ByteString(b) => serializer.serialize_bytes(b), 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /layouts/src/value/serde_cbor.rs: -------------------------------------------------------------------------------- 1 | use num_traits::{Signed, ToBytes}; 2 | 3 | use super::{Literal, Value}; 4 | 5 | impl From for serde_cbor::Value { 6 | fn from(value: Value) -> Self { 7 | match value { 8 | Value::Literal(Literal::Unit) => serde_cbor::Value::Null, 9 | Value::Literal(Literal::Boolean(b)) => serde_cbor::Value::Bool(b), 10 | Value::Literal(Literal::Number(n)) => match n.as_integer() { 11 | Some(i) => { 12 | if i.bits() <= 64 { 13 | let unsigned = i.iter_u64_digits().next().unwrap() as i128; 14 | 15 | let signed = if i.is_positive() { unsigned } else { -unsigned }; 16 | 17 | serde_cbor::Value::Integer(signed) 18 | } else { 19 | let tag = if i.is_positive() { 2 } else { 3 }; 20 | 21 | serde_cbor::Value::Tag( 22 | tag, 23 | Box::new(serde_cbor::Value::Bytes(i.to_be_bytes())), 24 | ) 25 | } 26 | } 27 | None => serde_cbor::Value::Float(n.to_f64()), 28 | }, 29 | Value::Literal(Literal::ByteString(bytes)) => serde_cbor::Value::Bytes(bytes), 30 | Value::Literal(Literal::TextString(string)) => serde_cbor::Value::Text(string), 31 | Value::Record(map) => serde_cbor::Value::Map( 32 | map.into_iter() 33 | .map(|(key, value)| (serde_cbor::Value::Text(key), value.into())) 34 | .collect(), 35 | ), 36 | Value::List(items) => { 37 | serde_cbor::Value::Array(items.into_iter().map(Into::into).collect()) 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /layouts/tests/distill/e01-in.nq: -------------------------------------------------------------------------------- 1 | _:john_smith "John Smith" . -------------------------------------------------------------------------------- /layouts/tests/distill/e01-layout.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "record", 3 | "input": ["self"], 4 | "fields": { 5 | "name": { 6 | "value": { "type": "string" }, 7 | "property": "https://schema.org/name", 8 | "required": true 9 | }, 10 | "email": { 11 | "value": { "type": "string" }, 12 | "property": "https://schema.org/email", 13 | "required": true 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /layouts/tests/distill/e01-out.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "John Smith" 3 | } -------------------------------------------------------------------------------- /layouts/tests/distill/t01-in.nq: -------------------------------------------------------------------------------- 1 | _:john_smith "John Smith" . -------------------------------------------------------------------------------- /layouts/tests/distill/t01-layout.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "record", 3 | "input": ["self"], 4 | "fields": { 5 | "name": { 6 | "intro": ["value"], 7 | "value": { 8 | "layout": { 9 | "input": ["self"], 10 | "type": "string", 11 | "resource": "_:self" 12 | }, 13 | "input": ["_:value"] 14 | }, 15 | "dataset": [ 16 | ["_:self", "https://schema.org/name", "_:value"] 17 | ] 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /layouts/tests/distill/t01-out.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "John Smith" 3 | } -------------------------------------------------------------------------------- /layouts/tests/distill/t02-in.nq: -------------------------------------------------------------------------------- 1 | _:john_smith "John Smith" . -------------------------------------------------------------------------------- /layouts/tests/distill/t02-layout.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "record", 3 | "fields": { 4 | "name": { 5 | "value": { 6 | "type": "string" 7 | }, 8 | "property": "https://schema.org/name" 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /layouts/tests/distill/t02-out.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "John Smith" 3 | } -------------------------------------------------------------------------------- /layouts/tests/distill/t03-in.nq: -------------------------------------------------------------------------------- 1 | _:receipt _:0 . 2 | _:receipt _:3 . 3 | _:receipt "2024-01-01T00:00:00Z"^^ . 4 | _:0 _:1 . 5 | _:0 _:2 . 6 | _:1 "1.49"^^ . 7 | _:1 "2"^^ . 8 | _:2 "10.99"^^ . 9 | _:2 "0.8"^^ . 10 | _:3 "Champ-de-Mars, Paris, France"^^ . 11 | _:3 "2"^^ . -------------------------------------------------------------------------------- /layouts/tests/distill/t03-layout.json: -------------------------------------------------------------------------------- 1 | { 2 | "prefixes": { 3 | "xsd": "http://www.w3.org/2001/XMLSchema#", 4 | "schema": "https://schema.org/", 5 | "ex": "https://example.org/#" 6 | }, 7 | "type": "record", 8 | "input": ["receipt"], 9 | "fields": { 10 | "date": { 11 | "intro": ["date"], 12 | "value": { 13 | "input": ["_:date"], 14 | "layout": { 15 | "type": "string", 16 | "input": ["value"], 17 | "resource": "_:value", 18 | "datatype": "xsd:dateTime" 19 | } 20 | }, 21 | "dataset": [ 22 | ["_:receipt", "ex:date", "_:date"] 23 | ] 24 | }, 25 | "bill": { 26 | "intro": ["bill"], 27 | "value": { 28 | "input": ["_:bill"], 29 | "layout": { 30 | "type": "record", 31 | "input": ["bill"], 32 | "fields": { 33 | "milk1L": { 34 | "intro": ["milk1L"], 35 | "value": { 36 | "input": ["_:milk1L"], 37 | "layout": { 38 | "id": "ex:ItemByUnit", 39 | "type": "record", 40 | "input": ["item"], 41 | "fields": { 42 | "unitPrice": { 43 | "intro": ["unitPrice"], 44 | "value": { 45 | "input": ["_:unitPrice"], 46 | "layout": { 47 | "type": "number", 48 | "input": ["value"], 49 | "datatype": "xsd:decimal", 50 | "resource": "_:value" 51 | } 52 | }, 53 | "dataset": [ 54 | ["_:item", "ex:unitPrice", "_:unitPrice"] 55 | ] 56 | }, 57 | "units": { 58 | "intro": ["units"], 59 | "value": { 60 | "input": ["_:units"], 61 | "layout": { 62 | "type": "number", 63 | "input": ["value"], 64 | "datatype": "xsd:nonNegativeInteger", 65 | "resource": "_:value" 66 | } 67 | }, 68 | "dataset": [ 69 | ["_:item", "ex:units", "_:units"] 70 | ] 71 | } 72 | } 73 | } 74 | }, 75 | "dataset": [ 76 | ["_:bill", "ex:milk1L", "_:milk1L"] 77 | ] 78 | }, 79 | "beef": { 80 | "intro": ["beef"], 81 | "value": { 82 | "input": ["_:beef"], 83 | "layout": { 84 | "id": "ex:ItemByVolume", 85 | "type": "record", 86 | "input": ["item"], 87 | "fields": { 88 | "volumePrice": { 89 | "intro": ["volumePrice"], 90 | "value": { 91 | "input": ["_:volumePrice"], 92 | "layout": { 93 | "type": "number", 94 | "input": ["value"], 95 | "datatype": "xsd:decimal", 96 | "resource":"_:value" 97 | } 98 | }, 99 | "dataset": [ 100 | ["_:item", "ex:volumePrice", "_:volumePrice"] 101 | ] 102 | }, 103 | "volume": { 104 | "intro": ["volume"], 105 | "value": { 106 | "input": ["_:volume"], 107 | "layout": { 108 | "type": "number", 109 | "input": ["value"], 110 | "datatype": "xsd:decimal", 111 | "resource":"_:value" 112 | } 113 | }, 114 | "dataset": [ 115 | ["_:item", "ex:volume", "_:volume"] 116 | ] 117 | } 118 | } 119 | } 120 | }, 121 | "dataset": [ 122 | ["_:bill", "ex:beef", "_:beef"] 123 | ] 124 | } 125 | } 126 | } 127 | }, 128 | "dataset": [ 129 | ["_:receipt", "ex:bill", "_:bill"] 130 | ] 131 | }, 132 | "pointOfSale": { 133 | "intro": ["pointOfSale"], 134 | "value": { 135 | "input": ["_:pointOfSale"], 136 | "layout": { 137 | "id": "ex:PointOfSale", 138 | "type": "record", 139 | "input": ["pointOfSale"], 140 | "fields": { 141 | "address": { 142 | "intro": ["address"], 143 | "value": { 144 | "input": ["_:address"], 145 | "layout": { 146 | "type": "string", 147 | "input": ["value"], 148 | "resource": "_:value", 149 | "datatype": "https://schema.org/Text" 150 | } 151 | }, 152 | "dataset": [ 153 | ["_:pointOfSale", "schema:address", "_:address"] 154 | ] 155 | }, 156 | "till": { 157 | "intro": ["till"], 158 | "value": { 159 | "input": ["_:till"], 160 | "layout": { 161 | "type": "number", 162 | "input": ["value"], 163 | "datatype": "xsd:nonNegativeInteger", 164 | "resource": "_:value" 165 | } 166 | }, 167 | "dataset": [ 168 | ["_:pointOfSale", "ex:till", "_:till"] 169 | ] 170 | } 171 | } 172 | } 173 | }, 174 | "dataset": [ 175 | ["_:receipt", "ex:pointOfSale", "_:pointOfSale"] 176 | ] 177 | } 178 | } 179 | } -------------------------------------------------------------------------------- /layouts/tests/distill/t03-out.json: -------------------------------------------------------------------------------- 1 | { 2 | "date": "2024-01-01T00:00:00Z", 3 | "bill": { 4 | "milk1L": { 5 | "unitPrice": 1.49, 6 | "units": 2 7 | }, 8 | "beef": { 9 | "volumePrice": 10.99, 10 | "volume": 0.8 11 | } 12 | }, 13 | "pointOfSale": { 14 | "address": "Champ-de-Mars, Paris, France", 15 | "till": 2 16 | } 17 | } -------------------------------------------------------------------------------- /layouts/tests/distill/t04-in.nq: -------------------------------------------------------------------------------- 1 | _:receipt _:0 . 2 | _:receipt _:3 . 3 | _:receipt "2024-01-01T00:00:00Z"^^ . 4 | _:0 _:1 . 5 | _:0 _:2 . 6 | _:1 "1.49"^^ . 7 | _:1 "2"^^ . 8 | _:2 "10.99"^^ . 9 | _:2 "0.8"^^ . 10 | _:3 "Champ-de-Mars, Paris, France"^^ . 11 | _:3 "2"^^ . -------------------------------------------------------------------------------- /layouts/tests/distill/t04-layout.json: -------------------------------------------------------------------------------- 1 | { 2 | "prefixes": { 3 | "xsd": "http://www.w3.org/2001/XMLSchema#", 4 | "schema": "https://schema.org/", 5 | "ex": "https://example.org/#" 6 | }, 7 | "type": "record", 8 | "fields": { 9 | "date": { 10 | "value": { 11 | "type": "string", 12 | "datatype": "xsd:dateTime" 13 | }, 14 | "property": "ex:date" 15 | }, 16 | "bill": { 17 | "value": { 18 | "type": "record", 19 | "fields": { 20 | "milk1L": { 21 | "value": { 22 | "id": "ex:ItemByUnit", 23 | "type": "record", 24 | "fields": { 25 | "unitPrice": { 26 | "value": { 27 | "type": "number", 28 | "datatype": "xsd:decimal" 29 | }, 30 | "property": "ex:unitPrice" 31 | }, 32 | "units": { 33 | "value": { 34 | "type": "number", 35 | "datatype": "xsd:nonNegativeInteger" 36 | }, 37 | "property": "ex:units" 38 | } 39 | } 40 | }, 41 | "property": "ex:milk1L" 42 | }, 43 | "beef": { 44 | "value": { 45 | "id": "ex:ItemByVolume", 46 | "type": "record", 47 | "fields": { 48 | "volumePrice": { 49 | "value": { 50 | "type": "number", 51 | "datatype": "xsd:decimal" 52 | }, 53 | "property": "ex:volumePrice" 54 | }, 55 | "volume": { 56 | "value": { 57 | "type": "number", 58 | "datatype": "xsd:decimal" 59 | }, 60 | "property": "ex:volume" 61 | } 62 | } 63 | }, 64 | "property": "ex:beef" 65 | } 66 | } 67 | }, 68 | "property": "ex:bill" 69 | }, 70 | "pointOfSale": { 71 | "value": { 72 | "id": "ex:PointOfSale", 73 | "type": "record", 74 | "fields": { 75 | "address": { 76 | "value": { 77 | "type": "string", 78 | "datatype": "https://schema.org/Text" 79 | }, 80 | "property": "schema:address" 81 | }, 82 | "till": { 83 | "value": { 84 | "type": "number", 85 | "datatype": "xsd:nonNegativeInteger" 86 | }, 87 | "property": "ex:till" 88 | } 89 | } 90 | }, 91 | "property": "ex:pointOfSale" 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /layouts/tests/distill/t04-out.json: -------------------------------------------------------------------------------- 1 | { 2 | "date": "2024-01-01T00:00:00Z", 3 | "bill": { 4 | "milk1L": { 5 | "unitPrice": 1.49, 6 | "units": 2 7 | }, 8 | "beef": { 9 | "volumePrice": 10.99, 10 | "volume": 0.8 11 | } 12 | }, 13 | "pointOfSale": { 14 | "address": "Champ-de-Mars, Paris, France", 15 | "till": 2 16 | } 17 | } -------------------------------------------------------------------------------- /layouts/tests/distill/t05-in.nq: -------------------------------------------------------------------------------- 1 | _:list "foo" . 2 | _:list _:0 . 3 | _:0 "bar" . 4 | _:0 _:1 . 5 | _:1 "baz" . 6 | _:1 . -------------------------------------------------------------------------------- /layouts/tests/distill/t05-layout.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "list", 3 | "input": "head", 4 | "node": { 5 | "intro": "item", 6 | "value": { 7 | "layout": { 8 | "type": "string", 9 | "input": "value", 10 | "resource": "_:value" 11 | }, 12 | "input": ["_:item"] 13 | }, 14 | "head": "head", 15 | "rest": "rest", 16 | "dataset": [ 17 | ["_:head", "http://www.w3.org/1999/02/22-rdf-syntax-ns#first", "_:item"], 18 | ["_:head", "http://www.w3.org/1999/02/22-rdf-syntax-ns#rest", "_:rest"] 19 | ] 20 | }, 21 | "head": "_:head", 22 | "tail": "http://www.w3.org/1999/02/22-rdf-syntax-ns#nil" 23 | } -------------------------------------------------------------------------------- /layouts/tests/distill/t05-out.json: -------------------------------------------------------------------------------- 1 | [ 2 | "foo", 3 | "bar", 4 | "baz" 5 | ] -------------------------------------------------------------------------------- /layouts/tests/distill/t06-in.nq: -------------------------------------------------------------------------------- 1 | _:list "foo" . 2 | _:list _:0 . 3 | _:0 "bar" . 4 | _:0 _:1 . 5 | _:1 "baz" . 6 | _:1 . -------------------------------------------------------------------------------- /layouts/tests/distill/t06-layout.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "list", 3 | "node": { 4 | "type": "string" 5 | } 6 | } -------------------------------------------------------------------------------- /layouts/tests/distill/t06-out.json: -------------------------------------------------------------------------------- 1 | [ 2 | "foo", 3 | "bar", 4 | "baz" 5 | ] -------------------------------------------------------------------------------- /layouts/tests/distill/t07-in.nq: -------------------------------------------------------------------------------- 1 | _:subject "foo" . 2 | _:subject "bar" . 3 | _:subject "baz" . -------------------------------------------------------------------------------- /layouts/tests/distill/t07-layout.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "set", 3 | "input": "subject", 4 | "item": { 5 | "intro": "item", 6 | "value": { 7 | "layout": { 8 | "type": "string", 9 | "input": "value", 10 | "resource": "_:value" 11 | }, 12 | "input": ["_:item"] 13 | }, 14 | "dataset": [ 15 | ["_:subject", "http://example.org/prop", "_:item"] 16 | ] 17 | } 18 | } -------------------------------------------------------------------------------- /layouts/tests/distill/t07-out.json: -------------------------------------------------------------------------------- 1 | [ 2 | "bar", 3 | "baz", 4 | "foo" 5 | ] -------------------------------------------------------------------------------- /layouts/tests/distill/t08-in.nq: -------------------------------------------------------------------------------- 1 | _:subject "foo" . 2 | _:subject "bar" . 3 | _:subject "baz" . -------------------------------------------------------------------------------- /layouts/tests/distill/t08-layout.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "set", 3 | "item": { 4 | "value": { 5 | "type": "string" 6 | }, 7 | "property": "http://example.org/prop" 8 | } 9 | } -------------------------------------------------------------------------------- /layouts/tests/distill/t08-out.json: -------------------------------------------------------------------------------- 1 | [ 2 | "bar", 3 | "baz", 4 | "foo" 5 | ] -------------------------------------------------------------------------------- /layouts/tests/distill/t09-in.nq: -------------------------------------------------------------------------------- 1 | _:subject "true"^^ . 2 | _:subject "12"^^ . 3 | _:subject "string" . -------------------------------------------------------------------------------- /layouts/tests/distill/t09-layout.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "tuple", 3 | "input": "subject", 4 | "items": [ 5 | { 6 | "intro": "item", 7 | "value": { 8 | "layout": { 9 | "type": "boolean", 10 | "input": "value", 11 | "resource": "_:value", 12 | "datatype": "http://www.w3.org/2001/XMLSchema#boolean" 13 | }, 14 | "input": ["_:item"] 15 | }, 16 | "dataset": [ 17 | ["_:subject", "http://example.org/prop1", "_:item"] 18 | ] 19 | }, 20 | { 21 | "intro": "item", 22 | "value": { 23 | "layout": { 24 | "type": "number", 25 | "input": "value", 26 | "resource": "_:value", 27 | "datatype": "http://www.w3.org/2001/XMLSchema#integer" 28 | }, 29 | "input": ["_:item"] 30 | }, 31 | "dataset": [ 32 | ["_:subject", "http://example.org/prop2", "_:item"] 33 | ] 34 | }, 35 | { 36 | "intro": "item", 37 | "value": { 38 | "layout": { 39 | "type": "string", 40 | "input": "value", 41 | "resource": "_:value", 42 | "datatype": "http://www.w3.org/2001/XMLSchema#string" 43 | }, 44 | "input": ["_:item"] 45 | }, 46 | "dataset": [ 47 | ["_:subject", "http://example.org/prop3", "_:item"] 48 | ] 49 | } 50 | ] 51 | } -------------------------------------------------------------------------------- /layouts/tests/distill/t09-out.json: -------------------------------------------------------------------------------- 1 | [ 2 | true, 3 | 12, 4 | "string" 5 | ] -------------------------------------------------------------------------------- /layouts/tests/distill/t10-in.nq: -------------------------------------------------------------------------------- 1 | _:subject "true"^^ . 2 | _:subject "12"^^ . 3 | _:subject "string" . -------------------------------------------------------------------------------- /layouts/tests/distill/t10-layout.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "tuple", 3 | "items": [ 4 | { 5 | "value": { 6 | "type": "boolean" 7 | }, 8 | "property": "http://example.org/prop1" 9 | }, 10 | { 11 | "value": { 12 | "type": "number", 13 | "datatype": "http://www.w3.org/2001/XMLSchema#integer" 14 | }, 15 | "property": "http://example.org/prop2" 16 | }, 17 | { 18 | "value": { 19 | "type": "string" 20 | }, 21 | "property": "http://example.org/prop3" 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /layouts/tests/distill/t10-out.json: -------------------------------------------------------------------------------- 1 | [ 2 | true, 3 | 12, 4 | "string" 5 | ] -------------------------------------------------------------------------------- /layouts/tests/distill/t11-in.nq: -------------------------------------------------------------------------------- 1 | "John Smith" . -------------------------------------------------------------------------------- /layouts/tests/distill/t11-layout.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "record", 3 | "fields": { 4 | "id": { 5 | "intro": [], 6 | "value": { 7 | "layout": { 8 | "type": "id" 9 | }, 10 | "input": "_:self" 11 | } 12 | }, 13 | "name": { 14 | "value": { 15 | "type": "string" 16 | }, 17 | "property": "https://schema.org/name" 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /layouts/tests/distill/t11-out.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "https://example.org/JohnSmith", 3 | "name": "John Smith" 4 | } -------------------------------------------------------------------------------- /layouts/tests/distill/t12-in.nq: -------------------------------------------------------------------------------- 1 | _:subject "true"^^ . 2 | _:subject "12"^^ . 3 | _:subject "string" . -------------------------------------------------------------------------------- /layouts/tests/distill/t12-layout.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "set", 3 | "item": { 4 | "value": { 5 | "type": "sum", 6 | "variants": { 7 | "integer": { 8 | "value": { 9 | "type": "number", 10 | "datatype": "http://www.w3.org/2001/XMLSchema#integer" 11 | } 12 | }, 13 | "boolean": { 14 | "value": { 15 | "type": "boolean" 16 | } 17 | }, 18 | "string": { 19 | "value": { 20 | "type": "string" 21 | } 22 | } 23 | } 24 | }, 25 | "property": "http://example.org/prop" 26 | } 27 | } -------------------------------------------------------------------------------- /layouts/tests/distill/t12-out.json: -------------------------------------------------------------------------------- 1 | [ 2 | true, 3 | 12, 4 | "string" 5 | ] -------------------------------------------------------------------------------- /layouts/tests/distill/t13-in.nq: -------------------------------------------------------------------------------- 1 | _:subject . -------------------------------------------------------------------------------- /layouts/tests/distill/t13-layout.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "unit", 3 | "const": "Hello World!", 4 | "dataset": [ 5 | ["_:self", "http://example.org/prop", "http://example.org/HelloWorld"] 6 | ] 7 | } -------------------------------------------------------------------------------- /layouts/tests/distill/t13-out.json: -------------------------------------------------------------------------------- 1 | "Hello World!" -------------------------------------------------------------------------------- /layouts/tests/distill/t14-in.nq: -------------------------------------------------------------------------------- 1 | _:subject "true"^^ . 2 | _:subject "false"^^ . -------------------------------------------------------------------------------- /layouts/tests/distill/t14-layout.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "record", 3 | "fields": { 4 | "foo": { 5 | "value": { 6 | "id": "https://example.org/#FieldLayout", 7 | "type": "boolean" 8 | }, 9 | "property": "https://example.org/#foo" 10 | }, 11 | "bar": { 12 | "value": "https://example.org/#FieldLayout", 13 | "property": "https://example.org/#bar" 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /layouts/tests/distill/t14-out.json: -------------------------------------------------------------------------------- 1 | { 2 | "foo": true, 3 | "bar": false 4 | } -------------------------------------------------------------------------------- /layouts/tests/distill/t15-in.nq: -------------------------------------------------------------------------------- 1 | _:subject "Foo"^^ . -------------------------------------------------------------------------------- /layouts/tests/distill/t15-layout.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "unit", 3 | "dataset": [ 4 | ["_:self", "https://example.org/#foo", { "value": "Foo", "type": "https://example.org/#Type" }] 5 | ] 6 | } -------------------------------------------------------------------------------- /layouts/tests/distill/t15-out.json: -------------------------------------------------------------------------------- 1 | null -------------------------------------------------------------------------------- /layouts/tests/distill/t16-in.nq: -------------------------------------------------------------------------------- 1 | _:subject "John Smith" . 2 | _:subject "john.smith@example.com" . 3 | _:subject . 4 | . 5 | "Project execution"@en . 6 | . 7 | "Contribute to solution design and implementation."@en . -------------------------------------------------------------------------------- /layouts/tests/distill/t16-layout.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "record", 3 | "fields": { 4 | "name": { "property": "https://schema.org/name", "value": { "type": "string" } }, 5 | "email": { "property": "https://schema.org/email", "value": { "type": "string" } }, 6 | "achievement": { 7 | "intro": [], 8 | "value": { 9 | "input": ["_:self"], 10 | "layout": { 11 | "type": "sum", 12 | "variants": { 13 | "projectExecution": { 14 | "intro": [], 15 | "value": { 16 | "input": ["_:self"], 17 | "layout": { 18 | "type": "unit", 19 | "const": "projectExecution", 20 | "dataset": [ 21 | ["_:self", "https://purl.imsglobal.org/spec/vc/ob/vocab.html#achievement", "http://example.com/#projectExecution"], 22 | ["http://example.com/#projectExecution", "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", "https://purl.imsglobal.org/spec/vc/ob/vocab.html#Achievement"], 23 | ["http://example.com/#projectExecution", "https://schema.org/name", { "value": "Project execution", "language": "en" }], 24 | ["http://example.com/#projectExecution", "https://purl.imsglobal.org/spec/vc/ob/vocab.html#Criteria", "http://example.com/#projectExecutionCriteria"], 25 | ["http://example.com/#projectExecutionCriteria", "https://purl.imsglobal.org/spec/vc/ob/vocab.html#narrative", { "value": "Contribute to solution design and implementation.", "language": "en" }] 26 | ] 27 | } 28 | } 29 | } 30 | } 31 | } 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /layouts/tests/distill/t16-out.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "John Smith", 3 | "email": "john.smith@example.com", 4 | "achievement": "projectExecution" 5 | } -------------------------------------------------------------------------------- /layouts/tests/distill/t17-in.nq: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spruceid/treeldr/82f86519141c1188fd69a2fd3730388973218207/layouts/tests/distill/t17-in.nq -------------------------------------------------------------------------------- /layouts/tests/distill/t17-layout.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "set", 3 | "item": { 4 | "value": { 5 | "type": "sum", 6 | "variants": { 7 | "integer": { 8 | "value": { 9 | "type": "number", 10 | "datatype": "http://www.w3.org/2001/XMLSchema#integer" 11 | } 12 | }, 13 | "boolean": { 14 | "value": { 15 | "type": "boolean" 16 | } 17 | }, 18 | "string": { 19 | "value": { 20 | "type": "string" 21 | } 22 | } 23 | } 24 | }, 25 | "property": "http://example.org/prop" 26 | } 27 | } -------------------------------------------------------------------------------- /layouts/tests/distill/t17-out.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /layouts/tests/distill/t18-in.nq: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spruceid/treeldr/82f86519141c1188fd69a2fd3730388973218207/layouts/tests/distill/t18-in.nq -------------------------------------------------------------------------------- /layouts/tests/distill/t18-layout.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "record", 3 | "fields": { 4 | "evidence": { 5 | "intro": [], 6 | "value": { 7 | "input": ["_:self"], 8 | "layout": { 9 | "type": "set", 10 | "input": ["self"], 11 | "intro": [], 12 | "item": { 13 | "intro": ["value"], 14 | "property": "https://www.w3.org/2018/credentials#evidence", 15 | "value": { 16 | "input": ["_:value"], 17 | "layout": { 18 | "type": "record", 19 | "fields": { 20 | "id": { 21 | "intro": [], 22 | "value": { 23 | "input": "_:self", 24 | "layout": { "type": "string" } 25 | } 26 | }, 27 | "type": { 28 | "intro": [], 29 | "value": { 30 | "input": [ 31 | "_:self" 32 | ], 33 | "layout": { 34 | "type": "unit", 35 | "const": ["Evidence"] 36 | } 37 | } 38 | } 39 | } 40 | } 41 | } 42 | } 43 | } 44 | } 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /layouts/tests/distill/t18-out.json: -------------------------------------------------------------------------------- 1 | { 2 | "evidence": [] 3 | } -------------------------------------------------------------------------------- /src/format/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod rdf; 2 | pub mod tree; 3 | 4 | pub use rdf::RDFFormat; 5 | pub use tree::TreeFormat; 6 | -------------------------------------------------------------------------------- /src/format/rdf.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | use std::io::{self, BufRead, Write}; 3 | 4 | use clap::builder::TypedValueParser; 5 | use locspan::Span; 6 | use nquads_syntax::Parse; 7 | use rdf_types::dataset::BTreeDataset; 8 | use rdf_types::Quad; 9 | 10 | #[derive(Debug, thiserror::Error)] 11 | pub enum LoadError { 12 | #[error("N-Quads parse error: {0}")] 13 | NQuads( 14 | #[from] nquads_syntax::parsing::MetaError, Span>, 15 | ), 16 | } 17 | 18 | #[derive(Debug, Clone)] 19 | pub enum RDFFormat { 20 | NQuads, 21 | } 22 | 23 | impl RDFFormat { 24 | pub const POSSIBLE_VALUES: &'static [&'static str] = 25 | &["application/n-quads", "n-quads", "nquads", "nq"]; 26 | 27 | pub fn parser( 28 | ) -> clap::builder::MapValueParser Self> { 29 | clap::builder::PossibleValuesParser::new(Self::POSSIBLE_VALUES) 30 | .map(|s| Self::new(&s).unwrap()) 31 | } 32 | 33 | pub fn new(name: &str) -> Option { 34 | match name { 35 | "nq" | "nquads" | "n-quads" | "application/n-quads" => Some(Self::NQuads), 36 | _ => None, 37 | } 38 | } 39 | 40 | pub fn as_str(&self) -> &'static str { 41 | match self { 42 | Self::NQuads => "application/n-quads", 43 | } 44 | } 45 | 46 | pub fn load(&self, input: impl BufRead) -> Result { 47 | match self { 48 | Self::NQuads => { 49 | let utf8_input = utf8_decode::UnsafeDecoder::new(input.bytes()); 50 | let document = nquads_syntax::GrdfDocument::parse_utf8(utf8_input) 51 | .map_err(LoadError::NQuads)? 52 | .into_value(); 53 | Ok(document 54 | .into_iter() 55 | .map(|q| nquads_syntax::strip_quad(q.into_value())) 56 | .collect()) 57 | } 58 | } 59 | } 60 | 61 | pub fn write( 62 | &self, 63 | dataset: impl IntoIterator, 64 | mut output: impl Write, 65 | ) -> Result<(), io::Error> { 66 | match self { 67 | Self::NQuads => { 68 | for quad in dataset { 69 | writeln!(output, "{} .", quad)?; 70 | } 71 | 72 | Ok(()) 73 | } 74 | } 75 | } 76 | } 77 | 78 | impl fmt::Display for RDFFormat { 79 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 80 | self.as_str().fmt(f) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/format/tree.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | use std::io::{self, BufRead, Write}; 3 | 4 | use clap::builder::TypedValueParser; 5 | use json_syntax::Print; 6 | use treeldr_layouts::{value::NonJsonValue, LayoutRegistry}; 7 | 8 | #[derive(Debug, thiserror::Error)] 9 | pub enum LoadError { 10 | #[error("JSON parse error: {0}")] 11 | Json(json_syntax::parse::Error), 12 | 13 | #[error("CBOR parse error: {0}")] 14 | Cbor(serde_cbor::Error), 15 | } 16 | 17 | #[derive(Debug, thiserror::Error)] 18 | pub enum WriteError { 19 | #[error(transparent)] 20 | NonJsonValue(NonJsonValue), 21 | 22 | #[error(transparent)] 23 | IO(#[from] io::Error), 24 | 25 | #[error("invalid CBOR tag: {0}")] 26 | CborTag(treeldr_layouts::value::cbor::InvalidTag), 27 | 28 | #[error(transparent)] 29 | Cbor(serde_cbor::Error), 30 | } 31 | 32 | #[derive(Debug, Clone)] 33 | pub enum TreeFormat { 34 | Json, 35 | Cbor, 36 | } 37 | 38 | impl TreeFormat { 39 | pub const POSSIBLE_VALUES: &'static [&'static str] = 40 | &["application/json", "json", "application/cbor", "cbor"]; 41 | 42 | pub fn parser( 43 | ) -> clap::builder::MapValueParser Self> { 44 | clap::builder::PossibleValuesParser::new(Self::POSSIBLE_VALUES) 45 | .map(|s| Self::new(&s).unwrap()) 46 | } 47 | 48 | pub fn new(name: &str) -> Option { 49 | match name { 50 | "application/json" | "json" => Some(Self::Json), 51 | "application/cbor" | "cbor" => Some(Self::Cbor), 52 | _ => None, 53 | } 54 | } 55 | 56 | pub fn as_str(&self) -> &'static str { 57 | match self { 58 | Self::Json => "application/json", 59 | Self::Cbor => "application/cbor", 60 | } 61 | } 62 | 63 | pub fn load(&self, input: impl BufRead) -> Result { 64 | match self { 65 | Self::Json => { 66 | use json_syntax::Parse; 67 | let utf8_input = utf8_decode::UnsafeDecoder::new(input.bytes()); 68 | let (json, _) = 69 | json_syntax::Value::parse_utf8(utf8_input).map_err(LoadError::Json)?; 70 | Ok(json.into()) 71 | } 72 | Self::Cbor => serde_cbor::from_reader(input).map_err(LoadError::Cbor), 73 | } 74 | } 75 | 76 | pub fn write_typed( 77 | &self, 78 | layouts: &impl LayoutRegistry, 79 | value: treeldr_layouts::TypedValue, 80 | pretty: bool, 81 | mut output: impl Write, 82 | ) -> Result<(), WriteError> { 83 | match self { 84 | Self::Json => { 85 | let json: json_syntax::Value = value 86 | .into_untyped() 87 | .try_into() 88 | .map_err(WriteError::NonJsonValue)?; 89 | if pretty { 90 | write!(output, "{}", json.pretty_print()).map_err(WriteError::IO) 91 | } else { 92 | write!(output, "{}", json.compact_print()).map_err(WriteError::IO) 93 | } 94 | } 95 | Self::Cbor => { 96 | let cbor = value 97 | .try_into_tagged_serde_cbor(layouts) 98 | .map_err(WriteError::CborTag)?; 99 | serde_cbor::to_writer(output, &cbor).map_err(WriteError::Cbor) 100 | } 101 | } 102 | } 103 | 104 | pub fn write_untyped( 105 | &self, 106 | value: treeldr_layouts::Value, 107 | pretty: bool, 108 | mut output: impl Write, 109 | ) -> Result<(), WriteError> { 110 | match self { 111 | Self::Json => { 112 | let json: json_syntax::Value = 113 | value.try_into().map_err(WriteError::NonJsonValue)?; 114 | if pretty { 115 | write!(output, "{}", json.pretty_print()).map_err(WriteError::IO) 116 | } else { 117 | write!(output, "{}", json.compact_print()).map_err(WriteError::IO) 118 | } 119 | } 120 | Self::Cbor => serde_cbor::to_writer(output, &value).map_err(WriteError::Cbor), 121 | } 122 | } 123 | } 124 | 125 | impl fmt::Display for TreeFormat { 126 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 127 | self.as_str().fmt(f) 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/rdf.rs: -------------------------------------------------------------------------------- 1 | use iref::Iri; 2 | use rdf_types::{BlankId, Term}; 3 | 4 | #[derive(Debug, thiserror::Error)] 5 | #[error("invalid RDF term `{0}`")] 6 | pub struct InvalidTerm(String); 7 | 8 | pub fn parse_term(input: &str) -> Result { 9 | match BlankId::new(input) { 10 | Ok(blank_id) => Ok(Term::blank(blank_id.to_owned())), 11 | Err(_) => match Iri::new(input) { 12 | Ok(iri) => Ok(Term::iri(iri.to_owned())), 13 | Err(_) => Err(InvalidTerm(input.to_owned())), 14 | }, 15 | } 16 | } 17 | --------------------------------------------------------------------------------