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 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,v 1.6 2006/12/01 12:34:30 hsivonen Exp $ 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 }