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 org.whattf.checker.table;
024
025 import org.xml.sax.ErrorHandler;
026 import org.xml.sax.Locator;
027 import org.xml.sax.SAXException;
028 import org.xml.sax.SAXParseException;
029
030 /**
031 * A table cell for table integrity checking.
032 *
033 * @version $Id: Cell.java 169 2007-05-25 07:21:55Z hsivonen $
034 * @author hsivonen
035 */
036 final class Cell implements Locator {
037
038 // See
039 // http://mxr-test.landfill.bugzilla.org/mxr-test/seamonkey/source/content/html/content/src/nsHTMLTableCellElement.cpp
040 // for the source of these magic numbers.
041
042 /**
043 * Magic number from Gecko.
044 */
045 private static final int MAX_COLSPAN = 1000;
046
047 /**
048 * Magic number from Gecko.
049 */
050 private static final int MAX_ROWSPAN = 8190;
051
052 /**
053 * The column in which this cell starts. (Zero before positioning.)
054 */
055 private int left;
056
057 /**
058 * The first row in the row group onto which this cell does not span.
059 * (rowspan before positioning)
060 *
061 * <p>However, <code>Integen.MAX_VALUE</code> is a magic value that means
062 * <code>rowspan=0</code>.
063 */
064 private int bottom;
065
066 /**
067 * The first column into which this cell does not span.
068 * (colspan before positioning.)
069 */
070 private int right;
071
072 /**
073 * The value of the <code>headers</code> attribute split on white space.
074 */
075 private final String[] headers;
076
077 /**
078 * Whether this is a <code>th</code> cell.
079 */
080 private final boolean header;
081
082 /**
083 * Source column.
084 */
085 private final int columnNumber;
086
087 /**
088 * Source line.
089 */
090 private final int lineNumber;
091
092 /**
093 * Source public id.
094 */
095 private final String publicId;
096
097 /**
098 * Source system id.
099 */
100 private final String systemId;
101
102 /**
103 * The error handler.
104 */
105 private final ErrorHandler errorHandler;
106
107 Cell(int colspan, int rowspan, String[] headers, boolean header,
108 Locator locator, ErrorHandler errorHandler) throws SAXException {
109 super();
110 this.errorHandler = errorHandler;
111 if (locator == null) {
112 this.columnNumber = -1;
113 this.lineNumber = -1;
114 this.publicId = null;
115 this.systemId = null;
116 } else {
117 this.columnNumber = locator.getColumnNumber();
118 this.lineNumber = locator.getLineNumber();
119 this.publicId = locator.getPublicId();
120 this.systemId = locator.getSystemId();
121 }
122 if (rowspan > MAX_ROWSPAN) {
123 warn("A rowspan attribute has the value " + rowspan
124 + ", which exceeds the magic Gecko limit of " + MAX_ROWSPAN
125 + ".");
126 }
127 if (colspan > MAX_COLSPAN) {
128 warn("A colspan attribute has the value " + colspan
129 + ", which exceeds the magic browser limit of "
130 + MAX_COLSPAN + ".");
131 }
132 if (rowspan == Integer.MAX_VALUE) {
133 throw new SAXException(
134 "Implementation limit reached. Table row counter overflowed.");
135 }
136 this.left = 0;
137 this.right = colspan;
138 this.bottom = (rowspan == 0 ? Integer.MAX_VALUE : rowspan);
139 this.headers = headers;
140 this.header = header;
141 }
142
143 /**
144 * Returns the headers.
145 *
146 * @return the headers
147 */
148 public String[] getHeadings() {
149 return headers;
150 }
151
152 /**
153 * Returns the header.
154 *
155 * @return the header
156 */
157 public boolean isHeader() {
158 return header;
159 }
160
161 public void warn(String message) throws SAXException {
162 if (errorHandler != null) {
163 errorHandler.warning(new SAXParseException(message, publicId,
164 systemId, lineNumber, columnNumber));
165 }
166 }
167
168 public void err(String message) throws SAXException {
169 if (errorHandler != null) {
170 errorHandler.error(new SAXParseException(message, publicId,
171 systemId, lineNumber, columnNumber));
172 }
173 }
174
175 /**
176 * Emit errors if this cell and the argument overlap horizontally.
177 * @param laterCell another cell
178 * @throws SAXException if the <code>ErrorHandler</code> throws
179 */
180 public void errOnHorizontalOverlap(Cell laterCell) throws SAXException {
181 if (!((laterCell.right <= left) || (right <= laterCell.left))) {
182 this.err("Table cell is overlapped by later table cell.");
183 laterCell.err("Table cell overlaps an earlier table cell.");
184 }
185 }
186
187 public void setPosition(int top, int left) throws SAXException {
188 this.left = left;
189 this.right += left;
190 if (this.right < 1) {
191 throw new SAXException(
192 "Implementation limit reached. Table column counter overflowed.");
193 }
194 if (this.bottom != Integer.MAX_VALUE) {
195 this.bottom += top;
196 if (this.bottom < 1) {
197 throw new SAXException(
198 "Implementation limit reached. Table row counter overflowed.");
199 }
200 }
201 }
202
203 public boolean shouldBeCulled(int row) {
204 return row >= bottom;
205 }
206
207 public int freeSlot(int potentialSlot) {
208 if (potentialSlot < left || potentialSlot >= right) {
209 return potentialSlot;
210 } else {
211 return right;
212 }
213 }
214
215 /**
216 * Returns the bottom.
217 *
218 * @return the bottom
219 */
220 public int getBottom() {
221 return bottom;
222 }
223
224 /**
225 * Returns the left.
226 *
227 * @return the left
228 */
229 int getLeft() {
230 return left;
231 }
232
233 /**
234 * Returns the right.
235 *
236 * @return the right
237 */
238 int getRight() {
239 return right;
240 }
241
242 public void errIfNotRowspanZero(String rowGroupType) throws SAXException {
243 if (this.bottom != Integer.MAX_VALUE) {
244 err("Table cell spans past the end of its "
245 + (rowGroupType == null ? "implicit row group"
246 : "row group established by a \u201C" + rowGroupType
247 + "\u201D element")
248 + "; clipped to the end of the row group.");
249 }
250 }
251
252 /**
253 * Returns the columnNumber.
254 *
255 * @return the columnNumber
256 */
257 public int getColumnNumber() {
258 return columnNumber;
259 }
260
261 /**
262 * Returns the lineNumber.
263 *
264 * @return the lineNumber
265 */
266 public int getLineNumber() {
267 return lineNumber;
268 }
269
270 /**
271 * Returns the publicId.
272 *
273 * @return the publicId
274 */
275 public String getPublicId() {
276 return publicId;
277 }
278
279 /**
280 * Returns the systemId.
281 *
282 * @return the systemId
283 */
284 public String getSystemId() {
285 return systemId;
286 }
287
288 public String elementName() {
289 return header ? "th" : "td";
290 }
291
292 }