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