001 /* 002 * Copyright (c) 2005, 2006, 2007 Henri Sivonen 003 * Copyright (c) 2007 Mozilla Foundation 004 * 005 * Permission is hereby granted, free of charge, to any person obtaining a 006 * copy of this software and associated documentation files (the "Software"), 007 * to deal in the Software without restriction, including without limitation 008 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 009 * and/or sell copies of the Software, and to permit persons to whom the 010 * Software is furnished to do so, subject to the following conditions: 011 * 012 * The above copyright notice and this permission notice shall be included in 013 * all copies or substantial portions of the Software. 014 * 015 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 016 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 017 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 018 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 019 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 020 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 021 * DEALINGS IN THE SOFTWARE. 022 */ 023 024 package nu.validator.messages; 025 026 import java.io.IOException; 027 028 import nu.validator.messages.types.MessageType; 029 import nu.validator.source.Location; 030 import nu.validator.source.SourceCode; 031 import nu.validator.source.SourceHandler; 032 import nu.validator.xml.CharacterUtil; 033 034 import org.whattf.checker.NormalizationChecker; 035 import org.xml.sax.ErrorHandler; 036 import org.xml.sax.SAXException; 037 import org.xml.sax.SAXParseException; 038 039 import com.ibm.icu.text.Normalizer; 040 041 042 public final class MessageEmitterAdapter implements ErrorHandler { 043 044 private final static char[] INDETERMINATE_MESSAGE = "The result cannot be determined due to a non-document-error.".toCharArray(); 045 046 private int warnings = 0; 047 048 private int errors = 0; 049 050 private int fatalErrors = 0; 051 052 private int nonDocumentErrors = 0; 053 054 private final SourceCode sourceCode; 055 056 private final MessageEmitter emitter; 057 058 private final ExactErrorHandler exactErrorHandler; 059 060 private final boolean showSource; 061 062 protected static String scrub(String s) throws SAXException { 063 if (s == null) { 064 return null; 065 } 066 s = CharacterUtil.prudentlyScrubCharacterData(s); 067 if (NormalizationChecker.startsWithComposingChar(s)) { 068 s = " " + s; 069 } 070 return Normalizer.normalize(s, Normalizer.NFC, 0); 071 } 072 073 public MessageEmitterAdapter(SourceCode sourceCode, boolean showSource, MessageEmitter messageEmitter) { 074 super(); 075 this.sourceCode = sourceCode; 076 this.emitter = messageEmitter; 077 this.exactErrorHandler = new ExactErrorHandler(this); 078 this.showSource = showSource; 079 } 080 081 /** 082 * @return Returns the errors. 083 */ 084 public int getErrors() { 085 return errors; 086 } 087 088 /** 089 * @return Returns the fatalErrors. 090 */ 091 public int getFatalErrors() { 092 return fatalErrors; 093 } 094 095 /** 096 * @return Returns the warnings. 097 */ 098 public int getWarnings() { 099 return warnings; 100 } 101 102 private boolean isErrors() { 103 return !(errors == 0 && fatalErrors == 0); 104 } 105 106 /** 107 * @see org.xml.sax.ErrorHandler#warning(org.xml.sax.SAXParseException) 108 */ 109 public void warning(SAXParseException e) throws SAXException { 110 warning(e, false); 111 } 112 113 /** 114 * @param e 115 * @throws SAXException 116 */ 117 private void warning(SAXParseException e, boolean exact) throws SAXException { 118 if (fatalErrors > 0 || nonDocumentErrors > 0) { 119 return; 120 } 121 this.warnings++; 122 messageFromSAXParseException(MessageType.WARNING, e, exact); 123 } 124 125 /** 126 * @see org.xml.sax.ErrorHandler#error(org.xml.sax.SAXParseException) 127 */ 128 public void error(SAXParseException e) throws SAXException { 129 error(e, false); 130 } 131 132 /** 133 * @param e 134 * @throws SAXException 135 */ 136 private void error(SAXParseException e, boolean exact) throws SAXException { 137 if (fatalErrors > 0 || nonDocumentErrors > 0) { 138 return; 139 } 140 this.errors++; 141 messageFromSAXParseException(MessageType.ERROR, e, exact); 142 } 143 144 /** 145 * @see org.xml.sax.ErrorHandler#fatalError(org.xml.sax.SAXParseException) 146 */ 147 public void fatalError(SAXParseException e) throws SAXException { 148 fatalError(e, false); 149 } 150 151 /** 152 * @param e 153 * @throws SAXException 154 */ 155 private void fatalError(SAXParseException e, boolean exact) throws SAXException { 156 if (fatalErrors > 0 || nonDocumentErrors > 0) { 157 return; 158 } 159 this.fatalErrors++; 160 Exception wrapped = e.getException(); 161 if (wrapped instanceof IOException) { 162 message(MessageType.IO, ((IOException) wrapped).getMessage(), null, -1, -1 , false); 163 } else { 164 messageFromSAXParseException(MessageType.FATAL, e, exact); 165 } 166 } 167 168 /** 169 * @see nu.validator.servlet.InfoErrorHandler#info(java.lang.String) 170 */ 171 public void info(String str) throws SAXException { 172 message(MessageType.INFO, str, null, -1, -1 , false); 173 } 174 175 /** 176 * @see nu.validator.servlet.InfoErrorHandler#ioError(java.io.IOException) 177 */ 178 public void ioError(IOException e) throws SAXException { 179 this.nonDocumentErrors++; 180 message(MessageType.IO, e.getMessage(), null, -1, -1 , false); 181 } 182 183 /** 184 * @see nu.validator.servlet.InfoErrorHandler#internalError(java.lang.Throwable, 185 * java.lang.String) 186 */ 187 public void internalError(Throwable e, String message) throws SAXException { 188 this.nonDocumentErrors++; 189 message(MessageType.INTERNAL, message, null, -1, -1 , false); 190 } 191 192 /** 193 * @see nu.validator.servlet.InfoErrorHandler#schemaError(java.lang.Exception) 194 */ 195 public void schemaError(Exception e) throws SAXException { 196 this.nonDocumentErrors++; 197 message(MessageType.SCHEMA, e.getMessage(), null, -1, -1 , false); 198 } 199 200 /** 201 * @see nu.validator.servlet.InfoErrorHandler#start() 202 */ 203 public void start(String documentUri) throws SAXException { 204 emitter.startMessages(scrub(documentUri), showSource); 205 } 206 207 /** 208 * @see nu.validator.servlet.InfoErrorHandler#end() 209 */ 210 public void end(String successMessage, String failureMessage) 211 throws SAXException { 212 ResultHandler resultHandler = emitter.startResult(); 213 if (resultHandler != null) { 214 if (isIndeterminate()) { 215 resultHandler.startResult(Result.INDETERMINATE); 216 resultHandler.characters(INDETERMINATE_MESSAGE, 0, INDETERMINATE_MESSAGE.length); 217 resultHandler.endResult(); 218 } else if (isErrors()) { 219 resultHandler.startResult(Result.FAILURE); 220 resultHandler.characters(failureMessage.toCharArray(), 0, failureMessage.length()); 221 resultHandler.endResult(); 222 } else { 223 resultHandler.startResult(Result.SUCCESS); 224 resultHandler.characters(successMessage.toCharArray(), 0, successMessage.length()); 225 resultHandler.endResult(); 226 } 227 } 228 emitter.endResult(); 229 230 if (showSource) { 231 SourceHandler sourceHandler = emitter.startFullSource(); 232 if (sourceHandler != null) { 233 sourceCode.emitSource(sourceHandler); 234 } 235 emitter.endFullSource(); 236 } 237 emitter.endMessages(); 238 } 239 240 private boolean isIndeterminate() { 241 return nonDocumentErrors > 0; 242 } 243 244 private void messageFromSAXParseException(MessageType type, SAXParseException spe, boolean exact) throws SAXException { 245 message(type, spe.getMessage(), spe.getSystemId(), spe.getLineNumber(), spe.getColumnNumber(), exact); 246 } 247 248 private void message(MessageType type, String message, String systemId, int oneBasedLine, int oneBasedColumn, boolean exact) throws SAXException { 249 String uri = sourceCode.getUri(); 250 if (oneBasedLine > -1 && (uri == systemId || (uri != null && uri.equals(systemId)))) { 251 if (oneBasedColumn > -1) { 252 if (exact) { 253 messageWithExact(type, message, oneBasedLine, oneBasedColumn); 254 } else { 255 messageWithRange(type, message, oneBasedLine, oneBasedColumn); 256 } 257 } else { 258 messageWithLine(type, message, oneBasedLine); 259 } 260 } else { 261 messageWithoutExtract(type, message, systemId, oneBasedLine, oneBasedColumn); 262 } 263 } 264 265 private void messageWithRange(MessageType type, String message, int oneBasedLine, int oneBasedColumn) throws SAXException { 266 Location rangeLast = sourceCode.newLocatorLocation(oneBasedLine, oneBasedColumn); 267 if (!sourceCode.isWithinKnownSource(rangeLast)) { 268 messageWithoutExtract(type, message, null, oneBasedLine, oneBasedColumn); 269 return; 270 } 271 Location rangeStart = sourceCode.rangeStartForRangeLast(rangeLast); 272 emitter.startMessage(type, null, rangeStart.getLine() + 1, rangeStart.getColumn() + 1, oneBasedLine, oneBasedColumn, false); 273 messageText(message); 274 SourceHandler sourceHandler = emitter.startSource(); 275 if (sourceHandler != null) { 276 sourceCode.rangeEndError(rangeStart, rangeLast, sourceHandler); 277 } 278 emitter.endSource(); 279 // XXX elaboration 280 emitter.endMessage(); 281 } 282 283 private void messageWithExact(MessageType type, String message, int oneBasedLine, int oneBasedColumn) throws SAXException { 284 emitter.startMessage(type, null, oneBasedLine, oneBasedColumn, oneBasedLine, oneBasedColumn, true); 285 messageText(message); 286 Location location = sourceCode.newLocatorLocation(oneBasedLine, oneBasedColumn); 287 if (sourceCode.isWithinKnownSource(location)) { 288 SourceHandler sourceHandler = emitter.startSource(); 289 if (sourceHandler != null) { 290 sourceCode.exactError(location, sourceHandler); 291 } 292 emitter.endSource(); 293 } else { 294 sourceCode.rememberExactError(location); 295 } 296 // XXX elaboration 297 emitter.endMessage(); 298 } 299 300 private void messageWithLine(MessageType type, String message, int oneBasedLine) throws SAXException { 301 if (!sourceCode.isWithinKnownSource(oneBasedLine)) { 302 throw new RuntimeException("Bug. Line out of range!"); 303 } 304 emitter.startMessage(type, null, oneBasedLine, -1, oneBasedLine, -1, false); 305 messageText(message); 306 SourceHandler sourceHandler = emitter.startSource(); 307 if (sourceHandler != null) { 308 sourceCode.lineError(oneBasedLine, sourceHandler); 309 } 310 emitter.endSource(); 311 // XXX elaboration 312 emitter.endMessage(); 313 } 314 315 private void messageWithoutExtract(MessageType type, String message, String systemId, int oneBasedLine, int oneBasedColumn) throws SAXException { 316 emitter.startMessage(type, scrub(systemId), oneBasedLine, oneBasedColumn, oneBasedLine, oneBasedColumn, false); 317 messageText(message); 318 // XXX elaboration 319 emitter.endMessage(); 320 } 321 322 /** 323 * @param message 324 * @throws SAXException 325 */ 326 private void messageText(String message) throws SAXException { 327 MessageTextHandler messageTextHandler = emitter.startText(); 328 if (messageTextHandler != null) { 329 emitStringWithQurlyQuotes(message, messageTextHandler); 330 } 331 emitter.endText(); 332 } 333 334 private void emitStringWithQurlyQuotes(String message, MessageTextHandler messageTextHandler) throws SAXException { 335 if (message == null) { 336 message = ""; 337 } 338 message = scrub(message); 339 int len = message.length(); 340 int start = 0; 341 int startQuotes = 0; 342 for (int i = 0; i < len; i++) { 343 char c = message.charAt(i); 344 if (c == '\u201C') { 345 startQuotes++; 346 if (startQuotes == 1) { 347 char[] scrubbed = scrub(message.substring(start, i)).toCharArray(); 348 messageTextHandler.characters(scrubbed, 0, scrubbed.length); 349 start = i + 1; 350 messageTextHandler.startCode(); 351 } 352 } else if (c == '\u201D' && startQuotes > 0) { 353 startQuotes--; 354 if (startQuotes == 0) { 355 char[] scrubbed = scrub(message.substring(start, i)).toCharArray(); 356 messageTextHandler.characters(scrubbed, 0, scrubbed.length); 357 start = i + 1; 358 messageTextHandler.endCode(); 359 } 360 } 361 } 362 if (start < len) { 363 char[] scrubbed = scrub(message.substring(start, len)).toCharArray(); 364 messageTextHandler.characters(scrubbed, 0, scrubbed.length); 365 } 366 if (startQuotes > 0) { 367 messageTextHandler.endCode(); 368 } 369 } 370 371 private final class ExactErrorHandler implements ErrorHandler { 372 373 private final MessageEmitterAdapter owner; 374 375 /** 376 * @param owner 377 */ 378 ExactErrorHandler(final MessageEmitterAdapter owner) { 379 this.owner = owner; 380 } 381 382 public void error(SAXParseException exception) throws SAXException { 383 owner.error(exception, true); 384 } 385 386 public void fatalError(SAXParseException exception) throws SAXException { 387 owner.fatalError(exception, true); 388 } 389 390 public void warning(SAXParseException exception) throws SAXException { 391 owner.warning(exception, true); 392 } 393 394 } 395 396 /** 397 * Returns the exactErrorHandler. 398 * 399 * @return the exactErrorHandler 400 */ 401 public ErrorHandler getExactErrorHandler() { 402 return exactErrorHandler; 403 } 404 }