001 //$HeadURL: https://sushibar/svn/deegree/base/trunk/resources/eclipse/svn_classfile_header_template.xml $
002 /*----------------------------------------------------------------------------
003 This file is part of deegree, http://deegree.org/
004 Copyright (C) 2001-2009 by:
005 Department of Geography, University of Bonn
006 and
007 lat/lon GmbH
008
009 This library is free software; you can redistribute it and/or modify it under
010 the terms of the GNU Lesser General Public License as published by the Free
011 Software Foundation; either version 2.1 of the License, or (at your option)
012 any later version.
013 This library is distributed in the hope that it will be useful, but WITHOUT
014 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
015 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
016 details.
017 You should have received a copy of the GNU Lesser General Public License
018 along with this library; if not, write to the Free Software Foundation, Inc.,
019 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020
021 Contact information:
022
023 lat/lon GmbH
024 Aennchenstr. 19, 53177 Bonn
025 Germany
026 http://lat-lon.de/
027
028 Department of Geography, University of Bonn
029 Prof. Dr. Klaus Greve
030 Postfach 1147, 53001 Bonn
031 Germany
032 http://www.geographie.uni-bonn.de/deegree/
033
034 e-mail: info@deegree.org
035 ----------------------------------------------------------------------------*/
036
037 package org.deegree.tools.xml;
038
039 import static java.lang.System.out;
040 import static org.deegree.framework.log.LoggerFactory.getLogger;
041 import static org.deegree.framework.xml.DOMPrinter.nodeToString;
042 import static org.deegree.framework.xml.XMLTools.getNode;
043 import static org.deegree.framework.xml.XMLTools.getNodes;
044 import static org.deegree.ogcbase.CommonNamespaces.getNamespaceContext;
045
046 import java.io.BufferedReader;
047 import java.io.File;
048 import java.io.IOException;
049 import java.io.InputStreamReader;
050 import java.net.MalformedURLException;
051 import java.net.URL;
052 import java.util.LinkedList;
053 import java.util.List;
054 import java.util.StringTokenizer;
055
056 import org.deegree.framework.log.ILogger;
057 import org.deegree.framework.util.Pair;
058 import org.deegree.framework.util.StringPair;
059 import org.deegree.framework.xml.NamespaceContext;
060 import org.deegree.framework.xml.XMLFragment;
061 import org.deegree.framework.xml.XMLParsingException;
062 import org.jaxen.JaxenException;
063 import org.jaxen.XPath;
064 import org.jaxen.dom.DOMXPath;
065 import org.w3c.dom.Node;
066 import org.xml.sax.SAXException;
067
068 /**
069 * <code>SimpleValidator</code> is a simple xpath based "validator". It can be used to crudely
070 * check XML documents for existing nodes. A sample rule file can be found right beneath in this
071 * package.
072 *
073 * @author <a href="mailto:schmitz@lat-lon.de">Andreas Schmitz</a>
074 * @author last edited by: $Author:$
075 *
076 * @version $Revision:$, $Date:$
077 */
078 public class SimpleValidator {
079
080 static final NamespaceContext nsContext = getNamespaceContext();
081
082 private static final ILogger LOG = getLogger( SimpleValidator.class );
083
084 private LinkedList<Rule> rules;
085
086 /**
087 * Initializes from configuration URL. File should be encoded in UTF-8.
088 *
089 * @param config
090 * @throws IOException
091 */
092 public SimpleValidator( URL config ) throws IOException {
093 rules = new LinkedList<Rule>();
094
095 BufferedReader in = new BufferedReader( new InputStreamReader( config.openStream() ) );
096
097 StringBuffer buf = new StringBuffer( 65536 );
098 String s;
099 while ( ( s = in.readLine() ) != null ) {
100 if ( s.startsWith( "#" ) ) {
101 continue;
102 }
103 buf.append( s ).append( " " );
104 }
105
106 in.close();
107
108 StringTokenizer tok = new StringTokenizer( buf.toString() );
109 String cur = tok.nextToken();
110 while ( tok.hasMoreTokens() ) {
111 Pair<Rule, String> p = parseRule( tok, cur );
112 cur = p.second;
113 rules.add( p.first );
114 }
115
116 LOG.logDebug( "Parsed rule file with " + rules.size() + " rules." );
117 }
118
119 private static Pair<Rule, String> parseRule( StringTokenizer tok, String first )
120 throws IOException {
121 String id = first;
122 String s = tok.nextToken();
123
124 if ( s.equalsIgnoreCase( "if" ) ) {
125 String test = tok.nextToken();
126 if ( !tok.nextToken().equalsIgnoreCase( "then" ) ) {
127 throw new IOException( "Missing 'then' after " + test + "." );
128 }
129
130 Pair<Rule, String> then = parseRule( tok, tok.nextToken() );
131
132 return new Pair<Rule, String>( new Rule( id, test, then.first ), then.second );
133 }
134
135 if ( s.equalsIgnoreCase( "oneof" ) ) {
136 String base = tok.nextToken();
137 List<String> choices = new LinkedList<String>();
138
139 s = tok.nextToken();
140 while ( tok.hasMoreTokens() && s.equalsIgnoreCase( "choice" ) ) {
141 choices.add( tok.nextToken() );
142 if ( tok.hasMoreTokens() ) {
143 s = tok.nextToken();
144 }
145 }
146
147 return new Pair<Rule, String>( new Rule( id, base, choices ), s );
148 }
149
150 boolean isBoolean = false;
151
152 if ( s.equalsIgnoreCase( "istrue" ) ) {
153 s = tok.nextToken();
154 // if ( !s.startsWith( "boolean(" ) ) {
155 // s = "boolean(" + s + ")";
156 // }
157 isBoolean = true;
158 }
159
160 String next = tok.hasMoreTokens() ? tok.nextToken() : null;
161 return new Pair<Rule, String>( new Rule( id, s, isBoolean ), next );
162 }
163
164 /**
165 * @param n
166 * @return a list of errors. A pair will include the id of the failed rule, and the context node
167 * as string (if applicable) or null (if not).
168 */
169 public LinkedList<StringPair> validate( Node n ) {
170 LinkedList<StringPair> list = new LinkedList<StringPair>();
171
172 for ( Rule r : rules ) {
173 if ( !r.eval( n ) ) {
174 list.addAll( r.errors );
175 }
176 }
177
178 return list;
179 }
180
181 /**
182 * @param args
183 */
184 public static void main( String[] args ) {
185 if ( args.length < 2 ) {
186 out.println( "Usage:" );
187 out.println( "org.deegree.tools.xml.SimpleValidator <rulesfile> <xmlfiletovalidate> [-v]" );
188 return;
189 }
190
191 boolean verbose = args.length > 2 && args[2].equals( "-v" );
192
193 try {
194 XMLFragment doc = new XMLFragment( new File( args[1] ) );
195 SimpleValidator val = new SimpleValidator( new File( args[0] ).toURI().toURL() );
196
197 LinkedList<StringPair> errors = val.validate( doc.getRootElement() );
198 if ( errors.size() > 0 ) {
199 out.println( "Errors:" );
200 for ( StringPair p : errors ) {
201 if ( verbose ) {
202 out.println( p );
203 } else {
204 out.print( p.first );
205 if ( errors.indexOf( p ) != errors.size() - 1 ) {
206 out.print( ", " );
207 }
208 }
209 }
210 if ( !verbose ) {
211 out.println();
212 }
213 } else {
214 out.println( "All rules passed." );
215 }
216 } catch ( MalformedURLException e ) {
217 out.println( "Error: one of the files is not a valid filename." );
218 out.println( "Usage:" );
219 out.println( "org.deegree.tools.xml.SimpleValidator <rulesfile> <xmlfiletovalidate> [-v]" );
220 } catch ( IOException e ) {
221 out.println( "Error: second file cannot be read." );
222 out.println( "Usage:" );
223 out.println( "org.deegree.tools.xml.SimpleValidator <rulesfile> <xmlfiletovalidate> [-v]" );
224 } catch ( SAXException e ) {
225 out.println( "Error: second file is not parsable XML." );
226 out.println( "Usage:" );
227 out.println( "org.deegree.tools.xml.SimpleValidator <rulesfile> <xmlfiletovalidate> [-v]" );
228 }
229 }
230
231 private static class Rule {
232
233 String id;
234
235 List<StringPair> errors;
236
237 private String xpath;
238
239 private Rule then;
240
241 private List<String> choices;
242
243 private boolean isBoolean;
244
245 Rule( String id, String xpath ) {
246 this.xpath = xpath;
247 this.id = id;
248 errors = new LinkedList<StringPair>();
249 }
250
251 Rule( String id, String xpath, Rule then ) {
252 this( id, xpath );
253 this.then = then;
254 }
255
256 Rule( String id, String base, List<String> choices ) {
257 this( id, base );
258 this.choices = choices;
259 }
260
261 Rule( String id, String xpath, boolean isBoolean ) {
262 this( id, xpath );
263 this.isBoolean = isBoolean;
264 }
265
266 boolean eval( Node n ) {
267 try {
268
269 if ( isBoolean ) {
270 try {
271 XPath xpath = new DOMXPath( this.xpath );
272 xpath.setNamespaceContext( nsContext );
273 boolean res = xpath.booleanValueOf( n );
274 if ( !res ) {
275 errors.add( new StringPair( id, "The IsTrue expression evaluated to false." ) );
276 }
277 return res;
278 } catch ( JaxenException e ) {
279 errors.add( new StringPair( id, "The xpath expression contained an error: "
280 + e.getLocalizedMessage() ) );
281 return false;
282 }
283 }
284
285 if ( choices != null ) {
286 boolean isOk = false;
287
288 Node baseNode = getNode( n, xpath, nsContext );
289
290 if ( baseNode == null ) {
291 errors.add( new StringPair( id, "(node not found)" ) );
292 return false;
293 }
294
295 for ( String x : choices ) {
296 Node tmp = getNode( baseNode, x, nsContext );
297 isOk = isOk || tmp != null;
298 }
299
300 if ( !isOk ) {
301 errors.add( new StringPair( id, nodeToString( baseNode, "UTF-8" ) ) );
302 }
303
304 return isOk;
305 }
306
307 List<Node> tmps = getNodes( n, xpath, nsContext );
308
309 if ( then == null ) {
310 if ( tmps.size() == 0 ) {
311 errors.add( new StringPair( id, null ) );
312 }
313 return tmps.size() != 0;
314 }
315
316 if ( tmps.size() == 0 ) {
317 return true;
318 }
319
320 boolean res = true;
321
322 for ( Node tmp : tmps ) {
323 if ( then.eval( tmp ) ) {
324 continue;
325 }
326
327 res = false;
328
329 errors.add( new StringPair( then.id, nodeToString( tmp, "UTF-8" ) ) );
330 }
331
332 return res;
333 } catch ( XMLParsingException e ) {
334 return false;
335 }
336 }
337
338 }
339
340 }