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 }