001    /*
002     * Copyright (c) 2007 Mozilla Foundation
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 nu.validator.messages;
024    
025    import java.util.SortedSet;
026    
027    import org.xml.sax.SAXException;
028    
029    import nu.validator.source.SourceHandler;
030    import nu.validator.xml.AttributesImpl;
031    import nu.validator.xml.XhtmlSaxEmitter;
032    
033    public class XhtmlSourceHandler implements SourceHandler {
034    
035        private static final char[] NEWLINE_SUBSTITUTE = {'\u21A9'};
036    
037        private static final AttributesImpl LINE_BREAK_ATTRS = new AttributesImpl();
038    
039        static {
040            LINE_BREAK_ATTRS.addAttribute("class", "lf");
041            LINE_BREAK_ATTRS.addAttribute("title", "Line break");
042        }
043        
044        private final AttributesImpl attrs = new AttributesImpl();
045        
046        private final XhtmlSaxEmitter emitter;
047        
048        private boolean listOpen;
049    
050        private boolean lineOpen;
051        
052        private String rangeOpen;
053        
054        private boolean charOpen;
055        
056        private int lineNumber;
057        
058        private boolean lineHadCharacters;
059    
060        private SortedSet<Integer> oneBasedLineErrors;
061        
062        /**
063         * @param emitter
064         */
065        public XhtmlSourceHandler(final XhtmlSaxEmitter emitter) {
066            this.emitter = emitter;
067        }
068        
069        public void characters(char[] ch, int start, int length)
070                throws SAXException {
071            maybeOpen();
072            lineHadCharacters = true;
073            emitter.characters(ch, start, length);
074        }
075    
076        @SuppressWarnings("boxing")
077        private void maybeOpen() throws SAXException {
078            assert !(lineOpen && !listOpen);
079            if (!listOpen) {
080                emitter.startElementWithClass("ol", "source");
081                listOpen = true;
082            }
083            if (!lineOpen) {
084                lineNumber++;
085                attrs.clear();
086                attrs.addAttribute("id", "l" + lineNumber);
087                if (oneBasedLineErrors != null && oneBasedLineErrors.contains(lineNumber)) {
088                    attrs.addAttribute("class", "b");
089                }
090                emitter.startElement("li", attrs);
091                emitter.startElement("code");
092                lineOpen = true;
093                lineHadCharacters = false;
094                if (rangeOpen != null) {
095                    emitter.startElementWithClass("b", rangeOpen);
096                }
097            }
098        }
099    
100        public void endCharHilite() throws SAXException {
101            if (!charOpen) {
102                return;
103            }
104            emitter.endElement("b");
105            charOpen = false;
106        }
107    
108        public void endRange() throws SAXException {
109            assert rangeOpen != null;
110            emitter.endElement("b");
111            rangeOpen = null;
112        }
113    
114        public void endSource() throws SAXException {
115            if (charOpen) {
116                endCharHilite();
117            }
118            assert rangeOpen == null;
119            if (lineOpen) {
120                emitter.endElement("code");
121                emitter.endElement("li");
122            }
123            if (listOpen) {
124                emitter.endElement("ol");
125            }
126        }
127    
128        public void newLine() throws SAXException {
129            maybeOpen();
130            if (charOpen) {
131                endCharHilite();
132            }
133            if (rangeOpen != null) {
134                emitter.endElement("b");
135            }
136            emitter.endElement("code");
137            emitter.startElement("code", LINE_BREAK_ATTRS);
138            emitter.characters(NEWLINE_SUBSTITUTE);
139            emitter.endElement("code");
140            emitter.endElement("li");
141            lineOpen = false;
142        }
143    
144        public void startCharHilite(int oneBasedLine, int oneBasedColumn)
145                throws SAXException {
146            maybeOpen();
147            assert !charOpen;
148            assert lineNumber == oneBasedLine;
149            attrs.clear();
150            attrs.addAttribute("id", "cl" + oneBasedLine + "c" + oneBasedColumn);
151            emitter.startElement("b", attrs);
152            charOpen = true;
153        }
154    
155        public void startRange(int oneBasedLine, int oneBasedColumn)
156                throws SAXException {
157            maybeOpen();
158            assert rangeOpen == null;
159            rangeOpen = "l" + oneBasedLine + "c" + oneBasedColumn;
160            attrs.clear();
161            attrs.addAttribute("id", rangeOpen);
162            attrs.addAttribute("class", rangeOpen);
163            emitter.startElement("b", attrs);
164        }
165    
166        public void startSource(String type, String encoding) throws SAXException {
167            listOpen = false;
168            lineOpen = false;
169            rangeOpen = null;
170            charOpen = false;
171            lineNumber = 0;
172            oneBasedLineErrors = null;
173        }
174    
175        public void setLineErrors(SortedSet<Integer> oneBasedLineErrors) throws SAXException {
176            this.oneBasedLineErrors = oneBasedLineErrors;
177        }
178    
179    }