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}