
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}