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 }