001package org.hl7.fhir.utilities.xhtml; 002 003/* 004 Copyright (c) 2011+, HL7, Inc. 005 All rights reserved. 006 007 Redistribution and use in source and binary forms, with or without modification, 008 are permitted provided that the following conditions are met: 009 010 * Redistributions of source code must retain the above copyright notice, this 011 list of conditions and the following disclaimer. 012 * Redistributions in binary form must reproduce the above copyright notice, 013 this list of conditions and the following disclaimer in the documentation 014 and/or other materials provided with the distribution. 015 * Neither the name of HL7 nor the names of its contributors may be used to 016 endorse or promote products derived from this software without specific 017 prior written permission. 018 019 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 022 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 023 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 024 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 025 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 026 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 027 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 028 POSSIBILITY OF SUCH DAMAGE. 029 030 */ 031 032 033 034/* 035Copyright (c) 2011+, HL7, Inc 036All rights reserved. 037 038Redistribution and use in source and binary forms, with or without modification, 039are permitted provided that the following conditions are met: 040 041 * Redistributions of source code must retain the above copyright notice, this 042 list of conditions and the following disclaimer. 043 * Redistributions in binary form must reproduce the above copyright notice, 044 this list of conditions and the following disclaimer in the documentation 045 and/or other materials provided with the distribution. 046 * Neither the name of HL7 nor the names of its contributors may be used to 047 endorse or promote products derived from this software without specific 048 prior written permission. 049 050THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 051ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 052WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 053IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 054INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 055NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 056PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 057WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 058ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 059POSSIBILITY OF SUCH DAMAGE. 060 061*/ 062 063import java.awt.Color; 064import java.awt.image.BufferedImage; 065import java.io.ByteArrayOutputStream; 066import java.io.File; 067import java.io.FileOutputStream; 068import java.io.IOException; 069import java.io.OutputStream; 070import java.util.ArrayList; 071import java.util.HashMap; 072import java.util.List; 073import java.util.Map; 074import java.util.Set; 075 076import javax.imageio.ImageIO; 077 078import org.apache.commons.codec.binary.Base64; 079import org.apache.commons.io.FileUtils; 080import org.commonmark.node.Node; 081import org.commonmark.parser.Parser; 082import org.commonmark.renderer.html.HtmlRenderer; 083import org.hl7.fhir.exceptions.FHIRException; 084import org.hl7.fhir.utilities.TranslatingUtilities; 085import org.hl7.fhir.utilities.Utilities; 086 087 088public class HierarchicalTableGenerator extends TranslatingUtilities { 089 public static final String TEXT_ICON_REFERENCE = "Reference to another Resource"; 090 public static final String TEXT_ICON_PRIMITIVE = "Primitive Data Type"; 091 public static final String TEXT_ICON_KEY = "JSON Key Value"; 092 public static final String TEXT_ICON_DATATYPE = "Data Type"; 093 public static final String TEXT_ICON_RESOURCE = "Resource"; 094 public static final String TEXT_ICON_ELEMENT = "Element"; 095 public static final String TEXT_ICON_OBJECT_BOX = "Object"; 096 public static final String TEXT_ICON_REUSE = "Reference to another Element"; 097 public static final String TEXT_ICON_EXTENSION = "Extension"; 098 public static final String TEXT_ICON_CHOICE = "Choice of Types"; 099 public static final String TEXT_ICON_SLICE = "Slice Definition"; 100 public static final String TEXT_ICON_SLICE_ITEM = "Slice Item"; 101 public static final String TEXT_ICON_FIXED = "Fixed Value"; 102 public static final String TEXT_ICON_EXTENSION_SIMPLE = "Simple Extension"; 103 public static final String TEXT_ICON_PROFILE = "Profile"; 104 public static final String TEXT_ICON_EXTENSION_COMPLEX = "Complex Extension"; 105 106 public static final int NEW_REGULAR = 0; 107 public static final int CONTINUE_REGULAR = 1; 108 public static final int NEW_SLICER = 2; 109 public static final int CONTINUE_SLICER = 3; 110 public static final int NEW_SLICE = 4; 111 public static final int CONTINUE_SLICE = 5; 112 private static final String BACKGROUND_ALT_COLOR = "#F7F7F7"; 113 public static boolean ACTIVE_TABLES = false; 114 115 public enum TextAlignment { 116 LEFT, CENTER, RIGHT; 117 } 118 119 private static Map<String, String> files = new HashMap<String, String>(); 120 121 private class Counter { 122 private int count = -1; 123 private void row() { 124 count++; 125 } 126 private boolean isOdd() { 127 return count % 2 == 1; 128 } 129 } 130 public class Piece { 131 private String tag; 132 private String reference; 133 private String text; 134 private String hint; 135 private String style; 136 private Map<String, String> attributes; 137 private XhtmlNodeList children; 138 139 public Piece(String tag) { 140 super(); 141 this.tag = tag; 142 } 143 144 public Piece(String reference, String text, String hint) { 145 super(); 146 this.reference = reference; 147 this.text = text; 148 this.hint = hint; 149 } 150 public String getReference() { 151 return reference; 152 } 153 public void setReference(String value) { 154 reference = value; 155 } 156 public String getText() { 157 return text; 158 } 159 public String getHint() { 160 return hint; 161 } 162 163 public String getTag() { 164 return tag; 165 } 166 167 public String getStyle() { 168 return style; 169 } 170 171 public void setTag(String tag) { 172 this.tag = tag; 173 } 174 175 public Piece setText(String text) { 176 this.text = text; 177 return this; 178 } 179 180 public void setHint(String hint) { 181 this.hint = hint; 182 } 183 184 public Piece setStyle(String style) { 185 this.style = style; 186 return this; 187 } 188 189 public Piece addStyle(String style) { 190 if (this.style != null) 191 this.style = this.style+"; "+style; 192 else 193 this.style = style; 194 return this; 195 } 196 197 public void addToHint(String text) { 198 if (this.hint == null) 199 this.hint = text; 200 else 201 this.hint += (this.hint.endsWith(".") || this.hint.endsWith("?") ? " " : ". ")+text; 202 } 203 204 public boolean hasChildren() { 205 return children != null && !children.isEmpty(); 206 } 207 208 public XhtmlNodeList getChildren() { 209 if (children == null) 210 children = new XhtmlNodeList(); 211 return children; 212 } 213 214 public Piece addHtml(XhtmlNode x) { 215 getChildren().add(x); 216 return this; 217 } 218 219 public Piece attr(String name, String value) { 220 if (attributes == null) { 221 attributes = new HashMap<>(); 222 } 223 attributes.put(name, value); 224 return this; 225 } 226 } 227 228 public class Cell { 229 private List<Piece> pieces = new ArrayList<HierarchicalTableGenerator.Piece>(); 230 private String cellStyle; 231 protected int span = 1; 232 private TextAlignment alignment = TextAlignment.LEFT; 233 private String id; 234 235 public Cell() { 236 237 } 238 public Cell(String prefix, String reference, String text, String hint, String suffix) { 239 super(); 240 if (!Utilities.noString(prefix)) 241 pieces.add(new Piece(null, prefix, null)); 242 pieces.add(new Piece(reference, text, hint)); 243 if (!Utilities.noString(suffix)) 244 pieces.add(new Piece(null, suffix, null)); 245 } 246 public List<Piece> getPieces() { 247 return pieces; 248 } 249 public Cell addPiece(Piece piece) { 250 pieces.add(piece); 251 return this; 252 } 253 254 255 256 public Cell addMarkdown(String md) { 257 if (!Utilities.noString(md)) { 258 try { 259 Parser parser = Parser.builder().build(); 260 Node document = parser.parse(md); 261 HtmlRenderer renderer = HtmlRenderer.builder().escapeHtml(true).build(); 262 String html = renderer.render(document); 263 pieces.addAll(htmlToParagraphPieces(html, null)); 264 } catch (Exception e) { 265 e.printStackTrace(); 266 } 267 } 268 return this; 269 } 270 271 public Cell addMarkdownNoPara(String md) { 272 return addMarkdownNoPara(md, null); 273 } 274 275 public Cell addMarkdownNoPara(String md, String style) { 276 try { 277 Parser parser = Parser.builder().build(); 278 Node document = parser.parse(md); 279 HtmlRenderer renderer = HtmlRenderer.builder().escapeHtml(true).build(); 280 String html = renderer.render(document); 281 pieces.addAll(htmlToParagraphPieces(html, style)); 282 } catch (Exception e) { 283 e.printStackTrace(); 284 } 285 return this; 286 } 287 288 private List<Piece> htmlToParagraphPieces(String html, String style) { 289 List<Piece> myPieces = new ArrayList<Piece>(); 290 try { 291 XhtmlNode node = new XhtmlParser().parseFragment("<html>"+html+"</html>"); 292 boolean first = true; 293 for (XhtmlNode c : node.getChildNodes()) { 294 if (first) { 295 first = false; 296 } else { 297 myPieces.add(new Piece("br")); 298 myPieces.add(new Piece("br")); 299 } 300 if (c.getNodeType() == NodeType.Text) { 301 if (!Utilities.isWhitespace(c.getContent())) 302 addNode(myPieces, c, style); 303 } else if ("p".equals(c.getName())) { 304 for (XhtmlNode g : c.getChildNodes()) { 305 addNode(myPieces, g, style); 306 } 307 } else { 308 Piece x = new Piece(c.getName()); 309 x.getChildren().addAll(c.getChildNodes()); 310 if (style != null) { 311 x.addStyle(style); 312 } 313 myPieces.add(x); 314 } 315 } 316// String[] paragraphs = html.replace("<p>", "").split("<\\/p>|<br \\/>"); 317// for (int i=0;i<paragraphs.length;i++) { 318// if (!paragraphs[i].isEmpty()) { 319// if (i!=0) { 320// myPieces.add(new Piece("br")); 321// myPieces.add(new Piece("br")); 322// } 323// myPieces.addAll(htmlFormattingToPieces(paragraphs[i])); 324// } 325// } 326 } catch (Exception e) { 327 throw new FHIRException("Exception parsing html: "+e.getMessage()+" for "+html, e); 328 } 329 330 return myPieces; 331 } 332 333 private List<Piece> htmlFormattingToPieces(String html) throws IOException, FHIRException { 334 List<Piece> myPieces = new ArrayList<Piece>(); 335 if (html.contains(("<"))) { 336 XhtmlNode node = new XhtmlParser().parseFragment("<p>"+html+"</p>"); 337 for (XhtmlNode c : node.getChildNodes()) { 338 addNode(myPieces, c, null); 339 } 340 } else 341 myPieces.add(new Piece(null, html, null)); 342 return myPieces; 343 } 344 345 private void addNode(List<Piece> list, XhtmlNode c, String style) { 346 if (c.getNodeType() == NodeType.Text) 347 list.add(styleIt(new Piece(null, c.getContent(), null), style)); 348 else if (c.getNodeType() == NodeType.Element) { 349 if (c.getName().equals("a")) { 350 list.add(styleIt(new Piece(c.getAttribute("href"), c.allText(), c.getAttribute("title")), style)); 351 } else if (c.getName().equals("b") || c.getName().equals("em") || c.getName().equals("strong")) { 352 list.add(styleIt(new Piece(null, c.allText(), null).setStyle("font-face: bold"), style)); 353 } else if (c.getName().equals("code")) { 354 list.add(styleIt(new Piece(null, c.allText(), null).setStyle("padding: 2px 4px; color: #005c00; background-color: #f9f2f4; white-space: nowrap; border-radius: 4px"), style)); 355 } else if (c.getName().equals("i")) { 356 list.add(styleIt(new Piece(null, c.allText(), null).setStyle("font-style: italic"), style)); 357 } else if (c.getName().equals("pre")) { 358 Piece p = styleIt(new Piece(c.getName()).setStyle("white-space: pre; font-family: courier"), style); 359 list.add(p); 360 p.getChildren().addAll(c.getChildNodes()); 361 } else if (c.getName().equals("ul") || c.getName().equals("ol")) { 362 Piece p = styleIt(new Piece(c.getName()), style); 363 list.add(p); 364 p.getChildren().addAll(c.getChildNodes()); 365 } else if (c.getName().equals("i")) { 366 list.add(styleIt(new Piece(null, c.allText(), null).setStyle("font-style: italic"), style)); 367 } else if (c.getName().equals("h1")||c.getName().equals("h2")||c.getName().equals("h3")||c.getName().equals("h4")) { 368 Piece p = styleIt(new Piece(c.getName()), style); 369 list.add(p); 370 p.getChildren().addAll(c.getChildNodes()); 371 } else if (c.getName().equals("br")) { 372 list.add(styleIt(new Piece(c.getName()), style)); 373 } else { 374 throw new Error("Not handled yet: "+c.getName()); 375 } 376 } else 377 throw new Error("Unhandled type "+c.getNodeType().toString()); 378 } 379 380 381 private Piece styleIt(Piece piece, String style) { 382 if (style != null) { 383 piece.addStyle(style); 384 } 385 return piece; 386 } 387 388 public Cell addStyle(String style) { 389 for (Piece p : pieces) 390 p.addStyle(style); 391 return this; 392 } 393 public void addToHint(String text) { 394 for (Piece p : pieces) 395 p.addToHint(text); 396 } 397 public Piece addStyledText(String hint, String alt, String fgColor, String bgColor, String link, boolean border) { 398 Piece p = new Piece(link, alt, hint); 399 p.addStyle("padding-left: 3px"); 400 p.addStyle("padding-right: 3px"); 401 if (border) { 402 p.addStyle("border: 1px grey solid"); 403 p.addStyle("font-weight: bold"); 404 } 405 if (fgColor != null) { 406 p.addStyle("color: "+fgColor); 407 p.addStyle("background-color: "+bgColor); 408 } else { 409 p.addStyle("color: black"); 410 p.addStyle("background-color: "+bgColor != null ? bgColor : "white"); 411 } 412 pieces.add(p); 413 return p; 414 } 415 public Piece addText(String text) { 416 Piece p = new Piece(null, text, null); 417 pieces.add(p); 418 return p; 419 } 420 public String text() { 421 StringBuilder b = new StringBuilder(); 422 for (Piece p : pieces) 423 b.append(p.text); 424 return b.toString(); 425 } 426 @Override 427 public String toString() { 428 if (span != 1) { 429 return text()+" {"+span+"}"; 430 } else { 431 return text(); 432 } 433 } 434 public Cell setStyle(String value) { 435 cellStyle = value; 436 return this; 437 } 438 439 public Cell span(int value) { 440 span = value; 441 return this; 442 } 443 public Cell center() { 444 alignment = TextAlignment.CENTER; 445 return this; 446 } 447 448 public String getId() { 449 return id; 450 } 451 public void setId(String id) { 452 this.id = id; 453 } 454 455 } 456 457 public class Title extends Cell { 458 private int width; 459 460 public Title(String prefix, String reference, String text, String hint, String suffix, int width) { 461 super(prefix, reference, text, hint, suffix); 462 this.width = width; 463 } 464 465 public Title(String prefix, String reference, String text, String hint, String suffix, int width, int span) { 466 super(prefix, reference, text, hint, suffix); 467 this.width = width; 468 this.span = span; 469 } 470 471 public Title setStyle(String value) { 472 super.setStyle(value); 473 return this; 474 } 475 } 476 477 public class Row { 478 private List<Row> subRows = new ArrayList<HierarchicalTableGenerator.Row>(); 479 private List<Cell> cells = new ArrayList<HierarchicalTableGenerator.Cell>(); 480 private String icon; 481 private String anchor; 482 private String hint; 483 private String color; 484 private int lineColor; 485 private String id; 486 private String opacity; 487 488 public List<Row> getSubRows() { 489 return subRows; 490 } 491 public List<Cell> getCells() { 492 return cells; 493 } 494 public String getIcon() { 495 return icon; 496 } 497 public void setIcon(String icon, String hint) { 498 this.icon = icon; 499 this.hint = hint; 500 } 501 public String getAnchor() { 502 return anchor; 503 } 504 public void setAnchor(String anchor) { 505 this.anchor = anchor; 506 } 507 public String getHint() { 508 return hint; 509 } 510 public String getColor() { 511 return color; 512 } 513 public void setColor(String color) { 514 this.color = color; 515 } 516 public int getLineColor() { 517 return lineColor; 518 } 519 public void setLineColor(int lineColor) { 520 assert lineColor >= 0; 521 assert lineColor <= 2; 522 this.lineColor = lineColor; 523 } 524 public String getId() { 525 return id; 526 } 527 public void setId(String id) { 528 this.id = id; 529 } 530 public String getOpacity() { 531 return opacity; 532 } 533 public void setOpacity(String opacity) { 534 this.opacity = opacity; 535 } 536 537 } 538 539 public class TableModel { 540 private String id; 541 private boolean active; 542 private List<Title> titles = new ArrayList<HierarchicalTableGenerator.Title>(); 543 private List<Row> rows = new ArrayList<HierarchicalTableGenerator.Row>(); 544 private String docoRef; 545 private String docoImg; 546 private boolean alternating; 547 548 public TableModel(String id, boolean active) { 549 super(); 550 this.id = id; 551 this.active = active; 552 } 553 public List<Title> getTitles() { 554 return titles; 555 } 556 public List<Row> getRows() { 557 return rows; 558 } 559 public String getDocoRef() { 560 return docoRef; 561 } 562 public String getDocoImg() { 563 return docoImg; 564 } 565 public void setDocoRef(String docoRef) { 566 this.docoRef = docoRef; 567 } 568 public void setDocoImg(String docoImg) { 569 this.docoImg = docoImg; 570 } 571 public String getId() { 572 return id; 573 } 574 575 public void setId(String id) { 576 this.id = id; 577 } 578 public boolean isActive() { 579 return active && ACTIVE_TABLES; 580 } 581 public boolean isAlternating() { 582 return alternating; 583 } 584 public void setAlternating(boolean alternating) { 585 this.alternating = alternating; 586 } 587 588 } 589 590 591 private String dest; 592 private boolean makeTargets; 593 594 /** 595 * There are circumstances where the table has to present in the absence of a stable supporting infrastructure. 596 * and the file paths cannot be guaranteed. For these reasons, you can tell the builder to inline all the graphics 597 * (all the styles are inlined anyway, since the table fbuiler has even less control over the styling 598 * 599 */ 600 private boolean inLineGraphics; 601 602 public HierarchicalTableGenerator() { 603 super(); 604 } 605 606 public HierarchicalTableGenerator(String dest, boolean inlineGraphics) { 607 super(); 608 this.dest = dest; 609 this.inLineGraphics = inlineGraphics; 610 this.makeTargets = true; 611 checkSetup(); 612 } 613 614 private void checkSetup() { 615 if (dest == null) { 616 throw new Error("what"); 617 } 618 619 } 620 621 public HierarchicalTableGenerator(String dest, boolean inlineGraphics, boolean makeTargets) { 622 super(); 623 this.dest = dest; 624 this.inLineGraphics = inlineGraphics; 625 this.makeTargets = makeTargets; 626 checkSetup(); 627 } 628 629 public TableModel initNormalTable(String prefix, boolean isLogical, boolean alternating, String id, boolean isActive) { 630 TableModel model = new TableModel(id, isActive); 631 632 model.setAlternating(alternating); 633 model.setDocoImg(Utilities.pathURL(prefix, "help16.png")); 634 model.setDocoRef(Utilities.pathURL("https://build.fhir.org/ig/FHIR/ig-guidance", "readingIgs.html#table-views")); 635 model.getTitles().add(new Title(null, model.getDocoRef(), translate("sd.head", "Name"), translate("sd.hint", "The logical name of the element"), null, 0)); 636 model.getTitles().add(new Title(null, model.getDocoRef(), translate("sd.head", "Flags"), translate("sd.hint", "Information about the use of the element"), null, 0)); 637 model.getTitles().add(new Title(null, model.getDocoRef(), translate("sd.head", "Card."), translate("sd.hint", "Minimum and Maximum # of times the the element can appear in the instance"), null, 0)); 638 model.getTitles().add(new Title(null, model.getDocoRef(), translate("sd.head", "Type"), translate("sd.hint", "Reference to the type of the element"), null, 100)); 639 model.getTitles().add(new Title(null, model.getDocoRef(), translate("sd.head", "Description & Constraints"), translate("sd.hint", "Additional information about the element"), null, 0)); 640 if (isLogical) { 641 model.getTitles().add(new Title(null, prefix+"structuredefinition.html#logical", "Implemented As", "How this logical data item is implemented in a concrete resource", null, 0)); 642 } 643 return model; 644 } 645 646 public TableModel initComparisonTable(String prefix, String id) { 647 TableModel model = new TableModel(id, true); 648 649 model.setAlternating(true); 650 model.setDocoImg(Utilities.pathURL(prefix, "help16.png")); 651 model.setDocoRef(Utilities.pathURL(prefix, "formats.html#table")); 652 model.getTitles().add(new Title(null, model.getDocoRef(), translate("sd.head", "Name"), translate("sd.hint", "The logical name of the element"), null, 0)); 653 model.getTitles().add(new Title(null, model.getDocoRef(), translate("sd.head", "L Flags"), translate("sd.hint", "Information about the use of the element - Left Structure"), null, 0).setStyle("border-left: 1px grey solid")); 654 model.getTitles().add(new Title(null, model.getDocoRef(), translate("sd.head", "L Card."), translate("sd.hint", "Minimum and Maximum # of times the the element can appear in the instance - Left Structure"), null, 0)); 655 model.getTitles().add(new Title(null, model.getDocoRef(), translate("sd.head", "L Type"), translate("sd.hint", "Reference to the type of the element - Left Structure"), null, 100)); 656 model.getTitles().add(new Title(null, model.getDocoRef(), translate("sd.head", "L Description & Constraints"), translate("sd.hint", "Additional information about the element - Left Structure"), null, 0).setStyle("border-right: 1px grey solid")); 657 model.getTitles().add(new Title(null, model.getDocoRef(), translate("sd.head", "R Flags"), translate("sd.hint", "Information about the use of the element - Left Structure"), null, 0).setStyle("border-left: 1px grey solid")); 658 model.getTitles().add(new Title(null, model.getDocoRef(), translate("sd.head", "R Card."), translate("sd.hint", "Minimum and Maximum # of times the the element can appear in the instance - Left Structure"), null, 0)); 659 model.getTitles().add(new Title(null, model.getDocoRef(), translate("sd.head", "L Type"), translate("sd.hint", "Reference to the type of the element - Left Structure"), null, 100)); 660 model.getTitles().add(new Title(null, model.getDocoRef(), translate("sd.head", "L Description & Constraints"), translate("sd.hint", "Additional information about the element - Left Structure"), null, 0).setStyle("border-right: 1px grey solid")); 661 model.getTitles().add(new Title(null, model.getDocoRef(), translate("sd.head", "Comments"), translate("sd.hint", "Comments about the comparison"), null, 0)); 662 return model; 663 } 664 665 666 667 public TableModel initGridTable(String prefix, String id) { 668 TableModel model = new TableModel(id, false); 669 670 model.getTitles().add(new Title(null, model.getDocoRef(), translate("sd.head", "Name"), translate("sd.hint", "The name of the element (Slice name in brackets). Mouse-over provides definition"), null, 0)); 671 model.getTitles().add(new Title(null, model.getDocoRef(), translate("sd.head", "Card."), translate("sd.hint", "Minimum and Maximum # of times the the element can appear in the instance. Super-scripts indicate additional constraints on appearance"), null, 0)); 672 model.getTitles().add(new Title(null, model.getDocoRef(), translate("sd.head", "Type"), translate("sd.hint", "Reference to the type of the element"), null, 100)); 673 model.getTitles().add(new Title(null, model.getDocoRef(), translate("sd.head", "Constraints and Usage"), translate("sd.hint", "Fixed values, length limits, vocabulary bindings and other usage notes"), null, 0)); 674 return model; 675 } 676 677 public XhtmlNode generate(TableModel model, String imagePath, int border, Set<String> outputTracker) throws IOException, FHIRException { 678 checkModel(model); 679 XhtmlNode table = new XhtmlNode(NodeType.Element, "table").setAttribute("border", Integer.toString(border)).setAttribute("cellspacing", "0").setAttribute("cellpadding", "0"); 680 681 if (model.isActive()) { 682 table.setAttribute("id", model.getId()); 683 } 684 table.setAttribute("style", "border: " + border + "px #F0F0F0 solid; font-size: 11px; font-family: verdana; vertical-align: top;"); 685 XhtmlNode tr = table.addTag("tr"); 686 tr.setAttribute("style", "border: " + Integer.toString(1 + border) + "px #F0F0F0 solid; font-size: 11px; font-family: verdana; vertical-align: top"); 687 XhtmlNode tc = null; 688 for (Title t : model.getTitles()) { 689 tc = renderCell(tr, t, "th", null, null, null, false, null, "white", 0, imagePath, border, outputTracker, model, null); 690 if (t.width != 0) 691 tc.setAttribute("style", "width: "+Integer.toString(t.width)+"px"); 692 } 693 if (tc != null && model.getDocoRef() != null) { 694 XhtmlNode img = tc.addTag("span").setAttribute("style", "float: right").addTag("a").setAttribute("title", "Legend for this format").setAttribute("href", model.getDocoRef()).addTag("img"); 695 img.setAttribute("alt", "doco").setAttribute("style", "background-color: inherit").setAttribute("src", model.getDocoImg()); 696 if (model.isActive()) { 697 img.setAttribute("onLoad", "fhirTableInit(this)"); 698 } 699 } 700 701 Counter counter = new Counter(); 702 for (Row r : model.getRows()) { 703 renderRow(table, r, 0, new ArrayList<Integer>(), imagePath, border, outputTracker, counter, model); 704 } 705 if (model.getDocoRef() != null) { 706 tr = table.addTag("tr"); 707 tc = tr.addTag("td"); 708 tc.setAttribute("class", "hierarchy"); 709 tc.setAttribute("colspan", Integer.toString(model.getTitles().size())); 710 tc.addTag("br"); 711 XhtmlNode a = tc.addTag("a").setAttribute("title", translate("sd.doco", "Legend for this format")).setAttribute("href", model.getDocoRef()); 712 if (model.getDocoImg() != null) 713 a.addTag("img").setAttribute("alt", "doco").setAttribute("style", "background-color: inherit").setAttribute("src", model.getDocoImg()); 714 a.addText(" "+translate("sd.doco", "Documentation for this format")); 715 } 716 return table; 717 } 718 719 720 private void renderRow(XhtmlNode table, Row r, int indent, List<Integer> indents, String imagePath, int border, Set<String> outputTracker, Counter counter, TableModel model) throws IOException { 721 counter.row(); 722 XhtmlNode tr = table.addTag("tr"); 723 String color = "white"; 724 if (r.getColor() != null) 725 color = r.getColor(); 726 else if (model.isAlternating() && counter.isOdd()) 727 color = BACKGROUND_ALT_COLOR; 728 729 tr.setAttribute("style", "border: " + border + "px #F0F0F0 solid; padding:0px; vertical-align: top; background-color: "+color+(r.getOpacity() == null ? "" : "; opacity: "+r.getOpacity())); 730 if (model.isActive()) { 731 tr.setAttribute("id", r.getId()); 732 } 733 boolean first = true; 734 for (Cell t : r.getCells()) { 735 renderCell(tr, t, "td", first ? r.getIcon() : null, first ? r.getHint() : null, first ? indents : null, !r.getSubRows().isEmpty(), first ? r.getAnchor() : null, color, r.getLineColor(), imagePath, border, outputTracker, model, r); 736 first = false; 737 } 738 table.addText("\r\n"); 739 740 for (int i = 0; i < r.getSubRows().size(); i++) { 741 Row c = r.getSubRows().get(i); 742 List<Integer> ind = new ArrayList<Integer>(); 743 ind.addAll(indents); 744 if (i == r.getSubRows().size() - 1) { 745 ind.add(r.getLineColor()*2); 746 } else { 747 ind.add(r.getLineColor()*2+1); 748 } 749 renderRow(table, c, indent+1, ind, imagePath, border, outputTracker, counter, model); 750 } 751 } 752 753 754 private XhtmlNode renderCell(XhtmlNode tr, Cell c, String name, String icon, String hint, List<Integer> indents, boolean hasChildren, String anchor, String color, int lineColor, String imagePath, int border, Set<String> outputTracker, TableModel table, Row row) throws IOException { 755 XhtmlNode tc = tr.addTag(name); 756 tc.setAttribute("class", "hierarchy"); 757 if (c.span > 1) { 758 tc.colspan(Integer.toString(c.span)); 759 } 760 if (c.getId() != null) { 761 tc.setAttribute("id", c.getId()); 762 } 763 764 if (indents != null) { 765 tc.addTag("img").setAttribute("src", srcFor(imagePath, "tbl_spacer.png")).setAttribute("style", "background-color: inherit").setAttribute("class", "hierarchy").setAttribute("alt", "."); 766 tc.setAttribute("style", "vertical-align: top; text-align : left; "+(c.cellStyle != null && c.cellStyle.contains("background-color") ? "" : "background-color: "+color+"; ")+"border: "+ border +"px #F0F0F0 solid; padding:0px 4px 0px 4px; white-space: nowrap; background-image: url("+imagePath+checkExists(indents, hasChildren, lineColor, outputTracker)+")"+(c.cellStyle != null ? ";"+c.cellStyle : "")); 767 for (int i = 0; i < indents.size()-1; i++) { 768 switch (indents.get(i)) { 769 case NEW_REGULAR: 770 case NEW_SLICER: 771 case NEW_SLICE: 772 tc.addTag("img").setAttribute("src", srcFor(imagePath, "tbl_blank.png")).setAttribute("style", "background-color: inherit").setAttribute("class", "hierarchy").setAttribute("alt", "."); 773 break; 774 case CONTINUE_REGULAR: 775 tc.addTag("img").setAttribute("src", srcFor(imagePath, "tbl_vline.png")).setAttribute("style", "background-color: inherit").setAttribute("class", "hierarchy").setAttribute("alt", "."); 776 break; 777 case CONTINUE_SLICER: 778 tc.addTag("img").setAttribute("src", srcFor(imagePath, "tbl_vline_slicer.png")).setAttribute("style", "background-color: inherit").setAttribute("class", "hierarchy").setAttribute("alt", "."); 779 break; 780 case CONTINUE_SLICE: 781 tc.addTag("img").setAttribute("src", srcFor(imagePath, "tbl_vline_slice.png")).setAttribute("style", "background-color: inherit").setAttribute("class", "hierarchy").setAttribute("alt", "."); 782 break; 783 default: 784 throw new Error("Unrecognized indent level: " + indents.get(i)); 785 } 786 } 787 if (!indents.isEmpty()) { 788 String sfx = table.isActive() && hasChildren ? "-open" : ""; 789 XhtmlNode img = tc.addTag("img"); 790 switch (indents.get(indents.size()-1)) { 791 case NEW_REGULAR: 792 img.setAttribute("src", srcFor(imagePath, "tbl_vjoin_end"+sfx+".png")).setAttribute("style", "background-color: inherit").setAttribute("class", "hierarchy").setAttribute("alt", "."); 793 break; 794 case NEW_SLICER: 795 img.setAttribute("src", srcFor(imagePath, "tbl_vjoin_end_slicer"+sfx+".png")).setAttribute("style", "background-color: inherit").setAttribute("class", "hierarchy").setAttribute("alt", "."); 796 break; 797 case NEW_SLICE: 798 img.setAttribute("src", srcFor(imagePath, "tbl_vjoin_end_slice"+sfx+".png")).setAttribute("style", "background-color: inherit").setAttribute("class", "hierarchy").setAttribute("alt", "."); 799 break; 800 case CONTINUE_REGULAR: 801 img.setAttribute("src", srcFor(imagePath, "tbl_vjoin"+sfx+".png")).setAttribute("style", "background-color: inherit").setAttribute("class", "hierarchy").setAttribute("alt", "."); 802 break; 803 case CONTINUE_SLICER: 804 img.setAttribute("src", srcFor(imagePath, "tbl_vjoin_slicer"+sfx+".png")).setAttribute("style", "background-color: inherit").setAttribute("class", "hierarchy").setAttribute("alt", "."); 805 break; 806 case CONTINUE_SLICE: 807 img.setAttribute("src", srcFor(imagePath, "tbl_vjoin_slice"+sfx+".png")).setAttribute("style", "background-color: inherit").setAttribute("class", "hierarchy").setAttribute("alt", "."); 808 break; 809 default: 810 throw new Error("Unrecognized indent level: " + indents.get(indents.size()-1)); 811 } 812 if (table.isActive() && hasChildren) { 813 img.setAttribute("onClick", "tableRowAction(this)"); 814 } 815 } 816 } 817 else 818 tc.setAttribute("style", "vertical-align: top; text-align : left; "+(c.cellStyle != null && c.cellStyle.contains("background-color") ? "" : "background-color: "+color+"; ")+"border: "+ border +"px #F0F0F0 solid; padding:0px 4px 0px 4px"+(c.cellStyle != null ? ";"+c.cellStyle : "")); 819 if (!Utilities.noString(icon)) { 820 XhtmlNode img = tc.addTag("img").setAttribute("alt", "icon").setAttribute("src", srcFor(imagePath, icon)).setAttribute("class", "hierarchy").setAttribute("style", "background-color: "+color+"; background-color: inherit").setAttribute("alt", "."); 821 if (hint != null) 822 img.setAttribute("title", hint); 823 tc.addText(" "); 824 } 825 for (Piece p : c.pieces) { 826 if (!Utilities.noString(p.getTag())) { 827 XhtmlNode tag = tc.addTag(p.getTag()); 828 if (p.attributes != null) 829 for (String n : p.attributes.keySet()) 830 tag.setAttribute(n, p.attributes.get(n)); 831 if (p.getHint() != null) 832 tag.setAttribute("title", p.getHint()); 833 addStyle(tag, p); 834 if (p.hasChildren()) { 835 tag.getChildNodes().addAll(p.getChildren()); 836 } 837 } else if (!Utilities.noString(p.getReference())) { 838 XhtmlNode a = addStyle(tc.addTag("a"), p); 839 a.setAttribute("href", p.getReference()); 840 if (!Utilities.noString(p.getHint())) 841 a.setAttribute("title", p.getHint()); 842 if (p.getText() != null) { 843 a.addText(p.getText()); 844 } else { 845 a.addChildren(p.getChildren()); 846 } 847 addStyle(a, p); 848 } else { 849 if (!Utilities.noString(p.getHint())) { 850 XhtmlNode s = addStyle(tc.addTag("span"), p); 851 s.setAttribute("title", p.getHint()); 852 s.addText(p.getText()); 853 } else if (p.getStyle() != null) { 854 XhtmlNode s = addStyle(tc.addTag("span"), p); 855 s.addText(p.getText()); 856 } else { 857 tc.addText(p.getText()); 858 } 859 if (p.hasChildren()) { 860 tc.getChildNodes().addAll(p.getChildren()); 861 } 862 } 863 } 864 if (makeTargets && !Utilities.noString(anchor)) 865 tc.addTag("a").setAttribute("name", nmTokenize(anchor)).addText(" "); 866 return tc; 867 } 868 869 870 private XhtmlNode addStyle(XhtmlNode node, Piece p) { 871 if (p.getStyle() != null) 872 node.setAttribute("style", p.getStyle()); 873 return node; 874 } 875 876 private String nmTokenize(String anchor) { 877 return anchor.replace("[", "_").replace("]", "_"); 878 } 879 880 private String srcFor(String corePrefix, String filename) throws IOException { 881 if (inLineGraphics) { 882 if (files.containsKey(filename)) 883 return files.get(filename); 884 StringBuilder b = new StringBuilder(); 885 b.append("data:image/png;base64,"); 886 byte[] bytes; 887 File file = new File(Utilities.path(dest, filename)); 888 if (!file.exists()) // because sometime this is called real early before the files exist. it will be built again later because of this 889 bytes = new byte[0]; 890 else 891 bytes = FileUtils.readFileToByteArray(file); 892 b.append(new String(Base64.encodeBase64(bytes))); 893// files.put(filename, b.toString()); 894 return b.toString(); 895 } else 896 return corePrefix+filename; 897 } 898 899 900 private void checkModel(TableModel model) throws FHIRException { 901 check(!model.getTitles().isEmpty(), "Must have titles"); 902 int tc = 0; 903 for (Cell c : model.getTitles()) { 904 check(c); 905 tc = tc + c.span; 906 } 907 int i = 0; 908 for (Row r : model.getRows()) { 909 check(r, "rows", tc, "", i, model.getRows().size()); 910 i++; 911 } 912 } 913 914 915 private void check(Cell c) throws FHIRException { 916 boolean hasText = false; 917 for (Piece p : c.pieces) 918 if (!Utilities.noString(p.getText())) 919 hasText = true; 920 check(hasText, "Title cells must have text"); 921 } 922 923 924 private void check(Row r, String string, int size, String path, int index, int total) throws FHIRException { 925 String id = Integer.toString(index)+"."; 926 if (total <= 26) { 927 char c = (char) ('a'+index); 928 id = Character.toString(c); 929 } 930 path = path + id; 931 r.setId(path); 932 int tc = 0; 933 for (Cell c : r.getCells()) { 934 tc = tc + c.span; 935 } 936 check(tc == size, "All rows must have the same number of columns as the titles ("+Integer.toString(size)+") but row "+path+" doesn't - it has "+tc+" ("+r.getCells().get(0).text()+"): "+r.getCells()); 937 int i = 0; 938 for (Row c : r.getSubRows()) { 939 check(c, "rows", size, path, i, r.getSubRows().size()); 940 i++; 941 } 942 } 943 944 945 private String checkExists(List<Integer> indents, boolean hasChildren, int lineColor, Set<String> outputTracker) throws IOException { 946 String filename = makeName(indents); 947 948 StringBuilder b = new StringBuilder(); 949 if (inLineGraphics) { 950 if (files.containsKey(filename)) 951 return files.get(filename); 952 ByteArrayOutputStream bytes = new ByteArrayOutputStream(); 953 genImage(indents, hasChildren, lineColor, bytes); 954 b.append("data:image/png;base64,"); 955 byte[] encodeBase64 = Base64.encodeBase64(bytes.toByteArray()); 956 b.append(new String(encodeBase64)); 957 files.put(filename, b.toString()); 958 return b.toString(); 959 } else { 960 b.append("tbl_bck"); 961 for (Integer i : indents) 962 b.append(Integer.toString(i)); 963 int indent = lineColor*2 + (hasChildren?1:0); 964 b.append(Integer.toString(indent)); 965 b.append(".png"); 966 String file = Utilities.path(dest, b.toString()); 967 if (!new File(file).exists()) { 968 File newFile = new File(file); 969 if (newFile.getParentFile() == null) { 970 throw new Error("No source directory provided. ("+file+")"); 971 } else { 972 newFile.getParentFile().mkdirs(); 973 } 974 newFile.createNewFile(); 975 FileOutputStream stream = new FileOutputStream(file); 976 genImage(indents, hasChildren, lineColor, stream); 977 if (outputTracker!=null) 978 outputTracker.add(file); 979 } 980 return b.toString(); 981 } 982 } 983 984 985 private void genImage(List<Integer> indents, boolean hasChildren, int lineColor, OutputStream stream) throws IOException { 986 BufferedImage bi = new BufferedImage(800, 2, BufferedImage.TYPE_INT_ARGB); 987 // i have no idea why this works to make these pixels transparent. It defies logic. 988 // But this combination of INT_ARGB and filling with grey magically worked when nothing else did. So it stays as is. 989 Color grey = new Color(99,99,99,0); 990 for (int i = 0; i < 800; i++) { 991 bi.setRGB(i, 0, grey.getRGB()); 992 bi.setRGB(i, 1, grey.getRGB()); 993 } 994 Color black = new Color(0, 0, 0); 995 Color green = new Color(14,209,69); 996 Color gold = new Color(212,168,21); 997 for (int i = 0; i < indents.size(); i++) { 998 int indent = indents.get(i).intValue(); 999 if (indent == CONTINUE_REGULAR) 1000 bi.setRGB(12+(i*16), 0, black.getRGB()); 1001 else if (indent == CONTINUE_SLICER) 1002 bi.setRGB(12+(i*16), 0, green.getRGB()); 1003 else if (indent == CONTINUE_SLICE) 1004 bi.setRGB(12+(i*16), 0, gold.getRGB()); 1005 } 1006 if (hasChildren) { 1007 if (lineColor==0) 1008 bi.setRGB(12+(indents.size()*16), 0, black.getRGB()); 1009 else if (lineColor==1) 1010 bi.setRGB(12+(indents.size()*16), 0, green.getRGB()); 1011 else if (lineColor==2) 1012 bi.setRGB(12+(indents.size()*16), 0, gold.getRGB()); 1013 } 1014 ImageIO.write(bi, "PNG", stream); 1015 } 1016 1017 private String makeName(List<Integer> indents) { 1018 StringBuilder b = new StringBuilder(); 1019 b.append("indents:"); 1020 for (Integer i : indents) 1021 b.append(Integer.toString(i)); 1022 return b.toString(); 1023 } 1024 1025 private void check(boolean check, String message) throws FHIRException { 1026 if (!check) 1027 throw new FHIRException(message); 1028 } 1029 1030 public void emptyRow(TableModel model, int cellCount) { 1031 Row r = new Row(); 1032 model.rows.add(r); 1033 for (int i = 0; i < cellCount; i++) { 1034 r.getCells().add(new Cell()); 1035 } 1036 } 1037}