1use crate::error::{OwlError, Result};
2use horned_owl::model::{
3 Build, ClassExpression, DataRange, FacetRestriction, Individual, Literal,
4 ObjectPropertyExpression, RcStr,
5};
6use horned_owl::vocab::Facet;
7use serde::Serialize;
8use std::collections::BTreeMap;
9use std::fmt::Write as _;
10
11#[derive(Debug, Clone, Serialize)]
12pub struct ManchesterDiagnostic {
13 pub message: String,
14 pub offset: usize,
15 pub length: usize,
16}
17
18#[derive(Debug, Clone)]
19pub struct ManchesterParseOutput {
20 pub normalized: String,
21 pub expression: ClassExpression<RcStr>,
22 pub tree: serde_json::Value,
23 pub diagnostics: Vec<ManchesterDiagnostic>,
24}
25
26pub fn parse_class_expression(
27 input: &str,
28 namespaces: &BTreeMap<String, String>,
29) -> Result<ManchesterParseOutput> {
30 parse_class_expression_with_datatypes(input, namespaces, &std::collections::BTreeSet::new())
31}
32
33pub fn parse_class_expression_with_datatypes(
35 input: &str,
36 namespaces: &BTreeMap<String, String>,
37 known_datatypes: &std::collections::BTreeSet<String>,
38) -> Result<ManchesterParseOutput> {
39 let trimmed = input.trim();
40 if trimmed.is_empty() {
41 return Err(OwlError::ManchesterInvalid("empty expression".to_string()));
42 }
43
44 let tokens = tokenize(trimmed).map_err(OwlError::ManchesterInvalid)?;
45 let mut parser = ManchesterParser { tokens, pos: 0 };
46 let ast = parser.parse_expression().map_err(OwlError::ManchesterInvalid)?;
47 if !parser.is_at_end() {
48 return Err(OwlError::ManchesterInvalid(format!("unexpected token: {:?}", parser.peek())));
49 }
50
51 let build = Build::default();
52 let expression = ast_to_class_expression(&ast, &build, namespaces, known_datatypes)?;
53 let normalized = class_expression_to_manchester(&expression, namespaces);
54 let tree = expression_tree_json(&expression, namespaces);
55 Ok(ManchesterParseOutput { normalized, expression, tree, diagnostics: Vec::new() })
56}
57
58pub fn class_expression_to_turtle_fragment(
59 expr: &ClassExpression<RcStr>,
60 predicate: &str,
61 namespaces: &BTreeMap<String, String>,
62) -> Result<String> {
63 let value = class_expression_to_turtle_value(expr, namespaces, 0)?;
64 Ok(format!(" {predicate} {value} ;\n"))
65}
66
67pub fn class_expression_to_manchester(
68 expr: &ClassExpression<RcStr>,
69 namespaces: &BTreeMap<String, String>,
70) -> String {
71 match expr {
72 ClassExpression::Class(c) => iri_to_manchester_term(&c.to_string(), namespaces),
73 ClassExpression::ObjectIntersectionOf(v) => {
74 let parts: Vec<String> =
75 v.iter().map(|e| class_expression_to_manchester(e, namespaces)).collect();
76 if parts.len() == 1 {
77 parts[0].clone()
78 } else {
79 parts.join(" and ")
80 }
81 }
82 ClassExpression::ObjectUnionOf(v) => {
83 let parts: Vec<String> =
84 v.iter().map(|e| class_expression_to_manchester(e, namespaces)).collect();
85 if parts.len() == 1 {
86 parts[0].clone()
87 } else {
88 format!("({})", parts.join(" or "))
89 }
90 }
91 ClassExpression::ObjectSomeValuesFrom { ope, bce } => {
92 let prop = ope_to_iri(ope);
93 let filler = class_expression_to_manchester(bce, namespaces);
94 format!("{} some {}", iri_to_manchester_term(&prop, namespaces), filler)
95 }
96 ClassExpression::ObjectAllValuesFrom { ope, bce } => {
97 let prop = ope_to_iri(ope);
98 let filler = class_expression_to_manchester(bce, namespaces);
99 format!("{} only {}", iri_to_manchester_term(&prop, namespaces), filler)
100 }
101 ClassExpression::ObjectMinCardinality { n, ope, bce } => cardinality_manchester(
102 &iri_to_manchester_term(&ope_to_iri(ope), namespaces),
103 "min",
104 *n,
105 bce,
106 namespaces,
107 ),
108 ClassExpression::ObjectMaxCardinality { n, ope, bce } => cardinality_manchester(
109 &iri_to_manchester_term(&ope_to_iri(ope), namespaces),
110 "max",
111 *n,
112 bce,
113 namespaces,
114 ),
115 ClassExpression::ObjectExactCardinality { n, ope, bce } => cardinality_manchester(
116 &iri_to_manchester_term(&ope_to_iri(ope), namespaces),
117 "exactly",
118 *n,
119 bce,
120 namespaces,
121 ),
122 ClassExpression::ObjectComplementOf(inner) => {
123 let body = class_expression_to_manchester(inner, namespaces);
124 format!("not ({body})")
125 }
126 ClassExpression::ObjectHasValue { ope, i } => {
127 format!(
128 "{} value {}",
129 iri_to_manchester_term(&ope_to_iri(ope), namespaces),
130 iri_to_manchester_term(i, namespaces)
131 )
132 }
133 ClassExpression::ObjectHasSelf(ope) => {
134 format!("{} Self", iri_to_manchester_term(&ope_to_iri(ope), namespaces))
135 }
136 ClassExpression::ObjectOneOf(inds) => {
137 let parts: Vec<String> =
138 inds.iter().map(|i| iri_to_manchester_term(i, namespaces)).collect();
139 format!("{{ {} }}", parts.join(" "))
140 }
141 ClassExpression::DataSomeValuesFrom { dp, dr } => {
142 format!(
143 "{} some {}",
144 iri_to_manchester_term(dp.0.as_ref(), namespaces),
145 data_range_to_manchester(dr, namespaces)
146 )
147 }
148 ClassExpression::DataAllValuesFrom { dp, dr } => {
149 format!(
150 "{} only {}",
151 iri_to_manchester_term(dp.0.as_ref(), namespaces),
152 data_range_to_manchester(dr, namespaces)
153 )
154 }
155 ClassExpression::DataHasValue { dp, l } => {
156 format!(
157 "{} value \"{}\"",
158 iri_to_manchester_term(dp.0.as_ref(), namespaces),
159 literal_lexical(l)
160 )
161 }
162 ClassExpression::DataMinCardinality { n, dp, dr } => {
163 data_cardinality_manchester(dp.0.as_ref(), "min", *n, dr, namespaces)
164 }
165 ClassExpression::DataMaxCardinality { n, dp, dr } => {
166 data_cardinality_manchester(dp.0.as_ref(), "max", *n, dr, namespaces)
167 }
168 ClassExpression::DataExactCardinality { n, dp, dr } => {
169 data_cardinality_manchester(dp.0.as_ref(), "exactly", *n, dr, namespaces)
170 }
171 }
172}
173
174pub fn expression_tree_json(
175 expr: &ClassExpression<RcStr>,
176 namespaces: &BTreeMap<String, String>,
177) -> serde_json::Value {
178 match expr {
179 ClassExpression::Class(c) => serde_json::json!({
180 "kind": "Class",
181 "label": iri_to_manchester_term(&c.to_string(), namespaces),
182 }),
183 ClassExpression::ObjectIntersectionOf(v) => serde_json::json!({
184 "kind": "ObjectIntersectionOf",
185 "children": v.iter().map(|e| expression_tree_json(e, namespaces)).collect::<Vec<_>>(),
186 }),
187 ClassExpression::ObjectUnionOf(v) => serde_json::json!({
188 "kind": "ObjectUnionOf",
189 "children": v.iter().map(|e| expression_tree_json(e, namespaces)).collect::<Vec<_>>(),
190 }),
191 ClassExpression::ObjectSomeValuesFrom { ope, bce } => serde_json::json!({
192 "kind": "ObjectSomeValuesFrom",
193 "property": iri_to_manchester_term(&ope_to_iri(ope), namespaces),
194 "filler": expression_tree_json(bce, namespaces),
195 }),
196 ClassExpression::ObjectAllValuesFrom { ope, bce } => serde_json::json!({
197 "kind": "ObjectAllValuesFrom",
198 "property": iri_to_manchester_term(&ope_to_iri(ope), namespaces),
199 "filler": expression_tree_json(bce, namespaces),
200 }),
201 ClassExpression::ObjectMinCardinality { n, ope, bce } => serde_json::json!({
202 "kind": "ObjectMinCardinality",
203 "cardinality": n,
204 "property": iri_to_manchester_term(&ope_to_iri(ope), namespaces),
205 "filler": expression_tree_json(bce, namespaces),
206 }),
207 ClassExpression::ObjectMaxCardinality { n, ope, bce } => serde_json::json!({
208 "kind": "ObjectMaxCardinality",
209 "cardinality": n,
210 "property": iri_to_manchester_term(&ope_to_iri(ope), namespaces),
211 "filler": expression_tree_json(bce, namespaces),
212 }),
213 ClassExpression::ObjectExactCardinality { n, ope, bce } => serde_json::json!({
214 "kind": "ObjectExactCardinality",
215 "cardinality": n,
216 "property": iri_to_manchester_term(&ope_to_iri(ope), namespaces),
217 "filler": expression_tree_json(bce, namespaces),
218 }),
219 ClassExpression::ObjectComplementOf(inner) => serde_json::json!({
220 "kind": "ObjectComplementOf",
221 "filler": expression_tree_json(inner, namespaces),
222 }),
223 ClassExpression::ObjectHasValue { ope, i } => serde_json::json!({
224 "kind": "ObjectHasValue",
225 "property": iri_to_manchester_term(&ope_to_iri(ope), namespaces),
226 "individual": iri_to_manchester_term(i, namespaces),
227 }),
228 ClassExpression::ObjectHasSelf(ope) => serde_json::json!({
229 "kind": "ObjectHasSelf",
230 "property": iri_to_manchester_term(&ope_to_iri(ope), namespaces),
231 }),
232 ClassExpression::ObjectOneOf(inds) => serde_json::json!({
233 "kind": "ObjectOneOf",
234 "individuals": inds.iter().map(|i| iri_to_manchester_term(i, namespaces)).collect::<Vec<_>>(),
235 }),
236 ClassExpression::DataSomeValuesFrom { dp, dr } => serde_json::json!({
237 "kind": "DataSomeValuesFrom",
238 "property": iri_to_manchester_term(dp.0.as_ref(), namespaces),
239 "range": data_range_to_manchester(dr, namespaces),
240 }),
241 ClassExpression::DataAllValuesFrom { dp, dr } => serde_json::json!({
242 "kind": "DataAllValuesFrom",
243 "property": iri_to_manchester_term(dp.0.as_ref(), namespaces),
244 "range": data_range_to_manchester(dr, namespaces),
245 }),
246 ClassExpression::DataHasValue { dp, l } => serde_json::json!({
247 "kind": "DataHasValue",
248 "property": iri_to_manchester_term(dp.0.as_ref(), namespaces),
249 "literal": literal_lexical(l),
250 }),
251 other => serde_json::json!({ "kind": format!("{other:?}") }),
252 }
253}
254
255#[derive(Debug, Clone)]
256enum ManchesterAst {
257 Class(String),
258 Some { property: String, filler: Box<ManchesterAst> },
259 Only { property: String, filler: Box<ManchesterAst> },
260 And(Vec<ManchesterAst>),
261 Or(Vec<ManchesterAst>),
262 Min { n: u32, property: String, filler: Box<ManchesterAst> },
263 Max { n: u32, property: String, filler: Box<ManchesterAst> },
264 Exactly { n: u32, property: String, filler: Box<ManchesterAst> },
265 Not(Box<ManchesterAst>),
266 HasValue { property: String, individual: String },
267 HasSelf { property: String },
268 OneOf(Vec<String>),
269 DataHasValue { property: String, literal: String },
270}
271
272fn ast_to_class_expression(
273 ast: &ManchesterAst,
274 build: &Build<RcStr>,
275 namespaces: &BTreeMap<String, String>,
276 known_datatypes: &std::collections::BTreeSet<String>,
277) -> Result<ClassExpression<RcStr>> {
278 match ast {
279 ManchesterAst::Class(iri) => {
280 let resolved = resolve_term_iri(iri, namespaces)?;
281 Ok(ClassExpression::Class(build.class(resolved)))
282 }
283 ManchesterAst::Some { property, filler } => {
284 let prop_iri = resolve_term_iri(property, namespaces)?;
285 if let Some(dr) = filler_as_data_range(filler, build, namespaces, known_datatypes)? {
286 Ok(ClassExpression::DataSomeValuesFrom { dp: build.data_property(prop_iri), dr })
287 } else {
288 let bce =
289 Box::new(ast_to_class_expression(filler, build, namespaces, known_datatypes)?);
290 Ok(ClassExpression::ObjectSomeValuesFrom {
291 ope: ObjectPropertyExpression::ObjectProperty(build.object_property(prop_iri)),
292 bce,
293 })
294 }
295 }
296 ManchesterAst::Only { property, filler } => {
297 let prop_iri = resolve_term_iri(property, namespaces)?;
298 if let Some(dr) = filler_as_data_range(filler, build, namespaces, known_datatypes)? {
299 Ok(ClassExpression::DataAllValuesFrom { dp: build.data_property(prop_iri), dr })
300 } else {
301 let bce =
302 Box::new(ast_to_class_expression(filler, build, namespaces, known_datatypes)?);
303 Ok(ClassExpression::ObjectAllValuesFrom {
304 ope: ObjectPropertyExpression::ObjectProperty(build.object_property(prop_iri)),
305 bce,
306 })
307 }
308 }
309 ManchesterAst::And(items) => {
310 let exprs: Result<Vec<_>> = items
311 .iter()
312 .map(|i| ast_to_class_expression(i, build, namespaces, known_datatypes))
313 .collect();
314 Ok(ClassExpression::ObjectIntersectionOf(exprs?))
315 }
316 ManchesterAst::Or(items) => {
317 let exprs: Result<Vec<_>> = items
318 .iter()
319 .map(|i| ast_to_class_expression(i, build, namespaces, known_datatypes))
320 .collect();
321 Ok(ClassExpression::ObjectUnionOf(exprs?))
322 }
323 ManchesterAst::Min { n, property, filler } => {
324 let prop_iri = resolve_term_iri(property, namespaces)?;
325 if let Some(dr) = filler_as_data_range(filler, build, namespaces, known_datatypes)? {
326 Ok(ClassExpression::DataMinCardinality {
327 n: *n,
328 dp: build.data_property(prop_iri),
329 dr,
330 })
331 } else {
332 let bce =
333 Box::new(ast_to_class_expression(filler, build, namespaces, known_datatypes)?);
334 Ok(ClassExpression::ObjectMinCardinality {
335 n: *n,
336 ope: ObjectPropertyExpression::ObjectProperty(build.object_property(prop_iri)),
337 bce,
338 })
339 }
340 }
341 ManchesterAst::Max { n, property, filler } => {
342 let prop_iri = resolve_term_iri(property, namespaces)?;
343 if let Some(dr) = filler_as_data_range(filler, build, namespaces, known_datatypes)? {
344 Ok(ClassExpression::DataMaxCardinality {
345 n: *n,
346 dp: build.data_property(prop_iri),
347 dr,
348 })
349 } else {
350 let bce =
351 Box::new(ast_to_class_expression(filler, build, namespaces, known_datatypes)?);
352 Ok(ClassExpression::ObjectMaxCardinality {
353 n: *n,
354 ope: ObjectPropertyExpression::ObjectProperty(build.object_property(prop_iri)),
355 bce,
356 })
357 }
358 }
359 ManchesterAst::Exactly { n, property, filler } => {
360 let prop_iri = resolve_term_iri(property, namespaces)?;
361 if let Some(dr) = filler_as_data_range(filler, build, namespaces, known_datatypes)? {
362 Ok(ClassExpression::DataExactCardinality {
363 n: *n,
364 dp: build.data_property(prop_iri),
365 dr,
366 })
367 } else {
368 let bce =
369 Box::new(ast_to_class_expression(filler, build, namespaces, known_datatypes)?);
370 Ok(ClassExpression::ObjectExactCardinality {
371 n: *n,
372 ope: ObjectPropertyExpression::ObjectProperty(build.object_property(prop_iri)),
373 bce,
374 })
375 }
376 }
377 ManchesterAst::Not(inner) => {
378 let ce = ast_to_class_expression(inner, build, namespaces, known_datatypes)?;
379 Ok(ClassExpression::ObjectComplementOf(Box::new(ce)))
380 }
381 ManchesterAst::HasValue { property, individual } => {
382 let prop = build.object_property(resolve_term_iri(property, namespaces)?);
383 let ind: Individual<RcStr> =
384 build.named_individual(resolve_term_iri(individual, namespaces)?).into();
385 Ok(ClassExpression::ObjectHasValue {
386 ope: ObjectPropertyExpression::ObjectProperty(prop),
387 i: ind,
388 })
389 }
390 ManchesterAst::HasSelf { property } => {
391 let prop = build.object_property(resolve_term_iri(property, namespaces)?);
392 Ok(ClassExpression::ObjectHasSelf(ObjectPropertyExpression::ObjectProperty(prop)))
393 }
394 ManchesterAst::OneOf(inds) => {
395 let individuals: Result<Vec<_>> = inds
396 .iter()
397 .map(|i| {
398 Ok(Individual::from(build.named_individual(resolve_term_iri(i, namespaces)?)))
399 })
400 .collect();
401 Ok(ClassExpression::ObjectOneOf(individuals?))
402 }
403 ManchesterAst::DataHasValue { property, literal } => Ok(ClassExpression::DataHasValue {
404 dp: build.data_property(resolve_term_iri(property, namespaces)?),
405 l: Literal::Simple { literal: literal.clone() },
406 }),
407 }
408}
409
410fn filler_as_data_range(
411 filler: &ManchesterAst,
412 _build: &Build<RcStr>,
413 namespaces: &BTreeMap<String, String>,
414 known_datatypes: &std::collections::BTreeSet<String>,
415) -> Result<Option<DataRange<RcStr>>> {
416 match filler {
417 ManchesterAst::Class(term) => {
418 let base = term.split('[').next().unwrap_or(term).trim();
421 let iri = resolve_term_iri(base, namespaces)?;
422 if !looks_like_datatype_iri(&iri, base, known_datatypes) {
423 return Ok(None);
424 }
425 let dr = parse_data_range(term, namespaces)?;
427 Ok(Some(dr))
428 }
429 _ => Ok(None),
430 }
431}
432
433pub fn parse_data_range(
435 input: &str,
436 namespaces: &BTreeMap<String, String>,
437) -> Result<DataRange<RcStr>> {
438 let trimmed = input.trim();
439 if trimmed.is_empty() {
440 return Err(OwlError::ManchesterInvalid("empty data range".to_string()));
441 }
442 let mut parser = DataRangeParser { input: trimmed, pos: 0, namespaces };
443 let dr = parser.parse_or()?;
444 parser.skip_ws();
445 if parser.pos < parser.input.len() {
446 return Err(OwlError::ManchesterInvalid(format!(
447 "unexpected trailing input in data range: '{}'",
448 &parser.input[parser.pos..]
449 )));
450 }
451 Ok(dr)
452}
453
454struct DataRangeParser<'a> {
455 input: &'a str,
456 pos: usize,
457 namespaces: &'a BTreeMap<String, String>,
458}
459
460impl<'a> DataRangeParser<'a> {
461 fn skip_ws(&mut self) {
462 while let Some(c) = self.input[self.pos..].chars().next() {
463 if !c.is_whitespace() {
464 break;
465 }
466 self.pos += c.len_utf8();
467 }
468 }
469
470 fn peek(&self) -> Option<char> {
471 self.input[self.pos..].chars().next()
472 }
473
474 fn bump(&mut self) -> Option<char> {
475 let c = self.peek()?;
476 self.pos += c.len_utf8();
477 Some(c)
478 }
479
480 fn parse_or(&mut self) -> Result<DataRange<RcStr>> {
481 let mut left = self.parse_and()?;
482 loop {
483 self.skip_ws();
484 if self.consume_keyword("or") {
485 let right = self.parse_and()?;
486 left = match left {
487 DataRange::DataUnionOf(mut v) => {
488 v.push(right);
489 DataRange::DataUnionOf(v)
490 }
491 other => DataRange::DataUnionOf(vec![other, right]),
492 };
493 } else {
494 break;
495 }
496 }
497 Ok(left)
498 }
499
500 fn parse_and(&mut self) -> Result<DataRange<RcStr>> {
501 let mut left = self.parse_unary()?;
502 loop {
503 self.skip_ws();
504 if self.consume_keyword("and") {
505 let right = self.parse_unary()?;
506 left = match left {
507 DataRange::DataIntersectionOf(mut v) => {
508 v.push(right);
509 DataRange::DataIntersectionOf(v)
510 }
511 other => DataRange::DataIntersectionOf(vec![other, right]),
512 };
513 } else {
514 break;
515 }
516 }
517 Ok(left)
518 }
519
520 fn parse_unary(&mut self) -> Result<DataRange<RcStr>> {
521 self.skip_ws();
522 if self.consume_keyword("not") {
523 let inner = self.parse_unary()?;
524 return Ok(DataRange::DataComplementOf(Box::new(inner)));
525 }
526 self.parse_primary()
527 }
528
529 fn parse_primary(&mut self) -> Result<DataRange<RcStr>> {
530 self.skip_ws();
531 if self.peek() == Some('{') {
532 return self.parse_one_of();
533 }
534 if self.peek() == Some('(') {
535 self.bump();
536 let inner = self.parse_or()?;
537 self.skip_ws();
538 if self.bump() != Some(')') {
539 return Err(OwlError::ManchesterInvalid("expected ')' in data range".to_string()));
540 }
541 return Ok(inner);
542 }
543 let name = self.parse_name()?;
544 let iri = resolve_term_iri(&name, self.namespaces)?;
545 let build = Build::new();
546 let datatype = build.datatype(iri.as_str());
547 let mut facets = Vec::new();
548 loop {
549 self.skip_ws();
550 if self.peek() != Some('[') {
551 break;
552 }
553 facets.push(self.parse_facet_bracket()?);
554 }
555 if facets.is_empty() {
556 Ok(DataRange::Datatype(datatype))
557 } else {
558 Ok(DataRange::DatatypeRestriction(datatype, facets))
559 }
560 }
561
562 fn parse_one_of(&mut self) -> Result<DataRange<RcStr>> {
563 self.bump(); let mut lits = Vec::new();
565 loop {
566 self.skip_ws();
567 if self.peek() == Some('}') {
568 self.bump();
569 break;
570 }
571 lits.push(self.parse_literal_value()?);
572 self.skip_ws();
573 if self.peek() == Some(',') {
574 self.bump();
575 continue;
576 }
577 if self.peek() == Some('}') {
578 self.bump();
579 break;
580 }
581 return Err(OwlError::ManchesterInvalid(
582 "expected ',' or '}' in data oneOf".to_string(),
583 ));
584 }
585 Ok(DataRange::DataOneOf(lits))
586 }
587
588 fn parse_literal_value(&mut self) -> Result<Literal<RcStr>> {
589 self.skip_ws();
590 if self.peek() == Some('"') {
591 self.bump();
592 let mut lit = String::new();
593 while let Some(c) = self.bump() {
594 if c == '"' {
595 break;
596 }
597 if c == '\\' {
598 if let Some(n) = self.bump() {
599 lit.push(n);
600 }
601 } else {
602 lit.push(c);
603 }
604 }
605 return Ok(Literal::Simple { literal: lit });
606 }
607 let start = self.pos;
608 while let Some(c) = self.peek() {
609 if c.is_whitespace() || c == ',' || c == '}' || c == ')' || c == ']' {
610 break;
611 }
612 self.bump();
613 }
614 let raw = self.input[start..self.pos].trim();
615 if raw.is_empty() {
616 return Err(OwlError::ManchesterInvalid("empty literal in data oneOf".into()));
617 }
618 Ok(Literal::Simple { literal: raw.to_string() })
619 }
620
621 fn parse_facet_bracket(&mut self) -> Result<FacetRestriction<RcStr>> {
622 self.bump(); self.skip_ws();
624 let (facet, value) = if self.consume_symbol(">=") {
625 (Facet::MinInclusive, self.parse_facet_value()?)
626 } else if self.consume_symbol("<=") {
627 (Facet::MaxInclusive, self.parse_facet_value()?)
628 } else if self.consume_symbol(">") {
629 (Facet::MinExclusive, self.parse_facet_value()?)
630 } else if self.consume_symbol("<") {
631 (Facet::MaxExclusive, self.parse_facet_value()?)
632 } else if self.consume_keyword("length") {
633 (Facet::Length, self.parse_facet_value()?)
634 } else if self.consume_keyword("minLength") {
635 (Facet::MinLength, self.parse_facet_value()?)
636 } else if self.consume_keyword("maxLength") {
637 (Facet::MaxLength, self.parse_facet_value()?)
638 } else if self.consume_keyword("pattern") {
639 (Facet::Pattern, self.parse_facet_value()?)
640 } else if self.consume_keyword("totalDigits") {
641 (Facet::TotalDigits, self.parse_facet_value()?)
642 } else if self.consume_keyword("fractionDigits") {
643 (Facet::FractionDigits, self.parse_facet_value()?)
644 } else {
645 return Err(OwlError::ManchesterInvalid(
646 "unknown facet in datatype restriction".to_string(),
647 ));
648 };
649 self.skip_ws();
650 if self.bump() != Some(']') {
651 return Err(OwlError::ManchesterInvalid("expected ']' after facet".to_string()));
652 }
653 Ok(FacetRestriction { f: facet, l: value })
654 }
655
656 fn parse_facet_value(&mut self) -> Result<Literal<RcStr>> {
657 self.skip_ws();
658 self.parse_literal_value()
659 }
660
661 fn parse_name(&mut self) -> Result<String> {
662 self.skip_ws();
663 if self.peek() == Some('<') {
664 self.bump();
665 let start = self.pos;
666 while let Some(c) = self.bump() {
667 if c == '>' {
668 return Ok(format!("<{}>", &self.input[start..self.pos - 1]));
669 }
670 }
671 return Err(OwlError::ManchesterInvalid("unclosed IRI in data range".into()));
672 }
673 let start = self.pos;
674 while let Some(c) = self.peek() {
675 if c.is_alphanumeric() || c == ':' || c == '_' || c == '-' || c == '.' {
676 self.bump();
677 } else {
678 break;
679 }
680 }
681 let name = self.input[start..self.pos].to_string();
682 if name.is_empty() {
683 return Err(OwlError::ManchesterInvalid("expected datatype name".into()));
684 }
685 Ok(name)
686 }
687
688 fn consume_keyword(&mut self, kw: &str) -> bool {
689 self.skip_ws();
690 let rest = &self.input[self.pos..];
691 if rest.len() >= kw.len()
692 && rest[..kw.len()].eq_ignore_ascii_case(kw)
693 && rest
694 .get(kw.len()..)
695 .and_then(|s| s.chars().next())
696 .map(|c| !c.is_alphanumeric() && c != '_')
697 .unwrap_or(true)
698 {
699 self.pos += kw.len();
700 true
701 } else {
702 false
703 }
704 }
705
706 fn consume_symbol(&mut self, sym: &str) -> bool {
707 self.skip_ws();
708 if self.input[self.pos..].starts_with(sym) {
709 self.pos += sym.len();
710 true
711 } else {
712 false
713 }
714 }
715}
716
717pub fn data_range_to_manchester(
719 dr: &DataRange<RcStr>,
720 namespaces: &BTreeMap<String, String>,
721) -> String {
722 match dr {
723 DataRange::Datatype(dt) => iri_to_curie_or_full(dt.0.as_ref(), namespaces),
724 DataRange::DatatypeRestriction(dt, facets) => {
725 let mut s = iri_to_curie_or_full(dt.0.as_ref(), namespaces);
726 for f in facets {
727 s.push_str(&format!("[{} {}]", facet_symbol(&f.f), literal_lexical(&f.l)));
728 }
729 s
730 }
731 DataRange::DataOneOf(lits) => {
732 let inner = lits
733 .iter()
734 .map(|l| format!("\"{}\"", escape_turtle_string_local(literal_lexical(l))))
735 .collect::<Vec<_>>()
736 .join(", ");
737 format!("{{{inner}}}")
738 }
739 DataRange::DataComplementOf(inner) => {
740 format!("not ({})", data_range_to_manchester(inner, namespaces))
741 }
742 DataRange::DataIntersectionOf(parts) => parts
743 .iter()
744 .map(|p| data_range_to_manchester(p, namespaces))
745 .collect::<Vec<_>>()
746 .join(" and "),
747 DataRange::DataUnionOf(parts) => parts
748 .iter()
749 .map(|p| data_range_to_manchester(p, namespaces))
750 .collect::<Vec<_>>()
751 .join(" or "),
752 }
753}
754
755fn facet_symbol(f: &Facet) -> &'static str {
756 match f {
757 Facet::MinInclusive => ">=",
758 Facet::MaxInclusive => "<=",
759 Facet::MinExclusive => ">",
760 Facet::MaxExclusive => "<",
761 Facet::Length => "length",
762 Facet::MinLength => "minLength",
763 Facet::MaxLength => "maxLength",
764 Facet::Pattern => "pattern",
765 Facet::TotalDigits => "totalDigits",
766 Facet::FractionDigits => "fractionDigits",
767 _ => "facet",
768 }
769}
770
771fn iri_to_curie_or_full(iri: &str, namespaces: &BTreeMap<String, String>) -> String {
772 for (prefix, base) in namespaces {
773 if !prefix.is_empty() && iri.starts_with(base.as_str()) {
774 return format!("{prefix}:{}", &iri[base.len()..]);
775 }
776 }
777 format!("<{iri}>")
778}
779
780fn looks_like_datatype_iri(
781 iri: &str,
782 original: &str,
783 known_datatypes: &std::collections::BTreeSet<String>,
784) -> bool {
785 iri.starts_with("http://www.w3.org/2001/XMLSchema#")
786 || iri.starts_with("http://www.w3.org/1999/02/22-rdf-syntax-ns#")
787 && (iri.ends_with("PlainLiteral")
788 || iri.ends_with("langString")
789 || iri.ends_with("HTML")
790 || iri.ends_with("XMLLiteral"))
791 || iri == "http://www.w3.org/2000/01/rdf-schema#Literal"
792 || iri == "http://www.w3.org/2002/07/owl#real"
793 || iri == "http://www.w3.org/2002/07/owl#rational"
794 || original.starts_with("xsd:")
795 || known_datatypes.contains(iri)
796}
797
798fn resolve_term_iri(term: &str, namespaces: &BTreeMap<String, String>) -> Result<String> {
799 if term.starts_with("http://") || term.starts_with("https://") {
800 return Ok(term.to_string());
801 }
802 if let Some(stripped) = term.strip_prefix('<').and_then(|s| s.strip_suffix('>')) {
803 return Ok(stripped.to_string());
804 }
805 if let Some((prefix, local)) = term.split_once(':') {
806 if prefix.is_empty() {
807 if let Some(ns) = namespaces.get("") {
809 return Ok(format!("{ns}{local}"));
810 }
811 return Err(OwlError::ManchesterInvalid(format!(
812 "empty prefix in QName '{term}' (no default prefix declared)"
813 )));
814 }
815 if let Some(ns) = namespaces.get(prefix) {
816 return Ok(format!("{ns}{local}"));
817 }
818 if prefix == "owl" && (local == "Thing" || local == "Nothing") {
820 return Ok(format!("http://www.w3.org/2002/07/owl#{local}"));
821 }
822 return Err(OwlError::ManchesterInvalid(format!("unknown prefix '{prefix}' in '{term}'")));
823 }
824 Err(OwlError::ManchesterInvalid(format!(
825 "bare name '{term}' is not an IRI; use prefix:local or <absolute-iri>"
826 )))
827}
828
829fn tokenize(input: &str) -> std::result::Result<Vec<Token>, String> {
830 let mut tokens = Vec::new();
831 let mut chars = input.char_indices().peekable();
832 while let Some((start, ch)) = chars.next() {
833 if ch.is_whitespace() {
834 continue;
835 }
836 match ch {
837 '(' => tokens.push(Token::LParen),
838 ')' => tokens.push(Token::RParen),
839 '{' => tokens.push(Token::LBrace),
840 '}' => tokens.push(Token::RBrace),
841 '"' => {
842 let mut lit = String::new();
843 let mut closed = false;
844 while let Some((_, c)) = chars.next() {
845 if c == '"' {
846 closed = true;
847 break;
848 }
849 if c == '\\' {
850 if let Some((_, escaped)) = chars.next() {
851 lit.push(escaped);
852 }
853 } else {
854 lit.push(c);
855 }
856 }
857 if !closed {
858 return Err(format!("unclosed string starting at {start}"));
859 }
860 tokens.push(Token::StringLit(lit));
861 }
862 '<' => {
863 let mut iri = String::new();
864 let mut closed = false;
865 for (_, c) in chars.by_ref() {
866 if c == '>' {
867 closed = true;
868 break;
869 }
870 iri.push(c);
871 }
872 if !closed {
873 return Err(format!("unclosed IRI starting at {start}"));
874 }
875 tokens.push(Token::Iri(iri));
876 }
877 '0'..='9' => {
878 let mut num = ch.to_string();
879 while chars.peek().is_some_and(|(_, c)| c.is_ascii_digit()) {
880 num.push(chars.next().unwrap().1);
881 }
882 let n: u32 = num.parse().map_err(|_| format!("invalid number at {start}"))?;
883 tokens.push(Token::Number(n));
884 }
885 _ if ch.is_alphabetic() || ch == '_' || ch == ':' => {
886 let mut ident = ch.to_string();
887 while chars.peek().is_some_and(|(_, c)| {
888 c.is_alphanumeric() || *c == '_' || *c == ':' || *c == '-'
889 }) {
890 ident.push(chars.next().unwrap().1);
891 }
892 let lower = ident.to_ascii_lowercase();
893 let kw = match lower.as_str() {
894 "and" => Some(Keyword::And),
895 "or" => Some(Keyword::Or),
896 "some" => Some(Keyword::Some),
897 "only" => Some(Keyword::Only),
898 "min" => Some(Keyword::Min),
899 "max" => Some(Keyword::Max),
900 "exactly" => Some(Keyword::Exactly),
901 "not" => Some(Keyword::Not),
902 "value" => Some(Keyword::Value),
903 "self" => Some(Keyword::SelfKw),
904 _ => None,
905 };
906 if let Some(k) = kw {
907 tokens.push(Token::Keyword(k));
908 } else {
909 tokens.push(Token::Ident(ident));
910 }
911 }
912 _ => return Err(format!("unexpected character '{ch}' at {start}")),
913 }
914 }
915 tokens.push(Token::Eof);
916 Ok(tokens)
917}
918
919#[derive(Debug, Clone)]
920enum Token {
921 Ident(String),
922 Iri(String),
923 StringLit(String),
924 Keyword(Keyword),
925 Number(u32),
926 LParen,
927 RParen,
928 LBrace,
929 RBrace,
930 Eof,
931}
932
933#[derive(Debug, Clone, Copy)]
934enum Keyword {
935 And,
936 Or,
937 Some,
938 Only,
939 Min,
940 Max,
941 Exactly,
942 Not,
943 Value,
944 SelfKw,
945}
946
947struct ManchesterParser {
948 tokens: Vec<Token>,
949 pos: usize,
950}
951
952impl ManchesterParser {
953 fn is_at_end(&self) -> bool {
954 matches!(self.peek(), Token::Eof)
955 }
956
957 fn peek(&self) -> &Token {
958 self.tokens.get(self.pos).unwrap_or(&Token::Eof)
959 }
960
961 fn advance(&mut self) -> Token {
962 let tok = self.peek().clone();
963 if !matches!(tok, Token::Eof) {
964 self.pos += 1;
965 }
966 tok
967 }
968
969 fn parse_expression(&mut self) -> std::result::Result<ManchesterAst, String> {
970 self.parse_or()
971 }
972
973 fn parse_or(&mut self) -> std::result::Result<ManchesterAst, String> {
974 let mut parts = vec![self.parse_and()?];
975 while matches!(self.peek(), Token::Keyword(Keyword::Or)) {
976 self.advance();
977 parts.push(self.parse_and()?);
978 }
979 if parts.len() == 1 {
980 Ok(parts.into_iter().next().unwrap())
981 } else {
982 Ok(ManchesterAst::Or(parts))
983 }
984 }
985
986 fn parse_and(&mut self) -> std::result::Result<ManchesterAst, String> {
987 let mut parts = vec![self.parse_unary()?];
988 while matches!(self.peek(), Token::Keyword(Keyword::And)) {
989 self.advance();
990 parts.push(self.parse_unary()?);
991 }
992 if parts.len() == 1 {
993 Ok(parts.into_iter().next().unwrap())
994 } else {
995 Ok(ManchesterAst::And(parts))
996 }
997 }
998
999 fn parse_unary(&mut self) -> std::result::Result<ManchesterAst, String> {
1000 if matches!(self.peek(), Token::Keyword(Keyword::Not)) {
1001 self.advance();
1002 let inner = self.parse_unary()?;
1003 return Ok(ManchesterAst::Not(Box::new(inner)));
1004 }
1005 self.parse_primary()
1006 }
1007
1008 fn parse_primary(&mut self) -> std::result::Result<ManchesterAst, String> {
1009 if matches!(self.peek(), Token::Keyword(Keyword::Min | Keyword::Max | Keyword::Exactly)) {
1010 return self.parse_cardinality();
1011 }
1012 if matches!(self.peek(), Token::LBrace) {
1013 return self.parse_one_of();
1014 }
1015 if matches!(self.peek(), Token::LParen) {
1016 self.advance();
1017 let inner = self.parse_expression()?;
1018 self.expect_paren_r()?;
1019 return Ok(inner);
1020 }
1021 let name = self.parse_name()?;
1022 if matches!(self.peek(), Token::Keyword(Keyword::SelfKw)) {
1023 self.advance();
1024 return Ok(ManchesterAst::HasSelf { property: name });
1025 }
1026 if matches!(self.peek(), Token::Keyword(Keyword::Value)) {
1027 self.advance();
1028 return match self.peek().clone() {
1029 Token::StringLit(lit) => {
1030 self.advance();
1031 Ok(ManchesterAst::DataHasValue { property: name, literal: lit })
1032 }
1033 _ => {
1034 let individual = self.parse_name()?;
1035 Ok(ManchesterAst::HasValue { property: name, individual })
1036 }
1037 };
1038 }
1039 if matches!(self.peek(), Token::Keyword(Keyword::Min | Keyword::Max | Keyword::Exactly)) {
1040 let kind = self.advance();
1041 let Token::Number(n) = self.advance() else {
1042 return Err("expected cardinality number".to_string());
1043 };
1044 let filler = if Self::starts_class_term(self.peek()) {
1045 self.parse_primary()?
1046 } else {
1047 ManchesterAst::Class("http://www.w3.org/2002/07/owl#Thing".to_string())
1049 };
1050 return Ok(match kind {
1051 Token::Keyword(Keyword::Min) => {
1052 ManchesterAst::Min { n, property: name, filler: Box::new(filler) }
1053 }
1054 Token::Keyword(Keyword::Max) => {
1055 ManchesterAst::Max { n, property: name, filler: Box::new(filler) }
1056 }
1057 Token::Keyword(Keyword::Exactly) => {
1058 ManchesterAst::Exactly { n, property: name, filler: Box::new(filler) }
1059 }
1060 _ => return Err("expected min, max, or exactly".to_string()),
1061 });
1062 }
1063 if matches!(self.peek(), Token::Keyword(Keyword::Some | Keyword::Only)) {
1064 let quant = self.advance();
1065 let filler = self.parse_primary()?;
1066 return match quant {
1067 Token::Keyword(Keyword::Some) => {
1068 Ok(ManchesterAst::Some { property: name, filler: Box::new(filler) })
1069 }
1070 Token::Keyword(Keyword::Only) => {
1071 Ok(ManchesterAst::Only { property: name, filler: Box::new(filler) })
1072 }
1073 _ => return Err("internal parser error after some/only keyword".to_string()),
1074 };
1075 }
1076 Ok(ManchesterAst::Class(name))
1077 }
1078
1079 fn parse_one_of(&mut self) -> std::result::Result<ManchesterAst, String> {
1080 if !matches!(self.advance(), Token::LBrace) {
1081 return Err("expected '{'".to_string());
1082 }
1083 let mut inds = Vec::new();
1084 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
1085 inds.push(self.parse_name()?);
1086 }
1087 if !matches!(self.advance(), Token::RBrace) {
1088 return Err("expected '}'".to_string());
1089 }
1090 if inds.is_empty() {
1091 return Err("ObjectOneOf must contain at least one individual".to_string());
1092 }
1093 Ok(ManchesterAst::OneOf(inds))
1094 }
1095
1096 fn parse_cardinality(&mut self) -> std::result::Result<ManchesterAst, String> {
1097 let kind = self.advance();
1098 let Token::Number(n) = self.advance() else {
1099 return Err("expected cardinality number".to_string());
1100 };
1101 let prop = self.parse_name()?;
1102 let filler = if matches!(self.peek(), Token::Keyword(Keyword::Some)) {
1103 self.advance();
1104 self.parse_primary()?
1105 } else if Self::starts_class_term(self.peek()) {
1106 self.parse_primary()?
1107 } else {
1108 ManchesterAst::Class("http://www.w3.org/2002/07/owl#Thing".to_string())
1110 };
1111 Ok(match kind {
1112 Token::Keyword(Keyword::Min) => {
1113 ManchesterAst::Min { n, property: prop, filler: Box::new(filler) }
1114 }
1115 Token::Keyword(Keyword::Max) => {
1116 ManchesterAst::Max { n, property: prop, filler: Box::new(filler) }
1117 }
1118 Token::Keyword(Keyword::Exactly) => {
1119 ManchesterAst::Exactly { n, property: prop, filler: Box::new(filler) }
1120 }
1121 _ => return Err("expected min, max, or exactly".to_string()),
1122 })
1123 }
1124
1125 fn starts_class_term(tok: &Token) -> bool {
1126 matches!(tok, Token::Ident(_) | Token::Iri(_) | Token::LParen | Token::LBrace)
1127 }
1128
1129 fn parse_name(&mut self) -> std::result::Result<String, String> {
1130 match self.advance() {
1131 Token::Ident(s) => Ok(s),
1132 Token::Iri(iri) => Ok(format!("<{iri}>")),
1133 other => Err(format!("expected name, got {other:?}")),
1134 }
1135 }
1136
1137 fn expect_paren_r(&mut self) -> std::result::Result<(), String> {
1138 if !matches!(self.advance(), Token::RParen) {
1139 return Err("expected ')'".to_string());
1140 }
1141 Ok(())
1142 }
1143}
1144
1145pub fn class_expression_to_turtle_value(
1146 expr: &ClassExpression<RcStr>,
1147 namespaces: &BTreeMap<String, String>,
1148 indent: usize,
1149) -> Result<String> {
1150 let pad = " ".repeat(indent);
1151 let inner_pad = " ".repeat(indent + 1);
1152 match expr {
1153 ClassExpression::Class(c) => iri_to_turtle_term(&c.to_string(), namespaces),
1154 ClassExpression::ObjectIntersectionOf(v) if v.len() == 1 => {
1155 class_expression_to_turtle_value(&v[0], namespaces, indent)
1156 }
1157 ClassExpression::ObjectSomeValuesFrom { ope, bce } => {
1158 let prop = iri_to_turtle_term(&ope_to_iri(ope), namespaces)?;
1159 let filler = class_expression_to_turtle_value(bce, namespaces, indent + 1)?;
1160 let mut out = String::new();
1161 writeln!(out, "[").ok();
1162 writeln!(out, "{inner_pad}a owl:Restriction ;").ok();
1163 writeln!(out, "{inner_pad}owl:onProperty {prop} ;").ok();
1164 writeln!(out, "{inner_pad}owl:someValuesFrom {filler}").ok();
1165 write!(out, "{pad}]").ok();
1166 Ok(out)
1167 }
1168 ClassExpression::ObjectAllValuesFrom { ope, bce } => {
1169 let prop = iri_to_turtle_term(&ope_to_iri(ope), namespaces)?;
1170 let filler = class_expression_to_turtle_value(bce, namespaces, indent + 1)?;
1171 let mut out = String::new();
1172 writeln!(out, "[").ok();
1173 writeln!(out, "{inner_pad}a owl:Restriction ;").ok();
1174 writeln!(out, "{inner_pad}owl:onProperty {prop} ;").ok();
1175 writeln!(out, "{inner_pad}owl:allValuesFrom {filler}").ok();
1176 write!(out, "{pad}]").ok();
1177 Ok(out)
1178 }
1179 ClassExpression::ObjectMinCardinality { n, ope, bce } => {
1180 cardinality_turtle("owl:minQualifiedCardinality", *n, ope, bce, namespaces, indent)
1181 }
1182 ClassExpression::ObjectMaxCardinality { n, ope, bce } => {
1183 cardinality_turtle("owl:maxQualifiedCardinality", *n, ope, bce, namespaces, indent)
1184 }
1185 ClassExpression::ObjectExactCardinality { n, ope, bce } => {
1186 cardinality_turtle("owl:qualifiedCardinality", *n, ope, bce, namespaces, indent)
1187 }
1188 ClassExpression::ObjectIntersectionOf(v) if v.len() > 1 => {
1189 let terms: Vec<String> = v
1190 .iter()
1191 .map(|e| class_expression_to_turtle_value(e, namespaces, indent + 2))
1192 .collect::<Result<_>>()?;
1193 let list = terms.join(" ");
1194 let mut out = String::new();
1195 writeln!(out, "[").ok();
1196 writeln!(out, "{inner_pad}a owl:Class ;").ok();
1197 writeln!(out, "{inner_pad}owl:intersectionOf ( {list} )").ok();
1198 write!(out, "{pad}]").ok();
1199 Ok(out)
1200 }
1201 ClassExpression::ObjectUnionOf(v) if v.len() > 1 => {
1202 let terms: Vec<String> = v
1203 .iter()
1204 .map(|e| class_expression_to_turtle_value(e, namespaces, indent + 2))
1205 .collect::<Result<_>>()?;
1206 let list = terms.join(" ");
1207 let mut out = String::new();
1208 writeln!(out, "[").ok();
1209 writeln!(out, "{inner_pad}a owl:Class ;").ok();
1210 writeln!(out, "{inner_pad}owl:unionOf ( {list} )").ok();
1211 write!(out, "{pad}]").ok();
1212 Ok(out)
1213 }
1214 ClassExpression::ObjectIntersectionOf(v) => {
1215 let Some(first) = v.first() else {
1216 return Err(OwlError::ManchesterInvalid(
1217 "empty intersection expression".to_string(),
1218 ));
1219 };
1220 class_expression_to_turtle_value(first, namespaces, indent)
1221 }
1222 ClassExpression::ObjectUnionOf(v) => {
1223 let Some(first) = v.first() else {
1224 return Err(OwlError::ManchesterInvalid("empty union expression".to_string()));
1225 };
1226 class_expression_to_turtle_value(first, namespaces, indent)
1227 }
1228 ClassExpression::ObjectComplementOf(inner) => {
1229 let ce = class_expression_to_turtle_value(inner, namespaces, indent + 1)?;
1230 let mut out = String::new();
1231 writeln!(out, "[").ok();
1232 writeln!(out, "{inner_pad}a owl:Class ;").ok();
1233 writeln!(out, "{inner_pad}owl:complementOf {ce}").ok();
1234 write!(out, "{pad}]").ok();
1235 Ok(out)
1236 }
1237 ClassExpression::ObjectHasValue { ope, i } => {
1238 let prop = iri_to_turtle_term(&ope_to_iri(ope), namespaces)?;
1239 let ind = iri_to_turtle_term(i, namespaces)?;
1240 let mut out = String::new();
1241 writeln!(out, "[").ok();
1242 writeln!(out, "{inner_pad}a owl:Restriction ;").ok();
1243 writeln!(out, "{inner_pad}owl:onProperty {prop} ;").ok();
1244 writeln!(out, "{inner_pad}owl:hasValue {ind}").ok();
1245 write!(out, "{pad}]").ok();
1246 Ok(out)
1247 }
1248 ClassExpression::ObjectHasSelf(ope) => {
1249 let prop = iri_to_turtle_term(&ope_to_iri(ope), namespaces)?;
1250 let mut out = String::new();
1251 writeln!(out, "[").ok();
1252 writeln!(out, "{inner_pad}a owl:Restriction ;").ok();
1253 writeln!(out, "{inner_pad}owl:onProperty {prop} ;").ok();
1254 writeln!(out, "{inner_pad}owl:hasSelf true").ok();
1255 write!(out, "{pad}]").ok();
1256 Ok(out)
1257 }
1258 ClassExpression::ObjectOneOf(inds) => {
1259 let terms: Result<Vec<_>> =
1260 inds.iter().map(|i| iri_to_turtle_term(i, namespaces)).collect();
1261 let list = terms?.join(" ");
1262 let mut out = String::new();
1263 writeln!(out, "[").ok();
1264 writeln!(out, "{inner_pad}a owl:Class ;").ok();
1265 writeln!(out, "{inner_pad}owl:oneOf ( {list} )").ok();
1266 write!(out, "{pad}]").ok();
1267 Ok(out)
1268 }
1269 ClassExpression::DataSomeValuesFrom { dp, dr } => {
1270 let prop = iri_to_turtle_term(dp.0.as_ref(), namespaces)?;
1271 let range = data_range_to_turtle(dr, namespaces)?;
1272 let mut out = String::new();
1273 writeln!(out, "[").ok();
1274 writeln!(out, "{inner_pad}a owl:Restriction ;").ok();
1275 writeln!(out, "{inner_pad}owl:onProperty {prop} ;").ok();
1276 writeln!(out, "{inner_pad}owl:someValuesFrom {range}").ok();
1277 write!(out, "{pad}]").ok();
1278 Ok(out)
1279 }
1280 ClassExpression::DataAllValuesFrom { dp, dr } => {
1281 let prop = iri_to_turtle_term(dp.0.as_ref(), namespaces)?;
1282 let range = data_range_to_turtle(dr, namespaces)?;
1283 let mut out = String::new();
1284 writeln!(out, "[").ok();
1285 writeln!(out, "{inner_pad}a owl:Restriction ;").ok();
1286 writeln!(out, "{inner_pad}owl:onProperty {prop} ;").ok();
1287 writeln!(out, "{inner_pad}owl:allValuesFrom {range}").ok();
1288 write!(out, "{pad}]").ok();
1289 Ok(out)
1290 }
1291 ClassExpression::DataHasValue { dp, l } => {
1292 let prop = iri_to_turtle_term(dp.0.as_ref(), namespaces)?;
1293 let lit = format!("\"{}\"", escape_turtle_string_local(literal_lexical(l)));
1294 let mut out = String::new();
1295 writeln!(out, "[").ok();
1296 writeln!(out, "{inner_pad}a owl:Restriction ;").ok();
1297 writeln!(out, "{inner_pad}owl:onProperty {prop} ;").ok();
1298 writeln!(out, "{inner_pad}owl:hasValue {lit}").ok();
1299 write!(out, "{pad}]").ok();
1300 Ok(out)
1301 }
1302 ClassExpression::DataMinCardinality { n, dp, dr } => data_cardinality_turtle(
1303 "owl:minQualifiedCardinality",
1304 *n,
1305 dp.0.as_ref(),
1306 dr,
1307 namespaces,
1308 indent,
1309 ),
1310 ClassExpression::DataMaxCardinality { n, dp, dr } => data_cardinality_turtle(
1311 "owl:maxQualifiedCardinality",
1312 *n,
1313 dp.0.as_ref(),
1314 dr,
1315 namespaces,
1316 indent,
1317 ),
1318 ClassExpression::DataExactCardinality { n, dp, dr } => data_cardinality_turtle(
1319 "owl:qualifiedCardinality",
1320 *n,
1321 dp.0.as_ref(),
1322 dr,
1323 namespaces,
1324 indent,
1325 ),
1326 }
1327}
1328
1329fn cardinality_turtle(
1330 pred: &str,
1331 n: u32,
1332 ope: &ObjectPropertyExpression<RcStr>,
1333 bce: &ClassExpression<RcStr>,
1334 namespaces: &BTreeMap<String, String>,
1335 indent: usize,
1336) -> Result<String> {
1337 let pad = " ".repeat(indent);
1338 let inner_pad = " ".repeat(indent + 1);
1339 let prop = iri_to_turtle_term(&ope_to_iri(ope), namespaces)?;
1340 let filler = class_expression_to_turtle_value(bce, namespaces, indent + 1)?;
1341 let mut out = String::new();
1342 writeln!(out, "[").ok();
1343 writeln!(out, "{inner_pad}a owl:Restriction ;").ok();
1344 writeln!(out, "{inner_pad}owl:onProperty {prop} ;").ok();
1345 writeln!(
1346 out,
1347 "{inner_pad}{pred} \"{n}\"^^<http://www.w3.org/2001/XMLSchema#nonNegativeInteger> ;"
1348 )
1349 .ok();
1350 writeln!(out, "{inner_pad}owl:onClass {filler}").ok();
1351 write!(out, "{pad}]").ok();
1352 Ok(out)
1353}
1354
1355fn data_cardinality_turtle(
1356 pred: &str,
1357 n: u32,
1358 property_iri: &str,
1359 dr: &DataRange<RcStr>,
1360 namespaces: &BTreeMap<String, String>,
1361 indent: usize,
1362) -> Result<String> {
1363 let pad = " ".repeat(indent);
1364 let inner_pad = " ".repeat(indent + 1);
1365 let prop = iri_to_turtle_term(property_iri, namespaces)?;
1366 let range = data_range_to_turtle(dr, namespaces)?;
1367 let mut out = String::new();
1368 writeln!(out, "[").ok();
1369 writeln!(out, "{inner_pad}a owl:Restriction ;").ok();
1370 writeln!(out, "{inner_pad}owl:onProperty {prop} ;").ok();
1371 writeln!(
1372 out,
1373 "{inner_pad}{pred} \"{n}\"^^<http://www.w3.org/2001/XMLSchema#nonNegativeInteger> ;"
1374 )
1375 .ok();
1376 writeln!(out, "{inner_pad}owl:onDataRange {range}").ok();
1377 write!(out, "{pad}]").ok();
1378 Ok(out)
1379}
1380
1381fn data_range_to_turtle(
1382 dr: &DataRange<RcStr>,
1383 namespaces: &BTreeMap<String, String>,
1384) -> Result<String> {
1385 match dr {
1386 DataRange::Datatype(dt) => iri_to_turtle_term(dt.0.as_ref(), namespaces),
1387 DataRange::DatatypeRestriction(dt, facets) => {
1388 let on = iri_to_turtle_term(dt.0.as_ref(), namespaces)?;
1389 let mut restrictions = String::new();
1390 for f in facets {
1391 let facet_iri = f.f.as_ref();
1392 let facet_term = iri_to_turtle_term(facet_iri, namespaces)?;
1393 let lit = format!("\"{}\"", escape_turtle_string_local(literal_lexical(&f.l)));
1394 write!(restrictions, " [ {facet_term} {lit} ]").ok();
1395 }
1396 Ok(format!(
1397 "[ a rdfs:Datatype ; owl:onDatatype {on} ; owl:withRestrictions ({restrictions} ) ]"
1398 ))
1399 }
1400 DataRange::DataOneOf(lits) => {
1401 let members = lits
1402 .iter()
1403 .map(|l| format!("\"{}\"", escape_turtle_string_local(literal_lexical(l))))
1404 .collect::<Vec<_>>()
1405 .join(" ");
1406 Ok(format!("[ a rdfs:Datatype ; owl:oneOf ( {members} ) ]"))
1407 }
1408 DataRange::DataComplementOf(inner) => {
1409 let inner_t = data_range_to_turtle(inner, namespaces)?;
1410 Ok(format!("[ a rdfs:Datatype ; owl:datatypeComplementOf {inner_t} ]"))
1411 }
1412 DataRange::DataIntersectionOf(parts) => {
1413 let mut members = String::new();
1414 for p in parts {
1415 let t = data_range_to_turtle(p, namespaces)?;
1416 write!(members, " {t}").ok();
1417 }
1418 Ok(format!("[ a rdfs:Datatype ; owl:intersectionOf ({members} ) ]"))
1419 }
1420 DataRange::DataUnionOf(parts) => {
1421 let mut members = String::new();
1422 for p in parts {
1423 let t = data_range_to_turtle(p, namespaces)?;
1424 write!(members, " {t}").ok();
1425 }
1426 Ok(format!("[ a rdfs:Datatype ; owl:unionOf ({members} ) ]"))
1427 }
1428 }
1429}
1430
1431pub fn data_range_to_turtle_term(
1433 dr: &DataRange<RcStr>,
1434 namespaces: &BTreeMap<String, String>,
1435) -> Result<String> {
1436 data_range_to_turtle(dr, namespaces)
1437}
1438
1439fn data_cardinality_manchester(
1440 prop: &str,
1441 keyword: &str,
1442 n: u32,
1443 dr: &DataRange<RcStr>,
1444 namespaces: &BTreeMap<String, String>,
1445) -> String {
1446 format!(
1447 "{} {keyword} {n} {}",
1448 iri_to_manchester_term(prop, namespaces),
1449 data_range_to_manchester(dr, namespaces)
1450 )
1451}
1452
1453fn literal_lexical(l: &Literal<RcStr>) -> &str {
1454 match l {
1455 Literal::Simple { literal } => literal.as_str(),
1456 Literal::Language { literal, .. } => literal.as_str(),
1457 Literal::Datatype { literal, .. } => literal.as_str(),
1458 }
1459}
1460
1461fn escape_turtle_string_local(value: &str) -> String {
1462 let mut out = String::with_capacity(value.len());
1463 for ch in value.chars() {
1464 match ch {
1465 '\\' => out.push_str("\\\\"),
1466 '"' => out.push_str("\\\""),
1467 '\n' => out.push_str("\\n"),
1468 '\r' => out.push_str("\\r"),
1469 '\t' => out.push_str("\\t"),
1470 c => out.push(c),
1471 }
1472 }
1473 out
1474}
1475
1476fn is_owl_thing(expr: &ClassExpression<RcStr>) -> bool {
1477 matches!(
1478 expr,
1479 ClassExpression::Class(c) if c.as_ref() == "http://www.w3.org/2002/07/owl#Thing"
1480 )
1481}
1482
1483fn cardinality_manchester(
1484 prop: &str,
1485 keyword: &str,
1486 n: u32,
1487 bce: &ClassExpression<RcStr>,
1488 namespaces: &BTreeMap<String, String>,
1489) -> String {
1490 if is_owl_thing(bce) {
1491 format!("{prop} {keyword} {n}")
1492 } else {
1493 let filler = class_expression_to_manchester(bce, namespaces);
1494 format!("{prop} {keyword} {n} {filler}")
1495 }
1496}
1497
1498fn ope_to_iri(ope: &ObjectPropertyExpression<RcStr>) -> String {
1499 match ope {
1500 ObjectPropertyExpression::ObjectProperty(p) => p.0.as_ref().to_string(),
1501 ObjectPropertyExpression::InverseObjectProperty(p) => {
1502 format!("inverse {}", p.0.as_ref())
1503 }
1504 }
1505}
1506
1507fn iri_to_manchester_term(iri: &str, namespaces: &BTreeMap<String, String>) -> String {
1508 if !crate::patch::is_safe_iri(iri) {
1509 return iri.chars().filter(|c| !c.is_control() && !c.is_whitespace()).collect();
1511 }
1512 if let Some((prefix, ns)) = crate::patch::best_namespace_match(iri, namespaces) {
1513 let local = &iri[ns.len()..];
1514 return format!("{prefix}:{local}");
1515 }
1516 if iri.starts_with("http://") || iri.starts_with("https://") {
1517 format!("<{iri}>")
1518 } else {
1519 iri.to_string()
1520 }
1521}
1522
1523fn iri_to_turtle_term(iri: &str, namespaces: &BTreeMap<String, String>) -> Result<String> {
1524 crate::patch::iri_to_turtle_term_impl(iri, namespaces)
1525 .map_err(|e| OwlError::ManchesterInvalid(e.to_string()))
1526}
1527
1528#[cfg(test)]
1529mod tests {
1530 use super::*;
1531
1532 fn ex_ns() -> BTreeMap<String, String> {
1533 BTreeMap::from([
1534 ("ex".to_string(), "http://example.org/people#".to_string()),
1535 ("owl".to_string(), "http://www.w3.org/2002/07/owl#".to_string()),
1536 ("rdfs".to_string(), "http://www.w3.org/2000/01/rdf-schema#".to_string()),
1537 ])
1538 }
1539
1540 fn clinic_ns() -> BTreeMap<String, String> {
1541 BTreeMap::from([
1542 ("ex".to_string(), "http://example.org/clinic#".to_string()),
1543 ("owl".to_string(), "http://www.w3.org/2002/07/owl#".to_string()),
1544 ])
1545 }
1546
1547 fn anatomy_ns() -> BTreeMap<String, String> {
1548 BTreeMap::from([
1549 ("ex".to_string(), "http://example.org/anatomy#".to_string()),
1550 ("owl".to_string(), "http://www.w3.org/2002/07/owl#".to_string()),
1551 ])
1552 }
1553
1554 #[test]
1555 fn parse_property_first_min_cardinality() {
1556 let ns = anatomy_ns();
1557 let out = parse_class_expression("ex:hasPart min 1 ex:Organ", &ns).expect("parse");
1558 assert!(out.normalized.contains("ex:hasPart min 1 ex:Organ"));
1559 }
1560
1561 #[test]
1562 fn parse_property_first_without_filler() {
1563 let ns = anatomy_ns();
1564 let out = parse_class_expression("ex:hasPart min 1", &ns).expect("parse");
1565 assert_eq!(out.normalized, "ex:hasPart min 1");
1566 match &out.expression {
1567 ClassExpression::ObjectMinCardinality { bce, .. } => {
1568 assert!(is_owl_thing(bce));
1569 }
1570 other => panic!("expected min cardinality, got {other:?}"),
1571 }
1572 }
1573
1574 #[test]
1575 fn unqualified_cardinality_without_owl_prefix() {
1576 let ns = BTreeMap::from([("ex".to_string(), "http://example.org/".to_string())]);
1577 let out = parse_class_expression("ex:p min 1", &ns).expect("parse without owl prefix");
1578 match &out.expression {
1579 ClassExpression::ObjectMinCardinality { bce, .. } => {
1580 assert!(is_owl_thing(bce), "default filler must be owl:Thing");
1581 }
1582 other => panic!("expected min cardinality, got {other:?}"),
1583 }
1584 }
1585
1586 #[test]
1587 fn parse_keyword_first_cardinality_without_some() {
1588 let ns = anatomy_ns();
1589 let out = parse_class_expression("min 1 ex:hasPart ex:Organ", &ns).expect("parse");
1590 assert!(out.normalized.contains("ex:hasPart min 1 ex:Organ"));
1591 }
1592
1593 #[test]
1594 fn cardinality_turtle_emits_typed_literal() {
1595 let ns = anatomy_ns();
1596 for input in [
1597 "ex:hasPart min 1 ex:Organ",
1598 "ex:hasPart max 2 ex:Organ",
1599 "ex:hasPart exactly 3 ex:Organ",
1600 ] {
1601 let out = parse_class_expression(input, &ns).expect("parse");
1602 let turtle = class_expression_to_turtle_value(&out.expression, &ns, 0).expect("turtle");
1603 assert!(
1604 turtle.contains("^^<http://www.w3.org/2001/XMLSchema#nonNegativeInteger>"),
1605 "expected typed cardinality literal in: {turtle}"
1606 );
1607 assert!(!turtle.contains("minQualifiedCardinality 1 ;"));
1608 assert!(!turtle.contains("maxQualifiedCardinality 2 ;"));
1609 assert!(!turtle.contains("qualifiedCardinality 3 ;"));
1610 }
1611 }
1612
1613 #[test]
1614 fn cardinality_round_trip_property_first() {
1615 let ns = anatomy_ns();
1616 let out = parse_class_expression("ex:hasPart min 1 ex:Organ", &ns).expect("parse");
1617 let turtle = class_expression_to_turtle_fragment(&out.expression, "rdfs:subClassOf", &ns)
1618 .expect("turtle");
1619 assert!(turtle.contains("owl:Restriction"));
1620 assert!(turtle.contains("owl:minQualifiedCardinality"));
1621 assert!(turtle.contains("owl:onClass"));
1622 assert!(turtle.contains("^^<http://www.w3.org/2001/XMLSchema#nonNegativeInteger>"));
1623 }
1624
1625 #[test]
1626 fn parse_some_values_from() {
1627 let ns = clinic_ns();
1628 let out = parse_class_expression("ex:hasRecord some ex:MedicalRecord", &ns).expect("parse");
1629 assert!(out.normalized.contains("some"));
1630 }
1631
1632 #[test]
1633 fn parse_and_expression() {
1634 let ns = ex_ns();
1635 let out = parse_class_expression("ex:Person and ex:Organization", &ns).expect("parse");
1636 assert!(out.normalized.contains("and"));
1637 }
1638
1639 #[test]
1640 fn turtle_fragment_for_restriction() {
1641 let ns = clinic_ns();
1642 let out = parse_class_expression("ex:hasRecord some ex:MedicalRecord", &ns).expect("parse");
1643 let turtle = class_expression_to_turtle_fragment(&out.expression, "rdfs:subClassOf", &ns)
1644 .expect("turtle");
1645 assert!(turtle.contains("owl:Restriction"));
1646 assert!(turtle.contains("owl:someValuesFrom"));
1647 }
1648
1649 #[test]
1650 fn turtle_intersection_includes_all_operands() {
1651 let ns = ex_ns();
1652 let out = parse_class_expression("ex:Person and ex:Organization", &ns).expect("parse");
1653 let turtle = class_expression_to_turtle_value(&out.expression, &ns, 0).expect("turtle");
1654 assert!(turtle.contains("ex:Person"));
1655 assert!(turtle.contains("ex:Organization"));
1656 assert!(turtle.contains("owl:intersectionOf"));
1657 }
1658
1659 #[test]
1660 fn turtle_term_longest_namespace_prefix_wins() {
1661 let ns = BTreeMap::from([
1662 ("ex".to_string(), "http://example.org/".to_string()),
1663 ("exfoo".to_string(), "http://example.org/foo/".to_string()),
1664 ("owl".to_string(), "http://www.w3.org/2002/07/owl#".to_string()),
1665 ]);
1666 let out = parse_class_expression("exfoo:Bar", &ns).expect("parse");
1667 let turtle = class_expression_to_turtle_fragment(&out.expression, "rdfs:subClassOf", &ns)
1668 .expect("turtle");
1669 assert!(turtle.contains("exfoo:Bar"));
1670 assert!(!turtle.contains("ex:foo/Bar"));
1671 }
1672
1673 #[test]
1674 fn parse_not_complement() {
1675 let ns = ex_ns();
1676 let out = parse_class_expression("not ex:Person", &ns).expect("parse not");
1677 assert!(matches!(out.expression, ClassExpression::ObjectComplementOf(_)));
1678 let turtle = class_expression_to_turtle_value(&out.expression, &ns, 0).expect("turtle");
1679 assert!(turtle.contains("owl:complementOf"));
1680 }
1681
1682 #[test]
1683 fn parse_has_value_and_has_self() {
1684 let ns = clinic_ns();
1685 let out = parse_class_expression("ex:hasRecord value ex:rec1", &ns).expect("has value");
1686 assert!(matches!(out.expression, ClassExpression::ObjectHasValue { .. }));
1687 let out = parse_class_expression("ex:likes Self", &ns).expect("has self");
1688 assert!(matches!(out.expression, ClassExpression::ObjectHasSelf(_)));
1689 }
1690
1691 #[test]
1692 fn parse_one_of() {
1693 let ns = ex_ns();
1694 let out = parse_class_expression("{ ex:Person ex:Organization }", &ns).expect("one of");
1695 match out.expression {
1696 ClassExpression::ObjectOneOf(inds) => assert_eq!(inds.len(), 2),
1697 other => panic!("expected oneOf, got {other:?}"),
1698 }
1699 }
1700
1701 #[test]
1702 fn parse_data_some_with_xsd() {
1703 let ns = BTreeMap::from([
1704 ("ex".to_string(), "http://example.org/".to_string()),
1705 ("xsd".to_string(), "http://www.w3.org/2001/XMLSchema#".to_string()),
1706 ]);
1707 let out = parse_class_expression("ex:age some xsd:integer", &ns).expect("data some");
1708 assert!(matches!(out.expression, ClassExpression::DataSomeValuesFrom { .. }));
1709 }
1710
1711 #[test]
1712 fn parse_data_some_with_known_custom_datatype() {
1713 let ns = BTreeMap::from([("ex".to_string(), "http://example.org/".to_string())]);
1715 let known = std::collections::BTreeSet::from(["http://example.org/SSN".to_string()]);
1716 let out = parse_class_expression_with_datatypes("ex:hasSSN some ex:SSN", &ns, &known)
1717 .expect("custom datatype filler");
1718 assert!(matches!(out.expression, ClassExpression::DataSomeValuesFrom { .. }));
1719 let as_class = parse_class_expression("ex:hasSSN some ex:SSN", &ns).expect("no known");
1720 assert!(matches!(as_class.expression, ClassExpression::ObjectSomeValuesFrom { .. }));
1721 }
1722
1723 #[test]
1724 fn parse_data_range_facets_and_one_of() {
1725 let ns =
1726 BTreeMap::from([("xsd".to_string(), "http://www.w3.org/2001/XMLSchema#".to_string())]);
1727 let restricted = parse_data_range("xsd:integer[>= 0][<= 10]", &ns).expect("facets");
1728 match restricted {
1729 DataRange::DatatypeRestriction(_, facets) => assert_eq!(facets.len(), 2),
1730 other => panic!("expected DatatypeRestriction, got {other:?}"),
1731 }
1732 let one_of = parse_data_range("{\"a\", \"b\"}", &ns).expect("oneOf");
1733 assert!(matches!(one_of, DataRange::DataOneOf(_)));
1734 let complement = parse_data_range("not xsd:string", &ns).expect("not");
1735 assert!(matches!(complement, DataRange::DataComplementOf(_)));
1736 }
1737}