001package org.hl7.fhir.dstu2016may.formats; 002 003/* 004 Copyright (c) 2011+, HL7, Inc. 005 All rights reserved. 006 007 Redistribution and use in source and binary forms, with or without modification, 008 are permitted provided that the following conditions are met: 009 010 * Redistributions of source code must retain the above copyright notice, this 011 list of conditions and the following disclaimer. 012 * Redistributions in binary form must reproduce the above copyright notice, 013 this list of conditions and the following disclaimer in the documentation 014 and/or other materials provided with the distribution. 015 * Neither the name of HL7 nor the names of its contributors may be used to 016 endorse or promote products derived from this software without specific 017 prior written permission. 018 019 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 022 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 023 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 024 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 025 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 026 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 027 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 028 POSSIBILITY OF SUCH DAMAGE. 029 030 */ 031 032 033 034import java.io.OutputStream; 035import java.io.OutputStreamWriter; 036import java.io.UnsupportedEncodingException; 037import java.util.ArrayList; 038import java.util.Collections; 039import java.util.HashMap; 040import java.util.HashSet; 041import java.util.List; 042import java.util.Map; 043import java.util.Set; 044 045import org.hl7.fhir.dstu2016may.formats.TurtleLexer.TurtleTokenType; 046import org.hl7.fhir.utilities.Utilities; 047 048public class RdfGenerator { 049 050 public abstract class Triple { 051 private String uri; 052 } 053 054 public class StringType extends Triple { 055 private String value; 056 057 public StringType(String value) { 058 super(); 059 this.value = value; 060 } 061 } 062 063 public class Complex extends Triple { 064 protected List<Predicate> predicates = new ArrayList<Predicate>(); 065 066 public boolean write(LineOutputStreamWriter writer, int indent) throws Exception { 067 if (predicates.isEmpty()) 068 return false; 069 if (predicates.size() == 1 && predicates.get(0).object instanceof StringType && Utilities.noString(predicates.get(0).comment)) { 070 writer.write(" "+predicates.get(0).predicate+" "+((StringType) predicates.get(0).object).value); 071 return false; 072 } 073 String left = Utilities.padLeft("", ' ', indent); 074 int i = 0; 075 for (Predicate po : predicates) { 076 writer.write("\r\n"); 077 if (po.getObject() instanceof StringType) 078 writer.write(left+" "+po.getPredicate()+" "+((StringType) po.getObject()).value); 079 else { 080 writer.write(left+" "+po.getPredicate()+" ["); 081 if (((Complex) po.getObject()).write(writer, indent+2)) 082 writer.write(left+" ]"); 083 else 084 writer.write(" ]"); 085 } 086 i++; 087 if (i < predicates.size()) 088 writer.write(";"); 089 if (!Utilities.noString(po.comment)) 090 writer.write(" # "+escape(po.comment, false)); 091 } 092 return true; 093 } 094 095 public Complex predicate(String predicate, String object) { 096 predicateSet.add(predicate); 097 objectSet.add(object); 098 return predicate(predicate, new StringType(object)); 099 } 100 101 public Complex predicate(String predicate, Triple object) { 102 Predicate p = new Predicate(); 103 p.predicate = predicate; 104 predicateSet.add(predicate); 105 if (object instanceof StringType) 106 objectSet.add(((StringType) object).value); 107 p.object = object; 108 predicates.add(p); 109 return this; 110 } 111 112 public Complex predicate(String predicate) { 113 predicateSet.add(predicate); 114 Complex c = complex(); 115 predicate(predicate, c); 116 return c; 117 } 118 119 public void prefix(String code, String url) { 120 RdfGenerator.this.prefix(code, url); 121 } 122 } 123 124 private class Predicate { 125 protected String predicate; 126 protected Triple object; 127 protected String comment; 128 129 public String getPredicate() { 130 return predicate; 131 } 132 public Triple getObject() { 133 return object; 134 } 135 public String getComment() { 136 return comment; 137 } 138 } 139 140 public class Subject extends Complex { 141 private String id; 142 143 public Predicate predicate(String predicate, Triple object, String comment) { 144 Predicate p = new Predicate(); 145 p.predicate = predicate; 146 predicateSet.add(predicate); 147 if (object instanceof StringType) 148 objectSet.add(((StringType) object).value); 149 p.object = object; 150 predicates.add(p); 151 p.comment = comment; 152 return p; 153 } 154 155 public void comment(String comment) { 156 if (!Utilities.noString(comment)) { 157 predicate("rdfs:comment", literal(comment)); 158 predicate("dcterms:description", literal(comment)); 159 } 160 } 161 162 public void label(String label) { 163 if (!Utilities.noString(label)) { 164 predicate("rdfs:label", literal(label)); 165 predicate("dc:title", literal(label)); 166 } 167 } 168 169 } 170 171 public class Section { 172 private String name; 173 private List<Subject> subjects = new ArrayList<Subject>(); 174 175 public Subject triple(String subject, String predicate, String object, String comment) { 176 return triple(subject, predicate, new StringType(object), comment); 177 } 178 179 public Subject triple(String subject, String predicate, String object) { 180 return triple(subject, predicate, new StringType(object)); 181 } 182 183 public Subject triple(String subject, String predicate, Triple object) { 184 return triple(subject, predicate, object, null); 185 } 186 187 public Subject triple(String subject, String predicate, Triple object, String comment) { 188 Subject s = subject(subject); 189 s.predicate(predicate, object, comment); 190 return s; 191 } 192 193 public void comment(String subject, String comment) { 194 triple(subject, "rdfs:comment", literal(comment)); 195 triple(subject, "dcterms:description", literal(comment)); 196 } 197 198 public void label(String subject, String comment) { 199 triple(subject, "rdfs:label", literal(comment)); 200 triple(subject, "dc:title", literal(comment)); 201 } 202 203 public void importTtl(String ttl) throws Exception { 204 if (!Utilities.noString(ttl)) { 205 // System.out.println("import ttl: "+ttl); 206 TurtleLexer lexer = new TurtleLexer(ttl); 207 String subject = null; 208 String predicate = null; 209 while (!lexer.done()) { 210 if (subject == null) 211 subject = lexer.next(); 212 if (predicate == null) 213 predicate = lexer.next(); 214 if (lexer.peekType() == null) { 215 throw new Error("Unexpected end of input parsing turtle"); 216 } if (lexer.peekType() == TurtleTokenType.TOKEN) { 217 triple(subject, predicate, lexer.next()); 218 } else if (lexer.peek() == null) { 219 throw new Error("Unexected - turtle lexer found no token"); 220 } else if (lexer.peek().equals("[")) { 221 triple(subject, predicate, importComplex(lexer)); 222 } else 223 throw new Exception("Not done yet"); 224 String n = lexer.next(); 225 if (Utilities.noString(n)) 226 break; 227 if (n.equals(".")) { 228 subject = null; 229 predicate = null; 230 } else if (n.equals(";")) { 231 predicate = null; 232 } else if (!n.equals(",")) 233 throw new Exception("Unexpected token "+n); 234 } 235 } 236 } 237 238 private Complex importComplex(TurtleLexer lexer) throws Exception { 239 lexer.next(); // read [ 240 Complex obj = new Complex(); 241 while (!lexer.peek().equals("]")) { 242 String predicate = lexer.next(); 243 if (lexer.peekType() == TurtleTokenType.TOKEN || lexer.peekType() == TurtleTokenType.LITERAL) { 244 obj.predicate(predicate, lexer.next()); 245 } else if (lexer.peek().equals("[")) { 246 obj.predicate(predicate, importComplex(lexer)); 247 } else 248 throw new Exception("Not done yet"); 249 if (lexer.peek().equals(";")) 250 lexer.next(); 251 } 252 lexer.next(); // read ] 253 return obj; 254 } 255 256 public Subject subject(String subject) { 257 for (Subject ss : subjects) 258 if (ss.id.equals(subject)) 259 return ss; 260 Subject s = new Subject(); 261 s.id = subject; 262 subjects.add(s); 263 return s; 264 } 265 } 266 267 private List<Section> sections = new ArrayList<Section>(); 268 protected Set<String> subjectSet = new HashSet<String>(); 269 protected Set<String> predicateSet = new HashSet<String>(); 270 protected Set<String> objectSet = new HashSet<String>(); 271 private OutputStream destination; 272 protected Map<String, String> prefixes = new HashMap<String, String>(); 273 274 275 public RdfGenerator(OutputStream destination) { 276 super(); 277 this.destination = destination; 278 } 279 280 protected String pctEncode(String s) { 281 if (s == null) 282 return ""; 283 284 StringBuilder b = new StringBuilder(); 285 for (char c : s.toCharArray()) { 286 if (c >= 'A' && c <= 'Z') 287 b.append(c); 288 else if (c >= 'a' && c <= 'z') 289 b.append(c); 290 else if (c >= '0' && c <= '9') 291 b.append(c); 292 else if (c == '.') 293 b.append(c); 294 else 295 b.append("%"+Integer.toHexString(c)); 296 } 297 return b.toString(); 298 } 299 300 protected List<String> sorted(Set<String> keys) { 301 List<String> names = new ArrayList<String>(); 302 names.addAll(keys); 303 Collections.sort(names); 304 return names; 305 } 306 307 308 public void prefix(String code, String url) { 309 if (!prefixes.containsKey(code)) 310 prefixes.put(code, url); 311 else if (!prefixes.get(code).equals(url)) 312 throw new Error("The prefix "+code+" is already assigned to "+prefixes.get(code)+" so cannot be set to "+url); 313 } 314 315 protected boolean hasSection(String sn) { 316 for (Section s : sections) 317 if (s.name.equals(sn)) 318 return true; 319 return false; 320 321 } 322 323 public Section section(String sn) { 324 if (hasSection(sn)) 325 throw new Error("Duplicate section name "+sn); 326 Section s = new Section(); 327 s.name = sn; 328 sections.add(s); 329 return s; 330 } 331 332 protected String matches(String url, String prefixUri, String prefix) { 333 if (url.startsWith(prefixUri)) { 334 prefixes.put(prefix, prefixUri); 335 return prefix+":"+escape(url.substring(prefixUri.length()), false); 336 } 337 return null; 338 } 339 340 // protected PredicateObject predicateObj(String predicate, TripleObject object) { 341 // PredicateObject obj = new PredicateObject(); 342 // obj.predicate = predicate; 343 // predicates.add(predicate); 344 // obj.object = object; 345 // return obj; 346 // } 347 // 348 // protected PredicateObject predicate(String predicate, String object) { 349 // PredicateObject obj = new PredicateObject(); 350 // obj.predicate = predicate; 351 // predicates.add(predicate); 352 // obj.object = new StringObject(object); 353 // return obj; 354 // } 355 // 356 // protected PredicateObject predicate(String predicate, String object, String comment) { 357 // PredicateObject obj = new PredicateObject(); 358 // obj.predicate = predicate; 359 // predicates.add(predicate); 360 // obj.object = new StringObject(object); 361 // obj.comment = comment; 362 // return obj; 363 // } 364 // 365 protected Complex complex() { 366 return new Complex(); 367 } 368 // 369 // protected TripleObject complex(PredicateObject predicate1, PredicateObject predicate2) { 370 // ComplexObject obj = new ComplexObject(); 371 // obj.predicates.add(predicate1); 372 // obj.predicates.add(predicate2); 373 // return obj; 374 // } 375 // 376 // protected TripleObject complex(PredicateObject predicate1, PredicateObject predicate2, PredicateObject predicate3) { 377 // ComplexObject obj = new ComplexObject(); 378 // obj.predicates.add(predicate1); 379 // obj.predicates.add(predicate2); 380 // obj.predicates.add(predicate3); 381 // return obj; 382 // } 383 // 384 // protected void triple(String section, String subject, String predicate, String object) { 385 // triple(section, subject, predicate, new StringObject(object), null); 386 // } 387 // 388 // protected void triple(String section, String subject, String predicate, TripleObject object) { 389 // triple(section, subject, predicate, object, null); 390 // } 391 // 392 // protected void triple(String section, String subject, String predicate, String object, String comment) { 393 // triple(section, subject, predicate, new StringObject(object), comment); 394 // } 395 // 396 // protected void primaryTriple(String section, String subject, String predicate, String object) { 397 // Section s = sections.get(sections.size()-1); 398 // if (s.primary != null) 399 // throw new Error("multiple primary objects"); 400 // s.primary = triple(section, null, subject, predicate, new StringObject(object), null); 401 // } 402 // 403 // protected Triple triple(String section, Integer order, String subject, String predicate, TripleObject object, String comment) { 404 // if (!hasSection(section)) 405 // throw new Error("use undefined section "+section); 406 // checkPrefix(subject); 407 // checkPrefix(predicate); 408 // checkPrefix(object); 409 // predicates.add(predicate); 410 // Triple t = new Triple(section, order, subject, predicate, object, comment == null ? "" : " # "+comment.replace("\r\n", " ").replace("\r", " ").replace("\n", " ")); 411 // triples.add(t); 412 // return t; 413 // } 414 415 private void checkPrefix(Triple object) { 416 if (object instanceof StringType) 417 checkPrefix(((StringType) object).value); 418 else { 419 Complex obj = (Complex) object; 420 for (Predicate po : obj.predicates) { 421 checkPrefix(po.getPredicate()); 422 checkPrefix(po.getObject()); 423 } 424 } 425 426 } 427 428 protected void checkPrefix(String pname) { 429 if (pname.startsWith("(")) 430 return; 431 if (pname.startsWith("\"")) 432 return; 433 if (pname.startsWith("<")) 434 return; 435 436 if (pname.contains(":")) { 437 String prefix = pname.substring(0, pname.indexOf(":")); 438 if (!prefixes.containsKey(prefix) && !prefix.equals("http")&& !prefix.equals("urn")) 439 throw new Error("undefined prefix "+prefix); 440 } 441 } 442 443 protected StringType literal(String s) { 444 return new StringType("\""+escape(s, true)+"\""); 445 } 446 447 public static String escape(String s, boolean string) { 448 if (s == null) 449 return ""; 450 451 StringBuilder b = new StringBuilder(); 452 for (char c : s.toCharArray()) { 453 if (c == '\r') 454 b.append("\\r"); 455 else if (c == '\n') 456 b.append("\\n"); 457 else if (c == '"') 458 b.append("\\\""); 459 else if (c == '\\') 460 b.append("\\\\"); 461 else if (c == '/' && !string) 462 b.append("\\/"); 463 else 464 b.append(c); 465 } 466 return b.toString(); 467 } 468 469 protected class LineOutputStreamWriter extends OutputStreamWriter { 470 private LineOutputStreamWriter(OutputStream out) throws UnsupportedEncodingException { 471 super(out, "UTF-8"); 472 } 473 474 private void ln() throws Exception { 475 write("\r\n"); 476 } 477 478 private void ln(String s) throws Exception { 479 write(s); 480 write("\r\n"); 481 } 482 483 } 484 485 486 public void commit(boolean header) throws Exception { 487 LineOutputStreamWriter writer = new LineOutputStreamWriter(destination); 488 commitPrefixes(writer, header); 489 for (Section s : sections) { 490 commitSection(writer, s); 491 } 492 writer.ln("# -------------------------------------------------------------------------------------"); 493 writer.ln(); 494 writer.flush(); 495 writer.close(); 496 } 497 498 private void commitPrefixes(LineOutputStreamWriter writer, boolean header) throws Exception { 499 if (header) { 500 writer.ln("# FHIR Sub-definitions"); 501 writer.write("# This is work in progress, and may change rapidly \r\n"); 502 writer.ln(); 503 writer.write("# A note about policy: the focus here is providing the knowledge from \r\n"); 504 writer.write("# the FHIR specification as a set of triples for knowledge processing. \r\n"); 505 writer.write("# Where appopriate, predicates defined external to FHIR are used. \"Where \r\n"); 506 writer.write("# appropriate\" means that the predicates are a faithful representation \r\n"); 507 writer.write("# of the FHIR semantics, and do not involve insane (or owful) syntax. \r\n"); 508 writer.ln(); 509 writer.write("# Where the community agrees on additional predicate statements (such \r\n"); 510 writer.write("# as OWL constraints) these are added in addition to the direct FHIR \r\n"); 511 writer.write("# predicates \r\n"); 512 writer.ln(); 513 writer.write("# This it not a formal ontology, though it is possible it may start to become one eventually\r\n"); 514 writer.ln(); 515 writer.write("# this file refers to concepts defined in rim.ttl and to others defined elsewhere outside HL7 \r\n"); 516 writer.ln(); 517 } 518 for (String p : sorted(prefixes.keySet())) 519 writer.ln("@prefix "+p+": <"+prefixes.get(p)+"> ."); 520 writer.ln(); 521 if (header) { 522 writer.ln("# Predicates used in this file:"); 523 for (String s : sorted(predicateSet)) 524 writer.ln(" # "+s); 525 writer.ln(); 526 } 527 } 528 529 // private String lastSubject = null; 530 // private String lastComment = ""; 531 532 private void commitSection(LineOutputStreamWriter writer, Section section) throws Exception { 533 writer.ln("# - "+section.name+" "+Utilities.padLeft("", '-', 75-section.name.length())); 534 writer.ln(); 535 for (Subject sbj : section.subjects) { 536 writer.write(sbj.id); 537 writer.write(" "); 538 int i = 0; 539 540 for (Predicate p : sbj.predicates) { 541 writer.write(p.getPredicate()); 542 writer.write(" "); 543 if (p.getObject() instanceof StringType) 544 writer.write(((StringType) p.getObject()).value); 545 else { 546 writer.write("["); 547 if (((Complex) p.getObject()).write(writer, 4)) 548 writer.write("\r\n ]"); 549 else 550 writer.write("]"); 551 } 552 String comment = p.comment == null? "" : " # "+p.comment; 553 i++; 554 if (i < sbj.predicates.size()) 555 writer.write(";"+comment+"\r\n "); 556 else 557 writer.write("."+comment+"\r\n\r\n"); 558 } 559 560 } 561 562 } 563 564 // private void coomitTriple(LineOutputStreamWriter writer, Triple t) throws Exception, IOException { 565 // boolean follow = false; 566 // if (lastSubject != null) { 567 // follow = lastSubject.equals(t.getSubject()); 568 // String c = follow ? ";" : "."; 569 // writer.ln(c+lastComment); 570 // if (!follow) 571 // writer.ln(); 572 // } 573 // String left = follow ? Utilities.padLeft("", ' ', 2) : t.getSubject(); 574 // lastComment = t.getComment(); 575 // lastSubject = t.getSubject(); 576 // } 577 578 579}