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    }