
001package org.hl7.fhir.r4.conformance; 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 032import java.io.FileOutputStream; 033/* 034Copyright (c) 2011+, HL7, Inc 035All rights reserved. 036 037Redistribution and use in source and binary forms, with or without modification, 038are permitted provided that the following conditions are met: 039 040 * Redistributions of source code must retain the above copyright notice, this 041 list of conditions and the following disclaimer. 042 * Redistributions in binary form must reproduce the above copyright notice, 043 this list of conditions and the following disclaimer in the documentation 044 and/or other materials provided with the distribution. 045 * Neither the name of HL7 nor the names of its contributors may be used to 046 endorse or promote products derived from this software without specific 047 prior written permission. 048 049THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 050ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 051WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 052IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 053INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 054NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 055PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 056WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 057ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 058POSSIBILITY OF SUCH DAMAGE. 059 060 */ 061import java.io.IOException; 062import java.io.OutputStreamWriter; 063import java.util.ArrayList; 064import java.util.HashMap; 065import java.util.HashSet; 066import java.util.LinkedList; 067import java.util.List; 068import java.util.Map; 069import java.util.Queue; 070import java.util.Set; 071 072import org.hl7.fhir.exceptions.FHIRException; 073import org.hl7.fhir.r4.context.IWorkerContext; 074import org.hl7.fhir.r4.model.ElementDefinition; 075import org.hl7.fhir.r4.model.ElementDefinition.PropertyRepresentation; 076import org.hl7.fhir.r4.model.ElementDefinition.TypeRefComponent; 077import org.hl7.fhir.r4.model.StructureDefinition; 078import org.hl7.fhir.r4.utils.ToolingExtensions; 079import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 080import org.hl7.fhir.utilities.Utilities; 081import org.hl7.fhir.utilities.filesystem.ManagedFileAccess; 082 083@Deprecated 084public class XmlSchemaGenerator { 085 086 public class QName { 087 088 public String type; 089 public String typeNs; 090 091 @Override 092 public String toString() { 093 return typeNs + ":" + type; 094 } 095 } 096 097 public class ElementToGenerate { 098 099 private String tname; 100 private StructureDefinition sd; 101 private ElementDefinition ed; 102 103 public ElementToGenerate(String tname, StructureDefinition sd, ElementDefinition edc) { 104 this.tname = tname; 105 this.sd = sd; 106 this.ed = edc; 107 } 108 109 } 110 111 private String folder; 112 private IWorkerContext context; 113 private boolean single; 114 private String version; 115 private String genDate; 116 private String license; 117 private boolean annotations; 118 119 public XmlSchemaGenerator(String folder, IWorkerContext context) { 120 this.folder = folder; 121 this.context = context; 122 } 123 124 public boolean isSingle() { 125 return single; 126 } 127 128 public void setSingle(boolean single) { 129 this.single = single; 130 } 131 132 public String getVersion() { 133 return version; 134 } 135 136 public void setVersion(String version) { 137 this.version = version; 138 } 139 140 public String getGenDate() { 141 return genDate; 142 } 143 144 public void setGenDate(String genDate) { 145 this.genDate = genDate; 146 } 147 148 public String getLicense() { 149 return license; 150 } 151 152 public void setLicense(String license) { 153 this.license = license; 154 } 155 156 public boolean isAnnotations() { 157 return annotations; 158 } 159 160 public void setAnnotations(boolean annotations) { 161 this.annotations = annotations; 162 } 163 164 private Set<ElementDefinition> processed = new HashSet<ElementDefinition>(); 165 private Set<StructureDefinition> processedLibs = new HashSet<StructureDefinition>(); 166 private Set<String> typeNames = new HashSet<String>(); 167 private OutputStreamWriter writer; 168 private Map<String, String> namespaces = new HashMap<String, String>(); 169 private Queue<ElementToGenerate> queue = new LinkedList<ElementToGenerate>(); 170 private Queue<StructureDefinition> queueLib = new LinkedList<StructureDefinition>(); 171 private Map<String, StructureDefinition> library; 172 private boolean useNarrative; 173 174 private void w(String s) throws IOException { 175 writer.write(s); 176 } 177 178 private void ln(String s) throws IOException { 179 writer.write(s); 180 writer.write("\r\n"); 181 } 182 183 private void close() throws IOException { 184 if (writer != null) { 185 ln("</xs:schema>"); 186 writer.flush(); 187 writer.close(); 188 writer = null; 189 } 190 } 191 192 private String start(StructureDefinition sd, String ns) throws IOException, FHIRException { 193 String lang = "en"; 194 if (sd.hasLanguage()) 195 lang = sd.getLanguage(); 196 197 if (single && writer != null) { 198 if (!ns.equals(getNs(sd))) 199 throw new FHIRException("namespace inconsistency: " + ns + " vs " + getNs(sd)); 200 return lang; 201 } 202 close(); 203 204 writer = new OutputStreamWriter(ManagedFileAccess.outStream(Utilities.path(folder, tail(sd.getType() + ".xsd"))), "UTF-8"); 205 ln("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); 206 ln("<!-- "); 207 ln(license); 208 ln(""); 209 ln(" Generated on " + genDate + " for FHIR v" + version + " "); 210 ln(""); 211 ln(" Note: this schema does not contain all the knowledge represented in the underlying content model"); 212 ln(""); 213 ln("-->"); 214 ln("<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:fhir=\"http://hl7.org/fhir\" xmlns:xhtml=\"http://www.w3.org/1999/xhtml\" " 215 + "xmlns:lm=\"" + ns + "\" targetNamespace=\"" + ns + "\" elementFormDefault=\"qualified\" version=\"1.0\">"); 216 ln(" <xs:import schemaLocation=\"fhir-common.xsd\" namespace=\"http://hl7.org/fhir\"/>"); 217 if (useNarrative) { 218 if (ns.equals("urn:hl7-org:v3")) 219 ln(" <xs:include schemaLocation=\"cda-narrative.xsd\"/>"); 220 else 221 ln(" <xs:import schemaLocation=\"cda-narrative.xsd\" namespace=\"urn:hl7-org:v3\"/>"); 222 } 223 namespaces.clear(); 224 namespaces.put(ns, "lm"); 225 namespaces.put("http://hl7.org/fhir", "fhir"); 226 typeNames.clear(); 227 228 return lang; 229 } 230 231 private String getNs(StructureDefinition sd) { 232 String ns = "http://hl7.org/fhir"; 233 if (sd.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace")) 234 ns = ToolingExtensions.readStringExtension(sd, 235 "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"); 236 return ns; 237 } 238 239 public void generate(StructureDefinition entry, Map<String, StructureDefinition> library) throws Exception { 240 processedLibs.clear(); 241 242 this.library = library; 243 checkLib(entry); 244 245 String ns = getNs(entry); 246 String lang = start(entry, ns); 247 248 w(" <xs:element name=\"" + tail(entry.getType()) + "\" type=\"lm:" + tail(entry.getType()) + "\""); 249 if (annotations) { 250 ln(">"); 251 ln(" <xs:annotation>"); 252 ln(" <xs:documentation xml:lang=\"" + lang + "\">" + Utilities.escapeXml(entry.getDescription()) 253 + "</xs:documentation>"); 254 ln(" </xs:annotation>"); 255 ln(" </xs:element>"); 256 } else 257 ln("/>"); 258 259 produceType(entry, entry.getSnapshot().getElement().get(0), tail(entry.getType()), 260 getQN(entry, entry.getBaseDefinition()), lang); 261 while (!queue.isEmpty()) { 262 ElementToGenerate q = queue.poll(); 263 produceType(q.sd, q.ed, q.tname, getQN(q.sd, q.ed, "http://hl7.org/fhir/StructureDefinition/Element", false), 264 lang); 265 } 266 while (!queueLib.isEmpty()) { 267 generateInner(queueLib.poll()); 268 } 269 close(); 270 } 271 272 private void checkLib(StructureDefinition entry) { 273 for (ElementDefinition ed : entry.getSnapshot().getElement()) { 274 if (ed.hasRepresentation(PropertyRepresentation.CDATEXT)) { 275 useNarrative = true; 276 } 277 } 278 for (StructureDefinition sd : library.values()) { 279 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 280 if (ed.hasRepresentation(PropertyRepresentation.CDATEXT)) { 281 useNarrative = true; 282 } 283 } 284 } 285 } 286 287 private void generateInner(StructureDefinition sd) throws IOException, FHIRException { 288 if (processedLibs.contains(sd)) 289 return; 290 processedLibs.add(sd); 291 292 String ns = getNs(sd); 293 String lang = start(sd, ns); 294 295 if (sd.getSnapshot().getElement().isEmpty()) 296 throw new FHIRException("no snap shot on " + sd.getUrl()); 297 298 produceType(sd, sd.getSnapshot().getElement().get(0), tail(sd.getType()), getQN(sd, sd.getBaseDefinition()), lang); 299 while (!queue.isEmpty()) { 300 ElementToGenerate q = queue.poll(); 301 produceType(q.sd, q.ed, q.tname, getQN(q.sd, q.ed, "http://hl7.org/fhir/StructureDefinition/Element", false), 302 lang); 303 } 304 } 305 306 private String tail(String url) { 307 return url.contains("/") ? url.substring(url.lastIndexOf("/") + 1) : url; 308 } 309 310 private String root(String url) { 311 return url.contains("/") ? url.substring(0, url.lastIndexOf("/")) : ""; 312 } 313 314 private String tailDot(String url) { 315 return url.contains(".") ? url.substring(url.lastIndexOf(".") + 1) : url; 316 } 317 318 private void produceType(StructureDefinition sd, ElementDefinition ed, String typeName, QName typeParent, String lang) 319 throws IOException, FHIRException { 320 if (processed.contains(ed)) 321 return; 322 processed.add(ed); 323 324 // ok 325 ln(" <xs:complexType name=\"" + typeName + "\">"); 326 if (annotations) { 327 ln(" <xs:annotation>"); 328 ln(" <xs:documentation xml:lang=\"" + lang + "\">" + Utilities.escapeXml(ed.getDefinition()) 329 + "</xs:documentation>"); 330 ln(" </xs:annotation>"); 331 } 332 ln(" <xs:complexContent>"); 333 ln(" <xs:extension base=\"" + typeParent.toString() + "\">"); 334 ln(" <xs:sequence>"); 335 336 // hack.... 337 for (ElementDefinition edc : ProfileUtilities.getChildList(sd, ed)) { 338 if (!(edc.hasRepresentation(PropertyRepresentation.XMLATTR) 339 || edc.hasRepresentation(PropertyRepresentation.XMLTEXT)) && !inheritedElement(edc)) 340 produceElement(sd, ed, edc, lang); 341 } 342 ln(" </xs:sequence>"); 343 for (ElementDefinition edc : ProfileUtilities.getChildList(sd, ed)) { 344 if ((edc.hasRepresentation(PropertyRepresentation.XMLATTR) 345 || edc.hasRepresentation(PropertyRepresentation.XMLTEXT)) && !inheritedElement(edc)) 346 produceAttribute(sd, ed, edc, lang); 347 } 348 ln(" </xs:extension>"); 349 ln(" </xs:complexContent>"); 350 ln(" </xs:complexType>"); 351 } 352 353 private boolean inheritedElement(ElementDefinition edc) { 354 return !edc.getPath().equals(edc.getBase().getPath()); 355 } 356 357 private void produceElement(StructureDefinition sd, ElementDefinition ed, ElementDefinition edc, String lang) 358 throws IOException, FHIRException { 359 if (edc.getType().size() == 0) 360 throw new Error("No type at " + edc.getPath()); 361 362 if (edc.getType().size() > 1 && edc.hasRepresentation(PropertyRepresentation.TYPEATTR)) { 363 // first, find the common base type 364 StructureDefinition lib = getCommonAncestor(edc.getType()); 365 if (lib == null) 366 throw new Error("Common ancester not found at " + edc.getPath()); 367 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 368 for (TypeRefComponent t : edc.getType()) { 369 b.append(getQN(sd, edc, t.getWorkingCode(), true).toString()); 370 } 371 372 String name = tailDot(edc.getPath()); 373 String min = String.valueOf(edc.getMin()); 374 String max = edc.getMax(); 375 if ("*".equals(max)) 376 max = "unbounded"; 377 378 QName qn = getQN(sd, edc, lib.getUrl(), true); 379 380 ln(" <xs:element name=\"" + name + "\" minOccurs=\"" + min + "\" maxOccurs=\"" + max + "\" type=\"" 381 + qn.typeNs + ":" + qn.type + "\">"); 382 ln(" <xs:annotation>"); 383 ln(" <xs:appinfo xml:lang=\"en\">Possible types: " + b.toString() + "</xs:appinfo>"); 384 if (annotations && edc.hasDefinition()) 385 ln(" <xs:documentation xml:lang=\"" + lang + "\">" + Utilities.escapeXml(edc.getDefinition()) 386 + "</xs:documentation>"); 387 ln(" </xs:annotation>"); 388 ln(" </xs:element>"); 389 } else 390 for (TypeRefComponent t : edc.getType()) { 391 String name = tailDot(edc.getPath()); 392 if (edc.getType().size() > 1) 393 name = name + Utilities.capitalize(t.getWorkingCode()); 394 QName qn = getQN(sd, edc, t.getWorkingCode(), true); 395 String min = String.valueOf(edc.getMin()); 396 String max = edc.getMax(); 397 if ("*".equals(max)) 398 max = "unbounded"; 399 400 w(" <xs:element name=\"" + name + "\" minOccurs=\"" + min + "\" maxOccurs=\"" + max + "\" type=\"" 401 + qn.typeNs + ":" + qn.type + "\""); 402 if (annotations && edc.hasDefinition()) { 403 ln(">"); 404 ln(" <xs:annotation>"); 405 ln(" <xs:documentation xml:lang=\"" + lang + "\">" + Utilities.escapeXml(edc.getDefinition()) 406 + "</xs:documentation>"); 407 ln(" </xs:annotation>"); 408 ln(" </xs:element>"); 409 } else 410 ln("/>"); 411 } 412 } 413 414 public QName getQN(StructureDefinition sd, String type) throws FHIRException { 415 return getQN(sd, sd.getSnapshot().getElementFirstRep(), type, false); 416 } 417 418 public QName getQN(StructureDefinition sd, ElementDefinition edc, String t, boolean chase) throws FHIRException { 419 QName qn = new QName(); 420 qn.type = Utilities.isAbsoluteUrl(t) ? tail(t) : t; 421 if (Utilities.isAbsoluteUrl(t)) { 422 String ns = root(t); 423 if (ns.equals(root(sd.getUrl()))) 424 ns = getNs(sd); 425 if (ns.equals("http://hl7.org/fhir/StructureDefinition")) 426 ns = "http://hl7.org/fhir"; 427 if (!namespaces.containsKey(ns)) 428 throw new FHIRException("Unknown type namespace " + ns + " for " + edc.getPath()); 429 qn.typeNs = namespaces.get(ns); 430 StructureDefinition lib = library.get(t); 431 if (lib == null && !Utilities.existsInList(t, "http://hl7.org/fhir/cda/StructureDefinition/StrucDoc.Text", 432 "http://hl7.org/fhir/StructureDefinition/Element")) 433 throw new FHIRException("Unable to resolve " + t + " for " + edc.getPath()); 434 if (lib != null) 435 queueLib.add(lib); 436 } else 437 qn.typeNs = namespaces.get("http://hl7.org/fhir"); 438 439 if (chase && qn.type.equals("Element")) { 440 String tname = typeNameFromPath(edc); 441 if (typeNames.contains(tname)) { 442 int i = 1; 443 while (typeNames.contains(tname + i)) 444 i++; 445 tname = tname + i; 446 } 447 queue.add(new ElementToGenerate(tname, sd, edc)); 448 qn.typeNs = "lm"; 449 qn.type = tname; 450 } 451 return qn; 452 } 453 454 private StructureDefinition getCommonAncestor(List<TypeRefComponent> type) throws FHIRException { 455 StructureDefinition sd = library.get(type.get(0).getWorkingCode()); 456 if (sd == null) 457 throw new FHIRException("Unable to find definition for " + type.get(0).getWorkingCode()); 458 for (int i = 1; i < type.size(); i++) { 459 StructureDefinition t = library.get(type.get(i).getWorkingCode()); 460 if (t == null) 461 throw new FHIRException("Unable to find definition for " + type.get(i).getWorkingCode()); 462 sd = getCommonAncestor(sd, t); 463 } 464 return sd; 465 } 466 467 private StructureDefinition getCommonAncestor(StructureDefinition sd1, StructureDefinition sd2) throws FHIRException { 468 // this will always return something because everything comes from Element 469 List<StructureDefinition> chain1 = new ArrayList<>(); 470 List<StructureDefinition> chain2 = new ArrayList<>(); 471 chain1.add(sd1); 472 chain2.add(sd2); 473 StructureDefinition root = library.get("Element"); 474 StructureDefinition common = findIntersection(chain1, chain2); 475 boolean chain1Done = false; 476 boolean chain2Done = false; 477 while (common == null) { 478 chain1Done = checkChain(chain1, root, chain1Done); 479 chain2Done = checkChain(chain2, root, chain2Done); 480 if (chain1Done && chain2Done) 481 return null; 482 common = findIntersection(chain1, chain2); 483 } 484 return common; 485 } 486 487 private StructureDefinition findIntersection(List<StructureDefinition> chain1, List<StructureDefinition> chain2) { 488 for (StructureDefinition sd1 : chain1) 489 for (StructureDefinition sd2 : chain2) 490 if (sd1 == sd2) 491 return sd1; 492 return null; 493 } 494 495 public boolean checkChain(List<StructureDefinition> chain1, StructureDefinition root, boolean chain1Done) 496 throws FHIRException { 497 if (!chain1Done) { 498 StructureDefinition sd = chain1.get(chain1.size() - 1); 499 String bu = sd.getBaseDefinition(); 500 if (bu == null) 501 throw new FHIRException("No base definition for " + sd.getUrl()); 502 StructureDefinition t = library.get(bu); 503 if (t == null) 504 chain1Done = true; 505 else 506 chain1.add(t); 507 } 508 return chain1Done; 509 } 510 511 private StructureDefinition getBase(StructureDefinition structureDefinition) { 512 return null; 513 } 514 515 private String typeNameFromPath(ElementDefinition edc) { 516 StringBuilder b = new StringBuilder(); 517 boolean up = true; 518 for (char ch : edc.getPath().toCharArray()) { 519 if (ch == '.') 520 up = true; 521 else if (up) { 522 b.append(Character.toUpperCase(ch)); 523 up = false; 524 } else 525 b.append(ch); 526 } 527 return b.toString(); 528 } 529 530 private void produceAttribute(StructureDefinition sd, ElementDefinition ed, ElementDefinition edc, String lang) 531 throws IOException, FHIRException { 532 TypeRefComponent t = edc.getTypeFirstRep(); 533 String name = tailDot(edc.getPath()); 534 String min = String.valueOf(edc.getMin()); 535 String max = edc.getMax(); 536 // todo: check it's a code... 537// if (!max.equals("1")) 538// throw new FHIRException("Illegal cardinality \""+max+"\" for attribute "+edc.getPath()); 539 540 String tc = t.getWorkingCode(); 541 if (Utilities.isAbsoluteUrl(tc)) 542 throw new FHIRException("Only FHIR primitive types are supported for attributes (" + tc + ")"); 543 String typeNs = namespaces.get("http://hl7.org/fhir"); 544 String type = tc; 545 546 w(" <xs:attribute name=\"" + name + "\" use=\"" 547 + (min.equals("0") || edc.hasFixed() || edc.hasDefaultValue() ? "optional" : "required") + "\" type=\"" + typeNs 548 + ":" + type + (typeNs.equals("fhir") ? "-primitive" : "") + "\"" 549 + (edc.hasFixed() ? " fixed=\"" + edc.getFixed().primitiveValue() + "\"" : "") 550 + (edc.hasDefaultValue() && !edc.hasFixed() ? " default=\"" + edc.getDefaultValue().primitiveValue() + "\"" 551 : "") 552 + ""); 553 if (annotations && edc.hasDefinition()) { 554 ln(">"); 555 ln(" <xs:annotation>"); 556 ln(" <xs:documentation xml:lang=\"" + lang + "\">" + Utilities.escapeXml(edc.getDefinition()) 557 + "</xs:documentation>"); 558 ln(" </xs:annotation>"); 559 ln(" </xs:attribute>"); 560 } else 561 ln("/>"); 562 } 563 564}