1

I have a html markup stored in database like:

<table style='border: 1px solid black'>
    <tr>
        <td  style='border: 1px solid black'><strong>1</strong></td>
        <td  style='border: 1px solid black'><strong>2</strong></td>
    </tr>
    <tr>
        <td  style='border: 1px solid black'>Data</td>
        <td  style='border: 1px solid black'>Data</td>
    </tr>
</table>

The HTML is generated dynamically and stored in the DB as text. I have to render this table as is in Japersoft Studio.


Things I have tried:

1. Making a Text Field and making its markup as HTML. Problem: This works only for text formatting. Does not work with table tags.

2. Making a Generic Element and rendering the HTML there(Reference). Problem: The rendered HTML is takes the height and width of the Text Field.


Any help would be very much appreciated on how to achieve this. I am starting to doubt if this is even possible through Jaspersoft Studio.

I am using TIBCO Jaspersoft Studio 6.3.1 final.

Petter Friberg
  • 21,252
  • 9
  • 60
  • 109
A Rogue Otaku
  • 913
  • 10
  • 20

2 Answers2

3

Inspired by Jarvis and mostly for fun

As you have correctly noted you can't render html table in textField you would need to use the HtmlComponent <hc:html/> but this will create an image and you will not only have scaling problems but also issue as selection and searchability of text. Furthermore as Dave Jarvis has stated in their answer this in general is not a suitable way to pass data to jasper reports.

But who cares about all that lets render your html

  1. We need to convert the html to a JRDataSource, we will do this using Jsoup

    public class HtmlTableDataSource implements JRDataSource {
    
        private List<Elements> rows;
        private Iterator<Elements> iterator;
        private Elements currentRow;
    
        public HtmlTableDataSource(String html) {
            super();
            init(html);
        }
    
        private void init(String html) {
            this.rows = new ArrayList<>();
            Document doc = Jsoup.parse(html);
            Elements tables = doc.select("table"); 
            if (tables == null || tables.isEmpty()) {
               return;
            }
            // Get first table ignore others
            Element table = tables.get(0);
            //Get all rows
            Elements trs = table.select("tr");
            for (Element element : trs) {
                //add all of our columns to our list
                this.rows.add(element.select("td"));
            }
    
            this.iterator = this.rows.iterator();
        }
    
        @Override
        public Object getFieldValue(JRField field) throws JRException {
            if (field==null||currentRow==null){
                return null;
            }
            try {
                int col = Integer.parseInt(field.getName());
                if (col<currentRow.size()){
                    return currentRow.get(col).html();
                }
            } catch (NumberFormatException e) {
                throw new JRException("Using the HTMLTableDataSource, the field name need to be numbers starting from 0");
            }
            return null;
        }
    
        @Override
        public boolean next() throws JRException {
            if (this.iterator == null || !this.iterator.hasNext()) {
                return false;
            }
            this.currentRow = this.iterator.next();
            return true;
        }
    }
    
  2. Now lets use it in a report (you need the above class and Jsoup in classpath)

    <?xml version="1.0" encoding="UTF-8"?>
    <jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd" name="htmlTable" pageWidth="595" pageHeight="842" whenNoDataType="AllSectionsNoDetail" columnWidth="555" leftMargin="20" rightMargin="20" topMargin="20" bottomMargin="20" uuid="597c0716-df6b-42ec-a7c8-863eb1b7174a">
        <style name="Table_TD" mode="Opaque" backcolor="#FFFFFF">
            <box>
                <pen lineWidth="0.5" lineColor="#000000"/>
                <topPen lineWidth="0.5" lineColor="#000000"/>
                <leftPen lineWidth="0.5" lineColor="#000000"/>
                <bottomPen lineWidth="0.5" lineColor="#000000"/>
                <rightPen lineWidth="0.5" lineColor="#000000"/>
            </box>
        </style>
        <subDataset name="TableDataset" uuid="998ba41a-db15-454b-a081-bc8613899c31">
            <field name="0" class="java.lang.String"/>
            <field name="1" class="java.lang.String"/>
        </subDataset>
        <summary>
            <band height="30">
                <componentElement>
                    <reportElement x="0" y="0" width="550" height="30" uuid="5d0d5bcb-a094-4446-a5e9-09de629cefc7"/>
                    <jr:table xmlns:jr="http://jasperreports.sourceforge.net/jasperreports/components" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports/components http://jasperreports.sourceforge.net/xsd/components.xsd">
                        <datasetRun subDataset="TableDataset" uuid="eac86c28-fad2-433a-a02e-0dd419d9e135">
                            <dataSourceExpression><![CDATA[new my.package.HtmlTableDataSource("<table style='border: 1px solid black'><tr><td  style='border: 1px solid black'><b>1</b></td><td  style='border: 1px solid black'><b>2</b></td></tr><tr><td  style='border: 1px solid black'>Data</td><td  style='border: 1px solid black'>Data</td></tr></table>")]]></dataSourceExpression>
                        </datasetRun>
                        <jr:column width="180" uuid="86ce5c9c-d285-4a51-bf84-30fa9c55579f">
                            <jr:detailCell style="Table_TD" height="30">
                                <textField>
                                    <reportElement x="0" y="0" width="180" height="30" uuid="3acb437b-8b59-4e78-ac1b-4f096f960e89"/>
                                    <textElement textAlignment="Center" verticalAlignment="Middle" markup="html"/>
                                    <textFieldExpression><![CDATA[$F{0}]]></textFieldExpression>
                                </textField>
                            </jr:detailCell>
                        </jr:column>
                        <jr:column width="150" uuid="741517bf-5107-4b13-9d61-44209f266c6c">
                            <jr:detailCell style="Table_TD" height="30">
                                <textField>
                                    <reportElement x="0" y="0" width="150" height="30" uuid="1174a60c-8b9e-441b-a9ef-4340b0cd7b68"/>
                                    <textElement textAlignment="Center" verticalAlignment="Middle" markup="html"/>
                                    <textFieldExpression><![CDATA[$F{1}]]></textFieldExpression>
                                </textField>
                            </jr:detailCell>
                        </jr:column>
                    </jr:table>
                </componentElement>
            </band>
        </summary>
    </jasperReport>
    

The <dataSourceExpression><![CDATA[new my.package.HtmlTableDataSource("<table..")]]></dataSourceExpression> this will instance our datasource and jasper reports will loop over it calling the next() method and the getFieldValue(JRField field) when field is to be evaluate

Result

Output

Other considerations

My implementation of the JRDataSource is just quick and fun, naturally it can be vastly improved.

Dynamic columns, if only a maximum number and they are fixed size you could use a solution similar to this https://stackoverflow.com/a/36911788/5292302 if not switch to use crosstabs modifying the JRDataSource as needed.

Yes I changed <strong/> to <b/> this is another problem Why does html tags (s, strong) not work in jasper reports?, in this case you could also handle it within your datasource, hence use Jsoup to substitute.

Petter Friberg
  • 21,252
  • 9
  • 60
  • 109
  • Wow.. This is quiet something.. I understand storing html code directly in db is not advised but I'm just the reports dev, my hands are tied. Btw you happen to know how I could take care of alignment also inside the table? – A Rogue Otaku Aug 13 '19 at 15:59
  • 2
    @AmitDas Pure jasper-reports [don't support dynamic alignment in textfield](https://stackoverflow.com/a/34207280/5292302), if you use stuff like dynamic jaspers/reports however you can handle it or directly or by attaching conditional styles. If in database you can only have so many solutions you could do different subreports and call the right one (utility method in java to understand which), if the html is completely unkown you probably better of looking at the dynamic libraries. – Petter Friberg Aug 13 '19 at 16:08
2

Broadly, here's how you can solve this problem:

  1. Convert the HTML table into CSV format.
  2. Convert each CSV element from HTML to Markdown.
    • This replaces <strong>1</strong> with *1*.
  3. Convert the Markdown to simple HTML.
    • Use a high-performance engine such as flexmark.
    • This must convert *1* into <b>1</b>, which JasperReports can parse.
    • If JasperReports can render Markdown, then conversion would not be necessary.
  4. Create a CSV data source for the data.
  5. Ensure the fields are marked up as HTML text.
  6. Render the CSV data source.
    • If the number of columns is known, use JasperReports as usual.
    • If the number of columns can vary, use DynamicJasper.

There are other possibilities, depending on your expertise with XSLT.

However, this approach is ill-advised. A far simpler approach is to determine why HTML tables are being added into the database, rather than as structured, relational data. Change the process to enter the data into the database as relational data, then make your reports use the data without so many levels of translation.

In short, try to re-frame the problem, as it is seldom efficient or easy to coerce non-relational data into relational form suitable for processing by JasperReports.

See also: http://xyproblem.info/

Dave Jarvis
  • 30,436
  • 41
  • 178
  • 315