/** * Additional export formats for ontologies * * This module provides conversion utilities for various * ontology export formats beyond SKOS. */ import { Ontology, OntologyConcept } from "@trustgraph/react-state"; /** * Export ontology as CSV */ export function exportToCSV(ontology: Ontology): string { const concepts = Object.values(ontology.concepts); // CSV headers const headers = [ "ID", "Preferred Label", "Alternative Labels", "Definition", "Scope Note", "Examples", "Notation", "Broader Concept ID", "Broader Concept Label", "Narrower Concept IDs", "Related Concept IDs", "Is Top Concept", ]; // Build rows const rows = [headers.join(",")]; concepts.forEach((concept) => { const broaderConcept = concept.broader ? ontology.concepts[concept.broader] : null; const row = [ `"${concept.id}"`, `"${concept.prefLabel.replace(/"/g, '""')}"`, `"${(concept.altLabel || []).join("; ").replace(/"/g, '""')}"`, `"${(concept.definition || "").replace(/"/g, '""')}"`, `"${(concept.scopeNote || "").replace(/"/g, '""')}"`, `"${(concept.example || []).join("; ").replace(/"/g, '""')}"`, `"${concept.notation || ""}"`, `"${concept.broader || ""}"`, `"${broaderConcept ? broaderConcept.prefLabel.replace(/"/g, '""') : ""}"`, `"${(concept.narrower || []).join("; ")}"`, `"${(concept.related || []).join("; ")}"`, `"${concept.topConcept ? "true" : "false"}"`, ]; rows.push(row.join(",")); }); return rows.join("\n"); } /** * Export ontology as JSON (formatted) */ export function exportToJSON(ontology: Ontology): string { return JSON.stringify(ontology, null, 2); } /** * Export ontology as plain text outline */ export function exportToText(ontology: Ontology): string { const lines = []; // Header lines.push(`Ontology: ${ontology.metadata.name}`); lines.push( `Description: ${ontology.metadata.description || "No description"}`, ); lines.push(`Version: ${ontology.metadata.version}`); lines.push(`Created: ${ontology.metadata.created}`); lines.push(`Modified: ${ontology.metadata.modified}`); lines.push(`Creator: ${ontology.metadata.creator}`); lines.push(""); // Get hierarchy const topConcepts = ontology.scheme.hasTopConcept .map((id) => ontology.concepts[id]) .filter(Boolean); if (topConcepts.length === 0) { // If no top concepts defined, find root concepts const rootConcepts = Object.values(ontology.concepts).filter( (c) => !c.broader || !ontology.concepts[c.broader], ); topConcepts.push(...rootConcepts); } // Render hierarchy lines.push("CONCEPTS:"); lines.push("========"); lines.push(""); const renderConcept = ( concept: OntologyConcept, indent: number = 0, ): void => { const prefix = " ".repeat(indent); lines.push(`${prefix}• ${concept.prefLabel} (${concept.id})`); if (concept.definition) { lines.push(`${prefix} Definition: ${concept.definition}`); } if (concept.altLabel && concept.altLabel.length > 0) { lines.push( `${prefix} Alternative labels: ${concept.altLabel.join(", ")}`, ); } if (concept.scopeNote) { lines.push(`${prefix} Scope note: ${concept.scopeNote}`); } if (concept.example && concept.example.length > 0) { lines.push(`${prefix} Examples: ${concept.example.join(", ")}`); } if (concept.notation) { lines.push(`${prefix} Notation: ${concept.notation}`); } if (concept.related && concept.related.length > 0) { const relatedLabels = concept.related .map((id) => ontology.concepts[id]?.prefLabel || id) .join(", "); lines.push(`${prefix} Related: ${relatedLabels}`); } lines.push(""); // Render narrower concepts if (concept.narrower && concept.narrower.length > 0) { concept.narrower.forEach((narrowerId) => { const narrowerConcept = ontology.concepts[narrowerId]; if (narrowerConcept) { renderConcept(narrowerConcept, indent + 1); } }); } }; topConcepts.forEach((concept) => renderConcept(concept)); return lines.join("\n"); } /** * Export ontology as GraphML (for network visualization) */ export function exportToGraphML(ontology: Ontology): string { const concepts = Object.values(ontology.concepts); const lines = []; // GraphML header lines.push(''); lines.push('', ); // Key definitions lines.push( ' ', ); lines.push( ' ', ); lines.push( ' ', ); lines.push(""); // Graph lines.push(' '); // Nodes (concepts) concepts.forEach((concept) => { lines.push(` `); lines.push( ` ${escapeXML(concept.prefLabel)}`, ); if (concept.definition) { lines.push( ` ${escapeXML(concept.definition)}`, ); } lines.push(" "); }); // Edges (relationships) concepts.forEach((concept) => { // Broader relationships if (concept.broader && ontology.concepts[concept.broader]) { lines.push( ` `, ); lines.push(' broader'); lines.push(" "); } // Related relationships if (concept.related) { concept.related.forEach((relatedId) => { if (ontology.concepts[relatedId]) { lines.push( ` `, ); lines.push(' related'); lines.push(" "); } }); } }); lines.push(" "); lines.push(""); return lines.join("\n"); } /** * Export ontology as DOT format (for Graphviz) */ export function exportToDOT(ontology: Ontology): string { const concepts = Object.values(ontology.concepts); const lines = []; lines.push("digraph ontology {"); lines.push(" rankdir=TB;"); lines.push( ' node [shape=box, style="rounded,filled", fillcolor=lightblue];', ); lines.push(" edge [color=darkgreen];"); lines.push(""); // Nodes concepts.forEach((concept) => { const label = concept.prefLabel.replace(/"/g, '\\"'); const shape = concept.topConcept ? "ellipse" : "box"; const fillColor = concept.topConcept ? "gold" : "lightblue"; lines.push( ` "${concept.id}" [label="${label}", shape=${shape}, fillcolor=${fillColor}];`, ); }); lines.push(""); // Edges concepts.forEach((concept) => { // Broader relationships (hierarchical) if (concept.broader && ontology.concepts[concept.broader]) { lines.push( ` "${concept.broader}" -> "${concept.id}" [label="narrower", color=blue];`, ); } // Related relationships if (concept.related) { concept.related.forEach((relatedId) => { if (ontology.concepts[relatedId] && concept.id < relatedId) { // Only draw one direction to avoid duplicate edges lines.push( ` "${concept.id}" -> "${relatedId}" [label="related", color=red, dir=both];`, ); } }); } }); lines.push("}"); return lines.join("\n"); } /** * Get export format information */ export const EXPORT_FORMATS = { "skos-rdf": { name: "SKOS RDF/XML", extension: "rdf", mimeType: "application/rdf+xml", description: "Standard SKOS format in RDF/XML syntax", }, "skos-turtle": { name: "SKOS Turtle", extension: "ttl", mimeType: "text/turtle", description: "Standard SKOS format in Turtle syntax", }, json: { name: "JSON", extension: "json", mimeType: "application/json", description: "Internal JSON format (for backup/restore)", }, csv: { name: "CSV", extension: "csv", mimeType: "text/csv", description: "Comma-separated values (for spreadsheet import)", }, text: { name: "Text Outline", extension: "txt", mimeType: "text/plain", description: "Human-readable text outline format", }, graphml: { name: "GraphML", extension: "graphml", mimeType: "application/xml", description: "Network graph format (for yEd, Gephi, etc.)", }, dot: { name: "DOT/Graphviz", extension: "dot", mimeType: "text/plain", description: "Graphviz format for network visualization", }, } as const; export type ExportFormat = keyof typeof EXPORT_FORMATS; /** * Export ontology in specified format */ export function exportOntology( ontology: Ontology, format: ExportFormat, ): string { switch (format) { case "csv": return exportToCSV(ontology); case "json": return exportToJSON(ontology); case "text": return exportToText(ontology); case "graphml": return exportToGraphML(ontology); case "dot": return exportToDOT(ontology); default: throw new Error(`Unsupported export format: ${format}`); } } // Helper function for XML escaping function escapeXML(text: string): string { return text .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); }