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}