├── .gitattributes ├── packages ├── explorer-views │ ├── src │ │ ├── sections │ │ │ ├── seealso.css │ │ │ ├── validation.css │ │ │ ├── rdf.css │ │ │ ├── turtle.css │ │ │ ├── footer.css │ │ │ ├── rdf-prefixes.css │ │ │ ├── seealso.tsx │ │ │ ├── rdf.tsx │ │ │ ├── rdf-prefixes.tsx │ │ │ ├── schema.css │ │ │ ├── footer.tsx │ │ │ ├── validation.tsx │ │ │ ├── schema-ontology.tsx │ │ │ └── schema-property.tsx │ │ ├── components │ │ │ ├── cardinality.css │ │ │ ├── tabview.tsx │ │ │ ├── listing.tsx │ │ │ ├── treeview.tsx │ │ │ ├── cardinality.tsx │ │ │ ├── listing.css │ │ │ ├── markdown.css │ │ │ ├── tabview.css │ │ │ ├── treeview.css │ │ │ ├── rdf.css │ │ │ ├── rdf-triple.tsx │ │ │ └── markdown.tsx │ │ ├── context.ts │ │ ├── pages │ │ │ ├── navigation.css │ │ │ ├── main.css │ │ │ ├── main.tsx │ │ │ └── navigation.tsx │ │ └── jsx │ │ │ └── html.ts │ ├── tsconfig.json │ └── package.json ├── cli │ ├── src │ │ ├── assets │ │ │ ├── scripts │ │ │ │ └── .gitignore │ │ │ ├── explorer │ │ │ │ └── .gitignore │ │ │ ├── fonts │ │ │ │ ├── iosevka-aile-custom-light.woff2 │ │ │ │ └── iosevka-aile-custom.css │ │ │ └── themes │ │ │ │ └── espresso.css │ │ ├── assets.d.ts │ │ ├── diagnostics.ts │ │ ├── commands │ │ │ ├── init.ts │ │ │ ├── remove-prefix.ts │ │ │ ├── remove-file.ts │ │ │ ├── add-prefix.ts │ │ │ ├── list-files.ts │ │ │ ├── list-prefixes.ts │ │ │ ├── serve.ts │ │ │ ├── list-terms.ts │ │ │ ├── list-imports.ts │ │ │ ├── list-dependencies.ts │ │ │ ├── add-file.ts │ │ │ └── make-explorer.tsx │ │ ├── type-checks.ts │ │ ├── prefixes.ts │ │ ├── main.css │ │ ├── model │ │ │ ├── ontology.ts │ │ │ ├── textfile.ts │ │ │ └── package.ts │ │ ├── workspace.ts │ │ └── options.ts │ ├── tsconfig.json │ └── package.json ├── explorer │ ├── src │ │ ├── worker │ │ │ └── .gitignore │ │ ├── fonts │ │ │ ├── iosevka-aile-custom-light.woff2 │ │ │ └── iosevka-aile-custom.css │ │ ├── assets.d.ts │ │ ├── themes │ │ │ └── espresso.css │ │ └── main.css │ ├── tsconfig.json │ └── package.json ├── rdf │ ├── tsconfig.json │ ├── src │ │ ├── terms.ts │ │ ├── graphs │ │ │ ├── simple.ts │ │ │ └── indexed.ts │ │ ├── terms │ │ │ ├── blanknode.ts │ │ │ └── iri.ts │ │ ├── engines │ │ │ ├── shacl.ts │ │ │ ├── rdf.ts │ │ │ └── xsd.ts │ │ ├── triples.ts │ │ └── graphs.ts │ └── package.json ├── text │ ├── src │ │ ├── main.ts │ │ ├── documents.ts │ │ ├── diagnostics.ts │ │ └── irireferences.ts │ ├── tsconfig.json │ └── package.json ├── iterable │ ├── tsconfig.json │ └── package.json ├── schema │ ├── tsconfig.json │ ├── src │ │ ├── decompiler.ts │ │ ├── main.ts │ │ ├── decompiler │ │ │ ├── owl.ts │ │ │ ├── rdfs.ts │ │ │ └── shacl.ts │ │ └── validator.ts │ └── package.json ├── turtle │ ├── tsconfig.json │ ├── src │ │ ├── main.ts │ │ ├── syntax.ts │ │ ├── syntax │ │ │ ├── trivia.ts │ │ │ └── token.ts │ │ ├── literate.ts │ │ └── syntax-tree.ts │ └── package.json ├── explorer-site │ ├── tsconfig.json │ ├── src │ │ └── main.ts │ └── package.json ├── explorer-shared │ ├── tsconfig.json │ ├── package.json │ └── src │ │ └── shared.ts ├── explorer-worker │ ├── tsconfig.json │ ├── package.json │ └── src │ │ ├── home.tsx │ │ └── prefixes.ts └── tsconfig-base.json ├── explorer ├── robots.txt ├── favicon.ico ├── favicon16.png ├── favicon32.png ├── rdfconfig.json ├── package.json └── vocab │ └── rdfs.ttl ├── .gitignore ├── .editorconfig ├── package.json ├── .eslintrc.js ├── .github └── workflows │ └── ci.yaml ├── LICENSE └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.ttl eol=lf 3 | -------------------------------------------------------------------------------- /packages/explorer-views/src/sections/seealso.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /explorer/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: / 3 | -------------------------------------------------------------------------------- /packages/cli/src/assets/scripts/.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | -------------------------------------------------------------------------------- /packages/explorer-views/src/components/cardinality.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/explorer/src/worker/.gitignore: -------------------------------------------------------------------------------- 1 | *.css 2 | *.js 3 | -------------------------------------------------------------------------------- /packages/cli/src/assets/explorer/.gitignore: -------------------------------------------------------------------------------- 1 | *.css 2 | *.js 3 | *.woff2 4 | -------------------------------------------------------------------------------- /explorer/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ektrah/rdf-toolkit/HEAD/explorer/favicon.ico -------------------------------------------------------------------------------- /explorer/favicon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ektrah/rdf-toolkit/HEAD/explorer/favicon16.png -------------------------------------------------------------------------------- /explorer/favicon32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ektrah/rdf-toolkit/HEAD/explorer/favicon32.png -------------------------------------------------------------------------------- /packages/explorer-views/src/sections/validation.css: -------------------------------------------------------------------------------- 1 | .validation-error, 2 | .validation-warning { 3 | color: var(--base08); 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/dist 2 | *.user 3 | *~ 4 | ._* 5 | .DS_Store 6 | .vs 7 | [Bb]in 8 | [Oo]bj 9 | explorer/public 10 | node_modules 11 | -------------------------------------------------------------------------------- /packages/rdf/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig-base.json", 3 | "compilerOptions": { 4 | "outDir": "./dist" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/text/src/main.ts: -------------------------------------------------------------------------------- 1 | export * from "./diagnostics.js"; 2 | export * from "./documents.js"; 3 | export * from "./irireferences.js"; 4 | -------------------------------------------------------------------------------- /packages/text/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig-base.json", 3 | "compilerOptions": { 4 | "outDir": "./dist" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/explorer-views/src/sections/rdf.css: -------------------------------------------------------------------------------- 1 | .rdf-triples .rdf-blanknode, 2 | .rdf-triples .rdf-collection { 3 | display: inline-grid; 4 | } 5 | -------------------------------------------------------------------------------- /packages/iterable/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig-base.json", 3 | "compilerOptions": { 4 | "outDir": "./dist" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/schema/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig-base.json", 3 | "compilerOptions": { 4 | "outDir": "./dist" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/turtle/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig-base.json", 3 | "compilerOptions": { 4 | "outDir": "./dist" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/explorer/src/fonts/iosevka-aile-custom-light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ektrah/rdf-toolkit/HEAD/packages/explorer/src/fonts/iosevka-aile-custom-light.woff2 -------------------------------------------------------------------------------- /packages/cli/src/assets/fonts/iosevka-aile-custom-light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ektrah/rdf-toolkit/HEAD/packages/cli/src/assets/fonts/iosevka-aile-custom-light.woff2 -------------------------------------------------------------------------------- /packages/turtle/src/main.ts: -------------------------------------------------------------------------------- 1 | export * from "./syntax.js"; 2 | export * from "./syntax-tree.js"; 3 | export * from "./syntax-visitor.js"; 4 | export * from "./symbol-table.js"; 5 | -------------------------------------------------------------------------------- /packages/turtle/src/syntax.ts: -------------------------------------------------------------------------------- 1 | export * from "./syntax/trivia.js"; 2 | export * from "./syntax/value.js"; 3 | export * from "./syntax/token.js"; 4 | export * from "./syntax/node.js"; 5 | -------------------------------------------------------------------------------- /packages/explorer-site/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig-base.json", 3 | "compilerOptions": { 4 | "target": "ES2020", 5 | "lib": [ "ES2020", "DOM" ], 6 | 7 | "outDir": "./dist" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/explorer-shared/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig-base.json", 3 | "compilerOptions": { 4 | "target": "ES2020", 5 | "lib": [ "ES2020", "WebWorker" ], 6 | 7 | "outDir": "./dist" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/text/src/documents.ts: -------------------------------------------------------------------------------- 1 | import { TextDocument } from "vscode-languageserver-textdocument"; 2 | import { DocumentUri, Location, Position, Range } from "vscode-languageserver-types"; 3 | 4 | export { DocumentUri, Location, TextDocument, Range, Position }; 5 | -------------------------------------------------------------------------------- /packages/explorer/src/fonts/iosevka-aile-custom.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Iosevka Aile Custom'; 3 | font-display: block; 4 | font-weight: 300; 5 | font-stretch: normal; 6 | font-style: normal; 7 | src: url('./iosevka-aile-custom-light.woff2') format('woff2'); 8 | } 9 | -------------------------------------------------------------------------------- /packages/cli/src/assets/fonts/iosevka-aile-custom.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Iosevka Aile Custom'; 3 | font-display: block; 4 | font-weight: 300; 5 | font-stretch: normal; 6 | font-style: normal; 7 | src: url('./iosevka-aile-custom-light.woff2') format('woff2'); 8 | } 9 | -------------------------------------------------------------------------------- /packages/cli/src/assets.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.min.js" { 2 | const href: string; 3 | export default href; 4 | } 5 | 6 | declare module "*.min.css" { 7 | const href: string; 8 | export default href; 9 | } 10 | 11 | declare module "*.woff2" { 12 | const href: string; 13 | export default href; 14 | } 15 | -------------------------------------------------------------------------------- /packages/explorer/src/assets.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.min.js" { 2 | const href: string; 3 | export default href; 4 | } 5 | 6 | declare module "*.min.css" { 7 | const href: string; 8 | export default href; 9 | } 10 | 11 | declare module "*.woff2" { 12 | const href: string; 13 | export default href; 14 | } 15 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.{ts,tsx}] 4 | charset = utf-8 5 | indent_size = 4 6 | indent_style = space 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | [*.json] 11 | charset = utf-8 12 | indent_size = 2 13 | indent_style = space 14 | insert_final_newline = true 15 | trim_trailing_whitespace = true 16 | -------------------------------------------------------------------------------- /packages/explorer/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig-base.json", 3 | "compilerOptions": { 4 | "target": "ES2020", 5 | "lib": [ "ES2020", "DOM" ], 6 | 7 | "baseUrl": "./src", 8 | "paths": { 9 | "@rdf-toolkit/explorer-shared": [ "../../explorer-shared/src/shared.js" ] 10 | }, 11 | 12 | "outDir": "./dist" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/explorer-views/src/sections/turtle.css: -------------------------------------------------------------------------------- 1 | .turtle-keyword { 2 | color: var(--base07); 3 | } 4 | 5 | .turtle-langtag { 6 | color: var(--base04); 7 | } 8 | 9 | .turtle-comment { 10 | color: var(--base04); 11 | } 12 | 13 | .turtle-punctuator { 14 | color: var(--base05); 15 | } 16 | 17 | .turtle-error { 18 | border-bottom: 2px dotted var(--base08); 19 | } 20 | -------------------------------------------------------------------------------- /packages/explorer-views/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig-base.json", 3 | "compilerOptions": { 4 | "jsx": "react-jsx", 5 | "jsxImportSource": "@rdf-toolkit/explorer-views/jsx", 6 | 7 | "baseUrl": "./src", 8 | "paths": { 9 | "@rdf-toolkit/explorer-views/*": [ "../../explorer-views/src/*.js" ] 10 | }, 11 | 12 | "outDir": "./dist" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/explorer-views/src/sections/footer.css: -------------------------------------------------------------------------------- 1 | footer { 2 | padding: 2.5rem 0; 3 | } 4 | 5 | footer summary { 6 | color: var(--base02); 7 | list-style-position: outside; 8 | margin-left: 1rem; 9 | } 10 | 11 | footer summary:active, 12 | footer summary:hover { 13 | color: var(--base06); 14 | text-decoration: underline; 15 | } 16 | -------------------------------------------------------------------------------- /packages/cli/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig-base.json", 3 | "compilerOptions": { 4 | "target": "ES2020", 5 | "lib": [ "ES2020", "WebWorker" ], 6 | "jsx": "react-jsx", 7 | "jsxImportSource": "@rdf-toolkit/explorer-views/jsx", 8 | 9 | "baseUrl": "./src", 10 | "paths": { 11 | "@rdf-toolkit/explorer-views/*": [ "../../explorer-views/src/*.js" ] 12 | }, 13 | 14 | "outDir": "./dist" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/explorer-shared/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "@rdf-toolkit/explorer-shared", 4 | "version": "0.1.0", 5 | "type": "module", 6 | "scripts": { 7 | "lint": "npx eslint src/", 8 | "build": "tsc --noEmit", 9 | "clean": "" 10 | }, 11 | "devDependencies": { 12 | "@typescript-eslint/eslint-plugin": "^8.31.0", 13 | "@typescript-eslint/parser": "^8.31.0", 14 | "eslint": "^9.25.1", 15 | "typescript": "^5.8.3" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /explorer/rdfconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "siteOptions": { 3 | "title": "Explorer", 4 | "icons": [ 5 | { 6 | "type": "image/png", 7 | "sizes": "32x32", 8 | "asset": "favicon32.png" 9 | }, 10 | { 11 | "type": "image/png", 12 | "sizes": "16x16", 13 | "asset": "favicon16.png" 14 | } 15 | ], 16 | "assets": { 17 | "favicon.ico": "favicon.ico", 18 | "robots.txt": "robots.txt" 19 | }, 20 | "outDir": "public", 21 | "cleanUrls": true 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/explorer-worker/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig-base.json", 3 | "compilerOptions": { 4 | "target": "ES2020", 5 | "lib": [ "ES2020", "WebWorker" ], 6 | "jsx": "react-jsx", 7 | "jsxImportSource": "@rdf-toolkit/explorer-views/jsx", 8 | 9 | "baseUrl": "./src", 10 | "paths": { 11 | "@rdf-toolkit/explorer-shared": [ "../../explorer-shared/src/shared.js" ], 12 | "@rdf-toolkit/explorer-views/*": [ "../../explorer-views/src/*.js" ] 13 | }, 14 | 15 | "outDir": "./dist" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/rdf/src/terms.ts: -------------------------------------------------------------------------------- 1 | import { BlankNode } from "./terms/blanknode.js"; 2 | import { IRI } from "./terms/iri.js"; 3 | import { Literal } from "./terms/literal.js"; 4 | 5 | export { BlankNode, IRI, Literal }; 6 | 7 | export type IRIOrBlankNode = 8 | | IRI 9 | | BlankNode 10 | 11 | export type Term = 12 | | IRI 13 | | BlankNode 14 | | Literal 15 | 16 | export namespace IRIOrBlankNode { 17 | 18 | export function is(term: Term): term is IRI | BlankNode { 19 | return term.termType === "NamedNode" || term.termType === "BlankNode"; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/explorer/src/themes/espresso.css: -------------------------------------------------------------------------------- 1 | /* https://github.com/alexmirrington/base16-espresso-scheme */ 2 | 3 | :root { 4 | --base00: #2d2d2d; 5 | --base01: #393939; 6 | --base02: #515151; 7 | --base03: #777777; 8 | --base04: #b4b7b4; 9 | --base05: #cccccc; 10 | --base06: #e0e0e0; 11 | --base07: #ffffff; 12 | --base08: #d25252; 13 | --base09: #f9a959; 14 | --base0A: #ffc66d; 15 | --base0B: #a5c261; 16 | --base0C: #bed6ff; 17 | --base0D: #6c99bb; 18 | --base0E: #d197d9; 19 | --base0F: #f97394; 20 | color-scheme: dark; 21 | } 22 | -------------------------------------------------------------------------------- /packages/cli/src/assets/themes/espresso.css: -------------------------------------------------------------------------------- 1 | /* https://github.com/alexmirrington/base16-espresso-scheme */ 2 | 3 | :root { 4 | --base00: #2d2d2d; 5 | --base01: #393939; 6 | --base02: #515151; 7 | --base03: #777777; 8 | --base04: #b4b7b4; 9 | --base05: #cccccc; 10 | --base06: #e0e0e0; 11 | --base07: #ffffff; 12 | --base08: #d25252; 13 | --base09: #f9a959; 14 | --base0A: #ffc66d; 15 | --base0B: #a5c261; 16 | --base0C: #bed6ff; 17 | --base0D: #6c99bb; 18 | --base0E: #d197d9; 19 | --base0F: #f97394; 20 | color-scheme: dark; 21 | } 22 | -------------------------------------------------------------------------------- /packages/explorer-site/src/main.ts: -------------------------------------------------------------------------------- 1 | window.onclick = function (ev) { 2 | let target = ev.target; 3 | while (target instanceof Element) { 4 | if (target.tagName === "A") { 5 | const href = target.getAttribute("data-href"); 6 | const rel = target.getAttribute("rel"); 7 | if (!rel) { 8 | ev.preventDefault(); 9 | if (href) { 10 | window.location.href = href; 11 | } 12 | } 13 | break; 14 | } 15 | target = target.parentElement; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "rdf-toolkit", 4 | "workspaces": [ 5 | "packages/iterable", 6 | "packages/text", 7 | "packages/rdf", 8 | "packages/turtle", 9 | "packages/schema", 10 | "packages/explorer-shared", 11 | "packages/explorer-views", 12 | "packages/explorer-worker", 13 | "packages/explorer", 14 | "packages/explorer-site", 15 | "packages/cli" 16 | ], 17 | "scripts": { 18 | "lint": "npm run lint --ws --if-present", 19 | "build": "npm run build --ws", 20 | "clean": "npm run clean --ws", 21 | "test": "npm test --ws --if-present" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/explorer-views/src/context.ts: -------------------------------------------------------------------------------- 1 | import { Graph } from "@rdf-toolkit/rdf/graphs"; 2 | import { Schema } from "@rdf-toolkit/schema"; 3 | import { TextDocument } from "@rdf-toolkit/text"; 4 | 5 | export interface RenderContext { 6 | readonly documents: Record; 7 | readonly graph: Graph; 8 | readonly schema: Schema; 9 | readonly rootClasses: ReadonlySet | null; 10 | 11 | getPrefixes(): ReadonlyArray<[string, string]>; 12 | lookupPrefixedName(iri: string): { readonly prefixLabel: string, readonly localName: string } | null; 13 | 14 | rewriteHref?(iri: string): string; 15 | rewriteHrefAsData?(iri: string): string | undefined; 16 | } 17 | -------------------------------------------------------------------------------- /packages/tsconfig-base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "lib": [ "ES2020" ], 5 | 6 | "module": "ES2020", 7 | "moduleResolution": "Bundler", 8 | 9 | "declaration": true, 10 | "declarationMap": true, 11 | "sourceMap": true, 12 | "removeComments": true, 13 | 14 | "isolatedModules": true, 15 | "forceConsistentCasingInFileNames": true, 16 | 17 | "strict": true, 18 | "noImplicitAny": true, 19 | "strictNullChecks": true, 20 | "strictFunctionTypes": true, 21 | "noImplicitThis": true, 22 | "noImplicitReturns": true, 23 | "noFallthroughCasesInSwitch": true, 24 | "noImplicitOverride": true 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/schema/src/decompiler.ts: -------------------------------------------------------------------------------- 1 | import { Graph } from "@rdf-toolkit/rdf/graphs"; 2 | import { Triple } from "@rdf-toolkit/rdf/triples"; 3 | import decompileOwl from "./decompiler/owl.js"; 4 | import decompileRdfs from "./decompiler/rdfs.js"; 5 | import decompileShacl from "./decompiler/shacl.js"; 6 | import { SchemaBuilder } from "./decompiler/utils.js"; 7 | import { Schema } from "./main.js"; 8 | 9 | export default function decompile(dataset: Iterable>, graph: Graph): Schema { 10 | const builder = new SchemaBuilder(); 11 | decompileOwl(graph, builder); 12 | decompileShacl(graph, builder); 13 | decompileRdfs(graph, builder); 14 | return builder.toSchema(dataset, graph); 15 | } 16 | -------------------------------------------------------------------------------- /packages/explorer-views/src/sections/rdf-prefixes.css: -------------------------------------------------------------------------------- 1 | table.rdf-prefixes { 2 | margin: 1em 0; 3 | width: 100%; 4 | } 5 | 6 | table.rdf-prefixes th, 7 | table.rdf-prefixes td { 8 | border-bottom: 1px solid var(--base01); 9 | padding: 5px 2ex 5px 0; 10 | } 11 | 12 | table.rdf-prefixes th:last-child, 13 | table.rdf-prefixes td:last-child { 14 | padding-right: 0; 15 | width: 100%; 16 | } 17 | 18 | .rdf-prefixes-prefixlabel { 19 | background: var(--base02); 20 | border-radius: 4px; 21 | color: var(--base05); 22 | font-size: small; 23 | letter-spacing: initial; 24 | padding: 3px 4px 2px; 25 | vertical-align: text-top; 26 | } 27 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"], 3 | parser: "@typescript-eslint/parser", 4 | plugins: ["@typescript-eslint"], 5 | root: true, 6 | rules: { 7 | "@typescript-eslint/no-empty-function": "off", 8 | "@typescript-eslint/no-empty-interface": "off", 9 | "@typescript-eslint/no-explicit-any": "off", 10 | "@typescript-eslint/no-namespace": "off", 11 | "@typescript-eslint/no-unused-vars": "off", 12 | "no-case-declarations": "off", 13 | "no-control-regex": "off", 14 | "no-empty": ["error", { "allowEmptyCatch": true }], 15 | "no-fallthrough": "off", 16 | "no-misleading-character-class": "warn", 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /packages/explorer-views/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "@rdf-toolkit/explorer-views", 4 | "version": "0.1.0", 5 | "type": "module", 6 | "scripts": { 7 | "lint": "npx eslint src/", 8 | "build": "tsc --noEmit", 9 | "clean": "" 10 | }, 11 | "dependencies": { 12 | "@rdf-toolkit/iterable": "0.1.0", 13 | "@rdf-toolkit/rdf": "0.1.0", 14 | "@rdf-toolkit/schema": "0.1.0", 15 | "@rdf-toolkit/text": "0.1.0", 16 | "@rdf-toolkit/turtle": "0.1.0", 17 | "commonmark": "^0.31.2" 18 | }, 19 | "devDependencies": { 20 | "@types/commonmark": "^0.27.9", 21 | "@typescript-eslint/eslint-plugin": "^8.31.0", 22 | "@typescript-eslint/parser": "^8.31.0", 23 | "eslint": "^9.25.1", 24 | "typescript": "^5.8.3" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/explorer-site/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "@rdf-toolkit/explorer-site", 4 | "version": "0.1.0", 5 | "type": "module", 6 | "files": [ 7 | "./dist/**/*" 8 | ], 9 | "main": "./dist/main.js", 10 | "scripts": { 11 | "lint": "npx eslint src/", 12 | "prebuild": "tsc --noEmit", 13 | "build": "esbuild src/main.ts --bundle --minify --platform=browser --target=es2020 --outfile=../cli/src/assets/scripts/site.min.js", 14 | "clean": "rimraf -g \"../cli/src/assets/scripts/*.js\"" 15 | }, 16 | "devDependencies": { 17 | "@typescript-eslint/eslint-plugin": "^8.31.0", 18 | "@typescript-eslint/parser": "^8.31.0", 19 | "esbuild": "^0.25.3", 20 | "eslint": "^9.25.1", 21 | "rimraf": "^6.0.1", 22 | "typescript": "^5.8.3" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/cli/src/diagnostics.ts: -------------------------------------------------------------------------------- 1 | import { DiagnosticBag } from "@rdf-toolkit/text"; 2 | import * as process from "node:process"; 3 | import { DiagnosticOptions } from "./options.js"; 4 | 5 | export function hasErrors(diagnostics: DiagnosticBag, options: DiagnosticOptions): boolean { 6 | return !!(options.warnAsError ? diagnostics.errors + diagnostics.warnings : diagnostics.errors); 7 | } 8 | 9 | export function printDiagnosticsAndExitOnError(diagnostics: DiagnosticBag, options: DiagnosticOptions): void { 10 | for (const [documentURI, diagnostic] of diagnostics) { 11 | console.dir({ documentURI, diagnostic }, { depth: undefined }); 12 | } 13 | if (hasErrors(diagnostics, options)) { 14 | console.log(`${diagnostics.errors} error(s), ${diagnostics.warnings} warning(s)`); 15 | process.exit(1); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/explorer-views/src/components/tabview.tsx: -------------------------------------------------------------------------------- 1 | import { Ix } from "@rdf-toolkit/iterable"; 2 | import { HtmlContent } from "../jsx/html.js"; 3 | import "./tabview.css"; 4 | 5 | export interface TabViewPage { 6 | readonly id: string; 7 | readonly label: string; 8 | readonly content: HtmlContent; 9 | } 10 | 11 | export default function render(name: string, pages: TabViewPage[]): HtmlContent { 12 | return
13 | {Ix.from(pages).map((page, index) => )} 14 |
15 | {Ix.from(pages).map(page => )} 16 |
17 |
18 | {Ix.from(pages).map(page => page.content)} 19 |
20 |
; 21 | } 22 | -------------------------------------------------------------------------------- /explorer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "prebuild": "rimraf -g \"./public/**/*\"", 5 | "build": "npx rdf make explorer", 6 | "clean": "rimraf -g \"./public/**/*\"", 7 | "serve": "npx rdf serve" 8 | }, 9 | "devDependencies": { 10 | "@rdf-toolkit/cli": "0.1.0", 11 | "rimraf": "^6.0.1" 12 | }, 13 | "rdf:files": { 14 | "http://www.w3.org/1999/02/22-rdf-syntax-ns": "vocab/rdf.ttl", 15 | "http://www.w3.org/2000/01/rdf-schema": "vocab/rdfs.ttl", 16 | "http://www.w3.org/2002/07/owl": "vocab/owl.ttl", 17 | "http://www.w3.org/ns/shacl": "vocab/shacl.ttl" 18 | }, 19 | "rdf:prefixes": { 20 | "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", 21 | "rdfs": "http://www.w3.org/2000/01/rdf-schema#", 22 | "owl": "http://www.w3.org/2002/07/owl#", 23 | "sh:": "http://www.w3.org/ns/shacl#" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/cli/src/commands/init.ts: -------------------------------------------------------------------------------- 1 | import * as path from "node:path"; 2 | import { PackageConfig, PACKAGE_JSON } from "../model/package.js"; 3 | import { Project } from "../model/project.js"; 4 | import { ForceOptions, ProjectOptions } from "../options.js"; 5 | 6 | type Options = 7 | & ForceOptions 8 | & ProjectOptions 9 | 10 | export default function main(options: Options): void { 11 | const project = new Project(options.project); 12 | 13 | // https://docs.npmjs.com/creating-a-package-json-file#default-values-extracted-from-the-current-directory 14 | const json: PackageConfig = { 15 | name: path.basename(options.project), 16 | version: "1.0.0", 17 | description: "", 18 | keywords: [], 19 | author: "", 20 | license: "ISC" 21 | }; 22 | 23 | project.package.writeJSON(PACKAGE_JSON, json, !options.force); 24 | } 25 | -------------------------------------------------------------------------------- /packages/cli/src/commands/remove-prefix.ts: -------------------------------------------------------------------------------- 1 | import * as os from "node:os"; 2 | import * as process from "node:process"; 3 | import { PACKAGE_JSON } from "../model/package.js"; 4 | import { Project } from "../model/project.js"; 5 | import { ProjectOptions } from "../options.js"; 6 | import { Is } from "../type-checks.js"; 7 | 8 | type Options = 9 | & ProjectOptions 10 | 11 | export default function main(prefixLabel: string, options: Options): void { 12 | const project = new Project(options.project); 13 | const json = project.package.json; 14 | 15 | if (Is.record(json["rdf:prefixes"], Is.string) && (prefixLabel in json["rdf:prefixes"])) { 16 | delete json["rdf:prefixes"][prefixLabel]; 17 | project.package.writeJSON(PACKAGE_JSON, json, false); 18 | process.stdout.write(prefixLabel); 19 | process.stdout.write(": \u00D7"); 20 | process.stdout.write(os.EOL); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/explorer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "@rdf-toolkit/explorer", 4 | "version": "0.1.0", 5 | "type": "module", 6 | "files": [ 7 | "./dist/**/*" 8 | ], 9 | "main": "./dist/explorer.js", 10 | "eslintIgnore": [ 11 | "*.min.js" 12 | ], 13 | "scripts": { 14 | "lint": "npx eslint src/", 15 | "prebuild": "tsc --noEmit", 16 | "build": "esbuild src/main.ts --bundle --minify --platform=browser --target=es2020 --asset-names=[name] --loader:.min.js=file --loader:.woff2=file --outfile=../cli/src/assets/explorer/explorer.min.js", 17 | "clean": "rimraf -g \"../cli/src/assets/explorer/*.{js,css,woff2}\"" 18 | }, 19 | "devDependencies": { 20 | "@typescript-eslint/eslint-plugin": "^8.31.0", 21 | "@typescript-eslint/parser": "^8.31.0", 22 | "esbuild": "^0.25.3", 23 | "eslint": "^9.25.1", 24 | "rimraf": "^6.0.1", 25 | "typescript": "^5.8.3" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/explorer-views/src/sections/seealso.tsx: -------------------------------------------------------------------------------- 1 | import { IRIOrBlankNode } from "@rdf-toolkit/rdf/terms"; 2 | import { Rdfs } from "@rdf-toolkit/rdf/vocab"; 3 | import renderRdfTerm from "../components/rdf-term.js"; 4 | import { RenderContext } from "../context.js"; 5 | import { HtmlContent } from "../jsx/html.js"; 6 | import "./seealso.css"; 7 | 8 | export default function render(subject: IRIOrBlankNode, context: RenderContext): HtmlContent { 9 | return context.graph.getSubProperties(Rdfs.seeAlso) 10 | .concatMap(p => context.graph.objects(subject, p)) 11 | .distinct() 12 | .sort((a, b) => a.compareTo(b)) 13 | .wrap(items =>
14 |

See Also

15 |
    16 | {items.map(item =>
  • {renderRdfTerm(item, context, { rawIRIs: true, rawBlankNodes: true, rawLiterals: true, anyURIAsLink: true })}
  • )} 17 |
18 |
, <>); 19 | } 20 | -------------------------------------------------------------------------------- /packages/explorer-views/src/pages/navigation.css: -------------------------------------------------------------------------------- 1 | nav { 2 | cursor: default; 3 | display: grid; 4 | grid-gap: 1em; 5 | grid-template-rows: min-content minmax(0, 1fr); 6 | height: 100vh; 7 | left: 0; 8 | padding: 1.5em 2px 1em 1em; 9 | position: fixed; 10 | top: 0; 11 | width: 25vw; 12 | } 13 | 14 | nav p { 15 | font-size: larger; 16 | margin: 0; 17 | } 18 | 19 | nav .tabview { 20 | display: grid; 21 | grid-template-rows: min-content minmax(0, 1fr); 22 | margin: 0; 23 | } 24 | 25 | nav .tabview-pages { 26 | background: var(--base01); 27 | border: 1px solid var(--base02); 28 | padding: 1.5rem .5rem 5rem; 29 | overflow: clip auto; 30 | overscroll-behavior: none; 31 | } 32 | 33 | nav ul { 34 | margin: 0; 35 | padding: 0; 36 | } 37 | 38 | nav .logo a { 39 | color: var(--base07); 40 | text-decoration: none; 41 | } 42 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: Build and Deploy 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | 7 | build: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v4 11 | - uses: actions/setup-node@v4 12 | with: 13 | node-version: 22 14 | - run: npm ci 15 | - run: npm run build 16 | - run: npm test 17 | - run: npm i @rdf-toolkit/cli 18 | - run: npx rdf make explorer 19 | working-directory: explorer 20 | - id: deployment 21 | uses: actions/upload-pages-artifact@v3 22 | with: 23 | path: ./explorer/public 24 | 25 | deploy: 26 | if: github.ref == 'refs/heads/main' 27 | needs: build 28 | permissions: 29 | pages: write 30 | id-token: write 31 | environment: 32 | name: github-pages 33 | url: ${{ steps.deployment.outputs.page_url }} 34 | runs-on: ubuntu-latest 35 | steps: 36 | - id: deployment 37 | uses: actions/deploy-pages@v4 38 | -------------------------------------------------------------------------------- /packages/cli/src/commands/remove-file.ts: -------------------------------------------------------------------------------- 1 | import { DocumentUri } from "@rdf-toolkit/text"; 2 | import * as os from "node:os"; 3 | import * as process from "node:process"; 4 | import { PACKAGE_JSON } from "../model/package.js"; 5 | import { Project } from "../model/project.js"; 6 | import { ProjectOptions } from "../options.js"; 7 | import { Is } from "../type-checks.js"; 8 | 9 | type Options = 10 | & ProjectOptions 11 | 12 | export default function main(documentURI: DocumentUri, options: Options): void { 13 | const project = new Project(options.project); 14 | const json = project.package.json; 15 | 16 | if (Is.record(json["rdf:files"], Is.string) && (documentURI in json["rdf:files"])) { 17 | delete json["rdf:files"][documentURI]; 18 | project.package.writeJSON(PACKAGE_JSON, json, false); 19 | process.stdout.write("<"); 20 | process.stdout.write(documentURI); 21 | process.stdout.write("> \u00D7"); 22 | process.stdout.write(os.EOL); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/explorer-views/src/sections/rdf.tsx: -------------------------------------------------------------------------------- 1 | import { IRIOrBlankNode } from "@rdf-toolkit/rdf/terms"; 2 | import renderRdfTerm from "../components/rdf-term.js"; 3 | import renderRdfTriple from "../components/rdf-triple.js"; 4 | import { RenderContext } from "../context.js"; 5 | import { HtmlContent } from "../jsx/html.js"; 6 | import "./rdf.css"; 7 | 8 | export default function render(subject: IRIOrBlankNode, context: RenderContext): HtmlContent { 9 | return context.graph.triples(subject) 10 | .map(triple => 11 |
  • 12 | {renderRdfTriple(triple, context)} 13 |
  • ) 14 | .wrap(content => 15 |
    16 | RDF data 17 |

    RDF

    18 |

    19 | {renderRdfTerm(subject, context, { rawBlankNodes: true, rawLiterals: true })} 20 |

    21 |
      22 | {content} 23 |
    24 |
    , <>); 25 | } 26 | -------------------------------------------------------------------------------- /packages/explorer-views/src/components/listing.tsx: -------------------------------------------------------------------------------- 1 | import { Ix } from "@rdf-toolkit/iterable"; 2 | import { HtmlContent, HtmlElement } from "../jsx/html.js"; 3 | import "./listing.css"; 4 | 5 | export function renderLines(lines: HtmlContent[]): HtmlElement { 6 | if (lines.length > 0) { 7 | const lastLine = lines[lines.length - 1]; 8 | if (lastLine === "" || Array.isArray(lastLine) && lastLine.length === 0) { 9 | lines.pop(); 10 | } 11 | else { 12 | lines.push(No newline at end of file); 13 | } 14 | } 15 | 16 | return
    17 | 18 | 19 | {Ix.from(lines).map((line, index) => )} 20 | 21 |
    {line}
    22 |
    ; 23 | } 24 | 25 | export function renderPreformatted(content: HtmlContent): HtmlContent { 26 | return
    27 |
    {content}
    28 |
    ; 29 | } 30 | -------------------------------------------------------------------------------- /packages/iterable/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@rdf-toolkit/iterable", 3 | "version": "0.1.0", 4 | "description": "", 5 | "homepage": "https://github.com/ektrah/rdf-toolkit#readme", 6 | "bugs": { 7 | "url": "https://github.com/ektrah/rdf-toolkit/issues" 8 | }, 9 | "license": "MIT", 10 | "author": "Klaus Hartke", 11 | "type": "module", 12 | "files": [ 13 | "./dist/**/*" 14 | ], 15 | "exports": { 16 | ".": { 17 | "import": { 18 | "types": "./dist/main.d.ts", 19 | "default": "./dist/main.js" 20 | } 21 | } 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/ektrah/rdf-toolkit.git" 26 | }, 27 | "scripts": { 28 | "lint": "npx eslint src/", 29 | "build": "tsc --build", 30 | "clean": "rimraf -g \"dist/**/*.{js,d.ts,map}\"" 31 | }, 32 | "devDependencies": { 33 | "@typescript-eslint/eslint-plugin": "^8.31.0", 34 | "@typescript-eslint/parser": "^8.31.0", 35 | "eslint": "^9.25.1", 36 | "rimraf": "^6.0.1", 37 | "typescript": "^5.8.3" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/cli/src/commands/add-prefix.ts: -------------------------------------------------------------------------------- 1 | import * as os from "node:os"; 2 | import * as process from "node:process"; 3 | import { PACKAGE_JSON } from "../model/package.js"; 4 | import { Project } from "../model/project.js"; 5 | import { ProjectOptions } from "../options.js"; 6 | import { Is } from "../type-checks.js"; 7 | 8 | type Options = 9 | & ProjectOptions 10 | 11 | export default function main(prefixLabel: string, namespaceIRI: string, options: Options): void { 12 | const project = new Project(options.project); 13 | const json = project.package.json; 14 | 15 | const prefixes = Is.record(json["rdf:prefixes"], Is.string) ? json["rdf:prefixes"] : {}; 16 | if (prefixes[prefixLabel] !== namespaceIRI) { 17 | prefixes[prefixLabel] = namespaceIRI; 18 | json["rdf:prefixes"] = prefixes; 19 | project.package.writeJSON(PACKAGE_JSON, json, false); 20 | 21 | process.stdout.write(prefixLabel); 22 | process.stdout.write(": <"); 23 | process.stdout.write(namespaceIRI); 24 | process.stdout.write(">"); 25 | process.stdout.write(os.EOL); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/explorer-views/src/components/treeview.tsx: -------------------------------------------------------------------------------- 1 | import { Ix } from "@rdf-toolkit/iterable"; 2 | import { HtmlContent } from "../jsx/html.js"; 3 | import "./treeview.css"; 4 | 5 | export interface TreeNode { 6 | readonly label: HtmlContent; 7 | readonly children?: Iterable; 8 | readonly open?: boolean; 9 | } 10 | 11 | function renderNode(node: TreeNode, depth: number): HtmlContent { 12 | return depth > 9 ?
  • {"\u2026"}
  • : Ix.from(node.children) 13 | .map(child => renderNode(child, node.open ? 1 : depth + 1)) 14 | .wrap(children => 15 |
  • 16 |
    3}> 17 | {node.label} 18 | {depth >= 9 ? "\u2026" :
      {children}
    } 19 |
    20 |
  • , 21 |
  • 22 | {node.label} 23 |
  • ); 24 | } 25 | 26 | export default function render(roots: Iterable): HtmlContent { 27 | return
      28 | {Ix.from(roots).map(root => renderNode(root, 0))} 29 |
    ; 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Siemens AG 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/explorer-worker/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "@rdf-toolkit/explorer-worker", 4 | "version": "0.1.0", 5 | "type": "module", 6 | "files": [ 7 | "./dist/**/*" 8 | ], 9 | "main": "./dist/explorer-worker.js", 10 | "scripts": { 11 | "lint": "npx eslint src/", 12 | "prebuild": "tsc --noEmit", 13 | "build": "esbuild src/main.tsx --bundle --minify --platform=browser --conditions=worker,browser --target=es2020 --outfile=../explorer/src/worker/worker.min.js", 14 | "clean": "rimraf -g \"../explorer/src/worker/*.{js,css}\"" 15 | }, 16 | "dependencies": { 17 | "@rdf-toolkit/iterable": "0.1.0", 18 | "@rdf-toolkit/rdf": "0.1.0", 19 | "@rdf-toolkit/schema": "0.1.0", 20 | "@rdf-toolkit/text": "0.1.0", 21 | "@rdf-toolkit/turtle": "0.1.0", 22 | "commonmark": "^0.31.2" 23 | }, 24 | "devDependencies": { 25 | "@types/commonmark": "^0.27.9", 26 | "@typescript-eslint/eslint-plugin": "^8.31.0", 27 | "@typescript-eslint/parser": "^8.31.0", 28 | "esbuild": "^0.25.3", 29 | "eslint": "^9.25.1", 30 | "rimraf": "^6.0.1", 31 | "typescript": "^5.8.3" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/explorer-views/src/pages/main.css: -------------------------------------------------------------------------------- 1 | main { 2 | margin: 0 0 0 25vw; 3 | padding: 2rem 2rem 5rem; 4 | } 5 | 6 | main > * { 7 | margin: 0 auto; 8 | max-width: 66rem; 9 | } 10 | 11 | h1, h2, h3, h4, h5, h6, th { 12 | letter-spacing: 1px; 13 | margin: 3rem 0 1.5rem; 14 | } 15 | 16 | h1 { 17 | font-size: 2.5rem; 18 | } 19 | 20 | h2 { 21 | font-size: 1.5rem; 22 | } 23 | 24 | h3, h4, h5, h6, th { 25 | text-transform: uppercase; 26 | } 27 | 28 | h1 a, h2 a, h3 a, h4 a, h5 a, h6 a, th a { 29 | color: inherit !important; 30 | } 31 | 32 | th, td { 33 | text-align: left; 34 | vertical-align: top; 35 | } 36 | 37 | dt { 38 | margin-top: .5em; 39 | } 40 | 41 | dt, caption { 42 | font-style: italic; 43 | color: var(--base05); 44 | } 45 | 46 | summary { 47 | color: var(--base02); 48 | cursor: pointer; 49 | outline: none; 50 | } 51 | 52 | ul { 53 | list-style: square; 54 | margin: 1rem 0; 55 | } 56 | 57 | em { 58 | font-style: italic; 59 | color: var(--base07); 60 | } 61 | 62 | strong { 63 | font-weight: 600; 64 | color: var(--base07); 65 | } 66 | -------------------------------------------------------------------------------- /packages/turtle/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@rdf-toolkit/turtle", 3 | "version": "0.1.0", 4 | "description": "", 5 | "homepage": "https://github.com/ektrah/rdf-toolkit#readme", 6 | "bugs": { 7 | "url": "https://github.com/ektrah/rdf-toolkit/issues" 8 | }, 9 | "license": "MIT", 10 | "author": "Klaus Hartke", 11 | "type": "module", 12 | "files": [ 13 | "./dist/**/*" 14 | ], 15 | "exports": { 16 | ".": { 17 | "import": { 18 | "types": "./dist/main.d.ts", 19 | "default": "./dist/main.js" 20 | } 21 | } 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/ektrah/rdf-toolkit.git" 26 | }, 27 | "scripts": { 28 | "lint": "npx eslint src/", 29 | "build": "tsc --build", 30 | "clean": "rimraf -g \"dist/**/*.{js,d.ts,map}\"" 31 | }, 32 | "dependencies": { 33 | "@rdf-toolkit/rdf": "0.1.0", 34 | "@rdf-toolkit/text": "0.1.0" 35 | }, 36 | "devDependencies": { 37 | "@typescript-eslint/eslint-plugin": "^8.31.0", 38 | "@typescript-eslint/parser": "^8.31.0", 39 | "eslint": "^9.25.1", 40 | "rimraf": "^6.0.1", 41 | "typescript": "^5.8.3" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/explorer-views/src/sections/rdf-prefixes.tsx: -------------------------------------------------------------------------------- 1 | import { Ix } from "@rdf-toolkit/iterable"; 2 | import { RenderContext } from "../context.js"; 3 | import { HtmlContent } from "../jsx/html.js"; 4 | import "./rdf-prefixes.css"; 5 | 6 | export default function render(context: RenderContext): HtmlContent { 7 | return Ix.from(context.getPrefixes()) 8 | .sort(([, a], [, b]) => a.localeCompare(b)) 9 | .filter(([, prefixLabel]) => prefixLabel !== "_") 10 | .map(([namespaceIRI, prefixLabel]) => 11 | 12 | 13 | {prefixLabel} 14 | 15 | {namespaceIRI} 16 | ) 17 | .wrap(content => 18 |
    19 | RDF prefixes 20 |

    RDF Prefixes

    21 | 22 | 23 | 24 | 25 | 26 | {content} 27 |
    PrefixNamespace IRI
    28 |
    , <>); 29 | } 30 | -------------------------------------------------------------------------------- /packages/text/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@rdf-toolkit/text", 3 | "version": "0.1.0", 4 | "description": "", 5 | "homepage": "https://github.com/ektrah/rdf-toolkit#readme", 6 | "bugs": { 7 | "url": "https://github.com/ektrah/rdf-toolkit/issues" 8 | }, 9 | "license": "MIT", 10 | "author": "Klaus Hartke", 11 | "type": "module", 12 | "files": [ 13 | "./dist/**/*" 14 | ], 15 | "exports": { 16 | ".": { 17 | "import": { 18 | "types": "./dist/main.d.ts", 19 | "default": "./dist/main.js" 20 | } 21 | } 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/ektrah/rdf-toolkit.git" 26 | }, 27 | "scripts": { 28 | "lint": "npx eslint src/", 29 | "build": "tsc --build", 30 | "clean": "rimraf -g \"dist/**/*.{js,d.ts,map}\"" 31 | }, 32 | "dependencies": { 33 | "vscode-languageserver-textdocument": "^1.0.12", 34 | "vscode-languageserver-types": "^3.17.5" 35 | }, 36 | "devDependencies": { 37 | "@typescript-eslint/eslint-plugin": "^8.31.0", 38 | "@typescript-eslint/parser": "^8.31.0", 39 | "eslint": "^9.25.1", 40 | "rimraf": "^6.0.1", 41 | "typescript": "^5.8.3" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/schema/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@rdf-toolkit/schema", 3 | "version": "0.1.0", 4 | "description": "", 5 | "homepage": "https://github.com/ektrah/rdf-toolkit#readme", 6 | "bugs": { 7 | "url": "https://github.com/ektrah/rdf-toolkit/issues" 8 | }, 9 | "license": "MIT", 10 | "author": "Klaus Hartke", 11 | "type": "module", 12 | "files": [ 13 | "./dist/**/*" 14 | ], 15 | "exports": { 16 | ".": { 17 | "import": { 18 | "types": "./dist/main.d.ts", 19 | "default": "./dist/main.js" 20 | } 21 | } 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/ektrah/rdf-toolkit.git" 26 | }, 27 | "scripts": { 28 | "lint": "npx eslint src/", 29 | "build": "tsc --build", 30 | "clean": "rimraf -g \"dist/**/*.{js,d.ts,map}\"" 31 | }, 32 | "dependencies": { 33 | "@rdf-toolkit/iterable": "0.1.0", 34 | "@rdf-toolkit/rdf": "0.1.0", 35 | "@rdf-toolkit/text": "0.1.0" 36 | }, 37 | "devDependencies": { 38 | "@typescript-eslint/eslint-plugin": "^8.31.0", 39 | "@typescript-eslint/parser": "^8.31.0", 40 | "eslint": "^9.25.1", 41 | "rimraf": "^6.0.1", 42 | "typescript": "^5.8.3" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/explorer-worker/src/home.tsx: -------------------------------------------------------------------------------- 1 | import { RenderContext } from "@rdf-toolkit/explorer-views/context"; 2 | import { HtmlContent } from "@rdf-toolkit/explorer-views/jsx/html"; 3 | import renderFooter from "@rdf-toolkit/explorer-views/sections/footer"; 4 | import renderRDFPrefixes from "@rdf-toolkit/explorer-views/sections/rdf-prefixes"; 5 | 6 | export default function render(context: RenderContext): HtmlContent { 7 | return <> 8 |
    9 |

    🅁🄳🄵 Explorer

    10 |

    11 | Welcome to RDF Explorer! 12 | This website allows you to explore RDF graphs. 13 | Here’s how it works: 14 |

    15 |
      16 |
    1. Simply drag and drop some Turtle files into the browser window.
    2. 17 |
    3. The files will be parsed and the resulting graph will be displayed.
    4. 18 |
    5. You can then click through the graph to explore its contents.
    6. 19 |
    20 |

    Have fun!

    21 |
    22 |
    23 | {renderRDFPrefixes(context)} 24 | {renderFooter(context)} 25 |
    26 | ; 27 | } 28 | -------------------------------------------------------------------------------- /packages/explorer-views/src/components/cardinality.tsx: -------------------------------------------------------------------------------- 1 | import { HtmlContent } from "../jsx/html.js"; 2 | import "./cardinality.css"; 3 | 4 | export default function render(minCount: bigint, maxCount: bigint, options: { noSymbols?: boolean } = {}): HtmlContent { 5 | if (!options.noSymbols) { 6 | if (minCount === 1n && maxCount === 1n) { 7 | return <>; 8 | } 9 | else if (minCount === 0n && maxCount === 1n) { 10 | return ? 11 | } 12 | else if (minCount === 1n && maxCount < 0n) { 13 | return +; 14 | } 15 | else if (minCount === 0n && maxCount < 0n) { 16 | return *; 17 | } 18 | } 19 | 20 | if (minCount === maxCount) { 21 | return {minCount}; 22 | } 23 | else if (minCount > maxCount) { 24 | return {minCount}..*; 25 | } 26 | else { 27 | return {minCount}..{maxCount}; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/explorer-views/src/sections/schema.css: -------------------------------------------------------------------------------- 1 | table.properties { 2 | overflow-wrap: anywhere; 3 | table-layout: fixed; 4 | width: 100%; 5 | } 6 | 7 | table.properties th, 8 | table.properties td { 9 | border-bottom: 1px solid var(--base01); 10 | padding: 5px 2ex 5px 0; 11 | } 12 | 13 | table.properties th:last-child, 14 | table.properties td:last-child { 15 | padding-right: 0; 16 | } 17 | 18 | table.properties p, 19 | table.properties ul { 20 | list-style: none; 21 | margin: 0; 22 | padding: 0; 23 | } 24 | 25 | table.properties p + p, 26 | table.properties p + ul, 27 | table.properties ul + p, 28 | table.properties ul + ul { 29 | margin-top: .5em; 30 | } 31 | 32 | .schema-columns { 33 | column-count: 3; 34 | column-fill: balance; 35 | column-gap: 2rem; 36 | } 37 | 38 | .schema-label { 39 | color: var(--base03); 40 | font-size: small; 41 | margin: 0; 42 | } 43 | 44 | .schema-label * { 45 | color: inherit !important; 46 | } 47 | 48 | .schema-label .rdf-pname-ln .rdf-iri-prefixlabel { 49 | background: var(--base01); 50 | vertical-align: bottom; 51 | } 52 | 53 | .schema-keyword { 54 | color: var(--base07); 55 | } 56 | 57 | .schema-list { 58 | display: inline-grid; 59 | } 60 | -------------------------------------------------------------------------------- /packages/cli/src/type-checks.ts: -------------------------------------------------------------------------------- 1 | export namespace Is { 2 | 3 | export function defined(value: any): boolean { 4 | return typeof value !== "undefined"; 5 | } 6 | 7 | export function undefined(value: any): boolean { // eslint-disable-line no-shadow-restricted-names 8 | return typeof value === "undefined"; 9 | } 10 | 11 | export function boolean(value: any): value is boolean { 12 | return value === true || value === false; 13 | } 14 | 15 | export function string(value: any): value is string { 16 | return typeof value === "string"; 17 | } 18 | 19 | export function number(value: any): value is number { 20 | return typeof value === "number"; 21 | } 22 | 23 | export function integer(value: any): value is number { 24 | return typeof value === "number" && Number.isSafeInteger(value); 25 | } 26 | 27 | export function objectLiteral(value: any): value is object { 28 | return value !== null && typeof value === "object"; 29 | } 30 | 31 | export function typedArray(value: any, is: (value: any) => value is T): value is T[] { 32 | return Array.isArray(value) && value.every(is); 33 | } 34 | 35 | export function record(value: any, is: (value: any) => value is T): value is Record { 36 | return value !== null && typeof value === "object" && Object.values(value).every(is); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/cli/src/commands/list-files.ts: -------------------------------------------------------------------------------- 1 | import { Ix } from "@rdf-toolkit/iterable"; 2 | import * as os from "node:os"; 3 | import * as process from "node:process"; 4 | import { Project } from "../model/project.js"; 5 | import { ProjectOptions } from "../options.js"; 6 | 7 | type Options = 8 | & ProjectOptions 9 | 10 | function printFiles(project: Project): void { 11 | const files = Array.from(project.files).sort(); 12 | 13 | for (let i = 0; i < files.length; i++) { 14 | const [documentURI, fileSet] = files[i]; 15 | const file = Ix.from(fileSet).singleOrDefault(null); 16 | 17 | process.stdout.write(i + 1 < files.length ? " \u251C" : " \u2570"); 18 | process.stdout.write("\u257C <"); 19 | process.stdout.write(documentURI); 20 | process.stdout.write(">"); 21 | if (file) { 22 | process.stdout.write(" \u2192 "); 23 | process.stdout.write(project.package.relative(file.filePath)); 24 | process.stdout.write(os.EOL); 25 | } 26 | else { 27 | process.stdout.write(" \u00D7"); 28 | process.stdout.write(os.EOL); 29 | } 30 | } 31 | } 32 | 33 | export default function main(options: Options): void { 34 | const project = new Project(options.project); 35 | 36 | if (project.files.size) { 37 | process.stdout.write(" \u2564"); 38 | process.stdout.write(os.EOL); 39 | 40 | printFiles(project); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/rdf/src/graphs/simple.ts: -------------------------------------------------------------------------------- 1 | import { Ix } from "@rdf-toolkit/iterable"; 2 | import { IRI, IRIOrBlankNode, Literal, Term } from "../terms.js"; 3 | import { Triple } from "../triples.js"; 4 | import { Rdfs } from "../vocab.js"; 5 | 6 | export class SimpleGraph { 7 | 8 | constructor(private readonly dataset: Iterable>) { 9 | } 10 | 11 | triples(subject?: IRIOrBlankNode, predicate?: IRI, object?: Term): Ix { 12 | let result = Ix.from(this.dataset).concatMap(triples => triples); 13 | result = subject ? result.filter(triple => triple.subject.equals(subject)) : result; 14 | result = predicate ? result.filter(triple => triple.predicate.equals(predicate)) : result; 15 | result = object ? result.filter(triple => triple.object.equals(object)) : result; 16 | return result; 17 | } 18 | 19 | subjects(predicate: IRI, object: Term): Ix { 20 | return this.triples(undefined, predicate, object).map(triple => triple.subject); 21 | } 22 | 23 | objects(subject: IRIOrBlankNode, predicate: IRI): Ix { 24 | return this.triples(subject, predicate, undefined).map(triple => triple.object); 25 | } 26 | 27 | getLabel(node: IRIOrBlankNode): Ix { 28 | return this.objects(node, Rdfs.label).ofType(Literal.is); 29 | } 30 | 31 | getComment(node: IRIOrBlankNode): Ix { 32 | return this.objects(node, Rdfs.comment).ofType(Literal.is); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/cli/src/commands/list-prefixes.ts: -------------------------------------------------------------------------------- 1 | import { Ix } from "@rdf-toolkit/iterable"; 2 | import * as os from "node:os"; 3 | import * as process from "node:process"; 4 | import { Project } from "../model/project.js"; 5 | import { ProjectOptions } from "../options.js"; 6 | 7 | type Options = 8 | & ProjectOptions 9 | 10 | function printPrefixes(project: Project): void { 11 | const prefixes = Array.from(project.prefixes).sort(); 12 | 13 | for (let i = 0; i < prefixes.length; i++) { 14 | const [prefixLabel, iriSet] = prefixes[i]; 15 | const namespaceIRI = Ix.from(iriSet).singleOrDefault(null); 16 | 17 | process.stdout.write(i + 1 < prefixes.length ? " \u251C" : " \u2570"); 18 | process.stdout.write("\u257C "); 19 | process.stdout.write(prefixLabel); 20 | process.stdout.write(":"); 21 | if (namespaceIRI) { 22 | process.stdout.write(" <"); 23 | process.stdout.write(namespaceIRI); 24 | process.stdout.write(">"); 25 | process.stdout.write(os.EOL); 26 | } 27 | else { 28 | process.stdout.write(" \u00D7"); 29 | process.stdout.write(os.EOL); 30 | } 31 | } 32 | } 33 | 34 | export default function main(options: Options): void { 35 | const project = new Project(options.project); 36 | 37 | if (project.prefixes.size) { 38 | process.stdout.write(" \u2564"); 39 | process.stdout.write(os.EOL); 40 | 41 | printPrefixes(project); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/cli/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "@rdf-toolkit/cli", 4 | "version": "0.1.0", 5 | "type": "module", 6 | "files": [ 7 | "./dist/**/*" 8 | ], 9 | "main": "./dist/main.js", 10 | "bin": { 11 | "rdf": "./dist/main.js" 12 | }, 13 | "eslintIgnore": [ 14 | "*.min.js" 15 | ], 16 | "scripts": { 17 | "lint": "npx eslint src/", 18 | "prebuild": "tsc --noEmit", 19 | "build": "esbuild src/main.ts --bundle --minify --platform=node --format=esm --target=es2020 --external:commonmark --external:koa --external:koa-send --external:yargs --asset-names=[name] --loader:.min.js=file --loader:.min.css=file --loader:.woff2=file --outfile=./dist/main.js", 20 | "clean": "rimraf -g \"./dist/*.{js,css,woff2}\"" 21 | }, 22 | "dependencies": { 23 | "@rdf-toolkit/iterable": "0.1.0", 24 | "@rdf-toolkit/rdf": "0.1.0", 25 | "@rdf-toolkit/schema": "0.1.0", 26 | "@rdf-toolkit/text": "0.1.0", 27 | "@rdf-toolkit/turtle": "0.1.0", 28 | "commonmark": "^0.31.2", 29 | "koa": "^2.16.1", 30 | "koa-send": "^5.0.1", 31 | "yargs": "^17.7.2" 32 | }, 33 | "devDependencies": { 34 | "@types/commonmark": "^0.27.9", 35 | "@types/koa": "^2.15.0", 36 | "@types/koa-send": "^4.1.6", 37 | "@types/node": "^22.14.1", 38 | "@types/yargs": "^17.0.33", 39 | "@typescript-eslint/eslint-plugin": "^8.31.0", 40 | "@typescript-eslint/parser": "^8.31.0", 41 | "esbuild": "^0.25.3", 42 | "eslint": "^9.25.1", 43 | "rimraf": "^6.0.1", 44 | "typescript": "^5.8.3" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/explorer-views/src/pages/main.tsx: -------------------------------------------------------------------------------- 1 | import { IRI, IRIOrBlankNode } from "@rdf-toolkit/rdf/terms"; 2 | import { TextDocument } from "@rdf-toolkit/text"; 3 | import renderRdfTerm from "../components/rdf-term.js"; 4 | import { RenderContext } from "../context.js"; 5 | import { HtmlContent } from "../jsx/html.js"; 6 | import renderFooter from "../sections/footer.js"; 7 | import renderRDF from "../sections/rdf.js"; 8 | import renderClass from "../sections/schema-class.js"; 9 | import renderOntology from "../sections/schema-ontology.js"; 10 | import renderProperty from "../sections/schema-property.js"; 11 | import renderSeeAlso from "../sections/seealso.js"; 12 | import renderTurtleDocument from "../sections/turtle.js"; 13 | import renderValidation from "../sections/validation.js"; 14 | import "./main.css"; 15 | 16 | export default function render(subject: IRIOrBlankNode, document: TextDocument | null, context: RenderContext): HtmlContent { 17 | return <> 18 |
    19 |

    {renderRdfTerm(subject, context, { rawBlankNodes: true, hideError: true })}

    20 |
    21 | {renderClass(subject, context)} 22 | {IRI.is(subject) ? renderProperty(subject, context) : null} 23 | {renderOntology(subject, context)} 24 | {document ? renderTurtleDocument(document, context) : null} 25 | {renderValidation(subject, context)} 26 | {renderSeeAlso(subject, context)} 27 |
    28 | {renderRDF(subject, context)} 29 | {renderFooter(context)} 30 |
    31 | ; 32 | } 33 | -------------------------------------------------------------------------------- /packages/explorer-views/src/components/listing.css: -------------------------------------------------------------------------------- 1 | .listing { 2 | background: var(--base01); 3 | border: 1px solid var(--base02); 4 | color: var(--base06); 5 | margin: 1.5rem 0; 6 | padding: 0; 7 | white-space: pre; 8 | } 9 | 10 | .listing pre { 11 | margin: 1em; 12 | overflow-x: auto; 13 | } 14 | 15 | .listing table { 16 | border-spacing: 0; 17 | counter-reset: line-number; 18 | line-height: 1.5; 19 | margin: 1em; 20 | overflow-wrap: anywhere; 21 | table-layout: auto; 22 | white-space: pre-wrap; 23 | } 24 | 25 | .listing td { 26 | padding: 0 .5em; 27 | vertical-align: top; 28 | } 29 | 30 | .listing tr:hover td { 31 | background: var(--base02); 32 | color: var(--base07); 33 | } 34 | 35 | .listing td:first-child { 36 | color: var(--base03); 37 | overflow-wrap: normal; 38 | text-align: right; 39 | white-space: pre; 40 | width: 0; 41 | } 42 | 43 | .listing td:first-child::before { 44 | content: counter(line-number); 45 | counter-increment: line-number; 46 | } 47 | 48 | .listing td:last-child { 49 | padding: 0 .5em 0 4em; 50 | text-indent: -3.5em; 51 | width: auto; 52 | } 53 | 54 | .listing-tab { 55 | color: var(--base02); 56 | } 57 | 58 | .listing-tab::after { 59 | content: "\2192\0020"; 60 | } 61 | 62 | .listing-no-newline { 63 | color: var(--base03); 64 | font-size: small; 65 | } 66 | -------------------------------------------------------------------------------- /packages/rdf/src/terms/blanknode.ts: -------------------------------------------------------------------------------- 1 | import { IRI } from "./iri.js"; 2 | import { Literal } from "./literal.js"; 3 | 4 | export interface BlankNode { 5 | readonly termType: "BlankNode"; 6 | readonly value: string; 7 | compareTo(other: IRI | BlankNode | Literal): number; 8 | equals(other: IRI | BlankNode | Literal | null | undefined): boolean; 9 | } 10 | 11 | class InternedBlankNode implements BlankNode { 12 | 13 | static readonly InternPool: { [K in string]: InternedBlankNode } = {}; 14 | 15 | constructor(readonly value: string) { 16 | return InternedBlankNode.InternPool[value] || (InternedBlankNode.InternPool[value] = this); 17 | } 18 | 19 | get termType(): "BlankNode" { 20 | return "BlankNode"; 21 | } 22 | 23 | compareTo(other: IRI | BlankNode | Literal): number { 24 | switch (other.termType) { 25 | case "BlankNode": 26 | return this.value.localeCompare(other.value); 27 | case "Literal": 28 | return 1; 29 | case "NamedNode": 30 | return 1; 31 | } 32 | } 33 | 34 | equals(other: IRI | BlankNode | Literal | null | undefined): boolean { 35 | return other === this; 36 | } 37 | } 38 | 39 | export namespace BlankNode { 40 | 41 | export function create(id: string): BlankNode { 42 | return new InternedBlankNode("http://example.com/.well-known/genid/" + id); 43 | } 44 | 45 | export function is(term: IRI | BlankNode | Literal): term is BlankNode { 46 | return term.termType === "BlankNode"; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/rdf/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@rdf-toolkit/rdf", 3 | "version": "0.1.0", 4 | "description": "", 5 | "homepage": "https://github.com/ektrah/rdf-toolkit#readme", 6 | "bugs": { 7 | "url": "https://github.com/ektrah/rdf-toolkit/issues" 8 | }, 9 | "license": "MIT", 10 | "author": "Klaus Hartke", 11 | "type": "module", 12 | "files": [ 13 | "./dist/**/*" 14 | ], 15 | "exports": { 16 | "./graphs": { 17 | "import": { 18 | "types": "./dist/graphs.d.ts", 19 | "default": "./dist/graphs.js" 20 | } 21 | }, 22 | "./terms": { 23 | "import": { 24 | "types": "./dist/terms.d.ts", 25 | "default": "./dist/terms.js" 26 | } 27 | }, 28 | "./triples": { 29 | "import": { 30 | "types": "./dist/triples.d.ts", 31 | "default": "./dist/triples.js" 32 | } 33 | }, 34 | "./vocab": { 35 | "import": { 36 | "types": "./dist/vocab.d.ts", 37 | "default": "./dist/vocab.js" 38 | } 39 | } 40 | }, 41 | "repository": { 42 | "type": "git", 43 | "url": "git+https://github.com/ektrah/rdf-toolkit.git" 44 | }, 45 | "scripts": { 46 | "lint": "npx eslint src/", 47 | "build": "tsc --build", 48 | "clean": "rimraf -g \"dist/**/*.{js,d.ts,map}\"" 49 | }, 50 | "dependencies": { 51 | "@rdf-toolkit/iterable": "0.1.0", 52 | "@rdf-toolkit/text": "0.1.0" 53 | }, 54 | "devDependencies": { 55 | "@typescript-eslint/eslint-plugin": "^8.31.0", 56 | "@typescript-eslint/parser": "^8.31.0", 57 | "eslint": "^9.25.1", 58 | "rimraf": "^6.0.1", 59 | "typescript": "^5.8.3" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /packages/cli/src/commands/serve.ts: -------------------------------------------------------------------------------- 1 | import Koa, { Middleware } from "koa"; 2 | import send, { SendOptions } from "koa-send"; 3 | import * as os from "node:os"; 4 | import * as readline from "node:readline"; 5 | import { Project } from "../model/project.js"; 6 | import { ProjectOptions, ServerOptions } from "../options.js"; 7 | import { Workspace } from "../workspace.js"; 8 | 9 | type Options = 10 | & ProjectOptions 11 | & ServerOptions 12 | 13 | function serve(opts: SendOptions): Middleware { 14 | return async function serve(ctx, next) { 15 | if (ctx.method === "HEAD" || ctx.method === "GET") { 16 | try { 17 | return await send(ctx, ctx.path, opts); 18 | } catch (err) { 19 | if ((err as { status?: number }).status !== 404) { 20 | throw err; 21 | } 22 | } 23 | } 24 | 25 | return await next(); 26 | } 27 | } 28 | 29 | export default function main(port: number, options: Options): void { 30 | const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); 31 | const project = new Project(options.project); 32 | const workspace = new Workspace(project.package.resolve(options.root || project.json.siteOptions?.outDir || "public")); 33 | 34 | const opts: SendOptions = { 35 | root: workspace.directoryPath, 36 | index: "index.html", 37 | gzip: false, 38 | brotli: false, 39 | extensions: [".html"], 40 | }; 41 | 42 | const server = new Koa().use(serve(opts)).listen(port); 43 | 44 | rl.question(`Serving "${workspace.directoryPath}" as ${os.EOL}`, () => { 45 | server.close(); 46 | rl.close(); 47 | }); 48 | } 49 | -------------------------------------------------------------------------------- /packages/explorer-shared/src/shared.ts: -------------------------------------------------------------------------------- 1 | type WorkerMessage = { 2 | readonly command: string; 3 | readonly arguments: any[]; 4 | }; 5 | 6 | export type WorkerChannel = { 7 | onmessage: ((ev: MessageEvent) => any) | null; 8 | postMessage(message: WorkerMessage, transfer: Transferable[]): void; 9 | } 10 | 11 | export namespace WorkerChannel { 12 | 13 | export function connect(channel: WorkerChannel, fn: (remote: Remote) => Local): Remote { 14 | const remote: Remote = new Proxy({}, { 15 | get: function (target: any, property: string, receiver: any): () => void { 16 | return function (...args: any): void { 17 | channel.postMessage({ command: property, arguments: [...args] }, [...args].filter(arg => arg instanceof ArrayBuffer)); 18 | } 19 | } 20 | }); 21 | const local: Local = fn(remote); 22 | channel.onmessage = function (e: MessageEvent): void { 23 | const message: WorkerMessage = e.data; 24 | (local as Record)[message.command].apply(local, message.arguments); // eslint-disable-line @typescript-eslint/ban-types 25 | }; 26 | return remote; 27 | } 28 | } 29 | 30 | export type Frontend = { 31 | showProgress(message: string): void; 32 | showDialog(innerHTML: string): void; 33 | hideProgress(): void; 34 | replaceNavigation(innerHTML: string): void; 35 | replaceMainContent(title: string, innerHTML: string): void; 36 | } 37 | 38 | export type Backend = { 39 | beforecompile(): void; 40 | compile(documentURI: string, sourceText: ArrayBuffer, sourceTextHash: ArrayBuffer, sourceLanguage: string): void; 41 | aftercompile(): void; 42 | 43 | navigateTo(iri: string): void; 44 | } 45 | -------------------------------------------------------------------------------- /packages/rdf/src/engines/shacl.ts: -------------------------------------------------------------------------------- 1 | import { Triple } from "../triples.js"; 2 | import { Rdf, Rdfs, Shacl } from "../vocab.js"; 3 | 4 | // https://www.w3.org/TR/2017/REC-shacl-20170720/ 5 | export class SHACLEngine { 6 | 7 | ingest(triple: Triple): boolean { 8 | return false; 9 | } 10 | 11 | *beforeinterpret(): Generator { 12 | } 13 | 14 | *interpret(triple: Triple): Generator { 15 | 16 | switch (triple.predicate) { 17 | case Shacl.class: 18 | case Shacl.returnType: 19 | case Shacl.targetClass: 20 | yield Triple.createAxiomatic(triple.predicate, Rdfs.range, Rdfs.Class); 21 | break; 22 | 23 | case Shacl.datatype: 24 | yield Triple.createAxiomatic(triple.predicate, Rdfs.range, Rdfs.Datatype); 25 | break; 26 | 27 | case Shacl.annotationProperty: 28 | case Shacl.disjoint: 29 | case Shacl.equals: 30 | case Shacl.lessThan: 31 | case Shacl.lessThanOrEquals: 32 | case Shacl.targetObjectsOf: 33 | case Shacl.targetSubjectsOf: 34 | yield Triple.createAxiomatic(triple.predicate, Rdfs.range, Rdf.Property); 35 | break; 36 | 37 | case Shacl.alternativePath: 38 | case Shacl.and: 39 | case Shacl.ignoredProperties: 40 | case Shacl.in: 41 | case Shacl.languageIn: 42 | case Shacl.or: 43 | case Shacl.xone: 44 | yield Triple.createAxiomatic(triple.predicate, Rdfs.range, Rdf.List); 45 | break; 46 | 47 | case Shacl.node: 48 | yield Triple.createAxiomatic(triple.predicate, Rdfs.range, Shacl.NodeShape); 49 | break; 50 | } 51 | } 52 | 53 | *afterinterpret(): Generator { 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /packages/cli/src/commands/list-terms.ts: -------------------------------------------------------------------------------- 1 | import * as os from "node:os"; 2 | import * as process from "node:process"; 3 | import { Project } from "../model/project.js"; 4 | import { TextFile } from "../model/textfile.js"; 5 | import { ProjectOptions } from "../options.js"; 6 | 7 | type Options = 8 | & ProjectOptions 9 | 10 | function printTerms(file: TextFile, indentation: string): void { 11 | const terms = Array.from(file.terms).sort((a, b) => a.compareTo(b)); 12 | 13 | for (let i = 0; i < terms.length; i++) { 14 | process.stdout.write(indentation); 15 | process.stdout.write(i + 1 < terms.length ? " \u251C" : " \u2570"); 16 | process.stdout.write("\u257C <"); 17 | process.stdout.write(terms[i].value); 18 | process.stdout.write(">"); 19 | process.stdout.write(os.EOL); 20 | } 21 | } 22 | 23 | function printFiles(project: Project): void { 24 | const files = Array.from(project.package.files).sort(); 25 | 26 | for (let i = 0; i < files.length; i++) { 27 | const [documentURI, file] = files[i]; 28 | 29 | process.stdout.write(i + 1 < files.length ? " \u251C" : " \u2570"); 30 | process.stdout.write("\u257C <"); 31 | process.stdout.write(documentURI); 32 | process.stdout.write(">"); 33 | if (file) { 34 | process.stdout.write(" \u2192 "); 35 | process.stdout.write(project.package.relative(file.filePath)); 36 | process.stdout.write(os.EOL); 37 | 38 | printTerms(file, i + 1 < files.length ? " \u2502" : " "); 39 | } 40 | else { 41 | process.stdout.write(" \u00D7"); 42 | process.stdout.write(os.EOL); 43 | } 44 | } 45 | } 46 | 47 | export default function main(options: Options): void { 48 | const project = new Project(options.project); 49 | 50 | if (project.files.size) { 51 | process.stdout.write(" \u2564"); 52 | process.stdout.write(os.EOL); 53 | 54 | printFiles(project); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /packages/cli/src/prefixes.ts: -------------------------------------------------------------------------------- 1 | export class PrefixTable { 2 | private readonly table: readonly [string, string][]; 3 | 4 | constructor(input: Iterable>) { 5 | const namespacesToPrefixLabels: Map = new Map(); 6 | const prefixLabelsToOrdinal: Map = new Map(); 7 | for (const namespaces of input) { 8 | for (let prefixLabel in namespaces) { 9 | const namespace = namespaces[prefixLabel]; 10 | if (!namespacesToPrefixLabels.has(namespace)) { 11 | if (/^$|^ns[0-9]+$/u.test(prefixLabel)) { 12 | const m = /[/]([A-Za-z][-0-9_A-Za-z]*)[/#]$/u.exec(namespace); 13 | prefixLabel = m ? m[1] : "ns"; 14 | } 15 | let ordinal = prefixLabelsToOrdinal.get(prefixLabel); 16 | if (ordinal) { 17 | let newPrefixLabel; 18 | do { newPrefixLabel = prefixLabel + "_" + (++ordinal); } while (prefixLabelsToOrdinal.has(newPrefixLabel)); 19 | prefixLabelsToOrdinal.set(prefixLabel, ordinal); 20 | prefixLabel = newPrefixLabel; 21 | } 22 | prefixLabelsToOrdinal.set(prefixLabel, 1); 23 | namespacesToPrefixLabels.set(namespace, prefixLabel); 24 | } 25 | } 26 | } 27 | this.table = Array.from(namespacesToPrefixLabels).sort((a, b) => b[0].length - a[0].length); 28 | } 29 | 30 | all(): readonly [string, string][] { 31 | return this.table; 32 | } 33 | 34 | lookup(iri: string): { readonly localName: string; readonly prefixLabel: string; } | null { 35 | for (const [namespace, prefixLabel] of this.table) { 36 | if (iri.startsWith(namespace)) { 37 | return { prefixLabel, localName: iri.slice(namespace.length) }; 38 | } 39 | } 40 | return null; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/explorer-worker/src/prefixes.ts: -------------------------------------------------------------------------------- 1 | export class PrefixTable { 2 | private readonly table: readonly [string, string][]; 3 | 4 | constructor(input: Iterable>) { 5 | const namespacesToPrefixLabels: Map = new Map(); 6 | const prefixLabelsToOrdinal: Map = new Map(); 7 | for (const namespaces of input) { 8 | for (let prefixLabel in namespaces) { 9 | const namespace = namespaces[prefixLabel]; 10 | if (!namespacesToPrefixLabels.has(namespace)) { 11 | if (/^$|^ns[0-9]+$/u.test(prefixLabel)) { 12 | const m = /[/]([A-Za-z][-0-9_A-Za-z]*)[/#]$/u.exec(namespace); 13 | prefixLabel = m ? m[1] : "ns"; 14 | } 15 | let ordinal = prefixLabelsToOrdinal.get(prefixLabel); 16 | if (ordinal) { 17 | let newPrefixLabel; 18 | do { newPrefixLabel = prefixLabel + "_" + (++ordinal); } while (prefixLabelsToOrdinal.has(newPrefixLabel)); 19 | prefixLabelsToOrdinal.set(prefixLabel, ordinal); 20 | prefixLabel = newPrefixLabel; 21 | } 22 | prefixLabelsToOrdinal.set(prefixLabel, 1); 23 | namespacesToPrefixLabels.set(namespace, prefixLabel); 24 | } 25 | } 26 | } 27 | this.table = Array.from(namespacesToPrefixLabels).sort((a, b) => b[0].length - a[0].length); 28 | } 29 | 30 | all(): readonly [string, string][] { 31 | return this.table; 32 | } 33 | 34 | lookup(iri: string): { readonly localName: string; readonly prefixLabel: string; } | null { 35 | for (const [namespace, prefixLabel] of this.table) { 36 | if (iri.startsWith(namespace)) { 37 | return { prefixLabel, localName: iri.slice(namespace.length) }; 38 | } 39 | } 40 | return null; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/cli/src/commands/list-imports.ts: -------------------------------------------------------------------------------- 1 | import { Ix } from "@rdf-toolkit/iterable"; 2 | import { DocumentUri } from "@rdf-toolkit/text"; 3 | import * as os from "node:os"; 4 | import { Project } from "../model/project.js"; 5 | import { TextFile } from "../model/textfile.js"; 6 | import { DiagnosticOptions, ProjectOptions } from "../options.js"; 7 | 8 | type Options = 9 | & DiagnosticOptions 10 | & ProjectOptions 11 | 12 | const stack: Array = []; 13 | 14 | function printFiles(files: ArrayLike<[DocumentUri, TextFile | null]>, project: Project, indentation: string): void { 15 | for (let i = 0; i < files.length; i++) { 16 | const [documentURI, file] = files[i]; 17 | 18 | process.stdout.write(indentation); 19 | process.stdout.write(i + 1 < files.length ? " \u251C" : " \u2570"); 20 | process.stdout.write("\u257C <"); 21 | process.stdout.write(documentURI); 22 | process.stdout.write(">"); 23 | if (file) { 24 | process.stdout.write(" \u2192 "); 25 | process.stdout.write(project.package.relative(file.filePath)); 26 | process.stdout.write(os.EOL); 27 | 28 | if (file.ontology) { 29 | stack.push(documentURI); 30 | printFiles(Ix.from(file.ontology.imports).map(ontologyIRI => project.resolveImport(ontologyIRI)).filter(([x]) => !stack.includes(x)).toArray().sort(), project, indentation + (i + 1 < files.length ? " \u2502" : " ")); 31 | stack.pop(); 32 | } 33 | } 34 | else { 35 | process.stdout.write(" \u00D7"); 36 | process.stdout.write(os.EOL); 37 | } 38 | } 39 | } 40 | 41 | export default function main(options: Options): void { 42 | const project = new Project(options.project); 43 | const files = Array.from(project.package.files).sort(); 44 | 45 | if (files.length) { 46 | process.stdout.write(" \u2564"); 47 | process.stdout.write(os.EOL); 48 | 49 | printFiles(files, project, ""); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RDF Toolkit 2 | 3 | RDF Toolkit is a TypeScript library for working with Turtle and RDF data, along with a set of tools built around it. 4 | 5 | 6 | ## Getting Started 7 | 8 | RDF Toolkit doesn't have any releases yet, so it must be built from source using `npm run build`. 9 | 10 | ```bash 11 | $ git clone --depth 1 https://github.com/ektrah/rdf-toolkit.git 12 | $ cd rdf-toolkit 13 | $ npm ci 14 | $ npm run build 15 | ``` 16 | 17 | 18 | ## Usage 19 | 20 | RDF Toolkit has a command-line interface with the syntax `rdf [options]`. 21 | 22 | 23 | ### make explorer 24 | 25 | This command will generate an HTML file that contains an interactive graph explorer that can be used to view and explore the contents of Turtle documents. 26 | 27 | 28 | ### make site 29 | 30 | This command will generate a static website from the contents of Turtle documents. 31 | 32 | 33 | ### serve 34 | 35 | This command will start a local HTTP server and serve the graph explorer or the static website created with the `make explorer` and `make site` commands, respectively. 36 | 37 | 38 | ### init 39 | 40 | This command will create a new configuration file. 41 | 42 | 43 | ### add file 44 | 45 | This command will add a Turtle file to the configuration file. 46 | 47 | 48 | ### list files 49 | 50 | This command will list the Turtle files in the configuration file. 51 | 52 | 53 | ### remove file 54 | 55 | This command will remove a Turtle file from the configuration file. 56 | 57 | 58 | ## Configuration 59 | 60 | The command-line interface is driven by a JSON configuration file. 61 | 62 | ```json 63 | { 64 | "ontologies": { 65 | "http://www.w3.org/1999/02/22-rdf-syntax-ns": "vocab/rdf.ttl", 66 | "http://www.w3.org/2000/01/rdf-schema": "vocab/rdfs.ttl", 67 | "http://www.w3.org/2002/07/owl": "vocab/owl.ttl" 68 | } 69 | } 70 | ``` 71 | 72 | 73 | ## Example 74 | 75 | To build and serve an example site: 76 | 77 | ```bash 78 | $ npm i @rdf-toolkit/cli 79 | $ cd explorer 80 | $ npx rdf make explorer 81 | $ npx rdf serve 82 | ``` 83 | 84 | 85 | ## License 86 | 87 | RDF Toolkit is released under the MIT license. For more information, see the LICENSE file. 88 | -------------------------------------------------------------------------------- /packages/explorer-views/src/components/markdown.css: -------------------------------------------------------------------------------- 1 | .markdown { 2 | color: var(--base06); 3 | counter-reset: section; 4 | counter-reset: subsection; 5 | counter-reset: subsubsection; 6 | counter-reset: subsubsubsection; 7 | } 8 | 9 | .markdown h3 { 10 | color: var(--base07); 11 | counter-increment: section; 12 | counter-reset: subsection; 13 | counter-reset: subsubsection; 14 | counter-reset: subsubsubsection; 15 | } 16 | 17 | .markdown h3::before { 18 | content: counter(section) ".\2002"; 19 | } 20 | 21 | .markdown h3 + h4 { 22 | margin-top: 1rem; 23 | } 24 | 25 | .markdown h4 { 26 | color: var(--base07); 27 | counter-increment: subsection; 28 | counter-reset: subsubsection; 29 | counter-reset: subsubsubsection; 30 | } 31 | 32 | .markdown h4::before { 33 | content: counter(section) "." counter(subsection) "\2003"; 34 | } 35 | 36 | .markdown h4 + h5 { 37 | margin-top: 1rem; 38 | } 39 | 40 | .markdown h5 { 41 | color: var(--base07); 42 | counter-increment: subsubsection; 43 | counter-reset: subsubsubsection; 44 | } 45 | 46 | .markdown h5::before { 47 | content: counter(section) "." counter(subsection) "." counter(subsubsection) "\2003"; 48 | } 49 | 50 | .markdown h5 + h6 { 51 | margin-top: 1rem; 52 | } 53 | 54 | .markdown h6 { 55 | color: var(--base07); 56 | counter-increment: subsubsubsection; 57 | } 58 | 59 | .markdown h6::before { 60 | content: counter(section) "." counter(subsection) "." counter(subsubsection) "." counter(subsubsubsection) "\2003"; 61 | } 62 | 63 | .markdown p { 64 | margin: 1rem 0; 65 | } 66 | 67 | .markdown ul { 68 | padding-left: 2rem; 69 | } 70 | 71 | .markdown em, 72 | .markdown strong { 73 | color: var(--base07); 74 | font: inherit; 75 | text-decoration: underline; 76 | } 77 | 78 | .markdown .markdown-html { 79 | color: var(--base04); 80 | } 81 | -------------------------------------------------------------------------------- /packages/turtle/src/syntax/trivia.ts: -------------------------------------------------------------------------------- 1 | import { SyntaxNode } from "./node.js"; 2 | import { SyntaxToken } from "./token.js"; 3 | 4 | export enum TriviaKind { 5 | Whitespace = 100, 6 | Comment, 7 | EndOfLine, 8 | } 9 | 10 | export interface SyntaxTrivia { 11 | readonly kind: TriviaKind; 12 | readonly text: string; 13 | } 14 | 15 | export namespace SyntaxTrivia { 16 | 17 | export const carriageReturn: SyntaxTrivia = Object.freeze({ kind: TriviaKind.EndOfLine, text: "\r" }); 18 | export const carriageReturnLineFeed: SyntaxTrivia = Object.freeze({ kind: TriviaKind.EndOfLine, text: "\r\n" }); 19 | export const lineFeed: SyntaxTrivia = Object.freeze({ kind: TriviaKind.EndOfLine, text: "\n" }); 20 | export const space: SyntaxTrivia = Object.freeze({ kind: TriviaKind.Whitespace, text: " " }); 21 | export const tab: SyntaxTrivia = Object.freeze({ kind: TriviaKind.Whitespace, text: "\t" }); 22 | 23 | export function createWhitespace(text: string): SyntaxTrivia; 24 | export function createWhitespace(numberOfSpaces: number): SyntaxTrivia; 25 | export function createWhitespace(textOrNumberOfSpaces: string | number): SyntaxTrivia { 26 | const text = typeof textOrNumberOfSpaces === "number" ? " ".repeat(textOrNumberOfSpaces) : textOrNumberOfSpaces; 27 | switch (text) { 28 | case space.text: return space; 29 | case tab.text: return tab; 30 | } 31 | return { kind: TriviaKind.Whitespace, text }; 32 | } 33 | 34 | export function createComment(text: string): SyntaxTrivia { 35 | return { kind: TriviaKind.Comment, text }; 36 | } 37 | 38 | export function createEndOfLine(text: string): SyntaxTrivia { 39 | switch (text) { 40 | case carriageReturn.text: return carriageReturn; 41 | case carriageReturnLineFeed.text: return carriageReturnLineFeed; 42 | case lineFeed.text: return lineFeed; 43 | } 44 | return { kind: TriviaKind.EndOfLine, text }; 45 | } 46 | 47 | export function is(node: SyntaxTrivia | SyntaxToken | SyntaxNode): node is SyntaxTrivia { 48 | return node.kind >= 100 && node.kind < 200; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/cli/src/commands/list-dependencies.ts: -------------------------------------------------------------------------------- 1 | import * as os from "node:os"; 2 | import * as process from "node:process"; 3 | import { Package } from "../model/package.js"; 4 | import { Project } from "../model/project.js"; 5 | import { AllOption, ProjectOptions, RecursiveOptions } from "../options.js"; 6 | 7 | type Options = 8 | & AllOption 9 | & ProjectOptions 10 | & RecursiveOptions 11 | 12 | const stack: Array = []; 13 | 14 | function printPackages(packages: Array, indentation: string, recursive: boolean): void { 15 | for (let i = 0; i < packages.length; i++) { 16 | const [moduleName, package_] = packages[i]; 17 | 18 | process.stdout.write(indentation); 19 | process.stdout.write(i + 1 < packages.length ? " \u251C" : " \u2570"); 20 | process.stdout.write("\u257C "); 21 | process.stdout.write(moduleName); 22 | 23 | if (package_) { 24 | if (package_.version) { 25 | process.stdout.write("@"); 26 | process.stdout.write(package_.version); 27 | } 28 | process.stdout.write(os.EOL); 29 | 30 | if (recursive) { 31 | const dependencies = Array.from(package_.dependencies) 32 | .filter(([x]) => !stack.includes(x)) 33 | .sort(); 34 | 35 | stack.push(moduleName); 36 | printPackages(dependencies, indentation + (i + 1 < packages.length ? " \u2502" : " "), recursive); 37 | stack.pop(); 38 | } 39 | } 40 | else { 41 | process.stdout.write(" \u00D7"); 42 | process.stdout.write(os.EOL); 43 | } 44 | } 45 | } 46 | 47 | export default function main(options: Options): void { 48 | const project = new Project(options.project); 49 | 50 | const packages = options.all 51 | ? Array.from(project.packages).filter(([, p]) => p !== project.package).sort() 52 | : Array.from(project.package.dependencies).sort(); 53 | 54 | if (packages.length) { 55 | process.stdout.write(" \u2564"); 56 | process.stdout.write(os.EOL); 57 | 58 | printPackages(packages, "", !!options.recursive); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /packages/explorer-views/src/components/tabview.css: -------------------------------------------------------------------------------- 1 | .tabview { 2 | margin: 1.5em 0; 3 | } 4 | 5 | .tabview label { 6 | background: var(--base02); 7 | border-color: var(--base02); 8 | border-style: solid; 9 | border-width: 1px 1px 0 1px; 10 | color: var(--base05); 11 | cursor: pointer; 12 | display: inline-block; 13 | font-size: smaller; 14 | margin-right: 1px; 15 | padding: .5em .8em; 16 | user-select: none; 17 | } 18 | 19 | .tabview label:hover { 20 | color: var(--base07); 21 | text-decoration: underline; 22 | } 23 | 24 | .tabview .tabview-pages > * { 25 | display: none; 26 | margin: 0; 27 | } 28 | 29 | .tabview input { 30 | display: none; 31 | } 32 | 33 | .tabview input:nth-child(1):checked ~ .tabview-tabs > :nth-child(1), 34 | .tabview input:nth-child(2):checked ~ .tabview-tabs > :nth-child(2), 35 | .tabview input:nth-child(3):checked ~ .tabview-tabs > :nth-child(3), 36 | .tabview input:nth-child(4):checked ~ .tabview-tabs > :nth-child(4), 37 | .tabview input:nth-child(5):checked ~ .tabview-tabs > :nth-child(5), 38 | .tabview input:nth-child(6):checked ~ .tabview-tabs > :nth-child(6), 39 | .tabview input:nth-child(7):checked ~ .tabview-tabs > :nth-child(7), 40 | .tabview input:nth-child(8):checked ~ .tabview-tabs > :nth-child(8) { 41 | background: var(--base01); 42 | color: var(--base07); 43 | } 44 | 45 | .tabview input:nth-child(1):checked ~ .tabview-pages > :nth-child(1), 46 | .tabview input:nth-child(2):checked ~ .tabview-pages > :nth-child(2), 47 | .tabview input:nth-child(3):checked ~ .tabview-pages > :nth-child(3), 48 | .tabview input:nth-child(4):checked ~ .tabview-pages > :nth-child(4), 49 | .tabview input:nth-child(5):checked ~ .tabview-pages > :nth-child(5), 50 | .tabview input:nth-child(6):checked ~ .tabview-pages > :nth-child(6), 51 | .tabview input:nth-child(7):checked ~ .tabview-pages > :nth-child(7), 52 | .tabview input:nth-child(8):checked ~ .tabview-pages > :nth-child(8) { 53 | display: block; 54 | } 55 | -------------------------------------------------------------------------------- /packages/rdf/src/graphs/indexed.ts: -------------------------------------------------------------------------------- 1 | import { Ix, ReadonlyMultiMap } from "@rdf-toolkit/iterable"; 2 | import { IRI, IRIOrBlankNode, Term } from "../terms.js"; 3 | import { Triple } from "../triples.js"; 4 | import { Rdf } from "../vocab.js"; 5 | import { SimpleGraph } from "./simple.js"; 6 | 7 | export class IndexedGraph extends SimpleGraph { 8 | 9 | private readonly triplesBySubject: ReadonlyMultiMap; 10 | 11 | constructor(dataset: Iterable>) { 12 | super(dataset); 13 | this.triplesBySubject = Ix.from(dataset).concatMap(triples => triples).toMultiMap(triple => triple.subject, triple => triple); 14 | } 15 | 16 | override triples(subject?: IRIOrBlankNode, predicate?: IRI, object?: Term): Ix { 17 | if (subject) { 18 | let result = this.triplesBySubject.get(subject); 19 | result = predicate ? result.filter(triple => triple.predicate.equals(predicate)) : result; 20 | result = object ? result.filter(triple => triple.object.equals(object)) : result; 21 | return result; 22 | } 23 | return super.triples(subject, predicate, object); 24 | } 25 | 26 | list(list: IRIOrBlankNode): Term[] | null { 27 | const result: Term[] = []; 28 | while (list !== Rdf.nil) { 29 | let first: Term | null = null; 30 | let rest: IRIOrBlankNode | null = null; 31 | for (const triple of this.triplesBySubject.get(list)) { 32 | switch (triple.predicate) { 33 | case Rdf.first: 34 | if (first) { 35 | return null; 36 | } 37 | first = triple.object; 38 | break; 39 | case Rdf.rest: 40 | if (!IRIOrBlankNode.is(triple.object) || rest) { 41 | return null; 42 | } 43 | rest = triple.object; 44 | break; 45 | } 46 | } 47 | if (!first || !rest) { 48 | return null; 49 | } 50 | result.push(first); 51 | list = rest; 52 | } 53 | return result; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /packages/explorer-views/src/components/treeview.css: -------------------------------------------------------------------------------- 1 | .tree { 2 | --spacing: 2rem; 3 | --radius: 10px; 4 | margin: var(--spacing) 0; 5 | } 6 | 7 | .tree li { 8 | display: block; 9 | position: relative; 10 | padding-left: calc(2 * var(--spacing) - var(--radius) - 2px); 11 | } 12 | 13 | .tree ul { 14 | margin-left: calc(var(--radius) - var(--spacing)); 15 | padding-left: 0; 16 | } 17 | 18 | .tree ul li { 19 | border-left: 2px solid var(--base03); 20 | } 21 | 22 | .tree ul li:last-child { 23 | border-color: transparent; 24 | } 25 | 26 | .tree ul li::before { 27 | content: ''; 28 | display: block; 29 | position: absolute; 30 | top: calc(var(--spacing) / -2); 31 | left: -2px; 32 | width: var(--spacing); 33 | height: var(--spacing); 34 | border: solid var(--base03); 35 | border-width: 0 0 2px 2px; 36 | } 37 | 38 | .tree summary { 39 | display: block; 40 | cursor: pointer; 41 | } 42 | 43 | .tree summary::marker, 44 | .tree summary::-webkit-details-marker { 45 | display: none; 46 | } 47 | 48 | .tree summary:focus { 49 | outline: none; 50 | } 51 | 52 | .tree li::after, 53 | .tree summary::before { 54 | content: ''; 55 | display: block; 56 | position: absolute; 57 | top: calc(var(--spacing) / 2 - var(--radius)); 58 | left: calc(var(--spacing) - var(--radius) - 1px); 59 | width: calc(2 * var(--radius)); 60 | height: calc(2 * var(--radius)); 61 | border-radius: 50%; 62 | background: var(--base03); 63 | } 64 | 65 | .tree summary::before { 66 | content: '+'; 67 | z-index: 1; 68 | background: var(--base0D); 69 | color: var(--base07); 70 | line-height: calc(2 * var(--radius) - 1px); 71 | text-align: center; 72 | } 73 | 74 | .tree details[open] > summary::before { 75 | content: '\2212'; 76 | } 77 | 78 | .tree a { 79 | display: inline-block; 80 | } 81 | -------------------------------------------------------------------------------- /packages/schema/src/main.ts: -------------------------------------------------------------------------------- 1 | import { Graph } from "@rdf-toolkit/rdf/graphs"; 2 | import { IRI, IRIOrBlankNode, Term } from "@rdf-toolkit/rdf/terms"; 3 | import { Triple } from "@rdf-toolkit/rdf/triples"; 4 | import decompileGraph from "./decompiler.js"; 5 | import validateInstance, { ValidationResult } from "./validator.js"; 6 | 7 | export { ResultSeverity, ResultType } from "./validator.js"; 8 | export type { CardinalityError, ClassDeprecatedWarning, ExtraPropertyWarning, MissingClassError, NotSatisfiedError, PropertyDeprecatedWarning, ValidationResult } from "./validator.js"; 9 | 10 | export enum EntityKind { 11 | Class, 12 | Property, 13 | Ontology, 14 | } 15 | 16 | export interface ClassProperty { 17 | readonly id: IRI; 18 | readonly description?: string; 19 | readonly value: readonly Term[]; 20 | readonly minCount: bigint; 21 | readonly maxCount: bigint; 22 | readonly deprecated: boolean; 23 | } 24 | 25 | export interface Class { 26 | readonly kind: EntityKind.Class; 27 | readonly id: IRIOrBlankNode; 28 | readonly description?: string; 29 | readonly subClassOf: readonly IRIOrBlankNode[]; 30 | readonly properties: readonly ClassProperty[]; 31 | readonly deprecated: boolean; 32 | } 33 | 34 | export interface Property { 35 | readonly kind: EntityKind.Property; 36 | readonly id: IRI; 37 | readonly description?: string; 38 | readonly subPropertyOf: readonly IRI[]; 39 | readonly domainIncludes: readonly IRIOrBlankNode[]; 40 | } 41 | 42 | export interface Ontology { 43 | readonly kind: EntityKind.Ontology; 44 | readonly id: IRIOrBlankNode; 45 | readonly title?: string; 46 | readonly description?: string; 47 | readonly definitions: readonly IRIOrBlankNode[]; 48 | } 49 | 50 | export interface Schema { 51 | readonly classes: ReadonlyMap; 52 | readonly properties: ReadonlyMap; 53 | readonly ontologies: ReadonlyMap; 54 | } 55 | 56 | export namespace Schema { 57 | 58 | export function decompile(dataset: Iterable>, graph: Graph): Schema { 59 | return decompileGraph(dataset, graph); 60 | } 61 | 62 | export function validate(subject: IRIOrBlankNode, graph: Graph, schema: Schema): ValidationResult[] { 63 | return validateInstance(subject, graph, schema); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /packages/rdf/src/triples.ts: -------------------------------------------------------------------------------- 1 | import { Range } from "@rdf-toolkit/text"; 2 | import { BlankNode, IRI, Literal } from "./terms.js"; 3 | 4 | export enum TripleKind { 5 | Axiomatic, 6 | Inferred, 7 | Parsed, 8 | } 9 | 10 | export interface AxiomaticTriple { 11 | readonly kind: TripleKind.Axiomatic; 12 | readonly subject: IRI | BlankNode; 13 | readonly predicate: IRI; 14 | readonly object: IRI | BlankNode | Literal; 15 | } 16 | 17 | export interface InferredTriple { 18 | readonly kind: TripleKind.Inferred; 19 | readonly subject: IRI | BlankNode; 20 | readonly predicate: IRI; 21 | readonly object: IRI | BlankNode | Literal; 22 | readonly premise: Triple; 23 | } 24 | 25 | export interface ParsedTriple { 26 | readonly kind: TripleKind.Parsed; 27 | readonly subject: IRI | BlankNode; 28 | readonly predicate: IRI; 29 | readonly object: IRI | BlankNode | Literal; 30 | readonly location: TripleLocation; 31 | } 32 | 33 | export type Triple = 34 | | AxiomaticTriple 35 | | InferredTriple 36 | | ParsedTriple 37 | 38 | export interface TripleLocation { 39 | readonly uri: string; 40 | readonly subjectRange: Range, 41 | readonly predicateRange: Range, 42 | readonly objectRange: Range, 43 | } 44 | 45 | export namespace Triple { 46 | 47 | export function createAxiomatic(subject: IRI | BlankNode, predicate: IRI, object: IRI | BlankNode | Literal): AxiomaticTriple { 48 | return { kind: TripleKind.Axiomatic, subject, predicate, object }; 49 | } 50 | 51 | export function createInferred(subject: IRI | BlankNode, predicate: IRI, object: IRI | BlankNode | Literal, premise: Triple): InferredTriple { 52 | return { kind: TripleKind.Inferred, subject, predicate, object, premise }; 53 | } 54 | 55 | export function createParsed(subject: IRI | BlankNode, predicate: IRI, object: IRI | BlankNode | Literal, location: TripleLocation): ParsedTriple { 56 | return { kind: TripleKind.Parsed, subject, predicate, object, location }; 57 | } 58 | 59 | export function isAxiomatic(triple: Triple): triple is AxiomaticTriple { 60 | return triple.kind === TripleKind.Axiomatic; 61 | } 62 | 63 | export function isInferred(triple: Triple): triple is InferredTriple { 64 | return triple.kind === TripleKind.Inferred; 65 | } 66 | 67 | export function isParsed(triple: Triple): triple is ParsedTriple { 68 | return triple.kind === TripleKind.Parsed; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /packages/explorer-views/src/components/rdf.css: -------------------------------------------------------------------------------- 1 | .rdf-iri { 2 | overflow-wrap: anywhere; 3 | text-decoration: none !important; 4 | } 5 | 6 | .rdf-iri-openangle, 7 | .rdf-iri-closeangle { 8 | color: var(--base05); 9 | } 10 | 11 | .rdf-pname-ln .rdf-iri-prefixlabel { 12 | background: var(--base02); 13 | border-radius: 4px; 14 | color: var(--base05); 15 | font-size: small; 16 | letter-spacing: initial; 17 | padding: 3px 4px 2px; 18 | vertical-align: text-top; 19 | } 20 | 21 | .rdf-pname-ln .rdf-iri-colon { 22 | color: transparent !important; 23 | letter-spacing: -1px; 24 | margin: 0 -.1em; 25 | } 26 | 27 | a[href].rdf-pname-ln:active .rdf-iri-localname, 28 | a[href].rdf-pname-ln:hover .rdf-iri-localname, 29 | a[href].rdf-iri:active .rdf-iri-ref, 30 | a[href].rdf-iri:hover .rdf-iri-ref, 31 | a[href].rdf-iri-blanknode:active, 32 | a[href].rdf-iri-blanknode:hover, 33 | a[href].rdf-custom:active, 34 | a[href].rdf-custom:hover { 35 | text-decoration: underline !important; 36 | } 37 | 38 | .rdf-blanknode, 39 | .rdf-iri-blanknode { 40 | color: var(--base07); 41 | } 42 | 43 | .rdf-string { 44 | white-space: pre-wrap; 45 | overflow-wrap: anywhere; 46 | } 47 | 48 | .rdf-langstring-tag, 49 | .rdf-literal-caretcaret { 50 | color: var(--base04); 51 | } 52 | 53 | .rdf-anyURI::before { 54 | border-bottom: 1px solid var(--base04); 55 | border-left: 1px solid var(--base04); 56 | color: var(--base04); 57 | content: "\2197"; 58 | display: inline-block; 59 | font-size: .6em; 60 | line-height: 1; 61 | margin-right: 4px; 62 | padding: 0 0 1px 1px; 63 | text-decoration: none; 64 | vertical-align: middle; 65 | } 66 | 67 | .rdf-provenance { 68 | color: var(--base03); 69 | display: inline-block; 70 | font-size: small; 71 | letter-spacing: initial; 72 | } 73 | 74 | .rdf-provenance *, 75 | .rdf-provenance a { 76 | color: inherit !important; 77 | } 78 | 79 | .rdf-provenance .rdf-pname-ln .rdf-iri-prefixlabel { 80 | background: var(--base01); 81 | vertical-align: bottom; 82 | } 83 | 84 | .rdf-deprecated::after { 85 | color: var(--base08); 86 | content: '(deprecated)'; 87 | display: inline-block; 88 | font-size: small; 89 | letter-spacing: initial; 90 | padding-left: .3rem; 91 | } 92 | 93 | .rdf-error .rdf-iri-localname, 94 | .rdf-error .rdf-iri-ref, 95 | .rdf-error.rdf-custom, 96 | .rdf-error.rdf-pname-ns { 97 | border-bottom: 2px dotted var(--base08); 98 | } 99 | -------------------------------------------------------------------------------- /packages/turtle/src/syntax/token.ts: -------------------------------------------------------------------------------- 1 | import { SyntaxNode } from "./node.js"; 2 | import { SyntaxTrivia } from "./trivia.js"; 3 | import { SyntaxTokenValue, SyntaxTokenValues } from "./value.js"; 4 | 5 | export enum TokenKind { 6 | AKeyword = 200, 7 | TrueKeyword, 8 | FalseKeyword, 9 | AtPrefixKeyword, 10 | AtBaseKeyword, 11 | BaseKeyword, 12 | PrefixKeyword, 13 | 14 | OpenParen, 15 | CloseParen, 16 | Comma, 17 | Dot, 18 | Semicolon, 19 | OpenBracket, 20 | CloseBracket, 21 | 22 | CaretCaret, 23 | 24 | EndOfFile, 25 | 26 | BadToken, 27 | 28 | IRIREF, 29 | PNAME_NS, 30 | PNAME_LN, 31 | BLANK_NODE_LABEL, 32 | LANGTAG, 33 | INTEGER, 34 | DECIMAL, 35 | DOUBLE, 36 | STRING_LITERAL_QUOTE, 37 | STRING_LITERAL_SINGLE_QUOTE, 38 | STRING_LITERAL_LONG_SINGLE_QUOTE, 39 | STRING_LITERAL_LONG_QUOTE, 40 | } 41 | 42 | interface _SyntaxToken { 43 | readonly kind: T; 44 | readonly leadingTrivia: readonly SyntaxTrivia[]; 45 | readonly offset: number; 46 | readonly text: string; 47 | readonly trailingTrivia: readonly SyntaxTrivia[]; 48 | readonly value: SyntaxTokenValues[T], 49 | } 50 | 51 | export type SyntaxTokens = { readonly [T in TokenKind]: _SyntaxToken } 52 | 53 | export type SyntaxToken = SyntaxTokens[TokenKind] 54 | 55 | export namespace SyntaxToken { 56 | 57 | export function create(kind: T, value: SyntaxTokenValues[T], text?: string, leadingTrivia?: readonly SyntaxTrivia[], trailingTrivia?: readonly SyntaxTrivia[]): SyntaxTokens[T] { 58 | return { 59 | kind, 60 | leadingTrivia: leadingTrivia || [], 61 | offset: Number.NaN, 62 | text: text || SyntaxTokenValue.stringify(kind, value), 63 | trailingTrivia: trailingTrivia || [], 64 | value, 65 | } as SyntaxTokens[T]; 66 | } 67 | 68 | export function createMissing(kind: T): SyntaxTokens[T] { 69 | return create(kind, SyntaxTokenValue.getErrorTokenValue(kind)); 70 | } 71 | 72 | export function is(node: SyntaxTrivia | SyntaxToken | SyntaxNode): node is SyntaxToken; 73 | export function is(node: SyntaxTrivia | SyntaxToken | SyntaxNode, kind: T): node is SyntaxTokens[T]; 74 | export function is(node: SyntaxTrivia | SyntaxToken | SyntaxNode, kind?: TokenKind): node is SyntaxToken { 75 | return kind ? node.kind === kind : node.kind >= 200 && node.kind < 300; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /packages/explorer-views/src/sections/footer.tsx: -------------------------------------------------------------------------------- 1 | import { Literal } from "@rdf-toolkit/rdf/terms"; 2 | import { Xsd } from "@rdf-toolkit/rdf/vocab"; 3 | import renderRdfTerm from "../components/rdf-term.js"; 4 | import { RenderContext } from "../context.js"; 5 | import { HtmlContent } from "../jsx/html.js"; 6 | import "./footer.css"; 7 | 8 | const repositoryURL: Literal = Literal.create("https://github.com/ektrah/rdf-toolkit", Xsd.anyURI); 9 | 10 | export default function render(context: RenderContext): HtmlContent { 11 | return <> 12 |
    13 | Terms of use 14 |

    15 | This website uses heuristics to determine the schema of RDF vocabulary. 16 | This process is not perfect and may not always provide an accurate view of the underlying RDF data. 17 | Users are advised to use the output of this website as a guide only and to check the accuracy of the results against the original Turtle files. 18 |

    19 |

    20 | This website is provided “as is”, without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. 21 | In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the website or the use or other dealings in the website. 22 |

    23 |
    24 |
    25 | Privacy notice 26 |

    27 | This website does not collect or process any information provided by users. 28 | All information, including all Turtle files, is stored in the browser and is not uploaded to any servers. 29 | This ensures that your data is never shared with anyone and remains private. 30 |

    31 |
    32 |
    33 | Report a bug 34 |

    35 | If you find an issue with the functioning of this website, please report it in {renderRdfTerm(repositoryURL, context, { linkContents: "the GitHub repository", anyURIAsLink: true })} by opening a new issue. 36 | Include as much detail as possible in your report, including steps to reproduce the issue if possible. Thank you for helping improve RDF Explorer! 37 |

    38 |
    39 | ; 40 | } 41 | -------------------------------------------------------------------------------- /packages/cli/src/main.css: -------------------------------------------------------------------------------- 1 | @import url("./assets/fonts/iosevka-aile-custom.css"); 2 | @import url("./assets/themes/espresso.css"); 3 | 4 | *, *:before, *:after { 5 | box-sizing: inherit; 6 | font: inherit; 7 | } 8 | 9 | html { 10 | box-sizing: border-box; 11 | overscroll-behavior: none; 12 | } 13 | 14 | body { 15 | background: var(--base00); 16 | color: var(--base07); 17 | font: 300 17px/1.6 "Iosevka Aile Custom", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 18 | font-feature-settings: "calt" 0; 19 | margin: 0; 20 | overflow-y: scroll; 21 | padding: 0; 22 | } 23 | 24 | a { 25 | color: var(--base0A); 26 | text-decoration: none; 27 | } 28 | 29 | a:active, 30 | a:hover { 31 | text-decoration: underline; 32 | color: var(--base07) !important; 33 | } 34 | 35 | /*@import url("./links.css");*/ 36 | 37 | a { 38 | color: var(--base0A); 39 | text-decoration: none; 40 | } 41 | 42 | a:active, 43 | a:hover { 44 | text-decoration: underline; 45 | color: var(--base07) !important; 46 | } 47 | 48 | a[href^='http://example.com/.well-known/genid/'] { 49 | color: var(--base05); 50 | } 51 | 52 | a[href='http://www.w3.org/2002/07/owl'], 53 | a[href^='http://www.w3.org/2002/07/owl#'], 54 | a[href='http://www.w3.org/ns/shacl'], 55 | a[href^='http://www.w3.org/ns/shacl#'] { 56 | color: var(--base0B); 57 | } 58 | 59 | a[href='http://www.w3.org/1999/02/22-rdf-syntax-ns'], 60 | a[href^='http://www.w3.org/1999/02/22-rdf-syntax-ns#'], 61 | a[href='http://www.w3.org/2000/01/rdf-schema'], 62 | a[href^='http://www.w3.org/2000/01/rdf-schema#'] { 63 | color: var(--base0E); 64 | } 65 | 66 | a[href='http://www.w3.org/2001/XMLSchema'], 67 | a[href^='http://www.w3.org/2001/XMLSchema#'] { 68 | color: var(--base0F); 69 | } 70 | 71 | a[href^='https://brickschema.org/schema/Brick#'], 72 | a[href^='https://brickschema.org/schema/BrickShape#'] { 73 | color: var(--base0C); 74 | } 75 | 76 | a[href='https://w3id.org/rec'], 77 | a[href^='https://w3id.org/rec#'] { 78 | color: var(--base0D); 79 | } 80 | 81 | nav a[href='http://www.w3.org/2002/07/owl#Thing'] .rdf-iri-localname { 82 | border: 2px solid var(--base0B); 83 | border-radius: 4px; 84 | padding: 3px 5px 2px; 85 | } 86 | 87 | nav a[href='http://www.w3.org/2000/01/rdf-schema#Resource'] .rdf-iri-localname { 88 | border: 2px solid var(--base0E); 89 | border-radius: 4px; 90 | padding: 3px 5px 2px; 91 | } 92 | -------------------------------------------------------------------------------- /packages/rdf/src/terms/iri.ts: -------------------------------------------------------------------------------- 1 | import { IRIReference } from "@rdf-toolkit/text"; 2 | import { BlankNode } from "./blanknode.js"; 3 | import { Literal } from "./literal.js"; 4 | 5 | export interface IRI extends IRIReference { 6 | readonly termType: "NamedNode"; 7 | readonly value: string; 8 | compareTo(other: IRI | BlankNode | Literal): number; 9 | equals(other: IRI | BlankNode | Literal | null | undefined): boolean; 10 | } 11 | 12 | class InternedIRI implements IRI { 13 | 14 | static readonly InternPool: { [K in string]: InternedIRI } = {}; 15 | 16 | constructor( 17 | readonly value: string, 18 | readonly scheme: string, 19 | readonly authority: string | undefined, 20 | readonly path: string, 21 | readonly query: string | undefined, 22 | readonly fragment: string | undefined) { 23 | 24 | return InternedIRI.InternPool[value] || (InternedIRI.InternPool[value] = this); 25 | } 26 | 27 | get termType(): "NamedNode" { 28 | return "NamedNode"; 29 | } 30 | 31 | compareTo(other: IRI | BlankNode | Literal): number { 32 | switch (other.termType) { 33 | case "BlankNode": 34 | return -1; 35 | case "Literal": 36 | return 1; 37 | case "NamedNode": 38 | return this.value.localeCompare(other.value); 39 | } 40 | } 41 | 42 | equals(other: IRI | BlankNode | Literal | null | undefined): boolean { 43 | return other === this; 44 | } 45 | } 46 | 47 | export namespace IRI { 48 | 49 | export function create(value: string | IRIReference): IRI { 50 | const components = typeof value === "string" ? IRIReference.parse(value) : value; 51 | if (!components || typeof components.scheme !== "string") { 52 | throw new TypeError(); 53 | } 54 | const string = typeof value === "string" ? value : IRIReference.recompose(components); 55 | return new InternedIRI(string, components.scheme, components.authority, components.path, components.query, components.fragment); 56 | } 57 | 58 | export function is(term: IRI | BlankNode | Literal): term is IRI { 59 | return term.termType === "NamedNode"; 60 | } 61 | 62 | export const RdfLangString: IRI = create("http://www.w3.org/1999/02/22-rdf-syntax-ns#langString"); 63 | export const XsdBoolean: IRI = create("http://www.w3.org/2001/XMLSchema#boolean"); 64 | export const XsdDecimal: IRI = create("http://www.w3.org/2001/XMLSchema#decimal"); 65 | export const XsdDouble: IRI = create("http://www.w3.org/2001/XMLSchema#double"); 66 | export const XsdInteger: IRI = create("http://www.w3.org/2001/XMLSchema#integer"); 67 | export const XsdString: IRI = create("http://www.w3.org/2001/XMLSchema#string"); 68 | } 69 | -------------------------------------------------------------------------------- /packages/text/src/diagnostics.ts: -------------------------------------------------------------------------------- 1 | import { CodeDescription, Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, DiagnosticTag } from "vscode-languageserver-types"; 2 | import { DocumentUri } from "./documents.js"; 3 | 4 | export { CodeDescription, Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, DiagnosticTag }; 5 | 6 | export interface DiagnosticBag { 7 | [Symbol.iterator](): IterableIterator<[DocumentUri, Diagnostic]>; 8 | add(documentUri: DocumentUri, diagnostic: Diagnostic): void; 9 | clear(): void; 10 | delete(documentUri: DocumentUri): void; 11 | entries(): IterableIterator<[DocumentUri, Diagnostic]>; 12 | get(documentUri: DocumentUri): IterableIterator; 13 | has(documentUri: DocumentUri): boolean; 14 | keys(): IterableIterator; 15 | readonly errors: number; 16 | readonly warnings: number; 17 | } 18 | 19 | class FullDiagnosticBag implements DiagnosticBag { 20 | 21 | private bag: { [P in string]?: Diagnostic[] }; 22 | 23 | errors: number; 24 | warnings: number; 25 | 26 | constructor() { 27 | this.bag = {}; 28 | this.errors = 0; 29 | this.warnings = 0; 30 | } 31 | 32 | [Symbol.iterator](): IterableIterator<[string, Diagnostic]> { 33 | return this.entries(); 34 | } 35 | 36 | add(documentUri: DocumentUri, diagnostic: Diagnostic): void { 37 | (this.bag[documentUri] || (this.bag[documentUri] = [])).push(diagnostic); 38 | this.errors += diagnostic.severity === DiagnosticSeverity.Error ? 1 : 0; 39 | this.warnings += diagnostic.severity === DiagnosticSeverity.Warning ? 1 : 0; 40 | } 41 | 42 | clear(): void { 43 | this.bag = {}; 44 | this.errors = 0; 45 | this.warnings = 0; 46 | } 47 | 48 | delete(documentUri: DocumentUri): void { 49 | delete this.bag[documentUri]; 50 | 51 | } 52 | 53 | *entries(): IterableIterator<[DocumentUri, Diagnostic]> { 54 | for (const key in this.bag) { 55 | const items = this.bag[key]; 56 | if (items) { 57 | for (const item of items) { 58 | yield [key, item]; 59 | } 60 | } 61 | } 62 | } 63 | 64 | *get(key: DocumentUri): IterableIterator { 65 | const items = this.bag[key]; 66 | if (items) { 67 | yield* items; 68 | } 69 | } 70 | 71 | has(key: DocumentUri): boolean { 72 | return key in this.bag; 73 | } 74 | 75 | *keys(): IterableIterator { 76 | for (const key in this.bag) { 77 | yield key; 78 | } 79 | } 80 | } 81 | 82 | export namespace DiagnosticBag { 83 | 84 | export function create(): DiagnosticBag { 85 | return new FullDiagnosticBag(); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /packages/turtle/src/literate.ts: -------------------------------------------------------------------------------- 1 | import { DiagnosticBag, TextDocument } from "@rdf-toolkit/text"; 2 | import { TurtleScanner } from "./scanner.js"; 3 | import { SyntaxToken, SyntaxTrivia, TokenKind } from "./syntax.js"; 4 | 5 | export function* tokenizeLiterateTurtle(document: TextDocument, diagnostics: DiagnosticBag): Generator { 6 | type Mutable = { 7 | -readonly [Key in keyof Type]: Type[Key]; 8 | }; 9 | 10 | const regexp = /(?<=\n|\r|^)[ ]{0,3}([`]{3,}|[~]{3,})([^\n\r]*)(?:\n|\r\n?|$)/ug; 11 | const text = document.getText(); 12 | 13 | let offset = 0; 14 | 15 | for (; ;) { 16 | const m = regexp.exec(text); 17 | if (!m) { 18 | break; 19 | } 20 | 21 | const start = regexp.lastIndex; 22 | let end: number; 23 | for (; ;) { 24 | const n = regexp.exec(text); 25 | if (!n) { 26 | end = text.length; 27 | break; 28 | } 29 | if (n[1].startsWith(m[1])) { 30 | end = n.index; 31 | break; 32 | } 33 | } 34 | 35 | if (/^\s*turtle\b/ui.test(m[2])) { 36 | const subDocument = TextDocument.create(document.uri, "turtle", document.version, text.slice(0, end)); 37 | const tokens = [...new TurtleScanner(subDocument, start, diagnostics)]; 38 | const endOfFileToken = tokens.pop(); 39 | 40 | if (endOfFileToken && tokens.length) { 41 | const firstToken: Mutable = tokens[0]; 42 | const lastToken: Mutable = tokens[tokens.length - 1]; 43 | 44 | firstToken.leadingTrivia = textToTrivia(text.slice(offset, start)).concat(firstToken.leadingTrivia); 45 | lastToken.trailingTrivia = (lastToken.trailingTrivia).concat(endOfFileToken.leadingTrivia, endOfFileToken.trailingTrivia); 46 | 47 | offset = end; 48 | yield* tokens; 49 | } 50 | } 51 | } 52 | 53 | yield { 54 | kind: TokenKind.EndOfFile, 55 | leadingTrivia: textToTrivia(text.slice(offset)), 56 | offset: text.length, 57 | text: "", 58 | trailingTrivia: [], 59 | value: undefined, 60 | }; 61 | } 62 | 63 | function textToTrivia(text: string): SyntaxTrivia[] { 64 | const parts = text.split(/(\n|\r\n?)/ug); 65 | const trivia: SyntaxTrivia[] = []; 66 | let i = 0; 67 | while (i + 1 < parts.length) { 68 | if (parts[i]) { 69 | trivia.push(SyntaxTrivia.createComment(parts[i])); 70 | } 71 | trivia.push(SyntaxTrivia.createEndOfLine(parts[i + 1])); 72 | i += 2; 73 | } 74 | if (i < parts.length) { 75 | if (parts[i]) { 76 | trivia.push(SyntaxTrivia.createComment(parts[i])); 77 | } 78 | } 79 | return trivia; 80 | } 81 | -------------------------------------------------------------------------------- /packages/cli/src/commands/add-file.ts: -------------------------------------------------------------------------------- 1 | import { DocumentUri } from "@rdf-toolkit/text"; 2 | import { PrefixDirective, SymbolTable, SyntaxKind } from "@rdf-toolkit/turtle"; 3 | import * as os from "node:os"; 4 | import * as process from "node:process"; 5 | import { printDiagnosticsAndExitOnError } from "../diagnostics.js"; 6 | import { PACKAGE_JSON } from "../model/package.js"; 7 | import { Project } from "../model/project.js"; 8 | import { DiagnosticOptions, ProjectOptions } from "../options.js"; 9 | import { Is } from "../type-checks.js"; 10 | 11 | type Options = 12 | & DiagnosticOptions 13 | & ProjectOptions 14 | 15 | export default function main(documentURI: DocumentUri, filePath: string, options: Options): void { 16 | const project = new Project(options.project); 17 | const json = project.package.json; 18 | 19 | filePath = project.package.relative(filePath); 20 | 21 | const diagnostics = project.diagnostics; 22 | const syntaxTree = project.package.readSyntaxTree(documentURI, filePath, diagnostics); 23 | const symbolTable = SymbolTable.from(syntaxTree, project.diagnostics); 24 | printDiagnosticsAndExitOnError(diagnostics, options); 25 | 26 | const files = Is.record(json["rdf:files"], Is.string) ? json["rdf:files"] : {}; 27 | const prefixes = Is.record(json["rdf:prefixes"], Is.string) ? json["rdf:prefixes"] : {}; 28 | if (files[documentURI] !== filePath) { 29 | files[documentURI] = filePath; 30 | json["rdf:files"] = files; 31 | 32 | let preferredPrefix: PrefixDirective | undefined; 33 | for (const statement of syntaxTree.root.statements) { 34 | if (statement.kind === SyntaxKind.PrefixDirective || statement.kind === SyntaxKind.SparqlPrefixDirective) { 35 | const prefix = symbolTable.get(statement); 36 | if (prefix && prefix.prefixLabel) { 37 | try { 38 | const url = new URL(prefix.namespaceIRI); 39 | url.hash = ""; 40 | if (url.href === documentURI) { 41 | preferredPrefix = prefix; 42 | break; 43 | } 44 | } 45 | catch { 46 | // continue regardless of error 47 | } 48 | } 49 | } 50 | } 51 | 52 | if (preferredPrefix && !Object.values(prefixes).includes(preferredPrefix.namespaceIRI)) { 53 | prefixes[preferredPrefix.prefixLabel] = preferredPrefix.namespaceIRI; 54 | json["rdf:prefixes"] = prefixes; 55 | } 56 | 57 | project.package.writeJSON(PACKAGE_JSON, json, false); 58 | 59 | process.stdout.write("<"); 60 | process.stdout.write(documentURI); 61 | process.stdout.write("> \u2192 "); 62 | process.stdout.write(filePath); 63 | process.stdout.write(os.EOL); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /packages/cli/src/model/ontology.ts: -------------------------------------------------------------------------------- 1 | import { IRI, Literal } from "@rdf-toolkit/rdf/terms"; 2 | import { Owl, Rdf, Xsd } from "@rdf-toolkit/rdf/vocab"; 3 | import { SubjectPredicateObjectListSyntax, SymbolTable, SyntaxKind, SyntaxNode, SyntaxTree } from "@rdf-toolkit/turtle"; 4 | 5 | export interface Ontology { 6 | readonly ontologyIRI: string; 7 | readonly versionIRI: string | undefined; 8 | readonly imports: ReadonlyArray; 9 | } 10 | 11 | export namespace Ontology { 12 | 13 | export function from(syntaxTree: SyntaxTree, symbolTable: SymbolTable): Ontology | undefined { 14 | const statement = findOWLOntology(syntaxTree, symbolTable); 15 | if (!statement) { 16 | return; 17 | } 18 | 19 | const subject = symbolTable.get(statement.subject); 20 | if (!subject) { 21 | return; 22 | } 23 | 24 | const imports = new Set(); 25 | for (const verbObjectList of SyntaxNode.iteratePredicateObjectList(statement.predicateObjectList)) { 26 | if (symbolTable.get(verbObjectList.verb) === Owl.imports) { 27 | for (const node of SyntaxNode.iterateObjectList(verbObjectList.objectList)) { 28 | const object = symbolTable.get(node); 29 | if (object && (IRI.is(object) || (Literal.is(object) && (object.datatype === Xsd.string || object.datatype === Xsd.anyURI)))) { 30 | imports.add(object.value); 31 | } 32 | } 33 | } 34 | } 35 | 36 | return { 37 | ontologyIRI: subject.value, 38 | versionIRI: undefined, // TODO 39 | imports: Array.from(imports) 40 | }; 41 | } 42 | } 43 | 44 | function findOWLOntology(syntaxTree: SyntaxTree, symbolTable: SymbolTable): SubjectPredicateObjectListSyntax | undefined { 45 | let ontology: SubjectPredicateObjectListSyntax | undefined; 46 | 47 | for (const statement of syntaxTree.root.statements) { 48 | if (statement.kind === SyntaxKind.SubjectPredicateObjectList) { 49 | for (const verbObjectList of SyntaxNode.iteratePredicateObjectList(statement.predicateObjectList)) { 50 | if (symbolTable.get(verbObjectList.verb) === Rdf.type) { 51 | for (const node of SyntaxNode.iterateObjectList(verbObjectList.objectList)) { 52 | if (symbolTable.get(node) === Owl.Ontology) { 53 | if (ontology && ontology !== statement) { 54 | // TODO: report error 55 | return; 56 | } 57 | ontology = statement; 58 | } 59 | } 60 | } 61 | } 62 | } 63 | } 64 | 65 | if (!ontology) { 66 | // TODO: report error 67 | return; 68 | } 69 | 70 | return ontology; 71 | } 72 | -------------------------------------------------------------------------------- /packages/explorer-views/src/sections/validation.tsx: -------------------------------------------------------------------------------- 1 | import { Ix } from "@rdf-toolkit/iterable"; 2 | import { IRIOrBlankNode } from "@rdf-toolkit/rdf/terms"; 3 | import { ResultSeverity, ResultType, Schema, ValidationResult } from "@rdf-toolkit/schema"; 4 | import renderCardinality from "../components/cardinality.js"; 5 | import renderRdfTerm, { RenderOptions } from "../components/rdf-term.js"; 6 | import { RenderContext } from "../context.js"; 7 | import { HtmlContent } from "../jsx/html.js"; 8 | import "./validation.css"; 9 | 10 | function renderResult(result: ValidationResult, context: RenderContext): HtmlContent { 11 | const options: RenderOptions = { rawBlankNodes: true, rawLiterals: true }; 12 | switch (result.type) { 13 | case ResultType.PropertyDeprecated: 14 | return {renderRdfTerm(result.property.id, context, options)} is deprecated; 15 | case ResultType.ClassDeprecated: 16 | return {renderRdfTerm(result.class.id, context, options)} is deprecated; 17 | case ResultType.ExtraProperty: 18 | return {renderRdfTerm(result.propertyID, context, options)} is extra; 19 | case ResultType.NoValue: 20 | return {renderRdfTerm(result.property.id, context, options)} has empty value range; 21 | 22 | case ResultType.Cardinality: 23 | return {renderRdfTerm(result.property.id, context, options)} ▹ found {result.actualCount} but expected {renderCardinality(result.property.minCount, result.property.maxCount, { noSymbols: true })}; 24 | case ResultType.MissingClass: 25 | return {renderRdfTerm(result.classID, context, options)} is not in schema; 26 | case ResultType.NotSatisfied: 27 | return {renderRdfTerm(result.property.id, context, options)} ▹ {renderRdfTerm(result.object, context, options)} is not an instance of/equal to {Ix.from(result.property.value).map(v => renderRdfTerm(v, context, options)).intersperse(" or ")}; 28 | } 29 | } 30 | 31 | function renderResultWithSeverity(result: ValidationResult, context: RenderContext): HtmlContent { 32 | switch (result.severity) { 33 | case ResultSeverity.Error: 34 | return <>error: {renderResult(result, context)}; 35 | case ResultSeverity.Warning: 36 | return <>warning: {renderResult(result, context)}; 37 | } 38 | } 39 | 40 | export default function render(subject: IRIOrBlankNode, context: RenderContext): HtmlContent { 41 | const results: ValidationResult[] = Schema.validate(subject, context.graph, context.schema); 42 | 43 | return results.length ? 44 |
    45 |

    Validation

    46 |
      47 | {Ix.from(results).map(r =>
    • {renderResultWithSeverity(r, context)}
    • )} 48 |
    49 |
    : <>; 50 | } 51 | -------------------------------------------------------------------------------- /packages/explorer-views/src/components/rdf-triple.tsx: -------------------------------------------------------------------------------- 1 | import { BlankNode, IRI } from "@rdf-toolkit/rdf/terms"; 2 | import { Triple, TripleLocation } from "@rdf-toolkit/rdf/triples"; 3 | import { RenderContext } from "../context.js"; 4 | import { HtmlContent } from "../jsx/html.js"; 5 | import renderRdfTerm, { RenderOptions } from "./rdf-term.js"; 6 | import "./rdf.css"; 7 | 8 | function renderLocation(location: TripleLocation, context: RenderContext): HtmlContent { 9 | return 10 | {renderRdfTerm(IRI.create(location.uri), context, { expandIRIs: true, rawIRIs: true, hideError: true })} 11 | {":"} 12 | {location.objectRange.start.line + 1} 13 | {":"} 14 | {location.objectRange.start.character + 1} 15 | ; 16 | } 17 | 18 | function renderProvenance(triple: Triple, context: RenderContext): HtmlContent { 19 | if (Triple.isParsed(triple)) { 20 | return <> [from {renderLocation(triple.location, context)}]; 21 | } 22 | else if (Triple.isAxiomatic(triple)) { 23 | return <> [axiom]; 24 | } 25 | else if (Triple.isInferred(triple)) { 26 | for (let i = 0; i < 10 && Triple.isInferred(triple); i++) { 27 | triple = triple.premise; 28 | } 29 | if (Triple.isParsed(triple)) { 30 | return <> [inferred from {renderLocation(triple.location, context)}]; 31 | } 32 | else if (Triple.isAxiomatic(triple)) { 33 | return <> [inferred from axiom]; 34 | } 35 | else { 36 | return <> [inferred]; 37 | } 38 | } 39 | else { 40 | return <>; 41 | } 42 | } 43 | 44 | function renderPredicateAndObject(triple: Triple, context: RenderContext, options: RenderOptions): HtmlContent { 45 | if (BlankNode.is(triple.subject)) { 46 | options = Object.assign({}, options, { hiddenNodes: [triple.subject, ...options.hiddenNodes || []] }); 47 | } 48 | 49 | return <> 50 | {renderRdfTerm(triple.predicate, context, options)} 51 | {" "} 52 | {renderRdfTerm(triple.object, context, options)} 53 | ; 54 | } 55 | 56 | function renderSubjectAndPredicateAndObject(triple: Triple, context: RenderContext, options: RenderOptions): HtmlContent { 57 | return <> 58 | {renderRdfTerm(triple.subject, context, options)} 59 | {IRI.is(triple.subject) ? <> {renderPredicateAndObject(triple, context, options)} : null} 60 | ; 61 | } 62 | 63 | export default function render(triple: Triple, context: RenderContext, options: RenderOptions = {}, includeSubject = false): HtmlContent { 64 | return <> 65 | {includeSubject ? renderSubjectAndPredicateAndObject(triple, context, options) : renderPredicateAndObject(triple, context, options)} 66 | {renderProvenance(triple, context)} 67 | ; 68 | } 69 | -------------------------------------------------------------------------------- /packages/explorer-views/src/sections/schema-ontology.tsx: -------------------------------------------------------------------------------- 1 | import { Ix } from "@rdf-toolkit/iterable"; 2 | import { IRI, IRIOrBlankNode } from "@rdf-toolkit/rdf/terms"; 3 | import { Owl } from "@rdf-toolkit/rdf/vocab"; 4 | import { Ontology } from "@rdf-toolkit/schema"; 5 | import renderMarkdown from "../components/markdown.js"; 6 | import renderRdfTerm from "../components/rdf-term.js"; 7 | import { RenderContext } from "../context.js"; 8 | import { HtmlContent } from "../jsx/html.js"; 9 | import "./schema.css"; 10 | 11 | function renderOntology(ontology: Ontology, context: RenderContext): HtmlContent { 12 | return <> 13 | { 14 | ontology.title ? 15 |
    16 | {renderMarkdown(ontology.title, context, IRI.is(ontology.id) ? ontology.id : null)} 17 |
    : null 18 | } 19 | { 20 | ontology.description ? 21 |
    22 |

    Overview

    23 | {renderMarkdown(ontology.description, context, IRI.is(ontology.id) ? ontology.id : null)} 24 |
    : null 25 | } 26 | { 27 | context.graph.objects(ontology.id, Owl.imports) 28 | .sort((a, b) => a.compareTo(b)) 29 | .wrap(imports => 30 |
    31 |

    Imports

    32 |
      33 | {imports.map(import_ =>
    • {renderRdfTerm(import_, context, { expandIRIs: true, rawIRIs: true, rawBlankNodes: true, rawLiterals: true })}
    • )} 34 |
    35 |
    , null) 36 | } 37 | { 38 | Ix.from(ontology.definitions) 39 | .ofType(IRI.is) 40 | .filter(term => context.graph.isClass(term)) 41 | .wrap(classes => 42 |
    43 |

    Classes

    44 |
      45 | {classes.map(class_ =>
    • {renderRdfTerm(class_, context, { hideBlankNodeDetails: true })}
    • )} 46 |
    47 |
    , null) 48 | } 49 | { 50 | Ix.from(ontology.definitions) 51 | .ofType(IRI.is) 52 | .filter(term => context.graph.isProperty(term)) 53 | .wrap(properties => 54 |
    55 |

    Properties

    56 |
      57 | {properties.map(property =>
    • {renderRdfTerm(property, context, { hideBlankNodeDetails: true })}
    • )} 58 |
    59 |
    , null) 60 | } 61 | ; 62 | } 63 | 64 | export default function render(subject: IRIOrBlankNode, context: RenderContext): HtmlContent { 65 | const ontology = context.schema.ontologies.get(subject); 66 | return ontology ? renderOntology(ontology, context) : <>; 67 | } 68 | -------------------------------------------------------------------------------- /packages/schema/src/decompiler/owl.ts: -------------------------------------------------------------------------------- 1 | import { Graph } from "@rdf-toolkit/rdf/graphs"; 2 | import { IRI, IRIOrBlankNode, Literal } from "@rdf-toolkit/rdf/terms"; 3 | import { Owl } from "@rdf-toolkit/rdf/vocab"; 4 | import { splitRange } from "./rdfs.js"; 5 | import { flattenOwlIntersections, getDescription, SchemaBuilder } from "./utils.js"; 6 | 7 | // 8 | // ?class a owl:Restriction; owl:onProperty ?property 9 | // 10 | // ?class rdf:subClassOf [ a owl:Restriction; owl:onProperty ?property ] 11 | // 12 | // ?class rdf:subClassOf [ a owl:Class; owl:intersectionOf ( [ a owl:Restriction; owl:onProperty ?property ] ) ] 13 | // 14 | export default function decompile(graph: Graph, builder: SchemaBuilder): void { 15 | for (const class_ of graph.classes()) { 16 | if (graph.isInstanceOf(class_, Owl.Restriction)) { 17 | decompileOWLRestriction(class_, graph, class_, builder); 18 | } 19 | else { 20 | for (const superClass of graph.getDirectSuperClasses(class_)) { 21 | for (const intersected of flattenOwlIntersections(superClass, graph)) { 22 | if (graph.isInstanceOf(intersected, Owl.Restriction)) { 23 | decompileOWLRestriction(intersected, graph, class_, builder); 24 | } 25 | } 26 | } 27 | } 28 | } 29 | } 30 | 31 | function decompileOWLRestriction(restriction: IRIOrBlankNode, graph: Graph, class_: IRIOrBlankNode, builder: SchemaBuilder): void { 32 | const properties = graph.objects(restriction, Owl.onProperty) 33 | .ofType(IRI.is); 34 | 35 | const values = graph.objects(restriction, Owl.onClass) 36 | .concat(graph.objects(restriction, Owl.onDataRange)) 37 | .concat(graph.objects(restriction, Owl.allValuesFrom)) 38 | .concat(graph.objects(restriction, Owl.someValuesFrom)) 39 | .concat(graph.objects(restriction, Owl.hasValue)) 40 | .concatMap(x => splitRange(x, graph)); 41 | 42 | const minCount = graph.objects(restriction, Owl.cardinality) 43 | .concat(graph.objects(restriction, Owl.minCardinality)) 44 | .concat(graph.objects(restriction, Owl.qualifiedCardinality)) 45 | .concat(graph.objects(restriction, Owl.minQualifiedCardinality)) 46 | .ofType(Literal.hasBigIntValue) 47 | .map(x => x.valueAsBigInt) 48 | .firstOrDefault(undefined); 49 | 50 | const maxCount = graph.objects(restriction, Owl.cardinality) 51 | .concat(graph.objects(restriction, Owl.maxCardinality)) 52 | .concat(graph.objects(restriction, Owl.qualifiedCardinality)) 53 | .concat(graph.objects(restriction, Owl.maxQualifiedCardinality)) 54 | .ofType(Literal.hasBigIntValue) 55 | .map(x => x.valueAsBigInt) 56 | .firstOrDefault(undefined); 57 | 58 | builder.addClass(class_, getDescription(class_, graph)); 59 | for (const property of properties) { 60 | builder.addClassProperty(class_, property, getDescription(property, graph), minCount, maxCount); 61 | builder.addPropertyClass(property, class_); 62 | for (const value of values.concatIfEmpty(graph.getPropertyRange(property).concatMap(x => splitRange(x, graph)))) { 63 | builder.addClassPropertyValue(class_, property, value); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /packages/cli/src/workspace.ts: -------------------------------------------------------------------------------- 1 | import { DiagnosticBag, TextDocument } from "@rdf-toolkit/text"; 2 | import { SyntaxTree } from "@rdf-toolkit/turtle"; 3 | import * as fs from "node:fs"; 4 | import * as os from "node:os"; 5 | import * as path from "node:path"; 6 | 7 | export class Workspace { 8 | 9 | constructor(readonly directoryPath: string) { 10 | } 11 | 12 | access(filePath: string, mode?: number): void { 13 | fs.accessSync(this.resolve(filePath), mode); 14 | } 15 | 16 | exists(filePath: string): boolean { 17 | return fs.existsSync(this.resolve(filePath)); 18 | } 19 | 20 | read(filePath: string): Buffer { 21 | return fs.readFileSync(this.resolve(filePath), { flag: "r" }); 22 | } 23 | 24 | readJSON(filePath: string): unknown { 25 | return JSON.parse(this.readText(filePath)); 26 | } 27 | 28 | readSyntaxTree(documentURI: string, filePath: string, diagnostics: DiagnosticBag): SyntaxTree { 29 | return SyntaxTree.parse(this.readTextDocument(documentURI, filePath), diagnostics); 30 | } 31 | 32 | readText(filePath: string): string { 33 | const content = fs.readFileSync(this.resolve(filePath), { encoding: "utf-8", flag: "r" }); 34 | return content.startsWith("\uFEFF") ? content.slice(1) : content; 35 | } 36 | 37 | readTextDocument(documentURI: string, filePath: string): TextDocument { 38 | // see 39 | const language = 40 | /[.](?:md|mkdn?|mdwn|mdown|markdown)$/i.test(filePath) ? "markdown" : 41 | /[.](?:ttl)$/i.test(filePath) ? "turtle" : 42 | /[.](?:owl|rdf|xml)$/i.test(filePath) ? "xml" : 43 | "plaintext"; 44 | return TextDocument.create(documentURI, language, 0, this.readText(filePath)); 45 | } 46 | 47 | relative(filePath: string): string { 48 | filePath = path.relative(this.directoryPath, filePath); 49 | return path.isAbsolute(filePath) ? filePath : path.posix.join(...filePath.split(path.sep)); 50 | } 51 | 52 | resolve(filePath: string): string { 53 | return path.resolve(this.directoryPath, filePath); 54 | } 55 | 56 | write(filePath: string, data: Buffer, preventOverwrite?: boolean): void { 57 | filePath = this.resolve(filePath); 58 | fs.mkdirSync(path.dirname(filePath), { recursive: true }); 59 | fs.writeFileSync(filePath, data, { encoding: null, flag: preventOverwrite ? "wx" : "w" }); 60 | } 61 | 62 | writeJSON(filePath: string, value: any, preventOverwrite?: boolean): void { 63 | const data = JSON.stringify(value, undefined, 2).replace(/\n|\r\n?/g, os.EOL); 64 | this.writeText(filePath, data.endsWith(os.EOL) ? data : data + os.EOL, preventOverwrite); 65 | } 66 | 67 | writeSyntaxTree(filePath: string, syntaxTree: SyntaxTree, preventOverwrite?: boolean): void { 68 | this.writeTextDocument(filePath, syntaxTree.document, preventOverwrite); 69 | } 70 | 71 | writeText(filePath: string, text: string, preventOverwrite?: boolean): void { 72 | filePath = this.resolve(filePath); 73 | fs.mkdirSync(path.dirname(filePath), { recursive: true }); 74 | fs.writeFileSync(filePath, text, { encoding: "utf-8", flag: preventOverwrite ? "wx" : "w" }); 75 | } 76 | 77 | writeTextDocument(filePath: string, document: TextDocument, preventOverwrite?: boolean): void { 78 | this.writeText(filePath, document.getText(), preventOverwrite); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /packages/explorer-views/src/pages/navigation.tsx: -------------------------------------------------------------------------------- 1 | import { Ix } from "@rdf-toolkit/iterable"; 2 | import { IRI, IRIOrBlankNode } from "@rdf-toolkit/rdf/terms"; 3 | import { Owl, Rdfs } from "@rdf-toolkit/rdf/vocab"; 4 | import { Class, Ontology, Property } from "@rdf-toolkit/schema"; 5 | import renderRdfTerm, { RenderOptions } from "../components/rdf-term.js"; 6 | import renderTabView from "../components/tabview.js"; 7 | import renderTreeView, { TreeNode } from "../components/treeview.js"; 8 | import { RenderContext } from "../context.js"; 9 | import { HtmlContent } from "../jsx/html.js"; 10 | import "./navigation.css"; 11 | 12 | function createTree(items: Iterable, parents: (item: T) => Iterable, context: RenderContext, rootIRIs: Iterable | null, options?: RenderOptions): TreeNode[] { 13 | const roots: TreeNode[] = []; 14 | const tree: { [P in string]: { readonly label: HtmlContent; readonly children: TreeNode[], readonly open: boolean } } = {}; 15 | 16 | for (const item of items) { 17 | if (IRI.is(item.id)) { 18 | const node = tree[item.id.value] || (tree[item.id.value] = { label: renderRdfTerm(item.id, context, options), children: [], open: item.id === Rdfs.Resource || item.id === Owl.Thing }); 19 | let hasParents = false; 20 | for (const parent of parents(item)) { 21 | (tree[parent.value] || (tree[parent.value] = { label: renderRdfTerm(parent, context, options), children: [], open: parent === Rdfs.Resource || parent === Owl.Thing })).children.push(node); 22 | hasParents = true; 23 | } 24 | if (!hasParents) { 25 | roots.push(node); 26 | } 27 | } 28 | } 29 | 30 | if (rootIRIs) { 31 | roots.length = 0; 32 | for (const rootIRI of rootIRIs) { 33 | const node = tree[rootIRI]; 34 | if (node) { 35 | roots.push(node); 36 | } 37 | } 38 | } 39 | 40 | return roots; 41 | } 42 | 43 | export default function render(title: string | undefined, context: RenderContext): HtmlContent { 44 | return <> 45 | 46 | { 47 | renderTabView("navigation", [ 48 | { 49 | id: "navigation-classes", 50 | label: "Classes", 51 | content: renderTreeView(createTree(context.schema.classes.values(), x => x.subClassOf, context, context.rootClasses, { rawBlankNodes: true })) 52 | }, 53 | { 54 | id: "navigation-properties", 55 | label: "Properties", 56 | content: renderTreeView(createTree(context.schema.properties.values(), x => x.subPropertyOf, context, null, { rawBlankNodes: true })) 57 | }, 58 | { 59 | id: "navigation-ontologies", 60 | label: "Ontologies", 61 | content: renderTreeView(createTree(context.schema.ontologies.values(), () => Ix.empty, context, null, { rawIRIs: true, rawBlankNodes: true })) 62 | }, 63 | { 64 | id: "navigation-files", 65 | label: "Files", 66 | content: renderTreeView(Object.keys(context.documents).sort().map(x => ({ label: renderRdfTerm(IRI.create(x), context, { rawIRIs: true, hideError: true }) }))) 67 | }, 68 | ]) 69 | } 70 | ; 71 | } 72 | -------------------------------------------------------------------------------- /packages/cli/src/options.ts: -------------------------------------------------------------------------------- 1 | import * as path from "node:path"; 2 | import * as yargs from "yargs"; 3 | 4 | export type AllOption = { 5 | readonly all: boolean | undefined; 6 | } 7 | 8 | export type DiagnosticOptions = { 9 | readonly noWarn: boolean | undefined; 10 | readonly warnAsError: boolean | undefined; 11 | } 12 | 13 | export type ForceOptions = { 14 | readonly force: boolean | undefined; 15 | } 16 | 17 | export type MakeOptions = { 18 | readonly output: string | undefined; 19 | } 20 | 21 | export type ProjectOptions = { 22 | readonly project: string; 23 | } 24 | 25 | export type RecursiveOptions = { 26 | readonly recursive: boolean | undefined; 27 | } 28 | 29 | export type ServerOptions = { 30 | readonly root: string | undefined; 31 | } 32 | 33 | export type SiteOptions = { 34 | readonly base: string | undefined; 35 | } 36 | 37 | export namespace Options { 38 | 39 | export const all = { 40 | "all": { 41 | alias: "a", 42 | description: "List all items", 43 | nargs: 0, 44 | type: "boolean" 45 | } 46 | } satisfies Record; 47 | 48 | export const base = { 49 | "base": { 50 | alias: "B", 51 | description: "Specify the base URL for generated pages", 52 | nargs: 1, 53 | type: "string" 54 | } 55 | } satisfies Record; 56 | 57 | export const force = { 58 | "force": { 59 | description: "Force overwriting existing files", 60 | nargs: 0, 61 | type: "boolean" 62 | } 63 | } satisfies Record; 64 | 65 | export const noWarnings = { 66 | "no-warn": { 67 | description: "Suppress all warnings", 68 | nargs: 0, 69 | type: "boolean" 70 | } 71 | } satisfies Record; 72 | 73 | export const output = { 74 | "output": { 75 | alias: "o", 76 | description: "Specify the output directory for generated files", 77 | nargs: 1, 78 | type: "string", 79 | coerce: (arg: string) => path.resolve(arg), 80 | } 81 | } satisfies Record; 82 | 83 | export const project = { 84 | "project": { 85 | alias: "p", 86 | description: "Specify the project directory", 87 | nargs: 1, 88 | type: "string", 89 | default: ".", 90 | coerce: (arg: string) => path.resolve(arg), 91 | } 92 | } satisfies Record; 93 | 94 | export const recursive = { 95 | "recursive": { 96 | alias: "r", 97 | description: "List items recursively", 98 | nargs: 0, 99 | type: "boolean" 100 | } 101 | } satisfies Record; 102 | 103 | export const root = { 104 | "root": { 105 | description: "Specify the root directory", 106 | nargs: 1, 107 | type: "string", 108 | coerce: (arg: string) => path.resolve(arg), 109 | } 110 | } satisfies Record; 111 | 112 | export const warnAsError = { 113 | "warn-as-error": { 114 | alias: "W", 115 | description: "Treat all warnings as errors", 116 | nargs: 0, 117 | type: "boolean" 118 | } 119 | } satisfies Record; 120 | } 121 | -------------------------------------------------------------------------------- /packages/rdf/src/engines/rdf.ts: -------------------------------------------------------------------------------- 1 | import { MultiMap } from "@rdf-toolkit/iterable"; 2 | import { IRIOrBlankNode } from "../terms.js"; 3 | import { Triple } from "../triples.js"; 4 | import { Rdf, Rdfs } from "../vocab.js"; 5 | 6 | // https://www.w3.org/TR/2014/REC-rdf11-mt-20140225/#rdf-interpretations 7 | export class RDFEngine { 8 | 9 | readonly typeMap: MultiMap; // {Class} <-- rdf:type -- {Resource} 10 | 11 | readonly firstMap: Map = new Map(); 12 | readonly restMap: Map = new Map(); 13 | 14 | constructor() { 15 | this.typeMap = new MultiMap(); 16 | } 17 | 18 | ingest(triple: Triple): boolean { 19 | switch (triple.predicate) { 20 | case Rdf.type: 21 | return IRIOrBlankNode.is(triple.object) && this.typeMap.add(triple.object, triple.subject); 22 | case Rdf.first: 23 | this.firstMap.set(triple.subject, triple); 24 | return false; 25 | case Rdf.rest: 26 | this.restMap.set(triple.subject, triple); 27 | return false; 28 | default: 29 | return false; 30 | } 31 | } 32 | 33 | *beforeinterpret(): Generator { 34 | yield Triple.createAxiomatic(Rdf.type, Rdf.type, Rdf.Property); 35 | yield Triple.createAxiomatic(Rdf.subject, Rdf.type, Rdf.Property); 36 | yield Triple.createAxiomatic(Rdf.predicate, Rdf.type, Rdf.Property); 37 | yield Triple.createAxiomatic(Rdf.object, Rdf.type, Rdf.Property); 38 | yield Triple.createAxiomatic(Rdf.first, Rdf.type, Rdf.Property); 39 | yield Triple.createAxiomatic(Rdf.rest, Rdf.type, Rdf.Property); 40 | yield Triple.createAxiomatic(Rdf.value, Rdf.type, Rdf.Property); 41 | yield Triple.createAxiomatic(Rdf.nil, Rdf.type, Rdf.List); 42 | 43 | yield Triple.createAxiomatic(Rdf.type, Rdfs.domain, Rdfs.Resource); 44 | yield Triple.createAxiomatic(Rdf.subject, Rdfs.domain, Rdf.Statement); 45 | yield Triple.createAxiomatic(Rdf.predicate, Rdfs.domain, Rdf.Statement); 46 | yield Triple.createAxiomatic(Rdf.object, Rdfs.domain, Rdf.Statement); 47 | yield Triple.createAxiomatic(Rdf.first, Rdfs.domain, Rdf.List); 48 | yield Triple.createAxiomatic(Rdf.rest, Rdfs.domain, Rdf.List); 49 | yield Triple.createAxiomatic(Rdf.value, Rdfs.domain, Rdfs.Resource); 50 | 51 | yield Triple.createAxiomatic(Rdf.type, Rdfs.range, Rdfs.Class); 52 | yield Triple.createAxiomatic(Rdf.subject, Rdfs.range, Rdfs.Resource); 53 | yield Triple.createAxiomatic(Rdf.predicate, Rdfs.range, Rdfs.Resource); 54 | yield Triple.createAxiomatic(Rdf.object, Rdfs.range, Rdfs.Resource); 55 | yield Triple.createAxiomatic(Rdf.first, Rdfs.range, Rdfs.Resource); 56 | yield Triple.createAxiomatic(Rdf.rest, Rdfs.range, Rdf.List); 57 | yield Triple.createAxiomatic(Rdf.value, Rdfs.range, Rdfs.Resource); 58 | 59 | yield Triple.createAxiomatic(Rdf.Alt, Rdfs.subClassOf, Rdfs.Container); 60 | yield Triple.createAxiomatic(Rdf.Bag, Rdfs.subClassOf, Rdfs.Container); 61 | yield Triple.createAxiomatic(Rdf.Seq, Rdfs.subClassOf, Rdfs.Container); 62 | } 63 | 64 | *interpret(triple: Triple): Generator { 65 | { 66 | // rdfD1 67 | } 68 | 69 | { 70 | const aaa = triple.predicate; 71 | 72 | // rdfD2 73 | yield Triple.createInferred(aaa, Rdf.type, Rdf.Property, triple); 74 | } 75 | } 76 | 77 | *afterinterpret(): Generator { 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /packages/rdf/src/engines/xsd.ts: -------------------------------------------------------------------------------- 1 | import { Triple } from "../triples.js"; 2 | import { Rdf, Rdfs, Xsd } from "../vocab.js"; 3 | 4 | // https://www.w3.org/TR/2014/REC-rdf11-concepts-20140225/#xsd-datatypes 5 | export class XSDEngine { 6 | 7 | ingest(triple: Triple): boolean { 8 | return false; 9 | } 10 | 11 | *beforeinterpret(): Generator { 12 | yield Triple.createAxiomatic(Xsd.string, Rdf.type, Rdfs.Datatype); 13 | yield Triple.createAxiomatic(Xsd.boolean, Rdf.type, Rdfs.Datatype); 14 | yield Triple.createAxiomatic(Xsd.decimal, Rdf.type, Rdfs.Datatype); 15 | yield Triple.createAxiomatic(Xsd.integer, Rdf.type, Rdfs.Datatype); 16 | yield Triple.createAxiomatic(Xsd.double, Rdf.type, Rdfs.Datatype); 17 | yield Triple.createAxiomatic(Xsd.float, Rdf.type, Rdfs.Datatype); 18 | yield Triple.createAxiomatic(Xsd.date, Rdf.type, Rdfs.Datatype); 19 | yield Triple.createAxiomatic(Xsd.time, Rdf.type, Rdfs.Datatype); 20 | yield Triple.createAxiomatic(Xsd.dateTime, Rdf.type, Rdfs.Datatype); 21 | yield Triple.createAxiomatic(Xsd.dateTimeStamp, Rdf.type, Rdfs.Datatype); 22 | yield Triple.createAxiomatic(Xsd.gYear, Rdf.type, Rdfs.Datatype); 23 | yield Triple.createAxiomatic(Xsd.gMonth, Rdf.type, Rdfs.Datatype); 24 | yield Triple.createAxiomatic(Xsd.gDay, Rdf.type, Rdfs.Datatype); 25 | yield Triple.createAxiomatic(Xsd.gYearMonth, Rdf.type, Rdfs.Datatype); 26 | yield Triple.createAxiomatic(Xsd.gMonthDay, Rdf.type, Rdfs.Datatype); 27 | yield Triple.createAxiomatic(Xsd.duration, Rdf.type, Rdfs.Datatype); 28 | yield Triple.createAxiomatic(Xsd.yearMonthDuration, Rdf.type, Rdfs.Datatype); 29 | yield Triple.createAxiomatic(Xsd.dayTimeDuration, Rdf.type, Rdfs.Datatype); 30 | yield Triple.createAxiomatic(Xsd.byte, Rdf.type, Rdfs.Datatype); 31 | yield Triple.createAxiomatic(Xsd.short, Rdf.type, Rdfs.Datatype); 32 | yield Triple.createAxiomatic(Xsd.int, Rdf.type, Rdfs.Datatype); 33 | yield Triple.createAxiomatic(Xsd.long, Rdf.type, Rdfs.Datatype); 34 | yield Triple.createAxiomatic(Xsd.unsignedByte, Rdf.type, Rdfs.Datatype); 35 | yield Triple.createAxiomatic(Xsd.unsignedShort, Rdf.type, Rdfs.Datatype); 36 | yield Triple.createAxiomatic(Xsd.unsignedInt, Rdf.type, Rdfs.Datatype); 37 | yield Triple.createAxiomatic(Xsd.unsignedLong, Rdf.type, Rdfs.Datatype); 38 | yield Triple.createAxiomatic(Xsd.positiveInteger, Rdf.type, Rdfs.Datatype); 39 | yield Triple.createAxiomatic(Xsd.nonNegativeInteger, Rdf.type, Rdfs.Datatype); 40 | yield Triple.createAxiomatic(Xsd.negativeInteger, Rdf.type, Rdfs.Datatype); 41 | yield Triple.createAxiomatic(Xsd.nonPositiveInteger, Rdf.type, Rdfs.Datatype); 42 | yield Triple.createAxiomatic(Xsd.hexBinary, Rdf.type, Rdfs.Datatype); 43 | yield Triple.createAxiomatic(Xsd.base64Binary, Rdf.type, Rdfs.Datatype); 44 | yield Triple.createAxiomatic(Xsd.anyURI, Rdf.type, Rdfs.Datatype); 45 | yield Triple.createAxiomatic(Xsd.language, Rdf.type, Rdfs.Datatype); 46 | yield Triple.createAxiomatic(Xsd.normalizedString, Rdf.type, Rdfs.Datatype); 47 | yield Triple.createAxiomatic(Xsd.token, Rdf.type, Rdfs.Datatype); 48 | yield Triple.createAxiomatic(Xsd.NMTOKEN, Rdf.type, Rdfs.Datatype); 49 | yield Triple.createAxiomatic(Xsd.Name, Rdf.type, Rdfs.Datatype); 50 | yield Triple.createAxiomatic(Xsd.NCName, Rdf.type, Rdfs.Datatype); 51 | } 52 | 53 | *interpret(triple: Triple): Generator { 54 | } 55 | 56 | *afterinterpret(): Generator { 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /packages/explorer-views/src/sections/schema-property.tsx: -------------------------------------------------------------------------------- 1 | import { Ix } from "@rdf-toolkit/iterable"; 2 | import { IRI } from "@rdf-toolkit/rdf/terms"; 3 | import { Property } from "@rdf-toolkit/schema"; 4 | import { renderPreformatted } from "../components/listing.js"; 5 | import renderMarkdown from "../components/markdown.js"; 6 | import renderRdfTerm, { RenderOptions } from "../components/rdf-term.js"; 7 | import renderTabView from "../components/tabview.js"; 8 | import { RenderContext } from "../context.js"; 9 | import { HtmlContent } from "../jsx/html.js"; 10 | import "./schema.css"; 11 | 12 | function renderPropertyCode(property: Property, context: RenderContext): HtmlContent { 13 | const options: RenderOptions = { rawBlankNodes: true }; 14 | return renderPreformatted(<> 15 | property 16 | {" "} 17 | {renderRdfTerm(property.id, context, options)} 18 | { 19 | property.subPropertyOf.length 20 | ? <> 21 | {" "} 22 | extends 23 | {" "} 24 | { 25 | Ix.from(property.subPropertyOf) 26 | .map(c => <>
    {renderRdfTerm(c, context, options)}) 27 | .intersperse(<>,) 28 | } 29 | 30 | : null 31 | } 32 | {" ."} 33 | ); 34 | } 35 | 36 | function renderPropertyDefinition(property: Property, context: RenderContext): HtmlContent { 37 | return
    38 |

    Definition

    39 | { 40 | property.description ? renderMarkdown(property.description, context, property.id) : null 41 | } 42 | { 43 | renderTabView("property-definition", [ 44 | { 45 | id: "property-definition-schema", 46 | label: "Schema", 47 | content: renderPropertyCode(property, context) 48 | }, 49 | ]) 50 | } 51 |
    ; 52 | } 53 | 54 | function renderEquivalentProperties(property: Property, context: RenderContext): HtmlContent { 55 | return context.graph.getEquivalentProperties(property.id) 56 | .filter(x => x !== property.id) 57 | .sort((a, b) => a.compareTo(b)) 58 | .map(property =>
  • {renderRdfTerm(property, context, { rawBlankNodes: true })}
  • ) 59 | .wrap(properties => 60 |
    61 |

    Equivalent Properties

    62 |
      {properties}
    63 |
    , null); 64 | } 65 | 66 | function renderPropertyDomain(property: Property, context: RenderContext): HtmlContent { 67 | return Ix.from(property.domainIncludes) 68 | .map(class_ =>
  • {renderRdfTerm(class_, context, { rawBlankNodes: true })}
  • ) 69 | .wrap(classes => 70 |
    71 |

    Domain Includes

    72 |
      {classes}
    73 |
    , null); 74 | } 75 | 76 | function renderProperty(property: Property, context: RenderContext): HtmlContent { 77 | return <> 78 | {renderPropertyDefinition(property, context)} 79 | {renderEquivalentProperties(property, context)} 80 | {renderPropertyDomain(property, context)} 81 | ; 82 | } 83 | 84 | export default function render(subject: IRI, context: RenderContext): HtmlContent { 85 | const property = context.schema.properties.get(subject); 86 | return property ? renderProperty(property, context) : <>; 87 | } 88 | -------------------------------------------------------------------------------- /packages/turtle/src/syntax-tree.ts: -------------------------------------------------------------------------------- 1 | import { BlankNode } from "@rdf-toolkit/rdf/terms"; 2 | import { ParsedTriple } from "@rdf-toolkit/rdf/triples"; 3 | import { DiagnosticBag, Position, Range, TextDocument } from "@rdf-toolkit/text"; 4 | import { TurtleCompiler } from "./compiler.js"; 5 | import { tokenizeLiterateTurtle } from "./literate.js"; 6 | import { TurtleParser } from "./parser.js"; 7 | import { TurtleScanner } from "./scanner.js"; 8 | import { DocumentSyntax, SyntaxNode, SyntaxToken } from "./syntax.js"; 9 | 10 | export interface ParserState { 11 | readonly baseIRI: string; 12 | readonly bnodeLabels: Record, 13 | readonly namespaces: Record, 14 | readonly triples: Array, 15 | } 16 | 17 | export interface SyntaxTree { 18 | getRange(node: SyntaxToken | SyntaxNode): Range; 19 | getPosition(node: SyntaxToken | SyntaxNode): Position; 20 | readonly document: TextDocument; 21 | readonly root: DocumentSyntax, 22 | } 23 | 24 | export namespace SyntaxTree { 25 | 26 | export function create(uri: string, languageId: string, version: number, root: DocumentSyntax): SyntaxTree { 27 | return new FullSyntaxTree(TextDocument.create(uri, languageId, version, SyntaxNode.toString(root)), root); 28 | } 29 | 30 | export function tokenize(document: TextDocument, diagnostics: DiagnosticBag): IterableIterator { 31 | switch (document.languageId) { 32 | case "turtle": 33 | return new TurtleScanner(document, 0, diagnostics); 34 | case "markdown": 35 | return tokenizeLiterateTurtle(document, diagnostics); 36 | default: 37 | throw new Error(); 38 | } 39 | } 40 | 41 | export function parse(document: TextDocument, diagnostics: DiagnosticBag): SyntaxTree { 42 | switch (document.languageId) { 43 | case "turtle": { 44 | const scanner = new TurtleScanner(document, 0, diagnostics); 45 | const parser = new TurtleParser(scanner, document, diagnostics); 46 | return new FullSyntaxTree(document, parser.parse()); 47 | } 48 | case "markdown": { 49 | const scanner = tokenizeLiterateTurtle(document, diagnostics); 50 | const parser = new TurtleParser(scanner, document, diagnostics); 51 | return new FullSyntaxTree(document, parser.parse()); 52 | } 53 | default: 54 | throw new Error("Unsupported file format"); 55 | } 56 | } 57 | 58 | export function compileTriples(syntaxTree: SyntaxTree, diagnostics: DiagnosticBag, options?: { returnParserState?: false }): ParsedTriple[]; 59 | export function compileTriples(syntaxTree: SyntaxTree, diagnostics: DiagnosticBag, options: { returnParserState: true }): ParserState; 60 | export function compileTriples(syntaxTree: SyntaxTree, diagnostics: DiagnosticBag, options: { returnParserState?: boolean } = {}): ParsedTriple[] | ParserState { 61 | if (options?.returnParserState) { 62 | return new TurtleCompiler(syntaxTree, diagnostics).compile(); 63 | } 64 | else { 65 | return new TurtleCompiler(syntaxTree, diagnostics).compile().triples; 66 | } 67 | } 68 | } 69 | 70 | class FullSyntaxTree implements SyntaxTree { 71 | 72 | constructor(readonly document: TextDocument, readonly root: DocumentSyntax) { 73 | } 74 | 75 | getRange(node: SyntaxToken | SyntaxNode): Range { 76 | const firstToken = SyntaxToken.is(node) ? node : SyntaxNode.getFirstToken(node); 77 | const lastToken = SyntaxToken.is(node) ? node : SyntaxNode.getLastToken(node); 78 | return Range.create( 79 | this.document.positionAt(firstToken.offset), 80 | this.document.positionAt(lastToken.offset + lastToken.text.length)); 81 | } 82 | 83 | getPosition(node: SyntaxToken | SyntaxNode): Position { 84 | const token = SyntaxToken.is(node) ? node : SyntaxNode.getFirstToken(node); 85 | return this.document.positionAt(token.offset); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /packages/cli/src/model/textfile.ts: -------------------------------------------------------------------------------- 1 | import { IRI } from "@rdf-toolkit/rdf/terms"; 2 | import { ParsedTriple } from "@rdf-toolkit/rdf/triples"; 3 | import { DocumentUri, TextDocument } from "@rdf-toolkit/text"; 4 | import { SymbolTable, SyntaxKind, SyntaxTree } from "@rdf-toolkit/turtle"; 5 | import * as fs from "node:fs"; 6 | import { Ontology } from "./ontology.js"; 7 | import { Package } from "./package.js"; 8 | import { Project } from "./project.js"; 9 | 10 | interface FileFormat { 11 | readonly contentType: string; 12 | readonly fileExtension: string; 13 | readonly languageId: string; 14 | } 15 | 16 | export class TextFile { 17 | private _buffer?: Buffer; 18 | private _document?: TextDocument; 19 | private _format?: FileFormat; 20 | private _ontology?: Ontology; 21 | private _symbolTable?: SymbolTable; 22 | private _syntaxTree?: SyntaxTree; 23 | private _terms?: ReadonlyArray; 24 | private _text?: string; 25 | private _triples?: ReadonlyArray; 26 | 27 | constructor(readonly documentURI: DocumentUri, readonly filePath: string, readonly containingPackage: Package) { 28 | } 29 | 30 | get buffer(): Buffer { 31 | return this._buffer ??= fs.readFileSync(this.filePath, { flag: "r" }); 32 | } 33 | 34 | get containingProject(): Project { 35 | return this.containingPackage.containingProject; 36 | } 37 | 38 | get contentType(): string { 39 | return (this._format ??= getFormat(this.filePath)).contentType; 40 | } 41 | 42 | get document(): TextDocument { 43 | return this._document ??= TextDocument.create(this.documentURI, this.languageId, 0, this.text); 44 | } 45 | 46 | get fileExtension(): string { 47 | return (this._format ??= getFormat(this.filePath)).fileExtension; 48 | } 49 | 50 | get languageId(): string { 51 | return (this._format ??= getFormat(this.filePath)).languageId; 52 | } 53 | 54 | get ontology(): Ontology | undefined { 55 | return this._ontology ??= Ontology.from(this.syntaxTree, this.symbolTable); 56 | } 57 | 58 | get symbolTable(): SymbolTable { 59 | return this._symbolTable ??= SymbolTable.from(this.syntaxTree, this.containingProject.diagnostics); 60 | } 61 | 62 | get syntaxTree(): SyntaxTree { 63 | return this._syntaxTree ??= SyntaxTree.parse(this.document, this.containingProject.diagnostics); 64 | } 65 | 66 | get terms(): ReadonlyArray { 67 | return this._terms ??= getTerms(this.syntaxTree, this.symbolTable); 68 | } 69 | 70 | get text(): string { 71 | return this._text ??= this.buffer.toString("utf-8"); 72 | } 73 | 74 | get triples(): ReadonlyArray { 75 | return this._triples ??= SyntaxTree.compileTriples(this.syntaxTree, this.containingProject.diagnostics); 76 | } 77 | } 78 | 79 | function getFormat(filePath: string): FileFormat { 80 | if (/[.](?:ttl)$/i.test(filePath)) { 81 | return { contentType: "text/turtle", fileExtension: "ttl", languageId: "turtle" }; 82 | } 83 | else if (/[.](?:md|mkdn?|mdwn|mdown|markdown)$/i.test(filePath)) { 84 | return { contentType: "text/markdown", fileExtension: "md", languageId: "markdown" }; 85 | } 86 | else if (/[.](?:owl|rdf|xml)$/i.test(filePath)) { 87 | return { contentType: "application/xml", fileExtension: "xml", languageId: "xml" }; 88 | } 89 | else { 90 | return { contentType: "text/plain", fileExtension: "txt", languageId: "plaintext" }; 91 | } 92 | } 93 | 94 | function getTerms(syntaxTree: SyntaxTree, symbolTable: SymbolTable): Array { 95 | const terms = new Set(); 96 | 97 | for (const statement of syntaxTree.root.statements) { 98 | if (statement.kind === SyntaxKind.SubjectPredicateObjectList) { 99 | const subject = symbolTable.get(statement.subject); 100 | if (subject) { 101 | terms.add(subject); 102 | } 103 | } 104 | } 105 | 106 | return Array.from(terms); 107 | } 108 | -------------------------------------------------------------------------------- /packages/cli/src/model/package.ts: -------------------------------------------------------------------------------- 1 | import { DocumentUri } from "@rdf-toolkit/text"; 2 | import * as fs from "node:fs"; 3 | import { Is } from "../type-checks.js"; 4 | import { Workspace } from "../workspace.js"; 5 | import { Project } from "./project.js"; 6 | import { TextFile } from "./textfile.js"; 7 | 8 | export const PACKAGE_JSON = "package.json"; 9 | 10 | export interface PackageConfig { 11 | name?: unknown; // string 12 | version?: unknown; // string 13 | description?: unknown; // string 14 | keywords?: unknown; // Array 15 | homepage?: unknown; // string 16 | bugs?: unknown; // Bugs 17 | license?: unknown; // string 18 | author?: unknown; // PersonConfig 19 | contributors?: unknown; // Array 20 | maintainers?: unknown; // Array 21 | repository?: unknown; // RepositoryConfig 22 | dependencies?: unknown; // Record 23 | devDependencies?: unknown; // Record 24 | 25 | "rdf:files"?: unknown, // Record 26 | "rdf:prefixes"?: unknown, // Record 27 | } 28 | 29 | export class Package extends Workspace { 30 | private _dependencies?: ReadonlyMap; 31 | private _files?: ReadonlyMap; 32 | private _json?: PackageConfig; 33 | private _prefixes?: ReadonlyMap; 34 | 35 | constructor(packagePath: string, readonly containingProject: Project) { 36 | super(packagePath); 37 | } 38 | 39 | get dependencies(): ReadonlyMap { 40 | return this._dependencies ??= getDependencies(this.json, this.containingProject); 41 | } 42 | 43 | get json(): PackageConfig { 44 | return this._json ??= getPackageConfig(this); 45 | } 46 | 47 | get files(): ReadonlyMap { 48 | return this._files ??= getFiles(this.json, this); 49 | } 50 | 51 | get prefixes(): ReadonlyMap { 52 | return this._prefixes ??= getPrefixes(this.json); 53 | } 54 | 55 | get version(): string | undefined { 56 | return Is.string(this.json.version) ? this.json.version : undefined; 57 | } 58 | } 59 | 60 | function getDependencies(json: PackageConfig, containingProject: Project): Map { 61 | const dependencies = new Map(); 62 | 63 | if (Is.objectLiteral(json.dependencies)) { 64 | for (const packageName in json.dependencies) { 65 | dependencies.set(packageName, containingProject.resolvePackage(packageName)); 66 | } 67 | } 68 | 69 | return dependencies; 70 | } 71 | 72 | function getFiles(json: PackageConfig, containingPackage: Package): Map { 73 | const files = new Map(); 74 | 75 | if (Is.record(json["rdf:files"], Is.string)) { 76 | for (const documentURI in json["rdf:files"]) { 77 | const filePath = containingPackage.resolve(json["rdf:files"][documentURI]); 78 | 79 | let file: TextFile | null = null; 80 | if (fs.existsSync(filePath)) { 81 | file = new TextFile(documentURI, fs.realpathSync(filePath), containingPackage); 82 | } 83 | files.set(documentURI, file); 84 | } 85 | } 86 | 87 | return files; 88 | } 89 | 90 | function getPackageConfig(workspace: Workspace): PackageConfig { 91 | const value = workspace.readJSON(PACKAGE_JSON); 92 | if (!Is.objectLiteral(value)) { 93 | throw new Error("Invalid " + PACKAGE_JSON); 94 | } 95 | return value; 96 | } 97 | 98 | function getPrefixes(json: PackageConfig): Map { 99 | const prefixes = new Map(); 100 | 101 | if (Is.record(json["rdf:prefixes"], Is.string)) { 102 | for (const prefixLabel in json["rdf:prefixes"]) { 103 | const namespaceIRI = json["rdf:prefixes"][prefixLabel]; 104 | 105 | prefixes.set(prefixLabel, namespaceIRI); 106 | } 107 | } 108 | 109 | return prefixes; 110 | } 111 | -------------------------------------------------------------------------------- /explorer/vocab/rdfs.ttl: -------------------------------------------------------------------------------- 1 | @prefix rdf: . 2 | @prefix rdfs: . 3 | @prefix owl: . 4 | @prefix dc: . 5 | 6 | a owl:Ontology ; 7 | dc:title "The RDF Schema vocabulary (RDFS)" . 8 | 9 | rdfs:Resource a rdfs:Class ; 10 | rdfs:isDefinedBy ; 11 | rdfs:label "Resource" ; 12 | rdfs:comment "The class resource, everything." . 13 | 14 | rdfs:Class a rdfs:Class ; 15 | rdfs:isDefinedBy ; 16 | rdfs:label "Class" ; 17 | rdfs:comment "The class of classes." ; 18 | rdfs:subClassOf rdfs:Resource . 19 | 20 | rdfs:subClassOf a rdf:Property ; 21 | rdfs:isDefinedBy ; 22 | rdfs:label "subClassOf" ; 23 | rdfs:comment "The subject is a subclass of a class." ; 24 | rdfs:range rdfs:Class ; 25 | rdfs:domain rdfs:Class . 26 | 27 | rdfs:subPropertyOf a rdf:Property ; 28 | rdfs:isDefinedBy ; 29 | rdfs:label "subPropertyOf" ; 30 | rdfs:comment "The subject is a subproperty of a property." ; 31 | rdfs:range rdf:Property ; 32 | rdfs:domain rdf:Property . 33 | 34 | rdfs:comment a rdf:Property ; 35 | rdfs:isDefinedBy ; 36 | rdfs:label "comment" ; 37 | rdfs:comment "A description of the subject resource." ; 38 | rdfs:domain rdfs:Resource ; 39 | rdfs:range rdfs:Literal . 40 | 41 | rdfs:label a rdf:Property ; 42 | rdfs:isDefinedBy ; 43 | rdfs:label "label" ; 44 | rdfs:comment "A human-readable name for the subject." ; 45 | rdfs:domain rdfs:Resource ; 46 | rdfs:range rdfs:Literal . 47 | 48 | rdfs:domain a rdf:Property ; 49 | rdfs:isDefinedBy ; 50 | rdfs:label "domain" ; 51 | rdfs:comment "A domain of the subject property." ; 52 | rdfs:range rdfs:Class ; 53 | rdfs:domain rdf:Property . 54 | 55 | rdfs:range a rdf:Property ; 56 | rdfs:isDefinedBy ; 57 | rdfs:label "range" ; 58 | rdfs:comment "A range of the subject property." ; 59 | rdfs:range rdfs:Class ; 60 | rdfs:domain rdf:Property . 61 | 62 | rdfs:seeAlso a rdf:Property ; 63 | rdfs:isDefinedBy ; 64 | rdfs:label "seeAlso" ; 65 | rdfs:comment "Further information about the subject resource." ; 66 | rdfs:range rdfs:Resource ; 67 | rdfs:domain rdfs:Resource . 68 | 69 | rdfs:isDefinedBy a rdf:Property ; 70 | rdfs:isDefinedBy ; 71 | rdfs:subPropertyOf rdfs:seeAlso ; 72 | rdfs:label "isDefinedBy" ; 73 | rdfs:comment "The defininition of the subject resource." ; 74 | rdfs:range rdfs:Resource ; 75 | rdfs:domain rdfs:Resource . 76 | 77 | rdfs:Literal a rdfs:Class ; 78 | rdfs:isDefinedBy ; 79 | rdfs:label "Literal" ; 80 | rdfs:comment "The class of literal values, eg. textual strings and integers." ; 81 | rdfs:subClassOf rdfs:Resource . 82 | 83 | rdfs:Container a rdfs:Class ; 84 | rdfs:isDefinedBy ; 85 | rdfs:label "Container" ; 86 | rdfs:subClassOf rdfs:Resource ; 87 | rdfs:comment "The class of RDF containers." . 88 | 89 | rdfs:ContainerMembershipProperty a rdfs:Class ; 90 | rdfs:isDefinedBy ; 91 | rdfs:label "ContainerMembershipProperty" ; 92 | rdfs:comment """The class of container membership properties, rdf:_1, rdf:_2, ..., 93 | all of which are sub-properties of 'member'.""" ; 94 | rdfs:subClassOf rdf:Property . 95 | 96 | rdfs:member a rdf:Property ; 97 | rdfs:isDefinedBy ; 98 | rdfs:label "member" ; 99 | rdfs:comment "A member of the subject resource." ; 100 | rdfs:domain rdfs:Resource ; 101 | rdfs:range rdfs:Resource . 102 | 103 | rdfs:Datatype a rdfs:Class ; 104 | rdfs:isDefinedBy ; 105 | rdfs:label "Datatype" ; 106 | rdfs:comment "The class of RDF datatypes." ; 107 | rdfs:subClassOf rdfs:Class . 108 | 109 | rdfs:seeAlso . 110 | -------------------------------------------------------------------------------- /packages/text/src/irireferences.ts: -------------------------------------------------------------------------------- 1 | export interface IRIReference { 2 | readonly scheme?: string; 3 | readonly authority?: string; 4 | readonly path: string; 5 | readonly query?: string; 6 | readonly fragment?: string; 7 | } 8 | 9 | export namespace IRIReference { 10 | 11 | export function parse(s: string): IRIReference | null { 12 | // 12 3 4 5 6 7 8 9 13 | const m = /^(([^:/?#]+):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/u.exec(s); 14 | return m ? { scheme: m[2], authority: m[4], path: m[5], query: m[7], fragment: m[9], } : null; 15 | } 16 | 17 | export function resolve(R: IRIReference, Base: IRIReference): IRIReference { 18 | let scheme: string | undefined; 19 | let authority: string | undefined; 20 | let path: string; 21 | let query: string | undefined; 22 | 23 | if (typeof R.scheme === "string") { 24 | scheme = R.scheme; 25 | authority = R.authority; 26 | path = removeDotSegments(R.path); 27 | query = R.query; 28 | } else { 29 | if (typeof R.authority === "string") { 30 | authority = R.authority; 31 | path = removeDotSegments(R.path); 32 | query = R.query; 33 | } else { 34 | if (R.path === "") { 35 | path = Base.path; 36 | if (typeof R.query === "string") { 37 | query = R.query; 38 | } else { 39 | query = Base.query; 40 | } 41 | } else { 42 | if (R.path.startsWith("/")) { 43 | path = removeDotSegments(R.path); 44 | } else { 45 | path = mergePaths(Base, R.path); 46 | path = removeDotSegments(path); 47 | } 48 | query = R.query; 49 | } 50 | authority = Base.authority; 51 | } 52 | scheme = Base.scheme; 53 | } 54 | 55 | return { scheme, authority, path, query, fragment: R.fragment }; 56 | } 57 | 58 | export function recompose(R: IRIReference): string { 59 | let result = ""; 60 | if (typeof R.scheme === "string") { 61 | result += R.scheme; 62 | result += ":"; 63 | } 64 | if (typeof R.authority === "string") { 65 | result += "//"; 66 | result += R.authority; 67 | } 68 | result += R.path; 69 | if (typeof R.query === "string") { 70 | result += "?"; 71 | result += R.query; 72 | } 73 | if (typeof R.fragment === "string") { 74 | result += "#"; 75 | result += R.fragment; 76 | } 77 | return result; 78 | } 79 | } 80 | 81 | function mergePaths(base: IRIReference, relativePath: string): string { 82 | if (base.authority && base.path === "") { 83 | return "/" + relativePath; 84 | } 85 | else { 86 | return base.path.slice(0, base.path.lastIndexOf("/") + 1) + relativePath; 87 | } 88 | } 89 | 90 | function removeDotSegments(input: string): string { 91 | let output = ""; 92 | 93 | while (input.length > 0) { 94 | if (input.startsWith("../")) { 95 | input = input.slice(3); 96 | } 97 | else if (input.startsWith("./") || input.startsWith("/./")) { 98 | input = input.slice(2); 99 | } 100 | else if (input === "/.") { 101 | input = "/"; 102 | } 103 | else if (input.startsWith("/../")) { 104 | input = input.slice(3); 105 | const pos = output.lastIndexOf("/"); 106 | output = (pos >= 0) ? output.slice(0, pos) : ""; 107 | } 108 | else if (input === "/..") { 109 | input = "/"; 110 | const pos = output.lastIndexOf("/"); 111 | output = (pos >= 0) ? output.slice(0, pos) : ""; 112 | } 113 | else if (input === "." || input === "..") { 114 | input = ""; 115 | } 116 | else { 117 | const q = input.indexOf("/", input.startsWith("/") ? 1 : 0); 118 | output += (q >= 0) ? input.slice(0, q) : input; 119 | input = (q >= 0) ? input.slice(q) : ""; 120 | } 121 | } 122 | 123 | return output; 124 | } 125 | -------------------------------------------------------------------------------- /packages/schema/src/decompiler/rdfs.ts: -------------------------------------------------------------------------------- 1 | import { Ix } from "@rdf-toolkit/iterable"; 2 | import { Graph } from "@rdf-toolkit/rdf/graphs"; 3 | import { IRIOrBlankNode, Term } from "@rdf-toolkit/rdf/terms"; 4 | import { Owl, Vocabulary } from "@rdf-toolkit/rdf/vocab"; 5 | import { getDescription, SchemaBuilder } from "./utils.js"; 6 | 7 | const Dcam = Vocabulary.create("http://purl.org/dc/dcam/", ["domainIncludes", "rangeIncludes"]); 8 | const HttpSchemaOrg = Vocabulary.create("http://schema.org/", ["domainIncludes", "rangeIncludes"]); 9 | const HttpsSchemaOrg = Vocabulary.create("https://schema.org/", ["domainIncludes", "rangeIncludes"]); 10 | 11 | // 12 | // ?class rdf:type rdfs:Class 13 | // 14 | // ?property rdfs:domain ?class ; rdfs:range ?value 15 | // 16 | // ?property dcam:domainIncludes ?class ; dcam:rangeIncludes ?value 17 | // 18 | export default function decompile(graph: Graph, builder: SchemaBuilder): void { 19 | for (const class_ of graph.classes()) { 20 | builder.addClass(class_, getDescription(class_, graph)); 21 | } 22 | 23 | for (const property of graph.properties()) { 24 | const propertyDescription = getDescription(property, graph); 25 | builder.addProperty(property, propertyDescription); 26 | 27 | const classes = graph.getPropertyDomain(property) 28 | .filter(x => !graph.getPropertyDomain(property).some(y => y !== x && graph.isSubClassOf(y, x))) 29 | .concatMap(x => splitDomain(x, graph)) 30 | .concat(graph.objects(property, Dcam.domainIncludes) 31 | .concat(graph.objects(property, HttpSchemaOrg.domainIncludes)) 32 | .concat(graph.objects(property, HttpsSchemaOrg.domainIncludes)) 33 | .ofType(IRIOrBlankNode.is)); 34 | 35 | const values = graph.getPropertyRange(property) 36 | .filter(x => !graph.getPropertyRange(property).some(y => y !== x && graph.isSubClassOf(y, x))) 37 | .concatMap(x => splitRange(x, graph)) 38 | .concat(graph.objects(property, Dcam.rangeIncludes) 39 | .concat(graph.objects(property, HttpSchemaOrg.rangeIncludes)) 40 | .concat(graph.objects(property, HttpsSchemaOrg.rangeIncludes))); 41 | 42 | for (const class_ of classes) { 43 | const classDescription = getDescription(class_, graph); 44 | builder.addClass(class_, classDescription); 45 | builder.addClassProperty(class_, property, propertyDescription, undefined, undefined); 46 | builder.addPropertyClass(property, class_); 47 | for (const value of values) { 48 | builder.addClassPropertyValue(class_, property, value); 49 | } 50 | } 51 | } 52 | } 53 | 54 | function* splitDomain(node: IRIOrBlankNode, graph: Graph): Generator { 55 | let didYield = false; 56 | for (const triple of graph.triples(node)) { 57 | switch (triple.predicate) { 58 | case Owl.unionOf: 59 | if (IRIOrBlankNode.is(triple.object)) { 60 | for (const item of Ix.from(graph.list(triple.object))) { 61 | if (IRIOrBlankNode.is(item)) { 62 | didYield = true; 63 | yield item; 64 | } 65 | } 66 | } 67 | break; 68 | } 69 | } 70 | if (!didYield) { 71 | yield node; 72 | } 73 | } 74 | 75 | export function splitRange(node: Term, graph: Graph): Iterable { 76 | const result: Term[] = []; 77 | if (IRIOrBlankNode.is(node)) { 78 | for (const triple of graph.triples(node)) { 79 | switch (triple.predicate) { 80 | case Owl.onClass: 81 | case Owl.onDataRange: 82 | case Owl.allValuesFrom: 83 | case Owl.someValuesFrom: 84 | case Owl.hasValue: 85 | result.push(...splitRange(triple.object, graph)); 86 | break; 87 | case Owl.unionOf: 88 | case Owl.oneOf: 89 | if (IRIOrBlankNode.is(triple.object)) { 90 | for (const item of Ix.from(graph.list(triple.object))) { 91 | result.push(item); 92 | } 93 | } 94 | break; 95 | case Owl.onDatatype: 96 | result.push(triple.object); 97 | break; 98 | case Owl.onProperty: 99 | return [node]; 100 | } 101 | } 102 | } 103 | if (!result.length) { 104 | result.push(node); 105 | } 106 | return result; 107 | } 108 | -------------------------------------------------------------------------------- /packages/cli/src/commands/make-explorer.tsx: -------------------------------------------------------------------------------- 1 | import renderHTML, { HtmlContent } from "@rdf-toolkit/explorer-views/jsx/html"; 2 | import { Ix } from "@rdf-toolkit/iterable"; 3 | import * as crypto from "node:crypto"; 4 | import * as fs from "node:fs"; 5 | import * as path from "node:path"; 6 | import * as url from "node:url"; 7 | import cssAssetFilePath from "../assets/explorer/explorer.min.css"; 8 | import scriptAssetFilePath from "../assets/explorer/explorer.min.js"; 9 | import fontAssetFilePath from "../assets/explorer/iosevka-aile-custom-light.woff2"; 10 | import workerScriptAssetFilePath from "../assets/explorer/worker.min.js"; 11 | import { Project } from "../model/project.js"; 12 | import { TextFile } from "../model/textfile.js"; 13 | import { MakeOptions, ProjectOptions } from "../options.js"; 14 | import { Workspace } from "../workspace.js"; 15 | 16 | type Options = 17 | & MakeOptions 18 | & ProjectOptions 19 | 20 | const DEFAULT_TITLE = "RDF Explorer"; 21 | 22 | const INDEX_FILE_NAME = "index.html"; 23 | const CSS_FILE_NAME = path.basename(cssAssetFilePath); 24 | const FONT_FILE_NAME = path.basename(fontAssetFilePath); 25 | const SCRIPT_FILE_NAME = path.basename(scriptAssetFilePath); 26 | const WORKER_SCRIPT_FILE_NAME = path.basename(workerScriptAssetFilePath); 27 | 28 | class Website { 29 | readonly files: Record = {}; 30 | 31 | constructor(readonly title: string) { 32 | } 33 | 34 | add(ontology: TextFile): void { 35 | this.files[ontology.documentURI] = { 36 | fileName: [path.parse(ontology.filePath).name, crypto.createHash("sha256").update(ontology.buffer).digest("hex").slice(0, 12), ontology.fileExtension].join("."), 37 | contentType: ontology.contentType, 38 | buffer: ontology.buffer, 39 | }; 40 | } 41 | } 42 | 43 | function renderIndex(context: Website, links: HtmlContent, scripts: HtmlContent): HtmlContent { 44 | return 45 | 46 | 47 | {context.title} 48 | {links} 49 | 50 | {Object.entries(context.files).map(([documentURI, item]) => )} 51 | 52 | 53 | 54 | {scripts} 55 | 56 | ; 57 | } 58 | 59 | export default function main(options: Options): void { 60 | const moduleFilePath = url.fileURLToPath(import.meta.url); 61 | const modulePath = path.dirname(moduleFilePath); 62 | 63 | const project = new Project(options.project); 64 | const icons = project.json.siteOptions?.icons || []; 65 | const assets = project.json.siteOptions?.assets || {}; 66 | const context = new Website(project.json.siteOptions?.title || DEFAULT_TITLE); 67 | const site = new Workspace(project.package.resolve(options.output || project.json.siteOptions?.outDir || "public")); 68 | 69 | for (const fileSet of project.files.values()) { 70 | const file = Ix.from(fileSet).singleOrDefault(null); 71 | if (file) { 72 | context.add(file); 73 | } 74 | } 75 | 76 | const links = <> 77 | {icons.map(iconConfig => )} 78 | 79 | ; 80 | 81 | const scripts = <> 82 | 83 | ; 84 | 85 | site.write(CSS_FILE_NAME, fs.readFileSync(path.resolve(modulePath, cssAssetFilePath))); 86 | site.write(FONT_FILE_NAME, fs.readFileSync(path.resolve(modulePath, fontAssetFilePath))); 87 | site.write(SCRIPT_FILE_NAME, fs.readFileSync(path.resolve(modulePath, scriptAssetFilePath))); 88 | site.write(WORKER_SCRIPT_FILE_NAME, fs.readFileSync(path.resolve(modulePath, workerScriptAssetFilePath))); 89 | 90 | for (const iconConfig of icons) { 91 | site.write(path.basename(iconConfig.asset), project.package.read(iconConfig.asset)); 92 | } 93 | 94 | for (const assetPath in assets) { 95 | site.write(assets[assetPath], project.package.read(assetPath)); 96 | } 97 | 98 | site.write(INDEX_FILE_NAME, Buffer.from("\n" + renderHTML(renderIndex(context, links, scripts)))); 99 | 100 | for (const item of Object.values(context.files)) { 101 | site.write(item.fileName, item.buffer); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /packages/explorer-views/src/components/markdown.tsx: -------------------------------------------------------------------------------- 1 | import { Ix } from "@rdf-toolkit/iterable"; 2 | import { IRI } from "@rdf-toolkit/rdf/terms"; 3 | import { IRIReference } from "@rdf-toolkit/text"; 4 | import * as commonmark from "commonmark"; 5 | import { RenderContext } from "../context.js"; 6 | import { HtmlContent } from "../jsx/html.js"; 7 | import { renderLines } from "./listing.js"; 8 | import "./markdown.css"; 9 | import renderRdfTerm from "./rdf-term.js"; 10 | 11 | export default function render(text: string, context: RenderContext, baseIRI: IRI | null): HtmlContent { 12 | const baseIRIReference = baseIRI ? IRIReference.parse(baseIRI.value) : null; 13 | 14 | function renderLiteral(node: commonmark.Node): HtmlContent { 15 | return (node.literal || "").replace(/\p{Zs}|\p{Cc}/ug, " "); 16 | } 17 | 18 | function* renderChildren(node: commonmark.Node): Generator { 19 | for (let child = node.firstChild; child; child = child.next) { 20 | yield renderNode(child); 21 | } 22 | } 23 | 24 | function renderCodeListing(node: commonmark.Node): HtmlContent { 25 | return renderLines((node.literal || "") 26 | .split(/\n|\r\n?|\p{Zl}|\p{Zp}/ug) 27 | .map(line => line.indexOf("\t") >= 0 28 | ? Ix.from(line.split(/\t/ug)) 29 | .map(segment => segment.replace(/\p{Zs}|\p{Cc}/ug, " ")) 30 | .intersperse( ) 31 | : line.replace(/\p{Zs}|\p{Cc}/ug, " "))); 32 | } 33 | 34 | function renderLink(node: commonmark.Node): HtmlContent { 35 | if (node.destination) { 36 | let destination = IRIReference.parse(node.destination); 37 | if (destination) { 38 | if (baseIRIReference) { 39 | destination = IRIReference.resolve(destination, baseIRIReference); 40 | } 41 | if (destination.scheme) { 42 | return renderRdfTerm(IRI.create(destination), context, { linkContents: node.firstChild ? renderChildren(node) : undefined }); 43 | } 44 | } 45 | } 46 | return "\uFFFC"; 47 | } 48 | 49 | function renderImage(node: commonmark.Node): HtmlContent { 50 | return "\uFFFC"; 51 | } 52 | 53 | function renderNode(node: commonmark.Node): HtmlContent { 54 | switch (node.type) { 55 | case "text": 56 | return renderLiteral(node); 57 | 58 | case "softbreak": 59 | return " "; 60 | 61 | case "linebreak": 62 | return
    ; 63 | 64 | case "emph": 65 | return {renderChildren(node)}; 66 | 67 | case "strong": 68 | return {renderChildren(node)}; 69 | 70 | case "html_inline": 71 | return {renderLiteral(node)}; 72 | 73 | case "link": 74 | return renderLink(node); 75 | 76 | case "image": 77 | return renderImage(node); 78 | 79 | case "code": 80 | return {renderLiteral(node)}; 81 | 82 | case "document": 83 | return
    {renderChildren(node)}
    ; 84 | 85 | case "paragraph": 86 | return

    {renderChildren(node)}

    ; 87 | 88 | case "block_quote": 89 | return
    {renderChildren(node)}
    ; 90 | 91 | case "item": 92 | return
  • {renderChildren(node)}
  • ; 93 | 94 | case "list": 95 | switch (node.listType) { 96 | case "bullet": return
      {renderChildren(node)}
    ; 97 | case "ordered": return
      {renderChildren(node)}
    ; 98 | } 99 | 100 | case "heading": 101 | switch (node.level) { 102 | case 1: return

    {renderChildren(node)}

    ; 103 | case 2: return

    {renderChildren(node)}

    ; 104 | case 3: return
    {renderChildren(node)}
    ; 105 | case 4: return
    {renderChildren(node)}
    ; 106 | default: return

    {renderChildren(node)}

    ; 107 | } 108 | 109 | case "code_block": 110 | return renderCodeListing(node); 111 | 112 | case "html_block": 113 | return

    {renderLiteral(node)}

    ; 114 | 115 | case "thematic_break": 116 | return
    ; 117 | 118 | case "custom_inline": 119 | case "custom_block": 120 | return null; 121 | } 122 | } 123 | 124 | return renderNode(new commonmark.Parser({ smart: true }).parse(text)); 125 | } 126 | -------------------------------------------------------------------------------- /packages/explorer-views/src/jsx/html.ts: -------------------------------------------------------------------------------- 1 | export type HtmlAttributeValue = 2 | | null 3 | | bigint 4 | | boolean 5 | | number 6 | | string 7 | | Iterable; 8 | 9 | export type HtmlAttributes = 10 | & { readonly [key: string]: HtmlAttributeValue } 11 | & { readonly children: HtmlContent }; 12 | 13 | export interface HtmlElement { 14 | readonly type: string; 15 | readonly props: HtmlAttributes; 16 | } 17 | 18 | export type HtmlContent = 19 | | null 20 | | bigint 21 | | boolean 22 | | number 23 | | string 24 | | HtmlElement 25 | | Iterable; 26 | 27 | const noEndTag: { readonly [key: string]: boolean } = { 28 | "base": true, 29 | "link": true, 30 | "meta": true, 31 | "hr": true, 32 | "br": true, 33 | "wbr": true, 34 | "source": true, 35 | "img": true, 36 | "embed": true, 37 | "track": true, 38 | "area": true, 39 | "col": true, 40 | "input": true, 41 | } 42 | 43 | const entities: { readonly [key: string]: string } = { 44 | "\x00": "\u2400", 45 | "\x01": "\u2401", 46 | "\x02": "\u2402", 47 | "\x03": "\u2403", 48 | "\x04": "\u2404", 49 | "\x05": "\u2405", 50 | "\x06": "\u2406", 51 | "\x07": "\u2407", 52 | "\x08": "\u2408", 53 | "\x09": "\u2409", 54 | "\x0A": "\u240A", 55 | "\x0B": "\u240B", 56 | "\x0C": "\u240C", 57 | "\x0D": "\u240D", 58 | "\x0E": "\u240E", 59 | "\x0F": "\u240F", 60 | "\x10": "\u2410", 61 | "\x11": "\u2411", 62 | "\x12": "\u2412", 63 | "\x13": "\u2413", 64 | "\x14": "\u2414", 65 | "\x15": "\u2415", 66 | "\x16": "\u2416", 67 | "\x17": "\u2417", 68 | "\x18": "\u2418", 69 | "\x19": "\u2419", 70 | "\x1A": "\u241A", 71 | "\x1B": "\u241B", 72 | "\x1C": "\u241C", 73 | "\x1D": "\u241D", 74 | "\x1E": "\u241E", 75 | "\x1F": "\u241F", 76 | "\x20": "\u2420", 77 | 78 | "\"": """, 79 | "&": "&", 80 | "'": "'", 81 | "<": "<", 82 | ">": ">", 83 | 84 | "\x7F": "\u2421", 85 | }; 86 | 87 | function isIterable(x: any): x is Iterable { 88 | return !!x?.[Symbol.iterator]; 89 | } 90 | 91 | function renderText(s: string): string { 92 | return s.replace(/[\0-\x1F"&'<>\x7F]/g, c => entities[c]); 93 | } 94 | 95 | function renderAttributes(props: HtmlAttributes) { 96 | let result = ""; 97 | for (const prop in props) { 98 | if (prop !== "children") { 99 | const value = props[prop]; 100 | if (typeof value === "bigint") { 101 | result += " " + prop + "=\"" + value.toString() + "\""; 102 | } 103 | else if (typeof value === "boolean") { 104 | result += value ? " " + prop : ""; 105 | } 106 | else if (typeof value === "number") { 107 | result += " " + prop + "=\"" + value.toString() + "\""; 108 | } 109 | else if (typeof value === "string") { 110 | result += " " + prop + "=\"" + renderText(value) + "\""; 111 | } 112 | else if (isIterable(value)) { 113 | let first = true; 114 | let list = ""; 115 | for (const item of value) { 116 | if (typeof item === "string") { 117 | if (first) { 118 | first = false; 119 | } 120 | else { 121 | list += " "; 122 | } 123 | list += renderText(item); 124 | } 125 | } 126 | result += " " + prop + "=\"" + list + "\""; 127 | } 128 | } 129 | } 130 | return result; 131 | } 132 | 133 | export default function render(content: HtmlContent): string { 134 | if (typeof content === "bigint") { 135 | return content.toString(); 136 | } 137 | else if (typeof content === "boolean") { 138 | return content ? "true" : "false"; 139 | } 140 | else if (typeof content === "number") { 141 | return content.toString(); 142 | } 143 | else if (typeof content === "string") { 144 | return renderText(content); 145 | } 146 | else if (isIterable(content)) { 147 | let result = ""; 148 | for (const item of content) { 149 | result += render(item); 150 | } 151 | return result; 152 | } 153 | else if (content) { 154 | let result = "<" + content.type + renderAttributes(content.props) + ">"; 155 | if (!(content.type in noEndTag)) { 156 | if (content.props) { 157 | result += render(content.props.children); 158 | } 159 | result += ""; 160 | } 161 | return result; 162 | } 163 | else { 164 | return ""; 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /packages/schema/src/decompiler/shacl.ts: -------------------------------------------------------------------------------- 1 | import { Ix } from "@rdf-toolkit/iterable"; 2 | import { Graph } from "@rdf-toolkit/rdf/graphs"; 3 | import { IRI, IRIOrBlankNode, Literal, Term } from "@rdf-toolkit/rdf/terms"; 4 | import { Rdfs, Shacl } from "@rdf-toolkit/rdf/vocab"; 5 | import { splitRange } from "./rdfs.js"; 6 | import { getDescription, SchemaBuilder } from "./utils.js"; 7 | 8 | // 9 | // ?class a rdf:Class, sh:NodeShape; sh:property [ a sh:PropertyShape; sh:path ?property ] 10 | // 11 | // [ a sh:NodeShape; sh:targetClass ?class; sh:property [ a sh:PropertyShape; sh:path ?property ] ] 12 | // 13 | export default function decompile(graph: Graph, builder: SchemaBuilder): void { 14 | for (const nodeShape of graph.getInstances(Shacl.NodeShape)) { 15 | const classes = (graph.isClass(nodeShape) ? Ix.of(nodeShape) : Ix.empty) 16 | .concat(graph.objects(nodeShape, Shacl.targetClass).ofType(IRIOrBlankNode.is)); 17 | 18 | for (const class_ of classes) { 19 | builder.addClass(class_, getDescription(class_, graph)); 20 | } 21 | 22 | for (const propertyShape of graph.objects(nodeShape, Shacl.property).ofType(IRIOrBlankNode.is)) { 23 | const properties = graph.objects(propertyShape, Shacl.path) 24 | .ofType(IRI.is); 25 | 26 | const values = graph.objects(propertyShape, Shacl.class) 27 | .concat(graph.objects(propertyShape, Shacl.datatype)) 28 | .concat(graph.objects(propertyShape, Shacl.or).concatMap(x => splitOr(x, graph))) 29 | .concat(graph.objects(propertyShape, Shacl.in).concatMap(x => splitIn(x, graph))) 30 | .concat(graph.objects(propertyShape, Shacl.xone).concatMap(x => splitOr(x, graph))) 31 | .concat(graph.objects(propertyShape, Shacl.hasValue)) 32 | .concat(graph.objects(propertyShape, Shacl.node)) 33 | .concat(graph.objects(propertyShape, Shacl.qualifiedValueShape).concatMap(x => extract(x, graph))) 34 | .concatIfEmpty(graph.objects(propertyShape, Shacl.nodeKind).concatMap(x => mapNodeKindToClass(x))); 35 | 36 | const minCount = graph.objects(propertyShape, Shacl.minCount) 37 | .concat(graph.objects(propertyShape, Shacl.qualifiedMinCount)) 38 | .ofType(Literal.hasBigIntValue) 39 | .map(x => x.valueAsBigInt) 40 | .firstOrDefault(undefined); 41 | 42 | const maxCount = graph.objects(propertyShape, Shacl.maxCount) 43 | .concat(graph.objects(propertyShape, Shacl.qualifiedMaxCount)) 44 | .ofType(Literal.hasBigIntValue) 45 | .map(x => x.valueAsBigInt) 46 | .firstOrDefault(undefined); 47 | 48 | const propertyDescription = getDescription(propertyShape, graph); 49 | for (const class_ of classes) { 50 | for (const property of properties) { 51 | builder.addClassProperty(class_, property, propertyDescription || getDescription(property, graph), minCount, maxCount); 52 | builder.addPropertyClass(property, class_); 53 | for (const value of values.concatIfEmpty(graph.getPropertyRange(property).concatMap(x => splitRange(x, graph)))) { 54 | builder.addClassPropertyValue(class_, property, value); 55 | } 56 | } 57 | } 58 | } 59 | } 60 | } 61 | 62 | function* extract(node: Term, graph: Graph): Generator { 63 | if (IRIOrBlankNode.is(node)) { 64 | for (const t of graph.triples(node)) { 65 | switch (t.predicate) { 66 | case Shacl.class: 67 | case Shacl.datatype: 68 | case Shacl.hasValue: 69 | case Shacl.nodeKind: 70 | yield t.object; 71 | } 72 | } 73 | } 74 | } 75 | 76 | function* splitIn(node: Term, graph: Graph): Generator { 77 | if (IRIOrBlankNode.is(node)) { 78 | const items = graph.list(node); 79 | if (items) { 80 | for (const item of items) { 81 | yield item; 82 | } 83 | } 84 | } 85 | } 86 | 87 | function* splitOr(node: Term, graph: Graph): Generator { 88 | if (IRIOrBlankNode.is(node)) { 89 | const items = graph.list(node); 90 | if (items) { 91 | for (const item of items) { 92 | yield* extract(item, graph); 93 | } 94 | } 95 | } 96 | } 97 | 98 | function* mapNodeKindToClass(node: Term): Generator { 99 | switch (node) { 100 | case Shacl.BlankNode: 101 | case Shacl.IRI: 102 | case Shacl.BlankNodeOrIRI: 103 | case Shacl.BlankNodeOrLiteral: 104 | case Shacl.IRIOrLiteral: 105 | yield Rdfs.Resource; 106 | break; 107 | case Shacl.Literal: 108 | yield Rdfs.Literal; 109 | break; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /packages/schema/src/validator.ts: -------------------------------------------------------------------------------- 1 | import { Graph } from "@rdf-toolkit/rdf/graphs"; 2 | import { IRI, IRIOrBlankNode, Term } from "@rdf-toolkit/rdf/terms"; 3 | import { Class, ClassProperty, Schema } from "./main.js"; 4 | 5 | export enum ResultSeverity { 6 | Error, 7 | Warning, 8 | Information, 9 | Hint, 10 | } 11 | 12 | export enum ResultType { 13 | PropertyDeprecated = 500, 14 | ClassDeprecated, 15 | ExtraProperty, 16 | NoValue, 17 | 18 | Cardinality = 600, 19 | MissingClass, 20 | NotSatisfied, 21 | } 22 | 23 | export type PropertyDeprecatedWarning = { 24 | readonly type: ResultType.PropertyDeprecated; 25 | readonly severity: ResultSeverity.Warning; 26 | readonly property: ClassProperty; 27 | } 28 | 29 | export type ClassDeprecatedWarning = { 30 | readonly type: ResultType.ClassDeprecated; 31 | readonly severity: ResultSeverity.Warning; 32 | readonly class: Class; 33 | } 34 | 35 | export type ExtraPropertyWarning = { 36 | readonly type: ResultType.ExtraProperty; 37 | readonly severity: ResultSeverity.Warning; 38 | readonly propertyID: IRI; 39 | } 40 | 41 | export type NoValueWarning = { 42 | readonly type: ResultType.NoValue; 43 | readonly severity: ResultSeverity.Warning; 44 | readonly property: ClassProperty; 45 | readonly subject: IRIOrBlankNode; 46 | readonly object: Term; 47 | } 48 | 49 | export type CardinalityError = { 50 | readonly type: ResultType.Cardinality; 51 | readonly severity: ResultSeverity.Error; 52 | readonly property: ClassProperty; 53 | readonly actualCount: bigint; 54 | } 55 | 56 | export type NotSatisfiedError = { 57 | readonly type: ResultType.NotSatisfied; 58 | readonly severity: ResultSeverity.Error; 59 | readonly property: ClassProperty; 60 | readonly subject: IRIOrBlankNode; 61 | readonly object: Term; 62 | } 63 | 64 | export type MissingClassError = { 65 | readonly type: ResultType.MissingClass; 66 | readonly severity: ResultSeverity.Error; 67 | readonly classID: IRIOrBlankNode; 68 | } 69 | 70 | export type ValidationResult = 71 | | PropertyDeprecatedWarning 72 | | ClassDeprecatedWarning 73 | | ExtraPropertyWarning 74 | | NoValueWarning 75 | | CardinalityError 76 | | NotSatisfiedError 77 | | MissingClassError; 78 | 79 | export default function validate(subject: IRIOrBlankNode, graph: Graph, schema: Schema): ValidationResult[] { 80 | const results: ValidationResult[] = []; 81 | 82 | function matches(object: Term, value: Term) { 83 | return object.equals(value) || IRIOrBlankNode.is(value) && graph.isInstanceOf(object, value); 84 | } 85 | 86 | function validateAgainstProperty(subject: IRIOrBlankNode, property: ClassProperty): void { 87 | let actualCount = 0n; 88 | let warnDeprecated = true; 89 | 90 | for (const object of graph.objects(subject, property.id)) { 91 | 92 | if (property.deprecated && warnDeprecated) { 93 | results.push({ type: ResultType.PropertyDeprecated, severity: ResultSeverity.Warning, property }); 94 | warnDeprecated = false; 95 | } 96 | 97 | if (!property.value.length) { 98 | results.push({ type: ResultType.NoValue, severity: ResultSeverity.Warning, property, subject, object }); 99 | } 100 | else if (!property.value.some(v => matches(object, v))) { 101 | results.push({ type: ResultType.NotSatisfied, severity: ResultSeverity.Error, property, subject, object }); 102 | } 103 | 104 | actualCount++; 105 | } 106 | 107 | if (actualCount < property.minCount || property.maxCount >= 0 && actualCount > property.maxCount) { 108 | results.push({ type: ResultType.Cardinality, severity: ResultSeverity.Error, property, actualCount }); 109 | } 110 | } 111 | 112 | function validateAgainstClass(subject: IRIOrBlankNode, class_: Class): void { 113 | if (class_.deprecated) { 114 | results.push({ type: ResultType.ClassDeprecated, severity: ResultSeverity.Warning, class: class_ }); 115 | } 116 | 117 | for (const property of class_.properties) { 118 | validateAgainstProperty(subject, property); 119 | } 120 | } 121 | 122 | function validate(subject: IRIOrBlankNode): void { 123 | const extraProperties: Set = graph.triples(subject).map(t => t.predicate).toSet(); 124 | 125 | for (const classID of graph.getTypes(subject).ofType(IRI.is)) { 126 | const class_ = schema.classes.get(classID); 127 | if (!class_) { 128 | results.push({ type: ResultType.MissingClass, severity: ResultSeverity.Error, classID }); 129 | } 130 | else { 131 | validateAgainstClass(subject, class_); 132 | 133 | for (const property of class_.properties) { 134 | extraProperties.delete(property.id); 135 | } 136 | } 137 | } 138 | 139 | for (const propertyID of extraProperties) { 140 | results.push({ type: ResultType.ExtraProperty, severity: ResultSeverity.Warning, propertyID }); 141 | } 142 | } 143 | 144 | validate(subject); 145 | return results; 146 | } 147 | -------------------------------------------------------------------------------- /packages/explorer/src/main.css: -------------------------------------------------------------------------------- 1 | @import url("./fonts/iosevka-aile-custom.css"); 2 | @import url("./themes/espresso.css"); 3 | 4 | *, *:before, *:after { 5 | box-sizing: inherit; 6 | font: inherit; 7 | } 8 | 9 | html { 10 | box-sizing: border-box; 11 | overscroll-behavior: none; 12 | } 13 | 14 | body { 15 | background: var(--base00); 16 | color: var(--base07); 17 | font: 300 17px/1.6 "Iosevka Aile Custom", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 18 | font-feature-settings: "calt" 0; 19 | margin: 0; 20 | overflow-y: scroll; 21 | padding: 0; 22 | } 23 | 24 | /*@import url("./dialog.css");*/ 25 | 26 | dialog { 27 | background: var(--base01); 28 | border: 10px solid var(--base02); 29 | color: var(--base07); 30 | margin: 0 auto; 31 | max-height: 70vh; 32 | overflow-y: auto; 33 | overscroll-behavior: none; 34 | padding: 3rem; 35 | position: relative; 36 | top: 10vh; 37 | width: 70vw; 38 | } 39 | 40 | dialog::backdrop { 41 | background: rgba(0, 0, 0, 0.4); 42 | } 43 | 44 | dialog button { 45 | background: var(--base02); 46 | border: none; 47 | border-radius: .5em; 48 | cursor: pointer; 49 | display: block; 50 | outline: none; 51 | padding: .5rem; 52 | position: absolute; 53 | right: 1.5rem; 54 | top: 1.5rem; 55 | } 56 | 57 | dialog button svg { 58 | display: block; 59 | height: 1.5rem; 60 | width: 1.5rem; 61 | stroke: var(--base06); 62 | stroke-width: 1px; 63 | } 64 | 65 | dialog h1 { 66 | text-align: center; 67 | } 68 | 69 | /*@import url("./loader.css");*/ 70 | 71 | .loader-box { 72 | height: 50px; 73 | margin: 88px 0; 74 | } 75 | 76 | .loader, 77 | .loader:before, 78 | .loader:after { 79 | animation: loader 1s infinite ease-in-out; 80 | background: var(--base07); 81 | color: var(--base07); 82 | height: 40px; 83 | width: 10px; 84 | } 85 | 86 | .loader { 87 | animation-delay: -0.16s; 88 | margin: 0 auto; 89 | position: relative; 90 | transform: translateZ(0); 91 | } 92 | 93 | .loader:before, 94 | .loader:after { 95 | content: ''; 96 | position: absolute; 97 | top: 0; 98 | } 99 | 100 | .loader:before { 101 | animation-delay: -0.32s; 102 | left: -15px; 103 | } 104 | 105 | .loader:after { 106 | left: 15px; 107 | } 108 | 109 | @keyframes loader { 110 | 0%, 80%, 100% { 111 | box-shadow: 0 0; 112 | height: 40px; 113 | } 114 | 115 | 40% { 116 | box-shadow: 0 -20px; 117 | height: 50px; 118 | } 119 | } 120 | 121 | /*@import url("./progress.css");*/ 122 | 123 | .progress-bar { 124 | background: var(--base0D); 125 | top: 0; 126 | height: 5px; 127 | left: 0; 128 | position: fixed; 129 | width: 0; 130 | } 131 | 132 | .progress-bar.loading { 133 | animation: loading cubic-bezier(0.0, 0.0, 0.05, 1.0) 30s; 134 | } 135 | 136 | .progress-bar.loaded { 137 | animation: loaded ease-out 1s; 138 | } 139 | 140 | @keyframes loading { 141 | 0% { 142 | height: 5px; 143 | opacity: 1; 144 | width: 0%; 145 | } 146 | 147 | 100% { 148 | height: 5px; 149 | opacity: 1; 150 | width: 100%; 151 | } 152 | } 153 | 154 | @keyframes loaded { 155 | 0% { 156 | height: 5px; 157 | opacity: 1; 158 | width: 100%; 159 | } 160 | 161 | 100% { 162 | height: 0; 163 | opacity: 0; 164 | width: 100%; 165 | } 166 | } 167 | 168 | /*@import url("./links.css");*/ 169 | 170 | a { 171 | color: var(--base0A); 172 | text-decoration: none; 173 | } 174 | 175 | a:active, 176 | a:hover { 177 | text-decoration: underline; 178 | color: var(--base07) !important; 179 | } 180 | 181 | a[href^='http://example.com/.well-known/genid/'] { 182 | color: var(--base05); 183 | } 184 | 185 | a[href='http://www.w3.org/2002/07/owl'], 186 | a[href^='http://www.w3.org/2002/07/owl#'], 187 | a[href='http://www.w3.org/ns/shacl'], 188 | a[href^='http://www.w3.org/ns/shacl#'] { 189 | color: var(--base0B); 190 | } 191 | 192 | a[href='http://www.w3.org/1999/02/22-rdf-syntax-ns'], 193 | a[href^='http://www.w3.org/1999/02/22-rdf-syntax-ns#'], 194 | a[href='http://www.w3.org/2000/01/rdf-schema'], 195 | a[href^='http://www.w3.org/2000/01/rdf-schema#'] { 196 | color: var(--base0E); 197 | } 198 | 199 | a[href='http://www.w3.org/2001/XMLSchema'], 200 | a[href^='http://www.w3.org/2001/XMLSchema#'] { 201 | color: var(--base0F); 202 | } 203 | 204 | a[href^='https://brickschema.org/schema/Brick#'], 205 | a[href^='https://brickschema.org/schema/BrickShape#'] { 206 | color: var(--base0C); 207 | } 208 | 209 | a[href='https://w3id.org/rec'], 210 | a[href^='https://w3id.org/rec#'] { 211 | color: var(--base0D); 212 | } 213 | 214 | nav a[href='http://www.w3.org/2002/07/owl#Thing'] .rdf-iri-localname { 215 | border: 2px solid var(--base0B); 216 | border-radius: 4px; 217 | padding: 3px 5px 2px; 218 | } 219 | 220 | nav a[href='http://www.w3.org/2000/01/rdf-schema#Resource'] .rdf-iri-localname { 221 | border: 2px solid var(--base0E); 222 | border-radius: 4px; 223 | padding: 3px 5px 2px; 224 | } 225 | 226 | .nav-divider { 227 | cursor: ew-resize; 228 | height: 100vh; 229 | left: 25vw; 230 | position: fixed; 231 | width: 8px; 232 | } 233 | 234 | .nav-divider:hover { 235 | background: var(--base02); 236 | } 237 | -------------------------------------------------------------------------------- /packages/rdf/src/graphs.ts: -------------------------------------------------------------------------------- 1 | import { Ix } from "@rdf-toolkit/iterable"; 2 | import { OWLEngine } from "./engines/owl.js"; 3 | import { RDFEngine } from "./engines/rdf.js"; 4 | import { RDFSEngine } from "./engines/rdfs.js"; 5 | import { SHACLEngine } from "./engines/shacl.js"; 6 | import { XSDEngine } from "./engines/xsd.js"; 7 | import { RichGraph } from "./graphs/rich.js"; 8 | import { IRI, IRIOrBlankNode, Literal, Term } from "./terms.js"; 9 | import { Triple } from "./triples.js"; 10 | 11 | export interface Graph { 12 | 13 | triples(subject?: IRIOrBlankNode, predicate?: IRI, object?: Term): Ix; 14 | subjects(predicate: IRI, object: Term): Ix; 15 | objects(subject: IRIOrBlankNode, predicate: IRI): Ix; 16 | 17 | getLabel(node: IRIOrBlankNode): Ix; 18 | getComment(node: IRIOrBlankNode): Ix; 19 | 20 | list(head: IRIOrBlankNode): Term[] | null; 21 | 22 | classes(): Ix; 23 | domains(): Ix<[IRI, IRIOrBlankNode]>; 24 | getInstances(class_: IRIOrBlankNode): Ix; 25 | getPropertyDomain(property: IRI): Ix; 26 | getPropertyRange(property: IRI): Ix; 27 | getSuperClasses(type: IRIOrBlankNode): Ix; 28 | getSuperProperties(property: IRI): Ix; 29 | isClass(resource: Term): boolean; 30 | isDatatype(resource: Term): boolean; 31 | isInstanceOf(resource: Term, type: IRIOrBlankNode): boolean; 32 | isProperty(property: Term): boolean; 33 | isSubClassOf(resource: Term, class_: IRIOrBlankNode): boolean; 34 | isSubPropertyOf(resource: Term, property: IRI): boolean; 35 | properties(): Ix; 36 | ranges(): Ix<[IRI, IRIOrBlankNode]>; 37 | 38 | getTypes(resource: Term): Ix; 39 | getSubClasses(class_: IRIOrBlankNode): Ix; 40 | getSubProperties(property: IRI): Ix; 41 | 42 | getDirectInstances(class_: IRIOrBlankNode): Ix; 43 | getDirectTypes(resource: Term): Ix; 44 | getDirectSuperClasses(type: IRIOrBlankNode): Ix; 45 | getDirectSubClasses(type: IRIOrBlankNode): Ix; 46 | getDirectSuperProperties(property: IRI): Ix; 47 | getDirectSubProperties(property: IRI): Ix; 48 | 49 | getEquivalentClasses(type: IRIOrBlankNode): Ix; 50 | getEquivalentProperties(property: IRI): Ix; 51 | isEquivalentClassOf(resource: Term, class_: IRIOrBlankNode): boolean; 52 | isEquivalentPropertyOf(resource: Term, property: IRI): boolean; 53 | isDeprecated(resource: IRIOrBlankNode): boolean; 54 | } 55 | 56 | export namespace Graph { 57 | 58 | export function from(dataset: readonly (readonly Triple[])[]): Graph { 59 | return new GraphBuilder(dataset).build(); 60 | } 61 | } 62 | 63 | interface Engine { 64 | ingest(triple: Triple): boolean; 65 | 66 | beforeinterpret(): Generator; 67 | interpret(triple: Triple): Generator; 68 | afterinterpret(): Generator; 69 | } 70 | 71 | class GraphBuilder { 72 | private readonly rdfEngine: RDFEngine = new RDFEngine(); 73 | private readonly rdfsEngine: RDFSEngine = new RDFSEngine(); 74 | private readonly owlEngine: OWLEngine = new OWLEngine(this.rdfEngine); 75 | private readonly shaclEngine: SHACLEngine = new SHACLEngine(); 76 | private readonly xsdEngine: XSDEngine = new XSDEngine(); 77 | 78 | private readonly dataset: Iterable>; 79 | private readonly triples: Triple[]; 80 | 81 | constructor(dataset: Iterable>) { 82 | this.dataset = [...dataset, this.triples = []]; 83 | } 84 | 85 | ingest(triples: Iterable): void { 86 | for (const triple of triples) { 87 | const a1 = this.rdfEngine.ingest(triple); 88 | const a2 = this.rdfsEngine.ingest(triple); 89 | const a3 = this.owlEngine.ingest(triple); 90 | const a4 = this.shaclEngine.ingest(triple); 91 | const a5 = this.xsdEngine.ingest(triple); 92 | if (a1 || a2 || a3 || a4 || a5) { 93 | this.triples.push(triple); 94 | } 95 | } 96 | } 97 | 98 | build(): Graph { 99 | for (const triples of this.dataset) { 100 | for (const triple of triples) { 101 | this.rdfEngine.ingest(triple); 102 | this.rdfsEngine.ingest(triple); 103 | this.owlEngine.ingest(triple); 104 | this.shaclEngine.ingest(triple); 105 | this.xsdEngine.ingest(triple); 106 | } 107 | } 108 | 109 | this.ingest(this.rdfEngine.beforeinterpret()); 110 | this.ingest(this.rdfsEngine.beforeinterpret()); 111 | this.ingest(this.owlEngine.beforeinterpret()); 112 | this.ingest(this.shaclEngine.beforeinterpret()); 113 | this.ingest(this.xsdEngine.beforeinterpret()); 114 | 115 | let length; 116 | do { 117 | length = this.triples.length; 118 | 119 | for (const triples of this.dataset) { 120 | for (const triple of triples) { 121 | this.ingest(this.rdfEngine.interpret(triple)); 122 | this.ingest(this.rdfsEngine.interpret(triple)); 123 | this.ingest(this.owlEngine.interpret(triple)); 124 | this.ingest(this.shaclEngine.interpret(triple)); 125 | this.ingest(this.xsdEngine.interpret(triple)); 126 | } 127 | } 128 | } 129 | while (length < this.triples.length); 130 | 131 | this.ingest(this.rdfEngine.afterinterpret()); 132 | this.ingest(this.rdfsEngine.afterinterpret()); 133 | this.ingest(this.owlEngine.afterinterpret()); 134 | this.ingest(this.shaclEngine.afterinterpret()); 135 | this.ingest(this.xsdEngine.afterinterpret()); 136 | 137 | return new RichGraph(this.dataset, this.rdfEngine, this.rdfsEngine, this.owlEngine); 138 | } 139 | } 140 | --------------------------------------------------------------------------------