1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.doxia.xsd;
20
21 import javax.xml.parsers.ParserConfigurationException;
22
23 import java.io.IOException;
24 import java.io.StringReader;
25 import java.util.ArrayList;
26 import java.util.Collections;
27 import java.util.List;
28 import java.util.Map;
29
30 import org.apache.maven.doxia.util.XmlValidator;
31 import org.codehaus.plexus.testing.PlexusTest;
32 import org.junit.jupiter.api.AfterEach;
33 import org.junit.jupiter.api.Test;
34 import org.slf4j.Logger;
35 import org.slf4j.LoggerFactory;
36 import org.xml.sax.EntityResolver;
37 import org.xml.sax.InputSource;
38 import org.xml.sax.SAXException;
39 import org.xml.sax.SAXNotRecognizedException;
40 import org.xml.sax.SAXNotSupportedException;
41 import org.xml.sax.SAXParseException;
42 import org.xml.sax.XMLReader;
43 import org.xml.sax.helpers.DefaultHandler;
44
45 import static org.junit.jupiter.api.Assertions.assertFalse;
46 import static org.junit.jupiter.api.Assertions.fail;
47
48
49
50
51
52
53
54
55 @PlexusTest
56 public abstract class AbstractXmlValidator {
57
58 protected static final String EOL = System.getProperty("line.separator");
59
60 protected final Logger logger = LoggerFactory.getLogger(getClass());
61
62
63 private XMLReader xmlReader;
64
65
66 private boolean validate = true;
67
68
69
70
71
72
73
74
75 protected boolean isFailErrorMessage(String message) {
76 return !(message.contains("schema_reference.4: Failed to read schema document 'http://www.w3.org/2001/xml.xsd'")
77 || message.contains("cvc-complex-type.4: Attribute 'alt' must appear on element 'img'.")
78 || message.contains("cvc-complex-type.2.4.a: Invalid content starting with element")
79 || message.contains("cvc-complex-type.2.4.a: Invalid content was found starting with element")
80 || message.contains("cvc-datatype-valid.1.2.1:")
81 || message.contains("cvc-attribute.3:"));
82 }
83
84 @AfterEach
85 protected void tearDown() {
86 xmlReader = null;
87 }
88
89
90
91
92
93
94
95
96
97 @Test
98 public void testValidateFiles() throws Exception {
99 Map<String, String> testDocuments = getTestDocuments();
100 assertFalse(testDocuments.isEmpty(), "No test documents found");
101 for (Map.Entry<String, String> entry : testDocuments.entrySet()) {
102 if (logger.isDebugEnabled()) {
103 logger.debug("Validate '" + entry.getKey() + "'");
104 }
105
106 List<ErrorMessage> errors = parseXML(entry.getValue());
107
108 for (ErrorMessage error : errors) {
109 if (isFailErrorMessage(error.getMessage())) {
110 fail(entry.getKey() + EOL + error.toString());
111 } else {
112 if (logger.isDebugEnabled()) {
113 logger.debug(entry.getKey() + EOL + error.toString());
114 }
115 }
116 }
117 }
118 }
119
120
121
122
123
124 protected abstract String addNamespaces(String content);
125
126
127
128
129
130 protected abstract Map<String, String> getTestDocuments() throws IOException;
131
132
133
134
135
136
137 protected abstract EntityResolver getEntityResolver();
138
139
140
141
142
143 protected boolean isValidate() {
144 return validate;
145 }
146
147
148
149
150
151 protected void setValidate(boolean validate) {
152 this.validate = validate;
153 }
154
155
156
157
158
159 private XMLReader getXMLReader() {
160 if (xmlReader == null) {
161 try {
162 XmlValidator validator = new XmlValidator();
163 validator.setValidate(validate);
164 validator.setDefaultHandler(new MessagesErrorHandler());
165 validator.setEntityResolver(getEntityResolver());
166 xmlReader = validator.getXmlReader();
167 } catch (SAXNotRecognizedException e) {
168 fail("SAXNotRecognizedException: " + e.getMessage());
169 } catch (SAXNotSupportedException e) {
170 fail("SAXNotSupportedException: " + e.getMessage());
171 } catch (SAXException e) {
172 fail("SAXException: " + e.getMessage());
173 } catch (ParserConfigurationException e) {
174 fail("ParserConfigurationException: " + e.getMessage());
175 }
176 }
177
178 ((MessagesErrorHandler) xmlReader.getErrorHandler()).clearMessages();
179
180 return xmlReader;
181 }
182
183
184
185
186
187
188
189 private List<ErrorMessage> parseXML(String content) throws IOException, SAXException {
190 String xmlContent = addNamespaces(content);
191
192 XMLReader xmlReader = getXMLReader();
193
194 MessagesErrorHandler errorHandler = (MessagesErrorHandler) xmlReader.getErrorHandler();
195
196 xmlReader.parse(new InputSource(new StringReader(xmlContent)));
197
198 return errorHandler.getMessages();
199 }
200
201 private static class ErrorMessage extends DefaultHandler {
202 private final String level;
203 private final String publicID;
204 private final String systemID;
205 private final int lineNumber;
206 private final int columnNumber;
207 private final String message;
208
209 ErrorMessage(String level, String publicID, String systemID, int lineNumber, int columnNumber, String message) {
210 super();
211 this.level = level;
212 this.publicID = publicID;
213 this.systemID = systemID;
214 this.lineNumber = lineNumber;
215 this.columnNumber = columnNumber;
216 this.message = message;
217 }
218
219
220
221
222 protected String getLevel() {
223 return level;
224 }
225
226
227
228
229 protected String getPublicID() {
230 return publicID;
231 }
232
233
234
235 protected String getSystemID() {
236 return systemID;
237 }
238
239
240
241 protected int getLineNumber() {
242 return lineNumber;
243 }
244
245
246
247 protected int getColumnNumber() {
248 return columnNumber;
249 }
250
251
252
253 protected String getMessage() {
254 return message;
255 }
256
257
258 @Override
259 public String toString() {
260 StringBuilder sb = new StringBuilder(512);
261
262 sb.append(level).append(EOL);
263 sb.append(" Public ID: ").append(publicID).append(EOL);
264 sb.append(" System ID: ").append(systemID).append(EOL);
265 sb.append(" Line number: ").append(lineNumber).append(EOL);
266 sb.append(" Column number: ").append(columnNumber).append(EOL);
267 sb.append(" Message: ").append(message).append(EOL);
268
269 return sb.toString();
270 }
271
272
273 @Override
274 public int hashCode() {
275 final int prime = 31;
276 int result = 1;
277 result = prime * result + columnNumber;
278 result = prime * result + ((level == null) ? 0 : level.hashCode());
279 result = prime * result + lineNumber;
280 result = prime * result + ((message == null) ? 0 : message.hashCode());
281 result = prime * result + ((publicID == null) ? 0 : publicID.hashCode());
282 result = prime * result + ((systemID == null) ? 0 : systemID.hashCode());
283 return result;
284 }
285
286
287 @Override
288 public boolean equals(Object obj) {
289 if (this == obj) {
290 return true;
291 }
292 if (obj == null) {
293 return false;
294 }
295 if (getClass() != obj.getClass()) {
296 return false;
297 }
298 ErrorMessage other = (ErrorMessage) obj;
299 if (columnNumber != other.getColumnNumber()) {
300 return false;
301 }
302 if (level == null) {
303 if (other.getLevel() != null) {
304 return false;
305 }
306 } else if (!level.equals(other.getLevel())) {
307 return false;
308 }
309 if (lineNumber != other.getLineNumber()) {
310 return false;
311 }
312 if (message == null) {
313 if (other.getMessage() != null) {
314 return false;
315 }
316 } else if (!message.equals(other.getMessage())) {
317 return false;
318 }
319 if (publicID == null) {
320 if (other.getPublicID() != null) {
321 return false;
322 }
323 } else if (!publicID.equals(other.getPublicID())) {
324 return false;
325 }
326 if (systemID == null) {
327 if (other.getSystemID() != null) {
328 return false;
329 }
330 } else if (!systemID.equals(other.getSystemID())) {
331 return false;
332 }
333 return true;
334 }
335 }
336
337 private static class MessagesErrorHandler extends DefaultHandler {
338 private final List<ErrorMessage> messages;
339
340 MessagesErrorHandler() {
341 messages = new ArrayList<>(8);
342 }
343
344
345 @Override
346 public void warning(SAXParseException e) throws SAXException {
347 addMessage("Warning", e);
348 }
349
350
351 @Override
352 public void error(SAXParseException e) throws SAXException {
353 addMessage("Error", e);
354 }
355
356
357 @Override
358 public void fatalError(SAXParseException e) throws SAXException {
359 addMessage("Fatal error", e);
360 }
361
362 private void addMessage(String pre, SAXParseException e) {
363 ErrorMessage error = new ErrorMessage(
364 pre, e.getPublicId(), e.getSystemId(), e.getLineNumber(), e.getColumnNumber(), e.getMessage());
365
366 messages.add(error);
367 }
368
369 protected List<ErrorMessage> getMessages() {
370 return Collections.unmodifiableList(messages);
371 }
372
373 protected void clearMessages() {
374 messages.clear();
375 }
376 }
377 }