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