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