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