parent
e90a65e4c2
commit
3c004980e0
@ -1,14 +0,0 @@ |
|||||||
# SPARQL builders |
|
||||||
|
|
||||||
Utilities to build SPARQL SELECT and CONSTRUCT queries from a ShapeConstraint structure. |
|
||||||
|
|
||||||
Exports: |
|
||||||
|
|
||||||
- buildSelectQuery(shape, options) |
|
||||||
- buildConstructQuery(shape, options) |
|
||||||
|
|
||||||
Options: |
|
||||||
|
|
||||||
- prefixes: Record<prefix, IRI> |
|
||||||
- graph: named graph IRI or CURIE |
|
||||||
- includeOptionalForMinZero: wrap min=0 predicates in OPTIONAL (default true) |
|
@ -1,152 +0,0 @@ |
|||||||
import type { |
|
||||||
BuildContext, |
|
||||||
PredicateConstraint, |
|
||||||
ShapeConstraint, |
|
||||||
SparqlBuildOptions, |
|
||||||
} from "./common"; |
|
||||||
import { |
|
||||||
predicateToSparql, |
|
||||||
prefixesToText, |
|
||||||
toIriOrCurie, |
|
||||||
uniqueVar, |
|
||||||
valuesBlock, |
|
||||||
varToken, |
|
||||||
} from "./common"; |
|
||||||
|
|
||||||
/** |
|
||||||
* Build a SPARQL SELECT query from a ShapeConstraint definition. |
|
||||||
* The query matches the shape subject and constraints; optional predicates (min=0) are wrapped in OPTIONAL. |
|
||||||
*/ |
|
||||||
export function buildSelectQuery( |
|
||||||
shape: ShapeConstraint, |
|
||||||
options?: SparqlBuildOptions, |
|
||||||
): string { |
|
||||||
const ctx: BuildContext = { usedVars: new Set<string>() }; |
|
||||||
const prefixes = prefixesToText(options?.prefixes); |
|
||||||
const subject = toIriOrCurie(shape.subject); |
|
||||||
|
|
||||||
const selectVars: string[] = []; |
|
||||||
const whereLines: string[] = []; |
|
||||||
const postFilters: string[] = []; |
|
||||||
const valuesBlocks: string[] = []; |
|
||||||
|
|
||||||
// ensure a consistent root variable when subject is a variable
|
|
||||||
const rootVar = |
|
||||||
subject.startsWith("?") || subject.startsWith("$") |
|
||||||
? subject |
|
||||||
: uniqueVar(ctx, "s"); |
|
||||||
if (!subject.startsWith("?") && !subject.startsWith("$")) { |
|
||||||
// bind fixed subject via VALUES for portability
|
|
||||||
valuesBlocks.push(valuesBlock(rootVar, [subject] as any)); |
|
||||||
} |
|
||||||
|
|
||||||
const predicates = Array.isArray(shape.predicates) |
|
||||||
? shape.predicates |
|
||||||
: [...shape.predicates]; |
|
||||||
|
|
||||||
for (const pred of predicates) { |
|
||||||
addPredicatePattern( |
|
||||||
ctx, |
|
||||||
pred, |
|
||||||
rootVar, |
|
||||||
whereLines, |
|
||||||
selectVars, |
|
||||||
postFilters, |
|
||||||
valuesBlocks, |
|
||||||
options, |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
const graphWrap = (body: string) => |
|
||||||
options?.graph |
|
||||||
? `GRAPH ${toIriOrCurie(options.graph)} {\n${body}\n}` |
|
||||||
: body; |
|
||||||
|
|
||||||
const where = [ |
|
||||||
...valuesBlocks, |
|
||||||
graphWrap(whereLines.join("\n")), |
|
||||||
...postFilters, |
|
||||||
] |
|
||||||
.filter(Boolean) |
|
||||||
.join("\n"); |
|
||||||
|
|
||||||
const select = selectVars.length ? selectVars.join(" ") : "*"; |
|
||||||
|
|
||||||
return [prefixes, `SELECT ${select} WHERE {`, where, `}`].join("\n"); |
|
||||||
} |
|
||||||
|
|
||||||
function addPredicatePattern( |
|
||||||
ctx: BuildContext, |
|
||||||
pred: PredicateConstraint, |
|
||||||
subjectVar: string, |
|
||||||
where: string[], |
|
||||||
selectVars: string[], |
|
||||||
postFilters: string[], |
|
||||||
valuesBlocks: string[], |
|
||||||
options?: SparqlBuildOptions, |
|
||||||
) { |
|
||||||
const p = predicateToSparql(pred.uri); |
|
||||||
const objVar = uniqueVar(ctx, pred.displayName || "o"); |
|
||||||
const objTerm = |
|
||||||
pred.type === "nested" && |
|
||||||
pred.nested?.subject && |
|
||||||
!pred.nested.subject.match(/^\?|^\$/) |
|
||||||
? toIriOrCurie(pred.nested.subject) |
|
||||||
: objVar; |
|
||||||
|
|
||||||
const triple = `${subjectVar} ${p} ${objTerm} .`; |
|
||||||
|
|
||||||
const isOptional = |
|
||||||
(pred.min ?? 0) === 0 && (options?.includeOptionalForMinZero ?? true); |
|
||||||
|
|
||||||
if (pred.type === "nested" && pred.nested) { |
|
||||||
// For nested, we select the nested object var and then recurse
|
|
||||||
if (objTerm === objVar) selectVars.push(objVar); |
|
||||||
const nestedBody: string[] = [triple]; |
|
||||||
const nestedPreds = Array.isArray(pred.nested.predicates) |
|
||||||
? pred.nested.predicates |
|
||||||
: [...pred.nested.predicates]; |
|
||||||
for (const n of nestedPreds) { |
|
||||||
addPredicatePattern( |
|
||||||
ctx, |
|
||||||
n, |
|
||||||
objTerm, |
|
||||||
nestedBody, |
|
||||||
selectVars, |
|
||||||
postFilters, |
|
||||||
valuesBlocks, |
|
||||||
options, |
|
||||||
); |
|
||||||
} |
|
||||||
const block = nestedBody.join("\n"); |
|
||||||
where.push(isOptional ? `OPTIONAL {\n${block}\n}` : block); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
// Non-nested: literals or IRIs
|
|
||||||
selectVars.push(objVar); |
|
||||||
const blockLines: string[] = [triple]; |
|
||||||
|
|
||||||
if (pred.type === "literal" && pred.literalValue !== undefined) { |
|
||||||
if (Array.isArray(pred.literalValue)) { |
|
||||||
// VALUES block for IN-like matching
|
|
||||||
valuesBlocks.push(valuesBlock(objVar, pred.literalValue as any[])); |
|
||||||
} else { |
|
||||||
// simple equality filter
|
|
||||||
const lit = |
|
||||||
typeof pred.literalValue === "string" || |
|
||||||
typeof pred.literalValue === "number" || |
|
||||||
typeof pred.literalValue === "boolean" |
|
||||||
? pred.literalValue |
|
||||||
: String(pred.literalValue); |
|
||||||
postFilters.push( |
|
||||||
`FILTER(${objVar} = ${typeof lit === "string" ? `"${String(lit).replace(/"/g, '\\"')}"` : lit})`, |
|
||||||
); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
const block = blockLines.join("\n"); |
|
||||||
where.push(isOptional ? `OPTIONAL {\n${block}\n}` : block); |
|
||||||
} |
|
||||||
|
|
||||||
export default buildSelectQuery; |
|
@ -0,0 +1,140 @@ |
|||||||
|
import type { Predicate, Shape, Schema } from "@nextgraph-monorepo/ng-shex-orm"; |
||||||
|
|
||||||
|
export const buildConstructQuery = ({ |
||||||
|
schema, |
||||||
|
shapeId, |
||||||
|
}: { |
||||||
|
schema: Schema; |
||||||
|
shapeId: keyof Schema; |
||||||
|
}): string => { |
||||||
|
const rootShape = schema[shapeId]; |
||||||
|
|
||||||
|
const constructStatements: { |
||||||
|
s: string; |
||||||
|
p: string; |
||||||
|
o: string; |
||||||
|
optional: boolean; |
||||||
|
literals: Predicate["literalValue"]; |
||||||
|
}[] = []; |
||||||
|
|
||||||
|
const idToVarName: Record<string, string> = {}; |
||||||
|
const getVarNameFor = (id: string) => { |
||||||
|
const currentName = idToVarName[id]; |
||||||
|
if (currentName) return currentName; |
||||||
|
|
||||||
|
const newVar = `o${Object.entries(idToVarName).length + 1}`; |
||||||
|
idToVarName[id] = newVar; |
||||||
|
return newVar; |
||||||
|
}; |
||||||
|
|
||||||
|
// Create s,p,o records where subject and object var names are mapped to shape or predicate ids.
|
||||||
|
const addTriples = (shape: Shape) => { |
||||||
|
const predicates = shape.predicates; |
||||||
|
const shapeId = shape.iri; |
||||||
|
|
||||||
|
for (const pred of predicates) { |
||||||
|
const subjectVarName = getVarNameFor(shapeId); |
||||||
|
|
||||||
|
if (pred.type === "nested") { |
||||||
|
if (typeof pred.nestedShape !== "string") |
||||||
|
throw new Error("Nested shapes must be by reference"); |
||||||
|
|
||||||
|
// If a name for this shape was assigned already, it's triples have been added
|
||||||
|
// and we don't have to recurse.
|
||||||
|
const shapeAlreadyRegistered = !!idToVarName[pred.nestedShape]; |
||||||
|
|
||||||
|
const shapeVarName = getVarNameFor(pred.nestedShape); |
||||||
|
|
||||||
|
constructStatements.push({ |
||||||
|
s: `?${subjectVarName}`, |
||||||
|
p: `<${pred.predicateUri}>`, |
||||||
|
o: `?${shapeVarName}`, |
||||||
|
optional: pred.minCardinality < 1, |
||||||
|
literals: pred.literalValue, |
||||||
|
// TODO: eitherOf ?
|
||||||
|
}); |
||||||
|
|
||||||
|
if (!shapeAlreadyRegistered) |
||||||
|
addTriples(schema[pred.nestedShape]); |
||||||
|
} else { |
||||||
|
const objVarName = getVarNameFor( |
||||||
|
shapeId + "__separator__" + pred.predicateUri |
||||||
|
); |
||||||
|
|
||||||
|
constructStatements.push({ |
||||||
|
s: `?${subjectVarName}`, |
||||||
|
p: `<${pred.predicateUri}>`, |
||||||
|
o: `?${objVarName}`, |
||||||
|
optional: pred.minCardinality < 1, |
||||||
|
literals: pred.literalValue, |
||||||
|
// TODO: eitherOf ?
|
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
addTriples(rootShape); |
||||||
|
|
||||||
|
const construct = `CONSTRUCT {
|
||||||
|
${constructStatements.map(({ s, p, o }) => ` ${s} ${p} ${o} .\n`).join("")} }`;
|
||||||
|
|
||||||
|
const statementToWhere = ({ |
||||||
|
s, |
||||||
|
p, |
||||||
|
o, |
||||||
|
optional, |
||||||
|
}: { |
||||||
|
s: string; |
||||||
|
p: string; |
||||||
|
o: string; |
||||||
|
optional: boolean; |
||||||
|
}) => { |
||||||
|
if (optional) return ` OPTIONAL { ${s} ${p} ${o} . }\n`; |
||||||
|
else return ` ${s} ${p} ${o} .\n`; |
||||||
|
}; |
||||||
|
|
||||||
|
const literalToSparqlFormat = ( |
||||||
|
literal: string | number | boolean |
||||||
|
): string => { |
||||||
|
if (typeof literal === "number") return String(literal); |
||||||
|
if (typeof literal === "boolean") return literal ? "true" : "false"; |
||||||
|
if (typeof literal === "string") { |
||||||
|
return isIri(literal) |
||||||
|
? `<${literal}>` |
||||||
|
: `"${escapeString(literal)}"`; |
||||||
|
} |
||||||
|
return `"${String(literal)}"`; |
||||||
|
}; |
||||||
|
|
||||||
|
// Filters for optional values.
|
||||||
|
const filters = constructStatements |
||||||
|
.filter((statement) => statement.literals !== undefined) |
||||||
|
.map((statement) => { |
||||||
|
const vals = arrayOf(statement.literals!); |
||||||
|
if (vals.length === 0) return ""; |
||||||
|
if (vals.length === 1) { |
||||||
|
return ` FILTER(${statement.o} = ${literalToSparqlFormat(vals[0]!)})\n`; |
||||||
|
} |
||||||
|
const list = vals.map(literalToSparqlFormat).join(", "); |
||||||
|
return ` FILTER(${statement.o} IN (${list}))\n`; |
||||||
|
}) |
||||||
|
.join(""); |
||||||
|
|
||||||
|
const where = `WHERE {
|
||||||
|
${constructStatements.map(statementToWhere).join("")} |
||||||
|
${filters} |
||||||
|
}`;
|
||||||
|
|
||||||
|
return `${construct}\n${where}`; |
||||||
|
}; |
||||||
|
|
||||||
|
const arrayOf = <T extends any>(arrayOrLiteral: T | T[]) => { |
||||||
|
if (typeof arrayOrLiteral === "undefined" || arrayOrLiteral === null) |
||||||
|
return []; |
||||||
|
if (Array.isArray(arrayOrLiteral)) return arrayOrLiteral; |
||||||
|
return [arrayOrLiteral]; |
||||||
|
}; |
||||||
|
|
||||||
|
const isIri = (str: string) => /^[a-zA-Z][a-zA-Z0-9+.-]{1,7}:/.test(str); |
||||||
|
|
||||||
|
const escapeString = (str: string) => str.replace(/["\\]/g, "\\$&"); |
@ -1,125 +0,0 @@ |
|||||||
/** |
|
||||||
* Shared helpers and types to build SPARQL queries from ShapeConstraint |
|
||||||
*/ |
|
||||||
|
|
||||||
export type LiteralKind = |
|
||||||
| "number" |
|
||||||
| "string" |
|
||||||
| "boolean" |
|
||||||
| "nested" |
|
||||||
| "literal"; |
|
||||||
|
|
||||||
export interface PredicateConstraint { |
|
||||||
displayName: string; |
|
||||||
uri: string; |
|
||||||
type: LiteralKind; |
|
||||||
literalValue?: number | string | boolean | number[] | string[]; |
|
||||||
nested?: ShapeConstraint; |
|
||||||
min: number; |
|
||||||
max: number; |
|
||||||
currentCount: number; |
|
||||||
} |
|
||||||
|
|
||||||
export interface ShapeConstraint { |
|
||||||
subject: string; |
|
||||||
// In upstream code this is typed as a 1-length tuple; we normalize to an array here
|
|
||||||
predicates: PredicateConstraint[] | [PredicateConstraint]; |
|
||||||
} |
|
||||||
|
|
||||||
export interface SparqlBuildOptions { |
|
||||||
prefixes?: Record<string, string>; |
|
||||||
graph?: string; // IRI of the named graph to query, if any
|
|
||||||
includeOptionalForMinZero?: boolean; // default true
|
|
||||||
} |
|
||||||
|
|
||||||
export const defaultPrefixes: Record<string, string> = { |
|
||||||
xsd: "http://www.w3.org/2001/XMLSchema#", |
|
||||||
rdf: "http://www.w3.org/1999/02/22-rdf-syntax-ns#", |
|
||||||
rdfs: "http://www.w3.org/2000/01/rdf-schema#", |
|
||||||
}; |
|
||||||
|
|
||||||
export function prefixesToText(prefixes?: Record<string, string>): string { |
|
||||||
const all = { ...defaultPrefixes, ...(prefixes ?? {}) }; |
|
||||||
return Object.entries(all) |
|
||||||
.map(([p, iri]) => `PREFIX ${p}: <${iri}>`) |
|
||||||
.join("\n"); |
|
||||||
} |
|
||||||
|
|
||||||
export function toIriOrCurie(term: string): string { |
|
||||||
// variable
|
|
||||||
if (term.startsWith("?") || term.startsWith("$")) return term; |
|
||||||
// blank node
|
|
||||||
if (term.startsWith("_:")) return term; |
|
||||||
// full IRI
|
|
||||||
if (term.includes("://")) return `<${term}>`; |
|
||||||
// fallback: assume CURIE or already-angled
|
|
||||||
if (term.startsWith("<") && term.endsWith(">")) return term; |
|
||||||
return term; // CURIE, caller must ensure prefix provided
|
|
||||||
} |
|
||||||
|
|
||||||
export function predicateToSparql(uri: string): string { |
|
||||||
// Allow CURIEs or IRIs
|
|
||||||
return toIriOrCurie(uri); |
|
||||||
} |
|
||||||
|
|
||||||
export function safeVarName(name: string): string { |
|
||||||
const base = name |
|
||||||
.replace(/[^a-zA-Z0-9_]/g, "_") |
|
||||||
.replace(/^([0-9])/, "_$1") |
|
||||||
.slice(0, 60); |
|
||||||
return base || "v"; |
|
||||||
} |
|
||||||
|
|
||||||
export function varToken(name: string): string { |
|
||||||
const n = name.startsWith("?") || name.startsWith("$") ? name.slice(1) : name; |
|
||||||
return `?${safeVarName(n)}`; |
|
||||||
} |
|
||||||
|
|
||||||
export function formatLiteral(value: string | number | boolean): string { |
|
||||||
if (typeof value === "number") return String(value); |
|
||||||
if (typeof value === "boolean") return value ? "true" : "false"; |
|
||||||
// default string literal
|
|
||||||
const escaped = value.replace(/"/g, '\\"'); |
|
||||||
return `"${escaped}"`; |
|
||||||
} |
|
||||||
|
|
||||||
export function formatTermForValues(value: string | number | boolean): string { |
|
||||||
if (typeof value === "number" || typeof value === "boolean") |
|
||||||
return formatLiteral(value); |
|
||||||
// strings: detect IRI or CURIE and keep raw; otherwise quote
|
|
||||||
const v = value.trim(); |
|
||||||
const looksLikeIri = v.startsWith("<") && v.endsWith(">"); |
|
||||||
const looksLikeHttp = v.includes("://"); |
|
||||||
const looksLikeCurie = |
|
||||||
/^[A-Za-z_][A-Za-z0-9_-]*:.+$/u.test(v) && !looksLikeHttp; |
|
||||||
if (looksLikeIri || looksLikeHttp || looksLikeCurie) { |
|
||||||
return looksLikeHttp ? `<${v}>` : v; |
|
||||||
} |
|
||||||
return formatLiteral(v); |
|
||||||
} |
|
||||||
|
|
||||||
export function valuesBlock( |
|
||||||
varName: string, |
|
||||||
values: Array<string | number | boolean>, |
|
||||||
): string { |
|
||||||
const rendered = values.map(formatTermForValues).join(" "); |
|
||||||
return `VALUES ${varName} { ${rendered} }`; |
|
||||||
} |
|
||||||
|
|
||||||
export interface BuildContext { |
|
||||||
// Tracks used variable names to avoid collisions
|
|
||||||
usedVars: Set<string>; |
|
||||||
} |
|
||||||
|
|
||||||
export function uniqueVar(ctx: BuildContext, base: string): string { |
|
||||||
let candidate = varToken(base); |
|
||||||
if (!ctx.usedVars.has(candidate)) { |
|
||||||
ctx.usedVars.add(candidate); |
|
||||||
return candidate; |
|
||||||
} |
|
||||||
let i = 2; |
|
||||||
while (ctx.usedVars.has(`${candidate}_${i}`)) i++; |
|
||||||
const unique = `${candidate}_${i}`; |
|
||||||
ctx.usedVars.add(unique); |
|
||||||
return unique; |
|
||||||
} |
|
@ -0,0 +1,9 @@ |
|||||||
|
import { buildConstructQuery } from "./buildSparqlConstructFromShape.ts"; |
||||||
|
import { testShapeSchema } from "./testShape.schema.ts"; |
||||||
|
|
||||||
|
console.log( |
||||||
|
buildConstructQuery({ |
||||||
|
schema: testShapeSchema, |
||||||
|
shapeId: "http://example.org/TestObject", |
||||||
|
}) |
||||||
|
); |
@ -0,0 +1,129 @@ |
|||||||
|
import type { Schema } from "@nextgraph-monorepo/ng-shex-orm"; |
||||||
|
|
||||||
|
/** |
||||||
|
* ============================================================================= |
||||||
|
* testShapeSchema: Schema for testShape |
||||||
|
* ============================================================================= |
||||||
|
*/ |
||||||
|
export const testShapeSchema: Schema = { |
||||||
|
"http://example.org/TestObject": { |
||||||
|
iri: "http://example.org/TestObject", |
||||||
|
predicates: [ |
||||||
|
{ |
||||||
|
type: "literal", |
||||||
|
literalValue: ["TestObject"], |
||||||
|
maxCardinality: 1, |
||||||
|
minCardinality: 1, |
||||||
|
predicateUri: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", |
||||||
|
readablePredicate: "type", |
||||||
|
extra: true, |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "string", |
||||||
|
maxCardinality: 1, |
||||||
|
minCardinality: 1, |
||||||
|
predicateUri: "http://example.org/stringValue", |
||||||
|
readablePredicate: "stringValue", |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "number", |
||||||
|
maxCardinality: 1, |
||||||
|
minCardinality: 1, |
||||||
|
predicateUri: "http://example.org/numValue", |
||||||
|
readablePredicate: "numValue", |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "boolean", |
||||||
|
maxCardinality: 1, |
||||||
|
minCardinality: 1, |
||||||
|
predicateUri: "http://example.org/boolValue", |
||||||
|
readablePredicate: "boolValue", |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "number", |
||||||
|
maxCardinality: -1, |
||||||
|
minCardinality: 0, |
||||||
|
predicateUri: "http://example.org/arrayValue", |
||||||
|
readablePredicate: "arrayValue", |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "nested", |
||||||
|
nestedShape: |
||||||
|
"http://example.org/TestObject||http://example.org/objectValue", |
||||||
|
maxCardinality: 1, |
||||||
|
minCardinality: 1, |
||||||
|
predicateUri: "http://example.org/objectValue", |
||||||
|
readablePredicate: "objectValue", |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "nested", |
||||||
|
nestedShape: |
||||||
|
"http://example.org/TestObject||http://example.org/anotherObject", |
||||||
|
maxCardinality: -1, |
||||||
|
minCardinality: 0, |
||||||
|
predicateUri: "http://example.org/anotherObject", |
||||||
|
readablePredicate: "anotherObject", |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "eitherOf", |
||||||
|
eitherOf: [ |
||||||
|
{ |
||||||
|
type: "string", |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "number", |
||||||
|
}, |
||||||
|
], |
||||||
|
maxCardinality: 1, |
||||||
|
minCardinality: 1, |
||||||
|
predicateUri: "http://example.org/numOrStr", |
||||||
|
readablePredicate: "numOrStr", |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
"http://example.org/TestObject||http://example.org/objectValue": { |
||||||
|
iri: "http://example.org/TestObject||http://example.org/objectValue", |
||||||
|
predicates: [ |
||||||
|
{ |
||||||
|
type: "string", |
||||||
|
maxCardinality: 1, |
||||||
|
minCardinality: 1, |
||||||
|
predicateUri: "http://example.org/nestedString", |
||||||
|
readablePredicate: "nestedString", |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "number", |
||||||
|
maxCardinality: 1, |
||||||
|
minCardinality: 1, |
||||||
|
predicateUri: "http://example.org/nestedNum", |
||||||
|
readablePredicate: "nestedNum", |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "number", |
||||||
|
maxCardinality: -1, |
||||||
|
minCardinality: 0, |
||||||
|
predicateUri: "http://example.org/nestedArray", |
||||||
|
readablePredicate: "nestedArray", |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
"http://example.org/TestObject||http://example.org/anotherObject": { |
||||||
|
iri: "http://example.org/TestObject||http://example.org/anotherObject", |
||||||
|
predicates: [ |
||||||
|
{ |
||||||
|
type: "string", |
||||||
|
maxCardinality: 1, |
||||||
|
minCardinality: 1, |
||||||
|
predicateUri: "http://example.org/prop1", |
||||||
|
readablePredicate: "prop1", |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "number", |
||||||
|
maxCardinality: 1, |
||||||
|
minCardinality: 1, |
||||||
|
predicateUri: "http://example.org/prop2", |
||||||
|
readablePredicate: "prop2", |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
}; |
Loading…
Reference in new issue