001package org.hl7.fhir.r5.elementmodel;
002
003import java.io.ByteArrayInputStream;
004
005/*
006  Copyright (c) 2011+, HL7, Inc.
007  All rights reserved.
008  
009  Redistribution and use in source and binary forms, with or without modification, 
010  are permitted provided that the following conditions are met:
011    
012   * Redistributions of source code must retain the above copyright notice, this 
013     list of conditions and the following disclaimer.
014   * Redistributions in binary form must reproduce the above copyright notice, 
015     this list of conditions and the following disclaimer in the documentation 
016     and/or other materials provided with the distribution.
017   * Neither the name of HL7 nor the names of its contributors may be used to 
018     endorse or promote products derived from this software without specific 
019     prior written permission.
020  
021  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
022  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
023  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
024  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
025  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
026  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
027  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
028  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
029  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
030  POSSIBILITY OF SUCH DAMAGE.
031  
032 */
033
034
035
036import java.io.IOException;
037import java.io.InputStream;
038import java.io.OutputStream;
039import java.util.ArrayList;
040import java.util.HashSet;
041import java.util.List;
042import java.util.Set;
043
044import org.hl7.fhir.exceptions.FHIRException;
045import org.hl7.fhir.exceptions.FHIRFormatError;
046import org.hl7.fhir.r5.context.IWorkerContext;
047import org.hl7.fhir.r5.elementmodel.Element.SpecialElement;
048import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat;
049import org.hl7.fhir.r5.formats.IParser.OutputStyle;
050import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
051import org.hl7.fhir.r5.model.StructureDefinition;
052import org.hl7.fhir.r5.utils.SnomedExpressions;
053import org.hl7.fhir.r5.utils.SnomedExpressions.Expression;
054import org.hl7.fhir.utilities.FileUtilities;
055import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage;
056import org.hl7.fhir.utilities.Utilities;
057import org.hl7.fhir.utilities.i18n.I18nConstants;
058import org.hl7.fhir.utilities.turtle.Turtle;
059import org.hl7.fhir.utilities.turtle.Turtle.Complex;
060import org.hl7.fhir.utilities.turtle.Turtle.Section;
061import org.hl7.fhir.utilities.turtle.Turtle.Subject;
062import org.hl7.fhir.utilities.turtle.Turtle.TTLComplex;
063import org.hl7.fhir.utilities.turtle.Turtle.TTLList;
064import org.hl7.fhir.utilities.turtle.Turtle.TTLLiteral;
065import org.hl7.fhir.utilities.turtle.Turtle.TTLObject;
066import org.hl7.fhir.utilities.turtle.Turtle.TTLURL;
067import org.hl7.fhir.utilities.validation.ValidationMessage;
068import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
069import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
070
071
072@MarkedToMoveToAdjunctPackage
073public class TurtleParser extends ParserBase {
074
075  private String base;
076
077  private OutputStyle style;
078
079  public static String FHIR_URI_BASE = "http://hl7.org/fhir/";
080  public static String FHIR_VERSION_BASE = "http://build.fhir.org/";
081
082  public TurtleParser(IWorkerContext context) {
083    super(context);
084  }
085  @Override
086  public List<ValidatedFragment> parse(InputStream inStream) throws IOException, FHIRException {
087    byte[] content = FileUtilities.streamToBytes(inStream);
088    ValidatedFragment focusFragment = new ValidatedFragment(ValidatedFragment.FOCUS_NAME, "ttl", content, false);
089    ByteArrayInputStream stream = new ByteArrayInputStream(content);
090
091    Turtle src = new Turtle();
092    if (policy == ValidationPolicy.EVERYTHING) {
093      try {
094        src.parse(FileUtilities.streamToString(stream));
095      } catch (Exception e) {  
096        logError(focusFragment.getErrors(), ValidationMessage.NO_RULE_DATE, -1, -1, "(document)", IssueType.INVALID, context.formatMessage(I18nConstants.ERROR_PARSING_TURTLE_, e.getMessage()), IssueSeverity.FATAL);
097        return null;
098      }
099      focusFragment.setElement(parse(focusFragment.getErrors(), src));
100    } else {
101      src.parse(FileUtilities.streamToString(stream));
102      focusFragment.setElement(parse(focusFragment.getErrors(), src));
103    }
104    List<ValidatedFragment> res = new ArrayList<>();
105    res.add(focusFragment);
106    return res;
107  }
108  
109  private Element parse(List<ValidationMessage> errors, Turtle src) throws FHIRException {
110    // we actually ignore the stated URL here
111    for (TTLComplex cmp : src.getObjects().values()) {
112      for (String p : cmp.getPredicates().keySet()) {
113        if ((FHIR_URI_BASE + "nodeRole").equals(p) && cmp.getPredicates().get(p).hasValue(FHIR_URI_BASE + "treeRoot")) {
114          return parse(errors, src, cmp);
115        }
116      }
117    }
118    // still here: well, we didn't find a start point
119    String msg = "Error parsing Turtle: unable to find any node maked as the entry point (where " + FHIR_URI_BASE + "nodeRole = " + FHIR_URI_BASE + "treeRoot)";
120    if (policy == ValidationPolicy.EVERYTHING) {
121      logError(errors, ValidationMessage.NO_RULE_DATE, -1, -1, "(document)", IssueType.INVALID, msg, IssueSeverity.FATAL);
122      return null;
123    } else {
124      throw new FHIRFormatError(msg);
125    } 
126  }
127  
128  private Element parse(List<ValidationMessage> errors, Turtle src, TTLComplex cmp) throws FHIRException {
129    TTLObject type = cmp.getPredicates().get("http://www.w3.org/2000/01/rdf-schema#type");
130    if (type == null) {
131      logError(errors, ValidationMessage.NO_RULE_DATE, cmp.getLine(), cmp.getCol(), "(document)", IssueType.INVALID, context.formatMessage(I18nConstants.UNKNOWN_RESOURCE_TYPE_MISSING_RDFSTYPE), IssueSeverity.FATAL);
132      return null;
133    }
134    if (type instanceof TTLList) {
135      // this is actually broken - really we have to look through the structure definitions at this point
136      for (TTLObject obj : ((TTLList) type).getList()) {
137        if (obj instanceof TTLURL && ((TTLURL) obj).getUri().startsWith(FHIR_URI_BASE)) {
138          type = obj;
139          break;
140        }
141      }
142    }
143    if (!(type instanceof TTLURL)) {
144      logError(errors, ValidationMessage.NO_RULE_DATE, cmp.getLine(), cmp.getCol(), "(document)", IssueType.INVALID, context.formatMessage(I18nConstants.UNEXPECTED_DATATYPE_FOR_RDFSTYPE), IssueSeverity.FATAL);
145      return null;
146    }
147    String name = ((TTLURL) type).getUri();
148    String ns = name.substring(0, name.lastIndexOf("/"));
149    name = name.substring(name.lastIndexOf("/")+1);
150    String path = "/"+name;
151
152    StructureDefinition sd = getDefinition(errors, cmp.getLine(), cmp.getCol(), ns, name);
153    if (sd == null)
154      return null;
155
156    Element result = new Element(name, new Property(context, sd.getSnapshot().getElement().get(0), sd, getProfileUtilities(), getContextUtilities())).setFormat(FhirFormat.TURTLE);
157    result.markLocation(cmp.getLine(), cmp.getCol());
158    result.setType(name);
159    parseChildren(errors, src, path, cmp, result, false);
160    result.numberChildren();
161    return result;  
162  }
163  
164  private void parseChildren(List<ValidationMessage> errors, Turtle src, String path, TTLComplex object, Element element, boolean primitive) throws FHIRException {
165
166    List<Property> properties = element.getProperty().getChildProperties(element.getName(), null);
167    Set<String> processed = new HashSet<String>();
168    if (primitive)
169      processed.add(FHIR_URI_BASE + "value");
170
171    // note that we do not trouble ourselves to maintain the wire format order here - we don't even know what it was anyway
172    // first pass: process the properties
173    for (Property property : properties) {
174      if (property.isChoice()) {
175        for (TypeRefComponent type : property.getDefinition().getType()) {
176          String eName = property.getName().substring(0, property.getName().length()-3) + Utilities.capitalize(type.getCode());
177          parseChild(errors, src, object, element, processed, property, path, getFormalName(property, eName));
178        }
179      } else  {
180        parseChild(errors, src, object, element, processed, property, path, getFormalName(property));
181      } 
182    }
183
184    // second pass: check for things not processed
185    if (policy != ValidationPolicy.NONE) {
186      for (String u : object.getPredicates().keySet()) {
187        if (!processed.contains(u)) {
188          TTLObject n = object.getPredicates().get(u);
189          logError(errors, ValidationMessage.NO_RULE_DATE, n.getLine(), n.getCol(), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.UNRECOGNISED_PREDICATE_, u), IssueSeverity.ERROR);
190        }
191      }
192    }
193  }
194  
195  private void parseChild(List<ValidationMessage> errors, Turtle src, TTLComplex object, Element context, Set<String> processed, Property property, String path, String name) throws FHIRException {
196    processed.add(name);
197    String npath = path+"/"+property.getName();
198    TTLObject e = object.getPredicates().get(FHIR_URI_BASE + name);
199    if (e == null)
200      return;
201    if (property.isList() && (e instanceof TTLList)) {
202      TTLList arr = (TTLList) e;
203      for (TTLObject am : arr.getList()) {
204        parseChildInstance(errors, src, npath, object, context, property, name, am);
205      }
206    } else {
207      parseChildInstance(errors, src, npath, object, context, property, name, e);
208    }
209  }
210
211  private void parseChildInstance(List<ValidationMessage> errors, Turtle src, String npath, TTLComplex object, Element element, Property property, String name, TTLObject e) throws FHIRException {
212    if (property.isResource())
213      parseResource(errors, src, npath, object, element, property, name, e);
214    else  if (e instanceof TTLComplex) {
215      TTLComplex child = (TTLComplex) e;
216      Element n = new Element(tail(name), property).markLocation(e.getLine(), e.getCol()).setFormat(FhirFormat.TURTLE);
217      element.getChildren().add(n);
218      if (property.isPrimitive(property.getType(tail(name)))) {
219        parseChildren(errors, src, npath, child, n, true);
220        TTLObject val = child.getPredicates().get(FHIR_URI_BASE + "value");
221        if (val != null) {
222          if (val instanceof TTLLiteral) {
223            String value = ((TTLLiteral) val).getValue();
224            String type = ((TTLLiteral) val).getType();
225            // todo: check type
226            n.setValue(value);
227          } else
228            logError(errors, 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);
229        }
230      } else 
231        parseChildren(errors, src, npath, child, n, false);
232
233    } else 
234      logError(errors, 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);
235  }
236
237
238  private String tail(String name) {
239    return name.substring(name.lastIndexOf(".")+1);
240  }
241
242  private void parseResource(List<ValidationMessage> errors, Turtle src, String npath, TTLComplex object, Element element, Property property, String name, TTLObject e) throws FHIRException {
243    TTLComplex obj;
244    if (e instanceof TTLComplex) 
245      obj = (TTLComplex) e;
246    else if (e instanceof TTLURL) {
247      String url = ((TTLURL) e).getUri();
248      obj = src.getObject(url);
249      if (obj == null) {
250        logError(errors, ValidationMessage.NO_RULE_DATE, e.getLine(), e.getCol(), npath, IssueType.INVALID, context.formatMessage(I18nConstants.REFERENCE_TO__CANNOT_BE_RESOLVED, url), IssueSeverity.FATAL);
251        return;
252      }
253    } else
254      throw new FHIRFormatError(context.formatMessage(I18nConstants.WRONG_TYPE_FOR_RESOURCE));
255      
256    TTLObject type = obj.getPredicates().get("http://www.w3.org/2000/01/rdf-schema#type");
257    if (type == null) {
258      logError(errors, ValidationMessage.NO_RULE_DATE, object.getLine(), object.getCol(), npath, IssueType.INVALID, context.formatMessage(I18nConstants.UNKNOWN_RESOURCE_TYPE_MISSING_RDFSTYPE), IssueSeverity.FATAL);
259      return;
260  }
261    if (type instanceof TTLList) {
262      // this is actually broken - really we have to look through the structure definitions at this point
263      for (TTLObject tobj : ((TTLList) type).getList()) {
264        if (tobj instanceof TTLURL && ((TTLURL) tobj).getUri().startsWith(FHIR_URI_BASE)) {
265          type = tobj;
266          break;
267        }
268      }
269    }
270    if (!(type instanceof TTLURL)) {
271      logError(errors, ValidationMessage.NO_RULE_DATE, object.getLine(), object.getCol(), npath, IssueType.INVALID, context.formatMessage(I18nConstants.UNEXPECTED_DATATYPE_FOR_RDFSTYPE), IssueSeverity.FATAL);
272      return;
273    }
274    String rt = ((TTLURL) type).getUri();
275    String ns = rt.substring(0, rt.lastIndexOf("/"));
276    rt = rt.substring(rt.lastIndexOf("/")+1);
277    
278    StructureDefinition sd = getDefinition(errors, object.getLine(), object.getCol(), ns, rt);
279    if (sd == null)
280      return;
281    
282    Element n = new Element(tail(name), property).markLocation(object.getLine(), object.getCol()).setFormat(FhirFormat.TURTLE);
283    element.getChildren().add(n);
284    n.updateProperty(new Property(this.context, sd.getSnapshot().getElement().get(0), sd, getProfileUtilities(), getContextUtilities()), SpecialElement.fromProperty(n.getProperty()), property);
285    n.setType(rt);
286    parseChildren(errors, 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(context.formatMessage(I18nConstants.ATTEMPT_TO_REPLACE_ELEMENT_NAME_FOR_A_NONCHOICE_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) throws IOException, FHIRException {
314        if (base != null) {
315                this.base = base;       
316        } else {
317                this.base = "http://hl7.org/fhir/";
318        }
319    this.style = style;
320        Turtle ttl = new Turtle();
321        compose(e, ttl, base);
322        ttl.commit(stream, false);
323  }
324
325  public void compose(Element e, Turtle ttl, String base) throws FHIRException {
326    if (e.getPath() == null) {
327      e.populatePaths(null);
328    }
329    
330    ttl.prefix("fhir", FHIR_URI_BASE);
331    ttl.prefix("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
332    ttl.prefix("rdfs", "http://www.w3.org/2000/01/rdf-schema#");
333    ttl.prefix("owl", "http://www.w3.org/2002/07/owl#");
334    ttl.prefix("xsd", "http://www.w3.org/2001/XMLSchema#");
335
336    Section section = ttl.section("resource");
337    if (style == OutputStyle.PRETTY) {
338      for (String s : e.getComments()) {
339        section.stringComment(s);
340      }
341    }
342    String subjId = genSubjectId(e);
343
344    Subject subject;
345    if (hasModifierExtension(e)) 
346        subject = section.triple(subjId, "a", "fhir:_" + e.getType());
347     else 
348        subject = section.triple(subjId, "a", "fhir:" + e.getType());
349     
350        subject.linkedPredicate("fhir:nodeRole", "fhir:treeRoot", linkResolver == null ? null : linkResolver.resolvePage("rdf.html#tree-root"), null);
351
352        for (Element child : e.getChildren()) {
353                composeElement(section, subject, child, null);
354        }
355
356  }
357  
358  private boolean hasModifierExtension(Element e) {
359          return e.getChildren().stream().anyMatch(p -> p.getName().equals("modifierExtension"));
360  }
361  
362  protected String getURIType(String uri) {
363    if(uri.startsWith("<" + FHIR_URI_BASE))
364      if(uri.substring(FHIR_URI_BASE.length() + 1).contains("/"))
365        return uri.substring(FHIR_URI_BASE.length() + 1, uri.indexOf('/', FHIR_URI_BASE.length() + 1));
366    return null;
367  }
368
369  protected String getReferenceURI(String ref) {
370    if (ref != null && (ref.startsWith("http://") || ref.startsWith("https://")))
371      return "<" + ref + ">";
372    else if (base != null && ref != null && ref.contains("/"))
373      return "<" + Utilities.appendForwardSlash(base) + ref + ">";
374    else
375      return null;
376    }
377
378  protected void decorateReference(Complex t, Element coding) {
379    String refURI = getReferenceURI(coding.getChildValue("reference"));
380    if(refURI != null)
381      t.linkedPredicate("fhir:link", refURI, linkResolver == null ? null : linkResolver.resolvePage("rdf.html#reference"), null);
382  }
383  
384  protected void decorateCanonical(Complex t, Element canonical) {
385    String refURI = getReferenceURI(canonical.primitiveValue());
386    if(refURI != null)
387      t.linkedPredicate("fhir:link", refURI, linkResolver == null ? null : linkResolver.resolvePage("rdf.html#reference"), null);
388  }
389  
390  private String genSubjectId(Element e) {
391    String id = e.getChildValue("id");
392    if (base == null || id == null)
393      return "";
394    else if (base.endsWith("#"))
395      return "<" + base + e.getType() + "-" + id + ">";
396    else
397      return "<" + Utilities.pathURL(base, e.getType(), id) + ">";
398  }
399
400        private String urlescape(String s) {
401          StringBuilder b = new StringBuilder();
402          for (char ch : s.toCharArray()) {
403            if (Utilities.charInSet(ch,  ':', ';', '=', ','))
404              b.append("%"+Integer.toHexString(ch));
405            else
406              b.append(ch);
407          }
408          return b.toString();
409  }
410
411  private void composeElement(Section section, Complex ctxt, Element element, Element parent) throws FHIRException {
412//    "Extension".equals(element.getType())?
413//            (element.getProperty().getDefinition().getIsModifier()? "modifierExtension" : "extension") ; 
414   
415    String en = getFormalName(element);
416
417    if (!wantCompose(parent == null ? "" : parent.getPath(), element)) {
418      return;
419    }
420    
421    String comment = null;
422    if (style == OutputStyle.PRETTY) {
423      comment = String.join(", ", element.getComments());
424    }
425          Complex t;
426          if (element.getSpecial() == SpecialElement.BUNDLE_ENTRY && parent != null && parent.getNamedChildValue("fullUrl") != null) {
427            String url = "<"+parent.getNamedChildValue("fullUrl")+">";
428            ctxt.linkedPredicate("fhir:"+en, url, linkResolver == null ? null : linkResolver.resolveProperty(element.getProperty()), comment, element.getProperty().isList());
429            t = section.subject(url);
430          } else {
431            t = ctxt.linkedPredicate("fhir:"+en, linkResolver == null ? null : linkResolver.resolveProperty(element.getProperty()), comment, element.getProperty().isList());
432          }
433        if (element.getProperty().getName().endsWith("[x]")) {
434          t.linkedPredicate("a", "fhir:" + element.fhirType(), linkResolver == null ? null : linkResolver.resolveType(element.fhirType()), null);
435        }
436    if (element.getSpecial() != null)
437      t.linkedPredicate("a", "fhir:"+element.fhirType(), linkResolver == null ? null : linkResolver.resolveType(element.fhirType()), null);
438          if (element.hasValue())
439                t.linkedPredicate("fhir:v", ttlLiteral(element.getValue(), element.getType()), linkResolver == null ? null : linkResolver.resolveType(element.getType()), null);
440
441          if ("Coding".equals(element.getType()))
442                decorateCoding(t, element, section);
443    if (Utilities.existsInList(element.getType(), "Reference"))
444      decorateReference(t, element);
445    else if (Utilities.existsInList(element.getType(), "canonical"))
446      decorateCanonical(t, element);
447                        
448    if("canonical".equals(element.getType())) {
449      String refURI = element.primitiveValue();
450      if (refURI != null) {
451        String uriType = getURIType(refURI);
452        if(uriType != null && !section.hasSubject(refURI))
453          section.triple(refURI, "a", "fhir:" + uriType);
454      }
455    }
456
457    if("Reference".equals(element.getType())) {
458      String refURI = getReferenceURI(element.getChildValue("reference"));
459      if (refURI != null) {
460        String uriType = getURIType(refURI);
461        if(uriType != null && !section.hasSubject(refURI))
462          section.triple(refURI, "a", "fhir:" + uriType);
463      }
464    }
465
466                for (Element child : element.getChildren()) {
467      if ("xhtml".equals(child.getType())) {
468        String childfn = getFormalName(child);
469        t.predicate("fhir:" + childfn, ttlLiteral(child.getValue(), child.getType()));
470      } else
471                        composeElement(section, t, child, element);
472                }
473        }
474
475  private String getFormalName(Element element) {
476    String en = null;
477    if (element.getSpecial() == null) 
478        en = element.getProperty().getName();
479    else if (element.getSpecial() == SpecialElement.BUNDLE_ENTRY)
480      en = "resource";
481    else if (element.getSpecial() == SpecialElement.BUNDLE_OUTCOME)
482      en = "outcome";
483    else if (element.getSpecial() == SpecialElement.BUNDLE_ISSUES)
484      en = "issues";
485    else if (element.getSpecial() == SpecialElement.PARAMETER)
486      en = element.getElementProperty().getDefinition().getPath();
487    else // CONTAINED
488      en = "contained";
489
490    if (en == null) 
491      en = element.getProperty().getName();
492    
493    if (en.endsWith("[x]")) 
494      en = en.substring(0, en.length()-3);
495    
496    if (hasModifierExtension(element))
497        return "_" + en;
498    else
499      return en;
500  }
501
502  static public String ttlLiteral(String value, String type) {
503        boolean quote = true;
504        String xst = "";
505        if (type.equals("boolean"))
506          quote = false;
507    else if (type.equals("integer"))
508        quote = false;
509    else if (type.equals("integer64"))
510      xst = "^^xsd:long";         
511    else if (type.equals("unsignedInt"))
512      xst = "^^xsd:nonNegativeInteger";
513    else if (type.equals("positiveInt"))
514      xst = "^^xsd:positiveInteger";
515    else if (type.equals("decimal")) {
516      if (value.contains(".")) {
517          quote = false;
518      } else {
519          xst = "^^xsd:decimal";
520      }
521    }
522    else if (type.equals("base64Binary"))
523      xst = "^^xsd:base64Binary";
524    else if (type.equals("canonical") || type.equals("oid") || type.equals("uri") || type.equals("url") || type.equals("uuid"))
525          xst = "^^xsd:anyURI";
526    else if (type.equals("xhtml"))
527      xst = "^^rdf:XMLLiteral";
528    else if (type.equals("instant"))
529      xst = "^^xsd:dateTime";
530    else if (type.equals("time"))
531      xst = "^^xsd:time";
532    else if (type.equals("date") || type.equals("dateTime") ) {
533      String v = value;
534      if (v.length() > 10) {
535        int i = value.substring(10).indexOf("-");
536        if (i == -1)
537          i = value.substring(10).indexOf("+");
538        v = i == -1 ? value : value.substring(0,  10+i);
539      }
540      if (v.length() > 10)
541        xst = "^^xsd:dateTime";
542      else if (v.length() == 10)
543        xst = "^^xsd:date";
544      else if (v.length() == 7)
545        xst = "^^xsd:gYearMonth";
546      else if (v.length() == 4)
547        xst = "^^xsd:gYear";
548    }
549        if (quote) {
550          return "\"" + Turtle.escape(value, true) + "\"" + xst;
551        } else {
552          return value; 
553        }               
554  }
555
556  protected void decorateCoding(Complex t, Element coding, Section section) throws FHIRException {
557    String system = coding.getChildValue("system");
558    String code = coding.getChildValue("code");
559    
560    if (system == null || code == null)
561      return;
562    if ("http://snomed.info/sct".equals(system)) {
563      t.prefix("sct", "http://snomed.info/id/");
564      if (code.contains(":") || code.contains("="))
565        generateLinkedPredicate(t, code);
566      else
567        t.linkedPredicate("a", "sct:" + urlescape(code), null, null);
568    } else if ("http://loinc.org".equals(system)) {
569      t.prefix("loinc", "https://loinc.org/rdf/");
570      t.linkedPredicate("a", "loinc:"+urlescape(code).toUpperCase(), null, null);
571    } else if ("https://www.nlm.nih.gov/mesh".equals(system)) {
572        t.prefix("mesh", "http://id.nlm.nih.gov/mesh/");
573        t.linkedPredicate("a", "mesh:"+urlescape(code), null, null);
574    }  
575  }
576
577  private void generateLinkedPredicate(Complex t, String code) throws FHIRException {
578    Expression expression = SnomedExpressions.parse(code);
579    
580  }
581  public OutputStyle getStyle() {
582    return style;
583  }
584  public void setStyle(OutputStyle style) {
585    this.style = style;
586  }
587
588
589//    128045006|cellulitis (disorder)|:{363698007|finding site|=56459004|foot structure|}
590//    Grahame Grieve: or
591//
592//    64572001|disease|:{116676008|associated morphology|=72704001|fracture|,363698007|finding site|=(12611008|bone structure of  tibia|:272741003|laterality|=7771000|left|)}
593//    Harold Solbrig:
594//    a sct:128045006,
595//      rdfs:subClassOf [
596//          a owl:Restriction;
597//          owl:onProperty sct:609096000 ;
598//          owl:someValuesFrom [
599//                a owl:Restriction;
600//                 owl:onProperty sct:363698007 ;
601//                owl:someValuesFrom sct:56459004 ] ] ;
602//    and
603//
604//    a sct:64572001,
605//       rdfs:subclassOf  [
606//           a owl:Restriction ;
607//           owl:onProperty sct:60909600 ;
608//           owl:someValuesFrom [ 
609//                 a owl:Class ;
610//                 owl:intersectionOf ( [
611//                      a owl:Restriction;
612//                      owl:onProperty sct:116676008;
613//                     owl:someValuesFrom sct:72704001 ] 
614//                 [  a owl:Restriction;
615//                      owl:onProperty sct:363698007 
616//                      owl:someValuesFrom [
617//                            a owl:Class ;
618//                            owl:intersectionOf(
619//                                 sct:12611008
620//                                 owl:someValuesFrom [
621//                                         a owl:Restriction;
622//                                         owl:onProperty sct:272741003;
623//                                         owl:someValuesFrom sct:7771000
624//                                  ] ) ] ] ) ] ]
625//    (an approximation -- I'll have to feed it into a translator to be sure I've got it 100% right)
626//
627  
628}