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

hazendaz / httpunit / 636

05 Dec 2025 03:27AM UTC coverage: 80.509%. Remained the same
636

push

github

hazendaz
Cleanup more old since tags

you guessed it, at this point going to jautodoc the rest so the warnings on builds go away ;)

3213 of 4105 branches covered (78.27%)

Branch coverage included in aggregate %.

8249 of 10132 relevant lines covered (81.42%)

0.81 hits per line

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

93.2
/src/main/java/com/meterware/httpunit/WebTable.java
1
/*
2
 * MIT License
3
 *
4
 * Copyright 2011-2025 Russell Gold
5
 *
6
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
7
 * documentation files (the "Software"), to deal in the Software without restriction, including without limitation
8
 * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
9
 * to permit persons to whom the Software is furnished to do so, subject to the following conditions:
10
 *
11
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions
12
 * of the Software.
13
 *
14
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
15
 * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
17
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
18
 * DEALINGS IN THE SOFTWARE.
19
 */
20
package com.meterware.httpunit;
21

22
import com.meterware.httpunit.scripting.ScriptableDelegate;
23

24
import java.net.URL;
25
import java.util.ArrayList;
26
import java.util.Enumeration;
27
import java.util.Hashtable;
28

29
import org.w3c.dom.Element;
30
import org.w3c.dom.Node;
31
import org.w3c.dom.html.HTMLTableCellElement;
32
import org.w3c.dom.html.HTMLTableRowElement;
33

34
/**
35
 * This class represents a table in an HTML page.
36
 **/
37
public class WebTable extends HTMLElementBase {
38

39
    /** Predicate to match the complete text of a table's first non-blank cell. **/
40
    public static final HTMLElementPredicate MATCH_FIRST_NONBLANK_CELL;
41

42
    /** Predicate to match a prefix of a table's first non-blank cell. **/
43
    public static final HTMLElementPredicate MATCH_FIRST_NONBLANK_CELL_PREFIX;
44

45
    /** Predicate to match a table's summary attribute. **/
46
    public static final HTMLElementPredicate MATCH_SUMMARY;
47

48
    /** Predicate to match a table's ID. **/
49
    public static final HTMLElementPredicate MATCH_ID;
50

51
    /**
52
     * Returns the number of rows in the table.
53
     **/
54
    public int getRowCount() {
55
        return getCells().length;
1✔
56
    }
57

58
    private TableCell[][] getCells() {
59
        if (_cells == null) {
1✔
60
            readTable();
1✔
61
        }
62
        return _cells;
1✔
63

64
    }
65

66
    /**
67
     * Returns the number of columns in the table.
68
     **/
69
    public int getColumnCount() {
70
        if (getCells().length == 0) {
1!
71
            return 0;
×
72
        }
73
        return getCells()[0].length;
1✔
74
    }
75

76
    /**
77
     * Returns the contents of the specified table cell as text. The row and column numbers are zero-based.
78
     *
79
     * @throws IndexOutOfBoundsException
80
     *             if the specified cell numbers are not valid
81
     **/
82
    public String getCellAsText(int row, int column) {
83
        TableCell cell = getTableCell(row, column);
1✔
84
        return cell == null ? "" : cell.getText();
1✔
85
    }
86

87
    /**
88
     * Returns the contents of the specified table cell as text. The row and column numbers are zero-based.
89
     *
90
     * @throws IndexOutOfBoundsException
91
     *             if the specified cell numbers are not valid
92
     **/
93
    public TableCell getTableCell(int row, int column) {
94
        return getCells()[row][column];
1✔
95
    }
96

97
    /**
98
     * Returns the contents of the specified table cell with a given ID
99
     *
100
     * @return TableCell with given ID or null if ID is not found.
101
     **/
102
    public TableCell getTableCellWithID(String id) {
103
        for (int i = 0; i < getRowCount(); i++) {
1✔
104
            for (int j = 0; j < getColumnCount(); j++) {
1✔
105
                final TableCell tableCell = getCells()[i][j];
1✔
106
                if (tableCell != null && tableCell.getID().equals(id)) {
1✔
107
                    return tableCell;
1✔
108
                }
109
            }
110
        }
111
        return null;
1✔
112
    }
113

114
    /**
115
     * Removes all rows and all columns from this table which have no visible text in them. patch [ 1117822 ] Patch for
116
     * purgeEmptyCells() problem by Glen Stampoultzis
117
     **/
118
    public void purgeEmptyCells() {
119
        int numRowsWithText = 0;
1✔
120
        int numColumnsWithText = 0;
1✔
121
        boolean[] rowHasText = new boolean[getRowCount()];
1✔
122
        boolean[] columnHasText = new boolean[getColumnCount()];
1✔
123
        Hashtable spanningCells = new Hashtable<>();
1✔
124

125
        // look for rows and columns with any text in a non-spanning cell
126
        for (int row = 0; row < rowHasText.length; row++) {
1✔
127
            for (int col = 0; col < columnHasText.length; col++) {
1✔
128
                if (getCellAsText(row, col).trim().isEmpty()) {
1✔
129
                    continue;
1✔
130
                }
131
                if (getTableCell(row, col).getColSpan() == 1 && getTableCell(row, col).getRowSpan() == 1) {
1✔
132
                    if (!rowHasText[row]) {
1✔
133
                        numRowsWithText++;
1✔
134
                    }
135
                    if (!columnHasText[col]) {
1✔
136
                        numColumnsWithText++;
1✔
137
                    }
138
                    rowHasText[row] = columnHasText[col] = true;
1✔
139
                } else // gstamp: altered the original code to deal with two issues:
140
                       // Firstly only the coordinates of the first spanning cell
141
                       // are added to the Map. The old code was using the last
142
                       // set of coordinates.
143
                       // Secondly I mark the starting coordinates as containing
144
                       // text which keeps the next section of code from removing
145
                       // the starting row/column.
146
                if (!spanningCells.containsKey(getTableCell(row, col))
1✔
147
                        && spanningCells.get(getTableCell(row, col)) == null) {
1!
148
                    if (!rowHasText[row]) {
1✔
149
                        numRowsWithText++;
1✔
150
                    }
151
                    if (!columnHasText[col]) {
1✔
152
                        numColumnsWithText++;
1✔
153
                    }
154
                    rowHasText[row] = columnHasText[col] = true;
1✔
155
                    spanningCells.put(getTableCell(row, col), new int[] { row, col });
1✔
156
                }
157
            }
158
        }
159

160
        // look for requirements to keep spanning cells: special processing is needed if either:
161
        // none of its rows already have text, or none of its columns already have text.
162
        for (Enumeration e = spanningCells.keys(); e.hasMoreElements();) {
1✔
163
            TableCell cell = (TableCell) e.nextElement();
1✔
164
            int[] coords = (int[]) spanningCells.get(cell);
1✔
165
            boolean neededInRow = true;
1✔
166
            boolean neededInCol = true;
1✔
167
            for (int i = coords[0]; neededInRow && i < rowHasText.length && i < coords[0] + cell.getRowSpan(); i++) {
1!
168
                neededInRow = !rowHasText[i];
1!
169
            }
170
            for (int j = coords[1]; neededInCol && j < columnHasText.length && j < coords[1] + cell.getColSpan(); j++) {
1!
171
                neededInCol = !columnHasText[j];
1!
172
            }
173
            if (neededInRow) {
1!
174
                rowHasText[coords[0]] = true;
×
175
                numRowsWithText++;
×
176
            }
177
            if (neededInCol) {
1!
178
                columnHasText[coords[1]] = true;
×
179
                numColumnsWithText++;
×
180
            }
181
        }
1✔
182

183
        TableCell[][] remainingCells = new TableCell[numRowsWithText][numColumnsWithText];
1✔
184

185
        int targetRow = 0;
1✔
186
        for (int i = 0; i < rowHasText.length; i++) {
1✔
187
            if (!rowHasText[i]) {
1✔
188
                continue;
1✔
189
            }
190
            int targetColumn = 0;
1✔
191
            for (int j = 0; j < columnHasText.length; j++) {
1✔
192
                if (!columnHasText[j]) {
1✔
193
                    continue;
1✔
194
                }
195
                remainingCells[targetRow][targetColumn] = _cells[i][j];
1✔
196
                targetColumn++;
1✔
197
            }
198
            targetRow++;
1✔
199
        }
200

201
        _cells = remainingCells;
1✔
202

203
    }
1✔
204

205
    /**
206
     * Returns a rendering of this table with all cells converted to text.
207
     **/
208
    public String[][] asText() {
209
        String[][] result = new String[getRowCount()][getColumnCount()];
1✔
210

211
        for (int i = 0; i < result.length; i++) {
1✔
212
            for (int j = 0; j < result[0].length; j++) {
1✔
213
                result[i][j] = getCellAsText(i, j);
1✔
214
            }
215
        }
216
        return result;
1✔
217
    }
218

219
    /**
220
     * Returns the summary attribute associated with this table.
221
     **/
222
    public String getSummary() {
223
        return NodeUtils.getNodeAttribute(_dom, "summary");
1✔
224
    }
225

226
    @Override
227
    public String toString() {
228
        String eol = System.lineSeparator();
1✔
229
        StringBuilder sb = new StringBuilder(HttpUnitUtils.DEFAULT_TEXT_BUFFER_SIZE).append("WebTable:").append(eol);
1✔
230
        for (int i = 0; i < getCells().length; i++) {
1✔
231
            sb.append("[").append(i).append("]: ");
1✔
232
            for (int j = 0; j < getCells()[i].length; j++) {
1✔
233
                sb.append("  [").append(j).append("]=");
1✔
234
                if (getCells()[i][j] == null) {
1!
235
                    sb.append("null");
×
236
                } else {
237
                    sb.append(getCells()[i][j].getText());
1✔
238
                }
239
            }
240
            sb.append(eol);
1✔
241
        }
242
        return sb.toString();
1✔
243
    }
244

245
    @Override
246
    public ScriptableDelegate newScriptable() {
247
        return new HTMLElementScriptable(this);
1✔
248
    }
249

250
    @Override
251
    public ScriptableDelegate getParentDelegate() {
252
        return _response.getDocumentScriptable();
1✔
253
    }
254

255
    // ----------------------------------- private members -----------------------------------
256

257
    private Element _dom;
258
    private URL _url;
259
    private FrameSelector _frameName;
260
    private String _baseTarget;
261
    private String _characterSet;
262
    private WebResponse _response;
263

264
    private TableCell[][] _cells;
265

266
    WebTable(WebResponse response, FrameSelector frame, Node domTreeRoot, URL sourceURL, String baseTarget,
267
            String characterSet) {
268
        super(domTreeRoot);
1✔
269
        _response = response;
1✔
270
        _frameName = frame;
1✔
271
        _dom = (Element) domTreeRoot;
1✔
272
        _url = sourceURL;
1✔
273
        _baseTarget = baseTarget;
1✔
274
        _characterSet = characterSet;
1✔
275
    }
1✔
276

277
    private void readTable() {
278
        TableRow[] rows = getRows();
1✔
279
        int[] columnsRequired = new int[rows.length];
1✔
280

281
        for (int i = 0; i < rows.length; i++) {
1✔
282
            TableCell[] cells = rows[i].getCells();
1✔
283
            for (TableCell cell : cells) {
1✔
284
                int spannedRows = Math.min(columnsRequired.length - i, cell.getRowSpan());
1✔
285
                for (int k = 0; k < spannedRows; k++) {
1✔
286
                    columnsRequired[i + k] += cell.getColSpan();
1✔
287
                }
288
            }
289
        }
290
        int numColumns = 0;
1✔
291
        for (int element : columnsRequired) {
1✔
292
            numColumns = Math.max(numColumns, element);
1✔
293
        }
294

295
        _cells = new TableCell[columnsRequired.length][numColumns];
1✔
296

297
        for (int i = 0; i < rows.length; i++) {
1✔
298
            TableCell[] cells = rows[i].getCells();
1✔
299
            for (int j = 0; j < cells.length; j++) {
1✔
300
                int spannedRows = Math.min(columnsRequired.length - i, cells[j].getRowSpan());
1✔
301
                for (int k = 0; k < spannedRows; k++) {
1✔
302
                    for (int l = 0; l < cells[j].getColSpan(); l++) {
1✔
303
                        placeCell(i + k, j + l, cells[j]);
1✔
304
                    }
305
                }
306
            }
307
        }
308
    }
1✔
309

310
    private void placeCell(int row, int column, TableCell cell) {
311
        while (_cells[row][column] != null) {
1✔
312
            column++;
1✔
313
        }
314
        _cells[row][column] = cell;
1✔
315
    }
1✔
316

317
    private ArrayList _rows = new ArrayList<>();
1✔
318

319
    void addRow(TableRow tableRow) {
320
        _cells = null;
1✔
321
        _rows.add(tableRow);
1✔
322
    }
1✔
323

324
    TableRow newTableRow(HTMLTableRowElement element) {
325
        return new TableRow(this, element);
1✔
326
    }
327

328
    /**
329
     * Returns an array of rows for this table.
330
     */
331
    public TableRow[] getRows() {
332
        return (TableRow[]) _rows.toArray(new TableRow[_rows.size()]);
1✔
333
    }
334

335
    TableCell newTableCell(HTMLTableCellElement element) {
336
        return new TableCell(_response, _frameName, element, _url, _baseTarget, _characterSet);
1✔
337
    }
338

339
    static {
340
        MATCH_FIRST_NONBLANK_CELL = (htmlElement, criteria) -> {
1✔
341
            WebTable table = (WebTable) htmlElement;
1✔
342
            for (int row = 0; row < table.getRowCount(); row++) {
1✔
343
                for (int col = 0; col < table.getColumnCount(); col++) {
1✔
344
                    if (HttpUnitUtils.matches(table.getCellAsText(row, col).trim(), (String) criteria)) {
1✔
345
                        return true;
1✔
346
                    }
347
                }
348
            }
349
            return false;
1✔
350
        };
351

352
        MATCH_FIRST_NONBLANK_CELL_PREFIX = (htmlElement, criteria) -> {
1✔
353
            WebTable table = (WebTable) htmlElement;
1✔
354
            for (int row = 0; row < table.getRowCount(); row++) {
1✔
355
                for (int col = 0; col < table.getColumnCount(); col++) {
1✔
356
                    if (HttpUnitUtils.hasPrefix(table.getCellAsText(row, col).trim(), (String) criteria)) {
1✔
357
                        return true;
1✔
358
                    }
359
                }
360
            }
361
            return false;
1✔
362
        };
363

364
        MATCH_ID = (htmlElement, criteria) -> HttpUnitUtils.matches(((WebTable) htmlElement).getID(),
1✔
365
                (String) criteria);
366

367
        MATCH_SUMMARY = (htmlElement, criteria) -> HttpUnitUtils.matches(((WebTable) htmlElement).getSummary(),
1✔
368
                (String) criteria);
369

370
    }
1✔
371

372
}
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