UNPKG

atuin

Version:

An RDF and SPARQL editor with graph visualization

246 lines (211 loc) 6.5 kB
/** * Utility class for working with RDF URIs */ export class URIUtils { /** * Split a URI into namespace and local name parts * * @param {string|Object} uri - URI string or RDFJS term * @returns {Object} Object with namespace and name properties */ static splitNamespace(uri) { // Handle RDFJS term objects if (uri && typeof uri === 'object' && uri.value) { uri = uri.value } if (!uri || typeof uri !== 'string') { return { namespace: '', name: '' } } // Handle literals if (this.isLiteral(uri)) { return { namespace: '', name: uri } } // Find the last hash or slash const lastHash = uri.lastIndexOf('#') const lastSlash = uri.lastIndexOf('/') const pos = Math.max(lastHash, lastSlash) + 1 if (pos <= 0) { return { namespace: '', name: uri } } return { namespace: uri.substring(0, pos), name: uri.substring(pos) } } /** * Check if a string or term is an RDF literal * * @param {string|Object} value - The value to check * @returns {boolean} - True if it's a literal */ static isLiteral(value) { // Handle RDFJS term objects if (value && typeof value === 'object') { // Check if it's a Literal term if (value.termType === 'Literal') { return true } // If it has a value property, extract that if (value.value) { value = value.value } else { return false } } if (!value || typeof value !== 'string') { return false } // Check for literal indicators in the string value return ( value.startsWith('"') || value.startsWith("'") || /^[+-]?\d+(\.\d+)?$/.test(value) || value === 'true' || value === 'false' ) } /** * Shrink a URI using namespace prefixes * * @param {string|Object} uri - URI string or RDFJS term * @param {Object} prefixes - Prefix mappings * @returns {string} The shortened URI */ static shrinkUri(uri, prefixes) { // Handle RDFJS term objects if (uri && typeof uri === 'object' && uri.value) { uri = uri.value } if (!uri || typeof uri !== 'string' || this.isLiteral(uri)) { return uri } for (const [prefix, namespace] of Object.entries(prefixes)) { if (uri.startsWith(namespace)) { const local = uri.substring(namespace.length) // Only return prefix:localName if local is not a full URI if (local && !local.startsWith('http')) { return `${prefix}:${local}` } } } // Fallback: try to split and use the prefix if the namespace matches for (const [prefix, namespace] of Object.entries(prefixes)) { const { namespace: ns, name } = this.splitNamespace(uri) if (ns === namespace && name && !name.startsWith('http')) { return `${prefix}:${name}` } } return uri } /** * Expand a prefixed URI to its full form * * @param {string|Object} prefixed - Prefixed URI string or RDFJS term * @param {Object} prefixes - Prefix mappings * @returns {string} The expanded URI */ static expandPrefixed(prefixed, prefixes) { // Handle RDFJS term objects if (prefixed && typeof prefixed === 'object' && prefixed.value) { prefixed = prefixed.value } if (!prefixed || typeof prefixed !== 'string' || this.isLiteral(prefixed)) { return prefixed } // Special case: 'a' shorthand for rdf:type if (prefixed === 'a') { return 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type' } const colonPos = prefixed.indexOf(':') if (colonPos === -1) { return prefixed } const prefix = prefixed.substring(0, colonPos) const local = prefixed.substring(colonPos + 1) if (prefixes[prefix]) { return `${prefixes[prefix]}${local}` } // Return original if prefix not found return prefixed } /** * Extract base URI from Turtle content * * @param {string} content - Turtle content * @returns {string|null} The base URI or null if not found */ static extractBaseUri(content) { if (!content || typeof content !== 'string') { return null } const match = content.match(/@base\s+<([^>]+)>\s*\./) return match ? match[1] : null } /** * Extract prefix declarations from Turtle content * * @param {string} content - Turtle content * @returns {Object} Map of prefix to namespace */ static extractPrefixes(content) { if (!content || typeof content !== 'string') { return {} } const prefixes = {} const regex = /@prefix\s+([a-zA-Z0-9_-]+):\s+<([^>]+)>\s*\./g let match while ((match = regex.exec(content)) !== null) { prefixes[match[1]] = match[2] } return prefixes } /** * Get a display label for a URI * * @param {string|Object} uri - URI string or RDFJS term * @param {Object} prefixes - Prefix mappings * @param {number} maxLength - Maximum length for the label * @returns {string} The display label */ static getLabel(uri, prefixes, maxLength = 30) { // Handle RDFJS term objects if (uri && typeof uri === 'object') { // If it's a Literal, format it appropriately if (uri.termType === 'Literal') { const value = uri.value || '' // For shorter literals, just return the value if (value.length <= maxLength || maxLength === 0) { return `"${value}"${uri.language ? `@${uri.language}` : ''}` } // For longer literals, truncate return `"${value.substring(0, maxLength - 3)}..."${uri.language ? `@${uri.language}` : ''}` } // For other term types, extract the value if (uri.value) { uri = uri.value } } if (!uri || typeof uri !== 'string') { return '' } // For literals, just return them if (this.isLiteral(uri)) { if (maxLength > 0 && uri.length > maxLength) { return `${uri.substring(0, maxLength - 3)}...` } return uri } // Try to shrink using prefixes const shortened = this.shrinkUri(uri, prefixes) // If we couldn't shrink, use the local name if (shortened === uri) { const parts = this.splitNamespace(uri) return parts.name || uri } // Truncate if too long if (maxLength > 0 && shortened.length > maxLength) { return `${shortened.substring(0, maxLength - 3)}...` } return shortened } }