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}