├── .nvmrc ├── .prettierignore ├── src ├── utilities │ ├── general │ │ ├── toArray.ts │ │ ├── isArray.ts │ │ ├── isBoolean.ts │ │ ├── uniquifyArray.ts │ │ ├── isNil.ts │ │ ├── cloneJson.ts │ │ ├── isArrayOfStrings.ts │ │ ├── isString.ts │ │ ├── isObject.ts │ │ ├── switchIRIProtocol.ts │ │ ├── isLanguageObjectVocab.ts │ │ ├── toAbsoluteIRI.ts │ │ ├── filterAndTransformIRIList.ts │ │ ├── outputTransformation.ts │ │ └── toCompactIRI.ts │ ├── graph │ │ ├── addEmptyArray.ts │ │ ├── nodeMergeOverwrite.ts │ │ ├── nodeMergeLanguageTerm.ts │ │ ├── isIgnoredVocabNode.ts │ │ ├── nodeMergeAddIds.ts │ │ ├── addInheritanceTermsClassAndEnum.ts │ │ ├── addInheritanceTermsDataTypesAndProperties.ts │ │ ├── curateRelationshipTermArray.ts │ │ ├── checkIfNamespaceFromListIsUsed.ts │ │ ├── checkVocabNodeType.ts │ │ ├── discoverUsedSchemaOrgProtocol.ts │ │ ├── getStandardContext.ts │ │ ├── preProcessVocab.ts │ │ ├── curateLanguageTerm.ts │ │ ├── extractFromClassMemory.ts │ │ ├── curateVocabNode.ts │ │ ├── generateContext.ts │ │ └── discoverEquateNamespaces.ts │ ├── infrastructure │ │ ├── checkIfUrlExists.ts │ │ ├── getGitHubBaseURL.ts │ │ ├── sortReleaseEntriesByDate.ts │ │ ├── getFileNameForSchemaOrgVersion.ts │ │ └── RetrievalMemory.ts │ └── reasoning │ │ ├── inferPropertiesFromSuperClasses.ts │ │ ├── inferSubDataTypes.ts │ │ ├── inferSuperDataTypes.ts │ │ ├── inferSubProperties.ts │ │ ├── inferSubClasses.ts │ │ ├── inferSuperClasses.ts │ │ ├── inferSuperProperties.ts │ │ ├── inferRangeOf.ts │ │ └── checkFilterValidity.ts ├── data │ ├── semantify.ts │ ├── schemaModules.ts │ └── namespaces.ts ├── types │ ├── OutputIRIType.type.ts │ ├── ParamObjIRIList.type.ts │ ├── ParamObjIRIListInference.type.ts │ ├── types.ts │ ├── ParamObjCreateSdoAdapter.type.ts │ └── FilterObject.type.ts ├── tsconfig.json ├── index.ts └── dist.ts ├── docs ├── .nojekyll ├── assets │ ├── custom.css │ ├── hierarchy.js │ ├── navigation.js │ └── highlight.css ├── hierarchy.html └── types │ └── Context.html ├── docu ├── typedoc.css ├── examples │ ├── example-node-external-vocabulary-2.js │ ├── example-typescript.ts │ ├── checking-enumeration-properties.js │ ├── example-node-import.mjs │ ├── checking-subproperties.js │ ├── example-browser-3.html │ ├── checking-subenumerations.js │ ├── example-node-2.js │ ├── example-browser.html │ ├── example-browser-2.html │ ├── example-node.js │ ├── comparing-schema-versions.js │ └── example-node-external-vocabulary.js └── docuReadme.md ├── tests ├── tsconfig.json ├── utilities │ ├── general │ │ ├── toAbsoluteIRI.test.ts │ │ └── toCompactIRI.test.ts │ ├── reasoning │ │ └── inferSuperClasses.test.ts │ ├── graph │ │ ├── discoverUsedSchemaOrgProtocol.test.ts │ │ ├── discoverEquateNamespaces.test.ts │ │ └── generateContext.test.ts │ └── infrastructure │ │ ├── sortReleaseEntriesByDate.test.ts │ │ ├── getFileNameForSchemaOrgVersion.test.ts │ │ └── commit-versions.test.ts ├── resources │ ├── data │ │ ├── vocabularies │ │ │ ├── graph-with-one-node.json │ │ │ ├── vocabulary-animal-dvs-altered.json │ │ │ ├── vocabulary-day-of-week.json │ │ │ ├── odta-new-context.json │ │ │ ├── vocabulary-animal.json │ │ │ ├── vocabulary-animal-2.json │ │ │ ├── vocabulary-animal-dvs.json │ │ │ ├── vocabulary-animal-altered-2.json │ │ │ └── vocabulary-animal-dvs-extend-enum-member.json │ │ ├── versions │ │ │ └── versions-24.0.json │ │ └── context │ │ │ ├── test-context.json │ │ │ └── test-context-2.json │ └── utilities │ │ └── testUtilities.ts ├── sdo-adapter │ ├── get-latest-version.test.ts │ ├── on-error.test.ts │ ├── direct-url.test.ts │ └── vocabulary-functions.test.ts ├── classes │ └── Context.test.ts ├── experimental │ └── vocabulary-comparison.ts └── index.test.ts ├── typedoc.json ├── compass.yml ├── .gitignore ├── eslint.config.mjs ├── package.json └── README.md /.nvmrc: -------------------------------------------------------------------------------- 1 | 20.19.5 -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | lib 3 | docs 4 | docu 5 | node_modules 6 | tests/resources/data 7 | -------------------------------------------------------------------------------- /src/utilities/general/toArray.ts: -------------------------------------------------------------------------------- 1 | /** @ignore */ 2 | export const toArray = (o: T | T[]): T[] => (Array.isArray(o) ? o : [o]); 3 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- 1 | TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. -------------------------------------------------------------------------------- /docu/typedoc.css: -------------------------------------------------------------------------------- 1 | .tsd-hierarchy, .tsd-sources { 2 | display: none; 3 | } 4 | 5 | ul.tsd-descriptions > li > :first-child { 6 | margin-top: 18px !important; 7 | } 8 | -------------------------------------------------------------------------------- /docs/assets/custom.css: -------------------------------------------------------------------------------- 1 | .tsd-hierarchy, .tsd-sources { 2 | display: none; 3 | } 4 | 5 | ul.tsd-descriptions > li > :first-child { 6 | margin-top: 18px !important; 7 | } 8 | -------------------------------------------------------------------------------- /docs/assets/hierarchy.js: -------------------------------------------------------------------------------- 1 | window.hierarchyData = "eJyFjjkOwyAURO8yNU4EbuLfRjmF5QIBkVFYJD6uLO5ukaRwlTRTzKI3O0rOlUGzWgSKewZnqs+JQTtUl6SjA+EeNDMEXj5ZkFQ3ga0EEEwPHF/fhctaY4D4mCBUtkNfDOY7N6sPtrgEmuU4LU1AjtMJ80hbdEX3C79gp9ofZGvtAPNBSuk=" -------------------------------------------------------------------------------- /tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "CommonJS", 4 | "esModuleInterop": true, 5 | "resolveJsonModule": true, 6 | "allowJs": true 7 | }, 8 | "include": [ 9 | "./*.test.ts", 10 | "./*.ts" , 11 | "./**/*.test.ts", 12 | "./**/*.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /src/utilities/general/isArray.ts: -------------------------------------------------------------------------------- 1 | /** @ignore 2 | * Checks if the given input is a JS array 3 | * 4 | * @param {any} value - the input element to check 5 | * @returns {boolean} true if the given input is a JS array 6 | */ 7 | export function isArray(value: unknown): value is unknown[] { 8 | return Array.isArray(value); 9 | } 10 | -------------------------------------------------------------------------------- /src/utilities/general/isBoolean.ts: -------------------------------------------------------------------------------- 1 | /** @ignore 2 | * Checks if the given input is a boolean 3 | * 4 | * @param {any} value - the input element to check 5 | * @returns {boolean} true if the given input is a boolean value 6 | */ 7 | export function isBoolean(value: unknown): value is boolean { 8 | return typeof value === "boolean" 9 | } 10 | -------------------------------------------------------------------------------- /src/utilities/general/uniquifyArray.ts: -------------------------------------------------------------------------------- 1 | /** @ignore 2 | * Removes duplicates from a given Array (the array should have the same kind of elements) 3 | * 4 | * @param {Array} array - the input array 5 | * @returns {Array} the input array without duplicates 6 | */ 7 | export function uniquifyArray(array: T[]): T[] { 8 | return [...new Set(array)]; 9 | } 10 | -------------------------------------------------------------------------------- /src/utilities/general/isNil.ts: -------------------------------------------------------------------------------- 1 | /** @ignore 2 | * Checks if the given input is undefined or null 3 | * 4 | * @param {any} value - the input element to check 5 | * @returns {boolean} true if the given input is undefined or null 6 | */ 7 | export function isNil(value: unknown): value is null | undefined { 8 | return value === undefined || value === null; 9 | } 10 | -------------------------------------------------------------------------------- /src/utilities/graph/addEmptyArray.ts: -------------------------------------------------------------------------------- 1 | import { VocabularyNode } from "../../types/types"; 2 | 3 | /** 4 | * @ignore 5 | * Adds an empty array for the given attribute, if it doesn't exist yet 6 | */ 7 | export function addEmptyArray(termObject: VocabularyNode, property: string) { 8 | if (!termObject[property]) { 9 | termObject[property] = []; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/data/semantify.ts: -------------------------------------------------------------------------------- 1 | // SEMANTIFY refers to the improved schema.org vocabulary versions hosted at semantify.it/dvs/ 2 | // hard-coded commit string, which tells SDO Adapter to use the special Semantify repository to fetch schema.org vocabularies 3 | export const SEMANTIFY_COMMIT = "SEMANTIFY"; 4 | 5 | export const SEMANTIFY_VERSION_FILE_URL = "https://semantify.it/dvs/vocabularies/schema/versions"; 6 | -------------------------------------------------------------------------------- /src/utilities/graph/nodeMergeOverwrite.ts: -------------------------------------------------------------------------------- 1 | import { VocabularyNode } from "../../types/types"; 2 | import { isNil } from "../general/isNil"; 3 | 4 | /** 5 | * @ignore 6 | */ 7 | export function nodeMergeOverwrite( 8 | oldNode: VocabularyNode, 9 | newNode: VocabularyNode, 10 | property: string 11 | ) { 12 | if (!isNil(newNode[property])) { 13 | oldNode[property] = newNode[property]; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/utilities/general/cloneJson.ts: -------------------------------------------------------------------------------- 1 | /** @ignore 2 | * Creates a clone of the given JSON input (without reference to the original input) 3 | * 4 | * @param input - the JSON element that should be copied 5 | * @returns copy of the given JSON element 6 | */ 7 | export function cloneJson(input: T): T { 8 | if (input === undefined) { 9 | return input; 10 | } 11 | return JSON.parse(JSON.stringify(input)); 12 | } 13 | -------------------------------------------------------------------------------- /src/utilities/general/isArrayOfStrings.ts: -------------------------------------------------------------------------------- 1 | import { isString } from "./isString"; 2 | 3 | /** @ignore 4 | * Checks if the given input is a JS array of strings 5 | * 6 | * @param {any} value - the input element to check 7 | * @returns {boolean} true if the given input is a JS array of strings 8 | */ 9 | export function isArrayOfStrings(value: unknown): value is string[] { 10 | return Array.isArray(value) && value.every(v => isString(v)); 11 | } 12 | -------------------------------------------------------------------------------- /src/utilities/infrastructure/checkIfUrlExists.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | /** @ignore 4 | * Sends a head-request to the given URL, checking if content exists. 5 | * 6 | * @param url - the URL to check 7 | * @returns Returns true if there is content 8 | */ 9 | export async function checkIfUrlExists(url: string) { 10 | try { 11 | await axios.head(url); 12 | return true; 13 | } catch (e) { 14 | return false; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/utilities/general/isString.ts: -------------------------------------------------------------------------------- 1 | import { isNil } from "./isNil"; 2 | 3 | /** @ignore 4 | * Checks if the given input is a string 5 | * 6 | * @param {any} value - the input element to check 7 | * @returns {boolean} true if the given input is a string 8 | */ 9 | export function isString(value: unknown): value is string { 10 | if (isNil(value)) { 11 | return false; 12 | } 13 | return typeof value === "string" || value instanceof String; 14 | } 15 | -------------------------------------------------------------------------------- /src/utilities/infrastructure/getGitHubBaseURL.ts: -------------------------------------------------------------------------------- 1 | 2 | /** @ignore 3 | * Returns the base URL for the corresponding GitHub repository, based on the given commit (commit -> original schema.org; else semantify fork) 4 | */ 5 | export function getGitHubBaseURL(commit?: string) { 6 | if (commit) { 7 | return "https://raw.githubusercontent.com/schemaorg/schemaorg/" + commit; 8 | } else { 9 | return "https://raw.githubusercontent.com/semantifyit/schemaorg/main"; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/utilities/general/isObject.ts: -------------------------------------------------------------------------------- 1 | import { isNil } from "./isNil"; 2 | import { isArray } from "./isArray"; 3 | 4 | /** @ignore 5 | * Checks if the given input is a JS object 6 | * 7 | * @param value - the input element to check 8 | * @returns true if the given input is a JS object 9 | */ 10 | export function isObject(value: unknown): value is Record { 11 | if (isArray(value)) { 12 | return false; 13 | } 14 | if (isNil(value)) { 15 | return false; 16 | } 17 | return typeof value === "object"; 18 | } 19 | -------------------------------------------------------------------------------- /src/utilities/general/switchIRIProtocol.ts: -------------------------------------------------------------------------------- 1 | /** @ignore 2 | * Returns the given absolute IRI, but with the opposite protocol (http vs. https) 3 | * 4 | * @param {string}IRI - the IRI that should be transformed 5 | * @returns {string} - the resulting transformed IRI 6 | */ 7 | export function switchIRIProtocol(IRI: string): string { 8 | if (IRI.startsWith("https://")) { 9 | return "http" + IRI.substring(5); 10 | } else if (IRI.startsWith("http://")) { 11 | return "https" + IRI.substring(4); 12 | } 13 | return IRI; 14 | } 15 | -------------------------------------------------------------------------------- /tests/utilities/general/toAbsoluteIRI.test.ts: -------------------------------------------------------------------------------- 1 | import { toAbsoluteIRI } from "../../../src/utilities/general/toAbsoluteIRI"; 2 | import CONTEXT_1 from "../../resources/data/context/test-context.json" 3 | 4 | describe("toAbsoluteIRI()", () => { 5 | test("toAbsoluteIRI", async () => { 6 | const input = "schema:Hotel"; 7 | const expectedOutcome = "http://schema.org/Hotel"; 8 | expect(toAbsoluteIRI(input, CONTEXT_1)).toBe(expectedOutcome); 9 | expect(() => toAbsoluteIRI("schemaaaa:Hotel", CONTEXT_1)).toThrow(); 10 | }); 11 | }) 12 | -------------------------------------------------------------------------------- /docs/assets/navigation.js: -------------------------------------------------------------------------------- 1 | window.navigationData = "eJyV09FugjAUBuB36bWZ0zi3ebfolpBgMOi8WXZRynHgoJD2NBlZfPcFmANsKe5O+c//geX49k0QvpAsyDKhUpIRySlGZEFY+RXkuLp8E2GakBH5jHlIFpPpw2n011tRpLsiB716TmztZ65SEBTjjOtAK7zSWEMagLBK9YjN24gsB4GFzpwTW3u78p5CmqPpMZrMJiwzXn36q2ORl2+ivtxtTm8f7yd301b7JU4QhBccgWlEOxtyPIW5Qsd3ui+3hjrhkLShgqZecFwKoAjbMNPOp0b75q71Hd9xY6n96Iv4n5rDDyCAM+0I+uaG/C2LIKXrLFSJZrazIWcHIi1P3/GdPU2UZl3m13ouDSCxis3EkLnPGA1UQkVxaTXJkMEyLlEohq++W5+PST0ozsp/txzb5rv3ms/at6mWzghWiaV6AGTR761AyLJkcgxjFvQD0KUIEjsNk2uetNPtRVtTZFEPrM1Z2FiaV7sBuxNWqn+725x9x3tI04Kb0L41n89O7z/0kHWj" -------------------------------------------------------------------------------- /tests/resources/data/vocabularies/graph-with-one-node.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": { 3 | "namespace": "http://janstestvocab.com/", 4 | "rdfs": "http://www.w3.org/2000/01/rdf-schema#", 5 | "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", 6 | "schema": "https://schema.org/" 7 | }, 8 | "@graph": [ 9 | { 10 | "@id": "namespace:AwesomePerson", 11 | "@type": "rdfs:Class", 12 | "rdfs:comment": "validValue", 13 | "rdfs:label": "validValue", 14 | "rdfs:subClassOf": { 15 | "@id": "schema:Person" 16 | } 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /src/utilities/graph/nodeMergeLanguageTerm.ts: -------------------------------------------------------------------------------- 1 | import { VocabularyNode } from "../../types/types"; 2 | import { isNil } from "../general/isNil"; 3 | 4 | /** 5 | * @ignore 6 | */ 7 | export function nodeMergeLanguageTerm( 8 | oldNode: VocabularyNode, 9 | newNode: VocabularyNode, 10 | property: string 11 | ) { 12 | if (!isNil(newNode[property])) { 13 | const langKeys = Object.keys(newNode[property]); 14 | // overwrite old one, if there was one 15 | for (const actLangKey of langKeys) { 16 | oldNode[property][actLangKey] = newNode[property][actLangKey]; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "out": "docs", 3 | "entryPoints": [ 4 | "./src/classes/", 5 | "./src/dist.ts", 6 | "./src/types/", 7 | "./src/data/namespaces.ts", 8 | "./src/data/schemaModules.ts" 9 | ], 10 | "entryPointStrategy": "expand", 11 | "tsconfig": "./src/tsconfig.json", 12 | "disableSources": true, 13 | "hideGenerator": true, 14 | "customCss": "./docu/typedoc.css", 15 | "readme": "./docu/docuReadme.md", 16 | "name": "Schema.org Adapter", 17 | "plugin": ["typedoc-plugin-merge-modules"], 18 | "mergeModulesRenameDefaults": true, 19 | "mergeModulesMergeMode": "project" 20 | } -------------------------------------------------------------------------------- /src/types/OutputIRIType.type.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Defines the format in which the IRI results from functions should be returned. 3 | * This option can be set for the SDO-Adapter on initialization, which becomes the standard (default = "Compact"). 4 | * After the initialization it is also possible to pass this option to some API-functions to get IRIs in a different format. The options are as follows: 5 | * * "Compact": The resulting IRIs are given in compact form, e.g. "schema:Hotel" 6 | * * "Absolute": The resulting IRIs are given in absolute form, e.g. "https://schema.org/Hotel" 7 | */ 8 | export type OutputIRIType = "Compact" | "Absolute"; 9 | -------------------------------------------------------------------------------- /src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2018", 4 | "lib": ["es2018"], 5 | "module": "commonjs", 6 | "outDir": "../lib/", 7 | "noImplicitAny": true, 8 | "sourceMap": true, 9 | "esModuleInterop": true, 10 | "baseUrl": "./", 11 | "rootDir": "./", 12 | "sourceRoot": "./", 13 | "resolveJsonModule": true, 14 | "removeComments": true, 15 | "noEmitOnError": true, 16 | "strict": true, 17 | "experimentalDecorators": true, 18 | "declaration": true, 19 | "declarationMap": true, 20 | "forceConsistentCasingInFileNames": true 21 | }, 22 | "include": [ 23 | "./**/*.ts" 24 | ] 25 | } -------------------------------------------------------------------------------- /src/utilities/infrastructure/sortReleaseEntriesByDate.ts: -------------------------------------------------------------------------------- 1 | /** @ignore 2 | * Returns a sorted Array of Arrays that have a schema.org vocabulary version as first entry, and it's release date as second entry. Latest is first in array. 3 | * 4 | * @param {object} releaseLog - the releaseLog object from the versionsFile of schema.org 5 | * @returns {Array>} - Array with sorted release Arrays -> [version, date] 6 | */ 7 | export function sortReleaseEntriesByDate( 8 | releaseLog: Record 9 | ): [string, string][] { 10 | const versionEntries = Object.entries(releaseLog); 11 | return versionEntries.sort((a, b) => +new Date(b[1]) - +new Date(a[1])); 12 | } 13 | -------------------------------------------------------------------------------- /tests/sdo-adapter/get-latest-version.test.ts: -------------------------------------------------------------------------------- 1 | import { SDOAdapter } from "../../src/classes/SDOAdapter"; 2 | import { commit, debugFuncErr } from "../resources/utilities/testUtilities"; 3 | import { getLatestSchemaVersion } from "../../src/classes/Infrastructure"; 4 | 5 | describe("SDO Adapter - getLatestVersion", () => { 6 | test("get latest sdo version", async () => { 7 | const mySA = new SDOAdapter({ 8 | commit, 9 | onError: debugFuncErr 10 | }); 11 | const latestVersionSDO = await mySA.getLatestSchemaVersion(); 12 | const latestVersionInfra = await getLatestSchemaVersion(commit); 13 | expect(latestVersionSDO).toBe(latestVersionInfra); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/utilities/general/isLanguageObjectVocab.ts: -------------------------------------------------------------------------------- 1 | import { isString } from "./isString"; 2 | import { isObject } from "./isObject"; 3 | import { LanguageObjectVocab } from "../../types/types"; 4 | 5 | /** @ignore 6 | * Check is the given input is a language input object, hence, an object with @language and @value 7 | * 8 | * @param value - the input element to check 9 | * @returns true if the given input is a language input object 10 | */ 11 | export function isLanguageObjectVocab( 12 | value: unknown 13 | ): value is LanguageObjectVocab { 14 | if (isObject(value)) { 15 | if (isString(value["@language"]) && isString(value["@value"])) { 16 | return true; 17 | } 18 | } 19 | return false; 20 | } 21 | -------------------------------------------------------------------------------- /compass.yml: -------------------------------------------------------------------------------- 1 | name: schema-org-adapter 2 | id: ari:cloud:compass:092f58fe-3a7a-406c-abcf-7bcbc65ebd4b:component/5eafce75-ceac-4e23-8e2b-ac4ed8100a28/cf0d8293-8288-42a1-9896-c61576b2df85 3 | description: Fast, simple & flexible API for the Schema.org Vocabulary (and vocabulary extensions!) for Node and Browsers 4 | configVersion: 1 5 | typeId: LIBRARY 6 | ownerId: null 7 | fields: 8 | tier: 4 9 | links: 10 | - name: null 11 | type: REPOSITORY 12 | url: https://github.com/semantifyit/schema-org-adapter 13 | relationships: 14 | DEPENDS_ON: [] 15 | labels: 16 | - json-ld 17 | - language:typescript 18 | - org 19 | - schema 20 | - schema-org 21 | - source:github 22 | - vocabulary 23 | customFields: null 24 | -------------------------------------------------------------------------------- /src/utilities/graph/isIgnoredVocabNode.ts: -------------------------------------------------------------------------------- 1 | import { VocabularyNode } from "../../types/types"; 2 | import { isString } from "../general/isString"; 3 | 4 | /** @ignore 5 | * ignore nodes that make no sense and "prominent" cases that are not really part of the schema vocabulary 6 | * 7 | * @param vocabNode 8 | */ 9 | export function isIgnoredVocabNode(vocabNode: VocabularyNode): boolean { 10 | const id = vocabNode["@id"]; 11 | return !isString(id) || 12 | id.startsWith("file://") || 13 | id.includes("://www.w3.org/wiki/WebSchemas/SchemaDotOrgSources") || 14 | id.includes("://meta.schema.org/") || 15 | id.includes("://publications.europa.eu") || 16 | id.includes("://www.w3.org/ns/regorg#RegisteredOrganization") 17 | } 18 | -------------------------------------------------------------------------------- /tests/utilities/general/toCompactIRI.test.ts: -------------------------------------------------------------------------------- 1 | import { toCompactIRI } from "../../../src/utilities/general/toCompactIRI"; 2 | import CONTEXT_1 from "../../resources/data/context/test-context.json"; 3 | 4 | describe("toCompactIRI()", () => { 5 | test("toCompactIRI", async () => { 6 | expect(toCompactIRI("http://schema.org/Book", CONTEXT_1)).toBe( 7 | "schema:Book" 8 | ); 9 | expect(toCompactIRI("http://schema.org/Book", CONTEXT_1, false)).toBe( 10 | "schema:Book" 11 | ); 12 | expect(toCompactIRI("https://schema.org/Book", CONTEXT_1, true)).toBe( 13 | "schema:Book" 14 | ); 15 | expect(() => 16 | toCompactIRI("https://schema.org/Book", CONTEXT_1, false) 17 | ).toThrow(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /docu/examples/example-node-external-vocabulary-2.js: -------------------------------------------------------------------------------- 1 | const { SOA } = require("../../lib/index"); // run the npm-script "buildTs" to generate js files for this example 2 | const VOC_EXAMPLE = require("../../tests/resources/data/vocabularies/paymentMethods.json"); // load our external vocabulary 3 | 4 | main(); 5 | 6 | /** 7 | * example usage of the SDOAdapter in node.js 8 | */ 9 | async function main() { 10 | const mySA = await SOA.create({ schemaVersion: "20.0", vocabularies: [VOC_EXAMPLE], commit: "SEMANTIFY" }); 11 | let PaymentMethod = mySA.getEnumeration("schema:PaymentMethod"); 12 | console.log(mySA.getVocabularies(true)); 13 | console.log(JSON.stringify(PaymentMethod.toJSON(), null, 2)); 14 | console.log(JSON.stringify(mySA.getEnumerationMember("gr:VISA").toJSON(), null, 2)); 15 | } 16 | -------------------------------------------------------------------------------- /tests/sdo-adapter/on-error.test.ts: -------------------------------------------------------------------------------- 1 | import { SDOAdapter } from "../../src/classes/SDOAdapter"; 2 | import { commit, debugFunc } from "../resources/utilities/testUtilities"; 3 | 4 | describe("SDO Adapter - OnError", () => { 5 | test("onError function", async () => { 6 | // this test should trigger the onError function, outputting invalid nodes in the schema.org vocabulary version 3.2 7 | let mySA = new SDOAdapter({ 8 | commit, 9 | onError: function (text) { 10 | debugFunc(text); 11 | } 12 | }); 13 | const versionUrl = await mySA.constructURLSchemaVocabulary("12.0"); 14 | await mySA.addVocabularies([versionUrl]); 15 | // test without onError function 16 | mySA = new SDOAdapter({ commit }); 17 | await mySA.addVocabularies([versionUrl]); 18 | // generic test 19 | expect(mySA.getListOfProperties().length > 300).toBe(true); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/types/ParamObjIRIList.type.ts: -------------------------------------------------------------------------------- 1 | import { FilterObject } from "./FilterObject.type"; 2 | import { OutputIRIType } from "./OutputIRIType.type"; 3 | 4 | /** 5 | * A **parameter object** to filter and format the output of a functions that returns a list of IRIs 6 | */ 7 | export type ParamObjIRIList = { 8 | /** 9 | * The filter to be applied on the result 10 | */ 11 | filter?: FilterObject; 12 | 13 | /** 14 | * Defines the format in which the IRI results of this functions should be returned. 15 | * Use this parameter only if the wished format is different to the default format set for the SDO-Adapter during its initialization. 16 | * The possible formats are: 17 | * * "Compact": The resulting IRIs are given in compact form, e.g. "schema:Hotel" 18 | * * "Absolute": The resulting IRIs are given in absolute form, e.g. "https://schema.org/Hotel" 19 | */ 20 | outputFormat?: OutputIRIType; 21 | }; 22 | -------------------------------------------------------------------------------- /tests/utilities/reasoning/inferSuperClasses.test.ts: -------------------------------------------------------------------------------- 1 | import { SOA } from "../../../src"; 2 | import VOC_OBJ_ZOO_A2 from "../../resources/data/vocabularies/vocabulary-animal-altered-2.json"; 3 | 4 | describe("inferSuperClasses()", () => { 5 | // applyFilter with partial vocabulary (referenced terms are not part of the current vocabulary) 6 | test("inferSuperClasses", async () => { 7 | const mySA = await SOA.create({ 8 | vocabularies: [VOC_OBJ_ZOO_A2] 9 | }); 10 | // custom vocabulary that includes terms referencing themselves as super-entities. Our API should not include them in the result and should not cause an infinite recursion 11 | const c = mySA.getClass("ex:Animal"); 12 | expect(c.getSuperClasses()).not.toContain("ex:Animal"); 13 | const p = mySA.getProperty("ex:numberOfLegs"); 14 | expect(p.getSuperProperties()).not.toContain("ex:numberOfLegs"); 15 | const e = mySA.getEnumeration("ex:AnimalLivingEnvironment"); 16 | expect(e.getSuperClasses()).not.toContain("ex:AnimalLivingEnvironment"); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // reexport as named-object for Node 2 | export * as SOA from "./dist"; 3 | // reexport all types and interfaces that could be used by the user 4 | export * from "./types/types"; 5 | export type { FilterObject } from "./types/FilterObject.type"; 6 | export type { ParamObjCreateSdoAdapter } from "./types/ParamObjCreateSdoAdapter.type"; 7 | export type { ParamObjIRIList } from "./types/ParamObjIRIList.type"; 8 | export type { OutputIRIType } from "./types/OutputIRIType.type"; 9 | export { TermTypeLabel, TermTypeIRI, TermType, TermTypeIRIValue, TermTypeLabelValue } from "./data/namespaces"; 10 | // export types for the classes provided for this library 11 | export type { Class } from "./classes/Class"; 12 | export type { DataType } from "./classes/DataType"; 13 | export type { Property } from "./classes/Property"; 14 | export type { Enumeration } from "./classes/Enumeration"; 15 | export type { EnumerationMember } from "./classes/EnumerationMember"; 16 | export type { Term } from "./classes/Term"; 17 | export type { SDOAdapter } from "./classes/SDOAdapter"; 18 | -------------------------------------------------------------------------------- /src/utilities/graph/nodeMergeAddIds.ts: -------------------------------------------------------------------------------- 1 | import { VocabularyNode } from "../../types/types"; 2 | import { isNil } from "../general/isNil"; 3 | import { isArray } from "../general/isArray"; 4 | 5 | /** 6 | * @ignore 7 | */ 8 | export function nodeMergeAddIds( 9 | oldNode: VocabularyNode, 10 | newNode: VocabularyNode, 11 | property: string 12 | ) { 13 | if (!isNil(newNode[property])) { 14 | let newValues = newNode[property] 15 | if(!isArray(newValues)){ 16 | // make sure the new values are used as array in the following algorithm 17 | newValues = [newValues] 18 | } 19 | for (const arrayElement of newValues) { 20 | // make sure the old value is changed to an array if needed 21 | if(!isArray(oldNode[property])){ 22 | if(oldNode[property] !== arrayElement){ 23 | oldNode[property] = [oldNode[property], arrayElement] 24 | } 25 | } else if (!oldNode[property].includes(arrayElement)) { 26 | // add new entry 27 | oldNode[property].push(arrayElement); 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/utilities/general/toAbsoluteIRI.ts: -------------------------------------------------------------------------------- 1 | import { Context, ContextWord } from "../../types/types"; 2 | 3 | /** @ignore 4 | * Returns the absolute IRI from a given compact IRI and a corresponding context. If the context does not contain the used namespace, then an error is thrown 5 | * 6 | * @param compactIRI - the compact IRI to transform 7 | * @param context - the context object holding key-value pairs that represent indicator-namespace pairs 8 | * @returns {?string} the absolute IRI (null, if given context does not contain the used namespace) 9 | */ 10 | export function toAbsoluteIRI(compactIRI: string, context: Context): string { 11 | const terms = Object.keys(context); 12 | for (let i = 0; i < terms.length; i++) { 13 | const vocabIRI = context[terms[i]] as ContextWord; 14 | if (compactIRI.substring(0, compactIRI.indexOf(":")) === terms[i]) { 15 | return vocabIRI.concat(compactIRI.substring(compactIRI.indexOf(":") + 1)); 16 | } 17 | } 18 | throw new Error( 19 | "Trying to get an absolute IRI for a term with no entry in the Context" 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /src/types/ParamObjIRIListInference.type.ts: -------------------------------------------------------------------------------- 1 | import { ParamObjIRIList } from "./ParamObjIRIList.type"; 2 | 3 | /** 4 | * A **parameter object** to filter and format the output of a functions that returns a list of IRIs. 5 | * Also includes a reasoning parameter "implicit", which defaults to true and makes it possible to include implicit data in the results based on inferences (e.g. the direct subclasses of a target class are **explicit subclasses**. The subclasses of those subclasses can be inferred as **implicit subclasses** for the target class) 6 | * 7 | * @example 8 | * ```JS 9 | * // the following parameter object uses no filter. It states that the returned result should be formatted as absolute IRIs and that implicit data should be included 10 | * const exampleParameters = { 11 | * outputFormat: "Absolute", 12 | * implicit: true 13 | * } 14 | * ``` 15 | */ 16 | export type ParamObjIRIListInference = ParamObjIRIList & { 17 | /** 18 | * If true (default), includes also implicit data (e.g. subclasses, superclasses, properties, etc.) 19 | */ 20 | implicit?: boolean; 21 | }; 22 | -------------------------------------------------------------------------------- /docu/examples/example-typescript.ts: -------------------------------------------------------------------------------- 1 | import { SOA } from "../../lib/"; 2 | import { SDOAdapter } from "../../lib/"; 3 | import { Class } from "../../lib/"; 4 | import { Property } from "../../lib/"; 5 | import { Term } from "../../lib/"; 6 | import { DataType } from "../../lib/"; 7 | import { EnumerationMember } from "../../lib/"; 8 | import { Enumeration } from "../../lib/"; 9 | 10 | main(); 11 | 12 | async function main() { 13 | const mySA: SDOAdapter = await SOA.create({ 14 | schemaVersion: "20.0", 15 | commit: "SEMANTIFY" 16 | }); 17 | const c: Class = mySA.getClass("schema:Hotel"); 18 | console.log(c.getNames()) 19 | const p: Property = mySA.getProperty("schema:name"); 20 | console.log(p.getNames()) 21 | const t: Term = mySA.getTerm("schema:name"); 22 | console.log(t.getNames()) 23 | const dt: DataType = mySA.getDataType("schema:Text"); 24 | console.log(dt.getNames()) 25 | const e: Enumeration = mySA.getEnumeration("schema:DayOfWeek"); 26 | console.log(e.getNames()) 27 | const em: EnumerationMember = mySA.getEnumerationMember("schema:Monday"); 28 | console.log(em.getNames()) 29 | } 30 | -------------------------------------------------------------------------------- /src/utilities/general/filterAndTransformIRIList.ts: -------------------------------------------------------------------------------- 1 | import { outputTransformation } from "./outputTransformation"; 2 | import { applyFilter } from "../reasoning/applyFilter"; 3 | import { ParamObjIRIList } from "../../types/ParamObjIRIList.type"; 4 | import { Graph } from "../../classes/Graph"; 5 | import { uniquifyArray } from "./uniquifyArray"; 6 | 7 | /** @ignore 8 | * shortcut function to apply a filter and a transformation function on a given compactIRIList (array of compact IRIs), also uniquifies the result 9 | * @param compactIRIList - Array of compact IRIs to filter and transform 10 | * @param graph - a reference to the Graph instance 11 | * @param paramObj - optional parameter object with parameters to filter and transform 12 | * @returns Array of filtered and transformed compact IRIs 13 | */ 14 | export function filterAndTransformIRIList(compactIRIList: string[], graph: Graph, paramObj?: ParamObjIRIList) { 15 | return uniquifyArray( 16 | outputTransformation( 17 | applyFilter({ data: compactIRIList, filter: paramObj?.filter, graph: graph }), 18 | graph, 19 | paramObj?.outputFormat 20 | ) 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /src/utilities/general/outputTransformation.ts: -------------------------------------------------------------------------------- 1 | import { OutputIRIType } from "../../types/OutputIRIType.type"; 2 | import { toAbsoluteIRI } from "./toAbsoluteIRI"; 3 | import { Graph } from "../../classes/Graph"; 4 | import { isString } from "./isString"; 5 | import { isArrayOfStrings } from "./isArrayOfStrings"; 6 | 7 | /** @ignore 8 | * transforms the input compact IRI(s) into the desired output format if given, else the default format stated in the Graph is taken 9 | */ 10 | export function outputTransformation(input: T, graph: Graph, outputFormat?: OutputIRIType): T { 11 | const format = outputFormat ? outputFormat : graph.outputFormat; 12 | if (format === "Compact") { 13 | return input; 14 | } 15 | // right now there is only the "Absolute" option left 16 | if (isString(input)) { 17 | // input is a string 18 | return toAbsoluteIRI(input, graph.context) as T; 19 | } else if (isArrayOfStrings(input)) { 20 | // input is an array of strings 21 | return input.map((s) => toAbsoluteIRI(s, graph.context)) as T; 22 | } 23 | throw new Error("Wrong input type! - must be string or array of strings"); 24 | } 25 | -------------------------------------------------------------------------------- /docu/examples/checking-enumeration-properties.js: -------------------------------------------------------------------------------- 1 | const { SOA } = require("../../lib/index"); // run the npm-script "buildTs" to generate js files for this example 2 | 3 | main(); 4 | 5 | // analyze the properties of enumerations 6 | async function main() { 7 | const sdoVersion = "28.1"; 8 | const commit = "9d7406b660197aa9559090bcaf516ad80fdce7a0"; 9 | const mySoa = await SOA.create({ schemaVersion: sdoVersion, commit: commit }); 10 | const enumerationList = mySoa.getListOfEnumerations(); 11 | console.log("#Enumerations " + sdoVersion + ": " + enumerationList.length); 12 | 13 | // enumerations with sub-enumerations 14 | for (const enumeration of enumerationList) { 15 | try { 16 | const enumInstance = mySoa.getEnumeration(enumeration); 17 | // const propertiesImpl = enumInstance.getProperties({implicit:true}) 18 | const propertiesExpl = enumInstance.getProperties({implicit:false}) 19 | if(propertiesExpl.length > 0) { 20 | console.log("Enumeration " + enumeration + " has following explicit properties: ", propertiesExpl); 21 | } 22 | } catch (e) { 23 | console.error(e); 24 | } 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/utilities/infrastructure/getFileNameForSchemaOrgVersion.ts: -------------------------------------------------------------------------------- 1 | /** @ignore 2 | * Returns the jsonld filename that holds the schema.org vocabulary for a given version. 3 | * 4 | * @param version - the schema.org version 5 | * @param [schemaHttps = true] - use https as protocol for the schema.org vocabulary - works only from version 9.0 upwards 6 | * @returns - the corresponding jsonld filename 7 | */ 8 | export function getFileNameForSchemaOrgVersion( 9 | version: string, 10 | schemaHttps = true 11 | ): string { 12 | // JSON-LD vocabulary files were available from version 3.1 on 13 | if (!(Number(version) > 3.0)) { 14 | throw new Error( 15 | "There is no jsonld file for the wanted schema.org version " + version 16 | ); 17 | } 18 | // JSON-LD https vocabulary files were available from version 9.0 on 19 | // we could throw an error if schemaHttps is required as true, but this could introduce a new bug for already existing code, since https is the default option and probably used a lot 20 | if (!(Number(version) > 8.0)) { 21 | return "all-layers.jsonld"; 22 | } 23 | if (schemaHttps) { 24 | return "schemaorg-all-https.jsonld"; 25 | } 26 | return "schemaorg-all-http.jsonld"; 27 | } 28 | -------------------------------------------------------------------------------- /docu/examples/example-node-import.mjs: -------------------------------------------------------------------------------- 1 | import { SOA } from "../../lib/index.js"; // run the npm-script "buildTs" to generate js files for this example 2 | main(); 3 | 4 | /** 5 | * example usage of the library within node.js 6 | */ 7 | async function main() { 8 | const mySA = await SOA.create(); 9 | const mySDOUrl = await mySA.constructURLSchemaVocabulary("latest"); 10 | console.log("The latest version is " + mySDOUrl); 11 | await mySA.addVocabularies([mySDOUrl]); 12 | let testClass = mySA.getClass("https://schema.org/Person"); 13 | console.log(mySA.getVocabularies()); 14 | console.log("getIRI() " + testClass.getIRI()); 15 | console.log("getName() " + testClass.getName()); 16 | console.log("getDescription(\"en\") " + testClass.getDescription("en")); 17 | console.log("getProperties() " + testClass.getProperties()); 18 | const testProp = mySA.getProperty("schema:aspect"); 19 | console.log("isSuperseededBy() " + testProp.isSupersededBy()); 20 | testClass = mySA.getClass("schema:Person"); 21 | console.log(JSON.stringify(testClass.toJSON({ implicit: false }), null, 2)); 22 | const testProperty = mySA.getProperty("schema:translationOfWork"); 23 | console.log(JSON.stringify(testProperty.toJSON({ implicit: false }), null, 2)); 24 | } 25 | -------------------------------------------------------------------------------- /tests/utilities/graph/discoverUsedSchemaOrgProtocol.test.ts: -------------------------------------------------------------------------------- 1 | import { discoverUsedSchemaOrgProtocol } from "../../../src/utilities/graph/discoverUsedSchemaOrgProtocol"; 2 | import VOC_OBJ_ZOO from "../../resources/data/vocabularies/vocabulary-animal.json"; 3 | import VOC_OBJ_ZOO_2 from "../../resources/data/vocabularies/vocabulary-animal-2.json"; 4 | import VOC_OBJ_SDO_3_7 from "../../resources/data/vocabularies/schema/schema-3.7.json"; 5 | import VOC_OBJ_SDO_10 from "../../resources/data/vocabularies/schema/schema-10.0.json"; 6 | import CONTEXT_1 from "../../resources/data/context/test-context.json"; 7 | import CONTEXT_2 from "../../resources/data/context/test-context-2.json"; 8 | 9 | describe("discoverUsedSchemaOrgProtocol()", () => { 10 | test("discoverUsedSchemaOrgProtocol", async () => { 11 | expect(discoverUsedSchemaOrgProtocol(VOC_OBJ_ZOO)).toBe("https"); 12 | expect(discoverUsedSchemaOrgProtocol(VOC_OBJ_ZOO_2)).toBe("https"); 13 | expect(discoverUsedSchemaOrgProtocol(VOC_OBJ_SDO_3_7)).toBe("http"); 14 | expect(discoverUsedSchemaOrgProtocol(VOC_OBJ_SDO_10)).toBe("https"); 15 | expect(discoverUsedSchemaOrgProtocol(CONTEXT_1)).toBe("http"); 16 | expect(discoverUsedSchemaOrgProtocol(CONTEXT_2)).toBe("http"); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /tests/utilities/graph/discoverEquateNamespaces.test.ts: -------------------------------------------------------------------------------- 1 | import { discoverEquateNamespaces } from "../../../src/utilities/graph/discoverEquateNamespaces"; 2 | import VOC_OBJ_ZOO from "../../resources/data/vocabularies/vocabulary-animal.json"; 3 | import VOC_OBJ_ZOO_2 from "../../resources/data/vocabularies/vocabulary-animal-2.json"; 4 | import VOC_OBJ_SDO_3_7 from "../../resources/data/vocabularies/schema/schema-3.7.json"; 5 | import CONTEXT_1 from "../../resources/data/context/test-context.json"; 6 | 7 | describe("discoverEquateNamespaces()", () => { 8 | test("discoverEquateNamespaces", async () => { 9 | expect( 10 | Array.isArray(discoverEquateNamespaces(CONTEXT_1, VOC_OBJ_ZOO)) 11 | ).toBe(true); 12 | expect(discoverEquateNamespaces(CONTEXT_1, VOC_OBJ_ZOO).length).toBe(1); 13 | expect(discoverEquateNamespaces(CONTEXT_1, VOC_OBJ_ZOO)[0]).toBe( 14 | "https://schema.org/" 15 | ); 16 | expect(discoverEquateNamespaces(CONTEXT_1, VOC_OBJ_ZOO_2).length).toBe(1); 17 | expect(discoverEquateNamespaces(CONTEXT_1, VOC_OBJ_ZOO_2)[0]).toBe( 18 | "https://schema.org/" 19 | ); 20 | expect( 21 | discoverEquateNamespaces(VOC_OBJ_ZOO["@context"], VOC_OBJ_SDO_3_7)[0] 22 | ).toBe("http://schema.org/"); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /tests/resources/data/versions/versions-24.0.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaversion": "24.0", 3 | "releaseLog": { 4 | "24.0": "2023-12-15", 5 | "23.0": "2023-10-17", 6 | "22.0": "2023-06-29", 7 | "21.0": "2023-05-23", 8 | "20.0": "2023-05-22", 9 | "19.0": "2023-05-19", 10 | "18.0": "2023-05-18", 11 | "17.0": "2023-05-17", 12 | "16.0": "2023-05-16", 13 | "15.0": "2022-10-25", 14 | "14.0": "2022-03-17", 15 | "13.0": "2021-07-07", 16 | "12.0": "2021-03-08", 17 | "11.01": "2020-12-04", 18 | "11.0": "2020-11-30", 19 | "10.0": "2020-08-15", 20 | "9.0": "2020-07-21", 21 | "8.0": "2020-04-29", 22 | "7.04": "2020-04-16", 23 | "7.03": "2020-04-02", 24 | "7.02": "2020-03-31", 25 | "7.01": "2020-03-22", 26 | "7.0": "2020-03-13", 27 | "6.0": "2020-01-15", 28 | "5.0": "2019-11-01", 29 | "4.0": "2019-10-15", 30 | "3.9": "2019-08-01", 31 | "3.8": "2019-07-01", 32 | "3.7": "2019-06-01", 33 | "3.6": "2019-05-01", 34 | "3.5": "2019-04-01", 35 | "3.4": "2018-06-15", 36 | "3.3": "2017-08-14", 37 | "3.2": "2017-03-23", 38 | "3.1": "2016-08-09", 39 | "3.0": "2016-05-04", 40 | "2.2": "2015-11-05", 41 | "2.1": "2015-08-06", 42 | "2.0": "2015-05-13" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/utilities/reasoning/inferPropertiesFromSuperClasses.ts: -------------------------------------------------------------------------------- 1 | import { Graph } from "../../classes/Graph"; 2 | import { NS } from "../../data/namespaces"; 3 | import { uniquifyArray } from "../general/uniquifyArray"; 4 | 5 | /** @ignore 6 | * Infers all properties that can be used by the given classes and all their implicit and explicit superClasses 7 | * 8 | * @param superClasses - Array with IRIs of classes/enumerations 9 | * @param graph - the graph 10 | * @returns {string[]} Array of IRIs of all properties from the given classes and their implicit and explicit superClasses 11 | */ 12 | export function inferPropertiesFromSuperClasses( 13 | superClasses: string[], 14 | graph: Graph 15 | ): string[] { 16 | const result = []; 17 | for (const superClass of superClasses) { 18 | const superClassObj = 19 | graph.classes[superClass] || graph.enumerations[superClass]; 20 | if (superClassObj) { 21 | result.push(...superClassObj[NS.soa.hasProperty]); 22 | if (superClassObj[NS.rdfs.subClassOf].length !== 0) { 23 | result.push( 24 | ...inferPropertiesFromSuperClasses( 25 | superClassObj[NS.rdfs.subClassOf], 26 | graph 27 | ) 28 | ); 29 | } 30 | } 31 | } 32 | return uniquifyArray(result); 33 | } 34 | -------------------------------------------------------------------------------- /src/utilities/graph/addInheritanceTermsClassAndEnum.ts: -------------------------------------------------------------------------------- 1 | import { TermMemory } from "../../types/types"; 2 | 3 | /** @ignore 4 | * Part D.1) of the addVocabulary-algorithm 5 | * Add link to subclass for classes and enumerations 6 | */ 7 | export function addInheritanceTermsClassAndEnum( 8 | memory: TermMemory, 9 | enumerationsMemory: TermMemory, 10 | subOfProperty: string, 11 | superOfProperty: string 12 | ) { 13 | const classesKeys = Object.keys(memory); 14 | for (const actClassKey of classesKeys) { 15 | const superClasses = memory[actClassKey][subOfProperty]; 16 | // add empty superClassOf if not defined 17 | if (!memory[actClassKey][superOfProperty]) { 18 | memory[actClassKey][superOfProperty] = []; 19 | } 20 | for (const actSuperClass of superClasses) { 21 | let superClass = memory[actSuperClass]; 22 | if (!superClass) { 23 | superClass = enumerationsMemory[actSuperClass]; 24 | } 25 | if (superClass) { 26 | if (superClass[superOfProperty]) { 27 | if (!superClass[superOfProperty].includes(actClassKey)) { 28 | superClass[superOfProperty].push(actClassKey); 29 | } 30 | } else { 31 | superClass[superOfProperty] = [actClassKey]; 32 | } 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /docu/examples/checking-subproperties.js: -------------------------------------------------------------------------------- 1 | const { SOA } = require("../../lib/index"); // run the npm-script "buildTs" to generate js files for this example 2 | 3 | main(); 4 | 5 | // analyze the hierarchy of properties 6 | async function main() { 7 | const sdoVersion = "28.1"; 8 | const commit = "9d7406b660197aa9559090bcaf516ad80fdce7a0"; 9 | const mySoa = await SOA.create({ schemaVersion: sdoVersion, commit: commit }); 10 | const propertiesV1 = mySoa.getListOfProperties(); 11 | console.log("#Properties " + sdoVersion + ": " + propertiesV1.length); 12 | 13 | // properties with ranges that they are no longer allowed to use 14 | for (const p of propertiesV1) { 15 | try { 16 | const prop = mySoa.getProperty(p); 17 | const subPropertiesDirect = prop.getSubProperties({implicit:false}); 18 | if (subPropertiesDirect.length > 0) { 19 | console.log("Property " + p + " has direct sub-properties:", subPropertiesDirect); 20 | } 21 | const subPropertiesIndirect = prop.getSubProperties({implicit:true}).filter(sp => !subPropertiesDirect.includes(sp)); 22 | if (subPropertiesIndirect.length > 0) { 23 | console.log("Property " + p + " has indirect sub-properties:", subPropertiesIndirect); 24 | } 25 | } catch (e) { 26 | console.error(e); 27 | } 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/utilities/graph/addInheritanceTermsDataTypesAndProperties.ts: -------------------------------------------------------------------------------- 1 | import { TermMemory } from "../../types/types"; 2 | 3 | /** @ignore 4 | * Part D.2) and D.3) of the addVocabulary-algorithm 5 | * Add link to subclass for classes and enumerations 6 | */ 7 | export function addInheritanceTermsDataTypesAndProperties( 8 | memory: TermMemory, 9 | subOfProperty: string, 10 | superOfProperty: string 11 | ) { 12 | const dataTypeKeys = Object.keys(memory); 13 | for (const actDtKey of dataTypeKeys) { 14 | const superClasses = memory[actDtKey][subOfProperty]; 15 | // add empty superClassOf if not defined 16 | if (!memory[actDtKey][superOfProperty]) { 17 | memory[actDtKey][superOfProperty] = []; 18 | } 19 | // add empty subClassOf if not defined 20 | if (!superClasses) { 21 | memory[actDtKey][subOfProperty] = []; 22 | } else { 23 | for (const actSuperClass of superClasses) { 24 | const superClass = memory[actSuperClass]; 25 | if (superClass) { 26 | if (superClass[superOfProperty]) { 27 | if (!superClass[superOfProperty].includes(actDtKey)) { 28 | superClass[superOfProperty].push(actDtKey); 29 | } 30 | } else { 31 | superClass[superOfProperty] = [actDtKey]; 32 | } 33 | } 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/utilities/reasoning/inferSubDataTypes.ts: -------------------------------------------------------------------------------- 1 | import { Graph } from "../../classes/Graph"; 2 | import { NS } from "../../data/namespaces"; 3 | import { cloneJson } from "../general/cloneJson"; 4 | import { uniquifyArray } from "../general/uniquifyArray"; 5 | 6 | /** @ignore 7 | * Infers all implicit and explicit subDataTypes of a given DataType 8 | * 9 | * @param dataTypeIRI - IRI of a DataType 10 | * @param graph - the graph 11 | * @returns Array of IRI of all implicit and explicit subDataTypes 12 | */ 13 | export function inferSubDataTypes(dataTypeIRI: string, graph: Graph): string[] { 14 | let result = []; 15 | const dataTypeObj = graph.dataTypes[dataTypeIRI]; 16 | if (dataTypeObj) { 17 | result.push(...dataTypeObj[NS.soa.superClassOf]); 18 | let addition = cloneJson(result); // make a copy 19 | do { 20 | let newAddition = []; 21 | for (const curAdd of addition) { 22 | const childDataTypeObj = graph.dataTypes[curAdd]; 23 | if (childDataTypeObj) { 24 | newAddition.push(...childDataTypeObj[NS.soa.superClassOf]); 25 | } 26 | } 27 | newAddition = uniquifyArray(newAddition); 28 | addition = cloneJson(newAddition); 29 | result.push(...newAddition); 30 | } while (addition.length !== 0); 31 | result = uniquifyArray(result); 32 | } 33 | return result; 34 | } 35 | -------------------------------------------------------------------------------- /src/utilities/reasoning/inferSuperDataTypes.ts: -------------------------------------------------------------------------------- 1 | import { Graph } from "../../classes/Graph"; 2 | import { NS } from "../../data/namespaces"; 3 | import { cloneJson } from "../general/cloneJson"; 4 | import { uniquifyArray } from "../general/uniquifyArray"; 5 | 6 | /** @ignore 7 | * Infers all implicit and explicit superDataTypes of a given DataType 8 | * 9 | * @param dataTypeIRI - IRI of a DataType 10 | * @param graph - the graph 11 | * @returns Array of IRI of all implicit and explicit superDataTypes 12 | */ 13 | export function inferSuperDataTypes( 14 | dataTypeIRI: string, 15 | graph: Graph 16 | ): string[] { 17 | let result = []; 18 | const dataTypeObj = graph.dataTypes[dataTypeIRI]; 19 | if (dataTypeObj) { 20 | result.push(...dataTypeObj[NS.rdfs.subClassOf]); 21 | let addition = cloneJson(result); // make a copy 22 | do { 23 | let newAddition = []; 24 | for (const curAdd of addition) { 25 | const parentDataTypeObj = graph.dataTypes[curAdd]; 26 | if (parentDataTypeObj) { 27 | newAddition.push(...parentDataTypeObj[NS.rdfs.subClassOf]); 28 | } 29 | } 30 | newAddition = uniquifyArray(newAddition); 31 | addition = cloneJson(newAddition); 32 | result.push(...newAddition); 33 | } while (addition.length !== 0); 34 | result = uniquifyArray(result); 35 | } 36 | return result; 37 | } 38 | -------------------------------------------------------------------------------- /src/utilities/reasoning/inferSubProperties.ts: -------------------------------------------------------------------------------- 1 | import { Graph } from "../../classes/Graph"; 2 | import { NS } from "../../data/namespaces"; 3 | import { cloneJson } from "../general/cloneJson"; 4 | import { uniquifyArray } from "../general/uniquifyArray"; 5 | 6 | /** @ignore 7 | * Infers all implicit and explicit subProperties of a given Property 8 | * 9 | * @param {string} propertyIRI - IRI of a Property 10 | * @param graph - the graph 11 | * @returns {string[]} Array of IRI of all implicit and explicit subProperties 12 | */ 13 | export function inferSubProperties( 14 | propertyIRI: string, 15 | graph: Graph 16 | ): string[] { 17 | let result = []; 18 | const propertyObj = graph.properties[propertyIRI]; 19 | if (propertyObj) { 20 | result.push(...propertyObj[NS.soa.superPropertyOf]); 21 | let addition = cloneJson(result); // make a copy 22 | do { 23 | let newAddition = []; 24 | for (const curAdd of addition) { 25 | const parentPropertyObj = graph.properties[curAdd]; 26 | if (parentPropertyObj) { 27 | newAddition.push(...parentPropertyObj[NS.soa.superPropertyOf]); 28 | } 29 | } 30 | newAddition = uniquifyArray(newAddition); 31 | addition = cloneJson(newAddition); 32 | result.push(...newAddition); 33 | } while (addition.length !== 0); 34 | result = uniquifyArray(result); 35 | } 36 | return result; 37 | } 38 | -------------------------------------------------------------------------------- /src/utilities/reasoning/inferSubClasses.ts: -------------------------------------------------------------------------------- 1 | import { Graph } from "../../classes/Graph"; 2 | import { NS } from "../../data/namespaces"; 3 | import { cloneJson } from "../general/cloneJson"; 4 | import { uniquifyArray } from "../general/uniquifyArray"; 5 | 6 | /** @ignore 7 | * Infers all implicit and explicit subClasses of a given Class/Enumeration 8 | * 9 | * @param classIRI - IRI of a Class/Enumeration 10 | * @param graph - the graph 11 | * @returns Array of IRI of all implicit and explicit subClasses 12 | */ 13 | export function inferSubClasses(classIRI: string, graph: Graph): string[] { 14 | let result = []; 15 | const classObj = graph.classes[classIRI] || graph.enumerations[classIRI]; 16 | if (classObj) { 17 | result.push(...classObj[NS.soa.superClassOf]); 18 | let addition = cloneJson(result); // make a copy 19 | do { 20 | let newAddition = []; 21 | for (const curAdd of addition) { 22 | const parentClassObj = 23 | graph.classes[curAdd] || graph.enumerations[curAdd]; 24 | if (parentClassObj) { 25 | newAddition.push(...parentClassObj[NS.soa.superClassOf]); 26 | } 27 | } 28 | newAddition = uniquifyArray(newAddition); 29 | addition = cloneJson(newAddition); 30 | result.push(...newAddition); 31 | } while (addition.length !== 0); 32 | result = uniquifyArray(result); 33 | } 34 | return result; 35 | } 36 | -------------------------------------------------------------------------------- /src/utilities/reasoning/inferSuperClasses.ts: -------------------------------------------------------------------------------- 1 | import { Graph } from "../../classes/Graph"; 2 | import { NS } from "../../data/namespaces"; 3 | import { cloneJson } from "../general/cloneJson"; 4 | import { uniquifyArray } from "../general/uniquifyArray"; 5 | 6 | /** @ignore 7 | * Infers all implicit and explicit superClasses of a given Class/Enumeration 8 | * 9 | * @param classIRI - IRI of a Class/Enumeration 10 | * @param graph - the graph 11 | * @returns Array of IRI of all implicit and explicit superClasses 12 | */ 13 | export function inferSuperClasses(classIRI: string, graph: Graph): string[] { 14 | let result = []; 15 | const classObj = graph.classes[classIRI] || graph.enumerations[classIRI]; 16 | if (classObj) { 17 | result.push(...classObj[NS.rdfs.subClassOf]); 18 | let addition = cloneJson(result); // make a copy 19 | do { 20 | let newAddition = []; 21 | for (const curAdd of addition) { 22 | const parentClassObj = 23 | graph.classes[curAdd] || graph.enumerations[curAdd]; 24 | if (parentClassObj) { 25 | newAddition.push(...parentClassObj[NS.rdfs.subClassOf]); 26 | } 27 | } 28 | newAddition = uniquifyArray(newAddition); 29 | addition = cloneJson(newAddition); 30 | result.push(...newAddition); 31 | } while (addition.length !== 0); 32 | result = uniquifyArray(result); 33 | } 34 | return result; 35 | } 36 | -------------------------------------------------------------------------------- /src/utilities/reasoning/inferSuperProperties.ts: -------------------------------------------------------------------------------- 1 | import { Graph } from "../../classes/Graph"; 2 | import { NS } from "../../data/namespaces"; 3 | import { cloneJson } from "../general/cloneJson"; 4 | import { uniquifyArray } from "../general/uniquifyArray"; 5 | 6 | /** @ignore 7 | * Infers all implicit and explicit superProperties of a given Property 8 | * 9 | * @param {string} propertyIRI - IRI of a Property 10 | * @param graph - the graph 11 | * @returns {string[]} Array of IRI of all implicit and explicit superProperties 12 | */ 13 | export function inferSuperProperties( 14 | propertyIRI: string, 15 | graph: Graph 16 | ): string[] { 17 | let result = []; 18 | const propertyObj = graph.properties[propertyIRI]; 19 | if (propertyObj) { 20 | result.push(...propertyObj[NS.rdfs.subPropertyOf]); 21 | let addition = cloneJson(result); // make a copy 22 | do { 23 | let newAddition = []; 24 | for (const curAdd of addition) { 25 | const parentPropertyObj = graph.properties[curAdd]; 26 | if (parentPropertyObj) { 27 | newAddition.push(...parentPropertyObj[NS.rdfs.subPropertyOf]); 28 | } 29 | } 30 | newAddition = uniquifyArray(newAddition); 31 | addition = cloneJson(newAddition); 32 | result.push(...newAddition); 33 | } while (addition.length !== 0); 34 | result = uniquifyArray(result); 35 | } 36 | return result; 37 | } 38 | -------------------------------------------------------------------------------- /src/utilities/graph/curateRelationshipTermArray.ts: -------------------------------------------------------------------------------- 1 | import { VocabularyNode, VocabularyNodeKey } from "../../types/types"; 2 | import { TermTypeIRIValue } from "../../data/namespaces"; 3 | import { isString } from "../general/isString"; 4 | import { isArray } from "../general/isArray"; 5 | 6 | /** @ignore 7 | * curates the value for a given relationship term in a given vocabulary node that should have an array as value 8 | * 9 | * @param vocabNode - the input vocabulary node 10 | * @param term - the term in question 11 | * @param initDefaultIf - the node type IRI that triggers a default initialization, e.g. rdfs:Class 12 | */ 13 | export function curateRelationshipTermArray( 14 | vocabNode: VocabularyNode, 15 | term: VocabularyNodeKey, 16 | initDefaultIf: TermTypeIRIValue 17 | ): void { 18 | // the relationships should always be an array, even for 1 and 0 (if the @type of the vocabulary node matches) values 19 | if (isString(vocabNode[term])) { 20 | vocabNode[term] = [vocabNode[term]]; 21 | } else if ( 22 | vocabNode[term] === undefined && 23 | vocabNode["@type"] === initDefaultIf 24 | ) { 25 | // initialize an empty array 26 | vocabNode[term] = []; 27 | } 28 | // remove terms that are defined as subclasses of themselves (see vocabulary-animal-altered-2.json for details) 29 | if (isArray(vocabNode[term])) { 30 | vocabNode[term] = vocabNode[term].filter((iri) => iri !== vocabNode["@id"]); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/utilities/graph/checkIfNamespaceFromListIsUsed.ts: -------------------------------------------------------------------------------- 1 | import { isObject } from "../general/isObject"; 2 | import { isString } from "../general/isString"; 3 | 4 | /** @ignore 5 | * Checks if the value includes an absolute IRI that is present in the given namespaceArray. If so, that match is added to the given result Set. 6 | * 7 | * @param value - the value to check, is expected to be either an array, an object, or a string. 8 | * @param namespaceArray - an array of IRIs to search for 9 | * @param result - a Set to save the found matches 10 | */ 11 | export function checkIfNamespaceFromListIsUsed( 12 | value: string | object | (string | object)[], 13 | namespaceArray: string[], 14 | result: Set 15 | ): void { 16 | if (Array.isArray(value)) { 17 | value.forEach(function (val) { 18 | checkIfNamespaceFromListIsUsed(val, namespaceArray, result); 19 | }); 20 | } else { 21 | let toCheck: string; 22 | // todo this could be refactored? what if toCheck is either a string or an @id object? use types 23 | if (isObject(value) && isString(value["@id"])) { 24 | toCheck = value["@id"]; 25 | } else { 26 | // } else if (isString(value)) { 27 | toCheck = value as string; 28 | } 29 | if (isString(toCheck) && toCheck.startsWith("http")) { 30 | const match = namespaceArray.find((el) => toCheck.startsWith(el)); 31 | if (match && !result.has(match)) { 32 | result.add(match); 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/utilities/graph/generateContext.test.ts: -------------------------------------------------------------------------------- 1 | import { generateContext } from "../../../src/utilities/graph/generateContext"; 2 | import CONTEXT_1 from "../../resources/data/context/test-context.json" 3 | import CONTEXT_2 from "../../resources/data/context/test-context-2.json" 4 | 5 | describe("generateContext()", () => { 6 | test("simple", async () => { 7 | const newContext = generateContext(CONTEXT_1, CONTEXT_1); 8 | expect(newContext).toEqual(CONTEXT_1); 9 | const newContext2 = generateContext(CONTEXT_1, CONTEXT_2); 10 | expect(newContext2).not.toEqual(CONTEXT_1); 11 | }); 12 | test("advanced", async () => { 13 | const contextA = { 14 | schema: "https://schema.org/", 15 | }; 16 | const contextB = { 17 | schema2: "https://schema.org/", 18 | }; 19 | const contextC = { 20 | schema: "http://schema.org/", 21 | }; 22 | const contextD = { 23 | schema2: "http://schema.org/", 24 | }; 25 | expect(generateContext(contextA, contextA)).toEqual(contextA); 26 | expect(generateContext(contextA, contextB)).toEqual(contextA); 27 | expect(generateContext(contextB, contextA)).toEqual(contextB); 28 | expect(generateContext(contextA, contextC)).toEqual({ 29 | schema: "https://schema.org/", 30 | schema1: "http://schema.org/", 31 | }); 32 | expect(generateContext(contextA, contextD)).toEqual({ 33 | schema: "https://schema.org/", 34 | schema2: "http://schema.org/", 35 | }); 36 | }); 37 | }) 38 | -------------------------------------------------------------------------------- /docu/examples/example-browser-3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SDO Adapter Test 6 | 7 | 8 |
9 |
10 |         Loading of multiple vocabularies...
11 |     
12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | 42 | 43 | -------------------------------------------------------------------------------- /docu/examples/checking-subenumerations.js: -------------------------------------------------------------------------------- 1 | const { SOA } = require("../../lib/index"); // run the npm-script "buildTs" to generate js files for this example 2 | 3 | main(); 4 | 5 | // analyze the hierarchy of enumerations 6 | async function main() { 7 | const sdoVersion = "28.1"; 8 | const commit = "9d7406b660197aa9559090bcaf516ad80fdce7a0"; 9 | const mySoa = await SOA.create({ schemaVersion: sdoVersion, commit: commit }); 10 | const enumerationList = mySoa.getListOfEnumerations(); 11 | console.log("#Enumerations " + sdoVersion + ": " + enumerationList.length); 12 | 13 | // enumerations with sub-enumerations 14 | for (const enumeration of enumerationList) { 15 | try { 16 | const enumInstance = mySoa.getEnumeration(enumeration); 17 | const enumMembersImpl = enumInstance.getEnumerationMembers({implicit:true}) 18 | const enumMembersExpl = enumInstance.getEnumerationMembers({implicit:false}) 19 | const subEnumerationsDirect = enumInstance.getSubClasses({implicit:false}); 20 | if(enumMembersExpl.length > 0 && subEnumerationsDirect.length > 0 ) { 21 | console.log("Enumeration " + enumeration + " has following implicit members: ", enumMembersImpl.filter(el => !enumMembersExpl.includes(el))); 22 | console.log("Enumeration " + enumeration + " has following explicit members: ", enumMembersExpl); 23 | } 24 | // const subEnumerationsDirect = enumInstance.getSubClasses({implicit:false}); 25 | // if (subEnumerationsDirect.length > 0) { 26 | // console.log("Enumeration " + enumeration + " has direct sub-classes:", subEnumerationsDirect); 27 | // } 28 | } catch (e) { 29 | console.error(e); 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/utilities/graph/checkVocabNodeType.ts: -------------------------------------------------------------------------------- 1 | import { TermTypeIRI, TermTypeLabel, TermTypeLabelValue } from "../../data/namespaces"; 2 | import { isString } from "../general/isString"; 3 | import { isArray } from "../general/isArray"; 4 | 5 | // returns the identified type for the given vocabulary node (expected in the input vocabulary) 6 | // classes ("@type" = "rdfs:Class") 7 | // properties ("@type" = "rdf:Property") 8 | // dataTypes ("@type" = "rdfs:Class" + "schema:DataType") 9 | // enumerations ("@type" = "rdfs:Class", has "schema:Enumeration" as implicit superclass) 10 | // enumerationMembers ("@type" = @id(s) of enumeration(s)) 11 | export function checkVocabNodeType(types: unknown): TermTypeLabelValue { 12 | if (isString(types)) { 13 | if (types === TermTypeIRI.class) { 14 | return TermTypeLabel.class; 15 | } 16 | if (types === TermTypeIRI.property) { 17 | return TermTypeLabel.property; 18 | } 19 | // @type is not something expected -> assume enumerationMember 20 | return TermTypeLabel.enumerationMember; 21 | } else if (isArray(types)) { 22 | // @type is not a string -> datatype or enumeration member 23 | // [ 24 | // "rdfs:Class", 25 | // "schema:DataType" 26 | // ] 27 | // [ 28 | // "schema:MedicalImagingTechnique", 29 | // "schema:MedicalSpecialty" 30 | // ] 31 | if (types.length === 2 && types.includes(TermTypeIRI.class) && types.includes(TermTypeIRI.dataType)) { 32 | return TermTypeLabel.dataType; 33 | } else { 34 | // @type is not something expected -> assume enumerationMember 35 | return TermTypeLabel.enumerationMember; 36 | } 37 | } 38 | throw new Error("Unexpected @type format: " + types); 39 | } 40 | -------------------------------------------------------------------------------- /src/utilities/general/toCompactIRI.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "../../types/types"; 2 | import { isString } from "./isString"; 3 | import { switchIRIProtocol } from "./switchIRIProtocol"; 4 | 5 | /** @ignore 6 | * Returns the compact IRI (e.g. schema:Hotel) from a given absolute IRI (https://schema.org/Hotel) and a corresponding context. If the context does not contain the used namespace, then an error is thrown 7 | * 8 | * @param absoluteIRI - the absolute IRI to transform 9 | * @param context - the context object holding key-value pairs that represent indicator-namespace pairs 10 | * @param [equateVocabularyProtocols = false] - treats namespaces as equal even if their protocols (http/https) are different, it defaults to false. 11 | * @returns the compact IRI (null, if given context does not contain the used namespace) 12 | */ 13 | export function toCompactIRI( 14 | absoluteIRI: string, 15 | context: Context, 16 | equateVocabularyProtocols = false 17 | ): string { 18 | for (const contextTerm of Object.keys(context)) { 19 | const vocabIRI = context[contextTerm]; 20 | if (isString(vocabIRI) && absoluteIRI.startsWith(vocabIRI as string)) { 21 | return ( 22 | contextTerm + ":" + absoluteIRI.substring((vocabIRI as string).length) 23 | ); 24 | } 25 | if (equateVocabularyProtocols && isString(vocabIRI)) { 26 | const protocolSwitchedIRI = switchIRIProtocol(vocabIRI as string); 27 | if (absoluteIRI.startsWith(protocolSwitchedIRI)) { 28 | return ( 29 | contextTerm + ":" + absoluteIRI.substring(protocolSwitchedIRI.length) 30 | ); 31 | } 32 | } 33 | } 34 | throw new Error( 35 | "Trying to get a compact IRI for a term with no entry in the Context" 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /docu/examples/example-node-2.js: -------------------------------------------------------------------------------- 1 | const { SOA } = require("../../lib/index"); // run the npm-script "buildTs" to generate js files for this example 2 | const SCHEMA_URL = "https://raw.githubusercontent.com/semantifyit/schemaorg/main/data/releases/13.0/schemaorg-all-https.jsonld"; 3 | const VOC_OBJ_ZOO = require("../../tests/resources/data/vocabularies/vocabulary-animal.json"); 4 | const VOC_OBJ_ZOO_DVS = require("../../tests/resources/data/vocabularies/vocabulary-animal-dvs.json"); 5 | main(); 6 | 7 | /** 8 | * example usage of the library within node.js 9 | */ 10 | async function main() { 11 | const mySA = await SOA.create({ vocabularies: [SCHEMA_URL, VOC_OBJ_ZOO] }); 12 | const mySDOUrl = await mySA.constructURLSchemaVocabulary("latest"); 13 | console.log("The latest version is " + mySDOUrl); 14 | let testClass = mySA.getClass("https://schema.org/Person"); 15 | console.log(mySA.getVocabularies()); 16 | console.log("getIRI() " + testClass.getIRI()); 17 | console.log(JSON.stringify(testClass.toJSON({ implicit: false }), null, 2)); 18 | 19 | const extVocClass = mySA.getClass("ex:Tiger"); 20 | console.log(JSON.stringify(extVocClass.toJSON(), null, 2)); 21 | const testEnumerationMember = mySA.getEnumerationMember("schema:Radiography"); 22 | console.log( 23 | JSON.stringify(testEnumerationMember.toJSON({ implicit: false }), null, 2) 24 | ); 25 | 26 | const mySA2 = await SOA.create({ vocabularies: [SCHEMA_URL, VOC_OBJ_ZOO_DVS] }); 27 | console.log(mySA2.getVocabularies()); 28 | console.log(mySA2.getListOfClasses({ 29 | filter: { 30 | fromVocabulary: "ex" 31 | } 32 | })); 33 | console.log(mySA2.getListOfClasses({ 34 | filter: { 35 | fromVocabulary: "ex" 36 | }, 37 | outputFormat:"Absolute" 38 | })); 39 | } 40 | -------------------------------------------------------------------------------- /src/dist.ts: -------------------------------------------------------------------------------- 1 | import { SDOAdapter } from "./classes/SDOAdapter"; 2 | import { fetchSchemaVersions, getLatestSchemaVersion, constructURLSchemaVocabulary } from "./classes/Infrastructure"; 3 | import { ParamObjCreateSdoAdapter } from "./types/ParamObjCreateSdoAdapter.type"; 4 | import { Vocabulary } from "./types/types"; 5 | 6 | /** 7 | * Creates a new {@link SDOAdapter | SDO Adapter} instance. The optional {@link ParamObjCreateSdoAdapter | parameter object} can help you to pass settings to the SDO Adapter. Have a look to understand the possible settings and default values. The minimal setting you would like to pass is the schema.org vocabulary version you want to use. 8 | * 9 | * @example 10 | * 11 | * ```JS 12 | * const { SOA } = require("schema-org-adapter"); 13 | * // create a new SDOAdapter instance with the latest version of schema.org 14 | * const mySdoAdapter = await SOA.create({schemaVersion: "latest"}); 15 | * ``` 16 | * 17 | * @param paramObj An optional parameter object that describes initialization settings for the newly created SDO Adapter instance. 18 | */ 19 | async function create(paramObj?: ParamObjCreateSdoAdapter) { 20 | const newInstance = new SDOAdapter(paramObj); 21 | const vocabulariesToAdd: (Vocabulary | string)[] = []; 22 | if (paramObj && paramObj.schemaVersion) { 23 | vocabulariesToAdd.push(await constructURLSchemaVocabulary(paramObj.schemaVersion, paramObj.schemaHttps, paramObj.commit)); 24 | } 25 | if (paramObj && paramObj.vocabularies) { 26 | vocabulariesToAdd.push(...paramObj.vocabularies); 27 | } 28 | await newInstance.addVocabularies(vocabulariesToAdd); 29 | return newInstance; 30 | } 31 | 32 | // export as separate functions for bundled dist 33 | export { create, fetchSchemaVersions, getLatestSchemaVersion, constructURLSchemaVocabulary }; 34 | -------------------------------------------------------------------------------- /src/utilities/reasoning/inferRangeOf.ts: -------------------------------------------------------------------------------- 1 | import { Graph } from "../../classes/Graph"; 2 | import { NS } from "../../data/namespaces"; 3 | import { uniquifyArray } from "../general/uniquifyArray"; 4 | import { inferSuperClasses } from "./inferSuperClasses"; 5 | import { inferSuperDataTypes } from "./inferSuperDataTypes"; 6 | 7 | /** @ignore 8 | * Infers all implicit and explicit properties that can have the given Class/Enumeration/DataType as range 9 | * 10 | * @param {string} rangeIRI - IRI of the range (Class/Enumeration/DataType) 11 | * @param graph - the graph 12 | * @returns {string[]} Array of IRI of all implicit and explicit properties that can use the given range 13 | */ 14 | export function inferRangeOf(rangeIRI: string, graph: Graph): string[] { 15 | const classObj = graph.classes[rangeIRI] || graph.enumerations[rangeIRI]; 16 | const result = []; 17 | if (classObj) { 18 | result.push(...classObj[NS.soa.isRangeOf]); 19 | const superClasses = inferSuperClasses(rangeIRI, graph); 20 | for (const superClass of superClasses) { 21 | const superClassObj = 22 | graph.classes[superClass] || graph.enumerations[superClass]; 23 | if (superClassObj) { 24 | result.push(...superClassObj[NS.soa.isRangeOf]); 25 | } 26 | } 27 | } else { 28 | const dataTypeObj = graph.dataTypes[rangeIRI]; 29 | if (dataTypeObj) { 30 | result.push(...dataTypeObj[NS.soa.isRangeOf]); 31 | const superDataTypes = inferSuperDataTypes(rangeIRI, graph); 32 | for (const superDataType of superDataTypes) { 33 | const superDataTypeObj = graph.dataTypes[superDataType]; 34 | if (superDataTypeObj) { 35 | result.push(...superDataTypeObj[NS.soa.isRangeOf]); 36 | } 37 | } 38 | } 39 | } 40 | return uniquifyArray(result); 41 | } 42 | -------------------------------------------------------------------------------- /src/utilities/graph/discoverUsedSchemaOrgProtocol.ts: -------------------------------------------------------------------------------- 1 | import { Vocabulary } from "../../types/types"; 2 | import { isObject } from "../general/isObject"; 3 | import { isString } from "../general/isString"; 4 | 5 | /** @ignore 6 | * Returns the protocol version used for schema.org in the given vocabulary. Returns "https" as the default 7 | * 8 | * @param vocabulary - the vocabulary in question 9 | * @returns the corresponding protocol version, either "http" or "https" 10 | */ 11 | export function discoverUsedSchemaOrgProtocol( 12 | vocabulary: Vocabulary | object 13 | ): string { 14 | const httpsIRI = "https://schema.org/"; 15 | const httpIRI = "http://schema.org/"; 16 | // 1. check if namespace is used in @context 17 | if ((vocabulary as Vocabulary)["@context"]) { 18 | for (const contextEntry of Object.values( 19 | (vocabulary as Vocabulary)["@context"] 20 | )) { 21 | if (isObject(contextEntry) && contextEntry["@vocab"]) { 22 | if (contextEntry["@vocab"] === httpsIRI) { 23 | return "https"; 24 | } else if (contextEntry["@vocab"] === httpIRI) { 25 | return "http"; 26 | } 27 | } else if (isString(contextEntry)) { 28 | if (contextEntry === httpsIRI) { 29 | return "https"; 30 | } else if (contextEntry === httpIRI) { 31 | return "http"; 32 | } 33 | } 34 | } 35 | } 36 | // 2. easiest way -> make a string and count occurrences for each protocol version 37 | const stringifiedVocab = JSON.stringify(vocabulary); 38 | const amountHttps = stringifiedVocab.split(httpsIRI).length - 1; 39 | const amountHttp = stringifiedVocab.split(httpIRI).length - 1; 40 | if (amountHttps > amountHttp) { 41 | return "https"; 42 | } else if (amountHttp > amountHttps) { 43 | return "http"; 44 | } else { 45 | return httpsIRI; // default case 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tests/resources/data/context/test-context.json: -------------------------------------------------------------------------------- 1 | { 2 | "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", 3 | "rdfs": "http://www.w3.org/2000/01/rdf-schema#", 4 | "xsd": "http://www.w3.org/2001/XMLSchema#", 5 | "dcterms": "http://purl.org/dc/terms/", 6 | "schema": "http://schema.org/", 7 | "ex": "https://example-vocab.ex/", 8 | "soa": "http://schema-org-adapter.at/vocabTerms/", 9 | "soa:superClassOf": { 10 | "@id": "soa:superClassOf", 11 | "@type": "@id" 12 | }, 13 | "soa:superPropertyOf": { 14 | "@id": "soa:superPropertyOf", 15 | "@type": "@id" 16 | }, 17 | "soa:hasProperty": { 18 | "@id": "soa:hasProperty", 19 | "@type": "@id" 20 | }, 21 | "soa:isRangeOf": { 22 | "@id": "soa:isRangeOf", 23 | "@type": "@id" 24 | }, 25 | "soa:hasEnumerationMember": { 26 | "@id": "soa:hasEnumerationMember", 27 | "@type": "@id" 28 | }, 29 | "soa:enumerationDomainIncludes": { 30 | "@id": "soa:enumerationDomainIncludes", 31 | "@type": "@id" 32 | }, 33 | "rdfs:subClassOf": { 34 | "@id": "rdfs:subClassOf", 35 | "@type": "@id" 36 | }, 37 | "rdfs:subPropertyOf": { 38 | "@id": "rdfs:subPropertyOf", 39 | "@type": "@id" 40 | }, 41 | "schema:isPartOf": { 42 | "@id": "schema:isPartOf", 43 | "@type": "@id" 44 | }, 45 | "schema:domainIncludes": { 46 | "@id": "schema:domainIncludes", 47 | "@type": "@id" 48 | }, 49 | "schema:rangeIncludes": { 50 | "@id": "schema:rangeIncludes", 51 | "@type": "@id" 52 | }, 53 | "schema:supersededBy": { 54 | "@id": "schema:supersededBy", 55 | "@type": "@id" 56 | }, 57 | "schema:inverseOf": { 58 | "@id": "schema:inverseOf", 59 | "@type": "@id" 60 | }, 61 | "dcterms:source": { 62 | "@id": "dcterms:source", 63 | "@type": "@id" 64 | }, 65 | "schema:source": { 66 | "@id": "schema:source", 67 | "@type": "@id" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /tests/resources/data/context/test-context-2.json: -------------------------------------------------------------------------------- 1 | { 2 | "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", 3 | "rdfs": "http://www.w3.org/2000/01/rdf-schema#", 4 | "xsd": "http://www.w3.org/2001/XMLSchema#", 5 | "dcterms": "http://purl.org/dc/terms/2", 6 | "schema2": "http://schema.org/", 7 | "ex": "https://example-vocab.ex/", 8 | "soa": "http://schema-org-adapter.at/vocabTerms/", 9 | "soa:superClassOf": { 10 | "@id": "soa:superClassOf", 11 | "@type": "@id" 12 | }, 13 | "soa:superPropertyOf": { 14 | "@id": "soa:superPropertyOf", 15 | "@type": "@id" 16 | }, 17 | "soa:hasProperty": { 18 | "@id": "soa:hasProperty", 19 | "@type": "@id" 20 | }, 21 | "soa:isRangeOf": { 22 | "@id": "soa:isRangeOf", 23 | "@type": "@id" 24 | }, 25 | "soa:hasEnumerationMember": { 26 | "@id": "soa:hasEnumerationMember", 27 | "@type": "@id" 28 | }, 29 | "soa:enumerationDomainIncludes": { 30 | "@id": "soa:enumerationDomainIncludes", 31 | "@type": "@id" 32 | }, 33 | "rdfs:subClassOf": { 34 | "@id": "rdfs:subClassOf", 35 | "@type": "@id" 36 | }, 37 | "rdfs:subPropertyOf": { 38 | "@id": "rdfs:subPropertyOf", 39 | "@type": "@id" 40 | }, 41 | "schema:isPartOf": { 42 | "@id": "schema:isPartOf", 43 | "@type": "@id" 44 | }, 45 | "schema:domainIncludes": { 46 | "@id": "schema:domainIncludes", 47 | "@type": "@id" 48 | }, 49 | "schema:rangeIncludes": { 50 | "@id": "schema:rangeIncludes", 51 | "@type": "@id" 52 | }, 53 | "schema:supersededBy": { 54 | "@id": "schema:supersededBy", 55 | "@type": "@id" 56 | }, 57 | "schema:inverseOf": { 58 | "@id": "schema:inverseOf", 59 | "@type": "@id" 60 | }, 61 | "dcterms:source": { 62 | "@id": "dcterms:source", 63 | "@type": "@id" 64 | }, 65 | "schema:source": { 66 | "@id": "schema:source", 67 | "@type": "@id" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/utilities/graph/getStandardContext.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "../../types/types"; 2 | import { NS } from "../../data/namespaces"; 3 | 4 | /** @ignore */ 5 | export function getStandardContext(): Context { 6 | // Simply speaking, a context is used to map terms to IRIs. Terms are case-sensitive and any valid string that is not a reserved JSON-LD keyword can be used as a term. 7 | // soa:superClassOf is an inverse of rdfs:subClassOf that should help us 8 | // soa:superPropertyOf is an inverse of rdfs:subPropertyOf that should help us 9 | // soa:hasProperty is an inverse of schema:domainIncludes 10 | // soa:isRangeOf is an inverse of schema:rangeIncludes 11 | // soa:hasEnumerationMember is used for enumerations to list all its enumeration members (their @type includes the @id of the enumeration) 12 | // soa:enumerationDomainIncludes is an inverse of soa:hasEnumerationMember 13 | // soa:EnumerationMember is introduced as meta type for the members of a schema:Enumeration 14 | const standardContext: Context = { 15 | rdf: NS.rdf._url, 16 | rdfs: NS.rdfs._url, 17 | xsd: NS.xsd._url, 18 | dcterms: NS.dcterms._url, 19 | // schema: 'http://schema.org/', this entry will be generated the first time a vocabulary is added to the graph 20 | soa: NS.soa._url, 21 | ds: NS.ds._url, 22 | }; 23 | const idEntries = [ 24 | NS.soa.superClassOf, 25 | NS.soa.superPropertyOf, 26 | NS.soa.hasProperty, 27 | NS.soa.isRangeOf, 28 | NS.soa.hasEnumerationMember, 29 | NS.soa.enumerationDomainIncludes, 30 | NS.rdfs.subClassOf, 31 | NS.rdfs.subPropertyOf, 32 | NS.schema.isPartOf, 33 | NS.schema.domainIncludes, 34 | NS.schema.rangeIncludes, 35 | NS.schema.supersededBy, 36 | NS.schema.inverseOf, 37 | NS.schema.source, 38 | NS.dcterms.source, 39 | ]; 40 | idEntries.map((el) => { 41 | standardContext[el] = { 42 | "@id": el, 43 | "@type": "@id", 44 | }; 45 | }); 46 | return standardContext; 47 | } 48 | -------------------------------------------------------------------------------- /tests/utilities/infrastructure/sortReleaseEntriesByDate.test.ts: -------------------------------------------------------------------------------- 1 | import { debugFunc } from "../../resources/utilities/testUtilities"; 2 | import { isString } from "../../../src/utilities/general/isString"; 3 | import { isArray } from "../../../src/utilities/general/isArray"; 4 | import { sortReleaseEntriesByDate } from "../../../src/utilities/infrastructure/sortReleaseEntriesByDate"; 5 | import VERSIONS_24 from "../../resources/data/versions/versions-24.0.json"; 6 | 7 | function checkReleaseEntriesStructure( 8 | releaseEntries: [string, string][], 9 | expectedLength: number 10 | ) { 11 | debugFunc(releaseEntries); 12 | expect(isArray(releaseEntries)).toBe(true); 13 | expect(releaseEntries).toHaveLength(expectedLength); 14 | for (const tuple of releaseEntries) { 15 | expect(isArray(tuple)).toBe(true); 16 | expect(isString(tuple[0])).toBe(true); 17 | expect(isString(tuple[1])).toBe(true); 18 | } 19 | } 20 | 21 | describe("sortReleaseEntriesByDate()", () => { 22 | test("original case", () => { 23 | const releaseEntries = sortReleaseEntriesByDate(VERSIONS_24.releaseLog); 24 | checkReleaseEntriesStructure(releaseEntries, 39); 25 | expect(releaseEntries[0][0]).toBe("24.0"); 26 | expect(releaseEntries[0][1]).toBe("2023-12-15"); 27 | expect(releaseEntries[releaseEntries.length - 1][0]).toBe("2.0"); 28 | expect(releaseEntries[releaseEntries.length - 1][1]).toBe("2015-05-13"); 29 | }); 30 | test("fantasy case", () => { 31 | const releaseEntries = sortReleaseEntriesByDate({ 32 | "1.1": "2005-05-13", 33 | "1.0": "2002-04-20", 34 | "13.37": "2042-04-20", 35 | ...VERSIONS_24.releaseLog, 36 | }); 37 | checkReleaseEntriesStructure(releaseEntries, 42); 38 | expect(releaseEntries[0][0]).toBe("13.37"); 39 | expect(releaseEntries[0][1]).toBe("2042-04-20"); 40 | expect(releaseEntries[releaseEntries.length - 1][0]).toBe("1.0"); 41 | expect(releaseEntries[releaseEntries.length - 1][1]).toBe("2002-04-20"); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Node template 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Dependency directories 17 | node_modules/ 18 | 19 | # Optional npm cache directory 20 | .npm 21 | 22 | # Optional eslint cache 23 | .eslintcache 24 | 25 | # Optional REPL history 26 | .node_repl_history 27 | 28 | # Output of 'npm pack' 29 | *.tgz 30 | 31 | # Yarn Integrity file 32 | .yarn-integrity 33 | 34 | # dotenv environment variables file 35 | .env 36 | 37 | # next.js build output 38 | .next 39 | ### JetBrains template 40 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 41 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 42 | 43 | # User-specific stuff: 44 | .idea/* 45 | 46 | # Sensitive or high-churn files: 47 | .idea/**/dataSources/ 48 | .idea/**/dataSources.ids 49 | .idea/**/dataSources.xml 50 | .idea/**/dataSources.local.xml 51 | .idea/**/sqlDataSources.xml 52 | .idea/**/dynamic.xml 53 | .idea/**/uiDesigner.xml 54 | 55 | # Gradle: 56 | .idea/**/gradle.xml 57 | .idea/**/libraries 58 | 59 | # CMake 60 | cmake-build-debug/ 61 | cmake-build-release/ 62 | 63 | # Mongo Explorer plugin: 64 | .idea/**/mongoSettings.xml 65 | 66 | ## File-based project format: 67 | *.iws 68 | 69 | ## Plugin-specific files: 70 | 71 | # mpeltonen/sbt-idea plugin 72 | .idea_modules/ 73 | 74 | # JIRA plugin 75 | atlassian-ide-plugin.xml 76 | 77 | # Cursive Clojure plugin 78 | .idea/replstate.xml 79 | 80 | # Crashlytics plugin (for Android Studio and IntelliJ) 81 | com_crashlytics_export_strings.xml 82 | crashlytics.properties 83 | crashlytics-build.properties 84 | fabric.properties 85 | 86 | # Generated js files should not be on github (but on npm) - dist files should be in both 87 | # Include eslint config for lib (checks if all functions work on es-2018) 88 | lib/ 89 | 90 | # personal todo list 91 | todo.txt 92 | -------------------------------------------------------------------------------- /docu/examples/example-browser.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SDO Adapter Test 6 | 7 | 8 |
9 |
10 |         This Div should render the test data generated by the Library soon
11 |     
12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | 42 | 43 | -------------------------------------------------------------------------------- /src/utilities/infrastructure/RetrievalMemory.ts: -------------------------------------------------------------------------------- 1 | import { CacheLiteral, CacheMap } from "../../types/types"; 2 | import { cloneJson } from "../general/cloneJson"; 3 | 4 | /** @ignore 5 | * RetrievalMemory is a singleton class that caches retrieved data 6 | */ 7 | export class RetrievalMemory { 8 | private static instance: RetrievalMemory; 9 | private cache: CacheMap; 10 | 11 | // eslint-disable-next-line @typescript-eslint/no-empty-function 12 | private constructor() { 13 | this.cache = new Map(); 14 | } 15 | 16 | /** 17 | * get the only instance of this class (singleton) 18 | * 19 | * @returns the only instance of this class 20 | */ 21 | public static getInstance(): RetrievalMemory { 22 | if (!RetrievalMemory.instance) { 23 | RetrievalMemory.instance = new RetrievalMemory(); 24 | } 25 | return RetrievalMemory.instance; 26 | } 27 | 28 | /** 29 | * Sets data for a given entry name and commit (default is standard) 30 | * @param dataId - the id/name where to set the data 31 | * @param data - the data to set 32 | * @param commit - the commit to set the entry, defaults to "standard" 33 | */ 34 | public setData(dataId: string, data: CacheLiteral, commit = "standard") { 35 | let entry = this.cache.get(commit); 36 | if (!entry) { 37 | entry = {}; 38 | this.cache.set(commit, entry); 39 | } 40 | entry[dataId] = cloneJson(data); 41 | } 42 | 43 | /** 44 | * Sets data for a given entry name and commit (default is standard) 45 | * @param dataId - the entry name where to set the data 46 | * @param commit - the commit to set the entry, defaults to "standard" 47 | * @returns the data found 48 | */ 49 | public getData(dataId: string, commit = "standard") { 50 | const entry = this.cache.get(commit); 51 | if (entry) { 52 | return cloneJson(entry[dataId]); 53 | } 54 | return undefined; 55 | } 56 | 57 | /** 58 | * Resets the cache Map -> all cached data is deleted 59 | */ 60 | public deleteCache() { 61 | this.cache = new Map(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /docu/examples/example-browser-2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SDO Adapter Test 6 | 7 | 8 |
9 |
10 |         This Div should render the test data generated by the Library soon
11 |     
12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | 46 | 47 | -------------------------------------------------------------------------------- /src/utilities/graph/preProcessVocab.ts: -------------------------------------------------------------------------------- 1 | import { Context, Vocabulary } from "../../types/types"; 2 | import { cloneJson } from "../general/cloneJson"; 3 | import jsonld from "jsonld"; 4 | 5 | /** @ignore 6 | * Transforms a given vocabulary to a wished format (including a given JSON-LD context) 7 | * 8 | * @param vocab - the vocabulary to process 9 | * @param newContext - the wished JSON-LD context that the vocabulary should have 10 | * @returns the transformed vocabulary 11 | */ 12 | export async function preProcessVocab( 13 | vocab: Vocabulary, 14 | newContext: Context 15 | ): Promise { 16 | // recursively put all nodes from inner @graphs to the outermost @graph (is the case for older schema.jsonld versions) 17 | let foundInnerGraph = false; 18 | do { 19 | const newGraph = []; 20 | foundInnerGraph = false; 21 | for (let i = 0; i < vocab["@graph"].length; i++) { 22 | if (vocab["@graph"][i]["@graph"] !== undefined) { 23 | newGraph.push(...cloneJson(vocab["@graph"][i]["@graph"])); // copy all elements of the inner @graph into the outer @graph 24 | foundInnerGraph = true; 25 | } else { 26 | newGraph.push(cloneJson(vocab["@graph"][i])); // copy this element to the outer @graph 27 | } 28 | } 29 | vocab["@graph"] = cloneJson(newGraph); 30 | } while (foundInnerGraph); 31 | 32 | // compact to apply the new context (which is supposed to have been merged before with the old context through the function generateContext()) 33 | // option "graph": true not feasible here, because then vocabs with "@id" result in inner @graphs again 34 | // solution: edge case handling (see below) 35 | const compactedVocab = await jsonld.compact(vocab, newContext); 36 | 37 | // edge case: @graph had only one node, so values of @graph are in outermost layer 38 | if (compactedVocab["@graph"] === undefined) { 39 | delete compactedVocab["@context"]; 40 | return { 41 | "@context": newContext, 42 | "@graph": [compactedVocab], 43 | }; 44 | } else { 45 | return compactedVocab as Vocabulary; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig, globalIgnores } from "eslint/config"; 2 | import jest from "eslint-plugin-jest"; 3 | import prettier from "eslint-plugin-prettier"; 4 | import typescriptEslint from "@typescript-eslint/eslint-plugin"; 5 | import globals from "globals"; 6 | import tsParser from "@typescript-eslint/parser"; 7 | import path from "node:path"; 8 | import { fileURLToPath } from "node:url"; 9 | import js from "@eslint/js"; 10 | import { FlatCompat } from "@eslint/eslintrc"; 11 | 12 | const __filename = fileURLToPath(import.meta.url); 13 | const __dirname = path.dirname(__filename); 14 | const compat = new FlatCompat({ 15 | baseDirectory: __dirname, 16 | recommendedConfig: js.configs.recommended, 17 | allConfig: js.configs.all 18 | }); 19 | 20 | export default defineConfig([ 21 | globalIgnores([ 22 | "lib", 23 | "dist", 24 | "docu", 25 | "docs", 26 | "node_modules", 27 | "tests/resources" 28 | ]), { 29 | extends: compat.extends( 30 | "plugin:@typescript-eslint/recommended", 31 | "prettier", 32 | "plugin:jest/recommended" 33 | ), 34 | plugins: { 35 | jest, 36 | prettier, 37 | "@typescript-eslint": typescriptEslint 38 | }, 39 | languageOptions: { 40 | globals: { 41 | ...globals.node, 42 | ...globals.browser, 43 | ...globals.jest, 44 | ...jest.environments.globals.globals 45 | }, 46 | parser: tsParser, 47 | ecmaVersion: 2020, 48 | sourceType: "module", 49 | parserOptions: { 50 | project: ["src/tsconfig.json", "tests/tsconfig.json"], 51 | allowImportExportEverywhere: true 52 | } 53 | }, 54 | rules: { 55 | "spaced-comment": ["warn", "always", { 56 | block: { 57 | balanced: true, 58 | exceptions: ["-"] 59 | }, 60 | line: { 61 | exceptions: ["-"] 62 | } 63 | }], 64 | "node/no-unpublished-require": "off", 65 | "@typescript-eslint/no-unused-vars": ["error", { 66 | caughtErrors: "none" 67 | }], 68 | "@typescript-eslint/ban-ts-comment": "warn" 69 | } 70 | }]); -------------------------------------------------------------------------------- /tests/utilities/infrastructure/getFileNameForSchemaOrgVersion.test.ts: -------------------------------------------------------------------------------- 1 | import { getFileNameForSchemaOrgVersion } from "../../../src/utilities/infrastructure/getFileNameForSchemaOrgVersion"; 2 | 3 | describe("getFileNameForSchemaOrgVersion()", () => { 4 | // Checks if the function getFileNameForSchemaOrgVersion() retrieves filenames (only jsonld) for the corresponding schema.org versions as expected 5 | test("getFileNameForSchemaOrgVersion", async () => { 6 | // 2.0 - 3.0 have no jsonld -> error 7 | // 3.1 - 8.0 have all-layers.jsonld 8 | // 9.0 + have schemaorg-all-http.jsonld 9 | const expectedFileMapping = { 10 | "10.0": "schemaorg-all-https.jsonld", 11 | "9.0": "schemaorg-all-https.jsonld", 12 | "8.0": "all-layers.jsonld", 13 | 7.04: "all-layers.jsonld", 14 | 7.03: "all-layers.jsonld", 15 | 7.02: "all-layers.jsonld", 16 | 7.01: "all-layers.jsonld", 17 | "7.0": "all-layers.jsonld", 18 | "6.0": "all-layers.jsonld", 19 | "5.0": "all-layers.jsonld", 20 | "4.0": "all-layers.jsonld", 21 | 3.9: "all-layers.jsonld", 22 | 3.8: "all-layers.jsonld", 23 | 3.7: "all-layers.jsonld", 24 | 3.6: "all-layers.jsonld", 25 | 3.5: "all-layers.jsonld", 26 | 3.4: "all-layers.jsonld", 27 | 3.3: "all-layers.jsonld", 28 | 3.2: "all-layers.jsonld", 29 | 3.1: "all-layers.jsonld", 30 | "3.0": null, 31 | 2.2: null, 32 | 2.1: null, 33 | "2.0": null, 34 | }; 35 | for (const currVersion of Object.entries(expectedFileMapping)) { 36 | if (currVersion[1] === null) { 37 | // expect to fail (You must wrap the code in a function, otherwise the error will not be caught and the assertion will fail.) 38 | // eslint-disable-next-line jest/no-conditional-expect 39 | expect(() => { 40 | getFileNameForSchemaOrgVersion(currVersion[0]); 41 | }).toThrow(); 42 | } else { 43 | // eslint-disable-next-line jest/no-conditional-expect 44 | expect(getFileNameForSchemaOrgVersion(currVersion[0])).toBe( 45 | currVersion[1] 46 | ); 47 | } 48 | } 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /src/utilities/graph/curateLanguageTerm.ts: -------------------------------------------------------------------------------- 1 | import { LanguageObjectSdoAdapter, VocabularyNode } from "../../types/types"; 2 | import { isString } from "../general/isString"; 3 | import { isLanguageObjectVocab } from "../general/isLanguageObjectVocab"; 4 | import { isArray } from "../general/isArray"; 5 | 6 | /** 7 | * @ignore 8 | * curates the language-tagged value for a given term in a given vocabulary node 9 | * 10 | * @param vocabNode - the input vocabulary node 11 | * @param term - the term in question 12 | */ 13 | export function curateLanguageTerm( 14 | vocabNode: VocabularyNode, 15 | term: string 16 | ): void { 17 | // wished format: 18 | // "term": { 19 | // "en": "english text", 20 | // "de": "german text" 21 | // } 22 | if (vocabNode[term] !== undefined) { 23 | if (isString(vocabNode[term])) { 24 | // cover case for a simple string, e.g. "term": "text" 25 | // for the output simply assume the language is "en" 26 | vocabNode[term] = { 27 | en: vocabNode[term], 28 | }; 29 | } else if (isLanguageObjectVocab(vocabNode[term])) { 30 | // cover case for a language object 31 | // "term": { 32 | // "@language": "en", 33 | // "@value": "english text" 34 | // } 35 | vocabNode[term] = { 36 | [vocabNode[term]["@language"]]: vocabNode[term]["@value"], 37 | }; 38 | } else if (isArray(vocabNode[term])) { 39 | // cover case for multiple language objects in an array 40 | // "term": [{ 41 | // "@language": "en", 42 | // "@value": "translationOfWork" 43 | // }, 44 | // { 45 | // "@language": "de", 46 | // "@value": "UebersetzungsArbeit" 47 | // }] 48 | const newVal: LanguageObjectSdoAdapter = {}; 49 | vocabNode[term].map((el: unknown) => { 50 | // it is assumed that an array element in this position is a language object 51 | if (isLanguageObjectVocab(el)) { 52 | newVal[el["@language"]] = el["@value"]; 53 | } 54 | }); 55 | vocabNode[term] = newVal; 56 | } 57 | } else { 58 | // if the term is not given, then create an empty LanguageObjectSdoAdapter 59 | vocabNode[term] = {}; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /tests/sdo-adapter/direct-url.test.ts: -------------------------------------------------------------------------------- 1 | import { SDOAdapter } from "../../src/classes/SDOAdapter"; 2 | import { commit, debugFunc, debugFuncErr } from "../resources/utilities/testUtilities"; 3 | 4 | describe("SDO Adapter - Direct URL", () => { 5 | test("fetch vocabulary directly from repo", async () => { 6 | // test fetch of vocabulary directly from the schema.org repo 7 | const mySA = new SDOAdapter({ 8 | onError: debugFuncErr 9 | }); 10 | await mySA.addVocabularies([ 11 | "https://raw.githubusercontent.com/schemaorg/schemaorg/main/data/releases/20.0/schemaorg-all-https.jsonld" 12 | ]); 13 | expect(mySA.getListOfClasses()).toHaveLength(804); 14 | expect(mySA.getListOfProperties()).toHaveLength(1464); 15 | }); 16 | 17 | test("fetch vocab by URL - direct URL", async () => { 18 | const mySA = new SDOAdapter({ 19 | commit, 20 | schemaHttps: true, 21 | onError: debugFuncErr 22 | }); 23 | await mySA.addVocabularies([ 24 | "https://raw.githubusercontent.com/semantifyit/schemaorg/main/data/releases/10.0/schemaorg-all-https.jsonld", 25 | "https://raw.githubusercontent.com/semantifyit/schema-org-adapter/master/tests/resources/data/vocabularies/vocabulary-animal.json" 26 | ]); 27 | const data1a = mySA.getAllProperties(); 28 | debugFunc(data1a.length); 29 | expect(data1a.length > 1000).toEqual(true); 30 | const Place = mySA.getClass("schema:Thing"); 31 | expect(Place.getSubClasses({ implicit: false }).length).toBe(11); 32 | expect(Place.getSubClasses({ implicit: false })).toContain("ex:Animal"); 33 | 34 | const mySaError = new SDOAdapter(); 35 | 36 | await expect(async () => await mySaError.fetchVocabularyFromURL("http://www.fantasyurl.test")).rejects.toThrow( 37 | "Could not find any resource at the given URL." 38 | ); 39 | 40 | await expect(async () => await mySaError.addVocabularies("http://www.fantasyurl.test")).rejects.toThrow( 41 | "The given URL http://www.fantasyurl.test did not contain a valid JSON-LD vocabulary." 42 | ); 43 | 44 | await expect(async () => await mySaError.addVocabularies("https://typedoc.org/")).rejects.toThrow( 45 | "The given URL https://typedoc.org/ did not contain a valid JSON-LD vocabulary." 46 | ); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /tests/resources/data/vocabularies/vocabulary-animal-dvs-altered.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": { 3 | "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", 4 | "rdfs": "http://www.w3.org/2000/01/rdf-schema#", 5 | "xsd": "http://www.w3.org/2001/XMLSchema#", 6 | "ds": "https://vocab.sti2.at/ds/", 7 | "schema": "https://schema.org/", 8 | "schematisch": "https://schematisch.org/", 9 | "rdfs:subClassOf": { 10 | "@id": "rdfs:subClassOf", 11 | "@type": "@id" 12 | }, 13 | "schema:domainIncludes": { 14 | "@id": "schema:domainIncludes", 15 | "@type": "@id" 16 | }, 17 | "schema:rangeIncludes": { 18 | "@id": "schema:rangeIncludes", 19 | "@type": "@id" 20 | }, 21 | "ex": "https://example-vocab.ex/", 22 | "ex2": "https://example-second-vocab.ex/" 23 | }, 24 | "@id": "https://semantify.it/voc/21345335", 25 | "@type": "ds:Vocabulary", 26 | "schema:name": [ 27 | { 28 | "@language": "en", 29 | "@value": "Animals altered" 30 | } 31 | ], 32 | "schema:description": [ 33 | { 34 | "@language": "en", 35 | "@value": "A vocabulary for Animals. Its not complete yet, but we got Tigers!" 36 | } 37 | ], 38 | "@graph": [ 39 | { 40 | "@id": "ex2:Reptilia", 41 | "@type": "rdfs:Class", 42 | "rdfs:comment": "Reptiles belong to the class Reptilia which consist of snakes, crocodiles and alligators.", 43 | "rdfs:label": [ 44 | { 45 | "@value": "Reptilia", 46 | "@language": "en" 47 | }, 48 | { 49 | "@value": "Reptilien", 50 | "@language": "de" 51 | } 52 | ], 53 | "rdfs:subClassOf": "ex:Animal" 54 | }, 55 | { 56 | "@id": "ex2:numberOfLegs", 57 | "@type": "rdf:Property", 58 | "rdfs:comment": "The number of legs an animal has.", 59 | "rdfs:label": "numberOfLegs", 60 | "schema:domainIncludes": "ex:Animal", 61 | "schema:rangeIncludes": "schema:Integer" 62 | }, 63 | { 64 | "@id": "schematisch:Hotel", 65 | "@type": "rdfs:Class", 66 | "rdfs:comment": "Testing strange vocab indicator", 67 | "rdfs:label": "Hotel", 68 | "rdfs:subClassOf": "ex:Thing" 69 | }, 70 | { 71 | "@id": "schematisch:Schema", 72 | "@type": "rdfs:Class", 73 | "rdfs:comment": "Testing strange vocab indicator", 74 | "rdfs:label": "schema", 75 | "rdfs:subClassOf": "ex:Thing" 76 | } 77 | ] 78 | } 79 | -------------------------------------------------------------------------------- /src/utilities/graph/extractFromClassMemory.ts: -------------------------------------------------------------------------------- 1 | import { TermMemory, VocabularyNode } from "../../types/types"; 2 | import { NS, TermTypeIRI } from "../../data/namespaces"; 3 | import { cloneJson } from "../general/cloneJson"; 4 | 5 | /** @ignore 6 | * Part C) of the addVocabulary-algorithm 7 | * This function is used to extract enumerations and data-types form the class memory and move them to the corresponding memories 8 | * */ 9 | export function extractFromClassMemory( 10 | classMemory: TermMemory, 11 | otherMemory: TermMemory, 12 | addGraphNodeFn: ( 13 | // eslint-disable-next-line no-unused-vars 14 | memory: Record, 15 | // eslint-disable-next-line no-unused-vars 16 | newNode: VocabularyNode, 17 | // eslint-disable-next-line no-unused-vars 18 | vocabURL?: string 19 | ) => boolean, 20 | vocabURL?: string 21 | ) { 22 | let termSwitched; 23 | do { 24 | termSwitched = false; 25 | const classesKeys = Object.keys(classMemory); 26 | const otherKeys = Object.keys(otherMemory); 27 | for (const actClassKey of classesKeys) { 28 | if (otherKeys.includes(actClassKey)) { 29 | // if an entity of the class memory is already in the other memory, then merge them in the other memory (use-case: a new vocabulary adds data to an already existing non-class) 30 | termSwitched = true; 31 | // merge 32 | addGraphNodeFn(otherMemory, classMemory[actClassKey], vocabURL); 33 | delete classMemory[actClassKey]; 34 | } else if (classMemory[actClassKey][NS.rdfs.subClassOf] !== undefined) { 35 | const subClassArray = classMemory[actClassKey][NS.rdfs.subClassOf]; 36 | for (const actSubClass of subClassArray) { 37 | if ( 38 | actSubClass === TermTypeIRI.enumeration || 39 | otherKeys.includes(actSubClass) 40 | ) { 41 | if (classMemory[actClassKey] && !otherMemory[actClassKey]) { 42 | termSwitched = true; 43 | otherMemory[actClassKey] = cloneJson(classMemory[actClassKey]); 44 | delete classMemory[actClassKey]; 45 | } else if (classMemory[actClassKey] && otherMemory[actClassKey]) { 46 | termSwitched = true; 47 | // merge 48 | addGraphNodeFn(otherMemory, classMemory[actClassKey], vocabURL); 49 | delete classMemory[actClassKey]; 50 | } 51 | } 52 | } 53 | } 54 | } 55 | } while (termSwitched); 56 | } 57 | -------------------------------------------------------------------------------- /docu/examples/example-node.js: -------------------------------------------------------------------------------- 1 | const { SOA } = require("../../lib/index"); // run the npm-script "buildTs" to generate js files for this example 2 | const VOC_OBJ_ZOO = require("../../tests/resources/data/vocabularies/vocabulary-animal.json"); 3 | main(); 4 | 5 | /** 6 | * example usage of the library within node.js 7 | */ 8 | async function main() { 9 | const mySA = await SOA.create(); 10 | const mySDOUrl = await mySA.constructURLSchemaVocabulary("latest"); 11 | console.log("The latest version is " + mySDOUrl); 12 | await mySA.addVocabularies([mySDOUrl, VOC_OBJ_ZOO]); 13 | let testClass = mySA.getClass("https://schema.org/Person"); 14 | console.log(mySA.getVocabularies()); 15 | console.log("getIRI() " + testClass.getIRI()); 16 | console.log("getIRI(\"Compact\") " + testClass.getIRI("Compact")); 17 | console.log("getName() " + testClass.getName()); 18 | console.log("getDescription(\"en\") " + testClass.getDescription("en")); 19 | console.log("getProperties({ implicit: false }) " + testClass.getProperties({ implicit: false })); 20 | console.log("getProperties() " + testClass.getProperties()); 21 | console.log("getProperties({ implicit: true }) " + testClass.getProperties({ implicit: true })); 22 | console.log(testClass.toString()); 23 | console.log(JSON.stringify(testClass.toJSON(), null, 2)); 24 | 25 | const testProp = mySA.getProperty("schema:aspect"); 26 | console.log("isSuperseededBy() " + testProp.isSupersededBy()); 27 | 28 | const extVocClass = mySA.getClass("ex:Tiger"); 29 | console.log(JSON.stringify(extVocClass.toJSON(), null, 2)); 30 | testClass = mySA.getClass("schema:Person"); 31 | console.log(JSON.stringify(testClass.toJSON({ implicit: false }), null, 2)); 32 | const testProperty = mySA.getProperty("schema:translationOfWork"); 33 | console.log(JSON.stringify(testProperty.toJSON({ implicit: false }), null, 2)); 34 | console.log(JSON.stringify(testProperty.getName("en"), null, 2)); 35 | const testDataType = mySA.getDataType("schema:Number"); 36 | console.log(JSON.stringify(testDataType.toJSON({ implicit: false }), null, 2)); 37 | let testEnumeration = mySA.getEnumeration("schema:MedicalEnumeration"); 38 | console.log(JSON.stringify(testEnumeration.toJSON({ implicit: false }), null, 2)); 39 | const testEnumerationMember = mySA.getEnumerationMember("schema:Radiography"); 40 | console.log( 41 | JSON.stringify(testEnumerationMember.toJSON({ implicit: false }), null, 2) 42 | ); 43 | console.log( 44 | JSON.stringify(testEnumerationMember.toJSON({ implicit: false }), null, 2) 45 | ); 46 | } 47 | -------------------------------------------------------------------------------- /tests/resources/data/vocabularies/vocabulary-day-of-week.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": { 3 | "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", 4 | "rdfs": "http://www.w3.org/2000/01/rdf-schema#", 5 | "xsd": "http://www.w3.org/2001/XMLSchema#", 6 | "schema": "https://schema.org/", 7 | "ex": "https://example-vocab.ex/", 8 | "rdfs:subClassOf": { 9 | "@id": "rdfs:subClassOf", 10 | "@type": "@id" 11 | }, 12 | "schema:domainIncludes": { 13 | "@id": "schema:domainIncludes", 14 | "@type": "@id" 15 | }, 16 | "schema:rangeIncludes": { 17 | "@id": "schema:rangeIncludes", 18 | "@type": "@id" 19 | } 20 | }, 21 | "@graph": [ 22 | { 23 | "@id": "schema:DayOfWeek", 24 | "rdfs:label": [ 25 | { 26 | "@value": "Wochentag", 27 | "@language": "de" 28 | }, 29 | { 30 | "@value": "DayOfWeek", 31 | "@language": "en" 32 | }, 33 | { 34 | "@value": "Giorno della settimana", 35 | "@language": "it" 36 | }, 37 | { 38 | "@value": "Jour de la semaine", 39 | "@language": "fr" 40 | }, 41 | { 42 | "@value": "Dag van de week", 43 | "@language": "nl" 44 | }, 45 | { 46 | "@value": "Dzień tygodnia", 47 | "@language": "pl" 48 | } 49 | ], 50 | "@type": "rdfs:Class" 51 | }, 52 | { 53 | "@id": "schema:PathologyTest", 54 | "rdfs:label": [ 55 | { 56 | "@value": "Pathologie Test", 57 | "@language": "de" 58 | }, 59 | { 60 | "@value": "PathologyTest", 61 | "@language": "en" 62 | }, 63 | { 64 | "@value": "Patologia", 65 | "@language": "it" 66 | }, 67 | { 68 | "@value": "Pathologie", 69 | "@language": "fr" 70 | }, 71 | { 72 | "@value": "Pathologytest", 73 | "@language": "nl" 74 | }, 75 | { 76 | "@value": "PatologyTest", 77 | "@language": "pl" 78 | } 79 | ], 80 | "rdfs:comment": [ 81 | { 82 | "@value": "Dies ist ein Pathologie Test", 83 | "@language": "de" 84 | }, 85 | { 86 | "@value": "This is a Pathology Test", 87 | "@language": "en" 88 | } 89 | ], 90 | "@type": "rdfs:Class", 91 | "rdfs:subClassOf": { 92 | "@id": "https://schema.org/Enumeration" 93 | } 94 | } 95 | ] 96 | } 97 | -------------------------------------------------------------------------------- /src/utilities/graph/curateVocabNode.ts: -------------------------------------------------------------------------------- 1 | import { VocabularyNode } from "../../types/types"; 2 | import { NS, TermTypeIRI } from "../../data/namespaces"; 3 | import { isString } from "../general/isString"; 4 | import { curateLanguageTerm } from "./curateLanguageTerm"; 5 | import { curateRelationshipTermArray } from "./curateRelationshipTermArray"; 6 | 7 | /** @ignore 8 | * Processes a given vocabulary node to a wished format (we call this process "curation") 9 | * 10 | * @param vocabNode - the input vocabulary node 11 | * @param vocabularies - the vocabularies used by the graph so far 12 | * @returns {object} the curated node 13 | */ 14 | export function curateVocabNode( 15 | vocabNode: VocabularyNode, 16 | vocabularies: Record 17 | ): VocabularyNode { 18 | curateLanguageTerm(vocabNode, NS.rdfs.comment); 19 | curateLanguageTerm(vocabNode, NS.rdfs.label); 20 | // terms with an array as default 21 | curateRelationshipTermArray(vocabNode, NS.rdfs.subClassOf, TermTypeIRI.class); 22 | curateRelationshipTermArray( 23 | vocabNode, 24 | NS.rdfs.subPropertyOf, 25 | TermTypeIRI.property 26 | ); 27 | curateRelationshipTermArray( 28 | vocabNode, 29 | NS.schema.domainIncludes, 30 | TermTypeIRI.property 31 | ); 32 | curateRelationshipTermArray( 33 | vocabNode, 34 | NS.schema.rangeIncludes, 35 | TermTypeIRI.property 36 | ); 37 | // terms with a string | null as default 38 | if ( 39 | vocabNode[NS.schema.inverseOf] === undefined && 40 | vocabNode["@type"] === TermTypeIRI.property 41 | ) { 42 | vocabNode[NS.schema.inverseOf] = null; 43 | } 44 | // if no schema:isPartOf property is stated yet (e.g. "https://pending.schema.org"), we detect the vocabulary used from the context, and put the corresponding (curated) IRI as value for this property (e.g. "https://schema.org") 45 | if (!isString(vocabNode[NS.schema.isPartOf])) { 46 | const vocabKeys = Object.keys(vocabularies); 47 | // e.g. schema 48 | let vocab = vocabKeys.find( 49 | (el) => 50 | vocabNode["@id"].substring(0, vocabNode["@id"].indexOf(":")) === el 51 | ); 52 | if (isString(vocab)) { 53 | // e.g. https://schema.org/ 54 | vocab = vocabularies[vocab]; 55 | let newChange; 56 | do { 57 | newChange = false; 58 | if (vocab.endsWith("/") || vocab.endsWith("#")) { 59 | vocab = vocab.substring(0, vocab.length - 1); 60 | newChange = true; 61 | } 62 | } while (newChange); 63 | // e.g. https://schema.org 64 | vocabNode[NS.schema.isPartOf] = vocab; 65 | } 66 | } 67 | return vocabNode; 68 | } 69 | -------------------------------------------------------------------------------- /tests/classes/Context.test.ts: -------------------------------------------------------------------------------- 1 | import { testSdoAdapter } from "../resources/utilities/testUtilities"; 2 | import VOC_OBJ_ODTA_NEW from "../resources/data/vocabularies/odta-new-context.json"; 3 | import VOC_OBJ_20 from "../resources/data/vocabularies/schema/schema-20.0.json"; 4 | import VOC_OBJ_20_NEW from "../resources/data/vocabularies/schema-20-new-context.json"; 5 | 6 | /** 7 | * Tests regarding different contexts in vocabularies, and SDO handling them 8 | */ 9 | describe("Context tests", () => { 10 | test("new context 1", async () => { 11 | const sdoAdapter = await testSdoAdapter({ 12 | vocabularies: [VOC_OBJ_ODTA_NEW], 13 | schemaVersion: "latest", 14 | equateVocabularyProtocols: true 15 | }); 16 | const p1 = sdoAdapter.getProperty("odta:sustainabilityFeature"); 17 | expect(p1.getName()).toEqual("sustainabilityFeature"); 18 | expect(p1.getDescription("en")).toEqual( 19 | "A property for defining sustainability features for different types of touristic entities." 20 | ); 21 | expect(p1.getDomains({ implicit: false })).toEqual([ 22 | "schema:Event", 23 | "schema:Offer", 24 | "schema:Organization", 25 | "schema:Place", 26 | "schema:Trip" 27 | ]); 28 | expect(p1.getRanges({ implicit: false })).toEqual(["odta:SustainableTransportationEnumeration"]); 29 | expect(p1.getInverseOf("Compact")).toEqual("schema:name"); 30 | expect(p1.isSupersededBy("Compact")).toEqual("schema:description"); 31 | expect(p1.getSuperProperties({ implicit: false })).toEqual(["schema:legalName"]); 32 | }); 33 | 34 | test("new context 2", async () => { 35 | const sdoAdapter1 = await testSdoAdapter({ 36 | vocabularies: [VOC_OBJ_20], 37 | equateVocabularyProtocols: true 38 | }); 39 | const sdoAdapter2 = await testSdoAdapter({ 40 | vocabularies: [VOC_OBJ_20_NEW], 41 | equateVocabularyProtocols: true 42 | }); 43 | const propsList1 = sdoAdapter1.getListOfProperties(); 44 | expect(propsList1).toEqual(sdoAdapter2.getListOfProperties()); 45 | for (const p of propsList1) { 46 | const p1 = sdoAdapter1.getProperty(p); 47 | const p2 = sdoAdapter2.getProperty(p); 48 | expect(p1.getName()).toEqual(p2.getName()); 49 | expect(p1.getDescriptions()).toEqual(p2.getDescriptions()); 50 | expect(p1.getDescription()).toEqual(p2.getDescription()); 51 | expect(p1.getRanges()).toEqual(p2.getRanges()); 52 | expect(p1.getDomains()).toEqual(p2.getDomains()); 53 | expect(p1.getInverseOf()).toEqual(p2.getInverseOf()); 54 | expect(p1.isSupersededBy()).toEqual(p2.isSupersededBy()); 55 | expect(p1.getSuperProperties()).toEqual(p2.getSuperProperties()); 56 | expect(p1.getSubProperties()).toEqual(p2.getSubProperties()); 57 | } 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /src/utilities/reasoning/checkFilterValidity.ts: -------------------------------------------------------------------------------- 1 | import { FilterObject } from "../../types/FilterObject.type"; 2 | import { isString } from "../general/isString"; 3 | import { isArrayOfStrings } from "../general/isArrayOfStrings"; 4 | import { toArray } from "../general/toArray"; 5 | import { isTermTypeLabelValue } from "../../data/namespaces"; 6 | import { isSchemaModule } from "../../data/schemaModules"; 7 | import { isBoolean } from "../general/isBoolean"; 8 | 9 | // checks if the input filter has a valid format (syntax) and values (semantic) 10 | export function checkFilterValidity(filter?: FilterObject) { 11 | if (!filter) { 12 | return; 13 | } 14 | if (filter.schemaModule !== undefined && filter.schemaModuleExclude !== undefined) { 15 | throw new Error("The filters schemaModule and schemaModuleExclude must not be used at the same time!"); 16 | } 17 | if (filter.termType !== undefined && filter.termTypeExclude !== undefined) { 18 | throw new Error("The filters termType and termTypeExclude must not be used at the same time!"); 19 | } 20 | if (filter.fromVocabulary !== undefined && filter.fromVocabularyExclude !== undefined) { 21 | throw new Error("The filters fromVocabulary and fromVocabularyExclude must not be used at the same time!"); 22 | } 23 | valOrArrayOfValCheck(filter.termType, "termType", isTermTypeLabelValue); 24 | valOrArrayOfValCheck(filter.termTypeExclude, "termTypeExclude", isTermTypeLabelValue); 25 | valOrArrayOfValCheck(filter.schemaModule, "schemaModule", isSchemaModule); 26 | valOrArrayOfValCheck(filter.schemaModuleExclude, "schemaModuleExclude", isSchemaModule); 27 | valOrArrayOfValCheck(filter.fromVocabulary, "fromVocabulary"); 28 | valOrArrayOfValCheck(filter.fromVocabularyExclude, "fromVocabularyExclude"); 29 | if (filter.isSuperseded !== undefined && !isBoolean(filter.isSuperseded)) { 30 | throw new Error("The filter isSuperseded must have true or false as value"); 31 | } 32 | } 33 | 34 | 35 | // checks if the given filter has a string or array of strings as value, also checks if those strings belong to a specific type 36 | function valOrArrayOfValCheck(filterValue: unknown, filterLabel: string, valueFunction?: (s: string) => boolean) { 37 | if (filterValue) { 38 | // invalid datatype (syntax) 39 | if (!isString(filterValue) && !isArrayOfStrings(filterValue)) { 40 | throw new Error("The filter " + filterLabel + " must have a string or an array of strings as value"); 41 | } 42 | // invalid value type (semantic) 43 | if (valueFunction) { 44 | const invalidValue = toArray(filterValue).find( 45 | (el) => !valueFunction(el) 46 | ); 47 | if (invalidValue) { 48 | throw new Error("Invalid value for filter " + filterLabel + ":" + invalidValue); 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/data/schemaModules.ts: -------------------------------------------------------------------------------- 1 | /** @ignore 2 | * schema.org extension identifiers as array 3 | */ 4 | export const SchemaModuleLabel = ["core", "health-lifesci", "pending", "bib", "meta", "auto", "attic"] as const; 5 | 6 | /** 7 | * Schema module identifiers which reflect the extension modules of schema.org. More information about the schema modules can be found in Organization of Schemas - Hosted Sections. `core` is added as an identifier for schema.org terms that belong to no particular extension: 8 | * > `"core"` -> core terms of the schema.org vocabulary - belong to no particular extension
9 | * `"pending"` -> The pending section for work-in-progress terms
10 | * `"health-lifesci"` -> The Health and Lifesciences Section
11 | * `"bib"` -> The Bibliographic Section
12 | * `"auto"` -> The Auto Section
13 | * `"meta"` -> The meta section designed to support the implementation of the Schema.org vocabulary itself. They are NOT advocated for widespread use across the web.
14 | * `"attic"` -> The attic section includes terms which are no longer part of the core vocabulary or its extensions. It is encouraged to NOT use these terms.
15 | */ 16 | export type SchemaModule = (typeof SchemaModuleLabel)[number]; 17 | 18 | /** @ignore 19 | * part of the namespaces for each schema module (protocol is omitted to allow http and https) 20 | */ 21 | export const SchemaModuleNamespaceMap: Record = { 22 | core: "//schema.org", 23 | "health-lifesci": "//health-lifesci.schema.org", 24 | pending: "//pending.schema.org", 25 | bib: "//bib.schema.org", 26 | meta: "//meta.schema.org", 27 | auto: "//auto.schema.org", 28 | attic: "//attic.schema.org" 29 | }; 30 | 31 | export function isSchemaModule(value: string): value is SchemaModule { 32 | return Object.keys(SchemaModuleNamespaceMap).includes(value); 33 | } 34 | 35 | // returns the matching schema module identifier for the given schema module namespace (if any) 36 | export function getSchemaModuleMatch(termVocabulary: string): SchemaModule | null { 37 | if (!termVocabulary) { 38 | return null; // should NOT happen, that a term has no vocabulary namespace assigned to it. We add this check for safety nevertheless 39 | } 40 | for (const k of Object.keys(SchemaModuleNamespaceMap) as SchemaModule[]) { 41 | if (termVocabulary.includes(SchemaModuleNamespaceMap[k])) { 42 | return k; 43 | } 44 | } 45 | return null; 46 | } 47 | -------------------------------------------------------------------------------- /tests/utilities/infrastructure/commit-versions.test.ts: -------------------------------------------------------------------------------- 1 | import { SDOAdapter, SOA } from "../../../src"; 2 | import { SEMANTIFY_COMMIT } from "../../../src/data/semantify"; 3 | 4 | const schemaRepoCommit = "d5ed5b8de39127c42cc7e252f242e4eb5e862996"; 5 | const semantifyHostCommit = SEMANTIFY_COMMIT 6 | 7 | // here we want to check that the schema.org ontologies are the same, despite the source of the vocabularies (different commit versions) 8 | // 1. no commit -> GitHub repo of semantify (fork from schema.org GitHub Repo) - stops at version 15.0 currently 9 | // 2. commit "4605d2024ea48ead95af78c3f271052619bdff30" -> GitHub repo of schema.org - latest version 29.3 10 | // 3. commit "SEMANTIFY" -> Self-hosted versions, starts at 12.0, latest is 28.1 11 | 12 | describe("Commit versions", () => { 13 | 14 | test("version 15.0", async () => { 15 | const soa1 = await SOA.create({ 16 | schemaVersion: "15.0", 17 | }); 18 | const soa2 = await SOA.create({ 19 | commit: schemaRepoCommit, 20 | schemaVersion: "15.0", 21 | }); 22 | const soa3 = await SOA.create({ 23 | commit: semantifyHostCommit, 24 | schemaVersion: "15.0", 25 | }); 26 | expect(soa1.getListOfTerms().length).toEqual(soa2.getListOfTerms().length) 27 | expect(soa1.getListOfTerms().length).toEqual(soa3.getListOfTerms().length) 28 | compareTerms(soa1, soa2) 29 | compareTerms(soa1, soa3) 30 | }); 31 | 32 | test("version 25.0", async () => { 33 | const soa2 = await SOA.create({ 34 | commit: schemaRepoCommit, 35 | schemaVersion: "25.0", 36 | }); 37 | const soa3 = await SOA.create({ 38 | commit: semantifyHostCommit, 39 | schemaVersion: "25.0", 40 | }); 41 | expect(soa2.getListOfTerms().length).toEqual(soa3.getListOfTerms().length) 42 | compareTerms(soa2, soa3) 43 | }); 44 | 45 | // if this test fails, it is likely because a new schema.org version is out, and the semantify self-host repository must be updated 46 | test("latest", async () => { 47 | const soa2 = await SOA.create({ 48 | commit: schemaRepoCommit, 49 | schemaVersion: "latest", 50 | }); 51 | const soa3 = await SOA.create({ 52 | commit: semantifyHostCommit, 53 | schemaVersion: "latest", 54 | }); 55 | expect(soa2.getListOfTerms().length).toEqual(soa3.getListOfTerms().length) 56 | compareTerms(soa2, soa3) 57 | }); 58 | 59 | }); 60 | 61 | function compareTerms(soa1: SDOAdapter, soa2: SDOAdapter){ 62 | compareArrays(soa1.getListOfClasses(), soa2.getListOfClasses()) 63 | compareArrays(soa1.getListOfProperties(), soa2.getListOfProperties()) 64 | compareArrays(soa1.getListOfEnumerations(), soa2.getListOfEnumerations()) 65 | compareArrays(soa1.getListOfEnumerationMembers(), soa2.getListOfEnumerationMembers()) 66 | compareArrays(soa1.getListOfDataTypes(), soa2.getListOfDataTypes()) 67 | } 68 | 69 | function compareArrays(arr1: string[],arr2: string[]){ 70 | expect(arr1.every(iri => arr2.includes(iri))).toBe(true); 71 | expect(arr2.every(iri => arr1.includes(iri))).toBe(true); 72 | } 73 | -------------------------------------------------------------------------------- /src/utilities/graph/generateContext.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "../../types/types"; 2 | import { cloneJson } from "../general/cloneJson"; 3 | import { isString } from "../general/isString"; 4 | import { isObject } from "../general/isObject"; 5 | 6 | /** @ignore 7 | * Merges 2 JSON-LD context objects into a new one 8 | * 9 | * @param currentContext - the first context object 10 | * @param newContext - the second context object 11 | * @returns the resulting context object 12 | */ 13 | export function generateContext( 14 | currentContext: Context, 15 | newContext: Context 16 | ): Context { 17 | const keysCurrentContext = Object.keys(currentContext); 18 | const keysNewContext = Object.keys(newContext); 19 | // add all the definitions of the old context 20 | let resultContext = cloneJson(currentContext); 21 | // add vocabs of new context that are not already used (value is IRI) 22 | for (const keyNC of keysNewContext) { 23 | if (isString(newContext[keyNC])) { 24 | // first: check if the IRI is already used, with any indicator 25 | let foundMatch = false; 26 | for (const keyCC of keysCurrentContext) { 27 | if (isString(resultContext[keyCC])) { 28 | if (resultContext[keyCC] === newContext[keyNC]) { 29 | // found match, the IRI is already covered 30 | foundMatch = true; 31 | break; 32 | } 33 | } 34 | } 35 | if (foundMatch) { 36 | continue; // IRI is already covered, continue with next 37 | } 38 | if (!resultContext[keyNC]) { 39 | // add new vocab indicator 40 | resultContext[keyNC] = newContext[keyNC]; 41 | } else { 42 | // check if the IRI is the same, if not: add new uri under new vocab indicator 43 | if (resultContext[keyNC] !== newContext[keyNC]) { 44 | let foundFreeName = false; 45 | let counter = 1; 46 | while (!foundFreeName) { 47 | const newVocabIndicator = keyNC + counter++; 48 | if (!resultContext[newVocabIndicator]) { 49 | foundFreeName = true; 50 | resultContext[newVocabIndicator] = newContext[keyNC]; 51 | } 52 | } 53 | } 54 | } 55 | } 56 | } 57 | // sort vocab IRIs by alphabet 58 | const ordered: Context = {}; 59 | Object.keys(resultContext) 60 | .sort() 61 | .forEach(function (key) { 62 | ordered[key] = resultContext[key]; 63 | }); 64 | // reorder context: Vocab Indicators first (value = string), then term handlers (value = object) 65 | resultContext = ordered; 66 | const keysResultContext = Object.keys(resultContext); 67 | const orderedResultContext: Context = {}; 68 | // add the Vocab Indicators (value = string) 69 | for (const keyRC of keysResultContext) { 70 | if (isString(resultContext[keyRC])) { 71 | orderedResultContext[keyRC] = resultContext[keyRC]; 72 | } 73 | } 74 | // add the term handlers (value = object) 75 | for (const keyRC of keysResultContext) { 76 | if (isObject(resultContext[keyRC])) { 77 | orderedResultContext[keyRC] = resultContext[keyRC]; 78 | } 79 | } 80 | return orderedResultContext; 81 | } 82 | -------------------------------------------------------------------------------- /tests/resources/data/vocabularies/odta-new-context.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": { 3 | "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", 4 | "rdfs": "http://www.w3.org/2000/01/rdf-schema#", 5 | "xsd": "http://www.w3.org/2001/XMLSchema#", 6 | "ds": "https://vocab.sti2.at/ds/", 7 | "schema": "https://schema.org/", 8 | "rdfs:subClassOf": { 9 | "@id": "rdfs:subClassOf", 10 | "@type": "@id", 11 | "@container": "@set" 12 | }, 13 | "rdfs:subPropertyOf": { 14 | "@id": "rdfs:subPropertyOf", 15 | "@type": "@id", 16 | "@container": "@set" 17 | }, 18 | "schema:inverseOf": { 19 | "@id": "schema:inverseOf", 20 | "@type": "@id" 21 | }, 22 | "schema:supersededBy": { 23 | "@id": "schema:supersededBy", 24 | "@type": "@id" 25 | }, 26 | "schema:domainIncludes": { 27 | "@id": "schema:domainIncludes", 28 | "@type": "@id", 29 | "@container": "@set" 30 | }, 31 | "schema:rangeIncludes": { 32 | "@id": "schema:rangeIncludes", 33 | "@type": "@id", 34 | "@container": "@set" 35 | }, 36 | "rdfs:label": { 37 | "@container": "@set" 38 | }, 39 | "rdfs:comment": { 40 | "@container": "@set" 41 | }, 42 | "odta": "https://odta.io/voc/" 43 | }, 44 | "@graph": [ 45 | { 46 | "@id": "odta:SustainableTransportationEnumeration", 47 | "@type": "rdfs:Class", 48 | "rdfs:comment": [ 49 | { 50 | "@language": "en", 51 | "@value": "An enumeration type for sustainable transportation" 52 | } 53 | ], 54 | "rdfs:label": [ 55 | { 56 | "@language": "en", 57 | "@value": "SustainableTransportationEnumeration" 58 | } 59 | ], 60 | "rdfs:subClassOf": [ 61 | "schema:Enumeration" 62 | ] 63 | }, 64 | { 65 | "@id": "odta:sustainabilityFeature", 66 | "@type": "rdf:Property", 67 | "rdfs:comment": [ 68 | { 69 | "@language": "en", 70 | "@value": "A property for defining sustainability features for different types of touristic entities." 71 | } 72 | ], 73 | "rdfs:label": [ 74 | { 75 | "@language": "en", 76 | "@value": "sustainabilityFeature" 77 | } 78 | ], 79 | "rdfs:subPropertyOf": [ 80 | "schema:legalName" 81 | ], 82 | "schema:domainIncludes": [ 83 | "schema:Event", 84 | "schema:Offer", 85 | "schema:Organization", 86 | "schema:Place", 87 | "schema:Trip" 88 | ], 89 | "schema:inverseOf": "schema:name", 90 | "schema:rangeIncludes": [ 91 | "odta:SustainableTransportationEnumeration" 92 | ], 93 | "schema:supersededBy": "schema:description" 94 | } 95 | ], 96 | "@id": "https://semantify.it/voc/test", 97 | "@type": "ds:Vocabulary", 98 | "schema:description": { 99 | "@language": "en", 100 | "@value": "test" 101 | }, 102 | "schema:name": { 103 | "@language": "en", 104 | "@value": "test" 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /tests/sdo-adapter/vocabulary-functions.test.ts: -------------------------------------------------------------------------------- 1 | import { SDOAdapter } from "../../src/classes/SDOAdapter"; 2 | import { commit, debugFuncErr } from "../resources/utilities/testUtilities"; 3 | import VOC_OBJ_ZOO from "../resources/data/vocabularies/vocabulary-animal.json"; 4 | import VOC_OBJ_SDO_3_7 from "../resources/data/vocabularies/schema/schema-3.7.json"; 5 | import VOC_OBJ_GWON from "../resources/data/vocabularies/graph-with-one-node.json"; 6 | 7 | /** 8 | * Tests regarding the JS-Class for "SDOAdapter" 9 | */ 10 | describe("SDO Adapter - Vocabulary functions", () => { 11 | test("addVocabularies()", async () => { 12 | const mySA = new SDOAdapter({ 13 | commit, 14 | onError: debugFuncErr 15 | }); 16 | await mySA.addVocabularies([VOC_OBJ_SDO_3_7, VOC_OBJ_GWON]); 17 | const testClass = mySA.getClass("namespace:AwesomePerson"); 18 | expect(testClass.getName()).toEqual("validValue"); 19 | await expect(mySA.addVocabularies("http://noVocab.com")).rejects.toEqual( 20 | Error("The given URL http://noVocab.com did not contain a valid JSON-LD vocabulary.") 21 | ); 22 | // we try to trigger an error with an invalid input 23 | await expect(mySA.addVocabularies([true] as unknown as string)).rejects.toEqual( 24 | Error( 25 | "The first argument of the function must be an Array of vocabularies or a single vocabulary (JSON-LD as Object/String)" 26 | ) 27 | ); 28 | await expect(mySA.addVocabularies("Test String that should trigger JSON.parse() to throw")).rejects.toEqual( 29 | Error("Parsing of vocabulary string produced an invalid JSON-LD.") 30 | ); 31 | await mySA.addVocabularies(JSON.stringify(VOC_OBJ_SDO_3_7)); // try stringified version 32 | }); 33 | 34 | test("addVocabularies() add single vocabulary", async () => { 35 | const mySA = new SDOAdapter({ 36 | commit, 37 | onError: debugFuncErr 38 | }); 39 | await mySA.addVocabularies(VOC_OBJ_SDO_3_7); 40 | const testClass = mySA.getClass("schema:Hotel"); 41 | expect(testClass.getName()).toEqual("Hotel"); 42 | }); 43 | 44 | test("addVocabularies() latest", async () => { 45 | const mySA = new SDOAdapter({ 46 | commit, 47 | onError: debugFuncErr 48 | }); 49 | await mySA.addVocabularies([await mySA.constructURLSchemaVocabulary("latest"), VOC_OBJ_GWON]); 50 | const testClass = mySA.getClass("namespace:AwesomePerson"); 51 | expect(testClass.getName()).toEqual("validValue"); 52 | }); 53 | 54 | test("getVocabularies() latest", async () => { 55 | const mySA = new SDOAdapter({ 56 | commit, 57 | onError: debugFuncErr 58 | }); 59 | await mySA.addVocabularies([await mySA.constructURLSchemaVocabulary("latest"), VOC_OBJ_ZOO]); 60 | const vocabs = mySA.getVocabularies(); 61 | expect(Object.keys(vocabs).length).toBe(2); 62 | expect(vocabs.schema).not.toBe(undefined); 63 | expect(vocabs.ex).not.toBe(undefined); 64 | expect(vocabs.ex).toBe("https://example-vocab.ex/"); 65 | const allVocabs = mySA.getVocabularies(false); 66 | expect(Object.keys(allVocabs).length).not.toBe(2); 67 | expect(allVocabs.schema).not.toBe(undefined); 68 | expect(allVocabs.dcterms).not.toBe(undefined); 69 | expect(allVocabs.rdf).not.toBe(undefined); 70 | expect(allVocabs.rdfs).not.toBe(undefined); 71 | expect(allVocabs.ex).not.toBe(undefined); 72 | expect(allVocabs.ex).toBe("https://example-vocab.ex/"); 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /tests/experimental/vocabulary-comparison.ts: -------------------------------------------------------------------------------- 1 | import { constructURLSchemaVocabulary } from "../../src/classes/Infrastructure"; 2 | import { SDOAdapter } from "../../src/classes/SDOAdapter"; 3 | import { isString } from "../../src/utilities/general/isString"; 4 | import { diff } from "deep-object-diff"; 5 | 6 | // to build (ignore errors) 7 | // tsc tests/experimental/vocabulary-comparison.ts 8 | // to execute 9 | // node tests/experimental/vocabulary-comparison.js 10 | // gotta delete all generated js files afterwards though 11 | 12 | const vocabVersion1 = "15.0"; 13 | const vocabVersion2 = "latest"; 14 | const schemaRepoCommit = "ae70d0a9dbab49c005fd8dd1fd522f7236b04be0"; 15 | 16 | main(); 17 | async function main() { 18 | const url1 = await constructURLSchemaVocabulary(vocabVersion1, true, schemaRepoCommit); 19 | const url2 = await constructURLSchemaVocabulary(vocabVersion2, true, schemaRepoCommit); 20 | console.log(url2); 21 | const sdoAdapter = new SDOAdapter(); 22 | let voc1 = await sdoAdapter.fetchVocabularyFromURL(url1); 23 | if (isString(voc1)) { 24 | voc1 = JSON.parse(voc1); 25 | } 26 | let voc2 = await sdoAdapter.fetchVocabularyFromURL(url2); 27 | if (isString(voc2)) { 28 | voc2 = JSON.parse(voc2); 29 | } 30 | const result: Record = {}; 31 | // context 32 | result.contextChanges = getDiff(voc1["@context"], voc2["@context"]); 33 | const g1 = voc1["@graph"]; 34 | const g2 = voc2["@graph"]; 35 | // in 1 but not in 2 36 | result.removals = g1.filter((el1) => !g2.find((el2) => el2["@id"] === el1["@id"])); 37 | // in 2 but not in 1 38 | result.additions = g2.filter((el2) => !g1.find((el1) => el1["@id"] === el2["@id"])); 39 | // in 1 and 2 40 | result.changes = g1 41 | .filter((el1) => g2.find((el2) => el2["@id"] === el1["@id"])) 42 | .map((el1) => { 43 | return { 44 | "@id": el1["@id"], 45 | ...getDiff( 46 | el1, 47 | g2.find((el2) => el2["@id"] === el1["@id"]) 48 | ) 49 | }; 50 | }) 51 | .filter((el) => Object.keys(el).length > 1); 52 | // console.log(JSON.stringify(result, null, 2)); 53 | console.log(result); 54 | } 55 | 56 | function getDiff(obj1: object, obj2: object) { 57 | const d = diff(sortObjValues(obj1), sortObjValues(obj2)); 58 | const result = {}; 59 | for (const k of Object.keys(d)) { 60 | // if (typeof d[k] !== "object" || Object.keys(d[k]).length !== 0) { 61 | if ( 62 | k !== "schema:source" && 63 | k !== "schema:contributor" && 64 | (typeof d[k] !== "object" || Object.keys(d[k]).length !== 0) 65 | ) { 66 | result[k] = d[k]; 67 | } 68 | } 69 | if (Object.keys(result).length === 0) { 70 | return undefined; 71 | } 72 | return result; 73 | } 74 | 75 | function sortObjValues(obj: Array | object | string) { 76 | if (Array.isArray(obj)) { 77 | let r1 = obj.sort(); 78 | if (r1[0] && typeof r1[0] === "object" && r1[0]["@id"]) { 79 | r1 = r1.sort((a, b) => { 80 | const keyA = a["@id"], 81 | keyB = b["@id"]; 82 | if (keyA < keyB) return -1; 83 | if (keyA > keyB) return 1; 84 | return 0; 85 | }); 86 | } 87 | return r1; 88 | } 89 | if (typeof obj !== "object") { 90 | return obj; 91 | } 92 | const result = {}; 93 | const sortedKeys = Object.keys(obj).sort(); 94 | for (const sk of sortedKeys) { 95 | result[sk] = sortObjValues(obj[sk]); 96 | } 97 | return result; 98 | } 99 | -------------------------------------------------------------------------------- /tests/index.test.ts: -------------------------------------------------------------------------------- 1 | import { FilterObject, SOA } from "../src/index"; 2 | import { commit, debugFuncErr } from "./resources/utilities/testUtilities"; 3 | import VOC_OBJ_SDO_3_7 from "./resources/data/vocabularies/schema/schema-3.7.json"; 4 | import VOC_OBJ_GWON from "./resources/data/vocabularies/graph-with-one-node.json"; 5 | import { getGitHubBaseURL } from "../src/utilities/infrastructure/getGitHubBaseURL"; 6 | import { SEMANTIFY_COMMIT } from "../src/data/semantify"; 7 | 8 | /** 9 | * Tests regarding the JS-Class for "SDOAdapter" 10 | */ 11 | describe("SDO Adapter methods", () => { 12 | test("create() without schema vocab", async () => { 13 | const mySA = await SOA.create({ 14 | commit: commit, 15 | schemaHttps: true, 16 | onError: debugFuncErr, 17 | vocabularies: [VOC_OBJ_GWON] 18 | }); 19 | const testClass = mySA.getClass("namespace:AwesomePerson"); 20 | expect(testClass.getName()).toEqual("validValue"); 21 | }); 22 | 23 | test("create() with vocab", async () => { 24 | const mySA = await SOA.create({ 25 | commit: commit, 26 | schemaHttps: true, 27 | onError: debugFuncErr, 28 | vocabularies: [VOC_OBJ_SDO_3_7, VOC_OBJ_GWON] 29 | }); 30 | const testClass = mySA.getClass("namespace:AwesomePerson"); 31 | expect(testClass.getName()).toEqual("validValue"); 32 | }); 33 | 34 | test("default filter", async () => { 35 | const defaultFilter: FilterObject = { 36 | schemaModuleExclude: "attic", 37 | isSuperseded: false 38 | }; 39 | const mySA = await SOA.create({ 40 | commit: commit, 41 | defaultFilter, 42 | vocabularies: [VOC_OBJ_SDO_3_7] 43 | }); 44 | expect(() => { 45 | mySA.getClass("schema:StupidType"); 46 | }).toThrow(); 47 | expect(mySA.getDefaultFilter()).toEqual(defaultFilter); 48 | mySA.setDefaultFilter(); 49 | expect(() => { 50 | mySA.getClass("schema:StupidType"); 51 | }).not.toThrow(); 52 | mySA.setDefaultFilter(defaultFilter); 53 | expect(mySA.getDefaultFilter()).toEqual(defaultFilter); 54 | expect(() => { 55 | mySA.getClass("schema:StupidType"); 56 | }).toThrow(); 57 | }); 58 | 59 | test("constructURLSchemaVocabulary()", async () => { 60 | // this test makes only sense for GitHub-hosted vocabulary versions 61 | if(commit === SEMANTIFY_COMMIT){ 62 | return; 63 | } 64 | const url = await SOA.constructURLSchemaVocabulary("9.0", true, commit); 65 | expect(url).toBe(getGitHubBaseURL(commit) + "/data/releases/9.0/schemaorg-all-https.jsonld"); 66 | const url2 = await SOA.constructURLSchemaVocabulary("3.9", false, commit); 67 | expect(url2).toBe(getGitHubBaseURL(commit) + "/data/releases/3.9/all-layers.jsonld"); 68 | const url3 = await SOA.constructURLSchemaVocabulary("9.0", false, commit); 69 | expect(url3).toBe(getGitHubBaseURL(commit) + "/data/releases/9.0/schemaorg-all-http.jsonld"); 70 | const url4 = await SOA.constructURLSchemaVocabulary("9.0", true, commit); 71 | expect(url4).toBe(getGitHubBaseURL(commit) + "/data/releases/9.0/schemaorg-all-https.jsonld"); 72 | const url5a = await SOA.constructURLSchemaVocabulary("latest"); 73 | const url5b = await SOA.constructURLSchemaVocabulary(); 74 | expect(url5a).toBe(url5b); 75 | const url6a = await SOA.constructURLSchemaVocabulary("9.0", true, "9a3ba46"); 76 | expect(url6a).toBe(getGitHubBaseURL("9a3ba46") + "/data/releases/9.0/schemaorg-all-https.jsonld"); 77 | }); 78 | 79 | test("getGitHubBaseURL()", async () => { 80 | expect(getGitHubBaseURL()).toBe("https://raw.githubusercontent.com/semantifyit/schemaorg/main"); 81 | expect(getGitHubBaseURL("9a3ba46")).toBe("https://raw.githubusercontent.com/schemaorg/schemaorg/9a3ba46"); 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /src/types/types.ts: -------------------------------------------------------------------------------- 1 | import { Graph } from "../classes/Graph"; 2 | import { TermTypeIRIValue, TermTypeLabelValue } from "../data/namespaces"; 3 | import { FilterObject } from "./FilterObject.type"; 4 | import { OutputIRIType } from "./OutputIRIType.type"; 5 | 6 | /** @ignore */ 7 | export interface ErrorFunction { 8 | (msg: string): void; 9 | } 10 | 11 | /** @ignore */ 12 | export type ParamObjSdoAdapter = { 13 | commit?: string; 14 | schemaHttps?: boolean; 15 | equateVocabularyProtocols?: boolean; 16 | onError?: ErrorFunction; 17 | outputFormat?: OutputIRIType; 18 | defaultFilter?: FilterObject; 19 | }; 20 | 21 | /** @ignore */ 22 | export type ToJsonTerm = { 23 | id: string; 24 | IRI: string; 25 | typeLabel: TermTypeLabelValue; 26 | typeIRI: TermTypeIRIValue; 27 | vocabURLs: string[] | null; 28 | vocabulary: string | null; 29 | source: string | string[] | null; 30 | supersededBy: string | null; 31 | name: string | null; 32 | description: string | null; 33 | }; 34 | 35 | /** @ignore */ 36 | export type ToJsonClass = ToJsonTerm & { 37 | superClasses: string[]; 38 | subClasses: string[]; 39 | properties: string[]; 40 | rangeOf: string[]; 41 | }; 42 | 43 | /** @ignore */ 44 | export type ToJsonEnumeration = ToJsonClass & { 45 | enumerationMembers: string[]; 46 | }; 47 | 48 | /** @ignore */ 49 | export type ToJsonDataType = ToJsonTerm & { 50 | superDataTypes: string[]; 51 | subDataTypes: string[]; 52 | rangeOf: string[]; 53 | }; 54 | 55 | /** @ignore */ 56 | export type ToJsonEnumerationMember = ToJsonTerm & { 57 | domainEnumerations: string[]; 58 | }; 59 | 60 | /** @ignore */ 61 | export type ToJsonProperty = ToJsonTerm & { 62 | ranges: string[]; 63 | domains: string[]; 64 | superProperties: string[]; 65 | subProperties: string[]; 66 | inverseOf: string | null; 67 | }; 68 | 69 | /** @ignore */ 70 | export type ContextWord = string; 71 | /** @ignore */ 72 | export type ContextObject = Record; 73 | /** @ignore */ 74 | export type ContextEntry = ContextWord | ContextObject; 75 | 76 | export type Context = Record; 77 | 78 | /** @ignore */ 79 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 80 | export type VocabularyNode = Record; 81 | /** @ignore */ 82 | export type VocabularyNodeKey = keyof VocabularyNode; 83 | 84 | export type Vocabulary = { 85 | "@context": Context; 86 | "@graph": VocabularyNode[]; 87 | "@id"?: string; 88 | }; 89 | 90 | /** @ignore */ 91 | export type TermMemory = Record; 92 | 93 | /** @ignore */ 94 | export interface VersionsFile { 95 | schemaversion: string; 96 | releaseLog: Record; 97 | } 98 | 99 | /** @ignore */ 100 | interface VersionsFileSemantifyItem { 101 | schemaVersion: string; // version identifier 102 | iri: string; // url to vocabulary 103 | lastUpdate: string; // ISO datetime string 104 | } 105 | 106 | /** @ignore */ 107 | export interface VersionsFileSemantify { 108 | latest: VersionsFileSemantifyItem; 109 | all: VersionsFileSemantifyItem[]; 110 | } 111 | 112 | /** @ignore */ 113 | export interface FilterParamObj { 114 | data: string[]; 115 | filter?: FilterObject; 116 | graph: Graph; 117 | } 118 | 119 | /** @ignore */ 120 | export interface LanguageObjectVocab { 121 | "@language": string; 122 | "@value": string; 123 | } 124 | 125 | /** @ignore */ 126 | export type LanguageObjectSdoAdapter = Record; 127 | 128 | /** @ignore */ 129 | export type CacheLiteral = string | number | object | boolean; 130 | /** @ignore */ 131 | export type CacheEntry = Record; 132 | /** @ignore */ 133 | export type CacheMap = Map; 134 | -------------------------------------------------------------------------------- /src/types/ParamObjCreateSdoAdapter.type.ts: -------------------------------------------------------------------------------- 1 | import { ErrorFunction, Vocabulary } from "./types"; 2 | import { OutputIRIType } from "./OutputIRIType.type"; 3 | import { FilterObject } from "./FilterObject.type"; 4 | 5 | /** 6 | * A **parameter object** to {@link create | create a new SDO Adapter} instance. All attributes of this object are optional (as well as the parameter object itself) and describe a certain setting that should be used for the created {@link SDOAdapter | SDO Adapter}. 7 | * @example 8 | * ```json 9 | * { 10 | * schemaVersion: "latest", 11 | * vocabularies: ["https://raw.githubusercontent.com/semantifyit/schema-org-adapter/master/tests/resources/data/vocabularies/vocabulary-animal.json"], 12 | * commit: "9a3ba46", 13 | * schemaHttps: true, 14 | * equateVocabularyProtocols: true, 15 | * outputFormat: "Compact", 16 | * defaultFilter: { 17 | * schemaModuleExclude: ["attic", "meta"], 18 | * isSuperseded: false 19 | * }, 20 | * onError: (e) => { 21 | * console.error(e); 22 | * } 23 | * } 24 | * ``` 25 | * @privateRemarks 26 | * we redefine the declarations from ParamObjSdoAdapter here to make them visible in the documentation 27 | */ 28 | export type ParamObjCreateSdoAdapter = { 29 | /** 30 | * The commit string from https://github.com/schemaorg/schemaorg which is taken as source for the SDO Adapter (if not given, the latest commit of our fork at https://github.com/semantifyit/schemaorg is taken). Use this parameter only if you want to change the schema.org repository used as source for the SDO Adapter. By standard, SDO Adapter uses a fork of the schema.org repository, which is updated only when schema.org releases a new vocabulary version, and that version passes all tests of SDO Adapter. 31 | */ 32 | commit?: string; 33 | /** 34 | * Enables the use of the https version of the schema.org vocabulary. Only available for schema.org version 9.0 upwards. (default = true) 35 | */ 36 | schemaHttps?: boolean; 37 | /** 38 | * If true, treat namespaces in input vocabularies as equal even if their protocols (http/https) are different. (default = false) 39 | */ 40 | equateVocabularyProtocols?: boolean; 41 | /** 42 | * A callback function(msg: string) that is called when an unexpected error happens. 43 | */ 44 | onError?: ErrorFunction; 45 | /** 46 | * Vocabularies that should be added to the SDO Adapter right after initialization. You have to pass the vocabulary either as a JSON-LD object, or as a URL pointing at such a JSON-LD object. If you use the setting **schemaVersion** then you should not add a schema.org vocabulary here. 47 | */ 48 | vocabularies?: (Vocabulary | string)[]; 49 | /** 50 | * The schema.org vocabulary version that should be added to the SDO Adapter right after initialization. You have to pass only the version string, e.g. `"13.0"`. It is also possible to pass `"latest"` to automatically fetch the latest version of schema.org. 51 | */ 52 | schemaVersion?: string; 53 | /** 54 | * Defines the format in which the IRI results from functions should be returned. 55 | * This option can be set for the SDO-Adapter on initialization, which becomes the standard (default = "Compact"). 56 | * After the initialization it is also possible to pass this option to some API-functions to get IRIs in a different format. The options are as follows: 57 | * * "Compact": The resulting IRIs are given in compact form, e.g. "schema:Hotel" 58 | * * "Absolute": The resulting IRIs are given in absolute form, e.g. "https://schema.org/Hotel" 59 | */ 60 | outputFormat?: OutputIRIType; 61 | /** 62 | * Sets a default filter for all results provided by the created SDO Adapter. Everytime no filter is passed to a function, this default filter is used. 63 | */ 64 | defaultFilter?: FilterObject; 65 | }; 66 | -------------------------------------------------------------------------------- /docu/docuReadme.md: -------------------------------------------------------------------------------- 1 | # API documentation 2 | 3 | This is the **technical API documentation** for the [schema-org-adapter](https://www.npmjs.com/package/schema-org-adapter) library. 4 | 5 | Additional documentation is linked at the end of this page. 6 | 7 | ## Setup 8 | 9 | The [readme](https://github.com/semantifyit/schema-org-adapter#schemaorg-adapter) explains the installation, loading and basic usage of this library. In any case, it is expected that the variable `SOA` references the library. 10 | 11 | Node.js 12 | 13 | ```javascript 14 | const { SOA } = require('schema-org-adapter'); 15 | // or 16 | import { SOA } from 'schema-org-adapter'; 17 | ``` 18 | 19 | Browser 20 | 21 | ```html 22 | 23 | ``` 24 | 25 | ## Usage 26 | 27 | The **schema-org-adapter** library itself (called **SOA** from now on) provides the following static functions: 28 | 29 | * {@link create | create()} 30 | * {@link fetchSchemaVersions | fetchSchemaVersions()} 31 | * {@link getLatestSchemaVersion | getLatestSchemaVersion()} 32 | * {@link constructURLSchemaVocabulary | constructURLSchemaVocabulary()} 33 | 34 | The most important function of **SOA** is {@link create | .create()}: it returns a new {@link SDOAdapter | SDOAdapter instance}. An `SDOAdapter` instance has its own settings and vocabularies, which are the base for the output given by its provided API. It is possible to pass these settings and vocabularies with a {@link ParamObjCreateSdoAdapter | parameter object} to the `.create()` function. 35 | 36 | In the following common use-case the SDOAdapter instance `mySdoAdapter` is created with default settings and the latest version of schema.org. 37 | 38 | ```javascript 39 | const mySdoAdapter = await SOA.create({ 40 | schemaVersion: "latest" 41 | }); 42 | ``` 43 | 44 | After the initialization of `mySdoAdapter`, its API can be used to retrieve information about the schema.org vocabulary. Check the {@link SDOAdapter | SDOAdapter reference page} to discover its API. The most important methods are those that create instances for vocabulary terms: 45 | 46 | * {@link SDOAdapter.getClass | getClass()} - creates a new {@link Class | Class instance} for a specific class 47 | * {@link SDOAdapter.getProperty | getProperty()} - creates a new {@link Property | Property instance} for a specific property 48 | * {@link SDOAdapter.getDataType | getDataType()} - creates a new {@link DataType | DataType instance} for a specific data-type 49 | * {@link SDOAdapter.getEnumeration | getEnumeration()} - creates a new {@link Enumeration | Enumeration instance} for a specific enumeration 50 | * {@link SDOAdapter.getEnumerationMember | getEnumerationMember()} - creates a new {@link EnumerationMember | EnumerationMember instance} for a specific enumeration member (a predefined value that an enumeration can have) 51 | 52 | In the following example a new Class instance is generated for the schema.org class https://schema.org/Hotel 53 | 54 | ```javascript 55 | const hotelClass = mySdoAdapter.getClass("schema:Hotel"); 56 | ``` 57 | 58 | The term instances have their own API depending on their type. A {@link Class | Class instance}, for example, provides a function to get all properties that the Class can use: 59 | 60 | ```javascript 61 | console.log(hotelClass.getProperties()); 62 | /* output: 63 | * [ 64 | * 'schema:amenityFeature', 65 | * 'schema:numberOfRooms', 66 | * 'schema:starRating', 67 | * 'schema:checkinTime', 68 | * 'schema:petsAllowed', 69 | * ... 70 | * ] 71 | */ 72 | ``` 73 | 74 | ## Additional Documentation 75 | 76 | #### [Data Model of SDO-Adapter](https://github.com/semantifyit/schema-org-adapter/blob/master/docu/dataModel.md) 77 | #### [Expected Vocabulary Structure](https://github.com/semantifyit/schema-org-adapter/blob/master/docu/vocabulary.md) 78 | #### [Conversion Algorithm for Vocabularies](https://github.com/semantifyit/schema-org-adapter/blob/master/docu/algorithm.md) 79 | -------------------------------------------------------------------------------- /src/data/namespaces.ts: -------------------------------------------------------------------------------- 1 | // Namespaces object 2 | /** @ignore */ 3 | export const NS = { 4 | xsd: { 5 | _url: "http://www.w3.org/2001/XMLSchema#", 6 | string: "xsd:string", 7 | decimal: "xsd:decimal", 8 | integer: "xsd:integer", 9 | float: "xsd:float", 10 | double: "xsd:double", 11 | boolean: "xsd:boolean", 12 | date: "xsd:date", 13 | time: "xsd:time", 14 | dateTime: "xsd:dateTime", 15 | anyURI: "xsd:anyURI" 16 | }, 17 | rdf: { 18 | _url: "http://www.w3.org/1999/02/22-rdf-syntax-ns#", 19 | property: "rdf:Property" 20 | }, 21 | rdfs: { 22 | _url: "http://www.w3.org/2000/01/rdf-schema#", 23 | class: "rdfs:Class", 24 | subClassOf: "rdfs:subClassOf", 25 | subPropertyOf: "rdfs:subPropertyOf", 26 | label: "rdfs:label", 27 | comment: "rdfs:comment" 28 | }, 29 | schema: { 30 | _url: "https://schema.org/", 31 | dataType: "schema:DataType", 32 | enumeration: "schema:Enumeration", 33 | isPartOf: "schema:isPartOf", 34 | domainIncludes: "schema:domainIncludes", 35 | rangeIncludes: "schema:rangeIncludes", 36 | supersededBy: "schema:supersededBy", 37 | inverseOf: "schema:inverseOf", 38 | source: "schema:source", 39 | category: "schema:category" 40 | }, 41 | dcterms: { 42 | _url: "http://purl.org/dc/terms/", 43 | source: "dcterms:source" 44 | }, 45 | soa: { 46 | _url: "http://schema-org-adapter.at/vocabTerms/", 47 | enumerationMember: "soa:EnumerationMember", 48 | superClassOf: "soa:superClassOf", 49 | superPropertyOf: "soa:superPropertyOf", 50 | hasProperty: "soa:hasProperty", 51 | isRangeOf: "soa:isRangeOf", 52 | hasEnumerationMember: "soa:hasEnumerationMember", 53 | enumerationDomainIncludes: "soa:enumerationDomainIncludes" 54 | }, 55 | ds: { 56 | _url: "https://vocab.sti2.at/ds/" 57 | } 58 | } as const; 59 | 60 | /** @ignore 61 | * The available types of terms as labels (human-readable name) 62 | */ 63 | export const TermTypeLabel = { 64 | class: "Class", 65 | property: "Property", 66 | enumeration: "Enumeration", 67 | enumerationMember: "EnumerationMember", 68 | dataType: "DataType" 69 | } as const; 70 | 71 | /** @ignore 72 | * The available types of terms as IRIs (computer readable name) 73 | */ 74 | export const TermTypeIRI = { 75 | class: NS.rdfs.class, 76 | property: NS.rdf.property, 77 | enumeration: NS.schema.enumeration, 78 | enumerationMember: NS.soa.enumerationMember, 79 | dataType: NS.schema.dataType 80 | } as const; 81 | 82 | /** @ignore 83 | * Term Types as simple (JS variable) strings 84 | */ 85 | export type TermType = keyof typeof TermTypeLabel; 86 | 87 | /** 88 | * A label (string) indicating the type of Term: 89 | * > {@link Class} -> `"Class"`
90 | * {@link Property} -> `"Property"`
91 | * {@link Enumeration} -> `"Enumeration"`
92 | * {@link EnumerationMember} -> `"EnumerationMember"`
93 | * {@link DataType} -> `"DataType"` 94 | * @privateRemarks 95 | * Type for values of TermTypeLabel 96 | */ 97 | export type TermTypeLabelValue = (typeof TermTypeLabel)[TermType]; 98 | 99 | export function isTermTypeLabelValue(value: string): value is TermTypeLabelValue { 100 | return Object.values(TermTypeLabel).includes(value as TermTypeLabelValue); 101 | } 102 | 103 | /** 104 | * An IRI (string) indicating the type of Term: 105 | * > {@link Class} -> `"rdfs:Class"`
106 | * {@link Property} -> `"rdf:Property"`
107 | * {@link Enumeration} -> `"schema:Enumeration"`
108 | * {@link EnumerationMember} -> `"soa:EnumerationMember"`
109 | * {@link DataType} -> `"schema:DataType"` 110 | * @privateRemarks 111 | * Type for values of TermTypeIRI 112 | */ 113 | export type TermTypeIRIValue = (typeof TermTypeIRI)[TermType]; 114 | 115 | export function isTermTypeIRIValue(value: string): value is TermTypeIRIValue { 116 | return Object.values(TermTypeIRI).includes(value as TermTypeIRIValue); 117 | } 118 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "schema-org-adapter", 3 | "version": "7.1.1", 4 | "engines": { 5 | "node": ">=10.0.0" 6 | }, 7 | "description": "Fast, simple & flexible API for the Schema.org Vocabulary (and vocabulary extensions!) for Node and Browsers", 8 | "main": "lib/index.js", 9 | "types": "lib/index.d.ts", 10 | "scripts": { 11 | "prebuild": "rimraf lib", 12 | "buildAll": "npm run-script prebuild && npm run-script buildTs && npm run-script buildDist && npm run-script buildDistMinified", 13 | "buildTs": "tsc -b src", 14 | "buildDist": "browserify lib/dist.js -p esmify -s SOA > dist/schema-org-adapter.js", 15 | "buildDistMinified": "browserify lib/dist.js -p esmify -s SOA | terser --compress --mangle > dist/schema-org-adapter.min.js", 16 | "testSemantifyRepo": "COMMIT=SEMANTIFY jest", 17 | "testSoaRepo": "jest", 18 | "testSchemaRepo": "COMMIT=d5ed5b8de39127c42cc7e252f242e4eb5e862996 jest", 19 | "testSchemaRepoInfra": "COMMIT=d5ed5b8de39127c42cc7e252f242e4eb5e862996 jest tests/classes/Infrastructure.test.ts", 20 | "buildTestExperimental1": "tsc tests/experimental/vocabulary-comparison.ts", 21 | "runTestExperimental1": "node tests/experimental/vocabulary-comparison.js", 22 | "eslint-check": "eslint src/**/*.ts tests/**/*.ts", 23 | "eslint-repair": "eslint --fix src/**/*.ts tests/**/*.ts", 24 | "prettier-check": "prettier --check src/**/*.ts tests/**/*.ts", 25 | "prettier-repair": "prettier --write src/**/*.ts tests/**/*.ts", 26 | "genTypeDoc": "typedoc", 27 | "watchTypeDoc": "typedoc --watch", 28 | "watchTs": "tsc -b src --watch", 29 | "vulnerabilities": "npm audit --audit-level=critical --registry=https://registry.npmjs.org --omit=dev" 30 | }, 31 | "author": { 32 | "name": "Omar J. A. Holzknecht for STI Innsbruck", 33 | "url": "https://semantify.it" 34 | }, 35 | "license": "Apache-2.0", 36 | "homepage": "https://semantifyit.github.io/schema-org-adapter/", 37 | "keywords": [ 38 | "schema.org", 39 | "adapter", 40 | "schema", 41 | "org", 42 | "converter", 43 | "sdo-adapter", 44 | "semantic web" 45 | ], 46 | "repository": { 47 | "type": "git", 48 | "url": "https://github.com/semantifyit/schema-org-adapter" 49 | }, 50 | "dependencies": { 51 | "axios": "^1.11.0", 52 | "jsonld": "^5.2.0" 53 | }, 54 | "devDependencies": { 55 | "@eslint/eslintrc": "^3.3.1", 56 | "@eslint/js": "^9.35.0", 57 | "@types/jest": "^30.0.0", 58 | "@types/jsonld": "^1.5.15", 59 | "@typescript-eslint/eslint-plugin": "^8.43.0", 60 | "@typescript-eslint/parser": "^8.43.0", 61 | "browserify": "^17.0.1", 62 | "deep-object-diff": "^1.1.9", 63 | "eslint": "^9.35.0", 64 | "eslint-config-prettier": "^10.1.8", 65 | "eslint-plugin-es": "^4.1.0", 66 | "eslint-plugin-jest": "^29.0.1", 67 | "eslint-plugin-prettier": "^5.5.4", 68 | "esmify": "^2.1.1", 69 | "globals": "^14.0.0", 70 | "jest": "^29.7.0", 71 | "prettier": "^3.6.2", 72 | "rimraf": "^6.0.1", 73 | "terser": "^4.8.1", 74 | "ts-jest": "^29.4.1", 75 | "typedoc": "^0.28.12", 76 | "typedoc-plugin-merge-modules": "^7.0.0", 77 | "typescript": "^5.9.2" 78 | }, 79 | "jest": { 80 | "preset": "ts-jest/presets/js-with-ts", 81 | "testEnvironment": "node", 82 | "testTimeout": 300000, 83 | "collectCoverage": true, 84 | "collectCoverageFrom": [ 85 | "src/**/*.ts" 86 | ], 87 | "coverageDirectory": "docu/coverage", 88 | "coverageReporters": [ 89 | "json-summary" 90 | ], 91 | "transform": { 92 | "^.+\\.tsx?$": [ 93 | "ts-jest", 94 | { 95 | "tsconfig": "./tests/tsconfig.json" 96 | } 97 | ] 98 | } 99 | }, 100 | "files": [ 101 | "lib/*", 102 | "dist/*", 103 | "README.md", 104 | "History.md", 105 | "LICENSE", 106 | "package.json" 107 | ], 108 | "prettier": { 109 | "printWidth": 120, 110 | "trailingComma": "none" 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /tests/resources/data/vocabularies/vocabulary-animal.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": { 3 | "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", 4 | "rdfs": "http://www.w3.org/2000/01/rdf-schema#", 5 | "xsd": "http://www.w3.org/2001/XMLSchema#", 6 | "schema": "https://schema.org/", 7 | "ex": "https://example-vocab.ex/", 8 | "rdfs:subClassOf": { 9 | "@id": "rdfs:subClassOf", 10 | "@type": "@id" 11 | }, 12 | "schema:domainIncludes": { 13 | "@id": "schema:domainIncludes", 14 | "@type": "@id" 15 | }, 16 | "schema:rangeIncludes": { 17 | "@id": "schema:rangeIncludes", 18 | "@type": "@id" 19 | } 20 | }, 21 | "@graph": [ 22 | { 23 | "@id": "ex:Animal", 24 | "@type": "rdfs:Class", 25 | "rdfs:comment": "Animals are multicellular eukaryotic organisms that form the biological kingdom Animalia. With few exceptions, animals consume organic material, breathe oxygen, are able to move, can reproduce sexually, and grow from a hollow sphere of cells, the blastula, during embryonic development.", 26 | "rdfs:label": "Animal", 27 | "rdfs:subClassOf": "schema:Thing" 28 | }, 29 | { 30 | "@id": "ex:numberOfLegs", 31 | "@type": "rdf:Property", 32 | "rdfs:comment": "The number of legs an animal has.", 33 | "rdfs:label": "numberOfLegs", 34 | "schema:domainIncludes": "ex:Animal", 35 | "schema:rangeIncludes": "schema:Integer" 36 | }, 37 | { 38 | "@id": "ex:animalLivingEnvironment", 39 | "@type": "rdf:Property", 40 | "rdfs:comment": "The living environment of an animal. As values for this property you can write plain text of choose from the following: 'https://example-vocab.ex/AnimalLivingEnvironmentFreedom', 'https://example-vocab.ex/AnimalLivingEnvironmentZoo', 'https://example-vocab.ex/AnimalLivingEnvironmentDomestic' ", 41 | "rdfs:label": "animalLivingEnvironment", 42 | "schema:domainIncludes": "ex:Animal", 43 | "schema:rangeIncludes": [ 44 | "schema:Text", 45 | "ex:AnimalLivingEnvironment" 46 | ] 47 | }, 48 | { 49 | "@id": "ex:AnimalLivingEnvironment", 50 | "@type": "rdfs:Class", 51 | "rdfs:comment": "An enumeration class that lists the possible living environments an animal can have.", 52 | "rdfs:label": "AnimalLivingEnvironment", 53 | "rdfs:subClassOf": "schema:Enumeration" 54 | }, 55 | { 56 | "@id": "ex:AnimalLivingEnvironmentFreedom", 57 | "@type": "ex:AnimalLivingEnvironment", 58 | "rdfs:comment": "The animal lives in freedom.", 59 | "rdfs:label": "AnimalLivingEnvironmentFreedom" 60 | }, 61 | { 62 | "@id": "ex:AnimalLivingEnvironmentZoo", 63 | "@type": "ex:AnimalLivingEnvironment", 64 | "rdfs:comment": "The animal lives in a public zoo.", 65 | "rdfs:label": "AnimalLivingEnvironmentZoo" 66 | }, 67 | { 68 | "@id": "ex:AnimalLivingEnvironmentDomestic", 69 | "@type": "ex:AnimalLivingEnvironment", 70 | "rdfs:comment": "The animal lives in a private domestic environment.", 71 | "rdfs:label": "AnimalLivingEnvironmentDomestic" 72 | }, 73 | { 74 | "@id": "ex:Tiger", 75 | "@type": "rdfs:Class", 76 | "rdfs:comment": [ 77 | { 78 | "@language": "en", 79 | "@value": "The tiger (Panthera tigris) is the largest species among the Felidae and classified in the genus Panthera. It is most recognisable for its dark vertical stripes on orangish-brown fur with a lighter underside." 80 | }, 81 | { 82 | "@language": "zh", 83 | "@value": "虎(学名:Panthera tigris),俗称老虎、大虫,被人称为百獸之王,是現存体型最大的两种猫科动物之一(另一种是狮)。野外個體可長達3.38米(11.1英尺)、重388.7公斤(857英磅)(此为狩猎数据,实测数据的最大值为261kg)" 84 | } 85 | ], 86 | "rdfs:label": [ 87 | { 88 | "@language": "en", 89 | "@value": "Tiger" 90 | }, 91 | { 92 | "@language": "de", 93 | "@value": "Tiger" 94 | }, 95 | { 96 | "@language": "es", 97 | "@value": "Tigre" 98 | }, 99 | { 100 | "@language": "zh", 101 | "@value": "虎" 102 | } 103 | ], 104 | "rdfs:subClassOf": "ex:Animal" 105 | } 106 | ] 107 | } -------------------------------------------------------------------------------- /tests/resources/data/vocabularies/vocabulary-animal-2.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": { 3 | "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", 4 | "rdfs": "http://www.w3.org/2000/01/rdf-schema#", 5 | "xsd": "http://www.w3.org/2001/XMLSchema#", 6 | "ex": "https://example-vocab.ex/" 7 | }, 8 | "@graph": [ 9 | { 10 | "@id": "ex:Animal", 11 | "@type": "rdfs:Class", 12 | "rdfs:comment": "Animals are multicellular eukaryotic organisms that form the biological kingdom Animalia. With few exceptions, animals consume organic material, breathe oxygen, are able to move, can reproduce sexually, and grow from a hollow sphere of cells, the blastula, during embryonic development.", 13 | "rdfs:label": "Animal", 14 | "rdfs:subClassOf": { 15 | "@id": "https://schema.org/Thing" 16 | } 17 | }, 18 | { 19 | "@id": "ex:numberOfLegs", 20 | "@type": "rdf:Property", 21 | "rdfs:comment": "The number of legs an animal has.", 22 | "rdfs:label": "numberOfLegs", 23 | "https://schema.org/domainIncludes": { 24 | "@id": "ex:Animal" 25 | }, 26 | "https://schema.org/rangeIncludes": { 27 | "@id": "https://schema.org/Integer" 28 | } 29 | }, 30 | { 31 | "@id": "ex:animalLivingEnvironment", 32 | "@type": "rdf:Property", 33 | "rdfs:comment": "The living environment of an animal. As values for this property you can write plain text of choose from the following: 'https://example-vocab.ex/AnimalLivingEnvironmentFreedom', 'https://example-vocab.ex/AnimalLivingEnvironmentZoo', 'https://example-vocab.ex/AnimalLivingEnvironmentDomestic' ", 34 | "rdfs:label": "animalLivingEnvironment", 35 | "https://schema.org/domainIncludes": { 36 | "@id": "ex:Animal" 37 | }, 38 | "https://schema.org/rangeIncludes": [ 39 | { 40 | "@id": "https://schema.org/Text" 41 | }, 42 | { 43 | "@id": "ex:AnimalLivingEnvironment" 44 | } 45 | ] 46 | }, 47 | { 48 | "@id": "ex:AnimalLivingEnvironment", 49 | "@type": "rdfs:Class", 50 | "rdfs:comment": "An enumeration class that lists the possible living environments an animal can have.", 51 | "rdfs:label": "AnimalLivingEnvironment", 52 | "rdfs:subClassOf": { 53 | "@id": "https://schema.org/Enumeration" 54 | } 55 | }, 56 | { 57 | "@id": "ex:AnimalLivingEnvironmentFreedom", 58 | "@type": "ex:AnimalLivingEnvironment", 59 | "rdfs:comment": "The animal lives in freedom.", 60 | "rdfs:label": "AnimalLivingEnvironmentFreedom" 61 | }, 62 | { 63 | "@id": "ex:AnimalLivingEnvironmentZoo", 64 | "@type": "ex:AnimalLivingEnvironment", 65 | "rdfs:comment": "The animal lives in a public zoo.", 66 | "rdfs:label": "AnimalLivingEnvironmentZoo" 67 | }, 68 | { 69 | "@id": "ex:AnimalLivingEnvironmentDomestic", 70 | "@type": "ex:AnimalLivingEnvironment", 71 | "rdfs:comment": "The animal lives in a private domestic environment.", 72 | "rdfs:label": "AnimalLivingEnvironmentDomestic" 73 | }, 74 | { 75 | "@id": "ex:Tiger", 76 | "@type": "rdfs:Class", 77 | "rdfs:comment": [ 78 | { 79 | "@language": "en", 80 | "@value": "The tiger (Panthera tigris) is the largest species among the Felidae and classified in the genus Panthera. It is most recognisable for its dark vertical stripes on orangish-brown fur with a lighter underside." 81 | }, 82 | { 83 | "@language": "zh", 84 | "@value": "虎(学名:Panthera tigris),俗称老虎、大虫,被人称为百獸之王,是現存体型最大的两种猫科动物之一(另一种是狮)。野外個體可長達3.38米(11.1英尺)、重388.7公斤(857英磅)(此为狩猎数据,实测数据的最大值为261kg)" 85 | } 86 | ], 87 | "rdfs:label": [ 88 | { 89 | "@language": "en", 90 | "@value": "Tiger" 91 | }, 92 | { 93 | "@language": "de", 94 | "@value": "Tiger" 95 | }, 96 | { 97 | "@language": "es", 98 | "@value": "Tigre" 99 | }, 100 | { 101 | "@language": "zh", 102 | "@value": "虎" 103 | } 104 | ], 105 | "rdfs:subClassOf": { 106 | "@id": "ex:Animal" 107 | } 108 | } 109 | ] 110 | } -------------------------------------------------------------------------------- /docu/examples/comparing-schema-versions.js: -------------------------------------------------------------------------------- 1 | const { SOA } = require("../../lib/index"); // run the npm-script "buildTs" to generate js files for this example 2 | 3 | main(); 4 | 5 | // compare the terms from different schema.org vocabulary versions 6 | async function main() { 7 | const v1 = "29.1"; // 3.1 is the lowest version possible 8 | const commit1 = "4d57f1f4d8dba156d40509c52d84624912bcafe0"; 9 | const v2 = "29.3"; 10 | const commit2 = "d5ed5b8de39127c42cc7e252f242e4eb5e862996"; 11 | console.log("Comparing version " + v1 + " from commit " + commit1 + " with version " + v2 + " from commit " + commit2); 12 | const mySa1 = await SOA.create({ schemaVersion: v1, commit: commit1 }); 13 | const mySa2 = await SOA.create({ schemaVersion: v2, commit: commit2 }); 14 | const classesV1 = mySa1.getListOfClasses(); 15 | const classesV2 = mySa2.getListOfClasses(); 16 | console.log("#Classes " + v1 + ": " + classesV1.length); 17 | console.log("#Classes " + v2 + ": " + classesV2.length); 18 | 19 | const propertiesV1 = mySa1.getListOfProperties(); 20 | const propertiesV2 = mySa2.getListOfProperties(); 21 | console.log("#Properties " + v1 + ": " + propertiesV1.length); 22 | console.log("#Properties " + v2 + ": " + propertiesV2.length); 23 | 24 | const enumsV1 = mySa1.getListOfEnumerations(); 25 | const enumsV2 = mySa2.getListOfEnumerations(); 26 | console.log("#Enumerations " + v1 + ": " + enumsV1.length); 27 | console.log("#Enumerations " + v2 + ": " + enumsV2.length); 28 | 29 | const enumsMembersV1 = mySa1.getListOfEnumerationMembers(); 30 | const enumsMembersV2 = mySa2.getListOfEnumerationMembers(); 31 | console.log("#Enumeration Members " + v1 + ": " + enumsMembersV1.length); 32 | console.log("#Enumeration Members " + v2 + ": " + enumsMembersV2.length); 33 | 34 | const dataTypesV1 = mySa1.getListOfDataTypes(); 35 | const dataTypesV2 = mySa2.getListOfDataTypes(); 36 | console.log("#DataTypes " + v1 + ": " + dataTypesV1.length); 37 | console.log("#DataTypes " + v2 + ": " + dataTypesV2.length); 38 | 39 | // terms that have been deleted in newer versions 40 | console.log("Deleted Classes:", classesV1.filter(c => !classesV2.includes(c))); 41 | console.log("Deleted Properties:", propertiesV1.filter(c => !propertiesV2.includes(c))); 42 | console.log("Deleted Enumerations:", enumsV1.filter(c => !enumsV2.includes(c))); 43 | console.log("Deleted Enumeration Members:", enumsMembersV1.filter(c => !enumsMembersV2.includes(c))); 44 | console.log("Deleted DataTypes:", dataTypesV1.filter(c => !dataTypesV2.includes(c))); 45 | 46 | // classes with properties that they are no longer allowed to use 47 | for (const c of classesV1) { 48 | try { 49 | const ci12 = mySa1.getClass(c); 50 | const ciLatest = mySa2.getClass(c); 51 | const ci12Properties = ci12.getProperties(); 52 | const ciLatestProperties = ciLatest.getProperties(); 53 | const deletedProps = ci12Properties.filter(p => !ciLatestProperties.includes(p)); 54 | if (deletedProps.length > 0) { 55 | console.log("Deleted Properties for " + c + ":", deletedProps); 56 | } 57 | } catch (e) { 58 | 59 | } 60 | 61 | } 62 | 63 | // properties with ranges that they are no longer allowed to use 64 | for (const p of propertiesV1) { 65 | try { 66 | const pi12 = mySa1.getProperty(p); 67 | const piLatest = mySa2.getProperty(p); 68 | const pi12Ranges = pi12.getRanges(); 69 | const piLatestRanges = piLatest.getRanges(); 70 | const deletedRanges = pi12Ranges.filter(p => !piLatestRanges.includes(p)); 71 | if (deletedRanges.length > 0) { 72 | console.log("Deleted Ranges for " + p + ":", deletedRanges); 73 | } 74 | } catch (e) { } 75 | } 76 | 77 | // enumerations with an enumeration-hierarchy with more than 2 levels (PaymentMethod was - before 28.0 - the only enumeration that had 2 further levels below) 78 | for (const e of enumsV2) { 79 | try { 80 | const ei = mySa2.getEnumeration(e); 81 | const subClassesImplicit = ei.getSubClasses({ implicit: true }); 82 | const subClassesExplicit = ei.getSubClasses({ implicit: false }); 83 | if (subClassesImplicit.length > 0) { 84 | if (subClassesExplicit.length !== subClassesImplicit.length) { 85 | console.log(e + " has explicit subclasses: " + JSON.stringify(subClassesExplicit)); 86 | console.log(e + " has implicit subclasses: " + JSON.stringify(subClassesImplicit)); 87 | } 88 | } 89 | } catch (e) { } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /docs/assets/highlight.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --light-hl-0: #0000FF; 3 | --dark-hl-0: #569CD6; 4 | --light-hl-1: #000000; 5 | --dark-hl-1: #D4D4D4; 6 | --light-hl-2: #0070C1; 7 | --dark-hl-2: #4FC1FF; 8 | --light-hl-3: #795E26; 9 | --dark-hl-3: #DCDCAA; 10 | --light-hl-4: #A31515; 11 | --dark-hl-4: #CE9178; 12 | --light-hl-5: #008000; 13 | --dark-hl-5: #6A9955; 14 | --light-hl-6: #AF00DB; 15 | --dark-hl-6: #C586C0; 16 | --light-hl-7: #001080; 17 | --dark-hl-7: #9CDCFE; 18 | --light-hl-8: #800000; 19 | --dark-hl-8: #808080; 20 | --light-hl-9: #800000; 21 | --dark-hl-9: #569CD6; 22 | --light-hl-10: #000000FF; 23 | --dark-hl-10: #D4D4D4; 24 | --light-hl-11: #E50000; 25 | --dark-hl-11: #9CDCFE; 26 | --light-hl-12: #0000FF; 27 | --dark-hl-12: #CE9178; 28 | --light-hl-13: #000000; 29 | --dark-hl-13: #C8C8C8; 30 | --light-hl-14: #EE0000; 31 | --dark-hl-14: #D7BA7D; 32 | --light-hl-15: #CD3131; 33 | --dark-hl-15: #F44747; 34 | --light-code-background: #FFFFFF; 35 | --dark-code-background: #1E1E1E; 36 | } 37 | 38 | @media (prefers-color-scheme: light) { :root { 39 | --hl-0: var(--light-hl-0); 40 | --hl-1: var(--light-hl-1); 41 | --hl-2: var(--light-hl-2); 42 | --hl-3: var(--light-hl-3); 43 | --hl-4: var(--light-hl-4); 44 | --hl-5: var(--light-hl-5); 45 | --hl-6: var(--light-hl-6); 46 | --hl-7: var(--light-hl-7); 47 | --hl-8: var(--light-hl-8); 48 | --hl-9: var(--light-hl-9); 49 | --hl-10: var(--light-hl-10); 50 | --hl-11: var(--light-hl-11); 51 | --hl-12: var(--light-hl-12); 52 | --hl-13: var(--light-hl-13); 53 | --hl-14: var(--light-hl-14); 54 | --hl-15: var(--light-hl-15); 55 | --code-background: var(--light-code-background); 56 | } } 57 | 58 | @media (prefers-color-scheme: dark) { :root { 59 | --hl-0: var(--dark-hl-0); 60 | --hl-1: var(--dark-hl-1); 61 | --hl-2: var(--dark-hl-2); 62 | --hl-3: var(--dark-hl-3); 63 | --hl-4: var(--dark-hl-4); 64 | --hl-5: var(--dark-hl-5); 65 | --hl-6: var(--dark-hl-6); 66 | --hl-7: var(--dark-hl-7); 67 | --hl-8: var(--dark-hl-8); 68 | --hl-9: var(--dark-hl-9); 69 | --hl-10: var(--dark-hl-10); 70 | --hl-11: var(--dark-hl-11); 71 | --hl-12: var(--dark-hl-12); 72 | --hl-13: var(--dark-hl-13); 73 | --hl-14: var(--dark-hl-14); 74 | --hl-15: var(--dark-hl-15); 75 | --code-background: var(--dark-code-background); 76 | } } 77 | 78 | :root[data-theme='light'] { 79 | --hl-0: var(--light-hl-0); 80 | --hl-1: var(--light-hl-1); 81 | --hl-2: var(--light-hl-2); 82 | --hl-3: var(--light-hl-3); 83 | --hl-4: var(--light-hl-4); 84 | --hl-5: var(--light-hl-5); 85 | --hl-6: var(--light-hl-6); 86 | --hl-7: var(--light-hl-7); 87 | --hl-8: var(--light-hl-8); 88 | --hl-9: var(--light-hl-9); 89 | --hl-10: var(--light-hl-10); 90 | --hl-11: var(--light-hl-11); 91 | --hl-12: var(--light-hl-12); 92 | --hl-13: var(--light-hl-13); 93 | --hl-14: var(--light-hl-14); 94 | --hl-15: var(--light-hl-15); 95 | --code-background: var(--light-code-background); 96 | } 97 | 98 | :root[data-theme='dark'] { 99 | --hl-0: var(--dark-hl-0); 100 | --hl-1: var(--dark-hl-1); 101 | --hl-2: var(--dark-hl-2); 102 | --hl-3: var(--dark-hl-3); 103 | --hl-4: var(--dark-hl-4); 104 | --hl-5: var(--dark-hl-5); 105 | --hl-6: var(--dark-hl-6); 106 | --hl-7: var(--dark-hl-7); 107 | --hl-8: var(--dark-hl-8); 108 | --hl-9: var(--dark-hl-9); 109 | --hl-10: var(--dark-hl-10); 110 | --hl-11: var(--dark-hl-11); 111 | --hl-12: var(--dark-hl-12); 112 | --hl-13: var(--dark-hl-13); 113 | --hl-14: var(--dark-hl-14); 114 | --hl-15: var(--dark-hl-15); 115 | --code-background: var(--dark-code-background); 116 | } 117 | 118 | .hl-0 { color: var(--hl-0); } 119 | .hl-1 { color: var(--hl-1); } 120 | .hl-2 { color: var(--hl-2); } 121 | .hl-3 { color: var(--hl-3); } 122 | .hl-4 { color: var(--hl-4); } 123 | .hl-5 { color: var(--hl-5); } 124 | .hl-6 { color: var(--hl-6); } 125 | .hl-7 { color: var(--hl-7); } 126 | .hl-8 { color: var(--hl-8); } 127 | .hl-9 { color: var(--hl-9); } 128 | .hl-10 { color: var(--hl-10); } 129 | .hl-11 { color: var(--hl-11); } 130 | .hl-12 { color: var(--hl-12); } 131 | .hl-13 { color: var(--hl-13); } 132 | .hl-14 { color: var(--hl-14); } 133 | .hl-15 { color: var(--hl-15); } 134 | pre, code { background: var(--code-background); } 135 | -------------------------------------------------------------------------------- /src/utilities/graph/discoverEquateNamespaces.ts: -------------------------------------------------------------------------------- 1 | import { Context, Vocabulary } from "../../types/types"; 2 | import { isString } from "../general/isString"; 3 | import { switchIRIProtocol } from "../general/switchIRIProtocol"; 4 | import { NS } from "../../data/namespaces"; 5 | import { checkIfNamespaceFromListIsUsed } from "./checkIfNamespaceFromListIsUsed"; 6 | 7 | /** @ignore 8 | * Checks if the given vocabulary uses terms (in context or content) that are present in the current given context but with another protocol (http/https), and returns those in a list 9 | * 10 | * @param currentContext - the current context 11 | * @param vocabulary - the vocabulary to be analyzed 12 | * @returns an array with the found equate namespaces 13 | */ 14 | export function discoverEquateNamespaces( 15 | currentContext: Context, 16 | vocabulary: Vocabulary 17 | ): string[] { 18 | const result: Set = new Set(); 19 | // 1. Make List of protocol switched namespaces from the current context 20 | const protocolSwitchedNamespaces: string[] = []; 21 | Object.values(currentContext).forEach(function (el) { 22 | if (isString(el)) { 23 | protocolSwitchedNamespaces.push(switchIRIProtocol(el)); 24 | } 25 | }); 26 | // 2. Look in vocabulary context if any protocol switched namespaces are present 27 | if (vocabulary["@context"]) { 28 | Object.values(vocabulary["@context"]).forEach(function (el) { 29 | if (isString(el) && protocolSwitchedNamespaces.includes(el)) { 30 | result.add(el); 31 | } 32 | }); 33 | } 34 | // 3. Look in vocabulary content if any protocol switched namespaces are present (everywhere, where @ids are expected) 35 | if (Array.isArray(vocabulary["@graph"])) { 36 | vocabulary["@graph"].forEach(function (vocabNode) { 37 | checkIfNamespaceFromListIsUsed( 38 | vocabNode["@id"], 39 | protocolSwitchedNamespaces, 40 | result 41 | ); 42 | checkIfNamespaceFromListIsUsed( 43 | vocabNode["@type"], 44 | protocolSwitchedNamespaces, 45 | result 46 | ); 47 | // super class 48 | checkIfNamespaceFromListIsUsed( 49 | vocabNode[NS.rdfs.subClassOf], 50 | protocolSwitchedNamespaces, 51 | result 52 | ); 53 | checkIfNamespaceFromListIsUsed( 54 | vocabNode["http://www.w3.org/2000/01/rdf-schema#subClassOf"], 55 | protocolSwitchedNamespaces, 56 | result 57 | ); 58 | // domain class 59 | checkIfNamespaceFromListIsUsed( 60 | vocabNode[NS.schema.domainIncludes], 61 | protocolSwitchedNamespaces, 62 | result 63 | ); 64 | checkIfNamespaceFromListIsUsed( 65 | vocabNode["http://schema.org/domainIncludes"], 66 | protocolSwitchedNamespaces, 67 | result 68 | ); 69 | checkIfNamespaceFromListIsUsed( 70 | vocabNode["https://schema.org/domainIncludes"], 71 | protocolSwitchedNamespaces, 72 | result 73 | ); 74 | // range class 75 | checkIfNamespaceFromListIsUsed( 76 | vocabNode[NS.schema.rangeIncludes], 77 | protocolSwitchedNamespaces, 78 | result 79 | ); 80 | checkIfNamespaceFromListIsUsed( 81 | vocabNode["http://schema.org/rangeIncludes"], 82 | protocolSwitchedNamespaces, 83 | result 84 | ); 85 | checkIfNamespaceFromListIsUsed( 86 | vocabNode["https://schema.org/rangeIncludes"], 87 | protocolSwitchedNamespaces, 88 | result 89 | ); 90 | // super property 91 | checkIfNamespaceFromListIsUsed( 92 | vocabNode[NS.rdfs.subPropertyOf], 93 | protocolSwitchedNamespaces, 94 | result 95 | ); 96 | checkIfNamespaceFromListIsUsed( 97 | vocabNode["http://www.w3.org/2000/01/rdf-schema#subPropertyOf"], 98 | protocolSwitchedNamespaces, 99 | result 100 | ); 101 | // inverse property 102 | checkIfNamespaceFromListIsUsed( 103 | vocabNode[NS.schema.inverseOf], 104 | protocolSwitchedNamespaces, 105 | result 106 | ); 107 | checkIfNamespaceFromListIsUsed( 108 | vocabNode["http://schema.org/inverseOf"], 109 | protocolSwitchedNamespaces, 110 | result 111 | ); 112 | checkIfNamespaceFromListIsUsed( 113 | vocabNode["https://schema.org/inverseOf"], 114 | protocolSwitchedNamespaces, 115 | result 116 | ); 117 | }); 118 | } 119 | return Array.from(result); 120 | } 121 | -------------------------------------------------------------------------------- /src/types/FilterObject.type.ts: -------------------------------------------------------------------------------- 1 | import { TermTypeLabelValue } from "../data/namespaces"; 2 | import { SchemaModule } from "../data/schemaModules"; 3 | 4 | /** 5 | * SDO-Adapter provides various query-functions that accept a **FilterObject** as an optional parameter to narrow down the results. These query-functions typically return arrays of IRIs for vocabulary terms. You could for example pass a filter to {@link SDOAdapter.getAllClasses | .getAllClasses()} to retrieve only classes from a specific vocabulary. You could pass a filter to {@link Property.getRanges | .getRanges()} to get only the ranges of a property that are enumerations (without classes or data-types). The optional attributes for a filter are described below. Keep in mind that there are **no default values** for these filters, that means that **not** using a certain attribute in the FilterObject results in **not** using that characteristic for filtering at all. 6 | * 7 | * @example 8 | * ```JS 9 | * // following filter can be passed to a function to retrieve only classes and enumerations that are from schema.org and are not superseded 10 | * { 11 | * isSuperseded: false, 12 | * fromVocabulary: ["https://schema.org/"], 13 | * termType: [ 14 | * "Class", 15 | * "Enumeration" 16 | * ] 17 | * } 18 | * 19 | * // the following filter can be passed to a function to retrieve only terms that are superseded 20 | * { 21 | * isSuperseded: true 22 | * } 23 | * ``` 24 | */ 25 | export type FilterObject = { 26 | /** 27 | * If true, only [superseded vocabulary terms](https://schema.org/supersededBy) are matched. If false, only vocabulary terms that are NOT superseded are matched. 28 | */ 29 | isSuperseded?: boolean; 30 | 31 | /** 32 | * Namespaces for vocabularies (e.g. `"https://schema.org/"`) can be passed here, which matches only vocabulary terms that use any of the given namespaces in their IRI. You can check the namespaces and corresponding identifiers (e.g. `"schema"`) used by an {@link SDOAdapter | SDO-Adapter} instance with {@link SDOAdapter.getVocabularies | .getVocabularies()}. It is also possible to get the corresponding namespace of Term instance with {@link Class.getVocabulary | .getVocabulary()}. 33 | */ 34 | fromVocabulary?: string | string[]; 35 | 36 | /** 37 | * Namespaces for vocabularies (e.g. `"https://schema.org/"`) can be passed here to sort out vocabulary terms with the given namespaces in their IRI. This filter is the opposite of `fromVocabulary` and they rule each other out (only one of them can be used at the same time). 38 | */ 39 | fromVocabularyExclude?: string | string[]; 40 | 41 | /** 42 | * {@link TermTypeLabelValue | Term types} can be passed here, which matches only vocabulary terms that have any of the given term types. `Class` and `Enumeration` are strictly differentiated here. 43 | */ 44 | termType?: TermTypeLabelValue | TermTypeLabelValue[]; 45 | 46 | /** 47 | * {@link TermTypeLabelValue | Term types} can be passed here to sort out vocabulary terms from the given term types. `Class` and `Enumeration` are strictly differentiated here. This filter is the opposite of `termType` and they rule each other out (only one of them can be used at the same time). 48 | */ 49 | termTypeExclude?: TermTypeLabelValue | TermTypeLabelValue[]; 50 | 51 | /** 52 | * {@link SchemaModule | Schema module identifiers} can be passed here, which matches only vocabulary terms from schema.org that are from any of the given schema modules. This filter applies only to schema.org terms, terms from other vocabularies are not filtered in/out. More information about the schema modules can be found in [Organization of Schemas - Hosted Sections](https://schema.org/docs/schemas.html) 53 | */ 54 | schemaModule?: SchemaModule | SchemaModule[]; 55 | 56 | /** 57 | * {@link SchemaModule | Schema module identifiers} can be passed here to sort out vocabulary terms from the given schema modules. This filter is the opposite of `schemaModule` and they rule each other out (only one of them can be used at the same time). More information about the schema modules can be found in [Organization of Schemas - Hosted Sections](https://schema.org/docs/schemas.html) 58 | */ 59 | schemaModuleExclude?: SchemaModule | SchemaModule[]; 60 | 61 | /** 62 | * The strictMode defines how IRIs of non-present terms (e.g. a Term that is linked by a vocabulary node, but is itself not present in the current vocabulary) should be handled. Some filters (e.g. termType) require term instances to do their checks. If the strictMode is true (default), then such IRIs are filtered out. If the strictMode is false then those IRIs are kept in the result. 63 | */ 64 | strictMode?: boolean; 65 | }; 66 | -------------------------------------------------------------------------------- /docu/examples/example-node-external-vocabulary.js: -------------------------------------------------------------------------------- 1 | const { SOA } = require("../../lib/index"); // run the npm-script "buildTs" to generate js files for this example 2 | const VOC_EXAMPLE = require("../../tests/resources/data/vocabularies/vocabulary-animal.json"); // load our external vocabulary 3 | 4 | main(); 5 | 6 | /** 7 | * example usage of the SDOAdapter in node.js 8 | */ 9 | async function main() { 10 | console.log(await SOA.fetchSchemaVersions()) 11 | // create an instance of the SDOAdapter with the latest schema.org vocabulary and the example vocabulary 12 | const mySA = await SOA.create({schemaVersion: "latest", vocabularies:[VOC_EXAMPLE], equateVocabularyProtocols: true}); 13 | let AnimalClass = mySA.getClass("ex:Animal"); // get a JS-Class for the Class-Term https://example-vocab.ex/Animal , note that the compacted version of the IRI is also a valid parameter for the function 14 | console.log(JSON.stringify(AnimalClass.toJSON(), null, 2)); // AnimalClass.toJSON() prints a JSON version of the Class, note that here we pass 'true' as parameter for the reasoning (the result will contain attributes resolved though reasoning, e.g. properties of superclasses) 15 | /* 16 | { 17 | "id": "ex:Animal", 18 | "IRI": "https://example-vocab.ex/Animal", 19 | "type": "rdfs:Class", 20 | "vocabulary": "https://example-vocab.ex", 21 | "source": null, 22 | "supersededBy": null, 23 | "name": "Animal", 24 | "description": "Animals are multicellular eukaryotic organisms that form the biological kingdom Animalia. With few exceptions, animals consume organic material, breathe oxygen, are able to move, can reproduce sexually, and grow from a hollow sphere of cells, the blastula, during embryonic development.", 25 | "superClasses": [ 26 | "schema:Thing" 27 | ], 28 | "subClasses": [ 29 | "ex:Tiger" 30 | ], 31 | "properties": [ 32 | "ex:numberOfLegs", 33 | "ex:animalLivingEnvironment", 34 | "schema:sameAs", 35 | "schema:url", 36 | "schema:image", 37 | "schema:additionalType", 38 | "schema:name", 39 | "schema:identifier", 40 | "schema:subjectOf", 41 | "schema:mainEntityOfPage", 42 | "schema:potentialAction", 43 | "schema:description", 44 | "schema:disambiguatingDescription", 45 | "schema:alternateName" 46 | ] 47 | } 48 | */ 49 | let TigerClass = mySA.getClass("ex:Tiger"); 50 | console.log(JSON.stringify(TigerClass.toJSON(), null, 2)); 51 | /* 52 | { 53 | "id": "ex:Tiger", 54 | "IRI": "https://example-vocab.ex/Tiger", 55 | "type": "rdfs:Class", 56 | "vocabulary": "https://example-vocab.ex", 57 | "source": null, 58 | "supersededBy": null, 59 | "name": "Tiger", 60 | "description": "The tiger (Panthera tigris) is the largest species among the Felidae and classified in the genus Panthera. It is most recognisable for its dark vertical stripes on orangish-brown fur with a lighter underside.", 61 | "superClasses": [ 62 | "ex:Animal", 63 | "schema:Thing" 64 | ], 65 | "subClasses": [], 66 | "properties": [ 67 | "ex:numberOfLegs", 68 | "ex:animalLivingEnvironment", 69 | "schema:sameAs", 70 | "schema:url", 71 | "schema:image", 72 | "schema:additionalType", 73 | "schema:name", 74 | "schema:identifier", 75 | "schema:subjectOf", 76 | "schema:mainEntityOfPage", 77 | "schema:potentialAction", 78 | "schema:description", 79 | "schema:disambiguatingDescription", 80 | "schema:alternateName" 81 | ] 82 | } 83 | */ 84 | console.log(TigerClass.getName()); // print the name. When no language tag is given as parameter, then "en" for english is taken as default 85 | /* Tiger */ 86 | console.log(TigerClass.getName("es")); // print the spanish name 87 | /* Tigre */ 88 | console.log(TigerClass.getName("zh")); // print the chinese (zh) name 89 | /* 虎 */ 90 | 91 | let AnimalLivingEnvironmentEnumeration = mySA.getEnumeration( 92 | "ex:AnimalLivingEnvironment" 93 | ); // get JS-Class for the Enumeration-Term "https://example-vocab.ex/AnimalLivingEnvironment" 94 | console.log( 95 | JSON.stringify( 96 | AnimalLivingEnvironmentEnumeration.getEnumerationMembers(), 97 | null, 98 | 2 99 | ) 100 | ); // print all EnumerationMembers of the Enumeration 101 | /* 102 | [ 103 | "ex:AnimalLivingEnvironmentFreedom", 104 | "ex:AnimalLivingEnvironmentZoo", 105 | "ex:AnimalLivingEnvironmentDomestic" 106 | ] 107 | */ 108 | } 109 | -------------------------------------------------------------------------------- /tests/resources/utilities/testUtilities.ts: -------------------------------------------------------------------------------- 1 | import { SOA, ParamObjCreateSdoAdapter, SDOAdapter, VersionsFile, VersionsFileSemantify } from "../../../src"; 2 | import VOC_OBJ_ZOO from "../data/vocabularies/vocabulary-animal.json"; 3 | import { isString } from "../../../src/utilities/general/isString"; 4 | import { SEMANTIFY_COMMIT } from "../../../src/data/semantify"; 5 | import { constructURLSchemaVocabulary } from "../../../src/classes/Infrastructure"; 6 | 7 | export type SdoAdapterMap = Record; 8 | 9 | const CONSOLE_OUTPUT = false; // Change to "true" if you want to see console.log and console.error outputs for the tests 10 | 11 | /** 12 | * console.log() output, only if CONSOLE_OUTPUT is set to "true" in file testUtility.js 13 | * 14 | * @param {string} out - the output string 15 | */ 16 | export function debugFunc(out: unknown) { 17 | if (CONSOLE_OUTPUT) { 18 | console.log(out); 19 | } 20 | } 21 | 22 | /** 23 | * console.error() output, only if CONSOLE_OUTPUT is set to "true" in file testUtility.js 24 | * 25 | * @param {string} out - the output string 26 | */ 27 | export function debugFuncErr(out: unknown) { 28 | if (CONSOLE_OUTPUT) { 29 | console.error(out); 30 | } 31 | } 32 | 33 | /** 34 | * executes the given test function for each SDO-Adapter in the given SDO-Adapter-Map 35 | * @param sdoAdapterMap - the SDO Adapter map with SDO-Adapter for all schema.org versions 36 | * @param fn - the test function to be tested 37 | */ 38 | export async function executeTestForEach( 39 | sdoAdapterMap: SdoAdapterMap, 40 | // eslint-disable-next-line no-unused-vars 41 | fn: (sdoAdapter: SDOAdapter) => void 42 | ) { 43 | for (const v of Object.keys(sdoAdapterMap)) { 44 | // console.log("schema version " + v); 45 | fn(sdoAdapterMap[v]); 46 | } 47 | } 48 | 49 | // https://github.com/schemaorg/schemaorg/commits/main 50 | export const commit = process.env.COMMIT; 51 | 52 | /** 53 | * returns the initialized SDO-Adapter ready for testing 54 | */ 55 | export async function testSdoAdapter(params: Partial = {}) { 56 | return SOA.create({ 57 | commit, 58 | onError: debugFuncErr, 59 | schemaVersion: "latest", 60 | vocabularies: [], 61 | outputFormat: "Compact", 62 | ...params 63 | }); 64 | } 65 | 66 | /** 67 | * creates an SDO-Adapter for testing for each version of schema.org available 68 | * (includes also the custom Zoo Vocabulary to test external vocabularies) 69 | */ 70 | export async function initializeSdoAdapterMap() { 71 | const sdoAdapterMap: SdoAdapterMap = {}; 72 | const schemaVersions: string[] = []; // array of schema version strings, e.g. "15.0" 73 | if(commit === SEMANTIFY_COMMIT){ 74 | const versionFile = (await SOA.fetchSchemaVersions(false, commit)) as VersionsFileSemantify 75 | schemaVersions.push(...versionFile.all.map(item => item.schemaVersion)) 76 | } else { 77 | const releaseLog = ((await SOA.fetchSchemaVersions(false, commit)) as VersionsFile).releaseLog; 78 | schemaVersions.push(...Object.keys(releaseLog)) 79 | } 80 | // console.log("schemaVersions",schemaVersions) 81 | for (const v of schemaVersions) { 82 | try { 83 | // this is supposed to throw an error for versions that are not supported in the current test scenario 84 | await constructURLSchemaVocabulary(v,true, commit); 85 | sdoAdapterMap[v] = await testSdoAdapter({ 86 | vocabularies: [VOC_OBJ_ZOO], 87 | schemaVersion: v, 88 | equateVocabularyProtocols: true 89 | }); 90 | } catch (e) { 91 | // jsonld versions of the vocab are only available from 3.1 upwards 92 | // console.log("sdoAdapterMap adding failed for version",v) 93 | // console.log(e) 94 | } 95 | } 96 | return sdoAdapterMap; 97 | } 98 | 99 | /** 100 | * Returns true if the given input is (string) or includes (string array) the given absolute IRI with http or https. 101 | * This test utility helps to check the different schema versions (older ones didn't have https available) 102 | * 103 | * @param input - the input string or array of strings that should be checked1 104 | * @param expectedIRIWithoutProtocol - the absolute IRI that should be searched for (without http/s protocol) 105 | */ 106 | export function isOrIncludesAbsoluteIRI(input: string|string[],expectedIRIWithoutProtocol: string){ 107 | if(isString(input)) { 108 | return input === "https://" + expectedIRIWithoutProtocol || input === "http://" + expectedIRIWithoutProtocol 109 | } 110 | //is Array 111 | return input.includes("https://"+expectedIRIWithoutProtocol) || 112 | input.includes("http://"+expectedIRIWithoutProtocol) 113 | } 114 | -------------------------------------------------------------------------------- /tests/resources/data/vocabularies/vocabulary-animal-dvs.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": { 3 | "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", 4 | "rdfs": "http://www.w3.org/2000/01/rdf-schema#", 5 | "xsd": "http://www.w3.org/2001/XMLSchema#", 6 | "ds": "https://vocab.sti2.at/ds/", 7 | "schema": "https://schema.org/", 8 | "rdfs:subClassOf": { 9 | "@id": "rdfs:subClassOf", 10 | "@type": "@id" 11 | }, 12 | "schema:domainIncludes": { 13 | "@id": "schema:domainIncludes", 14 | "@type": "@id" 15 | }, 16 | "schema:rangeIncludes": { 17 | "@id": "schema:rangeIncludes", 18 | "@type": "@id" 19 | }, 20 | "ex": "https://example-vocab.ex/" 21 | }, 22 | "@id": "https://semantify.it/voc/cp9JDKIZv", 23 | "@type": "ds:Vocabulary", 24 | "schema:name": [ 25 | { 26 | "@language": "en", 27 | "@value": "Animals" 28 | } 29 | ], 30 | "schema:description": [ 31 | { 32 | "@language": "en", 33 | "@value": "A vocabulary for Animals. Its not complete yet, but we got Tigers!" 34 | } 35 | ], 36 | "@graph": [ 37 | { 38 | "@id": "ex:Animal", 39 | "@type": "rdfs:Class", 40 | "rdfs:comment": "Animals are multicellular eukaryotic organisms that form the biological kingdom Animalia. With few exceptions, animals consume organic material, breathe oxygen, are able to move, can reproduce sexually, and grow from a hollow sphere of cells, the blastula, during embryonic development.", 41 | "rdfs:label": "Animal", 42 | "rdfs:subClassOf": "schema:Thing" 43 | }, 44 | { 45 | "@id": "ex:numberOfLegs", 46 | "@type": "rdf:Property", 47 | "rdfs:comment": "The number of legs an animal has.", 48 | "rdfs:label": "numberOfLegs", 49 | "schema:domainIncludes": "ex:Animal", 50 | "schema:rangeIncludes": "schema:Integer" 51 | }, 52 | { 53 | "@id": "ex:animalLivingEnvironment", 54 | "@type": "rdf:Property", 55 | "rdfs:comment": "The living environment of an animal. As values for this property you can write plain text of choose from the following: 'https://example-vocab.ex/AnimalLivingEnvironmentFreedom', 'https://example-vocab.ex/AnimalLivingEnvironmentZoo', 'https://example-vocab.ex/AnimalLivingEnvironmentDomestic' ", 56 | "rdfs:label": "animalLivingEnvironment", 57 | "schema:domainIncludes": "ex:Animal", 58 | "schema:rangeIncludes": [ 59 | "schema:Text", 60 | "ex:AnimalLivingEnvironment" 61 | ] 62 | }, 63 | { 64 | "@id": "ex:AnimalLivingEnvironment", 65 | "@type": "rdfs:Class", 66 | "rdfs:comment": "An enumeration class that lists the possible living environments an animal can have.", 67 | "rdfs:label": "AnimalLivingEnvironment", 68 | "rdfs:subClassOf": "schema:Enumeration" 69 | }, 70 | { 71 | "@id": "ex:AnimalLivingEnvironmentFreedom", 72 | "@type": "ex:AnimalLivingEnvironment", 73 | "rdfs:comment": "The animal lives in freedom.", 74 | "rdfs:label": "AnimalLivingEnvironmentFreedom" 75 | }, 76 | { 77 | "@id": "ex:AnimalLivingEnvironmentZoo", 78 | "@type": "ex:AnimalLivingEnvironment", 79 | "rdfs:comment": "The animal lives in a public zoo.", 80 | "rdfs:label": "AnimalLivingEnvironmentZoo" 81 | }, 82 | { 83 | "@id": "ex:AnimalLivingEnvironmentDomestic", 84 | "@type": "ex:AnimalLivingEnvironment", 85 | "rdfs:comment": "The animal lives in a private domestic environment.", 86 | "rdfs:label": "AnimalLivingEnvironmentDomestic" 87 | }, 88 | { 89 | "@id": "ex:Tiger", 90 | "@type": "rdfs:Class", 91 | "rdfs:comment": [ 92 | { 93 | "@language": "en", 94 | "@value": "The tiger (Panthera tigris) is the largest species among the Felidae and classified in the genus Panthera. It is most recognisable for its dark vertical stripes on orangish-brown fur with a lighter underside." 95 | }, 96 | { 97 | "@language": "zh", 98 | "@value": "虎(学名:Panthera tigris),俗称老虎、大虫,被人称为百獸之王,是現存体型最大的两种猫科动物之一(另一种是狮)。野外個體可長達3.38米(11.1英尺)、重388.7公斤(857英磅)(此为狩猎数据,实测数据的最大值为261kg)" 99 | } 100 | ], 101 | "rdfs:label": [ 102 | { 103 | "@language": "en", 104 | "@value": "Tiger" 105 | }, 106 | { 107 | "@language": "de", 108 | "@value": "Tiger" 109 | }, 110 | { 111 | "@language": "es", 112 | "@value": "Tigre" 113 | }, 114 | { 115 | "@language": "zh", 116 | "@value": "虎" 117 | } 118 | ], 119 | "rdfs:subClassOf": "ex:Animal" 120 | } 121 | ] 122 | } -------------------------------------------------------------------------------- /docs/hierarchy.html: -------------------------------------------------------------------------------- 1 | Schema.org Adapter
Schema.org Adapter
    Preparing search index...

    Schema.org Adapter

    Hierarchy Summary

    2 | -------------------------------------------------------------------------------- /docs/types/Context.html: -------------------------------------------------------------------------------- 1 | Context | Schema.org Adapter
    Schema.org Adapter
      Preparing search index...

      Type Alias Context

      Context: Record<string, ContextEntry>
      2 | -------------------------------------------------------------------------------- /tests/resources/data/vocabularies/vocabulary-animal-altered-2.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": { 3 | "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", 4 | "rdfs": "http://www.w3.org/2000/01/rdf-schema#", 5 | "xsd": "http://www.w3.org/2001/XMLSchema#", 6 | "ds": "https://vocab.sti2.at/ds/", 7 | "schema": "https://schema.org/", 8 | "rdfs:subClassOf": { 9 | "@id": "rdfs:subClassOf", 10 | "@type": "@id" 11 | }, 12 | "schema:domainIncludes": { 13 | "@id": "schema:domainIncludes", 14 | "@type": "@id" 15 | }, 16 | "schema:rangeIncludes": { 17 | "@id": "schema:rangeIncludes", 18 | "@type": "@id" 19 | }, 20 | "ex": "https://example-vocab.ex/" 21 | }, 22 | "@id": "https://semantify.it/voc/cp9JDKIZv", 23 | "@type": "ds:Vocabulary", 24 | "schema:name": [ 25 | { 26 | "@language": "en", 27 | "@value": "Animals" 28 | } 29 | ], 30 | "schema:description": [ 31 | { 32 | "@language": "en", 33 | "@value": "Test Vocabulary that includes terms itself as subclasses/sub-properties. In RDF this is theoretically always the case (every class is a subclass of itself) and therefor not expected as defined in input-vocabularies, and although the API of SDO Adapter does not expect or provide them in the corresponding functions, we have to handle this cases and avoid infinite recursions" 34 | } 35 | ], 36 | "@graph": [ 37 | { 38 | "@id": "ex:Animal", 39 | "@type": "rdfs:Class", 40 | "rdfs:comment": "Animals are multicellular eukaryotic organisms that form the biological kingdom Animalia. With few exceptions, animals consume organic material, breathe oxygen, are able to move, can reproduce sexually, and grow from a hollow sphere of cells, the blastula, during embryonic development.", 41 | "rdfs:label": "Animal", 42 | "rdfs:subClassOf": ["schema:Thing", "ex:Animal"] 43 | }, 44 | { 45 | "@id": "ex:numberOfLegs", 46 | "@type": "rdf:Property", 47 | "rdfs:comment": "The number of legs an animal has.", 48 | "rdfs:label": "numberOfLegs", 49 | "rdfs:subPropertyOf": "ex:numberOfLegs", 50 | "schema:domainIncludes": "ex:Animal", 51 | "schema:rangeIncludes": "schema:Integer" 52 | }, 53 | { 54 | "@id": "ex:animalLivingEnvironment", 55 | "@type": "rdf:Property", 56 | "rdfs:comment": "The living environment of an animal. As values for this property you can write plain text of choose from the following: 'https://example-vocab.ex/AnimalLivingEnvironmentFreedom', 'https://example-vocab.ex/AnimalLivingEnvironmentZoo', 'https://example-vocab.ex/AnimalLivingEnvironmentDomestic' ", 57 | "rdfs:label": "animalLivingEnvironment", 58 | "schema:domainIncludes": "ex:Animal", 59 | "schema:rangeIncludes": [ 60 | "schema:Text", 61 | "ex:AnimalLivingEnvironment" 62 | ] 63 | }, 64 | { 65 | "@id": "ex:AnimalLivingEnvironment", 66 | "@type": "rdfs:Class", 67 | "rdfs:comment": "An enumeration class that lists the possible living environments an animal can have.", 68 | "rdfs:label": "AnimalLivingEnvironment", 69 | "rdfs:subClassOf": ["schema:Enumeration","ex:AnimalLivingEnvironment"] 70 | }, 71 | { 72 | "@id": "ex:AnimalLivingEnvironmentFreedom", 73 | "@type": "ex:AnimalLivingEnvironment", 74 | "rdfs:comment": "The animal lives in freedom.", 75 | "rdfs:label": "AnimalLivingEnvironmentFreedom" 76 | }, 77 | { 78 | "@id": "ex:AnimalLivingEnvironmentZoo", 79 | "@type": "ex:AnimalLivingEnvironment", 80 | "rdfs:comment": "The animal lives in a public zoo.", 81 | "rdfs:label": "AnimalLivingEnvironmentZoo" 82 | }, 83 | { 84 | "@id": "ex:AnimalLivingEnvironmentDomestic", 85 | "@type": "ex:AnimalLivingEnvironment", 86 | "rdfs:comment": "The animal lives in a private domestic environment.", 87 | "rdfs:label": "AnimalLivingEnvironmentDomestic" 88 | }, 89 | { 90 | "@id": "ex:Tiger", 91 | "@type": "rdfs:Class", 92 | "rdfs:comment": [ 93 | { 94 | "@language": "en", 95 | "@value": "The tiger (Panthera tigris) is the largest species among the Felidae and classified in the genus Panthera. It is most recognisable for its dark vertical stripes on orangish-brown fur with a lighter underside." 96 | }, 97 | { 98 | "@language": "zh", 99 | "@value": "虎(学名:Panthera tigris),俗称老虎、大虫,被人称为百獸之王,是現存体型最大的两种猫科动物之一(另一种是狮)。野外個體可長達3.38米(11.1英尺)、重388.7公斤(857英磅)(此为狩猎数据,实测数据的最大值为261kg)" 100 | } 101 | ], 102 | "rdfs:label": [ 103 | { 104 | "@language": "en", 105 | "@value": "Tiger" 106 | }, 107 | { 108 | "@language": "de", 109 | "@value": "Tiger" 110 | }, 111 | { 112 | "@language": "es", 113 | "@value": "Tigre" 114 | }, 115 | { 116 | "@language": "zh", 117 | "@value": "虎" 118 | } 119 | ], 120 | "rdfs:subClassOf": "ex:Animal" 121 | } 122 | ] 123 | } 124 | -------------------------------------------------------------------------------- /tests/resources/data/vocabularies/vocabulary-animal-dvs-extend-enum-member.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": { 3 | "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", 4 | "rdfs": "http://www.w3.org/2000/01/rdf-schema#", 5 | "xsd": "http://www.w3.org/2001/XMLSchema#", 6 | "ds": "https://vocab.sti2.at/ds/", 7 | "schema": "https://schema.org/", 8 | "rdfs:subClassOf": { 9 | "@id": "rdfs:subClassOf", 10 | "@type": "@id" 11 | }, 12 | "schema:domainIncludes": { 13 | "@id": "schema:domainIncludes", 14 | "@type": "@id" 15 | }, 16 | "schema:rangeIncludes": { 17 | "@id": "schema:rangeIncludes", 18 | "@type": "@id" 19 | }, 20 | "ex": "https://example-vocab.ex/" 21 | }, 22 | "@id": "https://semantify.it/voc/cp9JDKIZv", 23 | "@type": "ds:Vocabulary", 24 | "schema:name": [ 25 | { 26 | "@language": "en", 27 | "@value": "Animals" 28 | } 29 | ], 30 | "schema:description": [ 31 | { 32 | "@language": "en", 33 | "@value": "A vocabulary for Animals. Its not complete yet, but we got Tigers!" 34 | } 35 | ], 36 | "@graph": [ 37 | { 38 | "@id": "ex:Animal", 39 | "@type": "rdfs:Class", 40 | "rdfs:comment": "Animals are multicellular eukaryotic organisms that form the biological kingdom Animalia. With few exceptions, animals consume organic material, breathe oxygen, are able to move, can reproduce sexually, and grow from a hollow sphere of cells, the blastula, during embryonic development.", 41 | "rdfs:label": "Animal", 42 | "rdfs:subClassOf": "schema:Thing" 43 | }, 44 | { 45 | "@id": "ex:numberOfLegs", 46 | "@type": "rdf:Property", 47 | "rdfs:comment": "The number of legs an animal has.", 48 | "rdfs:label": "numberOfLegs", 49 | "schema:domainIncludes": "ex:Animal", 50 | "schema:rangeIncludes": "schema:Integer" 51 | }, 52 | { 53 | "@id": "ex:animalLivingEnvironment", 54 | "@type": "rdf:Property", 55 | "rdfs:comment": "The living environment of an animal. As values for this property you can write plain text of choose from the following: 'https://example-vocab.ex/AnimalLivingEnvironmentFreedom', 'https://example-vocab.ex/AnimalLivingEnvironmentZoo', 'https://example-vocab.ex/AnimalLivingEnvironmentDomestic' ", 56 | "rdfs:label": "animalLivingEnvironment", 57 | "schema:domainIncludes": "ex:Animal", 58 | "schema:rangeIncludes": [ 59 | "schema:Text", 60 | "ex:AnimalLivingEnvironment" 61 | ] 62 | }, 63 | { 64 | "@id": "ex:AnimalLivingEnvironment", 65 | "@type": "rdfs:Class", 66 | "rdfs:comment": "An enumeration class that lists the possible living environments an animal can have.", 67 | "rdfs:label": "AnimalLivingEnvironment", 68 | "rdfs:subClassOf": "schema:Enumeration" 69 | }, 70 | { 71 | "@id": "ex:AnimalLivingEnvironmentFreedom", 72 | "@type": "ex:AnimalLivingEnvironment", 73 | "rdfs:comment": "The animal lives in freedom.", 74 | "rdfs:label": "AnimalLivingEnvironmentFreedom" 75 | }, 76 | { 77 | "@id": "ex:AnimalLivingEnvironmentZoo", 78 | "@type": "ex:AnimalLivingEnvironment", 79 | "rdfs:comment": "The animal lives in a public zoo.", 80 | "rdfs:label": "AnimalLivingEnvironmentZoo" 81 | }, 82 | { 83 | "@id": "ex:AnimalLivingEnvironmentDomestic", 84 | "@type": "ex:AnimalLivingEnvironment", 85 | "rdfs:comment": "The animal lives in a private domestic environment.", 86 | "rdfs:label": "AnimalLivingEnvironmentDomestic" 87 | }, 88 | { 89 | "@id": "ex:Tiger", 90 | "@type": "rdfs:Class", 91 | "rdfs:comment": [ 92 | { 93 | "@language": "en", 94 | "@value": "The tiger (Panthera tigris) is the largest species among the Felidae and classified in the genus Panthera. It is most recognisable for its dark vertical stripes on orangish-brown fur with a lighter underside." 95 | }, 96 | { 97 | "@language": "zh", 98 | "@value": "虎(学名:Panthera tigris),俗称老虎、大虫,被人称为百獸之王,是現存体型最大的两种猫科动物之一(另一种是狮)。野外個體可長達3.38米(11.1英尺)、重388.7公斤(857英磅)(此为狩猎数据,实测数据的最大值为261kg)" 99 | } 100 | ], 101 | "rdfs:label": [ 102 | { 103 | "@language": "en", 104 | "@value": "Tiger" 105 | }, 106 | { 107 | "@language": "de", 108 | "@value": "Tiger" 109 | }, 110 | { 111 | "@language": "es", 112 | "@value": "Tigre" 113 | }, 114 | { 115 | "@language": "zh", 116 | "@value": "虎" 117 | } 118 | ], 119 | "rdfs:subClassOf": "ex:Animal" 120 | }, 121 | { 122 | "@id": "ex:AnimalLivingEnvironmentDomestic2", 123 | "@type": "ex:AnimalLivingEnvironment", 124 | "rdfs:comment": "The animal lives in a private domestic environment.", 125 | "rdfs:label": "AnimalLivingEnvironmentDomestic" 126 | }, 127 | { 128 | "@id": "ex:AnimalLivingEnvironmentDomestic3", 129 | "rdfs:comment": "The animal lives in a private domestic environment.", 130 | "rdfs:label": "AnimalLivingEnvironmentDomestic" 131 | }, 132 | { 133 | "@id": "schema:Monday", 134 | "rdfs:label": { 135 | "@language": "de", 136 | "@value": "Montag" 137 | } 138 | } 139 | ] 140 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
      2 |

      Schema.org Adapter

      3 | Fast, simple & flexible API for the Schema.org Vocabulary (and vocabulary extensions!) for Node and Browsers 4 |

      5 | Built with TypeScript 6 | Issues open 7 | Code style in ESLint 8 |
      9 | NPM Version 10 | License: CC BY-SA 4.0 11 |
      12 | 13 | ## Features 14 | ⌘ **Empowers the semantic web:** Schema.org has become the standard vocabulary for the semantic web. The **Schema.org Adapter** (SDO-Adapter) gives developers a clear API to access the schema.org vocabulary in a simple way. 15 | 16 | ★ **Clear data model:** The data model of the rdf-based, machine-readable version of Schema.org is slightly adapted (see algorithm documentation for details) to create the clear and pragmatic data model for the **Schema.org Adapter**. 17 | 18 | ↹ **Supports schema.org versions and external vocabularies:** The **Schema.org Adapter** is lightweight because it does NOT include the vocabulary data, instead it allows the user to input his needed local/remote vocabularies (JSON-LD or URL to JSON-LD). This gives users the possibility to specify the version of Schema.org (including http/https variations) they need, also to use external vocabularies. 19 | 20 | ⚛ **Built-in reasoning:** The simple-to-use [API of this library](https://semantifyit.github.io/schema-org-adapter/) offers functions and parameters that enable built-in reasoning on the used vocabulary-terms (e.g. resolution of properties, subclasses, ranges, etc.) 21 | 22 | 23 | ## Install and load 24 | 25 | Independent of the installation and loading method it is expected that a variable named `SOA` provides this library. 26 | 27 | ### NPM 28 | 29 | ```bash 30 | npm install schema-org-adapter 31 | ``` 32 | 33 | #### Node.js 34 | 35 | Require/import the package: 36 | 37 | ```javascript 38 | const { SOA } = require('schema-org-adapter'); 39 | // or 40 | import { SOA } from 'schema-org-adapter'; 41 | ``` 42 | 43 | #### Browser 44 | 45 | Script-include the bundled package in **/dist**: 46 | 47 | ```html 48 | 49 | 50 | ``` 51 | 52 | ### CDN 53 | 54 | For the browser you can also directly load the library via CDN: 55 | 56 | ```html 57 | 58 | 59 | ``` 60 | 61 | ## Usage 62 | 63 | ```javascript 64 | // 1. Create an SDO-Adapter instance with the latest schema.org vocabulary version 65 | const mySdoAdapter = await SOA.create({ 66 | schemaVersion: "latest" 67 | }); 68 | 69 | // 2. Use the SDO-Adapter! 70 | 71 | // get all properties that are usable by the class schema:Hotel 72 | const hotelInstance = mySdoAdapter.getClass('schema:Hotel'); 73 | const hotelProperties = hotelInstance.getProperties(); 74 | // ["schema:audience", "schema:checkinTime", "schema:availableLanguage", ...] 75 | 76 | // get all data-types and classes that are valid ranges for the property schema:address 77 | const addressInstance = mySdoAdapter.getProperty("schema:address"); 78 | const addressRanges = addressInstance.getRanges(); 79 | // ["schema:PostalAddress", "schema:Text"] 80 | ``` 81 | 82 | ## Documentation 83 | 84 | ### [Technical API documentation](https://semantifyit.github.io/schema-org-adapter/) 85 | ### [Data Model of the Schema.org Adapter](https://github.com/semantifyit/schema-org-adapter/blob/master/docu/dataModel.md) 86 | ### [Expected Vocabulary Structure](https://github.com/semantifyit/schema-org-adapter/blob/master/docu/vocabulary.md) 87 | ### [Conversion Algorithm for Vocabularies](https://github.com/semantifyit/schema-org-adapter/blob/master/docu/algorithm.md) 88 | 89 | ## Code Examples 90 | 91 | ### [Example for Node](https://github.com/semantifyit/schema-org-adapter/blob/master/docu/examples/example-node-2.js) 92 | ### [Example for Browser](https://github.com/semantifyit/schema-org-adapter/blob/master/docu/examples/example-browser-2.html) 93 | ### [All examples](https://github.com/semantifyit/schema-org-adapter/blob/master/docu/examples) 94 | 95 | ## Changelog 96 | 97 | ### [History.md](https://github.com/semantifyit/schema-org-adapter/blob/master/History.md) 98 | ### [Migration Guide to v6.0.0](https://github.com/semantifyit/schema-org-adapter/blob/master/docu/migration-guide-6.md) 99 | 100 |
      101 |

      semantify.it

      102 | Made with ❤ in Tirol! 103 |
      104 | 105 | 106 | --------------------------------------------------------------------------------