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