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