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