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.LinkedList;
026    
027    import org.xml.sax.Attributes;
028    import org.xml.sax.SAXException;
029    
030    import fi.iki.hsivonen.xml.AttributeUtil;
031    import fi.iki.hsivonen.xml.checker.Checker;
032    
033    /**
034     * Checks XHTML table integrity: overlapping cells, spanning past the end 
035     * of row group, etc.
036     * 
037     * @version $Id: TableChecker.java,v 1.5 2006/12/01 12:34:30 hsivonen Exp $
038     * @author hsivonen
039     */
040    public final class TableChecker extends Checker {
041    
042        /**
043         * Constructor.
044         */
045        public TableChecker() {
046            super();
047        }
048    
049        /**
050         * Holds the current table. (Premature optimization to avoid 
051         * peeking the top of the stack all the time.)
052         */
053        private Table current;
054    
055        /**
056         * A stack for holding the tables that are open and ancestors of 
057         * the current table. Grows from the tail.
058         */
059        private final LinkedList<Table> stack = new LinkedList<Table>();
060    
061        /**
062         * Pushes the current table onto the stack and creates a new one.
063         */
064        private void push() {
065            if (current != null) {
066                stack.addLast(current);
067            }
068            current = new Table(this);
069        }
070    
071        /**
072         * Ends the current table, discards it and pops the top of the 
073         * stack to be the new current table.
074         * 
075         * @throws SAXException if ending the table throws
076         */
077        private void pop() throws SAXException {
078            if (current == null) {
079                throw new IllegalStateException("Bug!");
080            }
081            current.end();
082            if (stack.isEmpty()) {
083                current = null;
084            } else {
085                current = stack.removeLast();
086            }
087        }
088    
089        /**
090         * @see fi.iki.hsivonen.xml.checker.Checker#startElement(java.lang.String,
091         *      java.lang.String, java.lang.String, org.xml.sax.Attributes)
092         */
093        public void startElement(String uri, String localName, String qName,
094                Attributes atts) throws SAXException {
095            if ("http://www.w3.org/1999/xhtml".equals(uri)) {
096                if ("table".equals(localName)) {
097                    push();
098                } else if (current != null) {
099                    if ("td".equals(localName)) {
100                        current.startCell(false, atts);
101                    } else if ("th".equals(localName)) {
102                        current.startCell(true, atts);
103                    } else if ("tr".equals(localName)) {
104                        current.startRow();
105                    } else if ("tbody".equals(localName)
106                            || "thead".equals(localName)
107                            || "tfoot".equals(localName)) {
108                        current.startRowGroup(localName);
109                    } else if ("col".equals(localName)) {
110                        current.startCol(AttributeUtil.parseNonNegativeInteger(atts.getValue(
111                                "", "span")));
112                    } else if ("colgroup".equals(localName)) {
113                        current.startColGroup(AttributeUtil.parseNonNegativeInteger(atts.getValue(
114                                "", "span")));
115                    }
116                }
117            }
118        }
119    
120        /**
121         * @see fi.iki.hsivonen.xml.checker.Checker#endElement(java.lang.String,
122         *      java.lang.String, java.lang.String)
123         */
124        public void endElement(String uri, String localName, String qName)
125                throws SAXException {
126            if ("http://www.w3.org/1999/xhtml".equals(uri)) {
127                if ("table".equals(localName)) {
128                    pop();
129                } else if (current != null) {
130                    if ("td".equals(localName)) {
131                        current.endCell();
132                    } else if ("th".equals(localName)) {
133                        current.endCell();
134                    } else if ("tr".equals(localName)) {
135                        current.endRow();
136                    } else if ("tbody".equals(localName)
137                            || "thead".equals(localName)
138                            || "tfoot".equals(localName)) {
139                        current.endRowGroup();
140                    } else if ("col".equals(localName)) {
141                        current.endCol();
142                    } else if ("colgroup".equals(localName)) {
143                        current.endColGroup();
144                    }
145                }
146            }
147        }
148    
149        /**
150         * @see fi.iki.hsivonen.xml.checker.Checker#reset()
151         */
152        public void reset() {
153            stack.clear();
154            current = null;
155        }
156    
157    }