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 private boolean makeTargets; 388 389 /** 390 * There are circumstances where the table has to present in the absence of a stable supporting infrastructure. 391 * and the file paths cannot be guaranteed. For these reasons, you can tell the builder to inline all the graphics 392 * (all the styles are inlined anyway, since the table fbuiler has even less control over the styling 393 * 394 */ 395 private boolean inLineGraphics; 396 397 398 public HierarchicalTableGenerator() { 399 super(); 400 } 401 402 public HierarchicalTableGenerator(String dest, boolean inlineGraphics) { 403 super(); 404 this.dest = dest; 405 this.inLineGraphics = inlineGraphics; 406 this.makeTargets = true; 407 } 408 409 public HierarchicalTableGenerator(String dest, boolean inlineGraphics, boolean makeTargets) { 410 super(); 411 this.dest = dest; 412 this.inLineGraphics = inlineGraphics; 413 this.makeTargets = makeTargets; 414 } 415 416 public TableModel initNormalTable(String prefix, boolean isLogical) { 417 TableModel model = new TableModel(); 418 419 model.setDocoImg(prefix+"help16.png"); 420 model.setDocoRef(prefix+"formats.html#table"); 421 model.getTitles().add(new Title(null, model.getDocoRef(), translate("sd.head", "Name"), translate("sd.hint", "The logical name of the element"), null, 0)); 422 model.getTitles().add(new Title(null, model.getDocoRef(), translate("sd.head", "Flags"), translate("sd.hint", "Information about the use of the element"), null, 0)); 423 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)); 424 model.getTitles().add(new Title(null, model.getDocoRef(), translate("sd.head", "Type"), translate("sd.hint", "Reference to the type of the element"), null, 100)); 425 model.getTitles().add(new Title(null, model.getDocoRef(), translate("sd.head", "Description & Constraints"), translate("sd.hint", "Additional information about the element"), null, 0)); 426 if (isLogical) { 427 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)); 428 } 429 return model; 430 } 431 432 433 public TableModel initGridTable(String prefix) { 434 TableModel model = new TableModel(); 435 436 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)); 437 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)); 438 model.getTitles().add(new Title(null, model.getDocoRef(), translate("sd.head", "Type"), translate("sd.hint", "Reference to the type of the element"), null, 100)); 439 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)); 440 return model; 441 } 442 443 public XhtmlNode generate(TableModel model, String imagePath, int border, Set<String> outputTracker) throws IOException, FHIRException { 444 checkModel(model); 445 XhtmlNode table = new XhtmlNode(NodeType.Element, "table").setAttribute("border", Integer.toString(border)).setAttribute("cellspacing", "0").setAttribute("cellpadding", "0"); 446 table.setAttribute("style", "border: " + border + "px #F0F0F0 solid; font-size: 11px; font-family: verdana; vertical-align: top;"); 447 XhtmlNode tr = table.addTag("tr"); 448 tr.setAttribute("style", "border: " + Integer.toString(1 + border) + "px #F0F0F0 solid; font-size: 11px; font-family: verdana; vertical-align: top;"); 449 XhtmlNode tc = null; 450 for (Title t : model.getTitles()) { 451 tc = renderCell(tr, t, "th", null, null, null, false, null, "white", 0, imagePath, border, outputTracker); 452 if (t.width != 0) 453 tc.setAttribute("style", "width: "+Integer.toString(t.width)+"px"); 454 } 455 if (tc != null && model.getDocoRef() != null) 456 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()); 457 458 for (Row r : model.getRows()) { 459 renderRow(table, r, 0, new ArrayList<Integer>(), imagePath, border, outputTracker); 460 } 461 if (model.getDocoRef() != null) { 462 tr = table.addTag("tr"); 463 tc = tr.addTag("td"); 464 tc.setAttribute("class", "hierarchy"); 465 tc.setAttribute("colspan", Integer.toString(model.getTitles().size())); 466 tc.addTag("br"); 467 XhtmlNode a = tc.addTag("a").setAttribute("title", translate("sd.doco", "Legend for this format")).setAttribute("href", model.getDocoRef()); 468 if (model.getDocoImg() != null) 469 a.addTag("img").setAttribute("alt", "doco").setAttribute("style", "background-color: inherit").setAttribute("src", model.getDocoImg()); 470 a.addText(" "+translate("sd.doco", "Documentation for this format")); 471 } 472 return table; 473 } 474 475 476 private void renderRow(XhtmlNode table, Row r, int indent, List<Integer> indents, String imagePath, int border, Set<String> outputTracker) throws IOException { 477 XhtmlNode tr = table.addTag("tr"); 478 String color = "white"; 479 if (r.getColor() != null) 480 color = r.getColor(); 481 tr.setAttribute("style", "border: " + border + "px #F0F0F0 solid; padding:0px; vertical-align: top; background-color: "+color+";"); 482 boolean first = true; 483 for (Cell t : r.getCells()) { 484 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); 485 first = false; 486 } 487 table.addText("\r\n"); 488 489 for (int i = 0; i < r.getSubRows().size(); i++) { 490 Row c = r.getSubRows().get(i); 491 List<Integer> ind = new ArrayList<Integer>(); 492 ind.addAll(indents); 493 if (i == r.getSubRows().size() - 1) { 494 ind.add(r.getLineColor()*2); 495 } else { 496 ind.add(r.getLineColor()*2+1); 497 } 498 renderRow(table, c, indent+1, ind, imagePath, border, outputTracker); 499 } 500 } 501 502 503 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 { 504 XhtmlNode tc = tr.addTag(name); 505 tc.setAttribute("class", "hierarchy"); 506 if (indents != null) { 507 tc.addTag("img").setAttribute("src", srcFor(imagePath, "tbl_spacer.png")).setAttribute("style", "background-color: inherit").setAttribute("class", "hierarchy").setAttribute("alt", "."); 508 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)+")"); 509 for (int i = 0; i < indents.size()-1; i++) { 510 switch (indents.get(i)) { 511 case NEW_REGULAR: 512 case NEW_SLICER: 513 case NEW_SLICE: 514 tc.addTag("img").setAttribute("src", srcFor(imagePath, "tbl_blank.png")).setAttribute("style", "background-color: inherit").setAttribute("class", "hierarchy").setAttribute("alt", "."); 515 break; 516 case CONTINUE_REGULAR: 517 tc.addTag("img").setAttribute("src", srcFor(imagePath, "tbl_vline.png")).setAttribute("style", "background-color: inherit").setAttribute("class", "hierarchy").setAttribute("alt", "."); 518 break; 519 case CONTINUE_SLICER: 520 tc.addTag("img").setAttribute("src", srcFor(imagePath, "tbl_vline_slicer.png")).setAttribute("style", "background-color: inherit").setAttribute("class", "hierarchy").setAttribute("alt", "."); 521 break; 522 case CONTINUE_SLICE: 523 tc.addTag("img").setAttribute("src", srcFor(imagePath, "tbl_vline_slice.png")).setAttribute("style", "background-color: inherit").setAttribute("class", "hierarchy").setAttribute("alt", "."); 524 break; 525 default: 526 throw new Error("Unrecognized indent level: " + indents.get(i)); 527 } 528 } 529 if (!indents.isEmpty()) 530 switch (indents.get(indents.size()-1)) { 531 case NEW_REGULAR: 532 tc.addTag("img").setAttribute("src", srcFor(imagePath, "tbl_vjoin_end.png")).setAttribute("style", "background-color: inherit").setAttribute("class", "hierarchy").setAttribute("alt", "."); 533 break; 534 case NEW_SLICER: 535 tc.addTag("img").setAttribute("src", srcFor(imagePath, "tbl_vjoin_end_slicer.png")).setAttribute("style", "background-color: inherit").setAttribute("class", "hierarchy").setAttribute("alt", "."); 536 break; 537 case NEW_SLICE: 538 tc.addTag("img").setAttribute("src", srcFor(imagePath, "tbl_vjoin_end_slice.png")).setAttribute("style", "background-color: inherit").setAttribute("class", "hierarchy").setAttribute("alt", "."); 539 break; 540 case CONTINUE_REGULAR: 541 tc.addTag("img").setAttribute("src", srcFor(imagePath, "tbl_vjoin.png")).setAttribute("style", "background-color: inherit").setAttribute("class", "hierarchy").setAttribute("alt", "."); 542 break; 543 case CONTINUE_SLICER: 544 tc.addTag("img").setAttribute("src", srcFor(imagePath, "tbl_vjoin_slicer.png")).setAttribute("style", "background-color: inherit").setAttribute("class", "hierarchy").setAttribute("alt", "."); 545 break; 546 case CONTINUE_SLICE: 547 tc.addTag("img").setAttribute("src", srcFor(imagePath, "tbl_vjoin_slice.png")).setAttribute("style", "background-color: inherit").setAttribute("class", "hierarchy").setAttribute("alt", "."); 548 break; 549 default: 550 throw new Error("Unrecognized indent level: " + indents.get(indents.size()-1)); 551 } 552 } 553 else 554 tc.setAttribute("style", "vertical-align: top; text-align : left; background-color: "+color+"; border: "+ border +"px #F0F0F0 solid; padding:0px 4px 0px 4px"); 555 if (!Utilities.noString(icon)) { 556 XhtmlNode img = tc.addTag("img").setAttribute("src", srcFor(imagePath, icon)).setAttribute("class", "hierarchy").setAttribute("style", "background-color: "+color+"; background-color: inherit").setAttribute("alt", "."); 557 if (hint != null) 558 img.setAttribute("title", hint); 559 tc.addText(" "); 560 } 561 for (Piece p : c.pieces) { 562 if (!Utilities.noString(p.getTag())) { 563 XhtmlNode tag = tc.addTag(p.getTag()); 564 if (p.attributes != null) 565 for (String n : p.attributes.keySet()) 566 tag.setAttribute(n, p.attributes.get(n)); 567 if (p.getHint() != null) 568 tag.setAttribute("title", p.getHint()); 569 addStyle(tag, p); 570 if (p.hasChildren()) 571 tag.getChildNodes().addAll(p.getChildren()); 572 } else if (!Utilities.noString(p.getReference())) { 573 XhtmlNode a = addStyle(tc.addTag("a"), p); 574 a.setAttribute("href", p.getReference()); 575 if (!Utilities.noString(p.getHint())) 576 a.setAttribute("title", p.getHint()); 577 a.addText(p.getText()); 578 addStyle(a, p); 579 } else { 580 if (!Utilities.noString(p.getHint())) { 581 XhtmlNode s = addStyle(tc.addTag("span"), p); 582 s.setAttribute("title", p.getHint()); 583 s.addText(p.getText()); 584 } else if (p.getStyle() != null) { 585 XhtmlNode s = addStyle(tc.addTag("span"), p); 586 s.addText(p.getText()); 587 } else 588 tc.addText(p.getText()); 589 } 590 } 591 if (makeTargets && !Utilities.noString(anchor)) 592 tc.addTag("a").setAttribute("name", nmTokenize(anchor)).addText(" "); 593 return tc; 594 } 595 596 597 private XhtmlNode addStyle(XhtmlNode node, Piece p) { 598 if (p.getStyle() != null) 599 node.setAttribute("style", p.getStyle()); 600 return node; 601 } 602 603 private String nmTokenize(String anchor) { 604 return anchor.replace("[", "_").replace("]", "_"); 605 } 606 607 private String srcFor(String corePrefix, String filename) throws IOException { 608 if (inLineGraphics) { 609 if (files.containsKey(filename)) 610 return files.get(filename); 611 StringBuilder b = new StringBuilder(); 612 b.append("data: image/png;base64,"); 613 byte[] bytes; 614 File file = new File(Utilities.path(dest, filename)); 615 if (!file.exists()) // because sometime this is called real early before the files exist. it will be built again later because of this 616 bytes = new byte[0]; 617 else 618 bytes = FileUtils.readFileToByteArray(file); 619 b.append(new String(Base64.encodeBase64(bytes))); 620// files.put(filename, b.toString()); 621 return b.toString(); 622 } else 623 return corePrefix+filename; 624 } 625 626 627 private void checkModel(TableModel model) throws FHIRException { 628 check(!model.getRows().isEmpty(), "Must have rows"); 629 check(!model.getTitles().isEmpty(), "Must have titles"); 630 for (Cell c : model.getTitles()) 631 check(c); 632 int i = 0; 633 for (Row r : model.getRows()) { 634 check(r, "rows", model.getTitles().size(), Integer.toString(i)); 635 i++; 636 } 637 } 638 639 640 private void check(Cell c) throws FHIRException { 641 boolean hasText = false; 642 for (Piece p : c.pieces) 643 if (!Utilities.noString(p.getText())) 644 hasText = true; 645 check(hasText, "Title cells must have text"); 646 } 647 648 649 private void check(Row r, String string, int size, String path) throws FHIRException { 650 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()); 651 int i = 0; 652 for (Row c : r.getSubRows()) { 653 check(c, "rows", size, path+"."+Integer.toString(i)); 654 i++; 655 } 656 } 657 658 659 private String checkExists(List<Integer> indents, boolean hasChildren, int lineColor, Set<String> outputTracker) throws IOException { 660 String filename = makeName(indents); 661 662 StringBuilder b = new StringBuilder(); 663 if (inLineGraphics) { 664 if (files.containsKey(filename)) 665 return files.get(filename); 666 ByteArrayOutputStream bytes = new ByteArrayOutputStream(); 667 genImage(indents, hasChildren, lineColor, bytes); 668 b.append("data: image/png;base64,"); 669 byte[] encodeBase64 = Base64.encodeBase64(bytes.toByteArray()); 670 b.append(new String(encodeBase64)); 671 files.put(filename, b.toString()); 672 return b.toString(); 673 } else { 674 b.append("tbl_bck"); 675 for (Integer i : indents) 676 b.append(Integer.toString(i)); 677 int indent = lineColor*2 + (hasChildren?1:0); 678 b.append(Integer.toString(indent)); 679 b.append(".png"); 680 String file = Utilities.path(dest, b.toString()); 681 if (!new File(file).exists()) { 682 FileOutputStream stream = new FileOutputStream(file); 683 genImage(indents, hasChildren, lineColor, stream); 684 if (outputTracker!=null) 685 outputTracker.add(file); 686 } 687 return b.toString(); 688 } 689 } 690 691 692 private void genImage(List<Integer> indents, boolean hasChildren, int lineColor, OutputStream stream) throws IOException { 693 BufferedImage bi = new BufferedImage(800, 2, BufferedImage.TYPE_INT_ARGB); 694 // i have no idea why this works to make these pixels transparent. It defies logic. 695 // But this combination of INT_ARGB and filling with grey magically worked when nothing else did. So it stays as is. 696 Color grey = new Color(99,99,99,0); 697 for (int i = 0; i < 800; i++) { 698 bi.setRGB(i, 0, grey.getRGB()); 699 bi.setRGB(i, 1, grey.getRGB()); 700 } 701 Color black = new Color(0, 0, 0); 702 Color green = new Color(14,209,69); 703 Color gold = new Color(212,168,21); 704 for (int i = 0; i < indents.size(); i++) { 705 int indent = indents.get(i).intValue(); 706 if (indent == CONTINUE_REGULAR) 707 bi.setRGB(12+(i*16), 0, black.getRGB()); 708 else if (indent == CONTINUE_SLICER) 709 bi.setRGB(12+(i*16), 0, green.getRGB()); 710 else if (indent == CONTINUE_SLICE) 711 bi.setRGB(12+(i*16), 0, gold.getRGB()); 712 } 713 if (hasChildren) { 714 if (lineColor==0) 715 bi.setRGB(12+(indents.size()*16), 0, black.getRGB()); 716 else if (lineColor==1) 717 bi.setRGB(12+(indents.size()*16), 0, green.getRGB()); 718 else if (lineColor==2) 719 bi.setRGB(12+(indents.size()*16), 0, gold.getRGB()); 720 } 721 ImageIO.write(bi, "PNG", stream); 722 } 723 724 private String makeName(List<Integer> indents) { 725 StringBuilder b = new StringBuilder(); 726 b.append("indents:"); 727 for (Integer i : indents) 728 b.append(Integer.toString(i)); 729 return b.toString(); 730 } 731 732 private void check(boolean check, String message) throws FHIRException { 733 if (!check) 734 throw new FHIRException(message); 735 } 736}