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}