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