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 }