org.hl7.fhir.r4.elementmodel.TurtleParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of org.hl7.fhir.r4 Show documentation
Show all versions of org.hl7.fhir.r4 Show documentation
Builds the hapi fhir r4. Requires hapi-fhir-base and hapi-fhir-utilities be built first and be
excluded from any other poms requiring it.
The newest version!
package org.hl7.fhir.r4.elementmodel;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.r4.context.IWorkerContext;
import org.hl7.fhir.r4.elementmodel.Element.SpecialElement;
import org.hl7.fhir.r4.formats.IParser.OutputStyle;
import org.hl7.fhir.r4.model.ElementDefinition.TypeRefComponent;
import org.hl7.fhir.r4.model.StructureDefinition;
import org.hl7.fhir.r4.utils.SnomedExpressions;
import org.hl7.fhir.r4.utils.SnomedExpressions.Expression;
import org.hl7.fhir.r4.utils.formats.Turtle;
import org.hl7.fhir.r4.utils.formats.Turtle.Complex;
import org.hl7.fhir.r4.utils.formats.Turtle.Section;
import org.hl7.fhir.r4.utils.formats.Turtle.Subject;
import org.hl7.fhir.r4.utils.formats.Turtle.TTLComplex;
import org.hl7.fhir.r4.utils.formats.Turtle.TTLList;
import org.hl7.fhir.r4.utils.formats.Turtle.TTLLiteral;
import org.hl7.fhir.r4.utils.formats.Turtle.TTLObject;
import org.hl7.fhir.r4.utils.formats.Turtle.TTLURL;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
public class TurtleParser extends ParserBase {
private String base;
public static String FHIR_URI_BASE = "http://hl7.org/fhir/";
public static String FHIR_VERSION_BASE = "http://build.fhir.org/";
public TurtleParser(IWorkerContext context) {
super(context);
}
@Override
public Element parse(InputStream input) throws IOException, FHIRException {
Turtle src = new Turtle();
if (policy == ValidationPolicy.EVERYTHING) {
try {
src.parse(TextFile.streamToString(input));
} catch (Exception e) {
logError(-1, -1, "(document)", IssueType.INVALID, "Error parsing Turtle: " + e.getMessage(),
IssueSeverity.FATAL);
return null;
}
return parse(src);
} else {
src.parse(TextFile.streamToString(input));
return parse(src);
}
}
private Element parse(Turtle src) throws FHIRException {
// we actually ignore the stated URL here
for (TTLComplex cmp : src.getObjects().values()) {
for (String p : cmp.getPredicates().keySet()) {
if ((FHIR_URI_BASE + "nodeRole").equals(p) && cmp.getPredicates().get(p).hasValue(FHIR_URI_BASE + "treeRoot")) {
return parse(src, cmp);
}
}
}
// still here: well, we didn't find a start point
String msg = "Error parsing Turtle: unable to find any node maked as the entry point (where " + FHIR_URI_BASE
+ "nodeRole = " + FHIR_URI_BASE + "treeRoot)";
if (policy == ValidationPolicy.EVERYTHING) {
logError(-1, -1, "(document)", IssueType.INVALID, msg, IssueSeverity.FATAL);
return null;
} else {
throw new FHIRFormatError(msg);
}
}
private Element parse(Turtle src, TTLComplex cmp) throws FHIRException {
TTLObject type = cmp.getPredicates().get("http://www.w3.org/2000/01/rdf-schema#type");
if (type == null) {
logError(cmp.getLine(), cmp.getCol(), "(document)", IssueType.INVALID,
"Unknown resource type (missing rdfs:type)", IssueSeverity.FATAL);
return null;
}
if (type instanceof TTLList) {
// this is actually broken - really we have to look through the structure
// definitions at this point
for (TTLObject obj : ((TTLList) type).getList()) {
if (obj instanceof TTLURL && ((TTLURL) obj).getUri().startsWith(FHIR_URI_BASE)) {
type = obj;
break;
}
}
}
if (!(type instanceof TTLURL)) {
logError(cmp.getLine(), cmp.getCol(), "(document)", IssueType.INVALID, "Unexpected datatype for rdfs:type)",
IssueSeverity.FATAL);
return null;
}
String name = ((TTLURL) type).getUri();
String ns = name.substring(0, name.lastIndexOf("/"));
name = name.substring(name.lastIndexOf("/") + 1);
String path = "/" + name;
StructureDefinition sd = getDefinition(cmp.getLine(), cmp.getCol(), ns, name);
if (sd == null)
return null;
Element result = new Element(name, new Property(context, sd.getSnapshot().getElement().get(0), sd));
result.markLocation(cmp.getLine(), cmp.getCol());
result.setType(name);
parseChildren(src, path, cmp, result, false);
result.numberChildren();
return result;
}
private void parseChildren(Turtle src, String path, TTLComplex object, Element context, boolean primitive)
throws FHIRException {
List properties = context.getProperty().getChildProperties(context.getName(), null);
Set processed = new HashSet();
if (primitive)
processed.add(FHIR_URI_BASE + "value");
// note that we do not trouble ourselves to maintain the wire format order here
// - we don't even know what it was anyway
// first pass: process the properties
for (Property property : properties) {
if (property.isChoice()) {
for (TypeRefComponent type : property.getDefinition().getType()) {
String eName = property.getName().substring(0, property.getName().length() - 3)
+ Utilities.capitalize(type.getCode());
parseChild(src, object, context, processed, property, path, getFormalName(property, eName));
}
} else {
parseChild(src, object, context, processed, property, path, getFormalName(property));
}
}
// second pass: check for things not processed
if (policy != ValidationPolicy.NONE) {
for (String u : object.getPredicates().keySet()) {
if (!processed.contains(u)) {
TTLObject n = object.getPredicates().get(u);
logError(n.getLine(), n.getCol(), path, IssueType.STRUCTURE, "Unrecognised predicate '" + u + "'",
IssueSeverity.ERROR);
}
}
}
}
private void parseChild(Turtle src, TTLComplex object, Element context, Set processed, Property property,
String path, String name) throws FHIRException {
processed.add(name);
String npath = path + "/" + property.getName();
TTLObject e = object.getPredicates().get(FHIR_URI_BASE + name);
if (e == null)
return;
if (property.isList() && (e instanceof TTLList)) {
TTLList arr = (TTLList) e;
for (TTLObject am : arr.getList()) {
parseChildInstance(src, npath, object, context, property, name, am);
}
} else {
parseChildInstance(src, npath, object, context, property, name, e);
}
}
private void parseChildInstance(Turtle src, String npath, TTLComplex object, Element context, Property property,
String name, TTLObject e) throws FHIRException {
if (property.isResource())
parseResource(src, npath, object, context, property, name, e);
else if (e instanceof TTLComplex) {
TTLComplex child = (TTLComplex) e;
Element n = new Element(tail(name), property).markLocation(e.getLine(), e.getCol());
context.getChildren().add(n);
if (property.isPrimitive(property.getType(tail(name)))) {
parseChildren(src, npath, child, n, true);
TTLObject val = child.getPredicates().get(FHIR_URI_BASE + "value");
if (val != null) {
if (val instanceof TTLLiteral) {
String value = ((TTLLiteral) val).getValue();
String type = ((TTLLiteral) val).getType();
// todo: check type
n.setValue(value);
} else
logError(object.getLine(), object.getCol(), npath, IssueType.INVALID,
"This property must be a Literal, not a " + e.getClass().getName(), IssueSeverity.ERROR);
}
} else
parseChildren(src, npath, child, n, false);
} else
logError(object.getLine(), object.getCol(), npath, IssueType.INVALID,
"This property must be a URI or bnode, not a " + e.getClass().getName(), IssueSeverity.ERROR);
}
private String tail(String name) {
return name.substring(name.lastIndexOf(".") + 1);
}
private void parseResource(Turtle src, String npath, TTLComplex object, Element context, Property property,
String name, TTLObject e) throws FHIRException {
TTLComplex obj;
if (e instanceof TTLComplex)
obj = (TTLComplex) e;
else if (e instanceof TTLURL) {
String url = ((TTLURL) e).getUri();
obj = src.getObject(url);
if (obj == null) {
logError(e.getLine(), e.getCol(), npath, IssueType.INVALID, "reference to " + url + " cannot be resolved",
IssueSeverity.FATAL);
return;
}
} else
throw new FHIRFormatError("Wrong type for resource");
TTLObject type = obj.getPredicates().get("http://www.w3.org/2000/01/rdf-schema#type");
if (type == null) {
logError(object.getLine(), object.getCol(), npath, IssueType.INVALID, "Unknown resource type (missing rdfs:type)",
IssueSeverity.FATAL);
return;
}
if (type instanceof TTLList) {
// this is actually broken - really we have to look through the structure
// definitions at this point
for (TTLObject tobj : ((TTLList) type).getList()) {
if (tobj instanceof TTLURL && ((TTLURL) tobj).getUri().startsWith(FHIR_URI_BASE)) {
type = tobj;
break;
}
}
}
if (!(type instanceof TTLURL)) {
logError(object.getLine(), object.getCol(), npath, IssueType.INVALID, "Unexpected datatype for rdfs:type)",
IssueSeverity.FATAL);
return;
}
String rt = ((TTLURL) type).getUri();
String ns = rt.substring(0, rt.lastIndexOf("/"));
rt = rt.substring(rt.lastIndexOf("/") + 1);
StructureDefinition sd = getDefinition(object.getLine(), object.getCol(), ns, rt);
if (sd == null)
return;
Element n = new Element(tail(name), property).markLocation(object.getLine(), object.getCol());
context.getChildren().add(n);
n.updateProperty(new Property(this.context, sd.getSnapshot().getElement().get(0), sd),
SpecialElement.fromProperty(n.getProperty()), property);
n.setType(rt);
parseChildren(src, npath, obj, n, false);
}
private String getFormalName(Property property) {
String en = property.getDefinition().getBase().getPath();
if (en == null)
en = property.getDefinition().getPath();
// boolean doType = false;
// if (en.endsWith("[x]")) {
// en = en.substring(0, en.length()-3);
// doType = true;
// }
// if (doType || (element.getProperty().getDefinition().getType().size() > 1 && !allReference(element.getProperty().getDefinition().getType())))
// en = en + Utilities.capitalize(element.getType());
return en;
}
private String getFormalName(Property property, String elementName) {
String en = property.getDefinition().getBase().getPath();
if (en == null)
en = property.getDefinition().getPath();
if (!en.endsWith("[x]"))
throw new Error("Attempt to replace element name for a non-choice type");
return en.substring(0, en.lastIndexOf(".") + 1) + elementName;
}
@Override
public void compose(Element e, OutputStream stream, OutputStyle style, String base)
throws IOException, FHIRException {
this.base = base;
Turtle ttl = new Turtle();
compose(e, ttl, base);
ttl.commit(stream, false);
}
public void compose(Element e, Turtle ttl, String base) throws FHIRException {
ttl.prefix("fhir", FHIR_URI_BASE);
ttl.prefix("rdfs", "http://www.w3.org/2000/01/rdf-schema#");
ttl.prefix("owl", "http://www.w3.org/2002/07/owl#");
ttl.prefix("xsd", "http://www.w3.org/2001/XMLSchema#");
Section section = ttl.section("resource");
String subjId = genSubjectId(e);
String ontologyId = subjId.replace(">", ".ttl>");
Section ontology = ttl.section("ontology header");
ontology.triple(ontologyId, "a", "owl:Ontology");
ontology.triple(ontologyId, "owl:imports", "fhir:fhir.ttl");
if (ontologyId.startsWith("<" + FHIR_URI_BASE))
ontology.triple(ontologyId, "owl:versionIRI", ontologyId.replace(FHIR_URI_BASE, FHIR_VERSION_BASE));
Subject subject = section.triple(subjId, "a", "fhir:" + e.getType());
subject.linkedPredicate("fhir:nodeRole", "fhir:treeRoot",
linkResolver == null ? null : linkResolver.resolvePage("rdf.html#tree-root"));
for (Element child : e.getChildren()) {
composeElement(section, subject, child, null);
}
}
protected String getURIType(String uri) {
if (uri.startsWith("<" + FHIR_URI_BASE))
if (uri.substring(FHIR_URI_BASE.length() + 1).contains("/"))
return uri.substring(FHIR_URI_BASE.length() + 1, uri.indexOf('/', FHIR_URI_BASE.length() + 1));
return null;
}
protected String getReferenceURI(String ref) {
if (ref != null && (ref.startsWith("http://") || ref.startsWith("https://")))
return "<" + ref + ">";
else if (base != null && ref != null && ref.contains("/"))
return "<" + Utilities.appendForwardSlash(base) + ref + ">";
else
return null;
}
protected void decorateReference(Complex t, Element coding) {
String refURI = getReferenceURI(coding.getChildValue("reference"));
if (refURI != null)
t.linkedPredicate("fhir:link", refURI,
linkResolver == null ? null : linkResolver.resolvePage("rdf.html#reference"));
}
protected void decorateCanonical(Complex t, Element canonical) {
String refURI = getReferenceURI(canonical.primitiveValue());
if (refURI != null)
t.linkedPredicate("fhir:link", refURI,
linkResolver == null ? null : linkResolver.resolvePage("rdf.html#reference"));
}
private String genSubjectId(Element e) {
String id = e.getChildValue("id");
if (base == null || id == null)
return "";
else if (base.endsWith("#"))
return "<" + base + e.getType() + "-" + id + ">";
else
return "<" + Utilities.pathURL(base, e.getType(), id) + ">";
}
private String urlescape(String s) {
StringBuilder b = new StringBuilder();
for (char ch : s.toCharArray()) {
if (Utilities.charInSet(ch, ':', ';', '=', ','))
b.append("%" + Integer.toHexString(ch));
else
b.append(ch);
}
return b.toString();
}
private void composeElement(Section section, Complex ctxt, Element element, Element parent) throws FHIRException {
// "Extension".equals(element.getType())?
// (element.getProperty().getDefinition().getIsModifier()? "modifierExtension" : "extension") ;
String en = getFormalName(element);
Complex t;
if (element.getSpecial() == SpecialElement.BUNDLE_ENTRY && parent != null
&& parent.getNamedChildValue("fullUrl") != null) {
String url = "<" + parent.getNamedChildValue("fullUrl") + ">";
ctxt.linkedPredicate("fhir:" + en, url,
linkResolver == null ? null : linkResolver.resolveProperty(element.getProperty()));
t = section.subject(url);
} else {
t = ctxt.linkedPredicate("fhir:" + en,
linkResolver == null ? null : linkResolver.resolveProperty(element.getProperty()));
}
if (element.getSpecial() != null)
t.linkedPredicate("a", "fhir:" + element.fhirType(),
linkResolver == null ? null : linkResolver.resolveType(element.fhirType()));
if (element.hasValue())
t.linkedPredicate("fhir:value", ttlLiteral(element.getValue(), element.getType()),
linkResolver == null ? null : linkResolver.resolveType(element.getType()));
if (element.getProperty().isList() && (!element.isResource() || element.getSpecial() == SpecialElement.CONTAINED))
t.linkedPredicate("fhir:index", Integer.toString(element.getIndex()),
linkResolver == null ? null : linkResolver.resolvePage("rdf.html#index"));
if ("Coding".equals(element.getType()))
decorateCoding(t, element, section);
if (Utilities.existsInList(element.getType(), "Reference"))
decorateReference(t, element);
else if (Utilities.existsInList(element.getType(), "canonical"))
decorateCanonical(t, element);
if ("canonical".equals(element.getType())) {
String refURI = element.primitiveValue();
if (refURI != null) {
String uriType = getURIType(refURI);
if (uriType != null && !section.hasSubject(refURI))
section.triple(refURI, "a", "fhir:" + uriType);
}
}
if ("Reference".equals(element.getType())) {
String refURI = getReferenceURI(element.getChildValue("reference"));
if (refURI != null) {
String uriType = getURIType(refURI);
if (uriType != null && !section.hasSubject(refURI))
section.triple(refURI, "a", "fhir:" + uriType);
}
}
for (Element child : element.getChildren()) {
if ("xhtml".equals(child.getType())) {
String childfn = getFormalName(child);
t.predicate("fhir:" + childfn, ttlLiteral(child.getValue(), child.getType()));
} else
composeElement(section, t, child, element);
}
}
private String getFormalName(Element element) {
String en = null;
if (element.getSpecial() == null) {
if (element.getProperty().getDefinition().hasBase())
en = element.getProperty().getDefinition().getBase().getPath();
} else if (element.getSpecial() == SpecialElement.BUNDLE_ENTRY)
en = "Bundle.entry.resource";
else if (element.getSpecial() == SpecialElement.BUNDLE_OUTCOME)
en = "Bundle.entry.response.outcome";
else if (element.getSpecial() == SpecialElement.PARAMETER)
en = element.getElementProperty().getDefinition().getPath();
else // CONTAINED
en = "DomainResource.contained";
if (en == null)
en = element.getProperty().getDefinition().getPath();
boolean doType = false;
if (en.endsWith("[x]")) {
en = en.substring(0, en.length() - 3);
doType = true;
}
if (doType || (element.getProperty().getDefinition().getType().size() > 1
&& !allReference(element.getProperty().getDefinition().getType())))
en = en + Utilities.capitalize(element.getType());
return en;
}
private boolean allReference(List types) {
for (TypeRefComponent t : types) {
if (!t.getCode().equals("Reference"))
return false;
}
return true;
}
static public String ttlLiteral(String value, String type) {
String xst = "";
if (type.equals("boolean"))
xst = "^^xsd:boolean";
else if (type.equals("integer"))
xst = "^^xsd:integer";
else if (type.equals("unsignedInt"))
xst = "^^xsd:nonNegativeInteger";
else if (type.equals("positiveInt"))
xst = "^^xsd:positiveInteger";
else if (type.equals("decimal"))
xst = "^^xsd:decimal";
else if (type.equals("base64Binary"))
xst = "^^xsd:base64Binary";
else if (type.equals("instant"))
xst = "^^xsd:dateTime";
else if (type.equals("time"))
xst = "^^xsd:time";
else if (type.equals("date") || type.equals("dateTime")) {
String v = value;
if (v.length() > 10) {
int i = value.substring(10).indexOf("-");
if (i == -1)
i = value.substring(10).indexOf("+");
v = i == -1 ? value : value.substring(0, 10 + i);
}
if (v.length() > 10)
xst = "^^xsd:dateTime";
else if (v.length() == 10)
xst = "^^xsd:date";
else if (v.length() == 7)
xst = "^^xsd:gYearMonth";
else if (v.length() == 4)
xst = "^^xsd:gYear";
}
return "\"" + Turtle.escape(value, true) + "\"" + xst;
}
protected void decorateCoding(Complex t, Element coding, Section section) throws FHIRException {
String system = coding.getChildValue("system");
String code = coding.getChildValue("code");
if (system == null || code == null)
return;
if ("http://snomed.info/sct".equals(system)) {
t.prefix("sct", "http://snomed.info/id/");
if (code.contains(":") || code.contains("="))
generateLinkedPredicate(t, code);
else
t.linkedPredicate("a", "sct:" + urlescape(code), null);
} else if ("http://loinc.org".equals(system)) {
t.prefix("loinc", "http://loinc.org/rdf#");
t.linkedPredicate("a", "loinc:" + urlescape(code).toUpperCase(), null);
}
}
private void generateLinkedPredicate(Complex t, String code) throws FHIRException {
Expression expression = SnomedExpressions.parse(code);
}
// 128045006|cellulitis (disorder)|:{363698007|finding site|=56459004|foot structure|}
// Grahame Grieve: or
//
// 64572001|disease|:{116676008|associated morphology|=72704001|fracture|,363698007|finding site|=(12611008|bone structure of tibia|:272741003|laterality|=7771000|left|)}
// Harold Solbrig:
// a sct:128045006,
// rdfs:subClassOf [
// a owl:Restriction;
// owl:onProperty sct:609096000 ;
// owl:someValuesFrom [
// a owl:Restriction;
// owl:onProperty sct:363698007 ;
// owl:someValuesFrom sct:56459004 ] ] ;
// and
//
// a sct:64572001,
// rdfs:subclassOf [
// a owl:Restriction ;
// owl:onProperty sct:60909600 ;
// owl:someValuesFrom [
// a owl:Class ;
// owl:intersectionOf ( [
// a owl:Restriction;
// owl:onProperty sct:116676008;
// owl:someValuesFrom sct:72704001 ]
// [ a owl:Restriction;
// owl:onProperty sct:363698007
// owl:someValuesFrom [
// a owl:Class ;
// owl:intersectionOf(
// sct:12611008
// owl:someValuesFrom [
// a owl:Restriction;
// owl:onProperty sct:272741003;
// owl:someValuesFrom sct:7771000
// ] ) ] ] ) ] ]
// (an approximation -- I'll have to feed it into a translator to be sure I've got it 100% right)
//
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy