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