Skip to main content

ontocore_edit/
adapter.rs

1use crate::error::{EditError, Result};
2use crate::transaction::Transaction;
3use ontocore_core::OntologyFormat;
4use ontocore_obo::{self, OboPatchOp};
5use ontocore_owl::{self, PatchDiagnostic, PatchOp};
6use serde::{Deserialize, Serialize};
7use std::collections::BTreeMap;
8use std::path::Path;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum EditFormat {
12    Turtle,
13    Obo,
14    /// RDF/XML (`.owl` / `.rdf`) — full-document Horned re-serialize.
15    RdfXml,
16    /// OWL/XML (`.owx`) — full-document Horned re-serialize.
17    OwlXml,
18}
19
20impl EditFormat {
21    pub fn from_ontology_format(format: OntologyFormat) -> Result<Self> {
22        match format {
23            OntologyFormat::Turtle => Ok(Self::Turtle),
24            OntologyFormat::Obo => Ok(Self::Obo),
25            OntologyFormat::Owl | OntologyFormat::RdfXml => Ok(Self::RdfXml),
26            OntologyFormat::OwlXml => Ok(Self::OwlXml),
27            other => Err(EditError::UnsupportedFormat(other.as_str().to_string())),
28        }
29    }
30
31    pub fn from_path(path: &Path) -> Result<Self> {
32        let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
33        Self::from_ontology_format(OntologyFormat::from_extension(ext))
34    }
35
36    pub fn to_ontology_format(self) -> OntologyFormat {
37        match self {
38            Self::Turtle => OntologyFormat::Turtle,
39            Self::Obo => OntologyFormat::Obo,
40            Self::RdfXml => OntologyFormat::RdfXml,
41            Self::OwlXml => OntologyFormat::OwlXml,
42        }
43    }
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct ApplyTextResult {
48    pub applied: bool,
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub preview_text: Option<String>,
51    #[serde(default, skip_serializing_if = "Vec::is_empty")]
52    pub diagnostics: Vec<PatchDiagnostic>,
53}
54
55impl From<ontocore_owl::ApplyPatchResult> for ApplyTextResult {
56    fn from(value: ontocore_owl::ApplyPatchResult) -> Self {
57        Self {
58            applied: value.applied,
59            preview_text: value.preview_text,
60            diagnostics: value.diagnostics,
61        }
62    }
63}
64
65/// Apply using the transaction's inherent change format (Turtle span or OBO stanza).
66pub fn apply_transaction_to_text(
67    transaction: &Transaction,
68    source: &str,
69    preview_only: bool,
70    namespaces: &BTreeMap<String, String>,
71) -> Result<ApplyTextResult> {
72    apply_transaction_to_text_as(
73        transaction,
74        source,
75        preview_only,
76        namespaces,
77        transaction.format()?,
78    )
79}
80
81/// Apply using an explicit document format (required for RDF/XML and OWL/XML write-back).
82pub fn apply_transaction_to_text_as(
83    transaction: &Transaction,
84    source: &str,
85    preview_only: bool,
86    namespaces: &BTreeMap<String, String>,
87    format: EditFormat,
88) -> Result<ApplyTextResult> {
89    match format {
90        EditFormat::Turtle => {
91            let patches: Vec<PatchOp> = transaction.turtle_patches()?;
92            Ok(ontocore_owl::apply_patches_to_text(source, &patches, preview_only, namespaces)?
93                .into())
94        }
95        EditFormat::Obo => {
96            let patches: Vec<OboPatchOp> = transaction.obo_patches()?;
97            let result = ontocore_obo::apply_patches_to_text(source, &patches, preview_only)?;
98            Ok(ApplyTextResult {
99                applied: result.applied,
100                preview_text: result.preview_text,
101                diagnostics: result
102                    .diagnostics
103                    .into_iter()
104                    .map(|d| PatchDiagnostic { severity: d.severity, message: d.message })
105                    .collect(),
106            })
107        }
108        EditFormat::RdfXml | EditFormat::OwlXml => {
109            let patches: Vec<PatchOp> = transaction.turtle_patches()?;
110            Ok(ontocore_owl::apply_xml_patches_to_text(
111                source,
112                format.to_ontology_format(),
113                &patches,
114                preview_only,
115                namespaces,
116            )?
117            .into())
118        }
119    }
120}
121
122pub fn apply_transaction_to_path(
123    transaction: &Transaction,
124    document_path: &Path,
125    preview_only: bool,
126    namespaces: &BTreeMap<String, String>,
127) -> Result<ontocore_owl::ApplyPatchResult> {
128    let format = EditFormat::from_path(document_path)?;
129    match format {
130        EditFormat::Turtle => {
131            let patches = transaction.turtle_patches()?;
132            ontocore_owl::apply_patches(document_path, &patches, preview_only, namespaces)
133                .map_err(EditError::from)
134        }
135        EditFormat::Obo => {
136            let patches = transaction.obo_patches()?;
137            let result = ontocore_obo::apply_patches(document_path, &patches, preview_only)?;
138            Ok(ontocore_owl::ApplyPatchResult {
139                applied: result.applied,
140                preview_text: result.preview_text,
141                diagnostics: result
142                    .diagnostics
143                    .into_iter()
144                    .map(|d| PatchDiagnostic { severity: d.severity, message: d.message })
145                    .collect(),
146                document_path: result.document_path,
147            })
148        }
149        EditFormat::RdfXml | EditFormat::OwlXml => {
150            let patches = transaction.turtle_patches()?;
151            ontocore_owl::apply_xml_patches(document_path, &patches, preview_only, namespaces)
152                .map_err(EditError::from)
153        }
154    }
155}