/** * SPARQL-specific template filters for semantic web query generation * Provides filters for variables, URIs, literals, and query constructs */ /** * Convert a string to a SPARQL variable format (?variable) */ export function sparqlVar(value: string): string { if (!value) return ''; // If it already starts with ?, just return it (unless it has invalid chars after ?) if (value.startsWith('?')) { const varName = value.substring(1); if (varName.match(/^[a-zA-Z_][a-zA-Z0-9_]*$/)) { return value; } // If it has invalid chars after ?, clean them const cleaned = varName.replace(/[^a-zA-Z0-9_]/g, '_'); return `?${cleaned}`; } let cleaned = value.replace(/[^a-zA-Z0-9_]/g, '_'); // Ensure it starts with a letter or underscore (SPARQL variable names can't start with numbers) if (cleaned.match(/^[0-9]/)) { cleaned = '_' + cleaned; } return `?${cleaned}`; } /** * Convert a string to an RDF resource URI format */ export function rdfResource(value: string): string { if (!value) return ''; // If already a full URI, wrap in angle brackets if (value.match(/^https?:\/\//)) { return `<${value}>`; } // If it's a prefixed name (prefix:localname), return as is if (value.includes(':') && !value.startsWith('http')) { return value; } // Otherwise treat as a local resource return `<${value}>`; } /** * Convert a string to an RDF property format */ export function rdfProperty(value: string): string { if (!value) return ''; // Handle special properties if (value === 'a' || value === 'rdf:type') return 'a'; return rdfResource(value); } /** * Convert a value to appropriate SPARQL value format (variable, literal, or resource) */ export function sparqlValue(value: any): string { if (!value && value !== 0 && value !== false) return ''; // If it starts with ?, it's already a variable if (typeof value === 'string' && value.startsWith('?')) { return sparqlVar(value); } // If it's a number, return as numeric literal if (typeof value === 'number') { return value.toString(); } // If it's a boolean, return as boolean literal if (typeof value === 'boolean') { return value.toString(); } // If it looks like a URI, treat as resource if (typeof value === 'string' && (value.startsWith('http') || (value.includes(':') && !value.includes(' ')))) { return rdfResource(value); } // If it's a simple variable name (no spaces, starts with letter), treat as variable if (typeof value === 'string' && value.match(/^[a-zA-Z_][a-zA-Z0-9_]*$/)) { return sparqlVar(value); } // Otherwise, treat as string literal return sparqlString(value); } /** * Convert a string to a properly escaped SPARQL string literal */ export function sparqlString(value: string): string { if (!value) return '""'; const escaped = value .replace(/\\/g, '\\\\') .replace(/"/g, '\\"') .replace(/\n/g, '\\n') .replace(/\r/g, '\\r') .replace(/\t/g, '\\t'); return `"${escaped}"`; } /** * Convert a value to RDF typed literal */ export function rdfValue(value: any, datatype?: string): string { if (!value && value !== 0 && value !== false) return ''; if (typeof value === 'number') { const type = Number.isInteger(value) ? 'xsd:integer' : 'xsd:decimal'; return `"${value}"^^${type}`; } if (typeof value === 'boolean') { return `"${value}"^^xsd:boolean`; } if (datatype) { return `${sparqlString(value)}^^${datatype}`; } return sparqlString(value); } /** * Convert a string to RDF datatype URI */ export function rdfDatatype(type: string): string { if (!type) return ''; const xsdTypes: Record = { string: 'xsd:string', integer: 'xsd:integer', decimal: 'xsd:decimal', double: 'xsd:double', float: 'xsd:float', boolean: 'xsd:boolean', date: 'xsd:date', dateTime: 'xsd:dateTime', time: 'xsd:time', duration: 'xsd:duration' }; return xsdTypes[type] || (type.includes(':') ? type : `xsd:${type}`); } /** * Convert a string to Schema.org class URI */ export function schemaOrg(value: string): string { if (!value) return ''; // Convert to PascalCase for Schema.org classes const className = value.replace(/(?:^|[\s_-])([a-z])/g, (_, letter) => letter.toUpperCase()); return `schema:${className}`; } /** * Convert filter expression to SPARQL filter */ export function sparqlFilter(expression: string | object): string { if (!expression) return ''; if (typeof expression === 'string') { return expression; } if (typeof expression === 'object') { const { operator, left, right, value } = expression as any; switch (operator) { case 'equals': return `${sparqlVar(left)} = ${sparqlString(right)}`; case 'notEquals': return `${sparqlVar(left)} != ${sparqlValue(right)}`; case 'greaterThan': return `${sparqlVar(left)} > ${sparqlValue(right)}`; case 'lessThan': return `${sparqlVar(left)} < ${sparqlValue(right)}`; case 'greaterThanOrEqual': return `${sparqlVar(left)} >= ${sparqlValue(right)}`; case 'lessThanOrEqual': return `${sparqlVar(left)} <= ${sparqlValue(right)}`; case 'contains': return `contains(${sparqlVar(left)}, ${sparqlString(right)})`; case 'startsWith': return `strstarts(${sparqlVar(left)}, ${sparqlString(right)})`; case 'endsWith': return `strends(${sparqlVar(left)}, ${sparqlString(right)})`; case 'regex': return `regex(${sparqlVar(left)}, "${right}")`; case 'bound': return `bound(${sparqlVar(left)})`; case 'isIRI': return `isIRI(${sparqlVar(left)})`; case 'isBlank': return `isBlank(${sparqlVar(left)})`; case 'isLiteral': return `isLiteral(${sparqlVar(left)})`; case 'lang': return `lang(${sparqlVar(left)}) = "${right}"`; case 'datatype': return `datatype(${sparqlVar(left)}) = ${rdfDatatype(right)}`; default: return expression.toString(); } } return ''; } /** * Convert property path expression to SPARQL property path */ export function sparqlPropertyPath(path: string | object): string { if (!path) return ''; if (typeof path === 'string') { return rdfProperty(path); } const { type, properties } = path as any; switch (type) { case 'sequence': return properties.map(rdfProperty).join('/'); case 'alternative': return properties.map(rdfProperty).join('|'); case 'zeroOrMore': return `${rdfProperty(properties[0])}*`; case 'oneOrMore': return `${rdfProperty(properties[0])}+`; case 'zeroOrOne': return `${rdfProperty(properties[0])}?`; case 'inverse': return `^${rdfProperty(properties[0])}`; case 'negated': return `!(${properties.map(rdfProperty).join('|')})`; default: return rdfProperty(path); } } /** * Convert aggregation expression to SPARQL aggregation */ export function sparqlAggregation(agg: object): string { const { function: func, variable, distinct, alias } = agg as any; let result = ''; switch (func) { case 'count': result = distinct ? `COUNT(DISTINCT ${sparqlVar(variable)})` : `COUNT(${sparqlVar(variable)})`; break; case 'sum': result = `SUM(${sparqlVar(variable)})`; break; case 'avg': result = `AVG(${sparqlVar(variable)})`; break; case 'min': result = `MIN(${sparqlVar(variable)})`; break; case 'max': result = `MAX(${sparqlVar(variable)})`; break; case 'sample': result = `SAMPLE(${sparqlVar(variable)})`; break; case 'groupConcat': result = `GROUP_CONCAT(${sparqlVar(variable)})`; break; default: result = `${func.toUpperCase()}(${sparqlVar(variable)})`; } return alias ? `${result} AS ${sparqlVar(alias)}` : result; } /** * Convert order by expression to SPARQL ORDER BY clause */ export function sparqlOrderBy(orderBy: string | object): string { if (typeof orderBy === 'string') { return sparqlVar(orderBy); } const { variable, direction, expression } = orderBy as any; if (expression) { const orderExpr = direction === 'DESC' ? `DESC(${expression})` : `ASC(${expression})`; return orderExpr; } return direction === 'DESC' ? `DESC(${sparqlVar(variable)})` : sparqlVar(variable); } /** * Escape regex pattern for SPARQL */ export function escapeRegex(pattern: string): string { if (!pattern) return ''; return pattern .replace(/\\/g, '\\\\') .replace(/"/g, '\\"') .replace(/\//g, '\\/') .replace(/\./g, '\\.') .replace(/\*/g, '\\*') .replace(/\+/g, '\\+') .replace(/\?/g, '\\?') .replace(/\^/g, '\\^') .replace(/\$/g, '\\$') .replace(/\|/g, '\\|') .replace(/\[/g, '\\[') .replace(/\]/g, '\\]') .replace(/\{/g, '\\{') .replace(/\}/g, '\\}') .replace(/\(/g, '\\(') .replace(/\)/g, '\\)'); } // Export all filters as a collection export const sparqlFilters = { sparqlVar, rdfResource, rdfProperty, sparqlValue, sparqlString, rdfValue, rdfDatatype, schemaOrg, sparqlFilter, sparqlPropertyPath, sparqlAggregation, sparqlOrderBy, escapeRegex };