1 package org.apache.maven.doxia.util;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.IOException;
23 import java.io.StringReader;
24
25 import java.util.regex.Matcher;
26 import java.util.regex.Pattern;
27
28 import javax.xml.XMLConstants;
29
30 import org.apache.maven.doxia.logging.Log;
31 import org.apache.maven.doxia.markup.XmlMarkup;
32 import org.apache.maven.doxia.parser.AbstractXmlParser.CachedFileEntityResolver;
33 import org.apache.maven.doxia.parser.ParseException;
34
35 import org.xml.sax.InputSource;
36 import org.xml.sax.SAXException;
37 import org.xml.sax.SAXParseException;
38 import org.xml.sax.XMLReader;
39 import org.xml.sax.helpers.DefaultHandler;
40 import org.xml.sax.helpers.XMLReaderFactory;
41
42
43
44
45
46
47 public class XmlValidator
48 {
49
50
51
52
53 private static final Pattern PATTERN_DOCTYPE = Pattern.compile( ".*" + XmlMarkup.DOCTYPE_START + "([^>]*)>.*" );
54
55
56
57
58
59 private XMLReader xmlReader;
60
61 private Log logger;
62
63
64
65
66
67
68 public XmlValidator( Log log )
69 {
70 this.logger = log;
71 }
72
73
74
75
76
77
78
79 public void validate( String content )
80 throws ParseException
81 {
82 try
83 {
84
85 boolean hasDoctype = false;
86 Matcher matcher = PATTERN_DOCTYPE.matcher( content );
87 if ( matcher.find() )
88 {
89 hasDoctype = true;
90 }
91
92
93 boolean hasXsd = false;
94 matcher = PATTERN_TAG.matcher( content );
95 if ( matcher.find() )
96 {
97 String value = matcher.group( 2 );
98
99 if ( value.contains( XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI ) )
100 {
101 hasXsd = true;
102 }
103 }
104
105
106 getLog().debug( "Validating the content..." );
107 getXmlReader( hasXsd && hasDoctype ).parse( new InputSource( new StringReader( content ) ) );
108 }
109 catch ( IOException | SAXException e )
110 {
111 throw new ParseException( "Error validating the model: " + e.getMessage(), e );
112 }
113 }
114
115
116
117
118
119
120 private XMLReader getXmlReader( boolean hasDtdAndXsd )
121 throws SAXException
122 {
123 if ( xmlReader == null )
124 {
125 MessagesErrorHandler errorHandler = new MessagesErrorHandler( getLog() );
126
127 xmlReader = XMLReaderFactory.createXMLReader();
128 xmlReader.setFeature( "http://xml.org/sax/features/validation", true );
129 xmlReader.setFeature( "http://apache.org/xml/features/validation/schema", true );
130 xmlReader.setErrorHandler( errorHandler );
131 xmlReader.setEntityResolver( new CachedFileEntityResolver() );
132 }
133
134 ( (MessagesErrorHandler) xmlReader.getErrorHandler() ).setHasDtdAndXsd( hasDtdAndXsd );
135
136 return xmlReader;
137 }
138
139 private Log getLog()
140 {
141 return logger;
142 }
143
144
145
146
147 private static class MessagesErrorHandler
148 extends DefaultHandler
149 {
150 private static final int TYPE_UNKNOWN = 0;
151
152 private static final int TYPE_WARNING = 1;
153
154 private static final int TYPE_ERROR = 2;
155
156 private static final int TYPE_FATAL = 3;
157
158 private static final String EOL = XmlMarkup.EOL;
159
160
161 private static final Pattern ELEMENT_TYPE_PATTERN =
162 Pattern.compile( "Element type \".*\" must be declared.", Pattern.DOTALL );
163
164 private final Log log;
165
166 private boolean hasDtdAndXsd;
167
168 private MessagesErrorHandler( Log log )
169 {
170 this.log = log;
171 }
172
173
174
175
176 protected void setHasDtdAndXsd( boolean hasDtdAndXsd )
177 {
178 this.hasDtdAndXsd = hasDtdAndXsd;
179 }
180
181
182 @Override
183 public void warning( SAXParseException e )
184 throws SAXException
185 {
186 processException( TYPE_WARNING, e );
187 }
188
189
190 @Override
191 public void error( SAXParseException e )
192 throws SAXException
193 {
194
195
196
197 if ( !hasDtdAndXsd )
198 {
199 processException( TYPE_ERROR, e );
200 return;
201 }
202
203 Matcher m = ELEMENT_TYPE_PATTERN.matcher( e.getMessage() );
204 if ( !m.find() )
205 {
206 processException( TYPE_ERROR, e );
207 }
208 }
209
210
211 @Override
212 public void fatalError( SAXParseException e )
213 throws SAXException
214 {
215 processException( TYPE_FATAL, e );
216 }
217
218 private void processException( int type, SAXParseException e )
219 throws SAXException
220 {
221 StringBuilder message = new StringBuilder();
222
223 switch ( type )
224 {
225 case TYPE_WARNING:
226 message.append( "Warning:" );
227 break;
228
229 case TYPE_ERROR:
230 message.append( "Error:" );
231 break;
232
233 case TYPE_FATAL:
234 message.append( "Fatal error:" );
235 break;
236
237 case TYPE_UNKNOWN:
238 default:
239 message.append( "Unknown:" );
240 break;
241 }
242
243 message.append( EOL );
244 message.append( " Public ID: " ).append( e.getPublicId() ).append( EOL );
245 message.append( " System ID: " ).append( e.getSystemId() ).append( EOL );
246 message.append( " Line number: " ).append( e.getLineNumber() ).append( EOL );
247 message.append( " Column number: " ).append( e.getColumnNumber() ).append( EOL );
248 message.append( " Message: " ).append( e.getMessage() ).append( EOL );
249
250 final String logMessage = message.toString();
251
252 switch ( type )
253 {
254 case TYPE_WARNING:
255 log.warn( logMessage );
256 break;
257
258 case TYPE_UNKNOWN:
259 case TYPE_ERROR:
260 case TYPE_FATAL:
261 default:
262 throw new SAXException( logMessage );
263 }
264 }
265 }
266 }