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