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}