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