Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

First version of support for form:Scope and form:Listing #60

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export const getPathsForFieldsQuerySingleQuery = async function (
});
};

const getSimplePaths = async function (formStore: N3.Store) {
const getSimplePaths = async function (formStore: N3.Store, node: string) {
// NOTE: we don't support simple paths with modifiers (e.g. inverse path),
// in that case, just write it as ( [ sh:inversePath <predicate> ] ) instead of
// [ sh:inversePath <predicate> ], i.e. make it a complex path
Expand All @@ -101,7 +101,8 @@ const getSimplePaths = async function (formStore: N3.Store) {
SELECT
?field ?path
WHERE {
?field a form:Field.
BIND(${sparqlEscapeUri(node)} as ?field)

?field sh:path ?path.
# simple paths with direct predicates
?field sh:path ?path.
Expand All @@ -124,7 +125,7 @@ const getSimplePaths = async function (formStore: N3.Store) {
});
};

const getComplexPathHeads = async function (formStore: N3.Store) {
const getComplexPathHeads = async function (formStore: N3.Store, node: string) {
const query = `
PREFIX form: <http://lblod.data.gift/vocabularies/forms/>
PREFIX sh: <http://www.w3.org/ns/shacl#>
Expand All @@ -133,7 +134,8 @@ const getComplexPathHeads = async function (formStore: N3.Store) {
SELECT
?field ?path ?node ?modifier ?predicate
WHERE {
?field a form:Field.
BIND(${sparqlEscapeUri(node)} as ?field)

?field sh:path ?path.

# first step of a complex path (blank node)
Expand Down Expand Up @@ -168,7 +170,10 @@ const getComplexPathHeads = async function (formStore: N3.Store) {
});
};

const getComplexPathsTails = async function (formStore: N3.Store) {
const getComplexPathsTails = async function (
formStore: N3.Store,
node: string,
) {
const query = `
PREFIX form: <http://lblod.data.gift/vocabularies/forms/>
PREFIX sh: <http://www.w3.org/ns/shacl#>
Expand All @@ -177,7 +182,7 @@ const getComplexPathsTails = async function (formStore: N3.Store) {
SELECT
?field ?previous ?step ?node ?modifier ?predicate
WHERE {
?field a form:Field.
BIND(${sparqlEscapeUri(node)} as ?field)
?field sh:path ?path.
# mid or last step of a complex path (blank node)
?path rdf:rest+ ?step.
Expand Down Expand Up @@ -236,11 +241,14 @@ const getComplexPathsTails = async function (formStore: N3.Store) {
* "fields:1","nodeID://b10098","nodeID://b10100","dct:subject",,
* "fields:1","nodeID://b10101","nodeID://b10103","prov:startedAtTime",,
*/
export const getPathsForFieldsQuery = async function (formStore: N3.Store) {
export const getPathForNodeQuery = async function (
formStore: N3.Store,
node: string,
) {
const [simple, heads, tails] = await Promise.all([
getSimplePaths(formStore),
getComplexPathHeads(formStore),
getComplexPathsTails(formStore),
getSimplePaths(formStore, node),
getComplexPathHeads(formStore, node),
getComplexPathsTails(formStore, node),
]);
const bindings = [...simple, ...heads, ...tails];
return bindings;
Expand Down
173 changes: 166 additions & 7 deletions form-validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import ForkingStore from 'forking-store';
import { sparqlEscapeUri } from 'mu';
import { QueryEngine } from '@comunica/query-sparql';
import N3 from 'n3';
import { getPathsForFieldsQuery } from './domain/data-access/getPathsForFields';
import { getPathForNodeQuery } from './domain/data-access/getPathForNode';
import { queryStore } from './helpers/query-store';

import { getPathsForGeneratorQuery } from './domain/data-access/getPathsForGenerators';
import { ttlToStore } from './helpers/ttl-helpers';
import {
Expand Down Expand Up @@ -37,11 +39,74 @@ const buildPathChain = function (results: PathQueryResultItem[]) {
return { fieldPathStarts, previousToNext };
};

const getPathsForFields = async function (formStore: N3.Store) {
const results = await getPathsForFieldsQuery(formStore);
const getPathsForFields = async function (
formStore: N3.Store,
formItems: Array<{ type: string; node: string }>,
parentScopePath: string[] = [],
) {
let allPaths = {};
for (const item of formItems) {
if (item.type == 'http://lblod.data.gift/vocabularies/forms/Field') {
const formPaths = await getPathForNode(
formStore,
item.node,
parentScopePath,
);
allPaths = { ...allPaths, ...formPaths };
} else if (
item.type == 'http://lblod.data.gift/vocabularies/forms/Listing'
) {
const scopeNode = await getScopeForNode(formStore, item.node);
if (!scopeNode) {
throw new Error(`Expected Scope for ${item.node}`);
}
const scopePath = (
await getPathForNode(formStore, scopeNode, parentScopePath)
)[scopeNode];

const subFormNode = await getSubFormForListing(formStore, item.node);
if (!subFormNode) {
throw new Error(`Expected SubForm for ${subFormNode}`);
}

const subFormItems = await getFormItemsForNode(formStore, subFormNode);
const subFormPaths = await getPathsForFields(
formStore,
subFormItems,
scopePath,
);

allPaths = { ...allPaths, ...subFormPaths };
} else {
console.warn(`Unsupported type of included node: ${item.node}`);
}
}

return allPaths;
};

const getPathForNode = async function (
formStore: N3.Store,
node: string,
parentScopePath: string[] = [],
): Promise<object> {
const scopeForNode = await getScopeForNode(formStore, node);

let fullScopePath: string[] = [];

if (scopeForNode) {
fullScopePath = (
await getPathForNode(formStore, scopeForNode, parentScopePath)
)[scopeForNode];
} else {
fullScopePath = parentScopePath;
}

const results = await getPathForNodeQuery(formStore, node);
const { fieldPathStarts, previousToNext } = buildPathChain(results);

const fullPaths: Record<string, string[]> = {};
let nodePath: string[] = [];

Object.keys(fieldPathStarts).forEach((field) => {
const path = fieldPathStarts[field];
let current = path;
Expand All @@ -53,11 +118,102 @@ const getPathsForFields = async function (formStore: N3.Store) {
pathSteps.push(current.predicate);
current = previousToNext[current.step || ''];
if (!current) {
fullPaths[field] = pathSteps;
nodePath = [...nodePath, ...pathSteps];
}
}
});
return fullPaths;

const result = {};
result[node] = [...fullScopePath, ...nodePath];
return result;
};

const getScopeForNode = async function (formStore: N3.Store, node: string) {
const queryScope = `
PREFIX form: <http://lblod.data.gift/vocabularies/forms/>
SELECT DISTINCT ?scope WHERE {
BIND(${sparqlEscapeUri(node)} as ?node)

?node form:scope ?scope.
}
LIMIT 1
`;

const bindings = await queryStore(queryScope, formStore);
if (bindings.length) {
return bindings[0].get('scope')?.value;
}
return null;
};

const getFormRootNode = async function (formStore: N3.Store) {
const queryRootNode = `
PREFIX form: <http://lblod.data.gift/vocabularies/forms/>

SELECT DISTINCT ?rootNode WHERE {
?rootNode a form:TopLevelForm.
}
LIMIT 1
`;
const bindings = await queryStore(queryRootNode, formStore);
if (bindings.length) {
return bindings[0].get('rootNode')?.value;
}
return null;
};

const getFormItemsForNode = async function (formStore: N3.Store, node: string) {
const queryIncludes = `
PREFIX form: <http://lblod.data.gift/vocabularies/forms/>

SELECT DISTINCT ?node ?type WHERE {
VALUES ?parentNode {
${sparqlEscapeUri(node)}
}
?parentNode form:includes ?node.

?node a ?type.
}
`;
const bindings = await queryStore(queryIncludes, formStore);

if (bindings.length) {
return bindings.map((b) => {
return {
node: b.get('node').value,
type: b.get('type').value,
};
});
}
return [];
};

const getSubFormForListing = async function (
formStore: N3.Store,
node: string,
) {
const query = `
PREFIX form: <http://lblod.data.gift/vocabularies/forms/>

SELECT DISTINCT ?node WHERE {

VALUES ?listing {
${sparqlEscapeUri(node)}
}

?listing a form:Listing;
form:each ?node.

?node a form:SubForm.

}
LIMIT 1
`;
const bindings = await queryStore(query, formStore);
if (bindings.length) {
return bindings[0].get('node')?.value;
}
return null;
};

const getPathsForGenerators = async function (formStore: N3.Store) {
Expand Down Expand Up @@ -151,7 +307,10 @@ export const buildFormQuery = async function (
options?: QueryOptions,
) {
const formStore = await ttlToStore(formTtl);
const formPaths = await getPathsForFields(formStore);
const rootNode = await getFormRootNode(formStore);
const topLevelNodes = await getFormItemsForNode(formStore, rootNode);

const formPaths = await getPathsForFields(formStore, topLevelNodes);
const generatorPaths = await getPathsForGenerators(formStore);
const allPaths = { ...formPaths, ...generatorPaths };

Expand Down