• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hazendaz / displaytag / 1753

12 Feb 2026 03:17AM UTC coverage: 77.321% (-0.01%) from 77.334%
1753

push

github

web-flow
Merge pull request #1102 from hazendaz/renovate/javax-support-logback-monorepo

Update dependency ch.qos.logback:logback-classic to v1.5.29 (javax-support)

1438 of 2003 branches covered (71.79%)

Branch coverage included in aggregate %.

4034 of 5074 relevant lines covered (79.5%)

0.8 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.84
/displaytag/src/main/java/org/displaytag/export/XmlTotalsWriter.java
1
/*
2
 * SPDX-License-Identifier: MIT
3
 * See LICENSE file for details.
4
 *
5
 * Copyright 2002-2026 Fabrizio Giustina, the Displaytag team
6
 */
7
package org.displaytag.export;
8

9
import java.util.Arrays;
10
import java.util.Collections;
11
import java.util.HashMap;
12
import java.util.Iterator;
13
import java.util.List;
14
import java.util.Map;
15
import java.util.regex.Matcher;
16
import java.util.regex.Pattern;
17

18
import org.apache.commons.lang3.StringUtils;
19
import org.displaytag.decorator.TableDecorator;
20
import org.displaytag.model.Column;
21
import org.displaytag.model.HeaderCell;
22
import org.displaytag.model.Row;
23
import org.displaytag.model.TableModel;
24
import org.displaytag.render.TableTotaler;
25
import org.displaytag.render.TableWriterAdapter;
26
import org.displaytag.util.HtmlAttributeMap;
27
import org.displaytag.util.MultipleHtmlAttribute;
28
import org.displaytag.util.TagConstants;
29

30
/**
31
 * Writes the table as an XML file, including any totals and grouping information. Used by the FOP export.
32
 *
33
 * @see FopExportView
34
 */
35

36
/*
37
 * Sample fragment <table> <header> <header-cell >AntColumn</header-cell> </header> <data> <subgroup grouped-by="0">
38
 * <row> <cell grouped="true">Ant</cell> </row> <subtotal
39
 */
40
public class XmlTotalsWriter extends TableWriterAdapter {
41

42
    /** The style pat. */
43
    Pattern stylePat = Pattern.compile("\\s*?([\\w\\-]+?)\\s*?:\\s*?([\\w\\-]+?)(?:;|$)");
1✔
44

45
    /** The xml. */
46
    protected StringBuilder xml = new StringBuilder();
1✔
47

48
    /** The current grouping value by group. */
49
    Map<Integer, String> currentGroupingValueByGroup = new HashMap<>();
1✔
50

51
    /** The group id. */
52
    Integer groupId;
53

54
    /** The current grouping level. */
55
    int currentGroupingLevel = 0;
1✔
56

57
    /** The max word length. */
58
    int maxWordLength = 15;
1✔
59

60
    /** The Constant NOOP. */
61
    public static final TableDecorator NOOP = new TableDecorator() {
1✔
62
        @Override
63
        public String displayGroupedValue(final String cellValue, final short groupingStatus, final int columnNumber) {
64
            return cellValue;
1✔
65
        }
66
    };
67

68
    /**
69
     * Instantiates a new xml totals writer.
70
     *
71
     * @param m
72
     *            the m
73
     */
74
    public XmlTotalsWriter(final TableModel m) {
1✔
75
        this.setModel(m);
1✔
76
    }
1✔
77

78
    /**
79
     * Sets the model.
80
     *
81
     * @param m
82
     *            the new model
83
     */
84
    public void setModel(final TableModel m) {
85
        m.setTableDecorator(XmlTotalsWriter.NOOP);
1✔
86
        if (m.getTotaler() == null || m.getTotaler() == TableTotaler.NULL) {
1!
87
            final TableTotaler tt = new TableTotaler();
×
88
            tt.init(m);
×
89
            m.setTotaler(tt);
×
90
        }
91
    }
1✔
92

93
    /**
94
     * <code>TableModel</code>.
95
     *
96
     * @param model
97
     *            the model
98
     *
99
     * @throws Exception
100
     *             the exception
101
     */
102

103
    @Override
104
    protected void writeTableOpener(final TableModel model) throws Exception {
105
        this.xml.append("<?xml version=\"1.0\"?>\n<table>\n"); //$NON-NLS-1$
1✔
106
    }
1✔
107

108
    /**
109
     * Write subgroup start.
110
     *
111
     * @param model
112
     *            the model
113
     */
114
    @Override
115
    protected void writeSubgroupStart(final TableModel model) {
116
        final TableTotaler tt = model.getTotaler();
1✔
117

118
        // for each newly opened subgroup we need to output the opener, in order;
119
        // so we need to know somehow which groups are new since we last wrote out openers; how about we track a list of
120
        // the
121
        // already opened groups, and ask the tt for a list of all known groups?
122
        for (final int i : tt.getOpenedColumns()) {
1✔
123
            this.xml.append("<subgroup grouped-by=\"").append(i).append("\">");
1✔
124

125
        }
1✔
126
    }
1✔
127

128
    /**
129
     * Write subgroup stop.
130
     *
131
     * @param model
132
     *            the model
133
     */
134
    @Override
135
    protected void writeSubgroupStop(final TableModel model) {
136
        final List<Integer> closed = model.getTotaler().getClosedColumns();
1✔
137
        if (!closed.isEmpty()) {
1✔
138
            // write subtotals
139
            this.writeSubtotals(model, closed);
1✔
140
        }
141
    }
1✔
142

143
    /**
144
     * Write subtotals.
145
     *
146
     * @param model
147
     *            the model
148
     * @param closedColumns
149
     *            the closed columns
150
     */
151
    protected void writeSubtotals(final TableModel model, final List<Integer> closedColumns) {
152
        final TableTotaler tt = model.getTotaler();
1✔
153
        Collections.reverse(closedColumns);
1✔
154
        for (final int i : closedColumns) {
1✔
155
            this.xml.append("<subtotal>\n");
1✔
156
            for (final HeaderCell cell : model.getHeaderCellList()) {
1✔
157
                if (cell.isTotaled()) {
1✔
158
                    this.xml.append("\t<subtotal-cell ");
1✔
159
                    final HtmlAttributeMap atts = cell.getHtmlAttributes();
1✔
160
                    this.writeAttributes(atts);
1✔
161
                    this.xml.append('>');
1✔
162
                    this.cdata(tt.formatTotal(cell, tt.getTotalForColumn(cell.getColumnNumber(), tt.asGroup(i))));
1✔
163
                    this.xml.append("</subtotal-cell>");
1✔
164
                } else {
1✔
165
                    this.xml.append("\t<subtotal-cell/>");
1✔
166
                }
167
            }
1✔
168
            this.xml.append("\n</subtotal>\n");
1✔
169
            this.writeExtraGroupInfo(model, i);
1✔
170
            this.xml.append("</subgroup>\n");
1✔
171
        }
1✔
172
    }
1✔
173

174
    /**
175
     * Write extra group info.
176
     *
177
     * @param model
178
     *            the model
179
     * @param groupColumn
180
     *            the group column
181
     */
182
    protected void writeExtraGroupInfo(final TableModel model, final int groupColumn) {
183
        // Not Implemented
184
    }
1✔
185

186
    /**
187
     * Write attributes.
188
     *
189
     * @param atts
190
     *            the atts
191
     */
192
    protected void writeAttributes(final HtmlAttributeMap atts) {
193
        if (atts != null) {
1!
194
            final String style = (String) atts.get(TagConstants.ATTRIBUTE_STYLE);
1✔
195
            if (StringUtils.isNotBlank(style)) {
1✔
196
                final Matcher m = this.stylePat.matcher(style);
1✔
197
                while (m.find()) {
1✔
198
                    this.xml.append(m.group(1));
1✔
199
                    this.xml.append("=\"");
1✔
200
                    this.xml.append(m.group(2));
1✔
201
                    this.xml.append("\" ");
1✔
202
                }
203
            }
204
            final MultipleHtmlAttribute cssClass = (MultipleHtmlAttribute) atts.get(TagConstants.ATTRIBUTE_CLASS);
1✔
205
            if (cssClass != null && !cssClass.isEmpty()) {
1!
206
                this.xml.append(" class");
1✔
207
                this.xml.append("=\"");
1✔
208
                this.xml.append(cssClass.toString());
1✔
209
                this.xml.append("\"");
1✔
210
            }
211
        }
212

213
    }
1✔
214

215
    /**
216
     * Write column opener.
217
     *
218
     * @param column
219
     *            the column
220
     *
221
     * @throws Exception
222
     *             the exception
223
     */
224
    @Override
225
    protected void writeColumnOpener(final Column column) throws Exception {
226
        final boolean grouped = column.getHeaderCell().getGroup() >= this.currentGroupingLevel;
1✔
227
        String attr = "";
1✔
228
        if (grouped) {
1✔
229
            attr = " grouped=\"true\" ";
1✔
230
        }
231
        this.xml.append("\t<cell ");
1✔
232
        this.xml.append(attr);
1✔
233
        final HtmlAttributeMap atts = column.getHeaderCell().getHtmlAttributes();
1✔
234
        this.writeAttributes(atts);
1✔
235
        this.xml.append(">");
1✔
236
    }
1✔
237

238
    /**
239
     * Write column closer.
240
     *
241
     * @param column
242
     *            the column
243
     *
244
     * @throws Exception
245
     *             the exception
246
     */
247
    @Override
248
    protected void writeColumnCloser(final Column column) throws Exception {
249
        this.xml.append("</cell>\n");
1✔
250
    }
1✔
251

252
    /**
253
     * Write table header.
254
     *
255
     * @param model
256
     *            the model
257
     *
258
     * @throws Exception
259
     *             the exception
260
     */
261
    @Override
262
    protected void writeTableHeader(final TableModel model) throws Exception {
263
        final Iterator<HeaderCell> iterator = model.getHeaderCellList().iterator();
1✔
264

265
        this.xml.append("<header>\n");
1✔
266
        this.xml.append("\n");
1✔
267
        while (iterator.hasNext()) {
1✔
268
            // get the header cell
269
            final HeaderCell headerCell = iterator.next();
1✔
270
            this.xml.append("<header-cell>");
1✔
271
            this.cdata(headerCell.getTitle());
1✔
272
            this.xml.append("</header-cell>\n");
1✔
273
        }
1✔
274
        this.xml.append("</header>\n");
1✔
275
        this.xml.append("<data>");
1✔
276
        this.xml.append("<subgroup grouped-by=\"" + TableTotaler.WHOLE_TABLE + "\">");
1✔
277
    }
1✔
278

279
    // just use the hyphenate support from fop -- http://xmlgraphics.apache.org/fop/1.0/hyphenation.html
280

281
    /**
282
     * Cdata.
283
     *
284
     * @param str
285
     *            the str
286
     */
287
    protected void cdata(final Object str) {
288
        this.xml.append("<![CDATA[");
1✔
289
        final String defStr = StringUtils.defaultString("" + str);
1✔
290
        this.xml.append(defStr);
1✔
291
        this.xml.append("]]>");
1✔
292
    }
1✔
293

294
    /**
295
     * Write decorated table finish.
296
     *
297
     * @param model
298
     *            the model
299
     */
300
    @Override
301
    protected void writeDecoratedTableFinish(final TableModel model) {
302
        model.getTableDecorator().finish();
1✔
303
    }
1✔
304

305
    /**
306
     * Write decorated row start.
307
     *
308
     * @param model
309
     *            the model
310
     *
311
     * @see org.displaytag.render.TableWriterTemplate#writeDecoratedRowStart(org.displaytag.model.TableModel)
312
     */
313
    @Override
314
    protected void writeDecoratedRowStart(final TableModel model) {
315
        this.xml.append(StringUtils.defaultString(model.getTableDecorator().startRow()));
1✔
316
    }
1✔
317

318
    /**
319
     * Write table closer.
320
     *
321
     * @param model
322
     *            the model
323
     *
324
     * @throws Exception
325
     *             the exception
326
     */
327
    @Override
328
    protected void writeTableCloser(final TableModel model) throws Exception {
329
        this.xml.append("</table>"); //$NON-NLS-1$
1✔
330
    }
1✔
331

332
    /**
333
     * Write decorated row finish.
334
     *
335
     * @param model
336
     *            the model
337
     */
338
    @Override
339
    protected void writeDecoratedRowFinish(final TableModel model) {
340
        this.xml.append(StringUtils.defaultString(model.getTableDecorator().finishRow()));
1✔
341
    }
1✔
342

343
    /**
344
     * Write row opener.
345
     *
346
     * @param row
347
     *            the row
348
     *
349
     * @throws Exception
350
     *             the exception
351
     */
352
    @Override
353
    protected void writeRowOpener(final Row row) throws Exception {
354
        this.xml.append("\n<row>\n"); //$NON-NLS-1$
1✔
355
    }
1✔
356

357
    /**
358
     * Write column value.
359
     *
360
     * @param value
361
     *            the value
362
     * @param column
363
     *            the column
364
     *
365
     * @throws Exception
366
     *             the exception
367
     */
368
    @Override
369
    protected void writeColumnValue(final Object value, final Column column) throws Exception {
370
        final Object rawValue = column.getValue(true);
1✔
371
        this.cdata(rawValue);
1✔
372
    }
1✔
373

374
    /**
375
     * Write row with no columns.
376
     *
377
     * @param value
378
     *            the value
379
     *
380
     * @throws Exception
381
     *             the exception
382
     */
383
    @Override
384
    protected void writeRowWithNoColumns(final String value) throws Exception {
385
        this.xml.append("<row/>\n"); //$NON-NLS-1$
×
386
    }
×
387

388
    /**
389
     * Write row closer.
390
     *
391
     * @param row
392
     *            the row
393
     *
394
     * @throws Exception
395
     *             the exception
396
     */
397
    @Override
398
    protected void writeRowCloser(final Row row) throws Exception {
399
        this.xml.append("</row>\n"); //$NON-NLS-1$
1✔
400
    }
1✔
401

402
    /**
403
     * Write table body closer.
404
     *
405
     * @param model
406
     *            the model
407
     */
408
    @Override
409
    protected void writeTableBodyCloser(final TableModel model) {
410
        this.xml.append("\n<!-- grand totals -->\n");
1✔
411
        this.writeSubtotals(model, Arrays.asList(TableTotaler.WHOLE_TABLE));
1✔
412
        this.xml.append("</data>"); //$NON-NLS-1$
1✔
413
    }
1✔
414

415
    /**
416
     * Gets the xml.
417
     *
418
     * @return the xml
419
     */
420
    public String getXml() {
421

422
        return this.xml.toString();
1✔
423
    }
424
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc