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