001 /* 002 * Copyright (c) 2006 Henri Sivonen 003 * 004 * Permission is hereby granted, free of charge, to any person obtaining a 005 * copy of this software and associated documentation files (the "Software"), 006 * to deal in the Software without restriction, including without limitation 007 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 008 * and/or sell copies of the Software, and to permit persons to whom the 009 * Software is furnished to do so, subject to the following conditions: 010 * 011 * The above copyright notice and this permission notice shall be included in 012 * all copies or substantial portions of the Software. 013 * 014 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 015 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 016 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 017 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 018 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 019 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 020 * DEALINGS IN THE SOFTWARE. 021 */ 022 023 package fi.iki.hsivonen.xml.checker.table; 024 025 import java.util.Arrays; 026 import java.util.Iterator; 027 import java.util.SortedSet; 028 import java.util.TreeSet; 029 030 import org.xml.sax.SAXException; 031 032 /** 033 * Represents a row group (explicit or implicit) for table integrity checking. 034 * 035 * @version $Id: RowGroup.java,v 1.7 2006/12/01 12:34:30 hsivonen Exp $ 036 * @author hsivonen 037 */ 038 final class RowGroup { 039 040 /** 041 * Runtime type constant. 042 */ 043 private final Cell[] EMPTY_CELL_ARRAY = {}; 044 045 /** 046 * Keeps track of the current slot row of the insertion point. 047 */ 048 private int currentRow = -1; 049 050 /** 051 * The column slot of the insertion point. 052 */ 053 private int insertionPoint = 0; 054 055 /** 056 * The index of the next uninspected item in <code>cellsOnCurrentRow</code>. 057 */ 058 private int nextOldCell = 0; 059 060 /** 061 * The owning table. 062 */ 063 private final Table owner; 064 065 /** 066 * The set of cells from previous rows that are still in effect extending 067 * downwards. 068 */ 069 private final SortedSet<Cell> cellsIfEffect = new TreeSet<Cell>( 070 VerticalCellComparator.THE_INSTANCE); 071 072 /** 073 * A temporary copy of <code>cellsIfEffect</code> sorted differently. 074 */ 075 private Cell[] cellsOnCurrentRow; 076 077 /** 078 * Whether the current row has had cells. 079 */ 080 private boolean rowHadCells; 081 082 /** 083 * The local name of the element that established this row group or 084 * <code>null</code> if this is an implicit row group. 085 */ 086 private final String type; 087 088 RowGroup(Table owner, String type) { 089 super(); 090 this.owner = owner; 091 this.type = type; 092 } 093 094 public void cell(Cell cell) throws SAXException { 095 rowHadCells = true; 096 findInsertionPoint(); 097 cell.setPosition(currentRow, insertionPoint); 098 owner.cell(cell); 099 if (cell.getBottom() > currentRow + 1) { 100 cellsIfEffect.add(cell); 101 } 102 insertionPoint = cell.getRight(); 103 for (int i = nextOldCell; i < cellsOnCurrentRow.length; i++) { 104 cellsOnCurrentRow[i].errOnHorizontalOverlap(cell); 105 } 106 } 107 108 /** 109 * 110 */ 111 private void findInsertionPoint() { 112 for (;;) { 113 if (nextOldCell == cellsOnCurrentRow.length) { 114 break; 115 } 116 Cell other = cellsOnCurrentRow[nextOldCell]; 117 int newInsertionPoint = other.freeSlot(insertionPoint); 118 if (newInsertionPoint == insertionPoint) { 119 break; 120 } 121 nextOldCell++; 122 insertionPoint = newInsertionPoint; 123 } 124 } 125 126 public void end() throws SAXException { 127 for (Cell cell : cellsIfEffect) { 128 cell.errIfNotRowspanZero(type); 129 } 130 } 131 132 public void endRow() throws SAXException { 133 if (!rowHadCells) { 134 owner.err("Row " 135 + (currentRow + 1) 136 + " of " 137 + (type == null ? "an implicit row group" 138 : "a row group established by a \u201C" + type 139 + "\u201D element") 140 + " has no cells beginning on it."); 141 } 142 143 findInsertionPoint(); 144 cellsOnCurrentRow = null; 145 146 int columnCount = owner.getColumnCount(); 147 if (owner.isHardWidth()) { 148 if (insertionPoint > columnCount) { 149 owner.err("A table row was " 150 + insertionPoint 151 + " columns wide and exceeded the column count established using column markup (" 152 + columnCount + ")."); 153 } else if (insertionPoint < columnCount) { 154 owner.err("A table row was " 155 + insertionPoint 156 + " columns wide, which is less than the column count established using column markup (" 157 + columnCount + ")."); 158 } 159 } else if (columnCount == -1) { 160 // just saw the first row 161 owner.setColumnCount(insertionPoint); 162 } else { 163 if (insertionPoint > columnCount) { 164 owner.warn("A table row was " 165 + insertionPoint 166 + " columns wide and exceeded the column count established by the first row (" 167 + columnCount + ")."); 168 } else if (insertionPoint < columnCount) { 169 owner.warn("A table row was " 170 + insertionPoint 171 + " columns wide, which is less than the column count established by the first row (" 172 + columnCount + ")."); 173 } 174 } 175 176 // Get rid of cells that don't span to the next row 177 for (Iterator<Cell> iter = cellsIfEffect.iterator(); iter.hasNext();) { 178 Cell cell = iter.next(); 179 if (cell.shouldBeCulled(currentRow + 1)) { 180 iter.remove(); 181 } 182 } 183 } 184 185 public void startRow() { 186 currentRow++; 187 insertionPoint = 0; 188 nextOldCell = 0; 189 rowHadCells = false; 190 cellsOnCurrentRow = cellsIfEffect.toArray(EMPTY_CELL_ARRAY); 191 // the array should already be in the right order most of the time 192 Arrays.sort(cellsOnCurrentRow, HorizontalCellComparator.THE_INSTANCE); 193 } 194 195 }