001package com.identityworksllc.iiq.common.table; 002 003import org.apache.commons.lang.StringEscapeUtils; 004import sailpoint.tools.GeneralException; 005import sailpoint.tools.Util; 006 007import java.util.ArrayList; 008import java.util.Arrays; 009import java.util.Collection; 010import java.util.Collections; 011import java.util.List; 012 013/** 014 * A class representing a single cell in an HTML table. Cells can have differing 015 * row and column spans, can have CSS classes or styles, and have content. The 016 * content can be either a list or a string, which will be rendered differently. 017 * 018 * You can also specify that string content is HTML, which will be inserted as-is 019 * without escaping. Be careful to not present an opportunity for script injection 020 * if you use this. 021 */ 022public class Cell extends Element implements StyleTarget { 023 024 /** 025 * Constructs a new Cell that can be passed to {@link Table#cell(Cell)} 026 * @param content The content of the cell 027 * @param options The cell options 028 * @return The constructed Cell 029 */ 030 public static Cell of(Object content, CellOption... options) throws GeneralException { 031 return Cell.of(content, options == null ? Collections.emptyList() : Arrays.asList(options)); 032 } 033 034 /** 035 * Constructs a new Cell that can be passed to {@link Table#cell(Cell)} 036 * @param content The content of the cell 037 * @param options The cell options 038 * @return The constructed Cell 039 */ 040 public static Cell of(Object content, List<CellOption> options) throws GeneralException { 041 if (!(content instanceof String || content instanceof Collection || content == null)) { 042 throw new IllegalArgumentException("Cell values must be either String or Collection types"); 043 } 044 045 Cell theCell = new Cell(); 046 if (content == null) { 047 theCell.setContent(""); 048 } else { 049 theCell.setContent(content); 050 } 051 if (options != null) { 052 for (CellOption option : options) { 053 option.accept(theCell); 054 } 055 } 056 return theCell; 057 } 058 059 /** 060 * The value of the 'colspan' attribute 061 */ 062 private int colspan; 063 064 /** 065 * The content of the cell, which must be a string or list 066 */ 067 private Object content; 068 069 /** 070 * If true, this will be a 'th' and not a 'td' 071 */ 072 private boolean header; 073 074 /** 075 * If true, the content will be rendered as-is, rather than being escaped. Be 076 * careful with this option, as it can allow HTML injection. 077 */ 078 private boolean html; 079 080 /** 081 * The value of the 'rowspan' attribute for this cell 082 */ 083 private int rowspan; 084 085 /*package*/ Cell() { 086 super(); 087 this.colspan = 1; 088 this.rowspan = 1; 089 this.html = false; 090 this.header = false; 091 } 092 093 /** 094 * Figures out how to append the string contents, whether HTML or not 095 * @param builder The builder being used to construct the string 096 * @param value The value 097 */ 098 private void appendStringContents(StringBuilder builder, String value) { 099 if (this.html) { 100 builder.append(value); 101 } else { 102 builder.append(StringEscapeUtils.escapeHtml(value)); 103 } 104 } 105 106 public int getColspan() { 107 return colspan; 108 } 109 110 public Object getContent() { 111 return content; 112 } 113 114 public int getRowspan() { 115 return rowspan; 116 } 117 118 public boolean isHeader() { 119 return header; 120 } 121 122 public boolean isHtml() { 123 return html; 124 } 125 126 /** 127 * Renders this cell to HTML, inserting it into the builder 128 */ 129 public void render(StringBuilder builder, Row parent) { 130 String tag = "td"; 131 if (this.isHeader() || parent.isHeader()) { 132 tag = "th"; 133 } 134 builder.append("<").append(tag); 135 if (!this.getCssClasses().isEmpty()) { 136 builder.append(" class=\"").append(getEscapedCssClassAttr()).append("\""); 137 } 138 if (Util.isNotNullOrEmpty(getStyle())) { 139 builder.append(" style=\"").append(getEscapedStyle()).append("\""); 140 } 141 if (this.colspan > 1) { 142 builder.append(" colspan=\"").append(colspan).append("\""); 143 } 144 if (this.rowspan > 1) { 145 builder.append(" rowspan=\"").append(rowspan).append("\""); 146 } 147 if (this.isHeader() || parent.isHeader()) { 148 if (parent.isHeader()) { 149 builder.append(" scope=\"col\""); 150 } else if (this.isHeader()) { 151 builder.append(" scope=\"row\""); 152 } 153 } 154 builder.append(">"); 155 156 if (this.content instanceof String) { 157 appendStringContents(builder, (String)this.content); 158 } else if (this.content instanceof Collection) { 159 for(Object o : (Collection<?>)this.content) { 160 if (o instanceof String) { 161 builder.append(StringEscapeUtils.escapeHtml((String)o)); 162 builder.append("<br/>"); 163 } 164 } 165 } 166 167 builder.append("</").append(tag).append(">"); 168 } 169 170 public void setColspan(int colspan) { 171 this.colspan = colspan; 172 } 173 174 public void setContent(Object content) { 175 this.content = content; 176 } 177 178 public void setHeader(boolean header) { 179 this.header = header; 180 } 181 182 public void setHtml(boolean html) { 183 this.html = html; 184 } 185 186 public void setRowspan(int rowspan) { 187 this.rowspan = rowspan; 188 } 189 190}