001package org.hl7.fhir.r4.elementmodel; 002 003/* 004 Copyright (c) 2011+, HL7, Inc. 005 All rights reserved. 006 007 Redistribution and use in source and binary forms, with or without modification, 008 are permitted provided that the following conditions are met: 009 010 * Redistributions of source code must retain the above copyright notice, this 011 list of conditions and the following disclaimer. 012 * Redistributions in binary form must reproduce the above copyright notice, 013 this list of conditions and the following disclaimer in the documentation 014 and/or other materials provided with the distribution. 015 * Neither the name of HL7 nor the names of its contributors may be used to 016 endorse or promote products derived from this software without specific 017 prior written permission. 018 019 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 022 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 023 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 024 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 025 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 026 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 027 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 028 POSSIBILITY OF SUCH DAMAGE. 029 030 */ 031 032import java.io.IOException; 033import java.io.InputStream; 034import java.io.OutputStream; 035import java.util.HashSet; 036import java.util.List; 037import java.util.Set; 038 039import org.hl7.fhir.exceptions.FHIRException; 040import org.hl7.fhir.exceptions.FHIRFormatError; 041import org.hl7.fhir.r4.context.IWorkerContext; 042import org.hl7.fhir.r4.elementmodel.Element.SpecialElement; 043import org.hl7.fhir.r4.formats.IParser.OutputStyle; 044import org.hl7.fhir.r4.model.ElementDefinition.TypeRefComponent; 045import org.hl7.fhir.r4.model.StructureDefinition; 046import org.hl7.fhir.r4.utils.SnomedExpressions; 047import org.hl7.fhir.r4.utils.SnomedExpressions.Expression; 048import org.hl7.fhir.r4.utils.formats.Turtle; 049import org.hl7.fhir.r4.utils.formats.Turtle.Complex; 050import org.hl7.fhir.r4.utils.formats.Turtle.Section; 051import org.hl7.fhir.r4.utils.formats.Turtle.Subject; 052import org.hl7.fhir.r4.utils.formats.Turtle.TTLComplex; 053import org.hl7.fhir.r4.utils.formats.Turtle.TTLList; 054import org.hl7.fhir.r4.utils.formats.Turtle.TTLLiteral; 055import org.hl7.fhir.r4.utils.formats.Turtle.TTLObject; 056import org.hl7.fhir.r4.utils.formats.Turtle.TTLURL; 057import org.hl7.fhir.utilities.TextFile; 058import org.hl7.fhir.utilities.Utilities; 059import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; 060import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; 061 062public class TurtleParser extends ParserBase { 063 064 private String base; 065 066 public static String FHIR_URI_BASE = "http://hl7.org/fhir/"; 067 public static String FHIR_VERSION_BASE = "http://build.fhir.org/"; 068 069 public TurtleParser(IWorkerContext context) { 070 super(context); 071 } 072 073 @Override 074 public Element parse(InputStream input) throws IOException, FHIRException { 075 Turtle src = new Turtle(); 076 if (policy == ValidationPolicy.EVERYTHING) { 077 try { 078 src.parse(TextFile.streamToString(input)); 079 } catch (Exception e) { 080 logError(-1, -1, "(document)", IssueType.INVALID, "Error parsing Turtle: " + e.getMessage(), 081 IssueSeverity.FATAL); 082 return null; 083 } 084 return parse(src); 085 } else { 086 src.parse(TextFile.streamToString(input)); 087 return parse(src); 088 } 089 } 090 091 private Element parse(Turtle src) throws FHIRException { 092 // we actually ignore the stated URL here 093 for (TTLComplex cmp : src.getObjects().values()) { 094 for (String p : cmp.getPredicates().keySet()) { 095 if ((FHIR_URI_BASE + "nodeRole").equals(p) && cmp.getPredicates().get(p).hasValue(FHIR_URI_BASE + "treeRoot")) { 096 return parse(src, cmp); 097 } 098 } 099 } 100 // still here: well, we didn't find a start point 101 String msg = "Error parsing Turtle: unable to find any node maked as the entry point (where " + FHIR_URI_BASE 102 + "nodeRole = " + FHIR_URI_BASE + "treeRoot)"; 103 if (policy == ValidationPolicy.EVERYTHING) { 104 logError(-1, -1, "(document)", IssueType.INVALID, msg, IssueSeverity.FATAL); 105 return null; 106 } else { 107 throw new FHIRFormatError(msg); 108 } 109 } 110 111 private Element parse(Turtle src, TTLComplex cmp) throws FHIRException { 112 TTLObject type = cmp.getPredicates().get("http://www.w3.org/2000/01/rdf-schema#type"); 113 if (type == null) { 114 logError(cmp.getLine(), cmp.getCol(), "(document)", IssueType.INVALID, 115 "Unknown resource type (missing rdfs:type)", IssueSeverity.FATAL); 116 return null; 117 } 118 if (type instanceof TTLList) { 119 // this is actually broken - really we have to look through the structure 120 // definitions at this point 121 for (TTLObject obj : ((TTLList) type).getList()) { 122 if (obj instanceof TTLURL && ((TTLURL) obj).getUri().startsWith(FHIR_URI_BASE)) { 123 type = obj; 124 break; 125 } 126 } 127 } 128 if (!(type instanceof TTLURL)) { 129 logError(cmp.getLine(), cmp.getCol(), "(document)", IssueType.INVALID, "Unexpected datatype for rdfs:type)", 130 IssueSeverity.FATAL); 131 return null; 132 } 133 String name = ((TTLURL) type).getUri(); 134 String ns = name.substring(0, name.lastIndexOf("/")); 135 name = name.substring(name.lastIndexOf("/") + 1); 136 String path = "/" + name; 137 138 StructureDefinition sd = getDefinition(cmp.getLine(), cmp.getCol(), ns, name); 139 if (sd == null) 140 return null; 141 142 Element result = new Element(name, new Property(context, sd.getSnapshot().getElement().get(0), sd)); 143 result.markLocation(cmp.getLine(), cmp.getCol()); 144 result.setType(name); 145 parseChildren(src, path, cmp, result, false); 146 result.numberChildren(); 147 return result; 148 } 149 150 private void parseChildren(Turtle src, String path, TTLComplex object, Element context, boolean primitive) 151 throws FHIRException { 152 153 List<Property> properties = context.getProperty().getChildProperties(context.getName(), null); 154 Set<String> processed = new HashSet<String>(); 155 if (primitive) 156 processed.add(FHIR_URI_BASE + "value"); 157 158 // note that we do not trouble ourselves to maintain the wire format order here 159 // - we don't even know what it was anyway 160 // first pass: process the properties 161 for (Property property : properties) { 162 if (property.isChoice()) { 163 for (TypeRefComponent type : property.getDefinition().getType()) { 164 String eName = property.getName().substring(0, property.getName().length() - 3) 165 + Utilities.capitalize(type.getCode()); 166 parseChild(src, object, context, processed, property, path, getFormalName(property, eName)); 167 } 168 } else { 169 parseChild(src, object, context, processed, property, path, getFormalName(property)); 170 } 171 } 172 173 // second pass: check for things not processed 174 if (policy != ValidationPolicy.NONE) { 175 for (String u : object.getPredicates().keySet()) { 176 if (!processed.contains(u)) { 177 TTLObject n = object.getPredicates().get(u); 178 logError(n.getLine(), n.getCol(), path, IssueType.STRUCTURE, "Unrecognised predicate '" + u + "'", 179 IssueSeverity.ERROR); 180 } 181 } 182 } 183 } 184 185 private void parseChild(Turtle src, TTLComplex object, Element context, Set<String> processed, Property property, 186 String path, String name) throws FHIRException { 187 processed.add(name); 188 String npath = path + "/" + property.getName(); 189 TTLObject e = object.getPredicates().get(FHIR_URI_BASE + name); 190 if (e == null) 191 return; 192 if (property.isList() && (e instanceof TTLList)) { 193 TTLList arr = (TTLList) e; 194 for (TTLObject am : arr.getList()) { 195 parseChildInstance(src, npath, object, context, property, name, am); 196 } 197 } else { 198 parseChildInstance(src, npath, object, context, property, name, e); 199 } 200 } 201 202 private void parseChildInstance(Turtle src, String npath, TTLComplex object, Element context, Property property, 203 String name, TTLObject e) throws FHIRException { 204 if (property.isResource()) 205 parseResource(src, npath, object, context, property, name, e); 206 else if (e instanceof TTLComplex) { 207 TTLComplex child = (TTLComplex) e; 208 Element n = new Element(tail(name), property).markLocation(e.getLine(), e.getCol()); 209 context.getChildren().add(n); 210 if (property.isPrimitive(property.getType(tail(name)))) { 211 parseChildren(src, npath, child, n, true); 212 TTLObject val = child.getPredicates().get(FHIR_URI_BASE + "value"); 213 if (val != null) { 214 if (val instanceof TTLLiteral) { 215 String value = ((TTLLiteral) val).getValue(); 216 String type = ((TTLLiteral) val).getType(); 217 // todo: check type 218 n.setValue(value); 219 } else 220 logError(object.getLine(), object.getCol(), npath, IssueType.INVALID, 221 "This property must be a Literal, not a " + e.getClass().getName(), IssueSeverity.ERROR); 222 } 223 } else 224 parseChildren(src, npath, child, n, false); 225 226 } else 227 logError(object.getLine(), object.getCol(), npath, IssueType.INVALID, 228 "This property must be a URI or bnode, not a " + e.getClass().getName(), IssueSeverity.ERROR); 229 } 230 231 private String tail(String name) { 232 return name.substring(name.lastIndexOf(".") + 1); 233 } 234 235 private void parseResource(Turtle src, String npath, TTLComplex object, Element context, Property property, 236 String name, TTLObject e) throws FHIRException { 237 TTLComplex obj; 238 if (e instanceof TTLComplex) 239 obj = (TTLComplex) e; 240 else if (e instanceof TTLURL) { 241 String url = ((TTLURL) e).getUri(); 242 obj = src.getObject(url); 243 if (obj == null) { 244 logError(e.getLine(), e.getCol(), npath, IssueType.INVALID, "reference to " + url + " cannot be resolved", 245 IssueSeverity.FATAL); 246 return; 247 } 248 } else 249 throw new FHIRFormatError("Wrong type for resource"); 250 251 TTLObject type = obj.getPredicates().get("http://www.w3.org/2000/01/rdf-schema#type"); 252 if (type == null) { 253 logError(object.getLine(), object.getCol(), npath, IssueType.INVALID, "Unknown resource type (missing rdfs:type)", 254 IssueSeverity.FATAL); 255 return; 256 } 257 if (type instanceof TTLList) { 258 // this is actually broken - really we have to look through the structure 259 // definitions at this point 260 for (TTLObject tobj : ((TTLList) type).getList()) { 261 if (tobj instanceof TTLURL && ((TTLURL) tobj).getUri().startsWith(FHIR_URI_BASE)) { 262 type = tobj; 263 break; 264 } 265 } 266 } 267 if (!(type instanceof TTLURL)) { 268 logError(object.getLine(), object.getCol(), npath, IssueType.INVALID, "Unexpected datatype for rdfs:type)", 269 IssueSeverity.FATAL); 270 return; 271 } 272 String rt = ((TTLURL) type).getUri(); 273 String ns = rt.substring(0, rt.lastIndexOf("/")); 274 rt = rt.substring(rt.lastIndexOf("/") + 1); 275 276 StructureDefinition sd = getDefinition(object.getLine(), object.getCol(), ns, rt); 277 if (sd == null) 278 return; 279 280 Element n = new Element(tail(name), property).markLocation(object.getLine(), object.getCol()); 281 context.getChildren().add(n); 282 n.updateProperty(new Property(this.context, sd.getSnapshot().getElement().get(0), sd), 283 SpecialElement.fromProperty(n.getProperty()), property); 284 n.setType(rt); 285 parseChildren(src, npath, obj, n, false); 286 } 287 288 private String getFormalName(Property property) { 289 String en = property.getDefinition().getBase().getPath(); 290 if (en == null) 291 en = property.getDefinition().getPath(); 292// boolean doType = false; 293// if (en.endsWith("[x]")) { 294// en = en.substring(0, en.length()-3); 295// doType = true; 296// } 297// if (doType || (element.getProperty().getDefinition().getType().size() > 1 && !allReference(element.getProperty().getDefinition().getType()))) 298// en = en + Utilities.capitalize(element.getType()); 299 return en; 300 } 301 302 private String getFormalName(Property property, String elementName) { 303 String en = property.getDefinition().getBase().getPath(); 304 if (en == null) 305 en = property.getDefinition().getPath(); 306 if (!en.endsWith("[x]")) 307 throw new Error("Attempt to replace element name for a non-choice type"); 308 return en.substring(0, en.lastIndexOf(".") + 1) + elementName; 309 } 310 311 @Override 312 public void compose(Element e, OutputStream stream, OutputStyle style, String base) 313 throws IOException, FHIRException { 314 this.base = base; 315 316 Turtle ttl = new Turtle(); 317 compose(e, ttl, base); 318 ttl.commit(stream, false); 319 } 320 321 public void compose(Element e, Turtle ttl, String base) throws FHIRException { 322 ttl.prefix("fhir", FHIR_URI_BASE); 323 ttl.prefix("rdfs", "http://www.w3.org/2000/01/rdf-schema#"); 324 ttl.prefix("owl", "http://www.w3.org/2002/07/owl#"); 325 ttl.prefix("xsd", "http://www.w3.org/2001/XMLSchema#"); 326 327 Section section = ttl.section("resource"); 328 String subjId = genSubjectId(e); 329 330 String ontologyId = subjId.replace(">", ".ttl>"); 331 Section ontology = ttl.section("ontology header"); 332 ontology.triple(ontologyId, "a", "owl:Ontology"); 333 ontology.triple(ontologyId, "owl:imports", "fhir:fhir.ttl"); 334 if (ontologyId.startsWith("<" + FHIR_URI_BASE)) 335 ontology.triple(ontologyId, "owl:versionIRI", ontologyId.replace(FHIR_URI_BASE, FHIR_VERSION_BASE)); 336 337 Subject subject = section.triple(subjId, "a", "fhir:" + e.getType()); 338 subject.linkedPredicate("fhir:nodeRole", "fhir:treeRoot", 339 linkResolver == null ? null : linkResolver.resolvePage("rdf.html#tree-root")); 340 341 for (Element child : e.getChildren()) { 342 composeElement(section, subject, child, null); 343 } 344 345 } 346 347 protected String getURIType(String uri) { 348 if (uri.startsWith("<" + FHIR_URI_BASE)) 349 if (uri.substring(FHIR_URI_BASE.length() + 1).contains("/")) 350 return uri.substring(FHIR_URI_BASE.length() + 1, uri.indexOf('/', FHIR_URI_BASE.length() + 1)); 351 return null; 352 } 353 354 protected String getReferenceURI(String ref) { 355 if (ref != null && (ref.startsWith("http://") || ref.startsWith("https://"))) 356 return "<" + ref + ">"; 357 else if (base != null && ref != null && ref.contains("/")) 358 return "<" + Utilities.appendForwardSlash(base) + ref + ">"; 359 else 360 return null; 361 } 362 363 protected void decorateReference(Complex t, Element coding) { 364 String refURI = getReferenceURI(coding.getChildValue("reference")); 365 if (refURI != null) 366 t.linkedPredicate("fhir:link", refURI, 367 linkResolver == null ? null : linkResolver.resolvePage("rdf.html#reference")); 368 } 369 370 protected void decorateCanonical(Complex t, Element canonical) { 371 String refURI = getReferenceURI(canonical.primitiveValue()); 372 if (refURI != null) 373 t.linkedPredicate("fhir:link", refURI, 374 linkResolver == null ? null : linkResolver.resolvePage("rdf.html#reference")); 375 } 376 377 private String genSubjectId(Element e) { 378 String id = e.getChildValue("id"); 379 if (base == null || id == null) 380 return ""; 381 else if (base.endsWith("#")) 382 return "<" + base + e.getType() + "-" + id + ">"; 383 else 384 return "<" + Utilities.pathURL(base, e.getType(), id) + ">"; 385 } 386 387 private String urlescape(String s) { 388 StringBuilder b = new StringBuilder(); 389 for (char ch : s.toCharArray()) { 390 if (Utilities.charInSet(ch, ':', ';', '=', ',')) 391 b.append("%" + Integer.toHexString(ch)); 392 else 393 b.append(ch); 394 } 395 return b.toString(); 396 } 397 398 private void composeElement(Section section, Complex ctxt, Element element, Element parent) throws FHIRException { 399// "Extension".equals(element.getType())? 400// (element.getProperty().getDefinition().getIsModifier()? "modifierExtension" : "extension") ; 401 String en = getFormalName(element); 402 403 Complex t; 404 if (element.getSpecial() == SpecialElement.BUNDLE_ENTRY && parent != null 405 && parent.getNamedChildValue("fullUrl") != null) { 406 String url = "<" + parent.getNamedChildValue("fullUrl") + ">"; 407 ctxt.linkedPredicate("fhir:" + en, url, 408 linkResolver == null ? null : linkResolver.resolveProperty(element.getProperty())); 409 t = section.subject(url); 410 } else { 411 t = ctxt.linkedPredicate("fhir:" + en, 412 linkResolver == null ? null : linkResolver.resolveProperty(element.getProperty())); 413 } 414 if (element.getSpecial() != null) 415 t.linkedPredicate("a", "fhir:" + element.fhirType(), 416 linkResolver == null ? null : linkResolver.resolveType(element.fhirType())); 417 if (element.hasValue()) 418 t.linkedPredicate("fhir:value", ttlLiteral(element.getValue(), element.getType()), 419 linkResolver == null ? null : linkResolver.resolveType(element.getType())); 420 if (element.getProperty().isList() && (!element.isResource() || element.getSpecial() == SpecialElement.CONTAINED)) 421 t.linkedPredicate("fhir:index", Integer.toString(element.getIndex()), 422 linkResolver == null ? null : linkResolver.resolvePage("rdf.html#index")); 423 424 if ("Coding".equals(element.getType())) 425 decorateCoding(t, element, section); 426 if (Utilities.existsInList(element.getType(), "Reference")) 427 decorateReference(t, element); 428 else if (Utilities.existsInList(element.getType(), "canonical")) 429 decorateCanonical(t, element); 430 431 if ("canonical".equals(element.getType())) { 432 String refURI = element.primitiveValue(); 433 if (refURI != null) { 434 String uriType = getURIType(refURI); 435 if (uriType != null && !section.hasSubject(refURI)) 436 section.triple(refURI, "a", "fhir:" + uriType); 437 } 438 } 439 440 if ("Reference".equals(element.getType())) { 441 String refURI = getReferenceURI(element.getChildValue("reference")); 442 if (refURI != null) { 443 String uriType = getURIType(refURI); 444 if (uriType != null && !section.hasSubject(refURI)) 445 section.triple(refURI, "a", "fhir:" + uriType); 446 } 447 } 448 449 for (Element child : element.getChildren()) { 450 if ("xhtml".equals(child.getType())) { 451 String childfn = getFormalName(child); 452 t.predicate("fhir:" + childfn, ttlLiteral(child.getValue(), child.getType())); 453 } else 454 composeElement(section, t, child, element); 455 } 456 } 457 458 private String getFormalName(Element element) { 459 String en = null; 460 if (element.getSpecial() == null) { 461 if (element.getProperty().getDefinition().hasBase()) 462 en = element.getProperty().getDefinition().getBase().getPath(); 463 } else if (element.getSpecial() == SpecialElement.BUNDLE_ENTRY) 464 en = "Bundle.entry.resource"; 465 else if (element.getSpecial() == SpecialElement.BUNDLE_OUTCOME) 466 en = "Bundle.entry.response.outcome"; 467 else if (element.getSpecial() == SpecialElement.PARAMETER) 468 en = element.getElementProperty().getDefinition().getPath(); 469 else // CONTAINED 470 en = "DomainResource.contained"; 471 472 if (en == null) 473 en = element.getProperty().getDefinition().getPath(); 474 boolean doType = false; 475 if (en.endsWith("[x]")) { 476 en = en.substring(0, en.length() - 3); 477 doType = true; 478 } 479 if (doType || (element.getProperty().getDefinition().getType().size() > 1 480 && !allReference(element.getProperty().getDefinition().getType()))) 481 en = en + Utilities.capitalize(element.getType()); 482 return en; 483 } 484 485 private boolean allReference(List<TypeRefComponent> types) { 486 for (TypeRefComponent t : types) { 487 if (!t.getCode().equals("Reference")) 488 return false; 489 } 490 return true; 491 } 492 493 static public String ttlLiteral(String value, String type) { 494 String xst = ""; 495 if (type.equals("boolean")) 496 xst = "^^xsd:boolean"; 497 else if (type.equals("integer")) 498 xst = "^^xsd:integer"; 499 else if (type.equals("unsignedInt")) 500 xst = "^^xsd:nonNegativeInteger"; 501 else if (type.equals("positiveInt")) 502 xst = "^^xsd:positiveInteger"; 503 else if (type.equals("decimal")) 504 xst = "^^xsd:decimal"; 505 else if (type.equals("base64Binary")) 506 xst = "^^xsd:base64Binary"; 507 else if (type.equals("instant")) 508 xst = "^^xsd:dateTime"; 509 else if (type.equals("time")) 510 xst = "^^xsd:time"; 511 else if (type.equals("date") || type.equals("dateTime")) { 512 String v = value; 513 if (v.length() > 10) { 514 int i = value.substring(10).indexOf("-"); 515 if (i == -1) 516 i = value.substring(10).indexOf("+"); 517 v = i == -1 ? value : value.substring(0, 10 + i); 518 } 519 if (v.length() > 10) 520 xst = "^^xsd:dateTime"; 521 else if (v.length() == 10) 522 xst = "^^xsd:date"; 523 else if (v.length() == 7) 524 xst = "^^xsd:gYearMonth"; 525 else if (v.length() == 4) 526 xst = "^^xsd:gYear"; 527 } 528 529 return "\"" + Turtle.escape(value, true) + "\"" + xst; 530 } 531 532 protected void decorateCoding(Complex t, Element coding, Section section) throws FHIRException { 533 String system = coding.getChildValue("system"); 534 String code = coding.getChildValue("code"); 535 536 if (system == null || code == null) 537 return; 538 if ("http://snomed.info/sct".equals(system)) { 539 t.prefix("sct", "http://snomed.info/id/"); 540 if (code.contains(":") || code.contains("=")) 541 generateLinkedPredicate(t, code); 542 else 543 t.linkedPredicate("a", "sct:" + urlescape(code), null); 544 } else if ("http://loinc.org".equals(system)) { 545 t.prefix("loinc", "http://loinc.org/rdf#"); 546 t.linkedPredicate("a", "loinc:" + urlescape(code).toUpperCase(), null); 547 } 548 } 549 550 private void generateLinkedPredicate(Complex t, String code) throws FHIRException { 551 Expression expression = SnomedExpressions.parse(code); 552 553 } 554 555// 128045006|cellulitis (disorder)|:{363698007|finding site|=56459004|foot structure|} 556// Grahame Grieve: or 557// 558// 64572001|disease|:{116676008|associated morphology|=72704001|fracture|,363698007|finding site|=(12611008|bone structure of tibia|:272741003|laterality|=7771000|left|)} 559// Harold Solbrig: 560// a sct:128045006, 561// rdfs:subClassOf [ 562// a owl:Restriction; 563// owl:onProperty sct:609096000 ; 564// owl:someValuesFrom [ 565// a owl:Restriction; 566// owl:onProperty sct:363698007 ; 567// owl:someValuesFrom sct:56459004 ] ] ; 568// and 569// 570// a sct:64572001, 571// rdfs:subclassOf [ 572// a owl:Restriction ; 573// owl:onProperty sct:60909600 ; 574// owl:someValuesFrom [ 575// a owl:Class ; 576// owl:intersectionOf ( [ 577// a owl:Restriction; 578// owl:onProperty sct:116676008; 579// owl:someValuesFrom sct:72704001 ] 580// [ a owl:Restriction; 581// owl:onProperty sct:363698007 582// owl:someValuesFrom [ 583// a owl:Class ; 584// owl:intersectionOf( 585// sct:12611008 586// owl:someValuesFrom [ 587// a owl:Restriction; 588// owl:onProperty sct:272741003; 589// owl:someValuesFrom sct:7771000 590// ] ) ] ] ) ] ] 591// (an approximation -- I'll have to feed it into a translator to be sure I've got it 100% right) 592// 593 594}