001    /*
002     * Copyright (c) 2005, 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.verifierservlet;
024    
025    import java.io.IOException;
026    
027    import org.xml.sax.ContentHandler;
028    import org.xml.sax.ErrorHandler;
029    import org.xml.sax.SAXException;
030    import org.xml.sax.SAXParseException;
031    
032    import com.ibm.icu.text.Normalizer;
033    
034    import fi.iki.hsivonen.xml.XhtmlSaxEmitter;
035    import fi.iki.hsivonen.xml.checker.NormalizationChecker;
036    import fi.karppinen.xml.CharacterUtil;
037    
038    /**
039     * @version $Id: XhtmlEmittingErrorHandler.java,v 1.16 2006/12/01 12:52:52 hsivonen Exp $
040     * @author hsivonen
041     */
042    public class XhtmlEmittingErrorHandler implements ErrorHandler {
043    
044        private static final char[] INFO = "Info:".toCharArray();
045    
046        private static final char[] WARNING = "Warning:".toCharArray();
047    
048        private static final char[] ERROR = "Error:".toCharArray();
049    
050        private static final char[] FATAL_ERROR = "Fatal Error:".toCharArray();
051    
052        private static final char[] IO_ERROR = "IO Error:".toCharArray();
053    
054        private static final char[] INTERNAL_ERROR = "Internal Error:".toCharArray();
055    
056        private static final char[] SCHEMA_ERROR = "Schema Error:".toCharArray();
057    
058        private static final char[] SPACE = { ' ' };
059    
060        private static final char[] LINE = "Line ".toCharArray();
061    
062        private static final char[] COLUMN = ", column ".toCharArray();
063    
064        private static final char[] IN_RESOURCE = " in resource ".toCharArray();
065        
066        private XhtmlSaxEmitter emitter;
067    
068        private boolean listOpen = false;
069    
070        private int warnings = 0;
071    
072        private int errors = 0;
073    
074        private int fatalErrors = 0;
075    
076        private static String scrub(String s) throws SAXException {
077            s = CharacterUtil.prudentlyScrubCharacterData(s);
078            if (NormalizationChecker.startsWithComposingChar(s)) {
079                s = " " + s;
080            }
081            return Normalizer.normalize(s, Normalizer.NFC, 0);
082        }
083    
084        /**
085         * @return Returns the errors.
086         */
087        public int getErrors() {
088            return errors;
089        }
090    
091        /**
092         * @return Returns the fatalErrors.
093         */
094        public int getFatalErrors() {
095            return fatalErrors;
096        }
097    
098        /**
099         * @return Returns the warnings.
100         */
101        public int getWarnings() {
102            return warnings;
103        }
104    
105        /**
106         * @param contentHandler
107         */
108        public XhtmlEmittingErrorHandler(ContentHandler contentHandler) {
109            this.emitter = new XhtmlSaxEmitter(contentHandler);
110        }
111    
112        private void maybeOpenList() throws SAXException {
113            if (!this.listOpen) {
114                this.emitter.startElement("ol");
115                this.listOpen = true;
116            }
117        }
118        
119        private void emitMessage(String message) throws SAXException {
120            int len = message.length();
121            int start = 0;
122            int startQuotes = 0;
123            for (int i = 0; i < len; i++) {
124                char c = message.charAt(i);
125                if (c == '\u201C') {
126                    startQuotes++;
127                    if (startQuotes == 1) {
128                        this.emitter.characters(scrub(message.substring(start, i)));
129                        start = i + 1;
130                        this.emitter.startElement("code");
131                    }
132                } else if (c == '\u201D' && startQuotes > 0) {
133                    startQuotes--;
134                    if (startQuotes == 0) {
135                        this.emitter.characters(scrub(message.substring(start, i)));
136                        start = i + 1;
137                        this.emitter.endElement("code");                    
138                    }
139                }
140            }
141            if (start < len) {
142                this.emitter.characters(scrub(message.substring(start, len)));            
143            }
144            if (startQuotes > 0) {
145                this.emitter.endElement("code");                                
146            }
147        }
148    
149        private void emitErrorLevel(char[] level) throws SAXException {
150            this.emitter.startElement("strong");
151            this.emitter.characters(level);
152            this.emitter.endElement("strong");
153        }
154    
155        private void emitError(char[] level, SAXParseException e)
156                throws SAXException {
157            this.maybeOpenList();
158            this.emitter.startElementWithClass("li", levelToClass(level));
159            this.emitErrorMsg(level, e);
160            this.emitErrorLocation(e);
161            this.emitter.endElement("li");
162        }
163    
164        private void emitError(char[] level, Exception e) throws SAXException {
165            this.maybeOpenList();
166            this.emitter.startElementWithClass("li", levelToClass(level));
167            this.emitErrorMsg(level, e);
168            this.emitter.endElement("li");
169        }
170    
171        /**
172         * @param e
173         * @throws SAXException
174         */
175        private void emitErrorLocation(SAXParseException e) throws SAXException {
176            int line = e.getLineNumber();
177            String systemId = e.getSystemId();
178            if (systemId == null) {
179                return;
180            }
181            this.emitter.startElement("div");
182            if (line > -1) {
183                this.emitter.characters(LINE);
184                this.emitter.characters("" + line);
185                this.emitter.characters(COLUMN);
186                this.emitter.characters("" + e.getColumnNumber());
187                this.emitter.characters(IN_RESOURCE);
188            }
189            this.emitter.characters(scrub(systemId));
190            this.emitter.endElement("div");
191        }
192    
193        /**
194         * @param level
195         * @param e
196         * @throws SAXException
197         */
198        private void emitErrorMsg(char[] level, Exception e) throws SAXException {
199            this.emitter.startElement("div");
200            this.emitErrorLevel(level);
201    
202            this.emitter.characters(SPACE);
203            String msg = e.getMessage();
204            if (msg == null) {
205                msg = e.getClass().getName();
206            }
207            this.emitMessage(msg);
208            this.emitter.endElement("div");
209        }
210    
211        /**
212         * @see org.xml.sax.ErrorHandler#warning(org.xml.sax.SAXParseException)
213         */
214        public void warning(SAXParseException e) throws SAXException {
215            this.warnings++;
216            this.emitError(WARNING, e);
217        }
218    
219        /**
220         * @see org.xml.sax.ErrorHandler#error(org.xml.sax.SAXParseException)
221         */
222        public void error(SAXParseException e) throws SAXException {
223            this.errors++;
224            this.emitError(ERROR, e);
225        }
226    
227        /**
228         * @see org.xml.sax.ErrorHandler#fatalError(org.xml.sax.SAXParseException)
229         */
230        public void fatalError(SAXParseException e) throws SAXException {
231            this.fatalErrors++;
232            if (e.getException() instanceof IOException) {
233                this.emitError(IO_ERROR, e);
234            } else {
235                this.emitError(FATAL_ERROR, e);
236            }
237        }
238    
239        public void end() throws SAXException {
240            if (this.listOpen) {
241                this.emitter.endElement("ol");
242                this.listOpen = false;
243            }
244        }
245    
246        public void start() throws SAXException {
247    
248        }
249    
250        /**
251         * @param e
252         */
253        public void info(String str) throws SAXException {
254            this.maybeOpenList();
255            this.emitter.startElementWithClass("li", "info");
256            this.emitter.startElement("div");
257            this.emitErrorLevel(INFO);
258            this.emitter.characters(SPACE);
259            this.emitMessage(str);
260            this.emitter.endElement("div");
261            this.emitter.endElement("li");
262        }
263        
264        /**
265         * @param e
266         */
267        public void ioError(IOException e) throws SAXException {
268            this.fatalErrors++;
269            this.emitError(IO_ERROR, e);
270        }
271    
272        /**
273         * @param e
274         * @throws SAXException
275         */
276        public void internalError(Throwable e, String message) throws SAXException {
277            this.fatalErrors++;
278            this.maybeOpenList();
279            this.emitter.startElementWithClass("li", "internalerror");
280            this.emitter.startElement("div");
281            this.emitErrorLevel(INTERNAL_ERROR);
282            this.emitter.characters(SPACE);
283            this.emitMessage(message);
284            this.emitter.endElement("div");
285            this.emitter.endElement("li");
286        }
287    
288        private String levelToClass(char[] level) {
289            if (level == WARNING) {
290                return "warning";
291            } else if (level == ERROR) {
292                return "error";
293            } else if (level == FATAL_ERROR) {
294                return "fatalerror";
295            } else if (level == SCHEMA_ERROR) {
296                return "schemaerror";
297            } else {
298                return "ioerror";
299            }
300        }
301    
302        /**
303         * @param e
304         */
305        public void schemaError(Exception e) throws SAXException {
306            this.fatalErrors++;
307            this.emitError(SCHEMA_ERROR, e);
308        }
309        
310        public boolean isErrors() {
311            return !(errors == 0 && fatalErrors == 0);
312        }
313    }