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