001package org.hl7.fhir.utilities.xml;
002
003import java.io.IOException;
004import java.io.OutputStream;
005import java.io.UnsupportedEncodingException;
006import java.util.ArrayList;
007import java.util.List;
008
009import org.hl7.fhir.utilities.TextStreamWriter;
010import org.hl7.fhir.utilities.Utilities;
011
012
013public class SchematronWriter  extends TextStreamWriter  {
014
015  public enum SchematronType {
016    ALL_RESOURCES,
017    RESOURCE,
018    PROFILE
019  }
020
021  public class Assert {
022    private String test;
023    private String message; 
024  }
025  
026  public class Rule {
027    private String name; 
028    private List<Assert> asserts = new ArrayList<Assert>();   
029    public void assrt(String test, String message) {
030      Assert a = new Assert();
031      a.test = test;
032      a.message = message;
033      asserts.add(a);
034    }
035    
036    public boolean isSpecial() {
037      return name.contains("*") || name.contains("[");
038    }
039  }
040  public class Section {
041    private String title;
042    private List<Rule> rules = new ArrayList<Rule>();
043    
044    public String getTitle() {
045      return title;
046    }
047
048    public void setTitle(String title) {
049      this.title = title;
050    }
051
052    public Rule rule(String name) {
053      for (Rule r : rules) {
054        if (r.name.equals(name))
055          return r;
056      }
057      Rule r = new Rule();
058      r.name = name;
059      rules.add(r);
060      return r;
061    }
062
063    public boolean hasRegularContent() {
064      for (Rule r : rules) 
065        if (!r.asserts.isEmpty() && !r.isSpecial())
066          return true;
067      return false;
068    }
069
070    public boolean hasSpecialContent() {
071      for (Rule r : rules) 
072        if (!r.asserts.isEmpty() && r.isSpecial())
073          return true;
074      return false;
075    }
076    
077    public List<Rule> getRegularRules() {
078      List<Rule> regular = new ArrayList<Rule>();
079      for (Rule r : rules) 
080        if (!r.asserts.isEmpty() && !r.isSpecial())
081          regular.add(r);
082      return regular;
083    }
084
085    public List<Rule> getSpecialRules() {
086      List<Rule> regular = new ArrayList<Rule>();
087      for (Rule r : rules) 
088        if (!r.asserts.isEmpty() && r.isSpecial())
089          regular.add(r);
090      return regular;
091    }
092}
093
094  private SchematronType type;
095  private String description;
096  private List<Section> sections = new ArrayList<Section>();
097
098
099  public SchematronWriter(OutputStream out, SchematronType type, String description) throws UnsupportedEncodingException {
100    super(out);
101    this.type = type;
102    this.description = description;
103  }
104
105  public Section section(String title) {
106    for (Section s : sections) {
107      if (s.title.equals(title))
108        return s;
109    }
110    Section s = new Section();
111    s.title = title;
112    sections.add(s);
113    return s;
114  }
115  
116  public void dump() throws IOException {
117    ln("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
118    ln_i("<sch:schema xmlns:sch=\"http://purl.oclc.org/dsdl/schematron\" queryBinding=\"xslt2\">");
119    ln("<sch:ns prefix=\"f\" uri=\"http://hl7.org/fhir\"/>");
120    ln("<sch:ns prefix=\"h\" uri=\"http://www.w3.org/1999/xhtml\"/>");
121    addNote();
122
123    for (Section s : sections) {
124      if (s.hasRegularContent()) {
125        ln_i("<sch:pattern>");
126        ln("<sch:title>"+Utilities.escapeXml(s.title)+"</sch:title>");
127        for (Rule r : s.getRegularRules()) {
128          if (!r.asserts.isEmpty()) {
129            ln_i("<sch:rule context=\""+Utilities.escapeXml(r.name)+"\">");
130            for (Assert a : r.asserts) 
131              ln("<sch:assert test=\""+Utilities.escapeXml(a.test)+"\">"+Utilities.escapeXml(a.message)+"</sch:assert>");
132            ln_o("</sch:rule>");
133          }
134        }
135        ln_o("</sch:pattern>");
136      }
137      if (s.hasSpecialContent()) {
138        int i = 1;
139        for (Rule r : s.getSpecialRules()) {
140          ln_i("<sch:pattern>");
141          ln("<sch:title>"+Utilities.escapeXml(s.title)+" "+i+"</sch:title>");
142          i++;
143          if (!r.asserts.isEmpty()) {
144            ln_i("<sch:rule context=\""+Utilities.escapeXml(r.name)+"\">");
145            for (Assert a : r.asserts) 
146              ln("<sch:assert test=\""+Utilities.escapeXml(a.test)+"\">"+Utilities.escapeXml(a.message)+"</sch:assert>");
147            ln_o("</sch:rule>");
148          }
149          ln_o("</sch:pattern>");
150        }
151      }
152    }  
153    ln_o("</sch:schema>");
154    flush();
155    close();
156  }
157
158  private void addNote() throws IOException {
159    switch (type) {
160    case ALL_RESOURCES : addAllResourcesNote(); break;
161    case RESOURCE : addResourceNote(); break;
162    case PROFILE : addProfileNote(); break;
163    }
164  }
165
166  private void addAllResourcesNote() throws IOException {
167    ln("<!-- ");
168    ln("  This file contains constraints for all resources");
169    ln("  Because of the way containment works, this file should always )");
170    ln("  be used for validating resources. Alternatively you can use ");
171    ln("  the resource specific files to build a smaller version of");
172    ln("  this file (the contents are identical; only include those ");
173    ln("  resources relevant to your implementation).");
174    ln("-->");
175  }
176
177  private void addResourceNote() throws IOException {
178    ln("<!-- ");
179    ln("  This file contains just the constraints for the resource "+description);
180    ln("  It is provided for documentation purposes. When actually validating,");
181    ln("  always use fhir-invariants.sch (because of the way containment works)");
182    ln("  Alternatively you can use this file to build a smaller version of");
183    ln("  fhir-invariants.sch (the contents are identical; only include those ");
184    ln("  resources relevant to your implementation).");
185    ln("-->");
186  }
187
188  private void addProfileNote() throws IOException {
189    ln("<!-- ");
190    ln("  This file contains just the constraints for the profile "+description);
191    ln("  It includes the base constraints for the resource as well.");
192    ln("  Because of the way that schematrons and containment work, ");
193    ln("  you may need to use this schematron fragment to build a, ");
194    ln("  single schematron that validates contained resources (if you have any) ");
195    ln("-->");
196    
197  }
198
199}