
001package org.hl7.fhir.r5.renderers; 002 003import java.io.File; 004import java.io.FileInputStream; 005import java.io.IOException; 006import java.io.InputStream; 007import java.util.ArrayList; 008import java.util.HashMap; 009import java.util.HashSet; 010import java.util.List; 011import java.util.Map; 012import java.util.Set; 013 014import javax.xml.parsers.DocumentBuilder; 015import javax.xml.parsers.DocumentBuilderFactory; 016import javax.xml.parsers.ParserConfigurationException; 017 018import org.hl7.fhir.exceptions.FHIRException; 019import org.hl7.fhir.r5.conformance.profile.ProfileUtilities; 020import org.hl7.fhir.r5.conformance.profile.SnapshotGenerationPreProcessor; 021import org.hl7.fhir.r5.context.ContextUtilities; 022import org.hl7.fhir.r5.context.IWorkerContext; 023import org.hl7.fhir.r5.elementmodel.LanguageUtils; 024import org.hl7.fhir.r5.extensions.ExtensionDefinitions; 025import org.hl7.fhir.r5.extensions.ExtensionUtilities; 026import org.hl7.fhir.r5.model.CanonicalType; 027import org.hl7.fhir.r5.model.CodeableConcept; 028import org.hl7.fhir.r5.model.Coding; 029import org.hl7.fhir.r5.model.DataType; 030import org.hl7.fhir.r5.model.ElementDefinition; 031import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent; 032import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent; 033import org.hl7.fhir.r5.model.Enumerations.BindingStrength; 034import org.hl7.fhir.r5.model.MarkdownType; 035import org.hl7.fhir.r5.model.Quantity; 036import org.hl7.fhir.r5.model.StructureDefinition; 037import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind; 038import org.hl7.fhir.r5.model.ValueSet; 039import org.hl7.fhir.r5.renderers.utils.RenderingContext; 040import org.hl7.fhir.r5.renderers.utils.RenderingContext.KnownLinkType; 041 042import org.hl7.fhir.r5.utils.UserDataNames; 043import org.hl7.fhir.utilities.*; 044import org.hl7.fhir.utilities.i18n.RenderingI18nContext; 045import org.hl7.fhir.utilities.json.model.JsonElement; 046import org.hl7.fhir.utilities.json.model.JsonObject; 047import org.hl7.fhir.utilities.xhtml.NodeType; 048import org.hl7.fhir.utilities.xhtml.XhtmlComposer; 049import org.hl7.fhir.utilities.xhtml.XhtmlNode; 050import org.hl7.fhir.utilities.xml.XMLUtil; 051import org.w3c.dom.Document; 052import org.w3c.dom.Element; 053import org.xml.sax.SAXException; 054 055public class ClassDiagramRenderer { 056 private String defnFile; 057 private StructureDefinition defnSD; 058 059 public class LinkInfo { 060 061 private boolean use; 062 private String pathData; 063 private String diamondTransform; 064 private String diamondPoints; 065 } 066 067 private static final String SLICE_COLOR = "#62A856"; 068 069 public enum ClassItemMode { 070 NORMAL, SLICE; 071 } 072 073 private static final double CHAR_RATIO = 4.4; 074 075 public static class PointSpec { 076 private double x; 077 private double y; 078 public PointSpec(double x, double y) { 079 super(); 080 this.x = x; 081 this.y = y; 082 } 083 public double getX() { 084 return x; 085 } 086 public double getY() { 087 return y; 088 } 089 } 090 091 private RenderingContext rc; 092 private IWorkerContext context; 093 private ContextUtilities cutils; 094 private ProfileUtilities putils; 095 private Map<String, PointSpec> layout; 096 private Map<String, LinkInfo> linkLayouts; 097 private String sourceFolder; 098 private String destFolder; 099 private double minx = 0; 100 private double miny = 0; 101 private String prefix; 102 private String diagramId; 103 private Map<String, ClassItem> classes = new HashMap<String, ClassItem>(); 104 private boolean attributes; 105 private boolean innerClasses; 106 private boolean constraintMode; 107 private int nc = 0; 108 private List<Link> links = new ArrayList<Link>(); 109 private List<String> classNames = new ArrayList<String>(); 110 private String lang; 111 112 /** 113 * 114 * @param sourceFolder - where to look for existing designs 115 * @param destFolder - where to generate .svg and .html for diagram review 116 * @param diagramId - the id of the diagram (goes in the filenames and the diagram itself) 117 * @param prefix - a prefix to put on all ids to ensure anchor names don't clash. 118 * @param rc - rendering context 119 * @param lang 120 * 121 * @throws IOException 122 */ 123 public ClassDiagramRenderer(String sourceFolder, String destFolder, String diagramId, String prefix, RenderingContext rc, String lang) throws IOException { 124 this.sourceFolder = sourceFolder; 125 this.destFolder = destFolder; 126 this.rc = rc; 127 this.context = rc.getContext(); 128 this.cutils = rc.getContextUtilities(); 129 this.putils = rc.getProfileUtilities(); 130 File f = new File(destFolder); 131 if (!f.exists()) { 132 FileUtilities.createDirectory(destFolder); 133 } 134 layout = new HashMap<String, ClassDiagramRenderer.PointSpec>(); 135 linkLayouts = new HashMap<String, ClassDiagramRenderer.LinkInfo>(); 136 if (diagramId == null) { 137 throw new Error("An id is required"); 138 } 139 this.diagramId = diagramId; 140 this.prefix = (prefix == null ? "" : prefix); 141 this.lang = lang; 142 } 143 144 public boolean hasSource() { 145 try { 146 File f = new File(Utilities.path(sourceFolder, diagramId+".svg")); 147 return f.exists(); 148 } catch (IOException e) { 149 return false; 150 } 151 } 152 153 public String buildClassDiagram(JsonObject control) throws Exception { 154 File f = new File(Utilities.path(sourceFolder, diagramId+".svg")); 155 if (f.exists()) { 156 parseSvgFile(f, f.getAbsolutePath()); 157 } 158 159 attributes = control.asBoolean("attributes"); 160 innerClasses = !control.asBoolean("no-inner-classes"); 161 classNames = control.forceArray("classes").asStrings(); 162 XhtmlNode doc = new XhtmlNode(NodeType.Element, "div"); 163 XhtmlNode svg = doc.svg(); 164 165 minx = 0; 166 miny = 0; 167 168 Point size = determineMetrics(classNames); 169 adjustAllForMin(size); 170 svg.attribute("id", prefix+"n"+(++nc)); 171 svg.attribute("version", "1.1"); 172 svg.attribute("width", Integer.toString(Utilities.parseInt(control.forceObject("size").asString("width"), (int) size.x))); 173 svg.attribute("height", Integer.toString(Utilities.parseInt(control.forceObject("size").asString("height"), (int) size.y))); 174 175 shadowFilter(svg); 176 drawElement(svg, classNames); 177 countDuplicateLinks(); 178 XhtmlNode insertionPoint = svg.getChildNodes().get(0); 179 for (Link l : links) { 180 drawLink(svg, l, insertionPoint); 181 } 182 183 String s = new XhtmlComposer(true, true).compose(doc.getChildNodes()); 184 produceOutput("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+s); 185 return s; 186 } 187 188 public String buildClassDiagram(StructureDefinition sd, String defnFile) throws FHIRException, IOException { 189 this.defnFile = defnFile; 190 this.defnSD = sd; 191 192 File f = new File(Utilities.path(sourceFolder, diagramId+".svg")); 193 if (f.exists()) { 194 parseSvgFile(f, f.getAbsolutePath()); 195 } 196 197 attributes = true; 198 innerClasses = true; 199 classNames.add(sd.getName()); 200 XhtmlNode doc = new XhtmlNode(NodeType.Element, "div"); 201 XhtmlNode svg = doc.svg(); 202 203 minx = 0; 204 miny = 0; 205 206 Point size = determineClassMetrics(sd); 207 adjustAllForMin(size); 208 svg.attribute("id", prefix+"n"+(++nc)); 209 svg.attribute("version", "1.1"); 210 svg.attribute("width", Double.toString(size.x)); 211 svg.attribute("height", Double.toString(size.y)); 212 213 shadowFilter(svg); 214 drawClassElement(svg, sd); 215 countDuplicateLinks(); 216 XhtmlNode insertionPoint = svg.getChildNodes().get(0); 217 for (Link l : links) { 218 drawLink(svg, l, insertionPoint); 219 } 220 221 String s = new XhtmlComposer(true, true).compose(doc.getChildNodes()); 222 produceOutput("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+s); 223 return s; 224 } 225 226 private void produceOutput(String s) throws IOException { 227 if ("".equals(prefix)) { 228 String svg = standaloneSVG(s); 229 String html = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" 230 + "<!DOCTYPE HTML>\n" 231 + "<html xml:lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"en\">\n" 232 + " <head>\n" 233 + " <meta content=\"text/html;charset=utf-8\" http-equiv=\"Content-Type\"/>\n" 234 + " <title>"+diagramId+" Class Diagram</title>\n" 235 + " <link href=\"assets/fhir.css\" rel=\"stylesheet\"/>\n" 236 + " </head>\n" 237 + " <body>\n" 238 + " <h2>Embedded SVG</h2>\n" 239 + s+"\r\n" 240 + " <h2>External SVG</h2>\n" 241 + " <embed src=\""+diagramId+".svg\" type=\"image/svg+xml\">\n" 242 + " </body>\n" 243 + "</html>"; 244 245 FileUtilities.stringToFile(svg, Utilities.path(destFolder, diagramId+".svg")); 246 FileUtilities.stringToFile(html, Utilities.path(destFolder, diagramId+".html")); 247 } 248 } 249 250 private String standaloneSVG(String s) { 251 String css = "<?xml-stylesheet href=\"assets/fhir.css\" type=\"text/css\"?>"; 252 int i = s.indexOf(">")+1; 253 s = s.substring(0, i)+css+s.substring(i); 254 return s; 255 } 256 257 private void countDuplicateLinks() { 258 for (int i = 0; i < links.size(); i++) { 259 Link l = links.get(i); 260 if (l.count == 0) { 261 int c = 0; 262 for (int j = i+1; j < links.size(); j++) { 263 Link l2 = links.get(j); 264 if ((l2.source == l.source && l2.target == l.target) || 265 (l2.source == l.target && l2.target == l.source)) 266 c++; 267 } 268 l.count = c; 269 if (c > 0) { 270 int k = 0; 271 for (int j = i+1; j < links.size(); j++) { 272 Link l2 = links.get(j); 273 if ((l2.source == l.source && l2.target == l.target) || 274 (l2.source == l.target && l2.target == l.source) ) { 275 k++; 276 l2.count = c; 277 l2.index = k; 278 } 279 } 280 } 281 } 282 } 283 } 284 285 private void adjustAllForMin(Point size) { 286 size.x = size.x - minx; 287 size.y = size.y - miny; 288 for (ClassItem t : classes.values()) { 289 t.left = t.left - minx; 290 t.top = t.top - miny; 291 } 292 } 293 294 private void shadowFilter(XhtmlNode svg) throws IOException { 295 var defs = svg.addTag("defs"); 296 var filter = defs.addTag("filter").attribute("id", prefix+"shadow"+diagramId).attribute("x", "0").attribute("y", "0").attribute("width", "200%").attribute("height", "200%"); 297 var feOffset = filter.addTag("feOffset").attribute("result", "offOut").attribute("in", "SourceGraphic").attribute("dx", "3").attribute("dy", "3"); 298 var feColorMatrix = filter.addTag("feColorMatrix").attribute("result", "matrixOut").attribute("in", "offOut").attribute("type", "matrix").attribute("values", "0.2 0 0 0 0 0 0.2 0 0 0 0 0 0.2 0 0 0 0 0 1 0"); 299 var feGaussianBlur = filter.addTag("feGaussianBlur").attribute("result", "blurOut").attribute("in", "matrixOut").attribute("stdDeviation", "2"); 300 var feBlend = filter.addTag("feBlend").attribute("in", "SourceGraphic").attribute("in2", "blurOut").attribute("mode", "normal"); 301 } 302 303 private void parseSvgFile(File f, String name) throws FHIRException, IOException { 304 Document svg; 305 try { 306 svg = XMLUtil.parseFileToDom(f.getAbsolutePath()); 307 } catch (ParserConfigurationException | SAXException | IOException e) { 308 throw new IOException(e); 309 } 310 readElement(svg.getDocumentElement(), null); 311 fixLayout(); 312 } 313 314 private void fixLayout() { 315 double minx = Integer.MAX_VALUE; 316 double miny = Integer.MAX_VALUE; 317 for (PointSpec ps : layout.values()) { 318 if (ps.getX() < minx) { 319 minx = ps.getX(); 320 } 321 if (ps.getY() < miny) { 322 miny = ps.getY(); 323 } 324 } 325 for (String n : layout.keySet()) { 326 PointSpec ps = layout.get(n); 327 PointSpec nps = new PointSpec(ps.getX() - minx, ps.getY() - miny); 328 layout.put(n, nps); 329 } 330 } 331 332 private void readElement(Element e, Element p) { 333 String id = e.getAttribute("id"); 334 if (!Utilities.noString(id) && Character.isUpperCase(id.charAt(0))) { 335 switch (e.getNodeName()) { 336 case "rect": 337 case "text": 338 double x = Double.valueOf(e.getAttribute("x")); 339 double y = Double.valueOf(e.getAttribute("y")); 340 if (p.hasAttribute("transform")) { 341 String s = p.getAttribute("transform"); 342 if (s.startsWith("translate(")) { 343 String[] sp = s.substring(10, s.length()-1).split("\\,"); 344 double tx = Double.valueOf(sp[0]); 345 double ty = sp.length > 1 ? Double.valueOf(sp[1]) : 0; 346 x = x + tx; 347 y = y + ty; 348 } 349 } 350 layout.put(id, new PointSpec(x, y)); 351 break; 352 case "path": 353 String d = e.getAttribute("d"); 354 if (d != null) { // 'm ' - means that inkscape has edited it 355 linkLayout(id).pathData = d; 356 linkLayout(id).use = d.startsWith("m "); 357 } 358 break; 359 case "polygon": 360 String v = e.getAttribute("transform"); 361 if (v != null) { 362 linkLayout(id.replace("-lpolygon", "")).diamondTransform = v; 363 } 364 v = e.getAttribute("points"); 365 if (v != null) { 366 linkLayout(id.replace("-lpolygon", "")).diamondPoints = v; 367 } 368 break; 369 } 370 } 371 Element c = XMLUtil.getFirstChild(e); 372 while (c != null) { 373 readElement(c, e); 374 c = XMLUtil.getNextSibling(c); 375 } 376 } 377 378 379 private LinkInfo linkLayout(String id) { 380 if (!linkLayouts.containsKey(id)) { 381 LinkInfo res = new LinkInfo(); 382 linkLayouts.put(id, res); 383 } 384 return linkLayouts.get(id); 385 } 386 387 388 private static final double LINE_HEIGHT = 16; 389 private static final double HEADER_HEIGHT = 20; 390 private static final double GAP_HEIGHT = 4; 391 private static final double LEFT_MARGIN = 6; 392 private static final double SELF_LINK_HEIGHT = 25; 393 private static final double SELF_LINK_WIDTH = 60; 394 private static final double DUPLICATE_GAP = 50; 395 private static final double MARGIN_X = 100; 396 private static final double MARGIN_Y = 10; 397 private static final double WRAP_INDENT = 20; 398 private static final int LINE_MAX = 60; 399 public static final int MAX_NEG = -1000000; 400 private static final double UML_ROW_HEIGHT = 100; 401 402 private enum PointKind { 403 unknown, left, right, top, bottom; 404 } 405 private class Point { 406 private PointKind kind; 407 public Point(double x, double y, PointKind kind) { 408 this.x = x; 409 this.y = y; 410 this.kind = kind; 411 } 412 private double x; 413 private double y; 414 private String toPoint() { 415 return Double.toString(x)+","+Double.toString(y); 416 } 417 } 418 419 private class ClassItem { 420 public ClassItem(double left, double top, double width, double height, String id, String name, ClassItemMode mode) { 421 this.left = left; 422 this.top = top; 423 this.width = width; 424 this.height = height; 425 this.id = id; 426 this.name = name; 427 this.mode = mode; 428 if (layout != null && layout.containsKey(id)) { 429 this.left = layout.get(id).getX(); 430 this.top = layout.get(id).getY(); 431 } 432 } 433 private double left; 434 private double top; 435 private double width; 436 private double height; 437 private String id; 438 private String name; 439 private ClassItemMode mode; 440 public double right() { 441 return left + width; 442 } 443 public double centerH() { 444 return left + width / 2; 445 } 446 public double centerV() { 447 return top + height / 2; 448 } 449 public double bottom() { 450 return top + height; 451 } 452 public String getId() { 453 return id; 454 } 455 public String getName() { 456 return name; 457 } 458 public ClassItemMode getMode() { 459 return mode; 460 } 461 } 462 463 private class Segment { 464 465 public final Point start, end; 466 public final boolean isVertical; 467 public final double slope, intercept; 468 469 public Segment(Point start, Point end) { 470 this.start = start; 471 this.end = end; 472 //set isVertical, which indicates whether this Line 473 //is vertical or not on the coordinate plane 474 if (start.x == end.x) 475 isVertical = true; 476 else 477 isVertical = false; 478 479 //set slope and intercept 480 if (!isVertical){ 481 slope = (this.start.y - this.end.y) / (this.start.x - this.end.x); 482 intercept = (this.end.x * this.start.y - this.start.x * this.end.y ) /(this.start.x - this.end.x); 483 } 484 else { 485 slope = Double.MAX_VALUE; 486 intercept = - Double.MAX_VALUE; 487 } 488 } 489 } 490 private class LineStatus { 491 int line = 0; 492 int length = 0; 493 String current = ""; 494 List<String> list = new ArrayList<String>(); 495 496 public String see(String s) { 497 length = length + s.length(); 498 current = current + s; 499 return s; 500 } 501 502 public void close() { 503 line++; 504 list.add(current); 505 length = 0; 506 current = ""; 507 } 508 509 public void check(XhtmlNode html, XhtmlNode div, double left, double top, int l, String link) throws IOException { 510 if (length + l > LINE_MAX-2) { // always leave space for one or two 511 close(); 512 html.attribute("height", Double.toString(top + LINE_HEIGHT * line)); 513 div.br(); 514 see(" "); 515 div.nbsp(); 516 div.nbsp(); 517 div.nbsp(); 518 div.nbsp(); 519 div.nbsp(); 520 div.nbsp(); 521 } 522 } 523 } 524 private enum LinkType {SPECIALIZATION, CONSTRAINT, COMPOSITION, SLICE}; 525 private class Link { 526 private String path; 527 private String description; 528 public Link(ClassItem source, ClassItem target, LinkType type, String name, String cardinality, PointKind kind, String path, String description) { 529 this.source = source; 530 this.target = target; 531 this.type = type; 532 this.name = name; 533 this.cardinality = cardinality; 534 this.kind = kind; 535 this.path = path; 536 this.description = description; 537 } 538 private LinkType type; 539 private ClassItem source; 540 private ClassItem target; 541 private String name; 542 private String cardinality; 543 private PointKind kind; 544 private int count; 545 private int index; 546 } 547 548 549 private Point determineMetrics(List<String> classNames) throws Exception { 550 double width = textWidth("Element") * 1.8; 551 double height = HEADER_HEIGHT + GAP_HEIGHT*2; 552 // if ("true".equals(ini.getStringProperty("diagram", "element-attributes"))) { 553 // height = height + LINE_HEIGHT + GAP_HEIGHT; 554 // width = textWidth("extension : Extension 0..*"); 555 // } 556 557 Point p = new Point(0, 0, PointKind.unknown); 558 ClassItem item = new ClassItem(p.x, p.y, width, height, diagramId, null, ClassItemMode.NORMAL); 559 classes.put(null, item); 560 double x = item.right()+MARGIN_X; 561 double y = item.bottom()+MARGIN_Y; 562 if (classNames != null) { 563 for (String cn : classNames) { 564 StructureDefinition sd = context.fetchResource(StructureDefinition.class, cn); 565 if (sd == null) { 566 sd = cutils.fetchStructureByName(cn); 567 } 568 if (sd == null) { 569 throw new FHIRException("Unable to find class '"+cn+"'"); 570 } 571 ElementDefinition ed = sd.getSnapshot().getElementFirstRep(); 572 StructureDefinition base = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); 573 p = determineMetrics(sd, ed, item, ed.getName(), null, base, ClassItemMode.NORMAL); 574 x = Math.max(x, p.x+MARGIN_X); 575 y = Math.max(y, p.y+MARGIN_Y); 576 } 577 } 578 return new Point(x, y, PointKind.unknown); 579 } 580 581 private Point determineClassMetrics(StructureDefinition sd) throws FHIRException, IOException { 582 double width = textWidth("Element") * 1.8; 583 double height = HEADER_HEIGHT + GAP_HEIGHT*2; 584 // if ("true".equals(ini.getStringProperty("diagram", "element-attributes"))) { 585 // height = height + LINE_HEIGHT + GAP_HEIGHT; 586 // width = textWidth("extension : Extension 0..*"); 587 // } 588 589 Point p = new Point(0, 0, PointKind.unknown); 590 ClassItem item = new ClassItem(p.x, p.y, width, height, diagramId, null, ClassItemMode.NORMAL); 591 classes.put(null, item); 592 double x = item.right()+MARGIN_X; 593 double y = item.bottom()+MARGIN_Y; 594 ElementDefinition ed = sd.getSnapshot().getElementFirstRep(); 595 StructureDefinition base = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); 596 p = determineMetrics(sd, ed, item, ed.getName(), null, base, ClassItemMode.NORMAL); 597 x = Math.max(x, p.x+MARGIN_X); 598 y = Math.max(y, p.y+MARGIN_Y); 599 return new Point(x, y, PointKind.unknown); 600 } 601 602 603 private Point determineMetrics(StructureDefinition sd, ElementDefinition ed, ClassItem source, String path, String name, StructureDefinition base, ClassItemMode mode) throws FHIRException, IOException { 604 605 List<ElementDefinition> children = putils.getChildList(sd, ed); 606 String n = name; 607 if (n == null) { 608 if (!path.contains(".")) { 609 n = sd.getName(); 610 } else if (!constraintMode && !children.isEmpty()) { 611 String[] p = path.split("\\."); 612 StringBuilder b = new StringBuilder(); 613 for (String s : p) { 614 b.append(Utilities.capitalize(s)); 615 } 616 n = b.toString(); 617 } else if (constraintMode && ed.getType().size() == 1) { 618 n = ed.getTypeFirstRep().getWorkingCode(); 619 } else { 620 n = "DataType"; 621 } 622 } 623 624 String t = n; 625 if (path.contains(".")) { 626 if (ed.getType().size() == 1) { 627 if (!"Base".equals(ed.getTypeFirstRep().getWorkingCode())) { 628 t = t + " ("+ed.getTypeFirstRep().getWorkingCode()+")"; 629 } 630 } else { 631 t = ""; 632 } 633 } else if (base != null && !classNames.contains(base.getName())) { 634 t = t + " ("+base.getName()+")"; 635 } 636 if (sd.hasExtension(ExtensionDefinitions.EXT_RESOURCE_INTERFACE)) { 637 t = t + " ĞInterfaceğ"; 638 } 639 double width = textWidth(t) * 1.8; 640 //double width = textWidth(e.getName()) * 1.8 + (isRoot ? textWidth(" (Resource)") : 0); 641 double height; 642 if (attributes) { 643 int i = 0; 644 for (ElementDefinition c : children) 645 if (inScope(c) && countsAsAttribute(sd, c)) { 646 String[] texts = textForAttribute(sd, c); 647 i = i + texts.length; 648 double w = textWidth(texts[0]); 649 for (int j = 1; j < texts.length; j++) 650 w = Math.max(w, textWidth(texts[j])); 651 if (w > width) 652 width = w; 653 } 654 height = HEADER_HEIGHT + GAP_HEIGHT*2 + LINE_HEIGHT * i + GAP_HEIGHT * 2; 655 } else 656 height = HEADER_HEIGHT + GAP_HEIGHT*2; 657 658// if (control.forceObject("directions").has(path)) { 659// JsonElement uml = control.forceObject("directions").get(path); 660// if (uml.isJsonObject()) { 661// JsonObject svg = (JsonObject) uml; 662// ed.setUserData(UserDataNames.SvgLeft, svg.asInteger("left")); 663// ed.setUserData(UserDataNames.SvgTop, svg.asInteger("top")); 664// } else { 665// ed.setUserData(UserDataNames.UmlDir, uml.asString()); 666// } 667// } 668 669 Point p = new Point(getSvgLeft(ed), getSvgLeft(ed), PointKind.unknown); 670 if (p.y == MAX_NEG || p.x == MAX_NEG) { 671 if ("left".equals(ed.getUserString(UserDataNames.UmlDir))) { 672 p.x = source.left - 120 - width; 673 p.y = source.centerV() - height / 2; 674 p = findEmptyPlace(p, width, height, 0, 80); 675 } else if ("right".equals(ed.getUserString(UserDataNames.UmlDir))) { 676 p.x = source.right() + 120; 677 p.y = source.centerV() - height / 2; 678 p = findEmptyPlace(p, width, height, 0, 80); 679 } else if ("up".equals(ed.getUserString(UserDataNames.UmlDir))) { 680 p.x = source.centerH() - width / 2; 681 p.y = source.top - height - 80; 682 p = findEmptyPlace(p, width, height, 80, 0); 683 } else if ("down".equals(ed.getUserString(UserDataNames.UmlDir))) { 684 p.x = source.centerH() - width / 2; 685 p.y = source.bottom() + 80; 686 p = findEmptyPlace(p, width, height, +80, 0); 687 } else { 688 p.y = 0; 689 p.x = 0; 690 p = findEmptyPlace(p, width, height, 80, 0); 691 } 692 } 693 miny = Math.min(miny, p.y); 694 minx = Math.min(minx, p.x); 695 ClassItem item = new ClassItem(p.x, p.y, width, height, path, n, mode); 696 classes.put(path, item); 697 double x = item.right()+MARGIN_X; 698 double y = item.bottom()+MARGIN_Y; 699 700 if (innerClasses) { 701 for (ElementDefinition c : children) { 702 if (inScope(c) && countsAsRelationship(sd, c) && !c.hasSliceName()) { 703 if (c.hasContentReference()) { 704 String cr = c.getContentReference(); 705 ClassItem target = classes.get(cr.substring(cr.indexOf("#")+1)); 706 if (target == null) { 707 throw new Error("what?"); 708 } 709 // do something about x and y? 710 } else { 711 p = determineMetrics(sd, c, item, path+"."+c.getName(), null, null, ClassItemMode.NORMAL); 712 x = Math.max(x, p.x+MARGIN_X); 713 y = Math.max(y, p.y+MARGIN_Y); 714 // do we find slices? 715 if (c.hasSlicing()) { 716 List<ElementDefinition> slices = getSlices(children, c); 717 for (ElementDefinition s : slices) { 718 p = determineMetrics(sd, s, item, path+"."+c.getName()+":"+s.getSliceName(), s.getSliceName(), null, ClassItemMode.SLICE); 719 x = Math.max(x, p.x+MARGIN_X); 720 y = Math.max(y, p.y+MARGIN_Y); 721 } 722 } 723 } 724 } 725 } 726 } 727 return new Point(x, y, PointKind.unknown); 728 } 729 730 private boolean countsAsRelationship(StructureDefinition sd, ElementDefinition c) { 731 return !countsAsAttribute(sd, c); 732 } 733 734 private boolean countsAsAttribute(StructureDefinition sd, ElementDefinition ed) { 735 if (ed.hasContentReference()) { 736 return false; 737 } else if (ed.prohibited()) { 738 return true; 739 } else if (ed.getType().isEmpty()) { 740 return true; // really shouldn't be the case 741 } else { 742 List<ElementDefinition> children = putils.getChildList(sd, ed, false); 743 if (ed.getType().size() == 1) { 744 StructureDefinition sdt = context.fetchTypeDefinition(ed.getTypeFirstRep().getWorkingCode()); 745 if (sdt == null) { 746 return true; // also shouldn't happen 747 } else if (sdt.getKind() == StructureDefinitionKind.PRIMITIVETYPE) { 748 return true; 749 } else if (sdt.getAbstract()) { 750 return children.size() == 0; 751 } else if (sdt.getKind() == StructureDefinitionKind.COMPLEXTYPE && !"Base".equals(sdt.getName())) { 752 return !(constraintMode && (ed.hasSlicing() || ed.hasSliceName())); 753 } else { 754 return children.size() == 0; 755 } 756 } else { 757 return children.size() == 0 || (constraintMode && ed.hasSlicing()); 758 } 759 } 760 } 761 762 private List<ElementDefinition> getSlices(List<ElementDefinition> list, ElementDefinition focus) { 763 List<ElementDefinition> slices = new ArrayList<ElementDefinition>(); 764 for (int i = list.indexOf(focus)+1; i < list.size(); i++) { 765 ElementDefinition ed = list.get(i); 766 if (ed.getPath().equals(focus.getPath()) && ed.hasSliceName()) { 767 slices.add(ed); 768 } 769 } 770 return slices; 771 } 772 773 private boolean inScope(ElementDefinition c) { 774 return !constraintMode || c.hasUserData(UserDataNames.SNAPSHOT_FROM_DIFF); 775 } 776 777 private Point findEmptyPlace(Point p, double width, double height, double dx, double dy) { 778 while (overlaps(p.x, p.y, width, height)) { 779 p.x = p.x + dx; 780 p.y = p.y + dy; 781 if (p.x > 600) { 782 p.y = p.y + UML_ROW_HEIGHT; 783 p.x = 0; 784 } 785 } 786 return p; 787 } 788 789 private boolean overlaps(double x, double y, double w, double h) { 790 for (ClassItem c : classes.values()) { 791 if ((inBounds(x, c.left, c.right()) || inBounds(x+w, c.left, c.right())) && 792 (inBounds(y, c.top, c.bottom()) || inBounds(y+h, c.top, c.bottom()))) 793 return true; 794 if ((inBounds(c.left, x, x+w) || inBounds(c.right(), x, x+w)) && 795 (inBounds(c.top, y, y+h) || inBounds(c.bottom(), y, y+h))) 796 return true; 797 } 798 return false; 799 } 800 801 private boolean inBounds(double x, double x1, double x2) { 802 return (x1 < x2) ? (x >= x1 && x <= x2) : (x >= x2 && x <= x1); 803 } 804 805 private double getSvgLeft(ElementDefinition ed) { 806 Integer i = (Integer) ed.getUserData(UserDataNames.SvgLeft); 807 return i == null ? MAX_NEG : i; 808 } 809 810 private double getSvgTop(ElementDefinition ed) { 811 Integer i = (Integer) ed.getUserData(UserDataNames.SvgTop); 812 return i == null ? MAX_NEG : i; 813 } 814 815 private int addAttribute(XhtmlNode g, double left, double top, StructureDefinition sd, ElementDefinition e, String path, double height, double width) throws FHIRException, IOException { 816 LineStatus ls = new LineStatus(); 817 return addAttribute(g, left, top, sd, e, path, ls, height, width); 818 } 819 820 private int addAttribute(XhtmlNode g, double left, double top, StructureDefinition sd, ElementDefinition e, String path, LineStatus ls, double height, double width) throws FHIRException, IOException { 821 if (e.getStandardsStatus() != null) { 822 var rect = g.svgRect(null); 823 rect.attribute("x", Double.toString(left+1)); 824 rect.attribute("y", Double.toString(top-height+GAP_HEIGHT)); 825 rect.attribute("id", prefix+"n"+(++nc)); 826 rect.attribute("width", Double.toString(width-2)); 827 rect.attribute("height", Double.toString(height)); 828 rect.style("fill:"+e.getStandardsStatus().getColorSvg()+";stroke:black;stroke-width:0"); 829 } 830 var html = g.htmlObject(left + LEFT_MARGIN + (ls.line == 0 ? 0 : WRAP_INDENT), top + LINE_HEIGHT * (ls.line-1), width-2, height); 831 var div = html.div().attribute("xmlns", "http://www.w3.org/1999/xhtml"); 832 div.attribute("class", "diagram-class-detail"); 833 if (e.prohibited()) { 834 div.style("text-decoration: line-through"); 835 } 836 837 var a = div.ah(urlForAttribute(sd, e, path)).style("text-decoration: none;"); 838 a.attributeNN("title", getEnhancedDefinition(e)); 839 a.tx(ls.see(e.getName())); 840 841 div.tx(ls.see(" : ")); 842 if (e.hasContentReference()) { 843 encodeType(div, ls, e.getContentReference().substring(e.getContentReference().indexOf("#")+1)); 844 } else { 845 encodeType(div, ls, getTypeCodeForElement(e.getType())); 846 } 847 div.tx(ls.see(" ["+describeCardinality(e)+"]")); 848 849 if (!"0".equals(e.getMax())) { 850 if (constraintMode) { 851 // in constraint mode, we're (usually) even more constrained for space, and we have a lot more kinds of constraints 852 // to deal with 853 Set<String> p = new HashSet<>(); 854 Set<String> tp = new HashSet<>(); 855 for (TypeRefComponent tr : e.getType()) { 856 for (CanonicalType ct : tr.getProfile()) { 857 p.add(ct.asStringValue()); 858 } 859 for (CanonicalType ct : tr.getTargetProfile()) { 860 if (!ct.asStringValue().startsWith("http://hl7.org/fhir/StructureDefinition/")) { 861 tp.add(ct.asStringValue()); 862 } 863 } 864 } 865 flag(div, ls, !p.isEmpty(), "DP", "black", "#c787ff", rc.formatPhrase(RenderingContext.GENERAL_TYPE_PROFILE, CommaSeparatedStringBuilder.join(",", Utilities.sorted(p))), null); 866 flag(div, ls, !tp.isEmpty(), "TP", "black", "#c787ff", rc.formatPhrase(RenderingContext.GENERAL_TYPE_TARGET_PROFILE, CommaSeparatedStringBuilder.join(",", Utilities.sorted(tp))), null); 867 868 flag(div, ls, e.getIsModifier(), "?!", "black", "white", rc.formatPhrase(RenderingContext.STRUC_DEF_MOD), null); 869// flag(div, ls, e.getIsSummary(), "\u03A3", "white", "black", rc.formatPhrase(RenderingContext.STRUC_DEF_ELE_INCLUDED), null); 870 871 if (e.getMustSupport() && e.hasExtension(ExtensionDefinitions.EXT_OBLIGATION_CORE, ExtensionDefinitions.EXT_OBLIGATION_TOOLS)) { 872 flag(div, ls, e.getMustSupport(), "SO", "white", "red", rc.formatPhrase(RenderingContext.STRUC_DEF_OBLIG_SUPP), null); 873 } else if (e.hasExtension(ExtensionDefinitions.EXT_OBLIGATION_CORE, ExtensionDefinitions.EXT_OBLIGATION_TOOLS)) { 874 flag(div, ls, e.getMustSupport(), "O", "white", "red", rc.formatPhrase(RenderingContext.STRUC_DEF_OBLIG), null); 875 } else { 876 flag(div, ls, e.getMustSupport(), "S", "white", "red", rc.formatPhrase(RenderingContext.STRUC_DEF_ELE_MUST_SUPP), null); 877 } 878 flag(div, ls, e.getMustHaveValue(), "V", "black", "#f7a3ec", rc.formatPhrase(RenderingContext.STRUC_DEF_ELE), null); 879 flag(div, ls, e.hasValueAlternatives(), "?X", "black", "#f7a3ec", rc.formatPhrase(RenderingContext.STRUC_DEF_VALUE_ALT), null); 880 flag(div, ls, StructureDefinitionRenderer.hasNonBaseConstraints(e.getConstraint()) || StructureDefinitionRenderer.hasNonBaseConditions(e.getCondition()), "C", "black", "#7779e6", rc.formatPhrase(RenderingContext.STRUC_DEF_ELE_AFFECTED), null); 881 flag(div, ls, e.hasFixed(), "F", "black", "#95fc9c", rc.formatPhrase(RenderingContext.GENERAL_FIXED_VALUE, renderDT(e.getFixed())), null); 882 flag(div, ls, e.hasPattern(), "P", "black","#95fc9c", rc.formatPhrase(RenderingContext.GENERAL_PATTERN_VALUE, renderDT(e.getPattern())), null); 883 if (e.hasMinValue() || e.hasMaxValue()) { 884 if (e.hasMinValue() && e.hasMaxValue()) { 885 flag(div, ls, true, "L<<H", "black", "green", rc.formatPhrase(RenderingContext.GENERAL_VALUE_BOUNDED, e.getMaxLength()), null); 886 } else { 887 flag(div, ls, e.hasMaxValue(), "L<", "black", "#95fc9c", rc.formatPhrase(RenderingContext.GENERAL_VALUE_MIN, renderDT(e.getMinValue())), null); 888 flag(div, ls, e.hasMaxValue(), "<H", "black", "#95fc9c", rc.formatPhrase(RenderingContext.GENERAL_VALUE_MAX, renderDT(e.getMaxValue())), null); 889 } 890 } 891 flag(div, ls, e.hasMaxLength(), "<L", "black", "#95fc9c", rc.formatPhrase(RenderingContext.GENERAL_MAX_LENGTH, e.getMaxLength()), null); 892 if (e.hasBinding()) { 893 ValueSet vs = context.fetchResource(ValueSet.class, path); 894 if (e.getBinding().getStrength() == BindingStrength.REQUIRED) { 895 flag(div, ls, true, "B!", "black", "#fad570", rc.formatPhrase(RenderingContext.GENERAL_REQUIRED_BINDING, describeVS(e.getBinding().getValueSet(), vs)), vsLink(e.getBinding().getValueSet(), vs)); 896 } else if (e.getBinding().getStrength() == BindingStrength.EXTENSIBLE) { 897 flag(div, ls, true, "B?", "black", "#fad570", rc.formatPhrase(RenderingContext.GENERAL_REQUIRED_BINDING, describeVS(e.getBinding().getValueSet(), vs)), vsLink(e.getBinding().getValueSet(), vs)); 898 } 899 flag(div, ls, e.hasExtension(ExtensionDefinitions.EXT_BINDING_ADDITIONAL), "B+", "black", "#fad570", rc.formatPhrase(RenderingContext.GENERAL_ADDITIONAL_BINDING), null); 900 } 901 } else { 902 903 boolean hasTS = !((e.getType().isEmpty()) || (e.getType().size() == 1 && !isReference(e.getType().get(0).getName()))); 904 boolean hasBinding = e.hasBinding() && e.getBinding().getStrength() != BindingStrength.NULL; 905 if (hasTS || hasBinding) { 906 div.tx(ls.see(" \u00AB ")); 907 if (hasTS) { 908 if (isReference(e.getTypeFirstRep().getWorkingCode()) && e.getType().size() == 1) { 909 boolean first = true; 910 for (CanonicalType p : e.getTypeFirstRep().getTargetProfile()) { 911 if (first) 912 first = false; 913 else 914 div.tx(ls.see(" | ")); 915 StructureDefinition sdt = context.fetchResource(StructureDefinition.class, p.asStringValue(), null, sd); 916 String s = sdt == null ? tail(p.asStringValue()) : sdt.getName(); 917 ls.check(html, div, left, top, s.length(), null); 918 encodeType(div, ls, s); 919 } 920 } else { 921 boolean firstOuter = true; 922 for (TypeRefComponent t : e.getType()) { 923 if (firstOuter) 924 firstOuter = false; 925 else 926 div.tx(ls.see(" | ")); 927 928 ls.check(html, div, left, top, t.getName().length(), null); 929 encodeType(div, ls, t.getWorkingCode()); 930 } 931 } 932 } 933 if (hasTS && hasBinding) { 934 div.tx(ls.see("; ")); 935 } 936 if (hasBinding) { 937 ElementDefinitionBindingComponent b = e.getBinding(); 938 ValueSet vs = context.fetchResource(ValueSet.class, b.getValueSet()); 939 String name = vs != null ? vs.getName() : tail(b.getValueSet()); 940 if (name.toLowerCase().endsWith(" codes")) 941 name = name.substring(0, name.length()-5); 942 if (name.length() > 30) 943 name = name.substring(0, 29)+"..."; 944 String link = vs == null ? null : vs.getWebPath(); 945 String suffix = ""; 946 suffix = getBindingSuffix(b); 947 div.ahOrNot(link, b.getDescription()+" (Strength="+(b.getStrength() == null ? "null " : b.getStrength().getDisplay())+")").tx(ls.see(name+suffix)); 948 } 949 div.tx(ls.see(" \u00BB")); 950 951 } 952 } 953 } 954 return ls.line; 955 } 956 957 private String urlForAttribute(StructureDefinition sd, ElementDefinition ed, String path) { 958 String fullPath = path+"."+ed.getName(); //.replace("[", "_").replace("]", "_"); 959 if (!ed.getPath().equals(ed.getBase().getPath()) || (ed.getBase().hasPath() && sd.getDerivation() == StructureDefinition.TypeDerivationRule.CONSTRAINT)) { 960 StructureDefinition sdt = context.fetchTypeDefinition(head(ed.getBase().getPath())); 961 if (sdt != null && !ExtensionUtilities.hasExtension(sdt, ExtensionDefinitions.EXT_RESOURCE_INTERFACE)) { 962 sd = sdt; 963 fullPath = ed.getBase().getPath(); 964 } 965 } 966 return (sd == defnSD && defnFile != null ? defnFile : sd.getWebPath())+"#"+fullPath; 967 } 968 969 private String getBindingSuffix(ElementDefinitionBindingComponent b) { 970 String suffix; 971 if (b.getStrength() == null) { 972 return "??"; 973 } 974 switch (b.getStrength()) { 975 case EXAMPLE: 976 suffix = "??"; 977 break; 978 case EXTENSIBLE: 979 suffix = "+"; 980 break; 981 case PREFERRED: 982 suffix = "?"; 983 break; 984 case REQUIRED: 985 suffix = "!"; 986 break; 987 case NULL: 988 default: 989 suffix = "??"; 990 break; 991 } 992 return suffix; 993 } 994 995 private boolean hasNonBaseProfile(List<CanonicalType> list) { 996 for (CanonicalType ct : list) { 997 if (!ct.asStringValue().startsWith("http://hl7.org/fhir/StructureDefinition/")) { 998 return true; 999 } 1000 } 1001 return false; 1002 } 1003 1004 private String vsLink(String url, ValueSet vs) { 1005 if (vs != null) { 1006 return vs.getWebPath(); 1007 } else { 1008 return url; 1009 } 1010 } 1011 1012 private String describeVS(String url, ValueSet vs) { 1013 if (vs != null) { 1014 return vs.present()+" ("+url+")"; 1015 } else { 1016 return "("+url+")"; 1017 } 1018 } 1019 1020 private String renderDT(DataType dt) { 1021 if (dt == null) { 1022 return ""; 1023 } else if (dt.isPrimitive()) { 1024 return dt.primitiveValue(); 1025 } else { 1026 switch (dt.fhirType()) { 1027 case "Quantity" : return renderQty((Quantity) dt); 1028 case "Coding" : return renderCoding((Coding) dt); 1029 case "CodeableConcept" : return renderCC((CodeableConcept) dt); 1030 default: 1031 return new DataRenderer(rc).displayDataType(dt); 1032 } 1033 } 1034 } 1035 1036 private String renderCC(CodeableConcept cc) { 1037 StringBuilder b = new StringBuilder(); 1038 boolean first = true; 1039 for (Coding c : cc.getCoding()) { 1040 if (first) first = false; else b.append(", "); 1041 b.append(renderCoding(c)); 1042 } 1043 if (b.length() > 0 && cc.hasText()) { 1044 b.append(". "); 1045 } 1046 if (cc.hasText()) { 1047 b.append("text: "); 1048 b.append(cc.getText()); 1049 } 1050 return b.toString(); 1051 } 1052 1053 private String renderCoding(Coding c) { 1054 StringBuilder b = new StringBuilder(); 1055 if (c.hasSystem()) { 1056 b.append(new DataRenderer(rc).displaySystem(c.getSystem())); 1057 if (c.hasCode()) { 1058 b.append("#"); 1059 } 1060 } 1061 if (c.hasCode()) { 1062 b.append(c.getCode()); 1063 } 1064 if (c.hasDisplay()) { 1065 b.append("\""); 1066 b.append(c.getDisplay()); 1067 b.append("\""); 1068 } 1069 return b.toString(); 1070 } 1071 1072 private String renderQty(Quantity qty) { 1073 StringBuilder b = new StringBuilder(); 1074 if (qty.hasComparator()) { 1075 b.append(qty.getComparatorElement().asStringValue()); 1076 } 1077 b.append(qty.getValueElement().asStringValue()); 1078 if (qty.hasUnit()) { 1079 b.append(qty.getUnit()); 1080 } else if (qty.hasCode()) { 1081 b.append(qty.getCode()); 1082 } 1083 return b.toString(); 1084 } 1085 1086 private void flag(XhtmlNode x, LineStatus ls, Boolean show, String code, String foreColor, String backColor, String title, String url) throws IOException { 1087 if (show) { 1088 // <a style="padding-left: 3px; padding-right: 3px; border: 1px maroon solid; font-weight: bold; color: #301212; background-color: #fdf4f4;" href="conformance-rules.html#constraints" title="This element has or is affected by some invariants">C</a> 1089 x.tx(" "); 1090 var xx = url == null ? x.span() : x.ah(url).style("text-decoration: none;"); 1091 xx.style("padding-left: 3px; padding-right: 3px; font-weight: bold; font-family: verdana; color: "+foreColor+"; background-color: "+backColor); 1092 xx.attribute("title", title); 1093 xx.tx(code); 1094 ls.see(code+" "); 1095 } 1096 } 1097 1098 private String tail(String url) { 1099 if (Utilities.noString(url)) { 1100 return ""; 1101 } 1102 return url.contains("/") ? url.substring(url.lastIndexOf("/")+1) : url; 1103 } 1104 1105 private String describeCardinality(ElementDefinition e) { 1106 String min = !e.hasMin() ? "" : e.getMinElement().asStringValue(); 1107 String max = !e.hasMax() ? "" : e.getMax(); 1108 return min + ".." + max; 1109 } 1110 1111 private int encodeType(XhtmlNode text, LineStatus ls, String tc) throws FHIRException, IOException { 1112 if (tc == null) { 1113 return 0; 1114 } else if (tc.equals("*")) { 1115 var a = text.ah(Utilities.pathURL(rc.getLink(KnownLinkType.SPEC, true), "datatypes.html#open")).style("text-decoration: none;");; 1116 a.tx(ls.see(tc)); 1117 return tc.length(); 1118 } else if (tc.equals("Type")) { 1119 var a = text.ah(Utilities.pathURL(rc.getLink(KnownLinkType.SPEC, true), "formats.html#umlchoice")).style("text-decoration: none;");; 1120 a.tx(ls.see(tc)); 1121 return tc.length(); 1122 } else if (tc.startsWith("@")) { 1123 var a = text.ah("@"+tc.substring(1)).style("text-decoration: none;");; 1124 a.tx(ls.see(tc)); 1125 return tc.length(); 1126 } else { 1127 StructureDefinition sd = Utilities.isAbsoluteUrl(tc) ? context.fetchTypeDefinition(tc) : null; 1128 if (sd != null) { 1129 var a = text.ah(sd.getWebPath()).style("text-decoration: none;");; 1130 a.tx(ls.see(sd.getName())); 1131 return tc.length(); 1132 } 1133 var a = text.ah(makeLink(tc)).style("text-decoration: none;");; 1134 a.tx(ls.see(tc)); 1135 return tc.length(); 1136 } 1137 } 1138 1139 1140 private String makeLink(String tc) { 1141 StructureDefinition sd = context.fetchTypeDefinition(tc); 1142 return sd == null ? null : sd.getWebPath(); 1143 } 1144 1145 private String getEnhancedDefinition(ElementDefinition e) { 1146 String defn = pvt(e.getDefinitionElement()); 1147 if (e.getIsModifier() && e.getMustSupport()) { 1148 return Utilities.removePeriod(defn) + " "+rc.formatPhrase(RenderingI18nContext.SDR_MOD_SUPP); 1149 } else if (e.getIsModifier()) { 1150 return Utilities.removePeriod(defn) + " "+rc.formatPhrase(RenderingI18nContext.SDR_MOD); 1151 } else if (e.getMustSupport()) { 1152 return Utilities.removePeriod(defn) + " "+rc.formatPhrase(RenderingI18nContext.SDR_SUPP); 1153 } else { 1154 return Utilities.removePeriod(defn); 1155 } 1156 } 1157 1158 private String pvt(DataType ed) { 1159 return ed.getTranslation(lang); 1160 } 1161 1162 private String baseUrl(StructureDefinition sd, ElementDefinition ed, String path) throws FHIRException, IOException { 1163 if (!ed.getPath().equals(ed.getBase().getPath())) { 1164 StructureDefinition sdt = context.fetchTypeDefinition(head(ed.getBase().getPath())); 1165 if (sdt != null) { 1166 sd = sdt; 1167 } 1168 } 1169 return sd.getWebPath()+"#"; 1170 } 1171 1172 private String head(String path) { 1173 return path.contains(".") ? path.substring(0, path.indexOf(".")) : path; 1174 } 1175 1176 private String[] textForAttribute(StructureDefinition sd, ElementDefinition e) throws FHIRException, IOException { 1177 LineStatus ls = new LineStatus(); 1178 XhtmlNode svg = new XhtmlNode(NodeType.Element, "svg"); // this is a dummy 1179 addAttribute(svg.svgG(null), 0, 0, sd, e, "Element.id", ls, 0, 0); 1180 ls.close(); 1181 return ls.list.toArray(new String[] {}); 1182 } 1183 1184 1185 private String getTypeCodeForElement(List<TypeRefComponent> tl) { 1186 if (tl.isEmpty()) 1187 return "??"; 1188 if (tl.size() == 1 && !isReference(tl.get(0).getName())) 1189 return tl.get(0).getName(); 1190 String t = tl.get(0).getName(); 1191 boolean allSame = true; 1192 for (int i = 1; i < tl.size(); i++) { 1193 allSame = t.equals(tl.get(i).getName()); 1194 } 1195 if (allSame && t.equals("Reference")) { 1196 return "Reference"; 1197 } else if (allSame && t.equals("canonical")) { 1198 return "canonical"; 1199 } else if (allSame && t.equals("CodeableReference")) { 1200 return "CodeableReference"; 1201 } else { 1202 boolean allPrimitive = true; 1203 for (TypeRefComponent tr : tl) { 1204 StructureDefinition sd = context.fetchTypeDefinition(tr.getWorkingCode()); 1205 if (sd == null || sd.getKind() != StructureDefinitionKind.PRIMITIVETYPE) { 1206 allPrimitive = false; 1207 } 1208 } 1209 if (VersionUtilities.isR4BVer(context.getVersion())) { 1210 return "Element"; 1211 } if (allPrimitive) { 1212 return "PrimitiveType"; 1213 } else { 1214 return "DataType"; 1215 } 1216 } 1217 } 1218 1219 private boolean isReference(String name) { 1220 return name != null && (name.equals("Reference") || name.equals("canonical") || name.equals("CodeableReference")); 1221 } 1222 1223 1224 1225 private boolean isAttribute(StructureDefinition sd, ElementDefinition c) { 1226 return putils.getChildList(sd, c).isEmpty(); 1227 } 1228 1229 private double textWidth(String text) { 1230 return text.length() * CHAR_RATIO; 1231 } 1232 1233 1234 private ClassItem drawElement(XhtmlNode svg, List<String> classNames) throws Exception { 1235 if (classNames != null) { 1236 for (String cn : classNames) { 1237 StructureDefinition sd = context.fetchResource(StructureDefinition.class, cn); 1238 if (sd == null) { 1239 sd = cutils.fetchStructureByName(cn); 1240 } 1241 ElementDefinition ed = sd.getSnapshot().getElementFirstRep(); 1242 1243 StructureDefinition base = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); 1244 ClassItem parent = base == null ? null : classes.get(base.getName()); 1245 if (parent == null) { 1246 drawClass(svg, sd, ed, cn, sd.getStandardsStatus(), base); 1247 } else { 1248 links.add(new Link(parent, drawClass(svg, sd, ed, cn, sd.getStandardsStatus(), null), LinkType.SPECIALIZATION, null, null, PointKind.unknown, null, null)); 1249 } 1250 1251 } 1252 } 1253 return null; 1254 } 1255 1256 private ClassItem drawClassElement(XhtmlNode svg, StructureDefinition sd) throws FHIRException, IOException { 1257 ElementDefinition ed = sd.getSnapshot().getElementFirstRep(); 1258 1259 StructureDefinition base = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); 1260 ClassItem parent = base == null ? null : classes.get(base.getName()); 1261 if (parent == null) { 1262 drawClass(svg, sd, ed, ed.getName(), sd.getStandardsStatus(), base); 1263 } else { 1264 links.add(new Link(parent, drawClass(svg, sd, ed, sd.getName(), sd.getStandardsStatus(), null), LinkType.SPECIALIZATION, null, null, PointKind.unknown, null, null)); 1265 } 1266 return null; 1267 } 1268 1269 private ClassItem drawClass(XhtmlNode svg, StructureDefinition sd, ElementDefinition ed, String path, StandardsStatus status, StructureDefinition parent) throws FHIRException, IOException { 1270 ClassItem item = classes.get(path); 1271 if (item == null) { 1272 throw new FHIRException("Unable to find a class for "+path+" from "+CommaSeparatedStringBuilder.join(",", classes.keySet())); 1273 } 1274 1275 var g = svg.svgG(null); 1276 g.attribute("id", prefix+"n"+(++nc)); 1277 var rect = g.svgRect(null); 1278 rect.attribute("x", Double.toString(item.left)); 1279 rect.attribute("y", Double.toString(item.top)); 1280 rect.attribute("rx", "4"); 1281 rect.attribute("ry", "4"); 1282 rect.attribute("width", Double.toString(item.width)); 1283 rect.attribute("height", Double.toString(item.height)); 1284 rect.attribute("filter", "url(#shadow"+diagramId+")"); 1285 String borderColor = item.getMode() == ClassItemMode.SLICE ? SLICE_COLOR : "black"; 1286 if (status == null) { 1287 rect.style("fill:"+(status == null ? "#ffffff" : status.getColorSvg())+";stroke:"+borderColor+";stroke-width:1"); 1288 } else { 1289 rect.style("fill:"+status.getColorSvg()+";stroke:"+borderColor+";stroke-width:1"); 1290 } 1291 rect.attribute("id", prefix+item.getId()); 1292 1293 var line = g.svgLine(null); 1294 line.attribute("x1", Double.toString(item.left)); 1295 line.attribute("y1", Double.toString(item.top+HEADER_HEIGHT + GAP_HEIGHT*2)); 1296 line.attribute("x2", Double.toString(item.left+item.width)); 1297 line.attribute("y2", Double.toString(item.top+HEADER_HEIGHT + GAP_HEIGHT*2)); 1298 line.style("stroke:dimgrey;stroke-width:1"); 1299 line.attribute("id", prefix+"n"+(++nc)); 1300 1301 var text = g.svgText(null); 1302 text.attribute("x", Double.toString(item.left + item.width / 2)); 1303 text.attribute("y", Double.toString(item.top+HEADER_HEIGHT)); 1304 text.attribute("fill", "black"); 1305 if (!path.contains(".")) 1306 text.attribute("class", "diagram-class-title diagram-class-resource"); 1307 else 1308 text.attribute("class", "diagram-class-title"); 1309 if (parent == null) { 1310 text.attribute("id", prefix+"n"+(++nc)); 1311 if (!path.contains(".") && sd.getAbstract()) { 1312 text.style("font-style: italic"); 1313 } 1314 1315 var a = text.svgAx(makeLink(item.getName())); 1316 a.attribute("id", prefix+"n"+(++nc)); 1317 a.tx(item.getName()); 1318 1319 // if (definitions.getBaseResources().containsKey(e.getName()) && definitions.getBaseResources().get(e.getName()).isInterface()) { 1320 // xml.text(" "); 1321 // xml.attribute("xlink:href", "uml.html#interface"); 1322 // xml.enter("a"); 1323 // xml.text("ĞInterfaceğ"); 1324 // xml.exit("a"); 1325 // } 1326 } else if (!path.contains(".")) { 1327 text.attribute("id", prefix+"n"+(++nc)); 1328 if (!path.contains(".") && sd.getAbstract()) { 1329 text.style("font-style: italic"); 1330 } 1331 var a = text.svgAx(sd.getWebPath()); 1332 a.tx(item.getName()); 1333 text.tx(" ("); 1334 a = text.svgAx(parent.getWebPath()); 1335 a.attribute("class", "diagram-class-reference"); 1336 a.attribute("id", prefix+"n"+(++nc)); 1337 a.style("font-style: italic"); 1338 a.tx(parent.getName()); 1339 text.tx(")"); 1340 } else { 1341 text.attribute("id", prefix+"n"+(++nc)); 1342 text.tx(item.getName()); 1343 } 1344 1345 List<ElementDefinition> children = putils.getChildList(sd, ed); 1346 if (attributes) { 1347 int i = 0; 1348 for (ElementDefinition c : children) { 1349 if (inScope(c) && countsAsAttribute(sd, c)) { 1350 i++; 1351 addAttribute(g, item.left, item.top+HEADER_HEIGHT + GAP_HEIGHT*2 + LINE_HEIGHT * i, sd, c, path, LINE_HEIGHT, item.width); 1352 String[] texts = textForAttribute(sd, c); 1353 i = i + texts.length - 1; 1354 } 1355 } 1356 } 1357 1358 1359 if (innerClasses) { 1360 for (ElementDefinition c : children) { 1361 if (inScope(c) && countsAsRelationship(sd, c)) { 1362 if (!c.hasSliceName()) { 1363 if (c.hasContentReference()) { 1364 String cr = c.getContentReference(); 1365 ClassItem target = classes.get(cr.substring(cr.indexOf("#")+1)); 1366 links.add(new Link(item, target, LinkType.COMPOSITION, c.getName(), describeCardinality(c), PointKind.unknown, baseUrl(sd, c, path)+path+"."+c.getName(), getEnhancedDefinition(c))); 1367 } else { 1368 ClassItem cc = drawClass(svg, sd, c, path+"."+c.getName(), status, null); 1369 links.add(new Link(item, cc, LinkType.COMPOSITION, c.getName(), describeCardinality(c), PointKind.unknown, baseUrl(sd, c, path)+path+"."+c.getName(), getEnhancedDefinition(c))); 1370 if (c.hasSlicing()) { 1371 List<ElementDefinition> slices = getSlices(children, c); 1372 for (ElementDefinition s : slices) { 1373 ClassItem cc1 = drawClass(svg, sd, s, path+"."+c.getName()+":"+s.getSliceName(), status, null); 1374 links.add(new Link(cc, cc1, LinkType.SLICE, "", describeCardinality(s), PointKind.unknown, baseUrl(sd, s, path)+path+"."+c.getName()+":"+s.getSliceName(), getEnhancedDefinition(c))); 1375 } 1376 } 1377 } 1378 } 1379 } 1380 } 1381 } 1382 return item; 1383 } 1384 1385 private void drawLink(XhtmlNode svg, Link l, XhtmlNode insertionPoint) throws FHIRException, IOException { 1386 Point start; 1387 Point end; 1388 Point p1; 1389 Point p2; 1390 String id = l.source.id + "-" + l.target.id; 1391 if (l.source == l.target) { 1392 start = new Point(l.source.right(), l.source.centerV() - SELF_LINK_HEIGHT, PointKind.unknown); 1393 end = new Point(l.source.right(), l.source.centerV() + SELF_LINK_HEIGHT, PointKind.right); 1394 p1 = new Point(l.source.right() + SELF_LINK_WIDTH, l.source.centerV() - SELF_LINK_HEIGHT, PointKind.unknown); 1395 p2 = new Point(l.source.right() + SELF_LINK_WIDTH, l.source.centerV() + SELF_LINK_HEIGHT, PointKind.unknown); 1396 1397 var path = svg.svgPath(insertionPoint); 1398 path.attribute("id", prefix+id); 1399 path.attribute("d", checkForLinkPathData(id, "M"+start.x+" "+start.y+" L"+p1.x+" "+p1.y+" L"+p1.x+" "+p1.y+" L"+p2.x+" "+p2.y+" L"+end.x+" "+end.y)); 1400 path.style("stroke:navy;stroke-width:1;fill:none"); 1401 1402 } else { 1403 Point c1 = new Point(l.source.centerH(), l.source.centerV(), PointKind.unknown); 1404 Point c2 = new Point(l.target.centerH(), l.target.centerV(), PointKind.unknown); 1405 1406 start = intersection(c1, c2, l.source); 1407 end = intersection(c1, c2, l.target); 1408 if (l.count > 0) { 1409 start.x = adjustForDuplicateX(start.x, start.kind, l.index); 1410 start.y = adjustForDuplicateY(start.y, start.kind, l.index); 1411 end.x = adjustForDuplicateX(end.x, end.kind, l.index); 1412 end.y = adjustForDuplicateY(end.y, end.kind, l.index); 1413 1414 } 1415 p1 = end; 1416 p2 = start; 1417 if (start != null && end != null) { 1418 var path = svg.svgPath(insertionPoint); 1419 path.attribute("d", checkForLinkPathData(id, "M"+Double.toString(start.x)+" "+Double.toString(start.y)+" L"+Double.toString(end.x)+" "+Double.toString(end.y))); 1420 if (l.type == LinkType.CONSTRAINT) 1421 path.style("stroke:orange;stroke-width:1;fill:none"); 1422 else if (l.type == LinkType.SLICE) 1423 path.style("stroke:"+SLICE_COLOR+";stroke-width:2;fill:none"); 1424 else 1425 path.style("stroke:navy;stroke-width:1;fill:none"); 1426 path.attribute("id", prefix+id); 1427 } 1428 } 1429 1430 if (start != null && end != null) { 1431 if (l.name == null) { 1432 Point pd1 = calcGenRight(start, p1); 1433 Point pd2 = calcGenLeft(start, p1); 1434 var polygon = svg.svgPolygon(insertionPoint); 1435 polygon.attribute("points", start.toPoint() +" " +pd1.toPoint() +" " +pd2.toPoint() +" " +start.toPoint()); 1436 polygon.style("fill:white;stroke:navy;stroke-width:1"); 1437 polygon.attribute("transform", "rotate("+getAngle(start, p1)+" "+Double.toString(start.x)+" "+Double.toString(start.y)+")"); 1438 polygon.attribute("id", prefix+"n"+(++nc)); 1439 } else { 1440 // draw the diamond 1441 if (l.type != LinkType.SLICE) { 1442 Point pd2 = calcDiamondEnd(start, p1); 1443 Point pd1 = calcDiamondRight(start, p1); 1444 Point pd3 = calcDiamondLeft(start, p1); 1445 var polygon = svg.svgPolygon(insertionPoint); 1446 polygon.attribute("points", checkForLinkPoints(id, start.toPoint() +" " +pd1.toPoint() +" " +pd2.toPoint() +" " +pd3.toPoint()+" "+start.toPoint())); 1447 polygon.style("fill:navy;stroke:navy;stroke-width:1"); 1448 polygon.attribute("transform", checkForLinkTransform(id, "rotate("+getAngle(start, p1)+" "+Double.toString(start.x)+" "+Double.toString(start.y)+")")); 1449 polygon.attribute("id", prefix+id+"-lpolygon"); 1450 } 1451 1452 // draw the name half way along 1453 double x = (int) (p1.x + p2.x) / 2; 1454 double y = (int) (p1.y + p2.y) / 2 + LINE_HEIGHT / 2 + LINE_HEIGHT * l.index; 1455 double w = (int) (textWidth(l.name)); 1456 var g = svg.svgG(insertionPoint); 1457 var rect = g.svgRect(null); 1458 rect.attribute("x", checkForKnownX(id, id+"-lrect", Double.toString(x - w/2))); 1459 rect.attribute("y", checkForKnownY(id, id+"-lrect", Double.toString(y - LINE_HEIGHT))); 1460 rect.attribute("width", Double.toString(w)); 1461 rect.attribute("height", Double.toString(LINE_HEIGHT + GAP_HEIGHT)); 1462 rect.style("fill:white;stroke:black;stroke-width:0"); 1463 rect.attribute("id", prefix+id+"-lrect"); 1464 1465 var text = g.svgText(null); 1466 1467 text.attribute("x", checkForKnownX(id, id+"-lname", Double.toString(x))); 1468 text.attribute("y", checkForKnownY(id, id+"-lname", Double.toString(y - GAP_HEIGHT))); 1469 text.attribute("fill", "black"); 1470 text.attribute("class", "diagram-class-linkage"); 1471 text.attribute("id", prefix+id+"-lname"); 1472 var a = text.svgAx(l.path); 1473 a.attribute("id", prefix+"n"+(++nc)); 1474 a.addTag("title").tx(l.description); 1475 a.tx(l.name); 1476 1477 1478 // draw the cardinality at the terminal end 1479 x = end.x; 1480 y = end.y; 1481 if (end.kind == PointKind.left) { 1482 y = y - GAP_HEIGHT; 1483 x = x - 20; 1484 } else if (end.kind == PointKind.top) 1485 y = y - GAP_HEIGHT; 1486 else if (end.kind == PointKind.right) { 1487 y = y - GAP_HEIGHT; 1488 x = x + 15; 1489 } else if (end.kind == PointKind.bottom) 1490 y = y + LINE_HEIGHT; 1491 w = 18; 1492 text = svg.svgText(insertionPoint); 1493 text.attribute("x", checkForKnownX(id, id+"-lcard", Double.toString(x))); 1494 text.attribute("y", checkForKnownY(id, id+"-lcard", Double.toString(y))); 1495 text.attribute("fill", "black"); 1496 text.attribute("class", "diagram-class-linkage"); 1497 text.attribute("id", prefix+id+"-lcard"); 1498 text.tx("["+l.cardinality+"]"); 1499 } 1500 } 1501 } 1502 1503 private String checkForKnownY(String baseId, String id, String defaultValue) { 1504 if (linkLayouts.containsKey(baseId) && linkLayouts.get(baseId).use && layout.containsKey(id)) { 1505 return ""+layout.get(id).y; 1506 } else { 1507 return defaultValue; 1508 } 1509 } 1510 1511 private String checkForKnownX(String baseId, String id, String defaultValue) { 1512 if (linkLayouts.containsKey(baseId) && linkLayouts.get(baseId).use && layout.containsKey(id)) { 1513 return ""+layout.get(id).x; 1514 } else { 1515 return defaultValue; 1516 } 1517 } 1518 1519 private String checkForLinkTransform(String id, String defaultValue) { 1520 if (linkLayouts.containsKey(id) && linkLayouts.get(id).use && linkLayouts.get(id).diamondTransform != null) { 1521 return linkLayouts.get(id).diamondTransform; 1522 } else { 1523 return defaultValue; 1524 } 1525 } 1526 1527 private String checkForLinkPoints(String id, String defaultValue) { 1528 if (linkLayouts.containsKey(id) && linkLayouts.get(id).use && linkLayouts.get(id).diamondPoints != null) { 1529 return linkLayouts.get(id).diamondPoints; 1530 } else { 1531 return defaultValue; 1532 } 1533 } 1534 1535 private String checkForLinkPathData(String id, String defaultValue) { 1536 if (linkLayouts.containsKey(id) && linkLayouts.get(id).use && linkLayouts.get(id).pathData != null) { 1537 return linkLayouts.get(id).pathData; 1538 } else { 1539 return defaultValue; 1540 } 1541 } 1542 1543 private String abs(double d) { 1544 if (d < 0) { 1545 return Double.toString(-d); 1546 } else { 1547 return Double.toString(d); 1548 } 1549 } 1550 1551 private double adjustForDuplicateX(double x, PointKind kind, int index) { 1552 switch (kind) { 1553 case bottom: 1554 return x + (DUPLICATE_GAP * (index - 0.5)); 1555 case top: 1556 return x + (DUPLICATE_GAP * (index - 0.5)); 1557 default: 1558 return x; 1559 } 1560 } 1561 1562 private double adjustForDuplicateY(double y, PointKind kind, int index) { 1563 switch (kind) { 1564 case left: 1565 return y - (DUPLICATE_GAP * (index - 0.5)); 1566 case right: 1567 return y - (DUPLICATE_GAP * (index - 0.5)); 1568 default: 1569 return y; 1570 } 1571 } 1572 1573 private String getAngle(Point start, Point end) { 1574 double inRads = Math.atan2(end.y - start.y, end.x-start.x); 1575 // if (inRads < 0) 1576 // inRads = Math.abs(inRads); 1577 // else 1578 // inRads = 2*Math.PI - inRads; 1579 1580 return Double.toString(Math.toDegrees(inRads)); 1581 } 1582 1583 private Point calcDiamondEnd(Point start, Point end) { 1584 return new Point(start.x+12, start.y+0, PointKind.unknown); 1585 } 1586 1587 private Point calcDiamondRight(Point start, Point end) { 1588 return new Point(start.x+6, start.y+4, PointKind.unknown); 1589 } 1590 1591 private Point calcDiamondLeft(Point start, Point end) { 1592 return new Point(start.x+6, start.y-4, PointKind.unknown); 1593 } 1594 1595 private Point calcGenRight(Point start, Point end) { 1596 return new Point(start.x+8, start.y+6, PointKind.unknown); 1597 } 1598 1599 private Point calcGenLeft(Point start, Point end) { 1600 return new Point(start.x+8, start.y-6, PointKind.unknown); 1601 } 1602 1603 private Point intersection(Point start, Point end, ClassItem box) { 1604 Point p = calculateIntersect(start.x, start.y, end.x, end.y, box.left, box.top, box.left + box.width, box.top, PointKind.top); 1605 if (p == null) 1606 p = calculateIntersect(start.x, start.y, end.x, end.y, box.left, box.top+box.height, box.left+box.width, box.top+box.height, PointKind.bottom); 1607 if (p == null) 1608 p = calculateIntersect(start.x, start.y, end.x, end.y, box.left, box.top, box.left, box.top+box.height, PointKind.left); 1609 if (p == null) 1610 p = calculateIntersect(start.x, start.y, end.x, end.y, box.left+box.width, box.top, box.left+box.width, box.top+box.height, PointKind.right); 1611 return p; 1612 } 1613 1614 private Point calculateIntersect(double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4, PointKind kind) { 1615 Segment s1 = new Segment(new Point(x1,y1, PointKind.unknown), new Point(x2,y2, PointKind.unknown)); 1616 Segment s2 = new Segment(new Point(x3,y3, PointKind.unknown), new Point(x4,y4, PointKind.unknown)); 1617 return hasIntersection(s1, s2, kind); 1618 // double slope1 = (y2-y1) / (x2-x1); 1619 // double slope2 = (y4-y3) / (x4-x3); 1620 // 1621 // if (Math.abs(slope1 - slope2) < 0.000001) 1622 // return null; 1623 // 1624 // double x = ( ( (x4*y3 - y4*x3) / (x4-x3) ) - ( (x2-y1 - y2*x1) / (x2-x1) ) ) / ( slope1 - slope2 ); 1625 // double y = slope1 * x + ( (x2*y1 - y2*x1) / (x2-x1) ); 1626 // 1627 // if (inBounds(x, x1, x2) && inBounds(x, x3, x4) && inBounds(y, y1, y2) && inBounds(y, y3, y4)) 1628 // return new Point((int) x, (int) y); 1629 // else 1630 // return null; 1631 } 1632 1633 public Point hasIntersection(Segment segment1, Segment segment2, PointKind kind){ 1634 1635 if (segment1.isVertical){ 1636 if (segment2.isVertical) // ( (segment2.start.x - segment1.start.x)*(segment2.end.x - segment1.start.x) > 0 ) 1637 return null; 1638 else { 1639 double fx_at_segment1startx = segment2.slope * segment1.start.x - segment2.intercept; 1640 if (inBounds(fx_at_segment1startx, segment1.start.y, segment1.end.y) && inBounds(segment1.start.x, segment2.start.x, segment2.end.x)) 1641 return new Point(segment1.start.x, fx_at_segment1startx, kind); 1642 else 1643 return null; 1644 } 1645 } 1646 else if (segment2.isVertical){ 1647 return hasIntersection(segment2, segment1, kind); 1648 } 1649 else { //both segment1 and segment2 are not vertical 1650 if (segment1.slope == segment2.slope) 1651 return null; 1652 else { 1653 double x1 = segment1.start.x; 1654 double y1 = segment1.start.y; 1655 double x2 = segment1.end.x; 1656 double y2 = segment1.end.y; 1657 double x3 = segment2.start.x; 1658 double y3 = segment2.start.y; 1659 double x4 = segment2.end.x; 1660 double y4 = segment2.end.y; 1661 double x = ((x4*y3-y4*x3)/(x4-x3) - (x2*y1-y2*x1)/(x2-x1)) /( (y2-y1)/(x2-x1) - (y4-y3)/(x4-x3)); 1662 1663 if (inBounds(x, x1, x2) && inBounds(x, x3, x4)) { 1664 return new Point(x, (segment1.slope * x - segment1.intercept), kind); 1665 } else 1666 return null; 1667 } 1668 } 1669 } 1670 1671 public String buildConstraintDiagram(StructureDefinition profile, String defnFile) throws FHIRException, IOException { 1672 this.defnFile = defnFile; 1673 this.defnSD = profile; 1674 File f = new File(Utilities.path(sourceFolder, diagramId+".svg")); 1675 if (f.exists()) { 1676 parseSvgFile(f, f.getAbsolutePath()); 1677 } 1678 attributes = true; 1679 innerClasses = true; 1680 constraintMode = true; 1681 classNames.add(profile.getName()); 1682 1683 1684 XhtmlNode doc = new XhtmlNode(NodeType.Element, "div"); 1685 XhtmlNode svg = doc.svg(); 1686 1687 minx = 0; 1688 miny = 0; 1689 1690 StructureDefinition sd = new SnapshotGenerationPreProcessor(putils).trimSnapshot(profile); 1691 1692 Point size = determineConstraintMetrics(sd); 1693 adjustAllForMin(size); 1694 1695 svg.attribute("id", prefix+"n"+(++nc)); 1696 svg.attribute("version", "1.1"); 1697 svg.attribute("width", Double.toString(size.x)); 1698 svg.attribute("height", Double.toString(size.y)); 1699 1700 shadowFilter(svg); 1701 drawConstraintElement(svg, sd); 1702 countDuplicateLinks(); 1703 XhtmlNode insertionPoint = svg.getChildNodes().get(0); 1704 for (Link l : links) { 1705 drawLink(svg, l, insertionPoint); 1706 } 1707 1708 1709 String s = new XhtmlComposer(true, true).compose(doc.getChildNodes()); 1710 produceOutput("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+s); 1711 return s; 1712 } 1713 1714 private void drawConstraintElement(XhtmlNode svg, StructureDefinition sd) throws FHIRException, IOException { 1715 1716 ElementDefinition ed = sd.getSnapshot().getElementFirstRep(); 1717 1718 StructureDefinition base = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); 1719 drawClass(svg, sd, ed, ed.getPath(), sd.getStandardsStatus(), base); 1720 } 1721 1722 private Point determineConstraintMetrics(StructureDefinition sd) throws FHIRException, IOException { 1723 1724 double width = textWidth("Element") * 1.8; 1725 double height = HEADER_HEIGHT + GAP_HEIGHT*2; 1726 // if ("true".equals(ini.getStringProperty("diagram", "element-attributes"))) { 1727 // height = height + LINE_HEIGHT + GAP_HEIGHT; 1728 // width = textWidth("extension : Extension 0..*"); 1729 // } 1730 1731 Point p = new Point(0, 0, PointKind.unknown); 1732 ClassItem item = new ClassItem(p.x, p.y, width, height, diagramId, null, ClassItemMode.NORMAL); 1733 classes.put(null, item); 1734 double x = item.right()+MARGIN_X; 1735 double y = item.bottom()+MARGIN_Y; 1736 ElementDefinition ed = sd.getSnapshot().getElementFirstRep(); 1737 StructureDefinition base = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); 1738 p = determineMetrics(sd, ed, item, ed.getName(), null, base, ClassItemMode.NORMAL); 1739 x = Math.max(x, p.x+MARGIN_X); 1740 y = Math.max(y, p.y+MARGIN_Y); 1741 return new Point(x, y, PointKind.unknown); 1742 } 1743 1744}